patch: fix styles

This commit is contained in:
Edwin Joassart 2023-10-19 14:32:23 +02:00
parent ed5b9ecacd
commit d952d5129e
7 changed files with 298 additions and 298 deletions

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import ExclamationTriangleSvg from "@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg"; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import ChevronDownSvg from "@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg"; import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import * as sourceDestination from "etcher-sdk/build/source-destination/"; import * as sourceDestination from 'etcher-sdk/build/source-destination/';
import * as React from "react"; import * as React from 'react';
import { Flex, ModalProps, Txt, Badge, Link, TableColumn } from "rendition"; import { Flex, ModalProps, Txt, Badge, Link, TableColumn } from 'rendition';
import styled from "styled-components"; import styled from 'styled-components';
import { import {
getDriveImageCompatibilityStatuses, getDriveImageCompatibilityStatuses,
@ -27,24 +27,24 @@ import {
DriveStatus, DriveStatus,
DrivelistDrive, DrivelistDrive,
isDriveSizeLarge, isDriveSizeLarge,
} from "../../../../shared/drive-constraints"; } from '../../../../shared/drive-constraints';
import { compatibility, warning } from "../../../../shared/messages"; import { compatibility, warning } from '../../../../shared/messages';
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import { getDrives, hasAvailableDrives } from "../../models/available-drives"; import { getDrives, hasAvailableDrives } from '../../models/available-drives';
import { getImage, isDriveSelected } from "../../models/selection-state"; import { getImage, isDriveSelected } from '../../models/selection-state';
import { store } from "../../models/store"; import { store } from '../../models/store';
import { logEvent, logException } from "../../modules/analytics"; import { logEvent, logException } from '../../modules/analytics';
import { open as openExternal } from "../../os/open-external/services/open-external"; import { open as openExternal } from '../../os/open-external/services/open-external';
import { import {
Alert, Alert,
GenericTableProps, GenericTableProps,
Modal, Modal,
Table, Table,
} from "../../styled-components"; } from '../../styled-components';
import { SourceMetadata } from "../source-selector/source-selector"; import { SourceMetadata } from '../source-selector/source-selector';
import { middleEllipsis } from "../../utils/middle-ellipsis"; import { middleEllipsis } from '../../utils/middle-ellipsis';
import * as i18next from "i18next"; import * as i18next from 'i18next';
interface UsbbootDrive extends sourceDestination.UsbbootDrive { interface UsbbootDrive extends sourceDestination.UsbbootDrive {
progress: number; progress: number;
@ -70,7 +70,7 @@ function isDriverlessDrive(drive: Drive): drive is DriverlessDrive {
} }
function isDrivelistDrive(drive: Drive): drive is DrivelistDrive { function isDrivelistDrive(drive: Drive): drive is DrivelistDrive {
return typeof (drive as DrivelistDrive).size === "number"; return typeof (drive as DrivelistDrive).size === 'number';
} }
const DrivesTable = styled((props: GenericTableProps<Drive>) => ( const DrivesTable = styled((props: GenericTableProps<Drive>) => (
@ -119,7 +119,7 @@ const InitProgress = styled(
props?: React.ProgressHTMLAttributes<Element>; props?: React.ProgressHTMLAttributes<Element>;
}) => { }) => {
return <progress max="100" value={value} {...props} />; return <progress max="100" value={value} {...props} />;
} },
)` )`
/* Reset the default appearance */ /* Reset the default appearance */
appearance: none; appearance: none;
@ -138,7 +138,7 @@ const InitProgress = styled(
`; `;
export interface DriveSelectorProps export interface DriveSelectorProps
extends Omit<ModalProps, "done" | "cancel" | "onSelect"> { extends Omit<ModalProps, 'done' | 'cancel' | 'onSelect'> {
write: boolean; write: boolean;
multipleSelection: boolean; multipleSelection: boolean;
showWarnings?: boolean; showWarnings?: boolean;
@ -189,8 +189,8 @@ export class DriveSelector extends React.Component<
this.tableColumns = [ this.tableColumns = [
{ {
field: "description", field: 'description',
label: i18next.t("drives.name"), label: i18next.t('drives.name'),
render: (description: string, drive: Drive) => { render: (description: string, drive: Drive) => {
if (isDrivelistDrive(drive)) { if (isDrivelistDrive(drive)) {
const isLargeDrive = isDriveSizeLarge(drive); const isLargeDrive = isDriveSizeLarge(drive);
@ -201,7 +201,7 @@ export class DriveSelector extends React.Component<
{hasWarnings && ( {hasWarnings && (
<ExclamationTriangleSvg <ExclamationTriangleSvg
height="1em" height="1em"
fill={drive.isSystem ? "#fca321" : "#8f9297"} fill={drive.isSystem ? '#fca321' : '#8f9297'}
/> />
)} )}
<Txt ml={(hasWarnings && 8) || 0}> <Txt ml={(hasWarnings && 8) || 0}>
@ -214,9 +214,9 @@ export class DriveSelector extends React.Component<
}, },
}, },
{ {
field: "description", field: 'description',
key: "size", key: 'size',
label: i18next.t("drives.size"), label: i18next.t('drives.size'),
render: (_description: string, drive: Drive) => { render: (_description: string, drive: Drive) => {
if (isDrivelistDrive(drive) && drive.size !== null) { if (isDrivelistDrive(drive) && drive.size !== null) {
return prettyBytes(drive.size); return prettyBytes(drive.size);
@ -224,17 +224,17 @@ export class DriveSelector extends React.Component<
}, },
}, },
{ {
field: "description", field: 'description',
key: "link", key: 'link',
label: i18next.t("drives.location"), label: i18next.t('drives.location'),
render: (_description: string, drive: Drive) => { render: (_description: string, drive: Drive) => {
return ( return (
<Txt> <Txt>
{drive.displayName} {drive.displayName}
{isDriverlessDrive(drive) && ( {isDriverlessDrive(drive) && (
<> <>
{" "} {' '}
-{" "} -{' '}
<b> <b>
<a onClick={() => this.installMissingDrivers(drive)}> <a onClick={() => this.installMissingDrivers(drive)}>
{drive.linkCTA} {drive.linkCTA}
@ -247,8 +247,8 @@ export class DriveSelector extends React.Component<
}, },
}, },
{ {
field: "description", field: 'description',
key: "extra", key: 'extra',
// We use an empty React fragment otherwise it uses the field name as label // We use an empty React fragment otherwise it uses the field name as label
label: <></>, label: <></>,
render: (_description: string, drive: Drive) => { render: (_description: string, drive: Drive) => {
@ -300,7 +300,7 @@ export class DriveSelector extends React.Component<
private warningFromStatus( private warningFromStatus(
status: string, status: string,
drive: { device: string; size: number } drive: { device: string; size: number },
) { ) {
switch (status) { switch (status) {
case compatibility.containsImage(): case compatibility.containsImage():
@ -320,7 +320,7 @@ export class DriveSelector extends React.Component<
const statuses: DriveStatus[] = getDriveImageCompatibilityStatuses( const statuses: DriveStatus[] = getDriveImageCompatibilityStatuses(
drive, drive,
this.state.image, this.state.image,
this.props.write this.props.write,
).slice(0, 2); ).slice(0, 2);
return ( return (
// the column render fn expects a single Element // the column render fn expects a single Element
@ -336,7 +336,7 @@ export class DriveSelector extends React.Component<
key={status.message} key={status.message}
shade={badgeShade} shade={badgeShade}
mr="8px" mr="8px"
tooltip={this.props.showWarnings ? warningMessage : ""} tooltip={this.props.showWarnings ? warningMessage : ''}
> >
{status.message} {status.message}
</Badge> </Badge>
@ -348,7 +348,7 @@ export class DriveSelector extends React.Component<
private installMissingDrivers(drive: DriverlessDrive) { private installMissingDrivers(drive: DriverlessDrive) {
if (drive.link) { if (drive.link) {
logEvent("Open driver link modal", { logEvent('Open driver link modal', {
url: drive.link, url: drive.link,
}); });
this.setState({ missingDriversModal: { drive } }); this.setState({ missingDriversModal: { drive } });
@ -400,14 +400,14 @@ export class DriveSelector extends React.Component<
color="#5b82a7" color="#5b82a7"
style={{ fontWeight: 600 }} style={{ fontWeight: 600 }}
> >
{i18next.t("drives.find", { length: drives.length })} {i18next.t('drives.find', { length: drives.length })}
</Txt> </Txt>
</Flex> </Flex>
} }
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>} titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
cancel={() => cancel(this.originalList)} cancel={() => cancel(this.originalList)}
done={() => done(selectedList)} done={() => done(selectedList)}
action={i18next.t("drives.select", { select: selectedList.length })} action={i18next.t('drives.select', { select: selectedList.length })}
primaryButtonProps={{ primaryButtonProps={{
primary: !showWarnings, primary: !showWarnings,
warning: showWarnings, warning: showWarnings,
@ -441,7 +441,7 @@ export class DriveSelector extends React.Component<
data={displayedDrives} data={displayedDrives}
disabledRows={disabledDrives} disabledRows={disabledDrives}
getRowClass={(row: Drive) => getRowClass={(row: Drive) =>
isDrivelistDrive(row) && row.isSystem ? ["system"] : [] isDrivelistDrive(row) && row.isSystem ? ['system'] : []
} }
rowKey="displayName" rowKey="displayName"
onCheck={(rows: Drive[]) => { onCheck={(rows: Drive[]) => {
@ -453,14 +453,14 @@ export class DriveSelector extends React.Component<
const deselecting = selectedList.filter( const deselecting = selectedList.filter(
(selected) => (selected) =>
newSelection.filter( newSelection.filter(
(row) => row.device === selected.device (row) => row.device === selected.device,
).length === 0 ).length === 0,
); );
const selecting = newSelection.filter( const selecting = newSelection.filter(
(row) => (row) =>
selectedList.filter( selectedList.filter(
(selected) => row.device === selected.device (selected) => row.device === selected.device,
).length === 0 ).length === 0,
); );
deselecting.concat(selecting).forEach((row) => { deselecting.concat(selecting).forEach((row) => {
if (this.props.onSelect) { if (this.props.onSelect) {
@ -490,7 +490,7 @@ export class DriveSelector extends React.Component<
this.props.onSelect(row); this.props.onSelect(row);
} }
const index = selectedList.findIndex( const index = selectedList.findIndex(
(d) => d.device === row.device (d) => d.device === row.device,
); );
const newList = this.props.multipleSelection const newList = this.props.multipleSelection
? [...selectedList] ? [...selectedList]
@ -516,7 +516,7 @@ export class DriveSelector extends React.Component<
<Flex alignItems="center"> <Flex alignItems="center">
<ChevronDownSvg height="1em" fill="currentColor" /> <ChevronDownSvg height="1em" fill="currentColor" />
<Txt ml={8}> <Txt ml={8}>
{i18next.t("drives.showHidden", { {i18next.t('drives.showHidden', {
num: numberOfHiddenSystemDrives, num: numberOfHiddenSystemDrives,
})} })}
</Txt> </Txt>
@ -526,8 +526,8 @@ export class DriveSelector extends React.Component<
</> </>
)} )}
{this.props.showWarnings && hasSystemDrives ? ( {this.props.showWarnings && hasSystemDrives ? (
<Alert className="system-drive-alert" style={{ width: "67%" }}> <Alert className="system-drive-alert" style={{ width: '67%' }}>
{i18next.t("drives.systemDriveDanger")} {i18next.t('drives.systemDriveDanger')}
</Alert> </Alert>
) : null} ) : null}
@ -547,13 +547,13 @@ export class DriveSelector extends React.Component<
this.setState({ missingDriversModal: {} }); this.setState({ missingDriversModal: {} });
} }
}} }}
action={i18next.t("yesContinue")} action={i18next.t('yesContinue')}
cancelButtonProps={{ cancelButtonProps={{
children: i18next.t("cancel"), children: i18next.t('cancel'),
}} }}
children={ children={
missingDriversModal.drive.linkMessage || missingDriversModal.drive.linkMessage ||
i18next.t("drives.openInBrowser", { i18next.t('drives.openInBrowser', {
link: missingDriversModal.drive.link, link: missingDriversModal.drive.link,
}) })
} }

View File

@ -1,13 +1,13 @@
import ExclamationTriangleSvg from "@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg"; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import * as _ from "lodash"; import * as _ from 'lodash';
import * as React from "react"; import * as React from 'react';
import { Badge, Flex, Txt, ModalProps } from "rendition"; import { Badge, Flex, Txt, ModalProps } from 'rendition';
import { Modal, ScrollableFlex } from "../../styled-components"; import { Modal, ScrollableFlex } from '../../styled-components';
import { middleEllipsis } from "../../utils/middle-ellipsis"; import { middleEllipsis } from '../../utils/middle-ellipsis';
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import { DriveWithWarnings } from "../../pages/main/Flash"; import { DriveWithWarnings } from '../../pages/main/Flash';
import * as i18next from "i18next"; import * as i18next from 'i18next';
const DriveStatusWarningModal = ({ const DriveStatusWarningModal = ({
done, done,
@ -18,12 +18,12 @@ const DriveStatusWarningModal = ({
isSystem: boolean; isSystem: boolean;
drivesWithWarnings: DriveWithWarnings[]; drivesWithWarnings: DriveWithWarnings[];
}) => { }) => {
let warningSubtitle = i18next.t("drives.largeDriveWarning"); let warningSubtitle = i18next.t('drives.largeDriveWarning');
let warningCta = i18next.t("drives.largeDriveWarningMsg"); let warningCta = i18next.t('drives.largeDriveWarningMsg');
if (isSystem) { if (isSystem) {
warningSubtitle = i18next.t("drives.systemDriveWarning"); warningSubtitle = i18next.t('drives.systemDriveWarning');
warningCta = i18next.t("drives.systemDriveWarningMsg"); warningCta = i18next.t('drives.systemDriveWarningMsg');
} }
return ( return (
<Modal <Modal
@ -34,9 +34,9 @@ const DriveStatusWarningModal = ({
cancelButtonProps={{ cancelButtonProps={{
primary: false, primary: false,
warning: true, warning: true,
children: i18next.t("drives.changeTarget"), children: i18next.t('drives.changeTarget'),
}} }}
action={i18next.t("sure")} action={i18next.t('sure')}
primaryButtonProps={{ primaryButtonProps={{
primary: false, primary: false,
outline: true, outline: true,
@ -51,7 +51,7 @@ const DriveStatusWarningModal = ({
<Flex flexDirection="column"> <Flex flexDirection="column">
<ExclamationTriangleSvg height="2em" fill="#fca321" /> <ExclamationTriangleSvg height="2em" fill="#fca321" />
<Txt fontSize="24px" color="#fca321"> <Txt fontSize="24px" color="#fca321">
{i18next.t("warning")} {i18next.t('warning')}
</Txt> </Txt>
</Flex> </Flex>
<Txt fontSize="24px">{warningSubtitle}</Txt> <Txt fontSize="24px">{warningSubtitle}</Txt>
@ -66,11 +66,11 @@ const DriveStatusWarningModal = ({
{drivesWithWarnings.map((drive, i, array) => ( {drivesWithWarnings.map((drive, i, array) => (
<> <>
<Flex justifyContent="space-between" alignItems="baseline"> <Flex justifyContent="space-between" alignItems="baseline">
<strong>{middleEllipsis(drive.description, 28)}</strong>{" "} <strong>{middleEllipsis(drive.description, 28)}</strong>{' '}
{drive.size && prettyBytes(drive.size) + " "} {drive.size && prettyBytes(drive.size) + ' '}
<Badge shade={5}>{drive.statuses[0].message}</Badge> <Badge shade={5}>{drive.statuses[0].message}</Badge>
</Flex> </Flex>
{i !== array.length - 1 ? <hr style={{ width: "100%" }} /> : null} {i !== array.length - 1 ? <hr style={{ width: '100%' }} /> : null}
</> </>
))} ))}
</ScrollableFlex> </ScrollableFlex>

View File

@ -14,29 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
import CircleSvg from "@fortawesome/fontawesome-free/svgs/solid/circle.svg"; import CircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle.svg';
import CheckCircleSvg from "@fortawesome/fontawesome-free/svgs/solid/circle-check.svg"; import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-check.svg';
import TimesCircleSvg from "@fortawesome/fontawesome-free/svgs/solid/circle-xmark.svg"; import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-xmark.svg';
import * as React from "react"; import * as React from 'react';
import { Flex, FlexProps, Link, TableColumn, Txt } from "rendition"; import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition';
import styled from "styled-components"; import styled from 'styled-components';
import { progress } from "../../../../shared/messages"; import { progress } from '../../../../shared/messages';
import { bytesToMegabytes } from "../../../../shared/units"; import { bytesToMegabytes } from '../../../../shared/units';
import FlashSvg from "../../../assets/flash.svg"; import FlashSvg from '../../../assets/flash.svg';
import { getDrives } from "../../models/available-drives"; import { getDrives } from '../../models/available-drives';
import { resetState } from "../../models/flash-state"; import { resetState } from '../../models/flash-state';
import * as selection from "../../models/selection-state"; import * as selection from '../../models/selection-state';
import { middleEllipsis } from "../../utils/middle-ellipsis"; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { Modal, Table } from "../../styled-components"; import { Modal, Table } from '../../styled-components';
import * as i18next from "i18next"; import * as i18next from 'i18next';
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)` const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
&&& [data-display="table-head"], &&& [data-display='table-head'],
&&& [data-display="table-body"] { &&& [data-display='table-body'] {
> [data-display="table-row"] { > [data-display='table-row'] {
> [data-display="table-cell"] { > [data-display='table-cell'] {
&:first-child { &:first-child {
width: 30%; width: 30%;
} }
@ -58,11 +58,11 @@ const DoneIcon = (props: {
allFailed: boolean; allFailed: boolean;
}) => { }) => {
const svgProps = { const svgProps = {
width: "28px", width: '28px',
fill: props.color, fill: props.color,
style: { style: {
marginTop: "-25px", marginTop: '-25px',
marginLeft: "13px", marginLeft: '13px',
zIndex: 1, zIndex: 1,
}, },
}; };
@ -82,21 +82,21 @@ export interface FlashError extends Error {
function formattedErrors(errors: FlashError[]) { function formattedErrors(errors: FlashError[]) {
return errors return errors
.map((error) => `${error.device}: ${error.message || error.code}`) .map((error) => `${error.device}: ${error.message || error.code}`)
.join("\n"); .join('\n');
} }
const columns: Array<TableColumn<FlashError>> = [ const columns: Array<TableColumn<FlashError>> = [
{ {
field: "description", field: 'description',
label: i18next.t("flash.target"), label: i18next.t('flash.target'),
}, },
{ {
field: "device", field: 'device',
label: i18next.t("flash.location"), label: i18next.t('flash.location'),
}, },
{ {
field: "message", field: 'message',
label: i18next.t("flash.error"), label: i18next.t('flash.error'),
render: (message: string, { code }: FlashError) => { render: (message: string, { code }: FlashError) => {
return message ?? code; return message ?? code;
}, },
@ -118,7 +118,7 @@ function getEffectiveSpeed(results: {
export function FlashResults({ export function FlashResults({
goToMain, goToMain,
image = "", image = '',
errors, errors,
results, results,
skip, skip,
@ -142,7 +142,7 @@ export function FlashResults({
const allFailed = !skip && results?.devices?.successful === 0; const allFailed = !skip && results?.devices?.successful === 0;
const someFailed = results?.devices?.failed !== 0 || errors?.length !== 0; const someFailed = results?.devices?.failed !== 0 || errors?.length !== 0;
const effectiveSpeed = bytesToMegabytes(getEffectiveSpeed(results)).toFixed( const effectiveSpeed = bytesToMegabytes(getEffectiveSpeed(results)).toFixed(
1 1,
); );
return ( return (
<Flex flexDirection="column" {...props}> <Flex flexDirection="column" {...props}>
@ -158,16 +158,16 @@ export function FlashResults({
<DoneIcon <DoneIcon
skipped={skip} skipped={skip}
allFailed={allFailed} allFailed={allFailed}
color={allFailed || someFailed ? "#c6c8c9" : "#1ac135"} color={allFailed || someFailed ? '#c6c8c9' : '#1ac135'}
/> />
<Txt>{middleEllipsis(image, 24)}</Txt> <Txt>{middleEllipsis(image, 24)}</Txt>
</Flex> </Flex>
<Txt fontSize={24} color="#fff" mb="17px"> <Txt fontSize={24} color="#fff" mb="17px">
{allFailed {allFailed
? i18next.t("flash.flashFailed") ? i18next.t('flash.flashFailed')
: i18next.t("flash.flashCompleted")} : i18next.t('flash.flashCompleted')}
</Txt> </Txt>
{skip ? <Txt color="#7e8085">{i18next.t("flash.skip")}</Txt> : null} {skip ? <Txt color="#7e8085">{i18next.t('flash.skip')}</Txt> : null}
</Flex> </Flex>
<Flex flexDirection="column" color="#7e8085"> <Flex flexDirection="column" color="#7e8085">
{results.devices.successful !== 0 ? ( {results.devices.successful !== 0 ? (
@ -191,7 +191,7 @@ export function FlashResults({
{progress.failed(errors.length)} {progress.failed(errors.length)}
</Txt> </Txt>
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}> <Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
{i18next.t("flash.moreInfo")} {i18next.t('flash.moreInfo')}
</Link> </Link>
</Flex> </Flex>
) : null} ) : null}
@ -200,11 +200,11 @@ export function FlashResults({
fontSize="10px" fontSize="10px"
style={{ style={{
fontWeight: 500, fontWeight: 500,
textAlign: "center", textAlign: 'center',
}} }}
tooltip={i18next.t("flash.speedTip")} tooltip={i18next.t('flash.speedTip')}
> >
{i18next.t("flash.speed", { speed: effectiveSpeed })} {i18next.t('flash.speed', { speed: effectiveSpeed })}
</Txt> </Txt>
)} )}
</Flex> </Flex>
@ -214,11 +214,11 @@ export function FlashResults({
titleElement={ titleElement={
<Flex alignItems="baseline" mb={18}> <Flex alignItems="baseline" mb={18}>
<Txt fontSize={24} align="left"> <Txt fontSize={24} align="left">
{i18next.t("failedTarget")} {i18next.t('failedTarget')}
</Txt> </Txt>
</Flex> </Flex>
} }
action={i18next.t("failedRetry")} action={i18next.t('failedRetry')}
cancel={() => setShowErrorsInfo(false)} cancel={() => setShowErrorsInfo(false)}
done={() => { done={() => {
setShowErrorsInfo(false); setShowErrorsInfo(false);
@ -229,7 +229,7 @@ export function FlashResults({
return drive.device; return drive.device;
}) })
.filter((driveDevice) => .filter((driveDevice) =>
errors.some((error) => error.device === driveDevice) errors.some((error) => error.device === driveDevice),
) )
.forEach((driveDevice) => selection.selectDrive(driveDevice)); .forEach((driveDevice) => selection.selectDrive(driveDevice));
goToMain(); goToMain();

View File

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import CopySvg from "@fortawesome/fontawesome-free/svgs/solid/copy.svg"; import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg';
import FileSvg from "@fortawesome/fontawesome-free/svgs/solid/file.svg"; import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg';
import LinkSvg from "@fortawesome/fontawesome-free/svgs/solid/link.svg"; import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
import ExclamationTriangleSvg from "@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg"; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import ChevronDownSvg from "@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg"; import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import ChevronRightSvg from "@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg"; import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
import { ipcRenderer, IpcRendererEvent } from "electron"; import { ipcRenderer, IpcRendererEvent } from 'electron';
import { uniqBy, isNil } from "lodash"; import { uniqBy, isNil } from 'lodash';
import * as path from "path"; import * as path from 'path';
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import * as React from "react"; import * as React from 'react';
import { requestMetadata } from "../../app"; import { requestMetadata } from '../../app';
import { import {
Flex, Flex,
@ -36,17 +36,17 @@ import {
Input, Input,
Spinner, Spinner,
Link, Link,
} from "rendition"; } from 'rendition';
import styled from "styled-components"; import styled from 'styled-components';
import * as errors from "../../../../shared/errors"; import * as errors from '../../../../shared/errors';
import * as messages from "../../../../shared/messages"; import * as messages from '../../../../shared/messages';
import * as supportedFormats from "../../../../shared/supported-formats"; import * as supportedFormats from '../../../../shared/supported-formats';
import * as selectionState from "../../models/selection-state"; import * as selectionState from '../../models/selection-state';
import { observe } from "../../models/store"; import { observe } from '../../models/store';
import * as analytics from "../../modules/analytics"; import * as analytics from '../../modules/analytics';
import * as exceptionReporter from "../../modules/exception-reporter"; import * as exceptionReporter from '../../modules/exception-reporter';
import * as osDialog from "../../os/dialog"; import * as osDialog from '../../os/dialog';
import { import {
ChangeButton, ChangeButton,
@ -55,24 +55,24 @@ import {
StepButton, StepButton,
StepNameButton, StepNameButton,
ScrollableFlex, ScrollableFlex,
} from "../../styled-components"; } from '../../styled-components';
import { colors } from "../../theme"; import { colors } from '../../theme';
import { middleEllipsis } from "../../utils/middle-ellipsis"; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { SVGIcon } from "../svg-icon/svg-icon"; import { SVGIcon } from '../svg-icon/svg-icon';
import ImageSvg from "../../../assets/image.svg"; import ImageSvg from '../../../assets/image.svg';
import SrcSvg from "../../../assets/src.svg"; import SrcSvg from '../../../assets/src.svg';
import { DriveSelector } from "../drive-selector/drive-selector"; import { DriveSelector } from '../drive-selector/drive-selector';
import { DrivelistDrive } from "../../../../shared/drive-constraints"; import { DrivelistDrive } from '../../../../shared/drive-constraints';
import { isJson } from "../../../../shared/utils"; import { isJson } from '../../../../shared/utils';
import { import {
SourceMetadata, SourceMetadata,
Authentication, Authentication,
Source, Source,
} from "../../../../shared/typings/source-selector"; } from '../../../../shared/typings/source-selector';
import * as i18next from "i18next"; import * as i18next from 'i18next';
const recentUrlImagesKey = "recentUrlImages"; const recentUrlImagesKey = 'recentUrlImages';
function normalizeRecentUrlImages(urls: any[]): URL[] { function normalizeRecentUrlImages(urls: any[]): URL[] {
if (!Array.isArray(urls)) { if (!Array.isArray(urls)) {
@ -94,7 +94,7 @@ function normalizeRecentUrlImages(urls: any[]): URL[] {
function getRecentUrlImages(): URL[] { function getRecentUrlImages(): URL[] {
let urls = []; let urls = [];
try { try {
urls = JSON.parse(localStorage.getItem(recentUrlImagesKey) || "[]"); urls = JSON.parse(localStorage.getItem(recentUrlImagesKey) || '[]');
} catch { } catch {
// noop // noop
} }
@ -107,7 +107,7 @@ function setRecentUrlImages(urls: URL[]) {
} }
const isURL = (imagePath: string) => const isURL = (imagePath: string) =>
imagePath.startsWith("https://") || imagePath.startsWith("http://"); imagePath.startsWith('https://') || imagePath.startsWith('http://');
const Card = styled(BaseCard)` const Card = styled(BaseCard)`
hr { hr {
@ -136,7 +136,7 @@ function getState() {
} }
function isString(value: any): value is string { function isString(value: any): value is string {
return typeof value === "string"; return typeof value === 'string';
} }
const URLSelector = ({ const URLSelector = ({
@ -146,12 +146,12 @@ const URLSelector = ({
done: (imageURL: string, auth?: Authentication) => void; done: (imageURL: string, auth?: Authentication) => void;
cancel: () => void; cancel: () => void;
}) => { }) => {
const [imageURL, setImageURL] = React.useState(""); const [imageURL, setImageURL] = React.useState('');
const [recentImages, setRecentImages] = React.useState<URL[]>([]); const [recentImages, setRecentImages] = React.useState<URL[]>([]);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [showBasicAuth, setShowBasicAuth] = React.useState(false); const [showBasicAuth, setShowBasicAuth] = React.useState(false);
const [username, setUsername] = React.useState(""); const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState(""); const [password, setPassword] = React.useState('');
React.useEffect(() => { React.useEffect(() => {
const fetchRecentUrlImages = async () => { const fetchRecentUrlImages = async () => {
const recentUrlImages: URL[] = await getRecentUrlImages(); const recentUrlImages: URL[] = await getRecentUrlImages();
@ -165,7 +165,7 @@ const URLSelector = ({
primaryButtonProps={{ primaryButtonProps={{
disabled: loading || !imageURL, disabled: loading || !imageURL,
}} }}
action={loading ? <Spinner /> : i18next.t("ok")} action={loading ? <Spinner /> : i18next.t('ok')}
done={async () => { done={async () => {
setLoading(true); setLoading(true);
const urlStrings = recentImages.map((url: URL) => url.href); const urlStrings = recentImages.map((url: URL) => url.href);
@ -179,13 +179,13 @@ const URLSelector = ({
}} }}
> >
<Flex flexDirection="column"> <Flex flexDirection="column">
<Flex mb={15} style={{ width: "100%" }} flexDirection="column"> <Flex mb={15} style={{ width: '100%' }} flexDirection="column">
<Txt mb="10px" fontSize="24px"> <Txt mb="10px" fontSize="24px">
{i18next.t("source.useSourceURL")} {i18next.t('source.useSourceURL')}
</Txt> </Txt>
<Input <Input
value={imageURL} value={imageURL}
placeholder={i18next.t("source.enterValidURL")} placeholder={i18next.t('source.enterValidURL')}
type="text" type="text"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setImageURL(evt.target.value) setImageURL(evt.target.value)
@ -197,8 +197,8 @@ const URLSelector = ({
fontSize="14px" fontSize="14px"
onClick={() => { onClick={() => {
if (showBasicAuth) { if (showBasicAuth) {
setUsername(""); setUsername('');
setPassword(""); setPassword('');
} }
setShowBasicAuth(!showBasicAuth); setShowBasicAuth(!showBasicAuth);
}} }}
@ -210,7 +210,7 @@ const URLSelector = ({
{!showBasicAuth && ( {!showBasicAuth && (
<ChevronRightSvg height="1em" fill="currentColor" /> <ChevronRightSvg height="1em" fill="currentColor" />
)} )}
<Txt ml={8}>{i18next.t("source.auth")}</Txt> <Txt ml={8}>{i18next.t('source.auth')}</Txt>
</Flex> </Flex>
</Link> </Link>
{showBasicAuth && ( {showBasicAuth && (
@ -218,7 +218,7 @@ const URLSelector = ({
<Input <Input
mb={15} mb={15}
value={username} value={username}
placeholder={i18next.t("source.username")} placeholder={i18next.t('source.username')}
type="text" type="text"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setUsername(evt.target.value) setUsername(evt.target.value)
@ -226,7 +226,7 @@ const URLSelector = ({
/> />
<Input <Input
value={password} value={password}
placeholder={i18next.t("source.password")} placeholder={i18next.t('source.password')}
type="password" type="password"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setPassword(evt.target.value) setPassword(evt.target.value)
@ -249,10 +249,10 @@ const URLSelector = ({
setImageURL(recent.href); setImageURL(recent.href);
}} }}
style={{ style={{
overflowWrap: "break-word", overflowWrap: 'break-word',
}} }}
> >
{recent.pathname.split("/").pop()} - {recent.href} {recent.pathname.split('/').pop()} - {recent.href}
</Txt> </Txt>
)) ))
.reverse()} .reverse()}
@ -284,7 +284,7 @@ const FlowSelector = styled(
> >
{flow.label} {flow.label}
</StepButton> </StepButton>
) ),
)` )`
border-radius: 24px; border-radius: 24px;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
@ -349,20 +349,20 @@ export class SourceSelector extends React.Component<
this.unsubscribe = observe(() => { this.unsubscribe = observe(() => {
this.setState(getState()); this.setState(getState());
}); });
ipcRenderer.on("select-image", this.onSelectImage); ipcRenderer.on('select-image', this.onSelectImage);
ipcRenderer.send("source-selector-ready"); ipcRenderer.send('source-selector-ready');
} }
public componentWillUnmount() { public componentWillUnmount() {
this.unsubscribe?.(); this.unsubscribe?.();
ipcRenderer.removeListener("select-image", this.onSelectImage); ipcRenderer.removeListener('select-image', this.onSelectImage);
} }
private async onSelectImage(_event: IpcRendererEvent, imagePath: string) { private async onSelectImage(_event: IpcRendererEvent, imagePath: string) {
this.setState({ imageLoading: true }); this.setState({ imageLoading: true });
await this.selectSource( await this.selectSource(
imagePath, imagePath,
isURL(this.normalizeImagePath(imagePath)) ? "Http" : "File" isURL(this.normalizeImagePath(imagePath)) ? 'Http' : 'File',
).promise; ).promise;
this.setState({ imageLoading: false }); this.setState({ imageLoading: false });
} }
@ -376,7 +376,7 @@ export class SourceSelector extends React.Component<
} }
private reselectSource() { private reselectSource() {
analytics.logEvent("Reselect image", { analytics.logEvent('Reselect image', {
previousImage: selectionState.getImage(), previousImage: selectionState.getImage(),
}); });
@ -386,7 +386,7 @@ export class SourceSelector extends React.Component<
private selectSource( private selectSource(
selected: string | DrivelistDrive, selected: string | DrivelistDrive,
SourceType: Source, SourceType: Source,
auth?: Authentication auth?: Authentication,
): { promise: Promise<void>; cancel: () => void } { ): { promise: Promise<void>; cancel: () => void } {
let cancelled = false; let cancelled = false;
return { return {
@ -398,23 +398,23 @@ export class SourceSelector extends React.Component<
let metadata: SourceMetadata | undefined; let metadata: SourceMetadata | undefined;
if (isString(selected)) { if (isString(selected)) {
if ( if (
SourceType === "Http" && SourceType === 'Http' &&
!isURL(this.normalizeImagePath(selected)) !isURL(this.normalizeImagePath(selected))
) { ) {
this.handleError( this.handleError(
i18next.t("source.unsupportedProtocol"), i18next.t('source.unsupportedProtocol'),
selected, selected,
messages.error.unsupportedProtocol() messages.error.unsupportedProtocol(),
); );
return; return;
} }
if (supportedFormats.looksLikeWindowsImage(selected)) { if (supportedFormats.looksLikeWindowsImage(selected)) {
analytics.logEvent("Possibly Windows image", { image: selected }); analytics.logEvent('Possibly Windows image', { image: selected });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.looksLikeWindowsImage(), message: messages.warning.looksLikeWindowsImage(),
title: i18next.t("source.windowsImage"), title: i18next.t('source.windowsImage'),
}, },
}); });
} }
@ -426,29 +426,29 @@ export class SourceSelector extends React.Component<
metadata = await requestMetadata({ selected, SourceType, auth }); metadata = await requestMetadata({ selected, SourceType, auth });
if (!metadata?.hasMBR && this.state.warning === null) { if (!metadata?.hasMBR && this.state.warning === null) {
analytics.logEvent("Missing partition table", { metadata }); analytics.logEvent('Missing partition table', { metadata });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.missingPartitionTable(), message: messages.warning.missingPartitionTable(),
title: i18next.t("source.partitionTable"), title: i18next.t('source.partitionTable'),
}, },
}); });
} }
} catch (error: any) { } catch (error: any) {
this.handleError( this.handleError(
i18next.t("source.errorOpen"), i18next.t('source.errorOpen'),
sourcePath, sourcePath,
messages.error.openSource(sourcePath, error.message), messages.error.openSource(sourcePath, error.message),
error error,
); );
} }
} else { } else {
if (selected.partitionTableType === null) { if (selected.partitionTableType === null) {
analytics.logEvent("Missing partition table", { selected }); analytics.logEvent('Missing partition table', { selected });
this.setState({ this.setState({
warning: { warning: {
message: messages.warning.driveMissingPartitionTable(), message: messages.warning.driveMissingPartitionTable(),
title: i18next.t("source.partitionTable"), title: i18next.t('source.partitionTable'),
}, },
}); });
} }
@ -456,8 +456,8 @@ export class SourceSelector extends React.Component<
path: selected.device, path: selected.device,
displayName: selected.displayName, displayName: selected.displayName,
description: selected.displayName, description: selected.displayName,
size: selected.size as SourceMetadata["size"], size: selected.size as SourceMetadata['size'],
SourceType: "BlockDevice", SourceType: 'BlockDevice',
drive: selected, drive: selected,
}; };
} }
@ -466,7 +466,7 @@ export class SourceSelector extends React.Component<
metadata.auth = auth; metadata.auth = auth;
metadata.SourceType = SourceType; metadata.SourceType = SourceType;
selectionState.selectSource(metadata); selectionState.selectSource(metadata);
analytics.logEvent("Select image", { analytics.logEvent('Select image', {
// An easy way so we can quickly identify if we're making use of // An easy way so we can quickly identify if we're making use of
// certain features without printing pages of text to DevTools. // certain features without printing pages of text to DevTools.
image: { image: {
@ -484,7 +484,7 @@ export class SourceSelector extends React.Component<
title: string, title: string,
sourcePath: string, sourcePath: string,
description: string, description: string,
error?: Error error?: Error,
) { ) {
const imageError = errors.createUserError({ const imageError = errors.createUserError({
title, title,
@ -499,7 +499,7 @@ export class SourceSelector extends React.Component<
} }
private async openImageSelector() { private async openImageSelector() {
analytics.logEvent("Open image selector"); analytics.logEvent('Open image selector');
this.setState({ imageSelectorOpen: true }); this.setState({ imageSelectorOpen: true });
try { try {
@ -507,10 +507,10 @@ export class SourceSelector extends React.Component<
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imagePath) { if (!imagePath) {
analytics.logEvent("Image selector closed"); analytics.logEvent('Image selector closed');
return; return;
} }
await this.selectSource(imagePath, "File").promise; await this.selectSource(imagePath, 'File').promise;
} catch (error: any) { } catch (error: any) {
exceptionReporter.report(error); exceptionReporter.report(error);
} finally { } finally {
@ -521,12 +521,12 @@ export class SourceSelector extends React.Component<
private async onDrop(event: React.DragEvent<HTMLDivElement>) { private async onDrop(event: React.DragEvent<HTMLDivElement>) {
const [file] = event.dataTransfer.files; const [file] = event.dataTransfer.files;
if (file) { if (file) {
await this.selectSource(file.path, "File").promise; await this.selectSource(file.path, 'File').promise;
} }
} }
private openURLSelector() { private openURLSelector() {
analytics.logEvent("Open image URL selector"); analytics.logEvent('Open image URL selector');
this.setState({ this.setState({
showURLSelector: true, showURLSelector: true,
@ -534,7 +534,7 @@ export class SourceSelector extends React.Component<
} }
private openDriveSelector() { private openDriveSelector() {
analytics.logEvent("Open drive selector"); analytics.logEvent('Open drive selector');
this.setState({ this.setState({
showDriveSelector: true, showDriveSelector: true,
@ -552,7 +552,7 @@ export class SourceSelector extends React.Component<
} }
private showSelectedImageDetails() { private showSelectedImageDetails() {
analytics.logEvent("Show selected image tooltip", { analytics.logEvent('Show selected image tooltip', {
imagePath: selectionState.getImage()?.path, imagePath: selectionState.getImage()?.path,
}); });
@ -590,11 +590,11 @@ export class SourceSelector extends React.Component<
// noop // noop
}; };
image.name = image.description || image.name; image.name = image.description || image.name;
const imagePath = image.path || image.displayName || ""; const imagePath = image.path || image.displayName || '';
const imageBasename = path.basename(imagePath); const imageBasename = path.basename(imagePath);
const imageName = image.name || ""; const imageName = image.name || '';
const imageSize = image.size; const imageSize = image.size;
const imageLogo = image.logo || ""; const imageLogo = image.logo || '';
return ( return (
<> <>
@ -634,7 +634,7 @@ export class SourceSelector extends React.Component<
mb={14} mb={14}
onClick={() => this.reselectSource()} onClick={() => this.reselectSource()}
> >
{i18next.t("cancel")} {i18next.t('cancel')}
</ChangeButton> </ChangeButton>
)} )}
{!isNil(imageSize) && !imageLoading && ( {!isNil(imageSize) && !imageLoading && (
@ -649,7 +649,7 @@ export class SourceSelector extends React.Component<
key="Flash from file" key="Flash from file"
flow={{ flow={{
onClick: () => this.openImageSelector(), onClick: () => this.openImageSelector(),
label: i18next.t("source.fromFile"), label: i18next.t('source.fromFile'),
icon: <FileSvg height="1em" fill="currentColor" />, icon: <FileSvg height="1em" fill="currentColor" />,
}} }}
onMouseEnter={() => this.setDefaultFlowActive(false)} onMouseEnter={() => this.setDefaultFlowActive(false)}
@ -659,7 +659,7 @@ export class SourceSelector extends React.Component<
key="Flash from URL" key="Flash from URL"
flow={{ flow={{
onClick: () => this.openURLSelector(), onClick: () => this.openURLSelector(),
label: i18next.t("source.fromURL"), label: i18next.t('source.fromURL'),
icon: <LinkSvg height="1em" fill="currentColor" />, icon: <LinkSvg height="1em" fill="currentColor" />,
}} }}
onMouseEnter={() => this.setDefaultFlowActive(false)} onMouseEnter={() => this.setDefaultFlowActive(false)}
@ -669,7 +669,7 @@ export class SourceSelector extends React.Component<
key="Clone drive" key="Clone drive"
flow={{ flow={{
onClick: () => this.openDriveSelector(), onClick: () => this.openDriveSelector(),
label: i18next.t("source.clone"), label: i18next.t('source.clone'),
icon: <CopySvg height="1em" fill="currentColor" />, icon: <CopySvg height="1em" fill="currentColor" />,
}} }}
onMouseEnter={() => this.setDefaultFlowActive(false)} onMouseEnter={() => this.setDefaultFlowActive(false)}
@ -682,15 +682,15 @@ export class SourceSelector extends React.Component<
{this.state.warning != null && ( {this.state.warning != null && (
<SmallModal <SmallModal
style={{ style={{
boxShadow: "0 3px 7px rgba(0, 0, 0, 0.3)", boxShadow: '0 3px 7px rgba(0, 0, 0, 0.3)',
}} }}
titleElement={ titleElement={
<span> <span>
<ExclamationTriangleSvg fill="#fca321" height="1em" />{" "} <ExclamationTriangleSvg fill="#fca321" height="1em" />{' '}
<span>{this.state.warning.title}</span> <span>{this.state.warning.title}</span>
</span> </span>
} }
action={i18next.t("continue")} action={i18next.t('continue')}
cancel={() => { cancel={() => {
this.setState({ warning: null }); this.setState({ warning: null });
this.reselectSource(); this.reselectSource();
@ -708,17 +708,17 @@ export class SourceSelector extends React.Component<
{showImageDetails && ( {showImageDetails && (
<SmallModal <SmallModal
title={i18next.t("source.image")} title={i18next.t('source.image')}
done={() => { done={() => {
this.setState({ showImageDetails: false }); this.setState({ showImageDetails: false });
}} }}
> >
<Txt.p> <Txt.p>
<Txt.span bold>{i18next.t("source.name")}</Txt.span> <Txt.span bold>{i18next.t('source.name')}</Txt.span>
<Txt.span>{imageName || imageBasename}</Txt.span> <Txt.span>{imageName || imageBasename}</Txt.span>
</Txt.p> </Txt.p>
<Txt.p> <Txt.p>
<Txt.span bold>{i18next.t("source.path")}</Txt.span> <Txt.span bold>{i18next.t('source.path')}</Txt.span>
<Txt.span>{imagePath}</Txt.span> <Txt.span>{imagePath}</Txt.span>
</Txt.p> </Txt.p>
</SmallModal> </SmallModal>
@ -736,13 +736,13 @@ export class SourceSelector extends React.Component<
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imageURL) { if (!imageURL) {
analytics.logEvent("URL selector closed"); analytics.logEvent('URL selector closed');
} else { } else {
let promise; let promise;
({ promise, cancel: cancelURLSelection } = this.selectSource( ({ promise, cancel: cancelURLSelection } = this.selectSource(
imageURL, imageURL,
"Http", 'Http',
auth auth,
)); ));
await promise; await promise;
} }
@ -757,14 +757,14 @@ export class SourceSelector extends React.Component<
<DriveSelector <DriveSelector
write={false} write={false}
multipleSelection={false} multipleSelection={false}
titleLabel={i18next.t("source.selectSource")} titleLabel={i18next.t('source.selectSource')}
emptyListLabel={i18next.t("source.plugSource")} emptyListLabel={i18next.t('source.plugSource')}
emptyListIcon={<SrcSvg width="40px" />} emptyListIcon={<SrcSvg width="40px" />}
cancel={(originalList) => { cancel={(originalList) => {
if (originalList.length) { if (originalList.length) {
const originalSource = originalList[0]; const originalSource = originalList[0];
if (selectionImage?.drive?.device !== originalSource.device) { if (selectionImage?.drive?.device !== originalSource.device) {
this.selectSource(originalSource, "BlockDevice"); this.selectSource(originalSource, 'BlockDevice');
} }
} else { } else {
selectionState.deselectImage(); selectionState.deselectImage();
@ -779,7 +779,7 @@ export class SourceSelector extends React.Component<
) { ) {
return selectionState.deselectImage(); return selectionState.deselectImage();
} }
this.selectSource(drive, "BlockDevice"); this.selectSource(drive, 'BlockDevice');
} }
}} }}
/> />

View File

@ -14,25 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
import ExclamationTriangleSvg from "@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg"; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import * as React from "react"; import * as React from 'react';
import { Flex, FlexProps, Txt } from "rendition"; import { Flex, FlexProps, Txt } from 'rendition';
import { import {
getDriveImageCompatibilityStatuses, getDriveImageCompatibilityStatuses,
DriveStatus, DriveStatus,
} from "../../../../shared/drive-constraints"; } from '../../../../shared/drive-constraints';
import { compatibility, warning } from "../../../../shared/messages"; import { compatibility, warning } from '../../../../shared/messages';
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import { getImage, getSelectedDrives } from "../../models/selection-state"; import { getImage, getSelectedDrives } from '../../models/selection-state';
import { import {
ChangeButton, ChangeButton,
DetailsText, DetailsText,
StepButton, StepButton,
StepNameButton, StepNameButton,
} from "../../styled-components"; } from '../../styled-components';
import { middleEllipsis } from "../../utils/middle-ellipsis"; import { middleEllipsis } from '../../utils/middle-ellipsis';
import * as i18next from "i18next"; import * as i18next from 'i18next';
interface TargetSelectorProps { interface TargetSelectorProps {
targets: any[]; targets: any[];
@ -53,7 +53,7 @@ function getDriveWarning(status: DriveStatus) {
case compatibility.system(): case compatibility.system():
return warning.systemDrive(); return warning.systemDrive();
default: default:
return ""; return '';
} }
} }
@ -64,12 +64,12 @@ const DriveCompatibilityWarning = ({
warnings: string[]; warnings: string[];
} & FlexProps) => { } & FlexProps) => {
const systemDrive = warnings.find( const systemDrive = warnings.find(
(message) => message === warning.systemDrive() (message) => message === warning.systemDrive(),
); );
return ( return (
<Flex tooltip={warnings.join(", ")} {...props}> <Flex tooltip={warnings.join(', ')} {...props}>
<ExclamationTriangleSvg <ExclamationTriangleSvg
fill={systemDrive ? "#fca321" : "#8f9297"} fill={systemDrive ? '#fca321' : '#8f9297'}
height="1em" height="1em"
/> />
</Flex> </Flex>
@ -84,7 +84,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
const warnings = getDriveImageCompatibilityStatuses( const warnings = getDriveImageCompatibilityStatuses(
target, target,
getImage(), getImage(),
true true,
).map(getDriveWarning); ).map(getDriveWarning);
return ( return (
<> <>
@ -96,7 +96,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
</StepNameButton> </StepNameButton>
{!props.flashing && ( {!props.flashing && (
<ChangeButton plain mb={14} onClick={props.reselectDrive}> <ChangeButton plain mb={14} onClick={props.reselectDrive}>
{i18next.t("target.change")} {i18next.t('target.change')}
</ChangeButton> </ChangeButton>
)} )}
{target.size != null && ( {target.size != null && (
@ -112,13 +112,13 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
const warnings = getDriveImageCompatibilityStatuses( const warnings = getDriveImageCompatibilityStatuses(
target, target,
getImage(), getImage(),
true true,
).map(getDriveWarning); ).map(getDriveWarning);
targetsTemplate.push( targetsTemplate.push(
<DetailsText <DetailsText
key={target.device} key={target.device}
tooltip={`${target.description} ${target.displayName} ${ tooltip={`${target.description} ${target.displayName} ${
target.size != null ? prettyBytes(target.size) : "" target.size != null ? prettyBytes(target.size) : ''
}`} }`}
px={21} px={21}
> >
@ -127,17 +127,17 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
) : null} ) : null}
<Txt mr={2}>{middleEllipsis(target.description, 14)}</Txt> <Txt mr={2}>{middleEllipsis(target.description, 14)}</Txt>
{target.size != null && <Txt>{prettyBytes(target.size)}</Txt>} {target.size != null && <Txt>{prettyBytes(target.size)}</Txt>}
</DetailsText> </DetailsText>,
); );
} }
return ( return (
<> <>
<StepNameButton plain tooltip={props.tooltip}> <StepNameButton plain tooltip={props.tooltip}>
{targets.length} {i18next.t("target.targets")} {targets.length} {i18next.t('target.targets')}
</StepNameButton> </StepNameButton>
{!props.flashing && ( {!props.flashing && (
<ChangeButton plain onClick={props.reselectDrive} mb={14}> <ChangeButton plain onClick={props.reselectDrive} mb={14}>
{i18next.t("target.change")} {i18next.t('target.change')}
</ChangeButton> </ChangeButton>
)} )}
{targetsTemplate} {targetsTemplate}
@ -152,7 +152,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
disabled={props.disabled} disabled={props.disabled}
onClick={props.openDriveSelector} onClick={props.openDriveSelector}
> >
{i18next.t("target.selectTarget")} {i18next.t('target.selectTarget')}
</StepButton> </StepButton>
); );
} }

View File

@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import * as i18next from "i18next"; import * as i18next from 'i18next';
export interface FlashState { export interface FlashState {
active: number; active: number;
@ -23,61 +23,61 @@ export interface FlashState {
percentage?: number; percentage?: number;
speed: number; speed: number;
position: number; position: number;
type?: "decompressing" | "flashing" | "verifying"; type?: 'decompressing' | 'flashing' | 'verifying';
} }
export function fromFlashState({ export function fromFlashState({
type, type,
percentage, percentage,
position, position,
}: Pick<FlashState, "type" | "percentage" | "position">): { }: Pick<FlashState, 'type' | 'percentage' | 'position'>): {
status: string; status: string;
position?: string; position?: string;
} { } {
if (type === undefined) { if (type === undefined) {
return { status: i18next.t("progress.starting") }; return { status: i18next.t('progress.starting') };
} else if (type === "decompressing") { } else if (type === 'decompressing') {
if (percentage == null) { if (percentage == null) {
return { status: i18next.t("progress.decompressing") }; return { status: i18next.t('progress.decompressing') };
} else { } else {
return { return {
position: `${percentage}%`, position: `${percentage}%`,
status: i18next.t("progress.decompressing"), status: i18next.t('progress.decompressing'),
}; };
} }
} else if (type === "flashing") { } else if (type === 'flashing') {
if (percentage != null) { if (percentage != null) {
if (percentage < 100) { if (percentage < 100) {
return { return {
position: `${percentage}%`, position: `${percentage}%`,
status: i18next.t("progress.flashing"), status: i18next.t('progress.flashing'),
}; };
} else { } else {
return { status: i18next.t("progress.finishing") }; return { status: i18next.t('progress.finishing') };
} }
} else { } else {
return { return {
status: i18next.t("progress.flashing"), status: i18next.t('progress.flashing'),
position: `${position ? prettyBytes(position) : ""}`, position: `${position ? prettyBytes(position) : ''}`,
}; };
} }
} else if (type === "verifying") { } else if (type === 'verifying') {
if (percentage == null) { if (percentage == null) {
return { status: i18next.t("progress.verifying") }; return { status: i18next.t('progress.verifying') };
} else if (percentage < 100) { } else if (percentage < 100) {
return { return {
position: `${percentage}%`, position: `${percentage}%`,
status: i18next.t("progress.verifying"), status: i18next.t('progress.verifying'),
}; };
} else { } else {
return { status: i18next.t("progress.finishing") }; return { status: i18next.t('progress.finishing') };
} }
} }
return { status: i18next.t("progress.failing") }; return { status: i18next.t('progress.failing') };
} }
export function titleFromFlashState( export function titleFromFlashState(
state: Pick<FlashState, "type" | "percentage" | "position"> state: Pick<FlashState, 'type' | 'percentage' | 'position'>,
): string { ): string {
const { status, position } = fromFlashState(state); const { status, position } = fromFlashState(state);
if (position !== undefined) { if (position !== undefined) {

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
import { Dictionary } from "lodash"; import { Dictionary } from 'lodash';
import { outdent } from "outdent"; import { outdent } from 'outdent';
import prettyBytes from "pretty-bytes"; import prettyBytes from 'pretty-bytes';
import "../gui/app/i18n"; import '../gui/app/i18n';
import * as i18next from "i18next"; import * as i18next from 'i18next';
export const progress: Dictionary<(quantity: number) => string> = { export const progress: Dictionary<(quantity: number) => string> = {
successful: (quantity: number) => { successful: (quantity: number) => {
return i18next.t("message.flashSucceed", { count: quantity }); return i18next.t('message.flashSucceed', { count: quantity });
}, },
failed: (quantity: number) => { failed: (quantity: number) => {
return i18next.t("message.flashFail", { count: quantity }); return i18next.t('message.flashFail', { count: quantity });
}, },
}; };
@ -34,122 +34,122 @@ export const info = {
flashComplete: ( flashComplete: (
imageBasename: string, imageBasename: string,
[drive]: [{ description: string; displayName: string }], [drive]: [{ description: string; displayName: string }],
{ failed, successful }: { failed: number; successful: number } { failed, successful }: { failed: number; successful: number },
) => { ) => {
const targets = []; const targets = [];
if (failed + successful === 1) { if (failed + successful === 1) {
targets.push( targets.push(
i18next.t("message.toDrive", { i18next.t('message.toDrive', {
description: drive.description, description: drive.description,
name: drive.displayName, name: drive.displayName,
}) }),
); );
} else { } else {
if (successful) { if (successful) {
targets.push( targets.push(
i18next.t("message.toTarget", { i18next.t('message.toTarget', {
count: successful, count: successful,
num: successful, num: successful,
}) }),
); );
} }
if (failed) { if (failed) {
targets.push( targets.push(
i18next.t("message.andFailTarget", { count: failed, num: failed }) i18next.t('message.andFailTarget', { count: failed, num: failed }),
); );
} }
} }
return i18next.t("message.succeedTo", { return i18next.t('message.succeedTo', {
name: imageBasename, name: imageBasename,
target: targets.join(" "), target: targets.join(' '),
}); });
}, },
}; };
export const compatibility = { export const compatibility = {
sizeNotRecommended: () => { sizeNotRecommended: () => {
return i18next.t("message.sizeNotRecommended"); return i18next.t('message.sizeNotRecommended');
}, },
tooSmall: () => { tooSmall: () => {
return i18next.t("message.tooSmall"); return i18next.t('message.tooSmall');
}, },
locked: () => { locked: () => {
return i18next.t("message.locked"); return i18next.t('message.locked');
}, },
system: () => { system: () => {
return i18next.t("message.system"); return i18next.t('message.system');
}, },
containsImage: () => { containsImage: () => {
return i18next.t("message.containsImage"); return i18next.t('message.containsImage');
}, },
// The drive is large and therefore likely not a medium you want to write to. // The drive is large and therefore likely not a medium you want to write to.
largeDrive: () => { largeDrive: () => {
return i18next.t("message.largeDrive"); return i18next.t('message.largeDrive');
}, },
} as const; } as const;
export const warning = { export const warning = {
tooSmall: (source: { size: number }, target: { size: number }) => { tooSmall: (source: { size: number }, target: { size: number }) => {
return outdent({ newline: " " })` return outdent({ newline: ' ' })`
${i18next.t("message.sourceLarger", { ${i18next.t('message.sourceLarger', {
byte: prettyBytes(source.size - target.size), byte: prettyBytes(source.size - target.size),
})} })}
`; `;
}, },
exitWhileFlashing: () => { exitWhileFlashing: () => {
return i18next.t("message.exitWhileFlashing"); return i18next.t('message.exitWhileFlashing');
}, },
looksLikeWindowsImage: () => { looksLikeWindowsImage: () => {
return i18next.t("message.looksLikeWindowsImage"); return i18next.t('message.looksLikeWindowsImage');
}, },
missingPartitionTable: () => { missingPartitionTable: () => {
return i18next.t("message.missingPartitionTable", { return i18next.t('message.missingPartitionTable', {
type: i18next.t("message.image"), type: i18next.t('message.image'),
}); });
}, },
driveMissingPartitionTable: () => { driveMissingPartitionTable: () => {
return i18next.t("message.missingPartitionTable", { return i18next.t('message.missingPartitionTable', {
type: i18next.t("message.drive"), type: i18next.t('message.drive'),
}); });
}, },
largeDriveSize: () => { largeDriveSize: () => {
return i18next.t("message.largeDriveSize"); return i18next.t('message.largeDriveSize');
}, },
systemDrive: () => { systemDrive: () => {
return i18next.t("message.systemDrive"); return i18next.t('message.systemDrive');
}, },
sourceDrive: () => { sourceDrive: () => {
return i18next.t("message.sourceDrive"); return i18next.t('message.sourceDrive');
}, },
}; };
export const error = { export const error = {
notEnoughSpaceInDrive: () => { notEnoughSpaceInDrive: () => {
return i18next.t("message.noSpace"); return i18next.t('message.noSpace');
}, },
genericFlashError: (err: Error) => { genericFlashError: (err: Error) => {
return i18next.t("message.genericFlashError", { error: err.message }); return i18next.t('message.genericFlashError', { error: err.message });
}, },
validation: () => { validation: () => {
return i18next.t("message.validation"); return i18next.t('message.validation');
}, },
openSource: (sourceName: string, errorMessage: string) => { openSource: (sourceName: string, errorMessage: string) => {
return i18next.t("message.openError", { return i18next.t('message.openError', {
source: sourceName, source: sourceName,
error: errorMessage, error: errorMessage,
}); });
@ -157,37 +157,37 @@ export const error = {
flashFailure: ( flashFailure: (
imageBasename: string, imageBasename: string,
drives: Array<{ description: string; displayName: string }> drives: Array<{ description: string; displayName: string }>,
) => { ) => {
const target = const target =
drives.length === 1 drives.length === 1
? i18next.t("message.toDrive", { ? i18next.t('message.toDrive', {
description: drives[0].description, description: drives[0].description,
name: drives[0].displayName, name: drives[0].displayName,
}) })
: i18next.t("message.toTarget", { : i18next.t('message.toTarget', {
count: drives.length, count: drives.length,
num: drives.length, num: drives.length,
}); });
return i18next.t("message.flashError", { return i18next.t('message.flashError', {
image: imageBasename, image: imageBasename,
targets: target, targets: target,
}); });
}, },
driveUnplugged: () => { driveUnplugged: () => {
return i18next.t("message.unplug"); return i18next.t('message.unplug');
}, },
inputOutput: () => { inputOutput: () => {
return i18next.t("message.cannotWrite"); return i18next.t('message.cannotWrite');
}, },
childWriterDied: () => { childWriterDied: () => {
return i18next.t("message.childWriterDied"); return i18next.t('message.childWriterDied');
}, },
unsupportedProtocol: () => { unsupportedProtocol: () => {
return i18next.t("message.badProtocol"); return i18next.t('message.badProtocol');
}, },
}; };