mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 15:27:17 +00:00
Update progress bar style
Changelog-entry: Update progress bar style Change-type: patch
This commit is contained in:
parent
f5c7dc932a
commit
34349f64d5
@ -15,15 +15,16 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { ProgressBar } from 'rendition';
|
||||
import { Button, Flex, ProgressBar, Txt } from 'rendition';
|
||||
import { default as styled } from 'styled-components';
|
||||
|
||||
import { fromFlashState } from '../../modules/progress-status';
|
||||
import { StepButton } from '../../styled-components';
|
||||
|
||||
const FlashProgressBar = styled(ProgressBar)`
|
||||
> div {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
width: 220px;
|
||||
height: 12px;
|
||||
color: white !important;
|
||||
text-shadow: none !important;
|
||||
transition-duration: 0s;
|
||||
@ -32,8 +33,9 @@ const FlashProgressBar = styled(ProgressBar)`
|
||||
}
|
||||
}
|
||||
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
width: 220px;
|
||||
height: 12px;
|
||||
border-radius: 14px;
|
||||
font-size: 16px;
|
||||
line-height: 48px;
|
||||
|
||||
@ -44,8 +46,9 @@ interface ProgressButtonProps {
|
||||
type: 'decompressing' | 'flashing' | 'verifying';
|
||||
active: boolean;
|
||||
percentage: number;
|
||||
label: string;
|
||||
position: number;
|
||||
disabled: boolean;
|
||||
cancel: () => void;
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
@ -55,16 +58,41 @@ const colors = {
|
||||
verifying: '#1ac135',
|
||||
} as const;
|
||||
|
||||
const CancelButton = styled((props) => (
|
||||
<Button plain {...props}>
|
||||
Cancel
|
||||
</Button>
|
||||
))`
|
||||
font-weight: 600;
|
||||
&&& {
|
||||
width: auto;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
||||
public render() {
|
||||
const { status, position } = fromFlashState({
|
||||
type: this.props.type,
|
||||
position: this.props.position,
|
||||
percentage: this.props.percentage,
|
||||
});
|
||||
if (this.props.active) {
|
||||
return (
|
||||
<FlashProgressBar
|
||||
background={colors[this.props.type]}
|
||||
value={this.props.percentage}
|
||||
>
|
||||
{this.props.label}
|
||||
</FlashProgressBar>
|
||||
<div>
|
||||
<Flex justifyContent="space-between" style={{ fontWeight: 600 }}>
|
||||
<Flex>
|
||||
<Txt color="#fff">{status} </Txt>
|
||||
<Txt color={colors[this.props.type]}>{position}</Txt>
|
||||
</Flex>
|
||||
<CancelButton onClick={this.props.cancel} color="#00aeef" />
|
||||
</Flex>
|
||||
<FlashProgressBar
|
||||
background={colors[this.props.type]}
|
||||
value={this.props.percentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
@ -73,7 +101,7 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
||||
onClick={this.props.callback}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{this.props.label}
|
||||
Flash!
|
||||
</StepButton>
|
||||
);
|
||||
}
|
||||
|
@ -25,56 +25,53 @@ export interface FlashState {
|
||||
type?: 'decompressing' | 'flashing' | 'verifying';
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Make the progress status subtitle string
|
||||
*
|
||||
* @param {Object} state - flashing metadata
|
||||
*
|
||||
* @returns {String}
|
||||
*
|
||||
* @example
|
||||
* const status = progressStatus.fromFlashState({
|
||||
* type: 'flashing'
|
||||
* active: 1,
|
||||
* failed: 0,
|
||||
* percentage: 55,
|
||||
* speed: 2049,
|
||||
* })
|
||||
*
|
||||
* console.log(status)
|
||||
* // '55% Flashing'
|
||||
*/
|
||||
export function fromFlashState({
|
||||
type,
|
||||
percentage,
|
||||
position,
|
||||
}: Pick<FlashState, 'type' | 'percentage' | 'position'>): string {
|
||||
}: Pick<FlashState, 'type' | 'percentage' | 'position'>): {
|
||||
status: string;
|
||||
position?: string;
|
||||
} {
|
||||
if (type === undefined) {
|
||||
return 'Starting...';
|
||||
return { status: 'Starting...' };
|
||||
} else if (type === 'decompressing') {
|
||||
if (percentage == null) {
|
||||
return 'Decompressing...';
|
||||
return { status: 'Decompressing...' };
|
||||
} else {
|
||||
return `${percentage}% Decompressing`;
|
||||
return { position: `${percentage}%`, status: 'Decompressing...' };
|
||||
}
|
||||
} else if (type === 'flashing') {
|
||||
if (percentage != null) {
|
||||
if (percentage < 100) {
|
||||
return `${percentage}% Flashing`;
|
||||
return { position: `${percentage}%`, status: 'Flashing...' };
|
||||
} else {
|
||||
return 'Finishing...';
|
||||
return { status: 'Finishing...' };
|
||||
}
|
||||
} else {
|
||||
return `${bytesToClosestUnit(position)} flashed`;
|
||||
return {
|
||||
status: 'Flashing...',
|
||||
position: `${bytesToClosestUnit(position)}`,
|
||||
};
|
||||
}
|
||||
} else if (type === 'verifying') {
|
||||
if (percentage == null) {
|
||||
return 'Validating...';
|
||||
return { status: 'Validating...' };
|
||||
} else if (percentage < 100) {
|
||||
return `${percentage}% Validating`;
|
||||
return { position: `${percentage}%`, status: 'Validating...' };
|
||||
} else {
|
||||
return 'Finishing...';
|
||||
return { status: 'Finishing...' };
|
||||
}
|
||||
}
|
||||
return 'Failed';
|
||||
return { status: 'Failed' };
|
||||
}
|
||||
|
||||
export function titleFromFlashState(
|
||||
state: Pick<FlashState, 'type' | 'percentage' | 'position'>,
|
||||
): string {
|
||||
const { status, position } = fromFlashState(state);
|
||||
if (position !== undefined) {
|
||||
return `${position} ${status}`;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
import * as electron from 'electron';
|
||||
|
||||
import { percentageToFloat } from '../../../shared/utils';
|
||||
import { FlashState, fromFlashState } from '../modules/progress-status';
|
||||
import { FlashState, titleFromFlashState } from '../modules/progress-status';
|
||||
|
||||
/**
|
||||
* @summary The title of the main window upon program launch
|
||||
@ -29,7 +29,7 @@ const INITIAL_TITLE = document.title;
|
||||
*/
|
||||
function getWindowTitle(state?: FlashState) {
|
||||
if (state) {
|
||||
return `${INITIAL_TITLE} – ${fromFlashState(state)}`;
|
||||
return `${INITIAL_TITLE} – ${titleFromFlashState(state)}`;
|
||||
}
|
||||
return INITIAL_TITLE;
|
||||
}
|
||||
|
@ -14,13 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import { Button, Modal, Txt } from 'rendition';
|
||||
import styled from 'styled-components';
|
||||
import { Flex, Modal, Txt } from 'rendition';
|
||||
|
||||
import * as constraints from '../../../../shared/drive-constraints';
|
||||
import * as messages from '../../../../shared/messages';
|
||||
@ -34,7 +31,6 @@ import * as selection from '../../models/selection-state';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import { scanner as driveScanner } from '../../modules/drive-scanner';
|
||||
import * as imageWriter from '../../modules/image-writer';
|
||||
import * as progressStatus from '../../modules/progress-status';
|
||||
import * as notification from '../../os/notification';
|
||||
|
||||
const COMPLETED_PERCENTAGE = 100;
|
||||
@ -132,20 +128,6 @@ async function flashImageToDrive(
|
||||
return '';
|
||||
}
|
||||
|
||||
const getProgressButtonLabel = (
|
||||
isFlashing: boolean,
|
||||
// TODO: factorize
|
||||
type: 'decompressing' | 'flashing' | 'verifying',
|
||||
position: number,
|
||||
percentage?: number,
|
||||
) => {
|
||||
// TODO
|
||||
if (!isFlashing) {
|
||||
return 'Flash!';
|
||||
}
|
||||
return progressStatus.fromFlashState({ type, position, percentage });
|
||||
};
|
||||
|
||||
const formatSeconds = (totalSeconds: number) => {
|
||||
if (!totalSeconds && !_.isNumber(totalSeconds)) {
|
||||
return '';
|
||||
@ -156,12 +138,6 @@ const formatSeconds = (totalSeconds: number) => {
|
||||
return `${minutes}m${seconds}s`;
|
||||
};
|
||||
|
||||
const IconButton = styled(Button)`
|
||||
&& {
|
||||
width: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface FlashStepProps {
|
||||
shouldFlashStepBeDisabled: boolean;
|
||||
goToSuccess: () => void;
|
||||
@ -260,44 +236,33 @@ export class FlashStep extends React.PureComponent<
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-vertical-large" style={{ display: 'flex' }}>
|
||||
<div className="space-vertical-large">
|
||||
<ProgressButton
|
||||
type={this.props.step}
|
||||
active={this.props.isFlashing}
|
||||
percentage={this.props.percentage}
|
||||
label={getProgressButtonLabel(
|
||||
this.props.isFlashing,
|
||||
this.props.step,
|
||||
this.props.position,
|
||||
this.props.percentage,
|
||||
)}
|
||||
position={this.props.position}
|
||||
disabled={this.props.shouldFlashStepBeDisabled}
|
||||
cancel={imageWriter.cancel}
|
||||
callback={() => {
|
||||
this.tryFlash();
|
||||
}}
|
||||
/>
|
||||
{this.props.isFlashing && (
|
||||
<IconButton
|
||||
icon={<FontAwesomeIcon icon={faTimes} />}
|
||||
plain
|
||||
onClick={imageWriter.cancel}
|
||||
color="#fff"
|
||||
hoverIndicator={{ dark: true }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!_.isNil(this.props.speed) &&
|
||||
this.props.percentage !== COMPLETED_PERCENTAGE && (
|
||||
<p className="step-footer step-footer-split">
|
||||
{Boolean(this.props.speed) && (
|
||||
<span>{`${this.props.speed.toFixed(
|
||||
SPEED_PRECISION,
|
||||
)} MB/s`}</span>
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
fontSize="14px"
|
||||
color="#7e8085"
|
||||
>
|
||||
{!_.isNil(this.props.speed) && (
|
||||
<Txt>{this.props.speed.toFixed(SPEED_PRECISION)} MB/s</Txt>
|
||||
)}
|
||||
{!_.isNil(this.props.eta) && (
|
||||
<span>{`ETA: ${formatSeconds(this.props.eta)}`}</span>
|
||||
<Txt>ETA: {formatSeconds(this.props.eta)}</Txt>
|
||||
)}
|
||||
</p>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{Boolean(this.props.failed) && (
|
||||
|
@ -28,101 +28,10 @@ img[disabled] {
|
||||
height: 165px;
|
||||
}
|
||||
|
||||
.page-main .step-selection-text {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
color: $palette-theme-dark-foreground;
|
||||
}
|
||||
|
||||
.page-main .text-disabled > span {
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
}
|
||||
|
||||
.page-main .step-drive.text-warning {
|
||||
color: $palette-theme-warning-background;
|
||||
}
|
||||
|
||||
.page-main .relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-main .button-abort-write {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
right: -17px;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.button-brick {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.page-main .step-tooltip {
|
||||
display: block;
|
||||
margin: -5px auto -20px;
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.page-main .step-footer {
|
||||
width: 100%;
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.page-main p.step-footer {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.page-main .step-footer-split {
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
left: 28px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: $btn-min-width;
|
||||
}
|
||||
|
||||
.page-main .button.step-footer {
|
||||
font-size: 16px;
|
||||
color: $palette-theme-primary-background;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font-weight: 300;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.page-main .step-drive.glyphicon {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.page-main div.step-fill,
|
||||
.page-main span.step-fill {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.page-main .step-drive.step-list {
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: $palette-theme-dark-disabled-foreground;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-main .glyphicon {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
@ -137,22 +46,6 @@ img[disabled] {
|
||||
color: $palette-theme-primary-foreground;
|
||||
}
|
||||
|
||||
.page-main .step-size {
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
height: 21px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-main .step-list {
|
||||
height: 80px;
|
||||
margin: 15px;
|
||||
overflow-y: auto;
|
||||
color: $palette-theme-dark-disabled-foreground;
|
||||
}
|
||||
|
||||
.target-status-wrap {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@ -191,10 +84,6 @@ img[disabled] {
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.space-vertical-large {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import * as settings from '../../../lib/gui/app/models/settings';
|
||||
import * as progressStatus from '../../../lib/gui/app/modules/progress-status';
|
||||
|
||||
describe('Browser: progressStatus', function () {
|
||||
describe('.fromFlashState()', function () {
|
||||
describe('.titleFromFlashState()', function () {
|
||||
beforeEach(function () {
|
||||
this.state = {
|
||||
active: 1,
|
||||
@ -36,25 +36,31 @@ describe('Browser: progressStatus', function () {
|
||||
});
|
||||
|
||||
it('should report 0% if percentage == 0 but speed != 0', function () {
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal('0% Flashing');
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'0% Flashing...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 0, flashing, unmountOnSuccess', function () {
|
||||
this.state.speed = 0;
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal('0% Flashing');
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'0% Flashing...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 0, flashing, !unmountOnSuccess', function () {
|
||||
this.state.speed = 0;
|
||||
settings.set('unmountOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal('0% Flashing');
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'0% Flashing...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 0, verifying, unmountOnSuccess', function () {
|
||||
this.state.speed = 0;
|
||||
this.state.type = 'verifying';
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'0% Validating',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'0% Validating...',
|
||||
);
|
||||
});
|
||||
|
||||
@ -62,31 +68,31 @@ describe('Browser: progressStatus', function () {
|
||||
this.state.speed = 0;
|
||||
this.state.type = 'verifying';
|
||||
settings.set('unmountOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'0% Validating',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'0% Validating...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 50, flashing, unmountOnSuccess', function () {
|
||||
this.state.percentage = 50;
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'50% Flashing',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'50% Flashing...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 50, flashing, !unmountOnSuccess', function () {
|
||||
this.state.percentage = 50;
|
||||
settings.set('unmountOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'50% Flashing',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'50% Flashing...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 50, verifying, unmountOnSuccess', function () {
|
||||
this.state.percentage = 50;
|
||||
this.state.type = 'verifying';
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'50% Validating',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'50% Validating...',
|
||||
);
|
||||
});
|
||||
|
||||
@ -94,14 +100,14 @@ describe('Browser: progressStatus', function () {
|
||||
this.state.percentage = 50;
|
||||
this.state.type = 'verifying';
|
||||
settings.set('unmountOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
'50% Validating',
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'50% Validating...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle percentage == 100, flashing, unmountOnSuccess, validateWriteOnSuccess', function () {
|
||||
this.state.percentage = 100;
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'Finishing...',
|
||||
);
|
||||
});
|
||||
@ -109,7 +115,7 @@ describe('Browser: progressStatus', function () {
|
||||
it('should handle percentage == 100, flashing, unmountOnSuccess, !validateWriteOnSuccess', function () {
|
||||
this.state.percentage = 100;
|
||||
settings.set('validateWriteOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'Finishing...',
|
||||
);
|
||||
});
|
||||
@ -118,7 +124,7 @@ describe('Browser: progressStatus', function () {
|
||||
this.state.percentage = 100;
|
||||
settings.set('unmountOnSuccess', false);
|
||||
settings.set('validateWriteOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'Finishing...',
|
||||
);
|
||||
});
|
||||
@ -126,7 +132,7 @@ describe('Browser: progressStatus', function () {
|
||||
it('should handle percentage == 100, verifying, unmountOnSuccess', function () {
|
||||
this.state.percentage = 100;
|
||||
this.state.type = 'verifying';
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'Finishing...',
|
||||
);
|
||||
});
|
||||
@ -134,7 +140,7 @@ describe('Browser: progressStatus', function () {
|
||||
it('should handle percentage == 100, validatinf, !unmountOnSuccess', function () {
|
||||
this.state.percentage = 100;
|
||||
settings.set('unmountOnSuccess', false);
|
||||
expect(progressStatus.fromFlashState(this.state)).to.equal(
|
||||
expect(progressStatus.titleFromFlashState(this.state)).to.equal(
|
||||
'Finishing...',
|
||||
);
|
||||
});
|
||||
|
@ -74,20 +74,20 @@ describe('Browser: WindowProgress', function () {
|
||||
|
||||
it('should set the flashing title', function () {
|
||||
windowProgress.set(this.state);
|
||||
assert.calledWith(this.setTitleSpy, ' – 85% Flashing');
|
||||
assert.calledWith(this.setTitleSpy, ' – 85% Flashing...');
|
||||
});
|
||||
|
||||
it('should set the verifying title', function () {
|
||||
this.state.type = 'verifying';
|
||||
windowProgress.set(this.state);
|
||||
assert.calledWith(this.setTitleSpy, ' – 85% Validating');
|
||||
assert.calledWith(this.setTitleSpy, ' – 85% Validating...');
|
||||
});
|
||||
|
||||
it('should set the starting title', function () {
|
||||
this.state.percentage = 0;
|
||||
this.state.speed = 0;
|
||||
windowProgress.set(this.state);
|
||||
assert.calledWith(this.setTitleSpy, ' – 0% Flashing');
|
||||
assert.calledWith(this.setTitleSpy, ' – 0% Flashing...');
|
||||
});
|
||||
|
||||
it('should set the finishing title', function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user