diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/drive-selector/drive-selector.tsx similarity index 77% rename from lib/gui/app/components/target-selector/target-selector-modal.tsx rename to lib/gui/app/components/drive-selector/drive-selector.tsx index 58b7d004..5523c96b 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/drive-selector/drive-selector.tsx @@ -33,7 +33,7 @@ import { getDriveImageCompatibilityStatuses, hasListDriveImageCompatibilityStatus, isDriveValid, - TargetStatus, + DriveStatus, Image, } from '../../../../shared/drive-constraints'; import { compatibility } from '../../../../shared/messages'; @@ -49,7 +49,7 @@ import { logEvent, logException } from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { Modal, ScrollableFlex } from '../../styled-components'; -import TargetSVGIcon from '../../../assets/tgt.svg'; +import DriveSVGIcon from '../../../assets/tgt.svg'; interface UsbbootDrive extends sourceDestination.UsbbootDrive { progress: number; @@ -64,26 +64,26 @@ interface DriverlessDrive { linkCTA: string; } -type Target = scanner.adapters.DrivelistDrive | DriverlessDrive | UsbbootDrive; +type Drive = scanner.adapters.DrivelistDrive | DriverlessDrive | UsbbootDrive; -function isUsbbootDrive(drive: Target): drive is UsbbootDrive { +function isUsbbootDrive(drive: Drive): drive is UsbbootDrive { return (drive as UsbbootDrive).progress !== undefined; } -function isDriverlessDrive(drive: Target): drive is DriverlessDrive { +function isDriverlessDrive(drive: Drive): drive is DriverlessDrive { return (drive as DriverlessDrive).link !== undefined; } function isDrivelistDrive( - drive: Target, + drive: Drive, ): drive is scanner.adapters.DrivelistDrive { return typeof (drive as scanner.adapters.DrivelistDrive).size === 'number'; } -const TargetsTable = styled(({ refFn, ...props }) => { +const DrivesTable = styled(({ refFn, ...props }) => { return (
- ref={refFn} {...props} /> + ref={refFn} {...props} />
); })` @@ -145,30 +145,37 @@ const InitProgress = styled( } `; -interface TargetSelectorModalProps extends Omit { - done: (targets: scanner.adapters.DrivelistDrive[]) => void; +export interface DriveSelectorProps + extends Omit { + multipleSelection?: boolean; + cancel: () => void; + done: (drives: scanner.adapters.DrivelistDrive[]) => void; + titleLabel: string; + emptyListLabel: string; } -interface TargetSelectorModalState { - drives: Target[]; +interface DriveSelectorState { + drives: Drive[]; image: Image; missingDriversModal: { drive?: DriverlessDrive }; selectedList: scanner.adapters.DrivelistDrive[]; showSystemDrives: boolean; } -export class TargetSelectorModal extends React.Component< - TargetSelectorModalProps, - TargetSelectorModalState +export class DriveSelector extends React.Component< + DriveSelectorProps, + DriveSelectorState > { private unsubscribe: (() => void) | undefined; - tableColumns: Array>; + multipleSelection: boolean; + tableColumns: Array>; - constructor(props: TargetSelectorModalProps) { + constructor(props: DriveSelectorProps) { super(props); const defaultMissingDriversModalState: { drive?: DriverlessDrive } = {}; const selectedList = getSelectedDrives(); + this.multipleSelection = !!this.props.multipleSelection; this.state = { drives: getDrives(), @@ -182,7 +189,7 @@ export class TargetSelectorModal extends React.Component< { field: 'description', label: 'Name', - render: (description: string, drive: Target) => { + render: (description: string, drive: Drive) => { return isDrivelistDrive(drive) && drive.isSystem ? ( @@ -197,7 +204,7 @@ export class TargetSelectorModal extends React.Component< field: 'description', key: 'size', label: 'Size', - render: (_description: string, drive: Target) => { + render: (_description: string, drive: Drive) => { if (isDrivelistDrive(drive) && drive.size !== null) { return bytesToClosestUnit(drive.size); } @@ -207,7 +214,7 @@ export class TargetSelectorModal extends React.Component< field: 'description', key: 'link', label: 'Location', - render: (_description: string, drive: Target) => { + render: (_description: string, drive: Drive) => { return ( {drive.displayName} @@ -231,7 +238,7 @@ export class TargetSelectorModal extends React.Component< key: 'extra', // Space as empty string would use the field name as label label: ' ', - render: (_description: string, drive: Target) => { + render: (_description: string, drive: Drive) => { if (isUsbbootDrive(drive)) { return this.renderProgress(drive.progress); } else if (isDrivelistDrive(drive)) { @@ -244,7 +251,7 @@ export class TargetSelectorModal extends React.Component< ]; } - private driveShouldBeDisabled(drive: Target, image: any) { + private driveShouldBeDisabled(drive: Drive, image: any) { return ( isUsbbootDrive(drive) || isDriverlessDrive(drive) || @@ -252,8 +259,8 @@ export class TargetSelectorModal extends React.Component< ); } - private getDisplayedTargets(targets: Target[]): Target[] { - return targets.filter((drive) => { + private getDisplayedDrives(drives: Drive[]): Drive[] { + return drives.filter((drive) => { return ( isUsbbootDrive(drive) || isDriverlessDrive(drive) || @@ -264,7 +271,7 @@ export class TargetSelectorModal extends React.Component< }); } - private getDisabledTargets(drives: Target[], image: any): string[] { + private getDisabledDrives(drives: Drive[], image: any): string[] { return drives .filter((drive) => this.driveShouldBeDisabled(drive, image)) .map((drive) => drive.displayName); @@ -279,7 +286,7 @@ export class TargetSelectorModal extends React.Component< ); } - private renderStatuses(statuses: TargetStatus[]) { + private renderStatuses(statuses: DriveStatus[]) { return ( // the column render fn expects a single Element <> @@ -324,12 +331,12 @@ export class TargetSelectorModal extends React.Component< const { cancel, done, ...props } = this.props; const { selectedList, drives, image, missingDriversModal } = this.state; - const displayedTargets = this.getDisplayedTargets(drives); - const disabledTargets = this.getDisabledTargets(drives, image); + const displayedDrives = this.getDisplayedDrives(drives); + const disabledDrives = this.getDisabledDrives(drives, image); const numberOfSystemDrives = drives.filter( (drive) => isDrivelistDrive(drive) && drive.isSystem, ).length; - const numberOfDisplayedSystemDrives = displayedTargets.filter( + const numberOfDisplayedSystemDrives = displayedDrives.filter( (drive) => isDrivelistDrive(drive) && drive.isSystem, ).length; const numberOfHiddenSystemDrives = @@ -341,7 +348,7 @@ export class TargetSelectorModal extends React.Component< titleElement={ - Select target + {this.props.titleLabel} - - Plug a target drive + + {this.props.emptyListLabel} ) : ( - - ) => { + + ) => { if (t !== null) { t.setRowSelection(selectedList); } }} columns={this.tableColumns} - data={displayedTargets} - disabledRows={disabledTargets} + data={displayedDrives} + disabledRows={disabledDrives} rowKey="displayName" - onCheck={(rows: Target[]) => { + onCheck={(rows: Drive[]) => { + const newSelection = rows.filter(isDrivelistDrive); + if (this.multipleSelection) { + this.setState({ + selectedList: newSelection, + }); + return; + } this.setState({ - selectedList: rows.filter(isDrivelistDrive), + selectedList: newSelection.slice(newSelection.length - 1), }); }} - onRowClick={(row: Target) => { + onRowClick={(row: Drive) => { if ( !isDrivelistDrive(row) || this.driveShouldBeDisabled(row, image) ) { return; } - const newList = [...selectedList]; - const selectedIndex = selectedList.findIndex( - (target) => target.device === row.device, - ); - if (selectedIndex === -1) { - newList.push(row); - } else { - // Deselect if selected - newList.splice(selectedIndex, 1); + if (this.multipleSelection) { + const newList = [...selectedList]; + const selectedIndex = selectedList.findIndex( + (drive) => drive.device === row.device, + ); + if (selectedIndex === -1) { + newList.push(row); + } else { + // Deselect if selected + newList.splice(selectedIndex, 1); + } + this.setState({ + selectedList: newList, + }); + return; } this.setState({ - selectedList: newList, + selectedList: [row], }); }} /> 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 e6bd6424..f7abd371 100644 --- a/lib/gui/app/components/target-selector/target-selector-button.tsx +++ b/lib/gui/app/components/target-selector/target-selector-button.tsx @@ -67,7 +67,7 @@ function DriveCompatibilityWarning({ ); } -export function TargetSelector(props: TargetSelectorProps) { +export function TargetSelectorButton(props: TargetSelectorProps) { const targets = getSelectedDrives(); if (targets.length === 1) { diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/components/target-selector/target-selector.tsx similarity index 89% rename from lib/gui/app/pages/main/DriveSelector.tsx rename to lib/gui/app/components/target-selector/target-selector.tsx index f89e4d98..958e99f9 100644 --- a/lib/gui/app/pages/main/DriveSelector.tsx +++ b/lib/gui/app/components/target-selector/target-selector.tsx @@ -19,6 +19,10 @@ import * as React from 'react'; import { Flex } from 'rendition'; import { TargetSelector } from '../../components/target-selector/target-selector-button'; import { TargetSelectorModal } from '../../components/target-selector/target-selector-modal'; +import { + DriveSelector, + DriveSelectorProps, +} from '../drive-selector/drive-selector'; import { isDriveSelected, getImage, @@ -50,6 +54,16 @@ const getDriveSelectionStateSlice = () => ({ image: getImage(), }); +export const TargetSelectorModal = ( + props: Omit, +) => ( + +); + export const selectAllTargets = ( modalTargets: scanner.adapters.DrivelistDrive[], ) => { @@ -79,17 +93,17 @@ export const selectAllTargets = ( }); }; -interface DriveSelectorProps { +interface TargetSelectorProps { disabled: boolean; hasDrive: boolean; flashing: boolean; } -export const DriveSelector = ({ +export const TargetSelector = ({ disabled, hasDrive, flashing, -}: DriveSelectorProps) => { +}: TargetSelectorProps) => { // TODO: inject these from redux-connector const [ { showDrivesButton, driveListLabel, targets, image }, @@ -115,7 +129,7 @@ export const DriveSelector = ({ }} /> - + /> )} ); diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 24c6e9c6..b61853d4 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -24,7 +24,6 @@ import * as constraints from '../../../../shared/drive-constraints'; import * as messages from '../../../../shared/messages'; import { ProgressButton } from '../../components/progress-button/progress-button'; import { SourceOptions } from '../../components/source-selector/source-selector'; -import { TargetSelectorModal } from '../../components/target-selector/target-selector-modal'; import * as availableDrives from '../../models/available-drives'; import * as flashState from '../../models/flash-state'; import * as selection from '../../models/selection-state'; @@ -32,7 +31,10 @@ import * as analytics from '../../modules/analytics'; import { scanner as driveScanner } from '../../modules/drive-scanner'; import * as imageWriter from '../../modules/image-writer'; import * as notification from '../../os/notification'; -import { selectAllTargets } from './DriveSelector'; +import { + selectAllTargets, + TargetSelectorModal, +} from '../../components/target-selector/target-selector'; import FlashSvg from '../../../assets/flash.svg'; @@ -333,7 +335,7 @@ export class FlashStep extends React.PureComponent< selectAllTargets(modalTargets); this.setState({ showDriveSelectorModal: false }); }} - > + /> )} ); diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx index d4577175..e2306540 100644 --- a/lib/gui/app/pages/main/MainPage.tsx +++ b/lib/gui/app/pages/main/MainPage.tsx @@ -44,7 +44,10 @@ import { import { bytesToClosestUnit } from '../../../../shared/units'; -import { DriveSelector, getDriveListLabel } from './DriveSelector'; +import { + TargetSelector, + getDriveListLabel, +} from '../../components/target-selector/target-selector'; import { FlashStep } from './Flash'; import EtcherSvg from '../../../assets/etcher.svg'; @@ -252,7 +255,7 @@ export class MainPage extends React.Component< -