mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-29 06:06:33 +00:00
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:
parent
9b71772e35
commit
76086a8f91
@ -21,11 +21,14 @@ import * as analytics from '../../modules/analytics';
|
||||
import { SafeWebview } from '../safe-webview/safe-webview';
|
||||
|
||||
interface FeaturedProjectProps {
|
||||
shouldShow: boolean;
|
||||
onWebviewShow: (isWebviewShowing: boolean) => void;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface FeaturedProjectState {
|
||||
endpoint: string | null;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export class FeaturedProject extends React.Component<
|
||||
@ -34,14 +37,26 @@ export class FeaturedProject extends React.Component<
|
||||
> {
|
||||
constructor(props: FeaturedProjectProps) {
|
||||
super(props);
|
||||
this.state = { endpoint: null };
|
||||
this.state = {
|
||||
endpoint: null,
|
||||
show: false,
|
||||
};
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
try {
|
||||
const endpoint =
|
||||
let endpoint =
|
||||
(await settings.get('featuredProjectEndpoint')) ||
|
||||
'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 });
|
||||
} catch (error) {
|
||||
analytics.logException(error);
|
||||
@ -49,8 +64,16 @@ export class FeaturedProject extends React.Component<
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { style = {} } = this.props;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -14,25 +14,14 @@
|
||||
* 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 * as _ from 'lodash';
|
||||
import outdent from 'outdent';
|
||||
import * as React from 'react';
|
||||
import { Txt, Flex } from 'rendition';
|
||||
import styled from 'styled-components';
|
||||
import { left, position, space, top } from 'styled-system';
|
||||
|
||||
import { progress } from '../../../../shared/messages';
|
||||
import { bytesToMegabytes } from '../../../../shared/units';
|
||||
import { Underline } from '../../styled-components';
|
||||
|
||||
const Div = styled.div<any>`
|
||||
${position}
|
||||
${top}
|
||||
${left}
|
||||
${space}
|
||||
`;
|
||||
|
||||
export function FlashResults({
|
||||
errors,
|
||||
@ -50,15 +39,19 @@ export function FlashResults({
|
||||
};
|
||||
}) {
|
||||
const allDevicesFailed = results.devices.successful === 0;
|
||||
const effectiveSpeed = _.round(
|
||||
bytesToMegabytes(
|
||||
results.sourceMetadata.size /
|
||||
(results.bytesWritten / results.averageFlashingSpeed),
|
||||
),
|
||||
1,
|
||||
);
|
||||
const effectiveSpeed = bytesToMegabytes(
|
||||
results.sourceMetadata.size /
|
||||
(results.bytesWritten / results.averageFlashingSpeed),
|
||||
).toFixed(1);
|
||||
return (
|
||||
<Div position="absolute" left="153px" top="66px">
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '153px',
|
||||
top: '66px',
|
||||
}}
|
||||
>
|
||||
<Flex alignItems="center">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
@ -73,24 +66,22 @@ export function FlashResults({
|
||||
Flash Complete!
|
||||
</Txt>
|
||||
</Flex>
|
||||
<Div className="results" mr="0" mb="0" ml="40px">
|
||||
{_.map(results.devices, (quantity, type) => {
|
||||
<Flex flexDirection="column" mr="0" mb="0" ml="40px">
|
||||
{Object.keys(results.devices).map((type: 'failed' | 'successful') => {
|
||||
const quantity = results.devices[type];
|
||||
return quantity ? (
|
||||
<Underline
|
||||
<Flex
|
||||
color="#fff"
|
||||
alignItems="center"
|
||||
tooltip={type === 'failed' ? errors : undefined}
|
||||
key={type}
|
||||
>
|
||||
<div
|
||||
key={type}
|
||||
className={`target-status-line target-status-${type}`}
|
||||
>
|
||||
<span className="target-status-dot"></span>
|
||||
<span className="target-status-quantity">{quantity}</span>
|
||||
<span className="target-status-message">
|
||||
{progress[type](quantity)}
|
||||
</span>
|
||||
</div>
|
||||
</Underline>
|
||||
<FontAwesomeIcon
|
||||
color={type === 'failed' ? '#ff4444' : '#1ac135'}
|
||||
icon={faCircle}
|
||||
/>
|
||||
<Txt ml={10}>{quantity}</Txt>
|
||||
<Txt ml={10}>{progress[type](quantity)}</Txt>
|
||||
</Flex>
|
||||
) : null;
|
||||
})}
|
||||
{!allDevicesFailed && (
|
||||
@ -109,7 +100,7 @@ export function FlashResults({
|
||||
Effective speed: {effectiveSpeed} MB/s
|
||||
</Txt>
|
||||
)}
|
||||
</Div>
|
||||
</Div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -113,6 +113,9 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
||||
warning={this.props.warning}
|
||||
onClick={this.props.callback}
|
||||
disabled={this.props.disabled}
|
||||
style={{
|
||||
marginTop: 30,
|
||||
}}
|
||||
>
|
||||
Flash!
|
||||
</StepButton>
|
||||
|
@ -15,51 +15,20 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { default as styled } from 'styled-components';
|
||||
import { color } from 'styled-system';
|
||||
|
||||
import { SVGIcon } from '../svg-icon/svg-icon';
|
||||
import { Flex, Txt } from 'rendition';
|
||||
|
||||
import DriveSvg from '../../../assets/drive.svg';
|
||||
import ImageSvg from '../../../assets/image.svg';
|
||||
|
||||
const Div = styled.div`
|
||||
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}
|
||||
`;
|
||||
import { SVGIcon } from '../svg-icon/svg-icon';
|
||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||
|
||||
interface ReducedFlashingInfosProps {
|
||||
imageLogo: string;
|
||||
imageName: string;
|
||||
imageSize: string;
|
||||
driveTitle: string;
|
||||
shouldShow: boolean;
|
||||
driveLabel: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export class ReducedFlashingInfos extends React.Component<
|
||||
@ -71,24 +40,36 @@ export class ReducedFlashingInfos extends React.Component<
|
||||
}
|
||||
|
||||
public render() {
|
||||
return this.props.shouldShow ? (
|
||||
<Div>
|
||||
<Span className="step-name">
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
style={this.props.style ? this.props.style : undefined}
|
||||
>
|
||||
<Flex mb={16}>
|
||||
<SVGIcon
|
||||
disabled
|
||||
width="20px"
|
||||
width="21px"
|
||||
height="21px"
|
||||
contents={this.props.imageLogo}
|
||||
fallback={<ImageSvg className="disabled" width="20px" />}
|
||||
fallback={ImageSvg}
|
||||
style={{ marginRight: 9 }}
|
||||
/>
|
||||
<Span>{this.props.imageName}</Span>
|
||||
<Span color="#7e8085">{this.props.imageSize}</Span>
|
||||
</Span>
|
||||
<Txt
|
||||
style={{ marginRight: 9 }}
|
||||
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">
|
||||
<DriveSvg className="disabled" width="20px" />
|
||||
<Span>{this.props.driveTitle}</Span>
|
||||
</Span>
|
||||
</Div>
|
||||
) : null;
|
||||
<Flex>
|
||||
<DriveSvg width="21px" height="21px" style={{ marginRight: 9 }} />
|
||||
<Txt tooltip={{ text: this.props.driveLabel, placement: 'right' }}>
|
||||
{middleEllipsis(this.props.driveTitle, 16)}
|
||||
</Txt>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ interface SafeWebviewProps {
|
||||
refreshNow?: boolean;
|
||||
// Webview lifecycle event
|
||||
onWebviewShow?: (isWebviewShowing: boolean) => void;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface SafeWebviewState {
|
||||
@ -109,15 +110,18 @@ export class SafeWebview extends React.PureComponent<
|
||||
}
|
||||
|
||||
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 (
|
||||
<webview
|
||||
ref={this.webviewRef}
|
||||
partition={ELECTRON_SESSION}
|
||||
style={{
|
||||
flex: this.state.shouldShow ? undefined : '0 1',
|
||||
width: this.state.shouldShow ? undefined : '0',
|
||||
height: this.state.shouldShow ? undefined : '0',
|
||||
}}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -14,7 +14,11 @@
|
||||
* 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 { sourceDestination } from 'etcher-sdk';
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
@ -480,7 +484,10 @@ export class SourceSelector extends React.Component<
|
||||
>
|
||||
<SVGIcon
|
||||
contents={imageLogo}
|
||||
fallback={<ImageSvg width="40px" />}
|
||||
fallback={ImageSvg}
|
||||
style={{
|
||||
marginBottom: 30,
|
||||
}}
|
||||
/>
|
||||
|
||||
{hasImage ? (
|
||||
@ -525,10 +532,10 @@ export class SourceSelector extends React.Component<
|
||||
<Modal
|
||||
titleElement={
|
||||
<span>
|
||||
<span
|
||||
style={{ color: '#d9534f' }}
|
||||
className="glyphicon glyphicon-exclamation-sign"
|
||||
></span>{' '}
|
||||
<FontAwesomeIcon
|
||||
style={{ color: '#fca321' }}
|
||||
icon={faExclamationTriangle}
|
||||
/>{' '}
|
||||
<span>{this.state.warning.title}</span>
|
||||
</span>
|
||||
}
|
||||
|
@ -39,13 +39,14 @@ function tryParseSVGContents(contents?: string): string | undefined {
|
||||
interface SVGIconProps {
|
||||
// List of embedded SVG contents to be tried in succession if any fails
|
||||
contents: string;
|
||||
fallback: JSX.Element;
|
||||
fallback: React.FunctionComponent<React.SVGProps<HTMLOrSVGElement>>;
|
||||
// SVG image width unit
|
||||
width?: string;
|
||||
// SVG image height unit
|
||||
height?: string;
|
||||
// Should the element visually appear grayed out and disabled?
|
||||
disabled?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,17 +55,19 @@ interface SVGIconProps {
|
||||
export class SVGIcon extends React.PureComponent<SVGIconProps> {
|
||||
public render() {
|
||||
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) {
|
||||
const width = this.props.width || DEFAULT_SIZE;
|
||||
const height = this.props.height || DEFAULT_SIZE;
|
||||
return (
|
||||
<img
|
||||
className={this.props.disabled ? 'disabled' : ''}
|
||||
style={{ width, height }}
|
||||
style={style}
|
||||
src={svgData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return this.props.fallback;
|
||||
const { fallback: FallbackSVG } = this.props;
|
||||
return <FallbackSVG style={style} />;
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,8 @@
|
||||
*/
|
||||
|
||||
import { Drive as DrivelistDrive } from 'drivelist';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Txt } from 'rendition';
|
||||
import { default as styled } from 'styled-components';
|
||||
import { Txt, Flex, FlexProps } from 'rendition';
|
||||
|
||||
import {
|
||||
getDriveImageCompatibilityStatuses,
|
||||
@ -33,10 +31,8 @@ import {
|
||||
StepNameButton,
|
||||
} from '../../styled-components';
|
||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||
|
||||
const TargetDetail = styled((props) => <Txt.span {...props}></Txt.span>)`
|
||||
float: ${({ float }) => float};
|
||||
`;
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
interface TargetSelectorProps {
|
||||
targets: any[];
|
||||
@ -49,24 +45,26 @@ interface TargetSelectorProps {
|
||||
image: Image;
|
||||
}
|
||||
|
||||
function DriveCompatibilityWarning(props: {
|
||||
function DriveCompatibilityWarning({
|
||||
drive,
|
||||
image,
|
||||
...props
|
||||
}: {
|
||||
drive: DrivelistDrive;
|
||||
image: Image;
|
||||
}) {
|
||||
} & FlexProps) {
|
||||
const compatibilityWarnings = getDriveImageCompatibilityStatuses(
|
||||
props.drive,
|
||||
props.image,
|
||||
drive,
|
||||
image,
|
||||
);
|
||||
if (compatibilityWarnings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const messages = _.map(compatibilityWarnings, 'message');
|
||||
const messages = compatibilityWarnings.map((warning) => warning.message);
|
||||
return (
|
||||
<Txt.span
|
||||
className="glyphicon glyphicon-exclamation-sign"
|
||||
ml={2}
|
||||
tooltip={messages.join(', ')}
|
||||
/>
|
||||
<Flex tooltip={messages.join(', ')} {...props}>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,7 +84,11 @@ export function TargetSelector(props: TargetSelectorProps) {
|
||||
</ChangeButton>
|
||||
)}
|
||||
<DetailsText>
|
||||
<DriveCompatibilityWarning drive={target} image={props.image} />
|
||||
<DriveCompatibilityWarning
|
||||
drive={target}
|
||||
image={props.image}
|
||||
mr={2}
|
||||
/>
|
||||
{bytesToClosestUnit(target.size)}
|
||||
</DetailsText>
|
||||
</>
|
||||
@ -104,15 +106,13 @@ export function TargetSelector(props: TargetSelectorProps) {
|
||||
} ${bytesToClosestUnit(target.size)}`}
|
||||
px={21}
|
||||
>
|
||||
<Txt.span>
|
||||
<DriveCompatibilityWarning drive={target} image={props.image} />
|
||||
<TargetDetail float="left">
|
||||
{middleEllipsis(target.description, 14)}
|
||||
</TargetDetail>
|
||||
<TargetDetail float="right">
|
||||
{bytesToClosestUnit(target.size)}
|
||||
</TargetDetail>
|
||||
</Txt.span>
|
||||
<DriveCompatibilityWarning
|
||||
drive={target}
|
||||
image={props.image}
|
||||
mr={2}
|
||||
/>
|
||||
<Txt mr={2}>{middleEllipsis(target.description, 14)}</Txt>
|
||||
<Txt>{bytesToClosestUnit(target.size)}</Txt>
|
||||
</DetailsText>,
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import { observe } from '../../models/store';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import DriveSvg from '../../../assets/drive.svg';
|
||||
|
||||
const getDriveListLabel = () => {
|
||||
export const getDriveListLabel = () => {
|
||||
return getSelectedDrives()
|
||||
.map((drive: any) => {
|
||||
return `${drive.description} (${drive.displayName})`;
|
||||
@ -107,7 +107,13 @@ export const DriveSelector = ({
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" alignItems="center">
|
||||
<DriveSvg className={disabled ? 'disabled' : ''} width="40px" />
|
||||
<DriveSvg
|
||||
className={disabled ? 'disabled' : ''}
|
||||
width="40px"
|
||||
style={{
|
||||
marginBottom: 30,
|
||||
}}
|
||||
/>
|
||||
|
||||
<TargetSelector
|
||||
disabled={disabled}
|
||||
|
@ -34,6 +34,8 @@ import * as notification from '../../os/notification';
|
||||
import { selectAllTargets } from './DriveSelector';
|
||||
|
||||
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 SPEED_PRECISION = 2;
|
||||
@ -145,6 +147,8 @@ interface FlashStepProps {
|
||||
goToSuccess: () => void;
|
||||
source: SourceOptions;
|
||||
isFlashing: boolean;
|
||||
isWebviewShowing: boolean;
|
||||
style?: React.CSSProperties;
|
||||
// TODO: factorize
|
||||
step: 'decompressing' | 'flashing' | 'verifying';
|
||||
percentage: number;
|
||||
@ -234,10 +238,17 @@ export class FlashStep extends React.PureComponent<
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Flex flexDirection="column" alignItems="center">
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
alignItems="start"
|
||||
style={this.props.style}
|
||||
>
|
||||
<FlashSvg
|
||||
width="40px"
|
||||
className={this.props.shouldFlashStepBeDisabled ? 'disabled' : ''}
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ProgressButton
|
||||
@ -274,17 +285,11 @@ export class FlashStep extends React.PureComponent<
|
||||
)}
|
||||
|
||||
{Boolean(this.props.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">
|
||||
{this.props.failed}
|
||||
</span>
|
||||
<span className="target-status-message">
|
||||
{messages.progress.failed(this.props.failed)}{' '}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Flex color="#fff" alignItems="center" mt={35}>
|
||||
<FontAwesomeIcon color="#ff4444" icon={faCircle} />
|
||||
<Txt ml={10}>{this.props.failed}</Txt>
|
||||
<Txt ml={10}>{messages.progress.failed(this.props.failed)}</Txt>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
|
@ -41,11 +41,10 @@ import {
|
||||
IconButton as BaseIcon,
|
||||
ThemedProvider,
|
||||
} from '../../styled-components';
|
||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||
|
||||
import { bytesToClosestUnit } from '../../../../shared/units';
|
||||
|
||||
import { DriveSelector } from './DriveSelector';
|
||||
import { DriveSelector, getDriveListLabel } from './DriveSelector';
|
||||
import { FlashStep } from './Flash';
|
||||
|
||||
import EtcherSvg from '../../../assets/etcher.svg';
|
||||
@ -106,6 +105,7 @@ interface MainPageStateFromStore {
|
||||
imageSize: number;
|
||||
imageName: string;
|
||||
driveTitle: string;
|
||||
driveLabel: string;
|
||||
}
|
||||
|
||||
interface MainPageState {
|
||||
@ -142,6 +142,7 @@ export class MainPage extends React.Component<
|
||||
imageSize: selectionState.getImageSize(),
|
||||
imageName: getImageBasename(),
|
||||
driveTitle: getDrivesTitle(),
|
||||
driveLabel: getDriveListLabel(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -156,6 +157,8 @@ export class MainPage extends React.Component<
|
||||
const shouldDriveStepBeDisabled = !this.state.hasImage;
|
||||
const shouldFlashStepBeDisabled =
|
||||
!this.state.hasImage || !this.state.hasDrive;
|
||||
const notFlashingOrSplitView =
|
||||
!this.state.isFlashing || !this.state.isWebviewShowing;
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
@ -164,6 +167,8 @@ export class MainPage extends React.Component<
|
||||
width: '100%',
|
||||
padding: '13px 14px',
|
||||
textAlign: 'center',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
@ -213,49 +218,83 @@ export class MainPage extends React.Component<
|
||||
/>
|
||||
)}
|
||||
|
||||
<Flex m="110px 55px" justifyContent="space-between">
|
||||
<SourceSelector
|
||||
flashing={this.state.isFlashing}
|
||||
afterSelected={(source: SourceOptions) => this.setState({ source })}
|
||||
/>
|
||||
<Flex
|
||||
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{notFlashingOrSplitView && (
|
||||
<SourceSelector
|
||||
flashing={this.state.isFlashing}
|
||||
afterSelected={(source: SourceOptions) =>
|
||||
this.setState({ source })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(!this.state.isWebviewShowing || !this.state.isFlashing) && (
|
||||
{notFlashingOrSplitView && (
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldDriveStepBeDisabled} left />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<DriveSelector
|
||||
disabled={shouldDriveStepBeDisabled}
|
||||
hasDrive={this.state.hasDrive}
|
||||
flashing={this.state.isFlashing}
|
||||
/>
|
||||
{notFlashingOrSplitView && (
|
||||
<DriveSelector
|
||||
disabled={shouldDriveStepBeDisabled}
|
||||
hasDrive={this.state.hasDrive}
|
||||
flashing={this.state.isFlashing}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(!this.state.isWebviewShowing || !this.state.isFlashing) && (
|
||||
{notFlashingOrSplitView && (
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldFlashStepBeDisabled} right />
|
||||
</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
|
||||
shouldShow={this.state.isWebviewShowing}
|
||||
onWebviewShow={(isWebviewShowing: boolean) => {
|
||||
this.setState({ isWebviewShowing });
|
||||
}}
|
||||
/>
|
||||
<ReducedFlashingInfos
|
||||
imageLogo={this.state.imageLogo}
|
||||
imageName={middleEllipsis(this.state.imageName, 16)}
|
||||
imageSize={
|
||||
_.isNumber(this.state.imageSize)
|
||||
? (bytesToClosestUnit(this.state.imageSize) as string)
|
||||
: ''
|
||||
}
|
||||
driveTitle={middleEllipsis(this.state.driveTitle, 16)}
|
||||
shouldShow={
|
||||
this.state.isFlashing && this.state.isWebviewShowing
|
||||
}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '63.8vw',
|
||||
height: '100vh',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@ -264,13 +303,15 @@ export class MainPage extends React.Component<
|
||||
goToSuccess={() => this.setState({ current: 'success' })}
|
||||
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
||||
source={this.state.source}
|
||||
isFlashing={flashState.isFlashing()}
|
||||
isFlashing={this.state.isFlashing}
|
||||
isWebviewShowing={this.state.isWebviewShowing}
|
||||
step={state.type}
|
||||
percentage={state.percentage}
|
||||
position={state.position}
|
||||
failed={state.failed}
|
||||
speed={state.speed}
|
||||
eta={state.eta}
|
||||
style={{ zIndex: 1 }}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
@ -17,73 +17,3 @@
|
||||
.disabled {
|
||||
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;
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ $disabled-opacity: 0.2;
|
||||
@import "../../../../node_modules/flexboxgrid/dist/flexboxgrid.css";
|
||||
@import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
@import "./modules/theme";
|
||||
@import "./modules/space";
|
||||
@import "../pages/main/styles/main";
|
||||
@import "../pages/finish/styles/finish";
|
||||
@import "./desktop";
|
||||
|
@ -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;
|
||||
}
|
@ -21,6 +21,8 @@ import {
|
||||
Modal as ModalBase,
|
||||
Provider,
|
||||
Txt,
|
||||
Flex,
|
||||
FlexProps,
|
||||
} from 'rendition';
|
||||
import styled from 'styled-components';
|
||||
import { space } from 'styled-system';
|
||||
@ -98,15 +100,13 @@ export const Footer = styled(Txt)`
|
||||
font-size: 10px;
|
||||
`;
|
||||
|
||||
export const Underline = styled(Txt.span)`
|
||||
border-bottom: 1px dotted;
|
||||
padding-bottom: 2px;
|
||||
`;
|
||||
|
||||
export const DetailsText = styled(Txt.p)`
|
||||
color: ${colors.dark.disabled.foreground};
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
export const DetailsText = (props: FlexProps) => (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
color={colors.dark.disabled.foreground}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Modal = styled((props) => {
|
||||
return (
|
||||
|
@ -67,6 +67,16 @@ export const colors = {
|
||||
|
||||
export const theme = {
|
||||
colors,
|
||||
global: {
|
||||
font: {
|
||||
size: 16,
|
||||
},
|
||||
text: {
|
||||
medium: {
|
||||
size: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
button: {
|
||||
border: {
|
||||
width: '0',
|
||||
|
@ -19,12 +19,12 @@ import { Dictionary } from 'lodash';
|
||||
export const progress: Dictionary<(quantity: number) => string> = {
|
||||
successful: (quantity: number) => {
|
||||
const plural = quantity === 1 ? '' : 's';
|
||||
return `Successful device${plural}`;
|
||||
return `Successful target${plural}`;
|
||||
},
|
||||
|
||||
failed: (quantity: number) => {
|
||||
const plural = quantity === 1 ? '' : 's';
|
||||
return `Failed device${plural}`;
|
||||
return `Failed target${plural}`;
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user