diff --git a/lib/gui/app/components/featured-project/featured-project.tsx b/lib/gui/app/components/featured-project/featured-project.tsx index 9aab2d2c..ef4a80ef 100644 --- a/lib/gui/app/components/featured-project/featured-project.tsx +++ b/lib/gui/app/components/featured-project/featured-project.tsx @@ -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 ? ( - + ) : null; } } diff --git a/lib/gui/app/components/flash-results/flash-results.tsx b/lib/gui/app/components/flash-results/flash-results.tsx index 4c7000a5..a3e55d4b 100644 --- a/lib/gui/app/components/flash-results/flash-results.tsx +++ b/lib/gui/app/components/flash-results/flash-results.tsx @@ -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` - ${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 ( -
+ -
- {_.map(results.devices, (quantity, type) => { + + {Object.keys(results.devices).map((type: 'failed' | 'successful') => { + const quantity = results.devices[type]; return quantity ? ( - -
- - {quantity} - - {progress[type](quantity)} - -
-
+ + {quantity} + {progress[type](quantity)} +
) : null; })} {!allDevicesFailed && ( @@ -109,7 +100,7 @@ export function FlashResults({ Effective speed: {effectiveSpeed} MB/s )} -
-
+ + ); } diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 9c31aa26..d9466ed3 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -113,6 +113,9 @@ export class ProgressButton extends React.PureComponent { warning={this.props.warning} onClick={this.props.callback} disabled={this.props.disabled} + style={{ + marginTop: 30, + }} > Flash! diff --git a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx index 35d655c8..3fc46b03 100644 --- a/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx +++ b/lib/gui/app/components/reduced-flashing-infos/reduced-flashing-infos.tsx @@ -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 ? ( -
- + return ( + + } + fallback={ImageSvg} + style={{ marginRight: 9 }} /> - {this.props.imageName} - {this.props.imageSize} - + + {middleEllipsis(this.props.imageName, 16)} + + {this.props.imageSize} + - - - {this.props.driveTitle} - -
- ) : null; + + + + {middleEllipsis(this.props.driveTitle, 16)} + + + + ); } } diff --git a/lib/gui/app/components/safe-webview/safe-webview.tsx b/lib/gui/app/components/safe-webview/safe-webview.tsx index 234d7f23..961acc00 100644 --- a/lib/gui/app/components/safe-webview/safe-webview.tsx +++ b/lib/gui/app/components/safe-webview/safe-webview.tsx @@ -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 ( ); } diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index e9051c54..cbd25b4f 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -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< > } + fallback={ImageSvg} + style={{ + marginBottom: 30, + }} /> {hasImage ? ( @@ -525,10 +532,10 @@ export class SourceSelector extends React.Component< - {' '} + {' '} {this.state.warning.title} } diff --git a/lib/gui/app/components/svg-icon/svg-icon.tsx b/lib/gui/app/components/svg-icon/svg-icon.tsx index 077c5fd3..bcf2d777 100644 --- a/lib/gui/app/components/svg-icon/svg-icon.tsx +++ b/lib/gui/app/components/svg-icon/svg-icon.tsx @@ -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>; // 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 { 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 ( ); } - return this.props.fallback; + const { fallback: FallbackSVG } = this.props; + return ; } } diff --git a/lib/gui/app/components/target-selector/target-selector-button.tsx b/lib/gui/app/components/target-selector/target-selector-button.tsx index fe35b9b9..2d9e4315 100644 --- a/lib/gui/app/components/target-selector/target-selector-button.tsx +++ b/lib/gui/app/components/target-selector/target-selector-button.tsx @@ -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) => )` - 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 ( - + + + ); } @@ -86,7 +84,11 @@ export function TargetSelector(props: TargetSelectorProps) { )} - + {bytesToClosestUnit(target.size)} @@ -104,15 +106,13 @@ export function TargetSelector(props: TargetSelectorProps) { } ${bytesToClosestUnit(target.size)}`} px={21} > - - - - {middleEllipsis(target.description, 14)} - - - {bytesToClosestUnit(target.size)} - - + + {middleEllipsis(target.description, 14)} + {bytesToClosestUnit(target.size)} , ); } diff --git a/lib/gui/app/pages/finish/styles/_finish.scss b/lib/gui/app/pages/finish/styles/_finish.scss index 34ce3c91..ce4a12a6 100644 --- a/lib/gui/app/pages/finish/styles/_finish.scss +++ b/lib/gui/app/pages/finish/styles/_finish.scss @@ -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; -} diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/pages/main/DriveSelector.tsx index b91ec36c..f89e4d98 100644 --- a/lib/gui/app/pages/main/DriveSelector.tsx +++ b/lib/gui/app/pages/main/DriveSelector.tsx @@ -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 ( - + 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 ( <> - + -
- - - {this.props.failed} - - - {messages.progress.failed(this.props.failed)}{' '} - -
- + + + {this.props.failed} + {messages.progress.failed(this.props.failed)} + )}
diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index 7a8fa361..737baad4 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -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 ( <>
)} - - this.setState({ source })} - /> + + {notFlashingOrSplitView && ( + + this.setState({ source }) + } + /> + )} - {(!this.state.isWebviewShowing || !this.state.isFlashing) && ( + {notFlashingOrSplitView && ( )} - + {notFlashingOrSplitView && ( + + )} - {(!this.state.isWebviewShowing || !this.state.isFlashing) && ( + {notFlashingOrSplitView && ( )} - {this.state.isFlashing && this.state.isWebviewShowing && ( + {this.state.isFlashing && ( <> + + + { this.setState({ isWebviewShowing }); }} - /> - )} @@ -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 }} /> diff --git a/lib/gui/app/pages/main/styles/_main.scss b/lib/gui/app/pages/main/styles/_main.scss index a8e2d9ff..600d71e1 100644 --- a/lib/gui/app/pages/main/styles/_main.scss +++ b/lib/gui/app/pages/main/styles/_main.scss @@ -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; -} diff --git a/lib/gui/app/scss/main.scss b/lib/gui/app/scss/main.scss index ee934aa3..74e6e193 100644 --- a/lib/gui/app/scss/main.scss +++ b/lib/gui/app/scss/main.scss @@ -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"; diff --git a/lib/gui/app/scss/modules/_space.scss b/lib/gui/app/scss/modules/_space.scss deleted file mode 100644 index c4151951..00000000 --- a/lib/gui/app/scss/modules/_space.scss +++ /dev/null @@ -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; -} diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index 1ef3fc80..edeee4ef 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -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) => ( + +); export const Modal = styled((props) => { return ( diff --git a/lib/gui/app/theme.ts b/lib/gui/app/theme.ts index 46e744f9..122ada09 100644 --- a/lib/gui/app/theme.ts +++ b/lib/gui/app/theme.ts @@ -67,6 +67,16 @@ export const colors = { export const theme = { colors, + global: { + font: { + size: 16, + }, + text: { + medium: { + size: 16, + }, + }, + }, button: { border: { width: '0', diff --git a/lib/shared/messages.ts b/lib/shared/messages.ts index ff32c937..6272b8ea 100644 --- a/lib/shared/messages.ts +++ b/lib/shared/messages.ts @@ -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}`; }, };