diff --git a/lib/gui/app/modules/progress-status.js b/lib/gui/app/modules/progress-status.js
index 9ed1cd89..19399ac8 100644
--- a/lib/gui/app/modules/progress-status.js
+++ b/lib/gui/app/modules/progress-status.js
@@ -28,7 +28,10 @@ const utils = require('../../../shared/utils')
*
* @example
* const status = progressStatus.fromFlashState({
- * type: 'write',
+ * flashing: 1,
+ * validating: 0,
+ * succeeded: 0,
+ * failed: 0,
* percentage: 55,
* speed: 2049
* })
@@ -37,25 +40,26 @@ const utils = require('../../../shared/utils')
* // '55% Flashing'
*/
exports.fromFlashState = (state) => {
- const isChecking = state.type === 'check'
+ const isFlashing = Boolean(state.flashing)
+ const isValidating = !isFlashing && Boolean(state.validating)
const shouldValidate = settings.get('validateWriteOnSuccess')
const shouldUnmount = settings.get('unmountOnSuccess')
if (state.percentage === utils.PERCENTAGE_MINIMUM && !state.speed) {
- if (isChecking) {
+ if (isValidating) {
return 'Validating...'
}
return 'Starting...'
} else if (state.percentage === utils.PERCENTAGE_MAXIMUM) {
- if ((isChecking || !shouldValidate) && shouldUnmount) {
+ if ((isValidating || !shouldValidate) && shouldUnmount) {
return 'Unmounting...'
}
return 'Finishing...'
- } else if (state.type === 'write') {
+ } else if (isFlashing) {
return `${state.percentage}% Flashing`
- } else if (state.type === 'check') {
+ } else if (isValidating) {
return `${state.percentage}% Validating`
}
diff --git a/lib/gui/app/os/window-progress.js b/lib/gui/app/os/window-progress.js
index 65308748..30952aed 100644
--- a/lib/gui/app/os/window-progress.js
+++ b/lib/gui/app/os/window-progress.js
@@ -38,7 +38,10 @@ const INITIAL_TITLE = document.title
*
* @example
* const title = getWindowTitle({
- * type: 'write',
+ * flashing: 1,
+ * validating: 0,
+ * succeeded: 0,
+ * failed: 0,
* percentage: 55,
* speed: 2049
* });
@@ -78,7 +81,10 @@ exports.currentWindow = electron.remote.getCurrentWindow()
*
* @example
* windowProgress.set({
- * type: 'write',
+ * flashing: 1,
+ * validating: 0,
+ * succeeded: 0,
+ * failed: 0,
* percentage: 55,
* speed: 2049
* })
diff --git a/lib/gui/app/pages/main/controllers/main.js b/lib/gui/app/pages/main/controllers/main.js
index fc8a3792..a99d7bbb 100644
--- a/lib/gui/app/pages/main/controllers/main.js
+++ b/lib/gui/app/pages/main/controllers/main.js
@@ -23,6 +23,7 @@ const exceptionReporter = require('../../../modules/exception-reporter')
const availableDrives = require('../../../../../shared/models/available-drives')
const selectionState = require('../../../../../shared/models/selection-state')
const driveConstraints = require('../../../../../shared/drive-constraints')
+const messages = require('../../../../../shared/messages')
module.exports = function (
TooltipModalService,
@@ -35,6 +36,7 @@ module.exports = function (
this.settings = settings
this.external = OSOpenExternalService
this.constraints = driveConstraints
+ this.progressMessage = messages.progress
/**
* @summary Determine if the drive step should be disabled
diff --git a/lib/gui/app/pages/main/styles/_main.scss b/lib/gui/app/pages/main/styles/_main.scss
index 4a25aced..f28ba2ae 100644
--- a/lib/gui/app/pages/main/styles/_main.scss
+++ b/lib/gui/app/pages/main/styles/_main.scss
@@ -15,7 +15,7 @@
*/
.page-main {
- margin-top: 75px;
+ margin-top: 50px;
}
svg-icon > img[disabled] {
@@ -69,7 +69,6 @@ svg-icon > img[disabled] {
.page-main .step-footer {
margin-top: 10px;
- margin-bottom: -40px;
color: $palette-theme-dark-disabled-foreground;
font-size: 10px;
}
@@ -106,3 +105,47 @@ svg-icon > img[disabled] {
.page-main .step-size {
color: $palette-theme-dark-disabled-foreground;
}
+
+.target-status-wrap {
+ display: flex;
+ flex-direction: column;
+ margin: 8px 28px;
+ align-items: flex-start;
+}
+
+.target-status-line {
+ display: flex;
+ align-items: center;
+ font-size: 10px;
+ font-family: inherit;
+
+ > .target-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 5px;
+ }
+
+ &.target-status-flashing > .target-status-dot {
+ background-color: $palette-theme-warning-background;
+ }
+ &.target-status-validating > .target-status-dot {
+ background-color: $palette-theme-primary-background;
+ }
+ &.target-status-succeeded > .target-status-dot {
+ background-color: $palette-theme-success-background;
+ }
+ &.target-status-failed > .target-status-dot {
+ background-color: $palette-theme-danger-background;
+ }
+
+ > .target-status-quantity {
+ color: white;
+ font-weight: bold;
+ min-width: 32px;
+ }
+
+ > .target-status-message {
+ color: gray;
+ }
+}
diff --git a/lib/gui/app/pages/main/templates/main.tpl.html b/lib/gui/app/pages/main/templates/main.tpl.html
index 2265cb1f..da9ffb11 100644
--- a/lib/gui/app/pages/main/templates/main.tpl.html
+++ b/lib/gui/app/pages/main/templates/main.tpl.html
@@ -106,6 +106,15 @@
ETA: {{ main.state.getFlashState().eta | secondsToDate | amDateFormat:'m[m]ss[s]' }}
+
+
+
+
+ {{ quantity }}
+ {{ main.progressMessage[type]() }}
+
+
diff --git a/lib/gui/css/main.css b/lib/gui/css/main.css
index b5c0a66d..20317e02 100644
--- a/lib/gui/css/main.css
+++ b/lib/gui/css/main.css
@@ -6499,7 +6499,7 @@ svg-icon {
* limitations under the License.
*/
.page-main {
- margin-top: 75px; }
+ margin-top: 50px; }
svg-icon > img[disabled] {
opacity: 0.2; }
@@ -6539,7 +6539,6 @@ svg-icon > img[disabled] {
.page-main .step-footer {
margin-top: 10px;
- margin-bottom: -40px;
color: #787c7f;
font-size: 10px; }
@@ -6570,6 +6569,37 @@ svg-icon > img[disabled] {
.page-main .step-size {
color: #787c7f; }
+.target-status-wrap {
+ display: flex;
+ flex-direction: column;
+ margin: 8px 28px;
+ align-items: flex-start; }
+
+.target-status-line {
+ display: flex;
+ align-items: center;
+ font-size: 10px;
+ font-family: inherit; }
+ .target-status-line > .target-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 5px; }
+ .target-status-line.target-status-flashing > .target-status-dot {
+ background-color: #ff912f; }
+ .target-status-line.target-status-validating > .target-status-dot {
+ background-color: #5793db; }
+ .target-status-line.target-status-succeeded > .target-status-dot {
+ background-color: #5fb835; }
+ .target-status-line.target-status-failed > .target-status-dot {
+ background-color: #d9534f; }
+ .target-status-line > .target-status-quantity {
+ color: white;
+ font-weight: bold;
+ min-width: 32px; }
+ .target-status-line > .target-status-message {
+ color: gray; }
+
/*
* Copyright 2016 resin.io
*
diff --git a/lib/shared/messages.js b/lib/shared/messages.js
index 309ae260..72bf6d3f 100644
--- a/lib/shared/messages.js
+++ b/lib/shared/messages.js
@@ -25,6 +25,30 @@
*/
module.exports = {
+ /**
+ * @summary Progress messages
+ * @namespace progress
+ * @memberof messages
+ */
+ progress: {
+
+ flashing: () => {
+ return 'Flashing device(s)'
+ },
+
+ validating: () => {
+ return 'Validating device(s)'
+ },
+
+ succeeded: () => {
+ return 'Done device(s)'
+ },
+
+ failed: () => {
+ return 'Failed device(s)'
+ }
+ },
+
/**
* @summary Informational messages
* @namespace info
diff --git a/lib/shared/models/flash-state.js b/lib/shared/models/flash-state.js
index 37479f51..b7d4c3bc 100644
--- a/lib/shared/models/flash-state.js
+++ b/lib/shared/models/flash-state.js
@@ -114,25 +114,38 @@ exports.unsetFlashingFlag = (results) => {
* });
*/
exports.setProgressState = (state) => {
+ if (!_.isString(state.type)) {
+ throw new Error(`Invalid state type: ${state.type}`)
+ }
+
+ // NOTE(Shou): we can most likely remove `state.type` when multi-writes
+ // is in proper, thanks to the status quantities.
+ const isValidating = state.type === 'check'
+ const type = isValidating ? 'validating' : 'flashing'
+ const data = _.assign({
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
+ percentage: _.isNumber(state.percentage) && !_.isNaN(state.percentage)
+ ? Math.floor(state.percentage)
+ : state.percentage,
+ eta: state.eta,
+
+ speed: _.attempt(() => {
+ if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
+ // Preserve only two decimal places
+ const PRECISION = 2
+ return _.round(units.bytesToMegabytes(state.speed), PRECISION)
+ }
+
+ return null
+ })
+ }, { [type]: 1 })
+
store.dispatch({
type: store.Actions.SET_FLASH_STATE,
- data: {
- type: state.type,
- percentage: _.isNumber(state.percentage) && !_.isNaN(state.percentage)
- ? Math.floor(state.percentage)
- : state.percentage,
- eta: state.eta,
-
- speed: _.attempt(() => {
- if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
- // Preserve only two decimal places
- const PRECISION = 2
- return _.round(units.bytesToMegabytes(state.speed), PRECISION)
- }
-
- return null
- })
- }
+ data
})
}
@@ -164,6 +177,15 @@ exports.getFlashState = () => {
return store.getState().get('flashState').toJS()
}
+exports.getFlashQuantities = () => {
+ return _.pick(exports.getFlashState(), [
+ 'flashing',
+ 'validating',
+ 'succeeded',
+ 'failed'
+ ])
+}
+
/**
* @summary Determine if the last flash was cancelled
* @function
diff --git a/lib/shared/store.js b/lib/shared/store.js
index 43dba628..24700bf1 100644
--- a/lib/shared/store.js
+++ b/lib/shared/store.js
@@ -57,7 +57,6 @@ const verifyNoNilFields = (object, fields, name) => {
* @private
*/
const flashStateNoNilFields = [
- 'type',
'percentage',
'eta',
'speed'
@@ -88,6 +87,10 @@ const DEFAULT_STATE = Immutable.fromJS({
isFlashing: false,
flashResults: {},
flashState: {
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
speed: 0
},
@@ -241,9 +244,14 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
verifyNoNilFields(action.data, flashStateNoNilFields, 'flash')
- if (!_.isString(action.data.type)) {
+ if (_.every(_.pick(action.data, [
+ 'flashing',
+ 'validating',
+ 'succeeded',
+ 'failed'
+ ]), _.identity)) {
throw errors.createError({
- title: `Invalid state type: ${action.data.type}`
+ title: 'Missing state quantity field(s)'
})
}
diff --git a/tests/gui/modules/progress-status.spec.js b/tests/gui/modules/progress-status.spec.js
index 67fbb6fe..434b949a 100644
--- a/tests/gui/modules/progress-status.spec.js
+++ b/tests/gui/modules/progress-status.spec.js
@@ -8,7 +8,10 @@ describe('Browser: progressStatus', function () {
describe('.fromFlashState()', function () {
beforeEach(function () {
this.state = {
- type: 'write',
+ flashing: 1,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
eta: 15,
speed: 100000000000000
@@ -22,80 +25,86 @@ describe('Browser: progressStatus', function () {
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('0% Flashing')
})
- it('should handle percentage == 0, type == write, unmountOnSuccess', function () {
+ it('should handle percentage == 0, flashing, unmountOnSuccess', function () {
this.state.speed = 0
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Starting...')
})
- it('should handle percentage == 0, type == write, !unmountOnSuccess', function () {
+ it('should handle percentage == 0, flashing, !unmountOnSuccess', function () {
this.state.speed = 0
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Starting...')
})
- it('should handle percentage == 0, type == check, unmountOnSuccess', function () {
+ it('should handle percentage == 0, validating, unmountOnSuccess', function () {
this.state.speed = 0
- this.state.type = 'check'
+ this.state.flashing = 0
+ this.state.validating = 1
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Validating...')
})
- it('should handle percentage == 0, type == check, !unmountOnSuccess', function () {
+ it('should handle percentage == 0, validating, !unmountOnSuccess', function () {
this.state.speed = 0
- this.state.type = 'check'
+ this.state.flashing = 0
+ this.state.validating = 1
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Validating...')
})
- it('should handle percentage == 50, type == write, unmountOnSuccess', function () {
+ it('should handle percentage == 50, flashing, unmountOnSuccess', function () {
this.state.percentage = 50
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Flashing')
})
- it('should handle percentage == 50, type == write, !unmountOnSuccess', function () {
+ it('should handle percentage == 50, flashing, !unmountOnSuccess', function () {
this.state.percentage = 50
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Flashing')
})
- it('should handle percentage == 50, type == check, unmountOnSuccess', function () {
- this.state.type = 'check'
+ it('should handle percentage == 50, validating, unmountOnSuccess', function () {
+ this.state.flashing = 0
+ this.state.validating = 1
this.state.percentage = 50
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Validating')
})
- it('should handle percentage == 50, type == check, !unmountOnSuccess', function () {
- this.state.type = 'check'
+ it('should handle percentage == 50, validating, !unmountOnSuccess', function () {
+ this.state.flashing = 0
+ this.state.validating = 1
this.state.percentage = 50
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('50% Validating')
})
- it('should handle percentage == 100, type == write, unmountOnSuccess, validateWriteOnSuccess', function () {
+ it('should handle percentage == 100, flashing, unmountOnSuccess, validateWriteOnSuccess', function () {
this.state.percentage = 100
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Finishing...')
})
- it('should handle percentage == 100, type == write, unmountOnSuccess, !validateWriteOnSuccess', function () {
+ it('should handle percentage == 100, flashing, unmountOnSuccess, !validateWriteOnSuccess', function () {
this.state.percentage = 100
settings.set('validateWriteOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Unmounting...')
})
- it('should handle percentage == 100, type == write, !unmountOnSuccess, !validateWriteOnSuccess', function () {
+ it('should handle percentage == 100, flashing, !unmountOnSuccess, !validateWriteOnSuccess', function () {
this.state.percentage = 100
settings.set('unmountOnSuccess', false)
settings.set('validateWriteOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Finishing...')
})
- it('should handle percentage == 100, type == check, unmountOnSuccess', function () {
- this.state.type = 'check'
+ it('should handle percentage == 100, validating, unmountOnSuccess', function () {
+ this.state.flashing = 0
+ this.state.validating = 1
this.state.percentage = 100
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Unmounting...')
})
- it('should handle percentage == 100, type == check, !unmountOnSuccess', function () {
- this.state.type = 'check'
+ it('should handle percentage == 100, validatinf, !unmountOnSuccess', function () {
+ this.state.flashing = 0
+ this.state.validating = 1
this.state.percentage = 100
settings.set('unmountOnSuccess', false)
m.chai.expect(progressStatus.fromFlashState(this.state)).to.equal('Finishing...')
diff --git a/tests/gui/os/window-progress.spec.js b/tests/gui/os/window-progress.spec.js
index dd9cf55f..afdfd8d0 100644
--- a/tests/gui/os/window-progress.spec.js
+++ b/tests/gui/os/window-progress.spec.js
@@ -32,9 +32,12 @@ describe('Browser: WindowProgress', function () {
}
this.state = {
+ flashing: 1,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 85,
- speed: 100,
- type: 'write'
+ speed: 100
}
})
@@ -73,19 +76,18 @@ describe('Browser: WindowProgress', function () {
})
it('should set the flashing title', function () {
- this.state.type = 'write'
windowProgress.set(this.state)
m.chai.expect(this.setTitleSpy).to.have.been.calledWith(' \u2013 85% Flashing')
})
it('should set the validating title', function () {
- this.state.type = 'check'
+ this.state.flashing = 0
+ this.state.validating = 1
windowProgress.set(this.state)
m.chai.expect(this.setTitleSpy).to.have.been.calledWith(' \u2013 85% Validating')
})
it('should set the starting title', function () {
- this.state.type = 'write'
this.state.percentage = 0
this.state.speed = 0
windowProgress.set(this.state)
@@ -93,7 +95,6 @@ describe('Browser: WindowProgress', function () {
})
it('should set the finishing title', function () {
- this.state.type = 'write'
this.state.percentage = 100
windowProgress.set(this.state)
m.chai.expect(this.setTitleSpy).to.have.been.calledWith(' \u2013 Finishing...')
diff --git a/tests/shared/models/flash-state.spec.js b/tests/shared/models/flash-state.spec.js
index d5529a82..99b13977 100644
--- a/tests/shared/models/flash-state.spec.js
+++ b/tests/shared/models/flash-state.spec.js
@@ -38,6 +38,10 @@ describe('Model: flashState', function () {
flashState.resetState()
m.chai.expect(flashState.getFlashState()).to.deep.equal({
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
speed: 0
})
@@ -94,17 +98,6 @@ describe('Model: flashState', function () {
}).to.throw('Can\'t set the flashing state when not flashing')
})
- it('should throw if type is missing', function () {
- flashState.setFlashingFlag()
- m.chai.expect(function () {
- flashState.setProgressState({
- percentage: 50,
- eta: 15,
- speed: 100000000000
- })
- }).to.throw('Missing flash fields: type')
- })
-
it('should throw if type is not a string', function () {
flashState.setFlashingFlag()
m.chai.expect(function () {
@@ -267,6 +260,10 @@ describe('Model: flashState', function () {
flashState.resetState()
const currentFlashState = flashState.getFlashState()
m.chai.expect(currentFlashState).to.deep.equal({
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
speed: 0
})
@@ -283,7 +280,15 @@ describe('Model: flashState', function () {
flashState.setFlashingFlag()
flashState.setProgressState(state)
const currentFlashState = flashState.getFlashState()
- m.chai.expect(currentFlashState).to.deep.equal(state)
+ m.chai.expect(currentFlashState).to.deep.equal({
+ flashing: 1,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
+ percentage: 50,
+ eta: 15,
+ speed: 0
+ })
})
})
@@ -377,6 +382,10 @@ describe('Model: flashState', function () {
})
m.chai.expect(flashState.getFlashState()).to.not.deep.equal({
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
speed: 0
})
@@ -387,6 +396,10 @@ describe('Model: flashState', function () {
})
m.chai.expect(flashState.getFlashState()).to.deep.equal({
+ flashing: 0,
+ validating: 0,
+ succeeded: 0,
+ failed: 0,
percentage: 0,
speed: 0
})