Split drive selector from target selector

Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
This commit is contained in:
Lorenzo Alberto Maria Ambrosi 2020-06-22 17:15:55 +02:00
parent 07befd0bd1
commit 377dfb8e22
6 changed files with 104 additions and 62 deletions

View File

@ -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 (
<div>
<Table<Target> ref={refFn} {...props} />
<Table<Drive> ref={refFn} {...props} />
</div>
);
})`
@ -145,30 +145,37 @@ const InitProgress = styled(
}
`;
interface TargetSelectorModalProps extends Omit<ModalProps, 'done'> {
done: (targets: scanner.adapters.DrivelistDrive[]) => void;
export interface DriveSelectorProps
extends Omit<ModalProps, 'done' | 'cancel'> {
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<TableColumn<Target>>;
multipleSelection: boolean;
tableColumns: Array<TableColumn<Drive>>;
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 ? (
<Flex alignItems="center">
<ExclamationTriangleSvg height="1em" fill="#fca321" />
@ -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 (
<Txt>
{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={
<Flex alignItems="baseline" mb={18}>
<Txt fontSize={24} align="left">
Select target
{this.props.titleLabel}
</Txt>
<Txt
fontSize={11}
@ -372,36 +379,47 @@ export class TargetSelectorModal extends React.Component<
alignItems="center"
width="100%"
>
<TargetSVGIcon width="40px" height="90px" />
<b>Plug a target drive</b>
<DriveSVGIcon width="40px" height="90px" />
<b>{this.props.emptyListLabel}</b>
</Flex>
) : (
<ScrollableFlex flexDirection="column" width="100%">
<TargetsTable
refFn={(t: Table<Target>) => {
<ScrollableFlex
flexDirection="column"
width="100%"
>
<DrivesTable
refFn={(t: Table<Drive>) => {
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: rows.filter(isDrivelistDrive),
selectedList: newSelection,
});
return;
}
this.setState({
selectedList: newSelection.slice(newSelection.length - 1),
});
}}
onRowClick={(row: Target) => {
onRowClick={(row: Drive) => {
if (
!isDrivelistDrive(row) ||
this.driveShouldBeDisabled(row, image)
) {
return;
}
if (this.multipleSelection) {
const newList = [...selectedList];
const selectedIndex = selectedList.findIndex(
(target) => target.device === row.device,
(drive) => drive.device === row.device,
);
if (selectedIndex === -1) {
newList.push(row);
@ -412,6 +430,11 @@ export class TargetSelectorModal extends React.Component<
this.setState({
selectedList: newList,
});
return;
}
this.setState({
selectedList: [row],
});
}}
/>
{numberOfHiddenSystemDrives > 0 && (

View File

@ -67,7 +67,7 @@ function DriveCompatibilityWarning({
);
}
export function TargetSelector(props: TargetSelectorProps) {
export function TargetSelectorButton(props: TargetSelectorProps) {
const targets = getSelectedDrives();
if (targets.length === 1) {

View File

@ -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<DriveSelectorProps, 'titleLabel' | 'emptyListLabel'>,
) => (
<DriveSelector
titleLabel="Select target"
emptyListLabel="Plug a target drive"
{...props}
/>
);
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 = ({
}}
/>
<TargetSelector
<TargetSelectorButton
disabled={disabled}
show={!hasDrive && showDrivesButton}
tooltip={driveListLabel}
@ -138,7 +152,7 @@ export const DriveSelector = ({
selectAllTargets(modalTargets);
setShowTargetSelectorModal(false);
}}
></TargetSelectorModal>
/>
)}
</Flex>
);

View File

@ -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 });
}}
></TargetSelectorModal>
/>
)}
</>
);

View File

@ -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<
<Flex>
<StepBorder disabled={shouldDriveStepBeDisabled} left />
</Flex>
<DriveSelector
<TargetSelector
disabled={shouldDriveStepBeDisabled}
hasDrive={this.state.hasDrive}
flashing={this.state.isFlashing}

View File

@ -276,7 +276,7 @@ export function hasListDriveImageCompatibilityStatus(
return Boolean(getListDriveImageCompatibilityStatuses(drives, image).length);
}
export interface TargetStatus {
export interface DriveStatus {
message: string;
type: number;
}