refactor(GUI): deangular os-dialog and error module (#1546)

We remove the Angular dependency from `lib/gui/os/dialog` and
`lib/gui/modules/error` which is also now renamed to
`lib/gui/modules/exception`.

Changelog-Entry: Deangular the os-dialog and error modules
This commit is contained in:
Benedict Aas 2017-07-11 22:21:48 +01:00 committed by Juan Cruz Viotti
parent e52ef501bb
commit fc6c5bf585
12 changed files with 203 additions and 253 deletions

View File

@ -43,15 +43,14 @@ const updateNotifier = require('./components/update-notifier');
const availableDrives = require('./models/available-drives');
const selectionState = require('./models/selection-state');
const driveScanner = require('./modules/drive-scanner');
const osDialog = require('./os/dialog');
const exceptionReporter = require('./modules/exception-reporter');
const app = angular.module('Etcher', [
require('angular-ui-router'),
require('angular-ui-bootstrap'),
require('angular-if-state'),
// Etcher modules
require('./modules/error'),
// Components
require('./components/svg-icon'),
require('./components/warning-modal/warning-modal'),
@ -65,7 +64,6 @@ const app = angular.module('Etcher', [
// OS
require('./os/open-external/open-external'),
require('./os/dropzone/dropzone'),
require('./os/dialog/dialog'),
// Utils
require('./utils/manifest-bind/manifest-bind')
@ -85,7 +83,7 @@ app.run(() => {
].join('\n'));
});
app.run((ErrorService) => {
app.run(() => {
analytics.logEvent('Application start');
settings.load();
@ -148,7 +146,7 @@ app.run((ErrorService) => {
return updateNotifier.notify(latestVersion, {
allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION
});
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
});
app.run(() => {
@ -176,7 +174,7 @@ app.run(() => {
});
});
app.run(($timeout, ErrorService) => {
app.run(($timeout) => {
driveScanner.on('drives', (drives) => {
// Safely trigger a digest cycle.
@ -196,13 +194,13 @@ app.run(($timeout, ErrorService) => {
// spamming our error reporting service.
driveScanner.stop();
return ErrorService.reportException(error);
return exceptionReporter.report(error);
});
driveScanner.start();
});
app.run(($window, WarningModalService, ErrorService, OSDialogService) => {
app.run(($window) => {
let popupExists = false;
$window.addEventListener('beforeunload', (event) => {
@ -221,7 +219,7 @@ app.run(($window, WarningModalService, ErrorService, OSDialogService) => {
analytics.logEvent('Close attempt while flashing');
OSDialogService.showWarning({
osDialog.showWarning({
confirmationLabel: 'Yes, quit',
rejectionLabel: 'Cancel',
title: 'Are you sure you want to close Etcher?',
@ -240,7 +238,7 @@ app.run(($window, WarningModalService, ErrorService, OSDialogService) => {
analytics.logEvent('Close rejected while flashing');
popupExists = false;
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
});
});
@ -264,10 +262,9 @@ app.config(($urlRouterProvider) => {
});
app.config(($provide) => {
$provide.decorator('$exceptionHandler', ($delegate, $injector) => {
$provide.decorator('$exceptionHandler', ($delegate) => {
return (exception, cause) => {
const ErrorService = $injector.get('ErrorService');
ErrorService.reportException(exception);
exceptionReporter.report(exception);
$delegate(exception, cause);
};
});

View File

@ -1,55 +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.
*/
'use strict';
/**
* @module Etcher.Modules.Error
*/
const angular = require('angular');
const _ = require('lodash');
const analytics = require('../modules/analytics');
const MODULE_NAME = 'Etcher.Modules.Error';
const error = angular.module(MODULE_NAME, [
require('../os/dialog/dialog')
]);
error.service('ErrorService', function(OSDialogService) {
/**
* @summary Report an exception
* @function
* @public
*
* @param {Error} exception - exception
*
* @example
* ErrorService.reportException(new Error('Something happened'));
*/
this.reportException = (exception) => {
if (_.isUndefined(exception)) {
return;
}
OSDialogService.showError(exception);
analytics.logException(exception);
};
});
module.exports = MODULE_NAME;

View File

@ -16,17 +16,25 @@
'use strict';
const _ = require('lodash');
const analytics = require('../modules/analytics');
const osDialog = require('../os/dialog');
/**
* The purpose of this module is to provide an easy way
* to interact with OS dialogs.
* @summary Report an exception
* @function
* @public
*
* @module Etcher.OS.Dialog
* @param {Error} exception - exception
*
* @example
* exceptionReporter.report(new Error('Something happened'));
*/
exports.report = (exception) => {
if (_.isUndefined(exception)) {
return;
}
const angular = require('angular');
const MODULE_NAME = 'Etcher.OS.Dialog';
const OSDialog = angular.module(MODULE_NAME, []);
OSDialog.service('OSDialogService', require('./services/dialog'));
module.exports = MODULE_NAME;
osDialog.showError(exception);
analytics.logException(exception);
};

149
lib/gui/os/dialog.js Normal file
View File

@ -0,0 +1,149 @@
/*
* 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');
const electron = require('electron');
const Bluebird = require('bluebird');
const errors = require('../../shared/errors');
const supportedFormats = require('../../shared/supported-formats');
/**
* @summary Current renderer BrowserWindow instance
* @type {Object}
* @private
*/
const currentWindow = electron.remote.getCurrentWindow();
/**
* @summary Open an image selection dialog
* @function
* @public
*
* @description
* Notice that by image, we mean *.img/*.iso/*.zip/etc files.
*
* @fulfil {Object} - selected image
* @returns {Promise};
*
* @example
* osDialog.selectImage().then((image) => {
* console.log('The selected image is', image.path);
* });
*/
exports.selectImage = () => {
return new Bluebird((resolve) => {
electron.remote.dialog.showOpenDialog(currentWindow, {
// This variable is set when running in GNU/Linux from
// inside an AppImage, and represents the working directory
// from where the AppImage was run (which might not be the
// place where the AppImage is located). `OWD` stands for
// "Original Working Directory".
//
// See: https://github.com/probonopd/AppImageKit/commit/1569d6f8540aa6c2c618dbdb5d6fcbf0003952b7
defaultPath: process.env.OWD,
properties: [
'openFile'
],
filters: [
{
name: 'OS Images',
extensions: _.sortBy(supportedFormats.getAllExtensions())
}
]
}, (files) => {
// `_.first` is smart enough to not throw and return `undefined`
// if we pass it an `undefined` value (e.g: when the selection
// dialog was cancelled).
return resolve(_.first(files));
});
});
};
/**
* @summary Open a warning dialog
* @function
* @public
*
* @param {Object} options - options
* @param {String} options.title - dialog title
* @param {String} options.description - dialog description
* @param {String} [options.confirmationLabel="OK"] - confirmation label
* @param {String} [options.rejectionLabel="Cancel"] - rejection label
* @fulfil {Boolean} - whether the dialog was confirmed or not
* @returns {Promise};
*
* @example
* osDialog.showWarning({
* title: 'This is a warning',
* description: 'Are you sure you want to continue?',
* confirmationLabel: 'Yes, continue',
* rejectionLabel: 'Cancel'
* }).then((confirmed) => {
* if (confirmed) {
* console.log('The dialog was confirmed');
* }
* });
*/
exports.showWarning = (options) => {
_.defaults(options, {
confirmationLabel: 'OK',
rejectionLabel: 'Cancel'
});
const BUTTONS = [
options.confirmationLabel,
options.rejectionLabel
];
const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, options.confirmationLabel);
const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel);
return new Bluebird((resolve) => {
electron.remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
buttons: BUTTONS,
defaultId: BUTTON_REJECTION_INDEX,
cancelId: BUTTON_REJECTION_INDEX,
title: 'Attention',
message: options.title,
detail: options.description
}, (response) => {
return resolve(response === BUTTON_CONFIRMATION_INDEX);
});
});
};
/**
* @summary Show error dialog for an Error instance
* @function
* @public
*
* @param {Error} error - error
*
* @example
* osDialog.showError(new Error('Foo Bar'));
*/
exports.showError = (error) => {
const title = errors.getTitle(error);
const message = errors.getDescription(error);
electron.remote.dialog.showErrorBox(title, message);
};

View File

@ -1,152 +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.
*/
'use strict';
const _ = require('lodash');
const electron = require('electron');
const errors = require('../../../../shared/errors');
const supportedFormats = require('../../../../shared/supported-formats');
module.exports = function($q) {
/**
* @summary Current renderer BrowserWindow instance
* @type {Object}
* @private
*/
const currentWindow = electron.remote.getCurrentWindow();
/**
* @summary Open an image selection dialog
* @function
* @public
*
* @description
* Notice that by image, we mean *.img/*.iso/*.zip/etc files.
*
* @fulfil {Object} - selected image
* @returns {Promise};
*
* @example
* OSDialogService.selectImage().then((image) => {
* console.log('The selected image is', image.path);
* });
*/
this.selectImage = () => {
return $q((resolve) => {
electron.remote.dialog.showOpenDialog(currentWindow, {
// This variable is set when running in GNU/Linux from
// inside an AppImage, and represents the working directory
// from where the AppImage was run (which might not be the
// place where the AppImage is located). `OWD` stands for
// "Original Working Directory".
//
// See: https://github.com/probonopd/AppImageKit/commit/1569d6f8540aa6c2c618dbdb5d6fcbf0003952b7
defaultPath: process.env.OWD,
properties: [
'openFile'
],
filters: [
{
name: 'OS Images',
extensions: _.sortBy(supportedFormats.getAllExtensions())
}
]
}, (files) => {
// `_.first` is smart enough to not throw and return `undefined`
// if we pass it an `undefined` value (e.g: when the selection
// dialog was cancelled).
return resolve(_.first(files));
});
});
};
/**
* @summary Open a warning dialog
* @function
* @public
*
* @param {Object} options - options
* @param {String} options.title - dialog title
* @param {String} options.description - dialog description
* @param {String} [options.confirmationLabel="OK"] - confirmation label
* @param {String} [options.rejectionLabel="Cancel"] - rejection label
* @fulfil {Boolean} - whether the dialog was confirmed or not
* @returns {Promise};
*
* @example
* OSDialogService.showWarning({
* title: 'This is a warning',
* description: 'Are you sure you want to continue?',
* confirmationLabel: 'Yes, continue',
* rejectionLabel: 'Cancel'
* }).then((confirmed) => {
* if (confirmed) {
* console.log('The dialog was confirmed');
* }
* });
*/
this.showWarning = (options) => {
_.defaults(options, {
confirmationLabel: 'OK',
rejectionLabel: 'Cancel'
});
const BUTTONS = [
options.confirmationLabel,
options.rejectionLabel
];
const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, options.confirmationLabel);
const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel);
return $q((resolve) => {
electron.remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
buttons: BUTTONS,
defaultId: BUTTON_REJECTION_INDEX,
cancelId: BUTTON_REJECTION_INDEX,
title: 'Attention',
message: options.title,
detail: options.description
}, (response) => {
return resolve(response === BUTTON_CONFIRMATION_INDEX);
});
});
};
/**
* @summary Show error dialog for an Error instance
* @function
* @public
*
* @param {Error} error - error
*
* @example
* OSDialogService.showError(new Error('Foo Bar'));
*/
this.showError = (error) => {
const title = errors.getTitle(error);
const message = errors.getDescription(error);
electron.remote.dialog.showErrorBox(title, message);
};
};

View File

@ -19,8 +19,9 @@
const settings = require('../../../models/settings');
const selectionState = require('../../../models/selection-state');
const analytics = require('../../../modules/analytics');
const exceptionReporter = require('../../../modules/exception-reporter');
module.exports = function(ErrorService, DriveSelectorService) {
module.exports = function(DriveSelectorService) {
/**
* @summary Open drive selector
@ -42,7 +43,7 @@ module.exports = function(ErrorService, DriveSelectorService) {
device: drive.device,
unsafeMode: settings.get('unsafeMode')
});
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
};
/**

View File

@ -27,8 +27,7 @@ const path = require('path');
module.exports = function(
$state,
ImageWriterService,
FlashErrorModalService,
ErrorService
FlashErrorModalService
) {
/**
@ -96,7 +95,7 @@ module.exports = function(
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive());
} else {
FlashErrorModalService.show(messages.error.genericFlashError());
ErrorService.reportException(error);
error.reportException(error);
}
})

View File

@ -25,10 +25,11 @@ const imageStream = require('../../../../image-stream');
const supportedFormats = require('../../../../shared/supported-formats');
const analytics = require('../../../modules/analytics');
const selectionState = require('../../../models/selection-state');
const osDialog = require('../../../os/dialog');
const exceptionReporter = require('../../../modules/exception-reporter');
module.exports = function(
ErrorService,
OSDialogService,
$timeout,
WarningModalService
) {
@ -63,7 +64,7 @@ module.exports = function(
* @param {Object} image - image
*
* @example
* OSDialogService.selectImage()
* osDialogService.selectImage()
* .then(ImageSelectionController.selectImage);
*/
this.selectImage = (image) => {
@ -75,7 +76,7 @@ module.exports = function(
})
});
OSDialogService.showError(invalidImageError);
osDialog.showError(invalidImageError);
analytics.logEvent('Invalid image', image);
return;
}
@ -117,7 +118,7 @@ module.exports = function(
image.bmap = Boolean(image.bmap);
return analytics.logEvent('Select image', image);
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
};
/**
@ -132,7 +133,11 @@ module.exports = function(
*/
this.selectImageByPath = (imagePath) => {
imageStream.getImageMetadata(imagePath)
.then(this.selectImage)
.then((imageMetadata) => {
$timeout(() => {
this.selectImage(imageMetadata);
});
})
.catch((error) => {
const imageError = errors.createUserError({
title: 'Error opening image',
@ -142,7 +147,7 @@ module.exports = function(
})
});
OSDialogService.showError(imageError);
osDialog.showError(imageError);
analytics.logException(error);
});
};
@ -158,7 +163,7 @@ module.exports = function(
this.openImageSelector = () => {
analytics.logEvent('Open image selector');
OSDialogService.selectImage().then((imagePath) => {
osDialog.selectImage().then((imagePath) => {
// Avoid analytics and selection state changes
// if no file was resolved from the dialog.
@ -168,7 +173,7 @@ module.exports = function(
}
this.selectImageByPath(imagePath);
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
};
/**

View File

@ -19,12 +19,12 @@
const settings = require('../../../models/settings');
const flashState = require('../../../models/flash-state');
const analytics = require('../../../modules/analytics');
const exceptionReporter = require('../../../modules/exception-reporter');
const availableDrives = require('../../../models/available-drives');
const selectionState = require('../../../models/selection-state');
module.exports = function(
TooltipModalService,
ErrorService,
OSOpenExternalService
) {
@ -85,7 +85,7 @@ module.exports = function(
return TooltipModalService.show({
title: 'Image File Name',
message: selectionState.getImagePath()
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
};
};

View File

@ -39,12 +39,10 @@ const MainPage = angular.module(MODULE_NAME, [
require('../../components/flash-error-modal/flash-error-modal'),
require('../../components/progress-button/progress-button'),
require('../../os/dialog/dialog'),
require('../../os/open-external/open-external'),
require('../../os/dropzone/dropzone'),
require('../../modules/image-writer'),
require('../../modules/error'),
require('../../utils/byte-size/byte-size')
]);

View File

@ -20,8 +20,9 @@ const os = require('os');
const _ = require('lodash');
const settings = require('../../../models/settings');
const analytics = require('../../../modules/analytics');
const exceptionReporter = require('../../../modules/exception-reporter');
module.exports = function(WarningModalService, ErrorService) {
module.exports = function(WarningModalService) {
/**
* @summary Client platform
@ -100,7 +101,7 @@ module.exports = function(WarningModalService, ErrorService) {
this.model.set(setting, true);
this.refreshSettings();
}
}).catch(ErrorService.reportException);
}).catch(exceptionReporter.report);
};
};

View File

@ -24,8 +24,7 @@ const angular = require('angular');
const MODULE_NAME = 'Etcher.Pages.Settings';
const SettingsPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../components/warning-modal/warning-modal'),
require('../../modules/error')
require('../../components/warning-modal/warning-modal')
]);
SettingsPage.controller('SettingsController', require('./controllers/settings'));