Update progress bar style

Changelog-entry: Update progress bar style
Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-06-02 12:46:57 +02:00
parent f5c7dc932a
commit 34349f64d5
7 changed files with 113 additions and 228 deletions

View File

@ -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}&nbsp;</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>
);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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) && (

View File

@ -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;
}

View File

@ -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...',
);
});

View File

@ -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 () {