Rework and move flashing view elements

Change-type: patch
Changelog-entry: Rework and move flashing view elements
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
This commit is contained in:
Lorenzo Alberto Maria Ambrosi 2020-06-15 19:34:31 +02:00
parent 9b71772e35
commit 76086a8f91
18 changed files with 261 additions and 330 deletions

View File

@ -21,11 +21,14 @@ import * as analytics from '../../modules/analytics';
import { SafeWebview } from '../safe-webview/safe-webview'; import { SafeWebview } from '../safe-webview/safe-webview';
interface FeaturedProjectProps { interface FeaturedProjectProps {
shouldShow: boolean;
onWebviewShow: (isWebviewShowing: boolean) => void; onWebviewShow: (isWebviewShowing: boolean) => void;
style?: React.CSSProperties;
} }
interface FeaturedProjectState { interface FeaturedProjectState {
endpoint: string | null; endpoint: string | null;
show: boolean;
} }
export class FeaturedProject extends React.Component< export class FeaturedProject extends React.Component<
@ -34,14 +37,26 @@ export class FeaturedProject extends React.Component<
> { > {
constructor(props: FeaturedProjectProps) { constructor(props: FeaturedProjectProps) {
super(props); super(props);
this.state = { endpoint: null }; this.state = {
endpoint: null,
show: false,
};
} }
public async componentDidMount() { public async componentDidMount() {
try { try {
const endpoint = let endpoint =
(await settings.get('featuredProjectEndpoint')) || (await settings.get('featuredProjectEndpoint')) ||
'https://assets.balena.io/etcher-featured/index.html'; 'https://assets.balena.io/etcher-featured/index.html';
const efpParams = {
borderRight: false,
darkBackground: true,
};
let params = '?';
for (const [param, value] of Object.entries(efpParams)) {
params += `${param}=${value}&`;
}
endpoint += params;
this.setState({ endpoint }); this.setState({ endpoint });
} catch (error) { } catch (error) {
analytics.logException(error); analytics.logException(error);
@ -49,8 +64,16 @@ export class FeaturedProject extends React.Component<
} }
public render() { public render() {
const { style = {} } = this.props;
return this.state.endpoint ? ( return this.state.endpoint ? (
<SafeWebview src={this.state.endpoint} {...this.props}></SafeWebview> <SafeWebview
src={this.state.endpoint}
style={{
display: this.state.show ? 'block' : 'none',
...style,
}}
{...this.props}
></SafeWebview>
) : null; ) : null;
} }
} }

View File

@ -14,25 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons'; import { faCheckCircle, faCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import * as _ from 'lodash';
import outdent from 'outdent'; import outdent from 'outdent';
import * as React from 'react'; import * as React from 'react';
import { Txt, Flex } from 'rendition'; import { Txt, Flex } from 'rendition';
import styled from 'styled-components';
import { left, position, space, top } from 'styled-system';
import { progress } from '../../../../shared/messages'; import { progress } from '../../../../shared/messages';
import { bytesToMegabytes } from '../../../../shared/units'; import { bytesToMegabytes } from '../../../../shared/units';
import { Underline } from '../../styled-components';
const Div = styled.div<any>`
${position}
${top}
${left}
${space}
`;
export function FlashResults({ export function FlashResults({
errors, errors,
@ -50,15 +39,19 @@ export function FlashResults({
}; };
}) { }) {
const allDevicesFailed = results.devices.successful === 0; const allDevicesFailed = results.devices.successful === 0;
const effectiveSpeed = _.round( const effectiveSpeed = bytesToMegabytes(
bytesToMegabytes( results.sourceMetadata.size /
results.sourceMetadata.size / (results.bytesWritten / results.averageFlashingSpeed),
(results.bytesWritten / results.averageFlashingSpeed), ).toFixed(1);
),
1,
);
return ( return (
<Div position="absolute" left="153px" top="66px"> <Flex
flexDirection="column"
style={{
position: 'absolute',
left: '153px',
top: '66px',
}}
>
<Flex alignItems="center"> <Flex alignItems="center">
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheckCircle} icon={faCheckCircle}
@ -73,24 +66,22 @@ export function FlashResults({
Flash Complete! Flash Complete!
</Txt> </Txt>
</Flex> </Flex>
<Div className="results" mr="0" mb="0" ml="40px"> <Flex flexDirection="column" mr="0" mb="0" ml="40px">
{_.map(results.devices, (quantity, type) => { {Object.keys(results.devices).map((type: 'failed' | 'successful') => {
const quantity = results.devices[type];
return quantity ? ( return quantity ? (
<Underline <Flex
color="#fff"
alignItems="center"
tooltip={type === 'failed' ? errors : undefined} tooltip={type === 'failed' ? errors : undefined}
key={type}
> >
<div <FontAwesomeIcon
key={type} color={type === 'failed' ? '#ff4444' : '#1ac135'}
className={`target-status-line target-status-${type}`} icon={faCircle}
> />
<span className="target-status-dot"></span> <Txt ml={10}>{quantity}</Txt>
<span className="target-status-quantity">{quantity}</span> <Txt ml={10}>{progress[type](quantity)}</Txt>
<span className="target-status-message"> </Flex>
{progress[type](quantity)}
</span>
</div>
</Underline>
) : null; ) : null;
})} })}
{!allDevicesFailed && ( {!allDevicesFailed && (
@ -109,7 +100,7 @@ export function FlashResults({
Effective speed: {effectiveSpeed} MB/s Effective speed: {effectiveSpeed} MB/s
</Txt> </Txt>
)} )}
</Div> </Flex>
</Div> </Flex>
); );
} }

View File

@ -113,6 +113,9 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
warning={this.props.warning} warning={this.props.warning}
onClick={this.props.callback} onClick={this.props.callback}
disabled={this.props.disabled} disabled={this.props.disabled}
style={{
marginTop: 30,
}}
> >
Flash! Flash!
</StepButton> </StepButton>

View File

@ -15,51 +15,20 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { default as styled } from 'styled-components'; import { Flex, Txt } from 'rendition';
import { color } from 'styled-system';
import { SVGIcon } from '../svg-icon/svg-icon';
import DriveSvg from '../../../assets/drive.svg'; import DriveSvg from '../../../assets/drive.svg';
import ImageSvg from '../../../assets/image.svg'; import ImageSvg from '../../../assets/image.svg';
import { SVGIcon } from '../svg-icon/svg-icon';
const Div = styled.div` import { middleEllipsis } from '../../utils/middle-ellipsis';
position: absolute;
top: 45px;
left: 545px;
> span.step-name {
justify-content: flex-start;
> span {
margin-left: 10px;
}
> span:nth-child(2) {
font-weight: 500;
}
> span:nth-child(3) {
font-weight: 400;
font-style: italic;
}
}
.disabled {
opacity: 0.4;
}
`;
const Span = styled.span`
${color}
`;
interface ReducedFlashingInfosProps { interface ReducedFlashingInfosProps {
imageLogo: string; imageLogo: string;
imageName: string; imageName: string;
imageSize: string; imageSize: string;
driveTitle: string; driveTitle: string;
shouldShow: boolean; driveLabel: string;
style?: React.CSSProperties;
} }
export class ReducedFlashingInfos extends React.Component< export class ReducedFlashingInfos extends React.Component<
@ -71,24 +40,36 @@ export class ReducedFlashingInfos extends React.Component<
} }
public render() { public render() {
return this.props.shouldShow ? ( return (
<Div> <Flex
<Span className="step-name"> flexDirection="column"
style={this.props.style ? this.props.style : undefined}
>
<Flex mb={16}>
<SVGIcon <SVGIcon
disabled disabled
width="20px" width="21px"
height="21px"
contents={this.props.imageLogo} contents={this.props.imageLogo}
fallback={<ImageSvg className="disabled" width="20px" />} fallback={ImageSvg}
style={{ marginRight: 9 }}
/> />
<Span>{this.props.imageName}</Span> <Txt
<Span color="#7e8085">{this.props.imageSize}</Span> style={{ marginRight: 9 }}
</Span> tooltip={{ text: this.props.imageName, placement: 'right' }}
>
{middleEllipsis(this.props.imageName, 16)}
</Txt>
<Txt color="#7e8085">{this.props.imageSize}</Txt>
</Flex>
<Span className="step-name"> <Flex>
<DriveSvg className="disabled" width="20px" /> <DriveSvg width="21px" height="21px" style={{ marginRight: 9 }} />
<Span>{this.props.driveTitle}</Span> <Txt tooltip={{ text: this.props.driveLabel, placement: 'right' }}>
</Span> {middleEllipsis(this.props.driveTitle, 16)}
</Div> </Txt>
) : null; </Flex>
</Flex>
);
} }
} }

View File

@ -62,6 +62,7 @@ interface SafeWebviewProps {
refreshNow?: boolean; refreshNow?: boolean;
// Webview lifecycle event // Webview lifecycle event
onWebviewShow?: (isWebviewShowing: boolean) => void; onWebviewShow?: (isWebviewShowing: boolean) => void;
style?: React.CSSProperties;
} }
interface SafeWebviewState { interface SafeWebviewState {
@ -109,15 +110,18 @@ export class SafeWebview extends React.PureComponent<
} }
public render() { public render() {
const {
style = {
flex: this.state.shouldShow ? undefined : '0 1',
width: this.state.shouldShow ? undefined : '0',
height: this.state.shouldShow ? undefined : '0',
},
} = this.props;
return ( return (
<webview <webview
ref={this.webviewRef} ref={this.webviewRef}
partition={ELECTRON_SESSION} partition={ELECTRON_SESSION}
style={{ style={style}
flex: this.state.shouldShow ? undefined : '0 1',
width: this.state.shouldShow ? undefined : '0',
height: this.state.shouldShow ? undefined : '0',
}}
/> />
); );
} }

