Remove remaining angular

Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-01-03 19:07:21 +01:00 committed by Lorenzo Alberto Maria Ambrosi
parent 26d0e46367
commit d5eb679cf0
13 changed files with 387 additions and 627 deletions

View File

@ -20,12 +20,6 @@
'use strict'
/* eslint-disable no-var */
var angular = require('angular')
/* eslint-enable no-var */
const electron = require('electron')
const sdk = require('etcher-sdk')
const _ = require('lodash')
@ -79,68 +73,51 @@ store.dispatch({
const applicationSessionUuid = store.getState().toJS().applicationSessionUuid
const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid
const app = angular.module('Etcher', [
require('angular-ui-router'),
console.log([
' _____ _ _',
'| ___| | | |',
'| |__ | |_ ___| |__ ___ _ __',
'| __|| __/ __| \'_ \\ / _ \\ \'__|',
'| |___| || (__| | | | __/ |',
'\\____/ \\__\\___|_| |_|\\___|_|',
'',
'Interested in joining the Etcher team?',
'Drop us a line at join+etcher@balena.io',
'',
`Version = ${packageJSON.version}, Type = ${packageJSON.packageType}`
].join('\n'))
// Components
require('./components/safe-webview').MODULE_NAME,
const currentVersion = packageJSON.version
// Pages
require('./pages/main/main.ts').MODULE_NAME,
require('./components/finish/index.ts').MODULE_NAME
])
app.run(() => {
console.log([
' _____ _ _',
'| ___| | | |',
'| |__ | |_ ___| |__ ___ _ __',
'| __|| __/ __| \'_ \\ / _ \\ \'__|',
'| |___| || (__| | | | __/ |',
'\\____/ \\__\\___|_| |_|\\___|_|',
'',
'Interested in joining the Etcher team?',
'Drop us a line at join+etcher@balena.io',
'',
`Version = ${packageJSON.version}, Type = ${packageJSON.packageType}`
].join('\n'))
analytics.logEvent('Application start', {
packageType: packageJSON.packageType,
version: currentVersion,
applicationSessionUuid
})
app.run(() => {
const currentVersion = packageJSON.version
store.observe(() => {
if (!flashState.isFlashing()) {
return
}
analytics.logEvent('Application start', {
packageType: packageJSON.packageType,
version: currentVersion,
applicationSessionUuid
})
})
const currentFlashState = flashState.getFlashState()
const stateType = !currentFlashState.flashing && currentFlashState.verifying
? `Verifying ${currentFlashState.verifying}`
: `Flashing ${currentFlashState.flashing}`
app.run(() => {
store.observe(() => {
if (!flashState.isFlashing()) {
return
}
// NOTE: There is usually a short time period between the `isFlashing()`
// property being set, and the flashing actually starting, which
// might cause some non-sense flashing state logs including
// `undefined` values.
analytics.logDebug(
`${stateType} devices, ` +
`${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s ` +
`(total ${currentFlashState.totalSpeed} MB/s) ` +
`eta in ${currentFlashState.eta}s ` +
`with ${currentFlashState.failed} failed devices`
)
const currentFlashState = flashState.getFlashState()
const stateType = !currentFlashState.flashing && currentFlashState.verifying
? `Verifying ${currentFlashState.verifying}`
: `Flashing ${currentFlashState.flashing}`
// NOTE: There is usually a short time period between the `isFlashing()`
// property being set, and the flashing actually starting, which
// might cause some non-sense flashing state logs including
// `undefined` values.
analytics.logDebug(
`${stateType} devices, ` +
`${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s ` +
`(total ${currentFlashState.totalSpeed} MB/s) ` +
`eta in ${currentFlashState.eta}s ` +
`with ${currentFlashState.failed} failed devices`
)
windowProgress.set(currentFlashState)
})
windowProgress.set(currentFlashState)
})
/**
@ -197,242 +174,171 @@ const COMPUTE_MODULE_DESCRIPTIONS = {
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3'
}
app.run(($timeout) => {
const BLACKLISTED_DRIVES = settings.has('driveBlacklist')
? settings.get('driveBlacklist').split(',')
: []
const BLACKLISTED_DRIVES = settings.has('driveBlacklist')
? settings.get('driveBlacklist').split(',')
: []
// eslint-disable-next-line require-jsdoc
const driveIsAllowed = (drive) => {
return !(
BLACKLISTED_DRIVES.includes(drive.devicePath) ||
BLACKLISTED_DRIVES.includes(drive.device) ||
BLACKLISTED_DRIVES.includes(drive.raw)
)
}
// eslint-disable-next-line require-jsdoc
const driveIsAllowed = (drive) => {
return !(
BLACKLISTED_DRIVES.includes(drive.devicePath) ||
BLACKLISTED_DRIVES.includes(drive.device) ||
BLACKLISTED_DRIVES.includes(drive.raw)
)
}
// eslint-disable-next-line require-jsdoc,consistent-return
const prepareDrive = (drive) => {
if (drive instanceof sdk.sourceDestination.BlockDevice) {
return drive.drive
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
// This is a workaround etcher expecting a device string and a size
drive.device = drive.usbDevice.portId
drive.size = null
drive.progress = 0
drive.disabled = true
drive.on('progress', (progress) => {
updateDriveProgress(drive, progress)
})
return drive
} else if (drive instanceof sdk.sourceDestination.DriverlessDevice) {
const description = COMPUTE_MODULE_DESCRIPTIONS[drive.deviceDescriptor.idProduct] || 'Compute Module'
return {
device: `${usbIdToString(drive.deviceDescriptor.idVendor)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`,
displayName: 'Missing drivers',
description,
mountpoints: [],
isReadOnly: false,
isSystem: false,
disabled: true,
icon: 'warning',
size: null,
link: 'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md',
linkCTA: 'Install',
linkTitle: 'Install missing drivers',
linkMessage: [
'Would you like to download the necessary drivers from the Raspberry Pi Foundation?',
'This will open your browser.\n\n',
'Once opened, download and run the installer from the "Windows Installer" section to install the drivers.'
].join(' ')
}
// eslint-disable-next-line require-jsdoc,consistent-return
const prepareDrive = (drive) => {
if (drive instanceof sdk.sourceDestination.BlockDevice) {
return drive.drive
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
// This is a workaround etcher expecting a device string and a size
drive.device = drive.usbDevice.portId
drive.size = null
drive.progress = 0
drive.disabled = true
drive.on('progress', (progress) => {
updateDriveProgress(drive, progress)
})
return drive
} else if (drive instanceof sdk.sourceDestination.DriverlessDevice) {
const description = COMPUTE_MODULE_DESCRIPTIONS[drive.deviceDescriptor.idProduct] || 'Compute Module'
return {
device: `${usbIdToString(drive.deviceDescriptor.idVendor)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`,
displayName: 'Missing drivers',
description,
mountpoints: [],
isReadOnly: false,
isSystem: false,
disabled: true,
icon: 'warning',
size: null,
link: 'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md',
linkCTA: 'Install',
linkTitle: 'Install missing drivers',
linkMessage: [
'Would you like to download the necessary drivers from the Raspberry Pi Foundation?',
'This will open your browser.\n\n',
'Once opened, download and run the installer from the "Windows Installer" section to install the drivers.'
].join(' ')
}
}
}
// eslint-disable-next-line require-jsdoc
const setDrives = (drives) => {
availableDrives.setDrives(_.values(drives))
// eslint-disable-next-line require-jsdoc
const setDrives = (drives) => {
availableDrives.setDrives(_.values(drives))
}
// Safely trigger a digest cycle.
// In some cases, AngularJS doesn't acknowledge that the
// available drives list has changed, and incorrectly
// keeps asking the user to "Connect a drive".
$timeout()
// eslint-disable-next-line require-jsdoc
const getDrives = () => {
return _.keyBy(availableDrives.getDrives() || [], 'device')
}
// eslint-disable-next-line require-jsdoc
const addDrive = (drive) => {
const preparedDrive = prepareDrive(drive)
if (!driveIsAllowed(preparedDrive)) {
return
}
const drives = getDrives()
drives[preparedDrive.device] = preparedDrive
setDrives(drives)
}
// eslint-disable-next-line require-jsdoc
const getDrives = () => {
return _.keyBy(availableDrives.getDrives() || [], 'device')
}
// eslint-disable-next-line require-jsdoc
const removeDrive = (drive) => {
const preparedDrive = prepareDrive(drive)
const drives = getDrives()
// eslint-disable-next-line prefer-reflect
delete drives[preparedDrive.device]
setDrives(drives)
}
// eslint-disable-next-line require-jsdoc
const addDrive = (drive) => {
const preparedDrive = prepareDrive(drive)
if (!driveIsAllowed(preparedDrive)) {
return
}
const drives = getDrives()
drives[preparedDrive.device] = preparedDrive
// eslint-disable-next-line require-jsdoc
const updateDriveProgress = (drive, progress) => {
const drives = getDrives()
const driveInMap = drives[drive.device]
if (driveInMap) {
driveInMap.progress = progress
setDrives(drives)
}
}
// eslint-disable-next-line require-jsdoc
const removeDrive = (drive) => {
const preparedDrive = prepareDrive(drive)
const drives = getDrives()
// eslint-disable-next-line prefer-reflect
delete drives[preparedDrive.device]
setDrives(drives)
}
driveScanner.on('attach', addDrive)
driveScanner.on('detach', removeDrive)
// eslint-disable-next-line require-jsdoc
const updateDriveProgress = (drive, progress) => {
const drives = getDrives()
const driveInMap = drives[drive.device]
if (driveInMap) {
driveInMap.progress = progress
setDrives(drives)
}
}
driveScanner.on('error', (error) => {
// Stop the drive scanning loop in case of errors,
// otherwise we risk presenting the same error over
// and over again to the user, while also heavily
// spamming our error reporting service.
driveScanner.stop()
driveScanner.on('attach', addDrive)
driveScanner.on('detach', removeDrive)
driveScanner.on('error', (error) => {
// Stop the drive scanning loop in case of errors,
// otherwise we risk presenting the same error over
// and over again to the user, while also heavily
// spamming our error reporting service.
driveScanner.stop()
return exceptionReporter.report(error)
})
driveScanner.start()
return exceptionReporter.report(error)
})
app.run(($window) => {
let popupExists = false
driveScanner.start()
$window.addEventListener('beforeunload', (event) => {
if (!flashState.isFlashing() || popupExists) {
analytics.logEvent('Close application', {
isFlashing: flashState.isFlashing(),
applicationSessionUuid
})
return
}
let popupExists = false
// Don't close window while flashing
event.returnValue = false
// Don't open any more popups
popupExists = true
analytics.logEvent('Close attempt while flashing', { applicationSessionUuid, flashingWorkflowUuid })
osDialog.showWarning({
confirmationLabel: 'Yes, quit',
rejectionLabel: 'Cancel',
title: 'Are you sure you want to close Etcher?',
description: messages.warning.exitWhileFlashing()
}).then((confirmed) => {
if (confirmed) {
analytics.logEvent('Close confirmed while flashing', {
flashInstanceUuid: flashState.getFlashUuid(),
applicationSessionUuid,
flashingWorkflowUuid
})
// This circumvents the 'beforeunload' event unlike
// electron.remote.app.quit() which does not.
electron.remote.process.exit(EXIT_CODES.SUCCESS)
}
analytics.logEvent('Close rejected while flashing', { applicationSessionUuid, flashingWorkflowUuid })
popupExists = false
}).catch(exceptionReporter.report)
})
/**
* @summary Helper fn for events
* @function
* @private
* @example
* window.addEventListener('click', extendLock)
*/
const extendLock = () => {
updateLock.extend()
}
$window.addEventListener('click', extendLock)
$window.addEventListener('touchstart', extendLock)
// Initial update lock acquisition
extendLock()
})
app.run(($rootScope) => {
$rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => {
// Ignore first navigation
if (!fromState.name) {
return
}
analytics.logEvent('Navigate', {
to: toState.name,
from: fromState.name,
window.addEventListener('beforeunload', (event) => {
if (!flashState.isFlashing() || popupExists) {
analytics.logEvent('Close application', {
isFlashing: flashState.isFlashing(),
applicationSessionUuid
})
})
})
return
}
app.config(($urlRouterProvider) => {
$urlRouterProvider.otherwise('/main')
})
// Don't close window while flashing
event.returnValue = false
app.config(($provide) => {
$provide.decorator('$exceptionHandler', ($delegate) => {
return (exception, cause) => {
exceptionReporter.report(exception)
$delegate(exception, cause)
// Don't open any more popups
popupExists = true
analytics.logEvent('Close attempt while flashing', { applicationSessionUuid, flashingWorkflowUuid })
osDialog.showWarning({
confirmationLabel: 'Yes, quit',
rejectionLabel: 'Cancel',
title: 'Are you sure you want to close Etcher?',
description: messages.warning.exitWhileFlashing()
}).then((confirmed) => {
if (confirmed) {
analytics.logEvent('Close confirmed while flashing', {
flashInstanceUuid: flashState.getFlashUuid(),
applicationSessionUuid,
flashingWorkflowUuid
})
// This circumvents the 'beforeunload' event unlike
// electron.remote.app.quit() which does not.
electron.remote.process.exit(EXIT_CODES.SUCCESS)
}
})
})
app.config(($locationProvider) => {
// NOTE(Shou): this seems to invoke a minor perf decrease when set to true
$locationProvider.html5Mode({
rewriteLinks: false
})
})
app.controller('StateController', function ($rootScope, $scope) {
const unregisterStateChange = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => {
this.currentName = toState.name
})
$scope.$on('$destroy', unregisterStateChange)
/**
* @summary Get the current state name
* @function
* @public
*
* @returns {String} current state name
*
* @example
* if (StateController.currentName === 'main') {
* console.log('We are on the main screen!');
* }
*/
this.currentName = null
})
// Ensure user settings are loaded before
// we bootstrap the Angular.js application
angular.element(document).ready(() => {
settings.load().then(() => {
angular.bootstrap(document, [ 'Etcher' ])
analytics.logEvent('Close rejected while flashing', { applicationSessionUuid, flashingWorkflowUuid })
popupExists = false
}).catch(exceptionReporter.report)
})
/**
* @summary Helper fn for events
* @function
* @private
* @example
* window.addEventListener('click', extendLock)
*/
const extendLock = () => {
updateLock.extend()
}
window.addEventListener('click', extendLock)
window.addEventListener('touchstart', extendLock)
// Initial update lock acquisition
extendLock()
settings.load().catch(exceptionReporter.report)
require('./tsapp.tsx')

View File

@ -29,7 +29,7 @@ import { FlashAnother } from '../flash-another/flash-another';
import { FlashResults } from '../flash-results/flash-results';
import * as SVGIcon from '../svg-icon/svg-icon';
const restart = (options: any, $state: any) => {
const restart = (options: any, goToMain: () => void) => {
const {
applicationSessionUuid,
flashingWorkflowUuid,
@ -54,7 +54,7 @@ const restart = (options: any, $state: any) => {
data: uuidV4(),
});
$state.go('main');
goToMain();
};
const formattedErrors = () => {
@ -67,7 +67,7 @@ const formattedErrors = () => {
return errors.join('\n');
};
function FinishPage({ $state }: any) {
function FinishPage({ goToMain }: { goToMain: () => void }) {
// @ts-ignore
const results = flashState.getFlashResults().results || {};
const progressMessage = messages.progress;
@ -82,7 +82,7 @@ function FinishPage({ $state }: any) {
></FlashResults>
<FlashAnother
onClick={(options: any) => restart(options, $state)}
onClick={(options: any) => restart(options, goToMain)}
></FlashAnother>
</div>

View File

@ -1,35 +0,0 @@
/*
* Copyright 2019 balena.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.
*/
/**
* @module Etcher.Pages.Finish
*/
import * as angular from 'angular';
import { react2angular } from 'react2angular';
import FinishPage from './finish';
export const MODULE_NAME = 'Etcher.Pages.Finish';
const Finish = angular.module(MODULE_NAME, []);
Finish.component('finish', react2angular(FinishPage, [], ['$state']));
Finish.config(($stateProvider: any) => {
$stateProvider.state('success', {
url: '/success',
template: '<finish style="width:100%"></finish>',
});
});

View File

@ -1,28 +0,0 @@
/*
* Copyright 2018 balena.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.
*/
/**
* @module Etcher.Components.SafeWebview
*/
import * as angular from 'angular';
import { react2angular } from 'react2angular';
import * as SafeWebview from './safe-webview';
export const MODULE_NAME = 'Etcher.Components.SafeWebview';
const AngularSafeWebview = angular.module(MODULE_NAME, []);
AngularSafeWebview.component('safeWebview', react2angular(SafeWebview));

View File

@ -6,21 +6,9 @@
<link rel="stylesheet" type="text/css" href="../../../node_modules/flexboxgrid/dist/flexboxgrid.css">
<link rel="stylesheet" type="text/css" href="../css/main.css">
<link rel="stylesheet" type="text/css" href="../css/desktop.css">
<link rel="stylesheet" type="text/css" href="../css/angular.css">
<script src="../../../generated/gui.js"></script>
</head>
<body>
<main ui-view></main>
<div class="section-loader"
ng-controller="StateController as state"
ng-class="{
isFinish: state.currentName === 'success'
}">
<safe-webview src="'https://www.balena.io/etcher/success-banner/'">
</safe-webview>
</div>
<main id="main"></main>
<script src="../../../generated/gui.js"></script>
</body>
</html>

View File

@ -112,8 +112,7 @@ const ACTIONS = _.fromPairs(_.map([
'DESELECT_DRIVE',
'DESELECT_IMAGE',
'SET_APPLICATION_SESSION_UUID',
'SET_FLASHING_WORKFLOW_UUID',
'SET_WEBVIEW_SHOWING_STATUS'
'SET_FLASHING_WORKFLOW_UUID'
], (message) => {
return [ message, message ]
}))
@ -507,10 +506,6 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
return state.set('flashingWorkflowUuid', action.data)
}
case ACTIONS.SET_WEBVIEW_SHOWING_STATUS: {
return state.set('isWebviewShowing', action.data)
}
default: {
return state
}

View File

@ -160,15 +160,10 @@ const formatSeconds = (totalSeconds: number) => {
return `${minutes}m${seconds}s`;
};
export const Flash = ({
shouldFlashStepBeDisabled,
lastFlashErrorCode,
progressMessage,
goToSuccess,
}: any) => {
export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => {
const state: any = flashState.getFlashState();
const isFlashing = flashState.isFlashing();
const flashErrorCode = lastFlashErrorCode();
const flashErrorCode = flashState.getLastFlashErrorCode();
const [warningMessages, setWarningMessages] = React.useState<string[]>([]);
const [errorMessage, setErrorMessage] = React.useState('');
@ -272,7 +267,7 @@ export const Flash = ({
<span className="target-status-dot"></span>
<span className="target-status-quantity">{state.failed}</span>
<span className="target-status-message">
{progressMessage.failed(state.failed)}{' '}
{messages.progress.failed(state.failed)}{' '}
</span>
</div>
</div>

View File

@ -21,8 +21,10 @@ import * as React from 'react';
import { Button } from 'rendition';
import * as FeaturedProject from '../../components/featured-project/featured-project';
import FinishPage from '../../components/finish/finish';
import * as ImageSelector from '../../components/image-selector/image-selector';
import * as ReducedFlashingInfos from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import * as SafeWebview from '../../components/safe-webview/safe-webview';
import { SettingsModal } from '../../components/settings/settings';
import * as SvgIcon from '../../components/svg-icon/svg-icon.jsx';
import * as flashState from '../../models/flash-state';
@ -34,19 +36,16 @@ import { ThemedProvider } from '../../styled-components';
import { colors } from '../../theme';
import * as middleEllipsis from '../../utils/middle-ellipsis';
import * as messages from '../../../../shared/messages';
import { bytesToClosestUnit } from '../../../../shared/units';
import { DriveSelector } from './DriveSelector';
import { Flash } from './Flash';
const DEFAULT_SUPPORT_URL =
'https://github.com/balena-io/etcher/blob/master/SUPPORT.md';
const getDrivesTitle = (selection: any) => {
const drives = selection.getSelectedDrives();
function getDrivesTitle() {
const drives = selectionState.getSelectedDrives();
if (drives.length === 1) {
// @ts-ignore
return drives[0].description || 'Untitled Device';
}
@ -55,162 +54,204 @@ const getDrivesTitle = (selection: any) => {
}
return `${drives.length} Targets`;
};
}
const getImageBasename = (selection: any) => {
if (!selection.hasImage()) {
function getImageBasename() {
if (!selectionState.hasImage()) {
return '';
}
const selectionImageName = selection.getImageName();
const imageBasename = path.basename(selection.getImagePath());
const selectionImageName = selectionState.getImageName();
const imageBasename = path.basename(selectionState.getImagePath());
return selectionImageName || imageBasename;
};
}
const MainPage = ({ $state }: any) => {
const setRefresh = React.useState(false)[1];
const [isWebviewShowing, setIsWebviewShowing] = React.useState(false);
const [hideSettings, setHideSettings] = React.useState(true);
React.useEffect(() => {
return (store as any).observe(() => {
setRefresh(ref => !ref);
interface MainPageStateFromStore {
isFlashing: boolean;
hasImage: boolean;
hasDrive: boolean;
imageLogo: string;
imageSize: number;
imageName: string;
driveTitle: string;
}
interface MainPageState {
current: 'main' | 'success';
isWebviewShowing: boolean;
hideSettings: boolean;
}
export class MainPage extends React.Component<
{},
MainPageState & MainPageStateFromStore
> {
constructor(props: {}) {
super(props);
this.state = {
current: 'main',
isWebviewShowing: false,
hideSettings: true,
...this.stateHelper(),
};
}
private stateHelper(): MainPageStateFromStore {
return {
isFlashing: flashState.isFlashing(),
hasImage: selectionState.hasImage(),
hasDrive: selectionState.hasDrive(),
imageLogo: selectionState.getImageLogo(),
imageSize: selectionState.getImageSize(),
imageName: getImageBasename(),
driveTitle: getDrivesTitle(),
};
}
public componentDidMount() {
(store as any).observe(() => {
this.setState(this.stateHelper());
});
}, []);
}
const setWebviewShowing = (isShowing: boolean) => {
setIsWebviewShowing(isShowing);
store.dispatch({
type: 'SET_WEBVIEW_SHOWING_STATUS',
data: Boolean(isShowing),
});
};
public render() {
const shouldDriveStepBeDisabled = !this.state.hasImage;
const shouldFlashStepBeDisabled =
!this.state.hasImage || !this.state.hasDrive;
const isFlashing = flashState.isFlashing();
const shouldDriveStepBeDisabled = !selectionState.hasImage();
const shouldFlashStepBeDisabled =
!selectionState.hasDrive() || shouldDriveStepBeDisabled;
const hasDrive = selectionState.hasDrive();
const imageLogo = selectionState.getImageLogo();
const imageSize = bytesToClosestUnit(selectionState.getImageSize());
const imageName = middleEllipsis(getImageBasename(selectionState), 16);
const driveTitle = middleEllipsis(getDrivesTitle(selectionState), 16);
const shouldShowFlashingInfos = isFlashing && isWebviewShowing;
const lastFlashErrorCode = flashState.getLastFlashErrorCode;
const progressMessage = messages.progress;
return (
<ThemedProvider style={{ height: '100%' }}>
<header
id="app-header"
style={{
width: '100%',
padding: '13px 14px',
textAlign: 'center',
}}
>
<span
style={{
cursor: 'pointer',
}}
onClick={() =>
openExternal('https://www.balena.io/etcher?ref=etcher_footer')
}
tabIndex={100}
>
<SvgIcon
paths={['../../assets/etcher.svg']}
width="123px"
height="22px"
></SvgIcon>
</span>
<span
style={{
float: 'right',
position: 'absolute',
right: 0,
}}
>
<Button
icon={<FontAwesomeIcon icon={faCog} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain
onClick={() => setHideSettings(false)}
tabIndex={5}
/>
{!settings.get('disableExternalLinks') && (
<Button
icon={<FontAwesomeIcon icon={faQuestionCircle} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain
if (this.state.current === 'main') {
return (
<ThemedProvider style={{ height: '100%', width: '100%' }}>
<header
id="app-header"
style={{
width: '100%',
padding: '13px 14px',
textAlign: 'center',
}}
>
<span
style={{
cursor: 'pointer',
}}
onClick={() =>
openExternal(
selectionState.getImageSupportUrl() || DEFAULT_SUPPORT_URL,
)
openExternal('https://www.balena.io/etcher?ref=etcher_footer')
}
tabIndex={5}
tabIndex={100}
>
<SvgIcon
paths={['../../assets/etcher.svg']}
width="123px"
height="22px"
></SvgIcon>
</span>
<span
style={{
float: 'right',
position: 'absolute',
right: 0,
}}
>
<Button
icon={<FontAwesomeIcon icon={faCog} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain
onClick={() => this.setState({ hideSettings: false })}
tabIndex={5}
/>
{!settings.get('disableExternalLinks') && (
<Button
icon={<FontAwesomeIcon icon={faQuestionCircle} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain
onClick={() =>
openExternal(
selectionState.getImageSupportUrl() ||
'https://github.com/balena-io/etcher/blob/master/SUPPORT.md',
)
}
tabIndex={5}
/>
)}
</span>
</header>
{this.state.hideSettings ? null : (
<SettingsModal
toggleModal={(value: boolean) => {
this.setState({ hideSettings: !value });
}}
/>
)}
</span>
</header>
{hideSettings ? null : (
<SettingsModal
toggleModal={(value: boolean) => {
setHideSettings(!value);
}}
/>
)}
<div className="page-main row around-xs" style={{ margin: '110px 50px' }}>
<div className="col-xs">
<ImageSelector flashing={isFlashing} />
</div>
<div className="col-xs">
<DriveSelector
webviewShowing={isWebviewShowing}
disabled={shouldDriveStepBeDisabled}
nextStepDisabled={shouldFlashStepBeDisabled}
hasDrive={hasDrive}
flashing={isFlashing}
/>
</div>
{isFlashing && (
<div
className={`featured-project ${
isFlashing && isWebviewShowing ? 'fp-visible' : ''
}`}
className="page-main row around-xs"
style={{ margin: '110px 50px' }}
>
<FeaturedProject onWebviewShow={setWebviewShowing} />
<div className="col-xs">
<ImageSelector flashing={this.state.isFlashing} />
</div>
<div className="col-xs">
<DriveSelector
webviewShowing={this.state.isWebviewShowing}
disabled={shouldDriveStepBeDisabled}
nextStepDisabled={shouldFlashStepBeDisabled}
hasDrive={this.state.hasDrive}
flashing={this.state.isFlashing}
/>
</div>
{this.state.isFlashing && (
<div
className={`featured-project ${
this.state.isFlashing && this.state.isWebviewShowing
? 'fp-visible'
: ''
}`}
>
<FeaturedProject
onWebviewShow={(isWebviewShowing: boolean) => {
this.setState({ isWebviewShowing });
}}
/>
</div>
)}
<div>
<ReducedFlashingInfos
imageLogo={this.state.imageLogo}
imageName={middleEllipsis(this.state.imageName, 16)}
imageSize={bytesToClosestUnit(this.state.imageSize)}
driveTitle={middleEllipsis(this.state.driveTitle, 16)}
shouldShow={
this.state.isFlashing && this.state.isWebviewShowing
}
/>
</div>
<div className="col-xs">
<Flash
goToSuccess={() => this.setState({ current: 'success' })}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
/>
</div>
</div>
)}
<div>
<ReducedFlashingInfos
imageLogo={imageLogo}
imageName={imageName}
imageSize={imageSize}
driveTitle={driveTitle}
shouldShow={shouldShowFlashingInfos}
/>
</ThemedProvider>
);
} else if (this.state.current === 'success') {
return (
<div className="section-loader isFinish">
<FinishPage goToMain={() => this.setState({ current: 'main' })} />
<SafeWebview src="https://www.balena.io/etcher/success-banner/" />
</div>
<div className="col-xs">
<Flash
goToSuccess={() => $state.go('success')}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
lastFlashErrorCode={lastFlashErrorCode}
progressMessage={progressMessage}
/>
</div>
</div>
</ThemedProvider>
);
};
);
}
}
}
export default MainPage;

View File

@ -1,40 +0,0 @@
/*
* Copyright 2019 balena.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.
*/
/**
* This page represents the application main page.
*
* @module Etcher.Pages.Main
*/
import * as angular from 'angular';
// @ts-ignore
import * as angularRouter from 'angular-ui-router';
import { react2angular } from 'react2angular';
import MainPage from './MainPage';
export const MODULE_NAME = 'Etcher.Pages.Main';
const Main = angular.module(MODULE_NAME, [angularRouter]);
Main.component('mainPage', react2angular(MainPage, [], ['$state']));
Main.config(($stateProvider: any) => {
$stateProvider.state('main', {
url: '/main',
template: '<main-page style="width:100%"></main-page>',
});
});

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2020 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,9 @@
* limitations under the License.
*/
[ng-click] {
cursor: pointer;
-webkit-app-region: no-drag;
}
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import MainPage from './pages/main/MainPage';
ReactDOM.render(<MainPage />, document.getElementById('main'));

59
npm-shrinkwrap.json generated
View File

@ -1092,11 +1092,6 @@
"defer-to-connect": "^1.0.1"
}
},
"@types/angular": {
"version": "1.6.56",
"resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.56.tgz",
"integrity": "sha512-HxtqilvklZ7i6XOaiP7uIJIrFXEVEhfbSY45nfv2DeBRngncI58Y4ZOUMiUkcT8sqgLL1ablmbfylChUg7A3GA=="
},
"@types/bluebird": {
"version": "3.5.28",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.28.tgz",
@ -1186,14 +1181,6 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz",
"integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg=="
},
"@types/lodash.frompairs": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/lodash.frompairs/-/lodash.frompairs-4.0.6.tgz",
"integrity": "sha512-rwCUf4NMKhXpiVjL/RXP8YOk+rd02/J4tACADEgaMXRVnzDbSSlBMKFZoX/ARmHVLg3Qc98Um4PErGv8FbxU7w==",
"requires": {
"@types/lodash": "*"
}
},
"@types/marked": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz",
@ -1597,25 +1584,6 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
"angular": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.7.6.tgz",
"integrity": "sha512-QELpvuMIe1FTGniAkRz93O6A+di0yu88niDwcdzrSqtUHNtZMgtgFS4f7W/6Gugbuwej8Kyswlmymwdp8iPCWg=="
},
"angular-mocks": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.6.tgz",
"integrity": "sha512-t3eQmuAZczdOVdOQj7muCBwH+MBNwd+/FaAsV1SNp+597EQVWABQwxI6KXE0k0ZlyJ5JbtkNIKU8kGAj1znxhw==",
"dev": true
},
"angular-ui-router": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-0.4.3.tgz",
"integrity": "sha512-EGBG7G7ArFVkJPM+ZIgPKuMYuT16UQrr3zj3BEiXHKwxss867bGt3u7QD9g4BxR+K2qQOSWok6JGvgTWXAko3A==",
"requires": {
"angular": "^1.0.8"
}
},
"ansi-align": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
@ -9081,11 +9049,6 @@
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
},
"lodash.frompairs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.frompairs/-/lodash.frompairs-4.0.1.tgz",
"integrity": "sha1-vE5SB/onV8E25XNhTpZkUGsrG9I="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@ -10115,17 +10078,6 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"ngcomponent": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ngcomponent/-/ngcomponent-4.1.0.tgz",
"integrity": "sha512-cGL3iVoqMWTpCfaIwgRKhdaGqiy2Z+CCG0cVfjlBvdqE8saj8xap9B4OTf+qwObxLVZmDTJPDgx3bN6Q/lZ7BQ==",
"requires": {
"@types/angular": "^1.6.39",
"@types/lodash": "^4.14.85",
"angular": ">=1.5.0",
"lodash": "^4.17.4"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -11569,17 +11521,6 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react2angular": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/react2angular/-/react2angular-4.0.6.tgz",
"integrity": "sha512-MDl2WRoTyu7Gyh4+FAIlmsM2mxIa/DjSz6G/d90L1tK8ZRubqVEayKF6IPyAruC5DMhGDVJ7tlAIcu/gMNDjXg==",
"requires": {
"@types/lodash.frompairs": "^4.0.5",
"angular": ">=1.5",
"lodash.frompairs": "^4.0.1",
"ngcomponent": "^4.1.0"
}
},
"read-config-file": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-5.0.0.tgz",

View File

@ -56,8 +56,6 @@
"@fortawesome/free-brands-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7",
"angular": "1.7.6",
"angular-ui-router": "^0.4.2",
"bindings": "^1.3.0",
"bluebird": "^3.5.3",
"bootstrap-sass": "^3.3.6",
@ -79,7 +77,6 @@
"react": "^16.8.5",
"react-dom": "^16.8.5",
"react-dropzone": "^10.2.1",
"react2angular": "^4.0.2",
"redux": "^3.5.2",
"rendition": "^11.24.0",
"request": "^2.81.0",
@ -98,7 +95,6 @@
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"@types/react-dom": "^16.8.4",
"angular-mocks": "1.7.6",
"babel-loader": "^8.0.4",
"chalk": "^1.1.3",
"electron": "6.1.4",

View File

@ -1,12 +1,11 @@
'use strict'
const _ = require('lodash')
const m = require('mochainon')
const ipc = require('node-ipc')
const angular = require('angular')
const Bluebird = require('bluebird')
const flashState = require('../../../lib/gui/app/models/flash-state')
const imageWriter = require('../../../lib/gui/app/modules/image-writer')
require('angular-mocks')
describe('Browser: imageWriter', () => {
describe('.flash()', () => {
@ -41,7 +40,7 @@ describe('Browser: imageWriter', () => {
})
const writing = imageWriter.flash('foo.img', [ '/dev/disk2' ])
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop)
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop)
writing.finally(() => {
m.chai.expect(this.performWriteStub).to.have.been.calledOnce
})
@ -73,13 +72,13 @@ describe('Browser: imageWriter', () => {
})
it('should set flashing to false when done', () => {
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop).finally(() => {
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop).finally(() => {
m.chai.expect(flashState.isFlashing()).to.be.false
})
})
it('should set the error code in the flash results', () => {
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(angular.noop).finally(() => {
imageWriter.flash('foo.img', [ '/dev/disk2' ]).catch(_.noop).finally(() => {
const flashResults = flashState.getFlashResults()
m.chai.expect(flashResults.errorCode).to.equal('FOO')
})