Stricter types in target-selector-modal.tsx

Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-06-17 20:15:13 +02:00
parent d63f5eca0d
commit 9444f0e1b1
2 changed files with 107 additions and 112 deletions

View File

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

View File

@ -14,13 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { scanner } from 'etcher-sdk';
import * as React from 'react'; import * as React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { TargetSelector } from '../../components/target-selector/target-selector-button'; import { TargetSelector } from '../../components/target-selector/target-selector-button';
import { import { TargetSelectorModal } from '../../components/target-selector/target-selector-modal';
DrivelistTarget,
TargetSelectorModal,
} from '../../components/target-selector/target-selector-modal';
import { import {
isDriveSelected, isDriveSelected,
getImage, getImage,
@ -71,7 +70,9 @@ const getDriveSelectionStateSlice = () => ({
image: getImage(), image: getImage(),
}); });
export const selectAllTargets = (modalTargets: DrivelistTarget[]) => { export const selectAllTargets = (
modalTargets: scanner.adapters.DrivelistDrive[],
) => {
const selectedDrivesFromState = getSelectedDrives(); const selectedDrivesFromState = getSelectedDrives();
const deselected = selectedDrivesFromState.filter( const deselected = selectedDrivesFromState.filter(
(drive) => (drive) =>