View File

@ -14,7 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { faFile, faLink } from '@fortawesome/free-solid-svg-icons'; import {
faFile,
faLink,
faExclamationTriangle,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { sourceDestination } from 'etcher-sdk'; import { sourceDestination } from 'etcher-sdk';
import { ipcRenderer, IpcRendererEvent } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
@ -480,7 +484,10 @@ export class SourceSelector extends React.Component<
> >
<SVGIcon <SVGIcon
contents={imageLogo} contents={imageLogo}
fallback={<ImageSvg width="40px" />} fallback={ImageSvg}
style={{
marginBottom: 30,
}}
/> />
{hasImage ? ( {hasImage ? (
@ -525,10 +532,10 @@ export class SourceSelector extends React.Component<
<Modal <Modal
titleElement={ titleElement={
<span> <span>
<span <FontAwesomeIcon
style={{ color: '#d9534f' }} style={{ color: '#fca321' }}
className="glyphicon glyphicon-exclamation-sign" icon={faExclamationTriangle}
></span>{' '} />{' '}
<span>{this.state.warning.title}</span> <span>{this.state.warning.title}</span>
</span> </span>
} }

View File

@ -39,13 +39,14 @@ function tryParseSVGContents(contents?: string): string | undefined {
interface SVGIconProps { interface SVGIconProps {
// List of embedded SVG contents to be tried in succession if any fails // List of embedded SVG contents to be tried in succession if any fails
contents: string; contents: string;
fallback: JSX.Element; fallback: React.FunctionComponent<React.SVGProps<HTMLOrSVGElement>>;
// SVG image width unit // SVG image width unit
width?: string; width?: string;
// SVG image height unit // SVG image height unit
height?: string; height?: string;
// Should the element visually appear grayed out and disabled? // Should the element visually appear grayed out and disabled?
disabled?: boolean; disabled?: boolean;
style?: React.CSSProperties;
} }
/** /**
@ -54,17 +55,19 @@ interface SVGIconProps {
export class SVGIcon extends React.PureComponent<SVGIconProps> { export class SVGIcon extends React.PureComponent<SVGIconProps> {
public render() { public render() {
const svgData = tryParseSVGContents(this.props.contents); const svgData = tryParseSVGContents(this.props.contents);
const { width, height, style = {} } = this.props;
style.width = width || DEFAULT_SIZE;
style.height = height || DEFAULT_SIZE;
if (svgData !== undefined) { if (svgData !== undefined) {
const width = this.props.width || DEFAULT_SIZE;
const height = this.props.height || DEFAULT_SIZE;
return ( return (
<img <img
className={this.props.disabled ? 'disabled' : ''} className={this.props.disabled ? 'disabled' : ''}
style={{ width, height }} style={style}
src={svgData} src={svgData}
/> />
); );
} }
return this.props.fallback; const { fallback: FallbackSVG } = this.props;
return <FallbackSVG style={style} />;
} }
} }

View File

@ -15,10 +15,8 @@
*/ */
import { Drive as DrivelistDrive } from 'drivelist'; import { Drive as DrivelistDrive } from 'drivelist';
import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { Txt } from 'rendition'; import { Txt, Flex, FlexProps } from 'rendition';
import { default as styled } from 'styled-components';
import { import {
getDriveImageCompatibilityStatuses, getDriveImageCompatibilityStatuses,
@ -33,10 +31,8 @@ import {
StepNameButton, StepNameButton,
} from '../../styled-components'; } from '../../styled-components';
import { middleEllipsis } from '../../utils/middle-ellipsis'; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const TargetDetail = styled((props) => <Txt.span {...props}></Txt.span>)` import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
float: ${({ float }) => float};
`;
interface TargetSelectorProps { interface TargetSelectorProps {
targets: any[]; targets: any[];
@ -49,24 +45,26 @@ interface TargetSelectorProps {
image: Image; image: Image;
} }
function DriveCompatibilityWarning(props: { function DriveCompatibilityWarning({
drive,
image,
...props
}: {
drive: DrivelistDrive; drive: DrivelistDrive;
image: Image; image: Image;
}) { } & FlexProps) {
const compatibilityWarnings = getDriveImageCompatibilityStatuses( const compatibilityWarnings = getDriveImageCompatibilityStatuses(
props.drive, drive,
props.image, image,
); );
if (compatibilityWarnings.length === 0) { if (compatibilityWarnings.length === 0) {
return null; return null;
} }
const messages = _.map(compatibilityWarnings, 'message'); const messages = compatibilityWarnings.map((warning) => warning.message);
return ( return (
<Txt.span <Flex tooltip={messages.join(', ')} {...props}>
className="glyphicon glyphicon-exclamation-sign" <FontAwesomeIcon icon={faExclamationTriangle} />
ml={2} </Flex>
tooltip={messages.join(', ')}
/>
); );
} }
@ -86,7 +84,11 @@ export function TargetSelector(props: TargetSelectorProps) {
</ChangeButton> </ChangeButton>
)} )}
<DetailsText> <DetailsText>
<DriveCompatibilityWarning drive={target} image={props.image} /> <DriveCompatibilityWarning
drive={target}
image={props.image}
mr={2}
/>
{bytesToClosestUnit(target.size)} {bytesToClosestUnit(target.size)}
</DetailsText> </DetailsText>
</> </>
@ -104,15 +106,13 @@ export function TargetSelector(props: TargetSelectorProps) {
} ${bytesToClosestUnit(target.size)}`} } ${bytesToClosestUnit(target.size)}`}
px={21} px={21}
> >
<Txt.span> <DriveCompatibilityWarning
<DriveCompatibilityWarning drive={target} image={props.image} /> drive={target}
<TargetDetail float="left"> image={props.image}
{middleEllipsis(target.description, 14)} mr={2}
</TargetDetail> />
<TargetDetail float="right"> <Txt mr={2}>{middleEllipsis(target.description, 14)}</Txt>
{bytesToClosestUnit(target.size)} <Txt>{bytesToClosestUnit(target.size)}</Txt>
</TargetDetail>
</Txt.span>
</DetailsText>, </DetailsText>,
); );
} }

