diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index dd533d8e..03b025b1 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -18,7 +18,8 @@ import { faChevronDown, faExclamationTriangle, } from '@fortawesome/free-solid-svg-icons'; -import { Drive as DrivelistDrive } from 'drivelist'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { scanner, sourceDestination } from 'etcher-sdk'; import * as React from 'react'; import { Badge, @@ -47,34 +48,37 @@ import { isDriveSelected, } from '../../models/selection-state'; import { store } from '../../models/store'; -import * as analytics from '../../modules/analytics'; +import { logEvent, logException } from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { Modal } from '../../styled-components'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -export interface DrivelistTarget extends DrivelistDrive { - displayName: string; +interface UsbbootDrive extends sourceDestination.UsbbootDrive { progress: number; - device: string; +} + +interface DriverlessDrive { + displayName: string; // added in app.ts + description: string; link: string; linkTitle: string; linkMessage: string; linkCTA: string; } -/** - * @summary Get a drive's compatibility status object(s) - * - * @description - * Given a drive, return its compatibility status with the selected image, - * containing the status type (ERROR, WARNING), and accompanying - * status message. - */ -function getDriveStatuses( - drive: DrivelistTarget, - image: Image, -): TargetStatus[] { - return getDriveImageCompatibilityStatuses(drive, image); +type Target = scanner.adapters.DrivelistDrive | DriverlessDrive | UsbbootDrive; + +function isUsbbootDrive(drive: Target): drive is UsbbootDrive { + return (drive as UsbbootDrive).progress !== undefined; +} + +function isDriverlessDrive(drive: Target): drive is DriverlessDrive { + return (drive as DriverlessDrive).link !== undefined; +} + +function isDrivelistDrive( + drive: Target, +): drive is scanner.adapters.DrivelistDrive { + return typeof (drive as scanner.adapters.DrivelistDrive).size === 'number'; } const ScrollableFlex = styled(Flex)` @@ -88,14 +92,10 @@ const ScrollableFlex = styled(Flex)` const TargetsTable = styled(({ refFn, ...props }) => { return (
- ref={refFn} {...props} /> + ref={refFn} {...props} />
); })` - > div { - overflow: visible; - } - [data-display='table-head'] [data-display='table-row'] > [data-display='table-cell']:first-child { @@ -123,12 +123,6 @@ const TargetsTable = styled(({ refFn, ...props }) => { } `; -interface DriverlessDrive { - link: string; - linkTitle: string; - linkMessage: string; -} - function badgeShadeFromStatus(status: string) { switch (status) { case compatibility.containsImage(): @@ -148,7 +142,7 @@ const InitProgress = styled( value: number; props?: React.ProgressHTMLAttributes; }) => { - return ; + return ; }, )` /* Reset the default appearance */ @@ -167,20 +161,15 @@ const InitProgress = styled( } `; -interface TableData extends DrivelistTarget { - disabled: boolean; - extra: TargetStatus[] | number; -} - interface TargetSelectorModalProps extends Omit { - done: (targets: DrivelistTarget[]) => void; + done: (targets: scanner.adapters.DrivelistDrive[]) => void; } interface TargetSelectorModalState { - drives: any[]; + drives: Target[]; image: Image; missingDriversModal: { drive?: DriverlessDrive }; - selectedList: any[]; + selectedList: scanner.adapters.DrivelistDrive[]; showSystemDrives: boolean; } @@ -189,7 +178,7 @@ export class TargetSelectorModal extends React.Component< TargetSelectorModalState > { unsubscribe: () => void; - tableColumns: Array>; + tableColumns: Array>; constructor(props: TargetSelectorModalProps) { super(props); @@ -209,8 +198,8 @@ export class TargetSelectorModal extends React.Component< { field: 'description', label: 'Name', - render: (description: string, drive: DrivelistTarget) => { - return drive.isSystem ? ( + render: (description: string, drive: Target) => { + return isDrivelistDrive(drive) && drive.isSystem ? ( { + if (isDrivelistDrive(drive) && drive.size !== null) { + return bytesToClosestUnit(drive.size); + } + }, }, { - field: 'link', + field: 'description', + key: 'link', label: 'Location', - render: (link: string, drive: DrivelistTarget) => { - return link ? ( + render: (_description: string, drive: Target) => { + return ( - {drive.displayName} -{' '} - - this.installMissingDrivers(drive)}> - {drive.linkCTA} - - + {drive.displayName} + {isDriverlessDrive(drive) && ( + <> + {' '} + -{' '} + + this.installMissingDrivers(drive)}> + {drive.linkCTA} + + + + )} - ) : ( - {drive.displayName} ); }, }, { - field: 'extra', - label: ' ', - render: (extra: TargetStatus[] | number) => { - if (typeof extra === 'number') { - return this.renderProgress(extra); + field: 'description', + key: 'extra', + label: '', + render: (_description: string, drive: Target) => { + if (isUsbbootDrive(drive)) { + return this.renderProgress(drive.progress); + } else if (isDrivelistDrive(drive)) { + return this.renderStatuses( + getDriveImageCompatibilityStatuses(drive, this.state.image), + ); } - return this.renderStatuses(extra); }, }, ]; } - private buildTableData(drives: any[], image: any) { - return drives.map((drive) => { - return { - ...drive, - extra: - drive.progress !== undefined - ? drive.progress - : getDriveStatuses(drive, image), - disabled: !isDriveValid(drive, image) || drive.progress !== undefined, - }; + private driveShouldBeDisabled(drive: Target, image: any) { + return ( + isUsbbootDrive(drive) || + isDriverlessDrive(drive) || + !isDriveValid(drive, image) + ); + } + + private getDisplayedTargets(targets: Target[]): Target[] { + return targets.filter((drive) => { + return ( + isUsbbootDrive(drive) || + isDriverlessDrive(drive) || + isDriveSelected(drive.device) || + this.state.showSystemDrives || + !drive.isSystem + ); }); } - private getDisplayedTargets(enrichedDrivesData: any[]) { - return enrichedDrivesData.filter((drive) => { - const showIfSystemDrive = this.state.showSystemDrives || !drive.isSystem; - return isDriveSelected(drive.device) || showIfSystemDrive; - }); - } - - private getDisabledTargets(drives: any[], image: any): TableData[] { + private getDisabledTargets(drives: Target[], image: any): string[] { return drives - .filter( - (drive) => !isDriveValid(drive, image) || drive.progress !== undefined, - ) + .filter((drive) => this.driveShouldBeDisabled(drive, image)) .map((drive) => drive.displayName); } @@ -312,13 +313,9 @@ export class TargetSelectorModal extends React.Component< ); } - private installMissingDrivers(drive: { - link: string; - linkTitle: string; - linkMessage: string; - }) { + private installMissingDrivers(drive: DriverlessDrive) { if (drive.link) { - analytics.logEvent('Open driver link modal', { + logEvent('Open driver link modal', { url: drive.link, }); this.setState({ missingDriversModal: { drive } }); @@ -343,21 +340,15 @@ export class TargetSelectorModal extends React.Component< render() { const { cancel, done, ...props } = this.props; - const { - selectedList, - showSystemDrives, - drives, - image, - missingDriversModal, - } = this.state; + const { selectedList, drives, image, missingDriversModal } = this.state; - const targetsWithTableData = this.buildTableData(drives, image); - const displayedTargets = this.getDisplayedTargets(targetsWithTableData); + const displayedTargets = this.getDisplayedTargets(drives); const disabledTargets = this.getDisabledTargets(drives, image); - const numberOfSystemDrives = drives.filter((drive) => drive.isSystem) - .length; + const numberOfSystemDrives = drives.filter( + (drive) => isDrivelistDrive(drive) && drive.isSystem, + ).length; const numberOfDisplayedSystemDrives = displayedTargets.filter( - (drive) => drive.isSystem, + (drive) => isDrivelistDrive(drive) && drive.isSystem, ).length; const numberOfHiddenSystemDrives = numberOfSystemDrives - numberOfDisplayedSystemDrives; @@ -407,7 +398,7 @@ export class TargetSelectorModal extends React.Component< height="calc(100% - 15px)" > ) => { + refFn={(t: Table) => { if (t !== null) { t.setRowSelection(selectedList); } @@ -416,13 +407,16 @@ export class TargetSelectorModal extends React.Component< data={displayedTargets} disabledRows={disabledTargets} rowKey="displayName" - onCheck={(rows: TableData[]) => { + onCheck={(rows: Target[]) => { this.setState({ - selectedList: rows, + selectedList: rows.filter(isDrivelistDrive), }); }} - onRowClick={(row: TableData) => { - if (row.disabled) { + onRowClick={(row: Target) => { + if ( + !isDrivelistDrive(row) || + this.driveShouldBeDisabled(row, image) + ) { return; } const newList = [...selectedList]; @@ -440,7 +434,7 @@ export class TargetSelectorModal extends React.Component< }); }} /> - {!showSystemDrives && numberOfHiddenSystemDrives > 0 && ( + {numberOfHiddenSystemDrives > 0 && ( ({ image: getImage(), }); -export const selectAllTargets = (modalTargets: DrivelistTarget[]) => { +export const selectAllTargets = ( + modalTargets: scanner.adapters.DrivelistDrive[], +) => { const selectedDrivesFromState = getSelectedDrives(); const deselected = selectedDrivesFromState.filter( (drive) =>