mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 03:06:38 +00:00
Stricter types in target-selector-modal.tsx
Change-type: patch
This commit is contained in:
parent
d63f5eca0d
commit
9444f0e1b1
@ -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: {} });
|
||||||
}
|
}
|
||||||
|
@ -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) =>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user