View File

@ -101,20 +101,3 @@
} }
} }
} }
.inline-flex {
display: inline-flex;
}
.page-finish .tick {
/* hack(Shou): for some reason the height is stretched */
height: 24px;
width: 24px;
border: none;
padding: 0;
margin: 0 15px 0 0;
justify-content: center;
align-items: center;
display: flex;
font-size: 16px;
}

View File

@ -31,7 +31,7 @@ import { observe } from '../../models/store';
import * as analytics from '../../modules/analytics'; import * as analytics from '../../modules/analytics';
import DriveSvg from '../../../assets/drive.svg'; import DriveSvg from '../../../assets/drive.svg';
const getDriveListLabel = () => { export const getDriveListLabel = () => {
return getSelectedDrives() return getSelectedDrives()
.map((drive: any) => { .map((drive: any) => {
return `${drive.description} (${drive.displayName})`; return `${drive.description} (${drive.displayName})`;
@ -107,7 +107,13 @@ export const DriveSelector = ({
return ( return (
<Flex flexDirection="column" alignItems="center"> <Flex flexDirection="column" alignItems="center">
<DriveSvg className={disabled ? 'disabled' : ''} width="40px" /> <DriveSvg
className={disabled ? 'disabled' : ''}
width="40px"
style={{
marginBottom: 30,
}}
/>
<TargetSelector <TargetSelector
disabled={disabled} disabled={disabled}

View File

@ -34,6 +34,8 @@ import * as notification from '../../os/notification';
import { selectAllTargets } from './DriveSelector'; import { selectAllTargets } from './DriveSelector';
import FlashSvg from '../../../assets/flash.svg'; import FlashSvg from '../../../assets/flash.svg';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircle } from '@fortawesome/free-solid-svg-icons';
const COMPLETED_PERCENTAGE = 100; const COMPLETED_PERCENTAGE = 100;
const SPEED_PRECISION = 2; const SPEED_PRECISION = 2;
@ -145,6 +147,8 @@ interface FlashStepProps {
goToSuccess: () => void; goToSuccess: () => void;
source: SourceOptions; source: SourceOptions;
isFlashing: boolean; isFlashing: boolean;
isWebviewShowing: boolean;
style?: React.CSSProperties;
// TODO: factorize // TODO: factorize
step: 'decompressing' | 'flashing' | 'verifying'; step: 'decompressing' | 'flashing' | 'verifying';
percentage: number; percentage: number;
@ -234,10 +238,17 @@ export class FlashStep extends React.PureComponent<
public render() { public render() {
return ( return (
<> <>
<Flex flexDirection="column" alignItems="center"> <Flex
flexDirection="column"
alignItems="start"
style={this.props.style}
>
<FlashSvg <FlashSvg
width="40px" width="40px"
className={this.props.shouldFlashStepBeDisabled ? 'disabled' : ''} className={this.props.shouldFlashStepBeDisabled ? 'disabled' : ''}
style={{
margin: '0 auto',
}}
/> />
<ProgressButton <ProgressButton
@ -274,17 +285,11 @@ export class FlashStep extends React.PureComponent<
)} )}
{Boolean(this.props.failed) && ( {Boolean(this.props.failed) && (
<div className="target-status-wrap"> <Flex color="#fff" alignItems="center" mt={35}>
<div className="target-status-line target-status-failed"> <FontAwesomeIcon color="#ff4444" icon={faCircle} />
<span className="target-status-dot"></span> <Txt ml={10}>{this.props.failed}</Txt>
<span className="target-status-quantity"> <Txt ml={10}>{messages.progress.failed(this.props.failed)}</Txt>
{this.props.failed} </Flex>
</span>
<span className="target-status-message">
{messages.progress.failed(this.props.failed)}{' '}
</span>
</div>
</div>
)} )}
</Flex> </Flex>

View File

@ -41,11 +41,10 @@ import {
IconButton as BaseIcon, IconButton as BaseIcon,
ThemedProvider, ThemedProvider,
} from '../../styled-components'; } from '../../styled-components';
import { middleEllipsis } from '../../utils/middle-ellipsis';
import { bytesToClosestUnit } from '../../../../shared/units'; import { bytesToClosestUnit } from '../../../../shared/units';
import { DriveSelector } from './DriveSelector'; import { DriveSelector, getDriveListLabel } from './DriveSelector';
import { FlashStep } from './Flash'; import { FlashStep } from './Flash';
import EtcherSvg from '../../../assets/etcher.svg'; import EtcherSvg from '../../../assets/etcher.svg';
@ -106,6 +105,7 @@ interface MainPageStateFromStore {
imageSize: number; imageSize: number;
imageName: string; imageName: string;
driveTitle: string; driveTitle: string;
driveLabel: string;
} }
interface MainPageState { interface MainPageState {
@ -142,6 +142,7 @@ export class MainPage extends React.Component<
imageSize: selectionState.getImageSize(), imageSize: selectionState.getImageSize(),
imageName: getImageBasename(), imageName: getImageBasename(),
driveTitle: getDrivesTitle(), driveTitle: getDrivesTitle(),
driveLabel: getDriveListLabel(),
}; };
} }
@ -156,6 +157,8 @@ export class MainPage extends React.Component<
const shouldDriveStepBeDisabled = !this.state.hasImage; const shouldDriveStepBeDisabled = !this.state.hasImage;
const shouldFlashStepBeDisabled = const shouldFlashStepBeDisabled =
!this.state.hasImage || !this.state.hasDrive; !this.state.hasImage || !this.state.hasDrive;
const notFlashingOrSplitView =
!this.state.isFlashing || !this.state.isWebviewShowing;
return ( return (
<> <>
<header <header
@ -164,6 +167,8 @@ export class MainPage extends React.Component<
width: '100%', width: '100%',
padding: '13px 14px', padding: '13px 14px',
textAlign: 'center', textAlign: 'center',
position: 'relative',
zIndex: 1,
}} }}
> >
<span <span
@ -213,49 +218,83 @@ export class MainPage extends React.Component<
/> />
)} )}
<Flex m="110px 55px" justifyContent="space-between"> <Flex
<SourceSelector m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
flashing={this.state.isFlashing} justifyContent="space-between"
afterSelected={(source: SourceOptions) => this.setState({ source })} >
/> {notFlashingOrSplitView && (
<SourceSelector
flashing={this.state.isFlashing}
afterSelected={(source: SourceOptions) =>
this.setState({ source })
}
/>
)}
{(!this.state.isWebviewShowing || !this.state.isFlashing) && ( {notFlashingOrSplitView && (
<Flex> <Flex>
<StepBorder disabled={shouldDriveStepBeDisabled} left /> <StepBorder disabled={shouldDriveStepBeDisabled} left />
</Flex> </Flex>
)} )}
<DriveSelector {notFlashingOrSplitView && (
disabled={shouldDriveStepBeDisabled} <DriveSelector
hasDrive={this.state.hasDrive} disabled={shouldDriveStepBeDisabled}
flashing={this.state.isFlashing} hasDrive={this.state.hasDrive}
/> flashing={this.state.isFlashing}
/>
)}
{(!this.state.isWebviewShowing || !this.state.isFlashing) && ( {notFlashingOrSplitView && (
<Flex> <Flex>
<StepBorder disabled={shouldFlashStepBeDisabled} right /> <StepBorder disabled={shouldFlashStepBeDisabled} right />
</Flex> </Flex>
)} )}
{this.state.isFlashing && this.state.isWebviewShowing && ( {this.state.isFlashing && (
<> <>
<Flex
style={{
position: 'absolute',
top: 0,
left: 0,
width: '36.2vw',
height: '100vh',
zIndex: 1,
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
display: this.state.isWebviewShowing ? 'block' : 'none',
}}
>
<ReducedFlashingInfos
imageLogo={this.state.imageLogo}
imageName={this.state.imageName}
imageSize={
_.isNumber(this.state.imageSize)
? (bytesToClosestUnit(this.state.imageSize) as string)
: ''
}
driveTitle={this.state.driveTitle}
driveLabel={this.state.driveLabel}
style={{
position: 'absolute',
color: '#fff',
left: 35,
top: 72,
}}
/>
</Flex>
<FeaturedProject <FeaturedProject
shouldShow={this.state.isWebviewShowing}
onWebviewShow={(isWebviewShowing: boolean) => { onWebviewShow={(isWebviewShowing: boolean) => {
this.setState({ isWebviewShowing }); this.setState({ isWebviewShowing });
}} }}
/> style={{
<ReducedFlashingInfos position: 'absolute',
imageLogo={this.state.imageLogo} right: 0,
imageName={middleEllipsis(this.state.imageName, 16)} bottom: 0,
imageSize={ width: '63.8vw',
_.isNumber(this.state.imageSize) height: '100vh',
? (bytesToClosestUnit(this.state.imageSize) as string) }}
: ''
}
driveTitle={middleEllipsis(this.state.driveTitle, 16)}
shouldShow={
this.state.isFlashing && this.state.isWebviewShowing
}
/> />
</> </>
)} )}
@ -264,13 +303,15 @@ export class MainPage extends React.Component<
goToSuccess={() => this.setState({ current: 'success' })} goToSuccess={() => this.setState({ current: 'success' })}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
source={this.state.source} source={this.state.source}
isFlashing={flashState.isFlashing()} isFlashing={this.state.isFlashing}
isWebviewShowing={this.state.isWebviewShowing}
step={state.type} step={state.type}
percentage={state.percentage} percentage={state.percentage}
position={state.position} position={state.position}
failed={state.failed} failed={state.failed}
speed={state.speed} speed={state.speed}
eta={state.eta} eta={state.eta}
style={{ zIndex: 1 }}
/> />
</Flex> </Flex>
</> </>

