mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 23:37:18 +00:00
chore: move flash step to React
Changelog-entry: chore: move flash step to React Change-type: patch Signed-off-by: Stevche Radevski <stevche@balena.io>
This commit is contained in:
parent
5cd3c5fcc0
commit
1d15d582d9
287
lib/gui/app/pages/main/Flash.jsx
Normal file
287
lib/gui/app/pages/main/Flash.jsx
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 React = require('react')
|
||||
const _ = require('lodash')
|
||||
const messages = require('../../../../shared/messages')
|
||||
const flashState = require('../../models/flash-state')
|
||||
const driveScanner = require('../../modules/drive-scanner')
|
||||
const progressStatus = require('../../modules/progress-status')
|
||||
const notification = require('../../os/notification')
|
||||
const analytics = require('../../modules/analytics')
|
||||
const imageWriter = require('../../modules/image-writer')
|
||||
const path = require('path')
|
||||
const store = require('../../models/store')
|
||||
const constraints = require('../../../../shared/drive-constraints')
|
||||
const availableDrives = require('../../models/available-drives')
|
||||
const selection = require('../../models/selection-state')
|
||||
const SvgIcon = require('../../components/svg-icon/svg-icon.jsx')
|
||||
const ProgressButton = require('../../components/progress-button/progress-button.jsx')
|
||||
|
||||
const COMPLETED_PERCENTAGE = 100
|
||||
const SPEED_PRECISION = 2
|
||||
|
||||
/**
|
||||
* @summary Spawn a confirmation warning modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<String>} warningMessages - warning messages
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @returns {Promise} warning modal promise
|
||||
*
|
||||
* @example
|
||||
* confirmationWarningModal([ 'Hello, World!' ])
|
||||
*/
|
||||
const confirmationWarningModal = (warningMessages, WarningModalService) => {
|
||||
return WarningModalService.display({
|
||||
confirmationLabel: 'Continue',
|
||||
rejectionLabel: 'Change',
|
||||
description: [
|
||||
warningMessages.join('\n\n'),
|
||||
'Are you sure you want to continue?'
|
||||
].join(' ')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Display warning tailored to the warning of the current drives-image pair
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<Object>} drives - list of drive objects
|
||||
* @param {Object} image - image object
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @returns {Promise<Boolean>}
|
||||
*
|
||||
* @example
|
||||
* displayTailoredWarning(drives, image).then((ok) => {
|
||||
* if (ok) {
|
||||
* console.log('No warning was shown or continue was pressed')
|
||||
* } else {
|
||||
* console.log('Change was pressed')
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
const displayTailoredWarning = async (drives, image, WarningModalService) => {
|
||||
const warningMessages = []
|
||||
for (const drive of drives) {
|
||||
if (constraints.isDriveSizeLarge(drive)) {
|
||||
warningMessages.push(messages.warning.largeDriveSize(drive))
|
||||
} else if (!constraints.isDriveSizeRecommended(drive, image)) {
|
||||
warningMessages.push(messages.warning.unrecommendedDriveSize(image, drive))
|
||||
}
|
||||
|
||||
// TODO(Shou): we should consider adding the same warning dialog for system drives and remove unsafe mode
|
||||
}
|
||||
|
||||
if (!warningMessages.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
return confirmationWarningModal(warningMessages, WarningModalService)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Flash image to drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} $timeout - angular's timeout object
|
||||
* @param {Object} $state - angular's state object
|
||||
* @param {Object} WarningModalService - warning modal service
|
||||
* @param {Object} DriveSelectorService - drive selector service
|
||||
* @param {Object} FlashErrorModalService - flash error modal service
|
||||
*
|
||||
* @example
|
||||
* flashImageToDrive($timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService)
|
||||
*/
|
||||
const flashImageToDrive = async ($timeout, $state,
|
||||
WarningModalService, DriveSelectorService, FlashErrorModalService) => {
|
||||
const devices = selection.getSelectedDevices()
|
||||
const image = selection.getImage()
|
||||
const drives = _.filter(availableDrives.getDrives(), (drive) => {
|
||||
return _.includes(devices, drive.device)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (drives.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus(drives, image)
|
||||
if (hasDangerStatus) {
|
||||
if (!(await displayTailoredWarning(drives, image, WarningModalService))) {
|
||||
DriveSelectorService.open()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (flashState.isFlashing()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger Angular digests along with store updates, as the flash state
|
||||
// updates. The angular components won't update without it.
|
||||
// TODO: Remove once moved entirely to React
|
||||
const unsubscribe = store.observe($timeout)
|
||||
|
||||
// Stop scanning drives when flashing
|
||||
// otherwise Windows throws EPERM
|
||||
driveScanner.stop()
|
||||
|
||||
const iconPath = '../../../assets/icon.png'
|
||||
const basename = path.basename(image.path)
|
||||
try {
|
||||
await imageWriter.flash(image.path, drives)
|
||||
if (!flashState.wasLastFlashCancelled()) {
|
||||
const flashResults = flashState.getFlashResults()
|
||||
notification.send('Flash complete!', {
|
||||
body: messages.info.flashComplete(basename, drives, flashResults.results.devices),
|
||||
icon: iconPath
|
||||
})
|
||||
$state.go('success')
|
||||
}
|
||||
} catch (error) {
|
||||
// When flashing is cancelled before starting above there is no error
|
||||
if (!error) {
|
||||
return
|
||||
}
|
||||
|
||||
notification.send('Oops! Looks like the flash failed.', {
|
||||
body: messages.error.flashFailure(path.basename(image.path), drives),
|
||||
icon: iconPath
|
||||
})
|
||||
|
||||
// TODO: All these error codes to messages translations
|
||||
// should go away if the writer emitted user friendly
|
||||
// messages on the first place.
|
||||
if (error.code === 'EVALIDATION') {
|
||||
FlashErrorModalService.show(messages.error.validation())
|
||||
} else if (error.code === 'EUNPLUGGED') {
|
||||
FlashErrorModalService.show(messages.error.driveUnplugged())
|
||||
} else if (error.code === 'EIO') {
|
||||
FlashErrorModalService.show(messages.error.inputOutput())
|
||||
} else if (error.code === 'ENOSPC') {
|
||||
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive())
|
||||
} else if (error.code === 'ECHILDDIED') {
|
||||
FlashErrorModalService.show(messages.error.childWriterDied())
|
||||
} else {
|
||||
FlashErrorModalService.show(messages.error.genericFlashError())
|
||||
error.image = basename
|
||||
analytics.logException(error)
|
||||
}
|
||||
} finally {
|
||||
availableDrives.setDrives([])
|
||||
driveScanner.start()
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get progress button label
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} progress button label
|
||||
*
|
||||
* @example
|
||||
* const label = FlashController.getProgressButtonLabel()
|
||||
*/
|
||||
const getProgressButtonLabel = () => {
|
||||
if (!flashState.isFlashing()) {
|
||||
return 'Flash!'
|
||||
}
|
||||
|
||||
return progressStatus.fromFlashState(flashState.getFlashState())
|
||||
}
|
||||
|
||||
const formatSeconds = (totalSeconds) => {
|
||||
if (!totalSeconds && !_.isNumber(totalSeconds)) {
|
||||
return ''
|
||||
}
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
const minutes = Math.floor(totalSeconds / 60)
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
const seconds = Math.floor(totalSeconds - minutes * 60)
|
||||
|
||||
return `${minutes}m${seconds}s`
|
||||
}
|
||||
|
||||
const Flash = ({
|
||||
shouldFlashStepBeDisabled, lastFlashErrorCode, progressMessage,
|
||||
$timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService
|
||||
}) => {
|
||||
// This is a hack to re-render the component whenever the global state changes. Remove once we get rid of angular and use redux correctly.
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
const setRefresh = React.useState(false)[1]
|
||||
const state = flashState.getFlashState()
|
||||
const isFlashing = flashState.isFlashing()
|
||||
const isFlashStepDisabled = shouldFlashStepBeDisabled()
|
||||
const flashErrorCode = lastFlashErrorCode()
|
||||
|
||||
React.useEffect(() => {
|
||||
return store.observe(() => {
|
||||
setRefresh((ref) => !ref)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return <div className="box text-center">
|
||||
<div className="center-block">
|
||||
<SvgIcon paths={[ '../../assets/flash.svg' ]} disabled={isFlashStepDisabled}/>
|
||||
</div>
|
||||
|
||||
<div className="space-vertical-large">
|
||||
<ProgressButton
|
||||
tabindex="3"
|
||||
striped={state.type === 'verifying'}
|
||||
active={isFlashing}
|
||||
percentage={state.percentage}
|
||||
label={getProgressButtonLabel()}
|
||||
disabled={Boolean(flashErrorCode) || isFlashStepDisabled}
|
||||
callback={() =>
|
||||
flashImageToDrive($timeout, $state, WarningModalService, DriveSelectorService, FlashErrorModalService)}>
|
||||
</ProgressButton>
|
||||
|
||||
{
|
||||
isFlashing && <button className="button button-link button-abort-write" onClick={imageWriter.cancel}>
|
||||
<span className="glyphicon glyphicon-remove-sign"></span>
|
||||
</button>
|
||||
}
|
||||
{
|
||||
!_.isNil(state.speed) && state.percentage !== COMPLETED_PERCENTAGE &&
|
||||
<p className="step-footer step-footer-split">
|
||||
{Boolean(state.speed) && <span >{`${state.speed.toFixed(SPEED_PRECISION)} MB/s`}</span>}
|
||||
{!_.isNil(state.eta) && <span>{`ETA: ${formatSeconds(state.eta)}` }</span>}
|
||||
</p>
|
||||
}
|
||||
|
||||
{
|
||||
Boolean(state.failed) && <div className="target-status-wrap">
|
||||
<div className="target-status-line target-status-failed">
|
||||
<span className="target-status-dot"></span>
|
||||
<span className="target-status-quantity">{state.failed}</span>
|
||||
<span className="target-status-message">{progressMessage.failed(state.failed)} </span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
module.exports = Flash
|
@ -1,222 +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 messages = require('../../../../../shared/messages')
|
||||
const flashState = require('../../../models/flash-state')
|
||||
const driveScanner = require('../../../modules/drive-scanner')
|
||||
const progressStatus = require('../../../modules/progress-status')
|
||||
const notification = require('../../../os/notification')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
const imageWriter = require('../../../modules/image-writer')
|
||||
const path = require('path')
|
||||
const store = require('../../../models/store')
|
||||
const constraints = require('../../../../../shared/drive-constraints')
|
||||
const availableDrives = require('../../../models/available-drives')
|
||||
const selection = require('../../../models/selection-state')
|
||||
|
||||
module.exports = function (
|
||||
$state,
|
||||
$timeout,
|
||||
FlashErrorModalService,
|
||||
WarningModalService,
|
||||
DriveSelectorService
|
||||
) {
|
||||
/**
|
||||
* @summary Spawn a confirmation warning modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<String>} warningMessages - warning messages
|
||||
* @returns {Promise} warning modal promise
|
||||
*
|
||||
* @example
|
||||
* confirmationWarningModal([ 'Hello, World!' ])
|
||||
*/
|
||||
const confirmationWarningModal = (warningMessages) => {
|
||||
return WarningModalService.display({
|
||||
confirmationLabel: 'Continue',
|
||||
rejectionLabel: 'Change',
|
||||
description: [
|
||||
warningMessages.join('\n\n'),
|
||||
'Are you sure you want to continue?'
|
||||
].join(' ')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Display warning tailored to the warning of the current drives-image pair
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Array<Object>} drives - list of drive objects
|
||||
* @param {Object} image - image object
|
||||
* @returns {Promise<Boolean>}
|
||||
*
|
||||
* @example
|
||||
* displayTailoredWarning(drives, image).then((ok) => {
|
||||
* if (ok) {
|
||||
* console.log('No warning was shown or continue was pressed')
|
||||
* } else {
|
||||
* console.log('Change was pressed')
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
const displayTailoredWarning = async (drives, image) => {
|
||||
const warningMessages = []
|
||||
for (const drive of drives) {
|
||||
if (constraints.isDriveSizeLarge(drive)) {
|
||||
warningMessages.push(messages.warning.largeDriveSize(drive))
|
||||
} else if (!constraints.isDriveSizeRecommended(drive, image)) {
|
||||
warningMessages.push(messages.warning.unrecommendedDriveSize(image, drive))
|
||||
}
|
||||
|
||||
// TODO(Shou): we should consider adding the same warning dialog for system drives and remove unsafe mode
|
||||
}
|
||||
|
||||
if (!warningMessages.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
return confirmationWarningModal(warningMessages)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Flash image to drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* FlashController.flashImageToDrive({
|
||||
* path: 'rpi.img',
|
||||
* size: 1000000000,
|
||||
* compressedSize: 1000000000,
|
||||
* isSizeEstimated: false,
|
||||
* }, [
|
||||
* '/dev/disk2',
|
||||
* '/dev/disk5'
|
||||
* ])
|
||||
*/
|
||||
this.flashImageToDrive = async () => {
|
||||
const devices = selection.getSelectedDevices()
|
||||
const image = selection.getImage()
|
||||
const drives = _.filter(availableDrives.getDrives(), (drive) => {
|
||||
return _.includes(devices, drive.device)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (drives.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus(drives, image)
|
||||
if (hasDangerStatus) {
|
||||
if (!(await displayTailoredWarning(drives, image))) {
|
||||
DriveSelectorService.open()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (flashState.isFlashing()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger Angular digests along with store updates, as the flash state
|
||||
// updates. Without this there is essentially no progress to watch.
|
||||
const unsubscribe = store.observe($timeout)
|
||||
|
||||
// Stop scanning drives when flashing
|
||||
// otherwise Windows throws EPERM
|
||||
driveScanner.stop()
|
||||
|
||||
const iconPath = '../../../assets/icon.png'
|
||||
const basename = path.basename(image.path)
|
||||
try {
|
||||
await imageWriter.flash(image.path, drives)
|
||||
if (!flashState.wasLastFlashCancelled()) {
|
||||
const flashResults = flashState.getFlashResults()
|
||||
notification.send('Flash complete!', {
|
||||
body: messages.info.flashComplete(basename, drives, flashResults.results.devices),
|
||||
icon: iconPath
|
||||
})
|
||||
$state.go('success')
|
||||
}
|
||||
} catch (error) {
|
||||
// When flashing is cancelled before starting above there is no error
|
||||
if (!error) {
|
||||
return
|
||||
}
|
||||
|
||||
notification.send('Oops! Looks like the flash failed.', {
|
||||
body: messages.error.flashFailure(path.basename(image.path), drives),
|
||||
icon: iconPath
|
||||
})
|
||||
|
||||
// TODO: All these error codes to messages translations
|
||||
// should go away if the writer emitted user friendly
|
||||
// messages on the first place.
|
||||
if (error.code === 'EVALIDATION') {
|
||||
FlashErrorModalService.show(messages.error.validation())
|
||||
} else if (error.code === 'EUNPLUGGED') {
|
||||
FlashErrorModalService.show(messages.error.driveUnplugged())
|
||||
} else if (error.code === 'EIO') {
|
||||
FlashErrorModalService.show(messages.error.inputOutput())
|
||||
} else if (error.code === 'ENOSPC') {
|
||||
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive())
|
||||
} else if (error.code === 'ECHILDDIED') {
|
||||
FlashErrorModalService.show(messages.error.childWriterDied())
|
||||
} else {
|
||||
FlashErrorModalService.show(messages.error.genericFlashError())
|
||||
error.image = basename
|
||||
analytics.logException(error)
|
||||
}
|
||||
} finally {
|
||||
availableDrives.setDrives([])
|
||||
driveScanner.start()
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get progress button label
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} progress button label
|
||||
*
|
||||
* @example
|
||||
* const label = FlashController.getProgressButtonLabel()
|
||||
*/
|
||||
this.getProgressButtonLabel = () => {
|
||||
if (!flashState.isFlashing()) {
|
||||
return 'Flash!'
|
||||
}
|
||||
|
||||
return progressStatus.fromFlashState(flashState.getFlashState())
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Abort write process
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* FlashController.cancelFlash()
|
||||
*/
|
||||
this.cancelFlash = imageWriter.cancel
|
||||
}
|
@ -23,15 +23,11 @@
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
const MODULE_NAME = 'Etcher.Pages.Main'
|
||||
|
||||
require('angular-moment')
|
||||
|
||||
const MainPage = angular.module(MODULE_NAME, [
|
||||
'angularMoment',
|
||||
|
||||
require('angular-ui-router'),
|
||||
require('angular-seconds-to-date'),
|
||||
|
||||
require('../../components/drive-selector/drive-selector'),
|
||||
require('../../components/tooltip-modal/tooltip-modal'),
|
||||
@ -54,7 +50,9 @@ const MainPage = angular.module(MODULE_NAME, [
|
||||
|
||||
MainPage.controller('MainController', require('./controllers/main'))
|
||||
MainPage.controller('DriveSelectionController', require('./controllers/drive-selection'))
|
||||
MainPage.controller('FlashController', require('./controllers/flash'))
|
||||
MainPage.component('flash', react2angular(require('./Flash.jsx'),
|
||||
[ 'shouldFlashStepBeDisabled', 'lastFlashErrorCode', 'progressMessage' ],
|
||||
[ '$timeout', '$state', 'WarningModalService', 'DriveSelectorService', 'FlashErrorModalService' ]))
|
||||
|
||||
MainPage.config(($stateProvider) => {
|
||||
$stateProvider
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
svg-icon > img[disabled] {
|
||||
img[disabled] {
|
||||
opacity: $disabled-opacity;
|
||||
}
|
||||
|
||||
|
@ -56,45 +56,11 @@
|
||||
></reduced-flashing-infos>
|
||||
</div>
|
||||
|
||||
<div class="col-xs" ng-controller="FlashController as flash">
|
||||
<div class="box text-center">
|
||||
<div class="center-block">
|
||||
<svg-icon paths="[ '../../assets/flash.svg' ]"
|
||||
disabled="main.shouldFlashStepBeDisabled()"></svg-icon>
|
||||
</div>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
<progress-button
|
||||
tabindex="3"
|
||||
striped="main.state.getFlashState().type == 'verifying'"
|
||||
active = "main.state.isFlashing()"
|
||||
percentage="main.state.getFlashState().percentage"
|
||||
label="flash.getProgressButtonLabel()"
|
||||
disabled="!!main.state.getLastFlashErrorCode() || main.shouldFlashStepBeDisabled()"
|
||||
callback="flash.flashImageToDrive" >
|
||||
</progress-button>
|
||||
|
||||
<button class="button button-link button-abort-write"
|
||||
ng-if="main.state.isFlashing()"
|
||||
ng-click="flash.cancelFlash()">
|
||||
<span class="glyphicon glyphicon-remove-sign"></span>
|
||||
</button>
|
||||
|
||||
<p class="step-footer step-footer-split" ng-if="main.state.getFlashState().speed != null && main.state.getFlashState().percentage != 100">
|
||||
<span ng-bind="main.state.getFlashState().speed.toFixed(2) + ' MB/s'"></span>
|
||||
<span ng-if="main.state.getFlashState().eta != null">ETA: {{ main.state.getFlashState().eta | secondsToDate | amDateFormat:'m[m]ss[s]' }}</span>
|
||||
</p>
|
||||
|
||||
<div class="target-status-wrap" ng-if="main.state.getFlashState().failed">
|
||||
<div class="target-status-line target-status-failed">
|
||||
<span class="target-status-dot"></span>
|
||||
<span class="target-status-quantity">{{ main.state.getFlashState().failed }}</span>
|
||||
<span class="target-status-message">{{
|
||||
main.progressMessage.failed(main.state.getFlashState().failed)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs">
|
||||
<flash
|
||||
should-flash-step-be-disabled="main.shouldFlashStepBeDisabled"
|
||||
last-flash-error-code="main.state.getLastFlashErrorCode"
|
||||
progress-image="main.progressImage"
|
||||
></flash>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6368,8 +6368,9 @@ svg-icon {
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
svg-icon > img[disabled] {
|
||||
opacity: 0.2; }
|
||||
img[disabled] {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.page-main {
|
||||
flex: 1;
|
||||
|
16
npm-shrinkwrap.json
generated
16
npm-shrinkwrap.json
generated
@ -1568,22 +1568,6 @@
|
||||
"integrity": "sha512-t3eQmuAZczdOVdOQj7muCBwH+MBNwd+/FaAsV1SNp+597EQVWABQwxI6KXE0k0ZlyJ5JbtkNIKU8kGAj1znxhw==",
|
||||
"dev": true
|
||||
},
|
||||
"angular-moment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
|
||||
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
|
||||
"requires": {
|
||||
"moment": ">=2.8.0 <3.0.0"
|
||||
}
|
||||
},
|
||||
"angular-seconds-to-date": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/angular-seconds-to-date/-/angular-seconds-to-date-1.0.1.tgz",
|
||||
"integrity": "sha1-mTi6xPKkeyvJVc0h0TwU8s3odj0=",
|
||||
"requires": {
|
||||
"angular": "^1.5.6"
|
||||
}
|
||||
},
|
||||
"angular-ui-bootstrap": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-2.5.6.tgz",
|
||||
|
@ -46,8 +46,6 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.7",
|
||||
"angular": "1.7.6",
|
||||
"angular-if-state": "^1.0.0",
|
||||
"angular-moment": "^1.0.1",
|
||||
"angular-seconds-to-date": "^1.0.0",
|
||||
"angular-ui-bootstrap": "^2.5.0",
|
||||
"angular-ui-router": "^0.4.2",
|
||||
"bindings": "^1.3.0",
|
||||
|
@ -26,7 +26,7 @@ angularValidate.validate(
|
||||
],
|
||||
{
|
||||
customtags: [
|
||||
'settings'
|
||||
'settings', 'flash'
|
||||
],
|
||||
customattrs: [
|
||||
|
||||
|
@ -20,7 +20,6 @@ const m = require('mochainon')
|
||||
const _ = require('lodash')
|
||||
const fs = require('fs')
|
||||
const angular = require('angular')
|
||||
const flashState = require('../../../lib/gui/app/models/flash-state')
|
||||
const availableDrives = require('../../../lib/gui/app/models/available-drives')
|
||||
const selectionState = require('../../../lib/gui/app/models/selection-state')
|
||||
|
||||
@ -165,44 +164,6 @@ describe('Browser: MainPage', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('FlashController', function () {
|
||||
let $controller
|
||||
|
||||
beforeEach(angular.mock.inject(function (_$controller_) {
|
||||
$controller = _$controller_
|
||||
}))
|
||||
|
||||
describe('.getProgressButtonLabel()', function () {
|
||||
it('should return "Flash!" given a clean state', function () {
|
||||
const controller = $controller('FlashController', {
|
||||
$scope: {}
|
||||
})
|
||||
|
||||
flashState.resetState()
|
||||
m.chai.expect(controller.getProgressButtonLabel()).to.equal('Flash!')
|
||||
})
|
||||
|
||||
it('should display the flashing progress', function () {
|
||||
const controller = $controller('FlashController', {
|
||||
$scope: {}
|
||||
})
|
||||
|
||||
flashState.setFlashingFlag()
|
||||
flashState.setProgressState({
|
||||
flashing: 1,
|
||||
verifying: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
percentage: 85,
|
||||
eta: 15,
|
||||
speed: 1000,
|
||||
totalSpeed: 2000
|
||||
})
|
||||
m.chai.expect(controller.getProgressButtonLabel()).to.equal('85% Flashing')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('DriveSelectionController', function () {
|
||||
let $controller
|
||||
let DriveSelectionController
|
||||
|
Loading…
x
Reference in New Issue
Block a user