View File

@ -17,73 +17,3 @@
.disabled { .disabled {
opacity: $disabled-opacity; opacity: $disabled-opacity;
} }
.page-main {
flex: 1;
align-self: center;
margin: 20px;
}
.page-main > .col-xs {
height: 165px;
}
.page-main .relative {
position: relative;
}
.page-main .glyphicon {
vertical-align: text-top;
}
.page-main .step-name {
display: flex;
justify-content: center;
align-items: center;
height: 39px;
width: 100%;
font-weight: bold;
color: $palette-theme-primary-foreground;
}
.target-status-wrap {
display: flex;
position: absolute;
top: 62px;
flex-direction: column;
margin: 8px 28px;
align-items: flex-start;
}
.target-status-line {
display: flex;
align-items: baseline;
> .target-status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 10px;
}
&.target-status-successful > .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;
}
> .target-status-message {
color: gray;
margin-left: 10px;
}
}
.space-vertical-large {
position: relative;
}

View File

@ -25,7 +25,6 @@ $disabled-opacity: 0.2;
@import "../../../../node_modules/flexboxgrid/dist/flexboxgrid.css"; @import "../../../../node_modules/flexboxgrid/dist/flexboxgrid.css";
@import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; @import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
@import "./modules/theme"; @import "./modules/theme";
@import "./modules/space";
@import "../pages/main/styles/main"; @import "../pages/main/styles/main";
@import "../pages/finish/styles/finish"; @import "../pages/finish/styles/finish";
@import "./desktop"; @import "./desktop";

View File

@ -1,55 +0,0 @@
/*
* Copyright 2016 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.
*/
$spacing-large: 30px;
$spacing-medium: 15px;
$spacing-small: 10px;
$spacing-tiny: 5px;
.space-medium {
margin: $spacing-medium;
}
.space-vertical-medium {
margin-top: $spacing-medium;
margin-bottom: $spacing-medium;
}
.space-vertical-small {
margin-top: $spacing-small;
margin-bottom: $spacing-small;
}
.space-top-large {
margin-top: $spacing-large;
}
.space-vertical-large {
margin-top: $spacing-large;
margin-bottom: $spacing-large;
}
.space-bottom-medium {
margin-bottom: $spacing-medium;
}
.space-bottom-large {
margin-bottom: $spacing-large;
}
.space-right-tiny {
margin-right: $spacing-tiny;
}

View File

@ -21,6 +21,8 @@ import {
Modal as ModalBase, Modal as ModalBase,
Provider, Provider,
Txt, Txt,
Flex,
FlexProps,
} from 'rendition'; } from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
import { space } from 'styled-system'; import { space } from 'styled-system';
@ -98,15 +100,13 @@ export const Footer = styled(Txt)`
font-size: 10px; font-size: 10px;
`; `;
export const Underline = styled(Txt.span)` export const DetailsText = (props: FlexProps) => (
border-bottom: 1px dotted; <Flex
padding-bottom: 2px; alignItems="center"
`; color={colors.dark.disabled.foreground}
{...props}
export const DetailsText = styled(Txt.p)` />
color: ${colors.dark.disabled.foreground}; );
margin-bottom: 0;
`;
export const Modal = styled((props) => { export const Modal = styled((props) => {
return ( return (

View File

@ -67,6 +67,16 @@ export const colors = {
export const theme = { export const theme = {
colors, colors,
global: {
font: {
size: 16,
},
text: {
medium: {
size: 16,
},
},
},
button: { button: {
border: { border: {
width: '0', width: '0',

View File

@ -19,12 +19,12 @@ import { Dictionary } from 'lodash';
export const progress: Dictionary<(quantity: number) => string> = { export const progress: Dictionary<(quantity: number) => string> = {
successful: (quantity: number) => { successful: (quantity: number) => {
const plural = quantity === 1 ? '' : 's'; const plural = quantity === 1 ? '' : 's';
return `Successful device${plural}`; return `Successful target${plural}`;
}, },
failed: (quantity: number) => { failed: (quantity: number) => {
const plural = quantity === 1 ? '' : 's'; const plural = quantity === 1 ? '' : 's';
return `Failed device${plural}`; return `Failed target${plural}`;
}, },
}; };