mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-19 09:16:38 +00:00
Make TargetSelectorModal a React.Component
Change-type: patch Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
This commit is contained in:
parent
7aec8a4ae2
commit
2dc359b19c
@ -35,6 +35,7 @@ const FlashProgressBar = styled(ProgressBar)`
|
|||||||
|
|
||||||
width: 220px;
|
width: 220px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 48px;
|
line-height: 48px;
|
||||||
@ -81,8 +82,16 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
});
|
});
|
||||||
if (this.props.active) {
|
if (this.props.active) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Flex justifyContent="space-between" style={{ fontWeight: 600 }}>
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
style={{
|
||||||
|
marginTop: 42,
|
||||||
|
marginBottom: '6px',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Txt color="#fff">{status} </Txt>
|
<Txt color="#fff">{status} </Txt>
|
||||||
<Txt color={colors[this.props.type]}>{position}</Txt>
|
<Txt color={colors[this.props.type]}>{position}</Txt>
|
||||||
@ -93,7 +102,7 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
background={colors[this.props.type]}
|
background={colors[this.props.type]}
|
||||||
value={this.props.percentage}
|
value={this.props.percentage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -19,17 +19,24 @@ import {
|
|||||||
faExclamationTriangle,
|
faExclamationTriangle,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { Drive as DrivelistDrive } from 'drivelist';
|
import { Drive as DrivelistDrive } from 'drivelist';
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Badge, Table as BaseTable, Txt, Flex, Link } from 'rendition';
|
import {
|
||||||
|
Badge,
|
||||||
|
Table,
|
||||||
|
Txt,
|
||||||
|
Flex,
|
||||||
|
Link,
|
||||||
|
TableColumn,
|
||||||
|
ModalProps,
|
||||||
|
} from 'rendition';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDriveImageCompatibilityStatuses,
|
getDriveImageCompatibilityStatuses,
|
||||||
hasListDriveImageCompatibilityStatus,
|
hasListDriveImageCompatibilityStatus,
|
||||||
isDriveValid,
|
isDriveValid,
|
||||||
hasDriveImageCompatibilityStatus,
|
|
||||||
TargetStatus,
|
TargetStatus,
|
||||||
|
Image,
|
||||||
} from '../../../../shared/drive-constraints';
|
} from '../../../../shared/drive-constraints';
|
||||||
import { compatibility } from '../../../../shared/messages';
|
import { compatibility } from '../../../../shared/messages';
|
||||||
import { bytesToClosestUnit } from '../../../../shared/units';
|
import { bytesToClosestUnit } from '../../../../shared/units';
|
||||||
@ -63,13 +70,32 @@ export interface DrivelistTarget extends DrivelistDrive {
|
|||||||
* containing the status type (ERROR, WARNING), and accompanying
|
* containing the status type (ERROR, WARNING), and accompanying
|
||||||
* status message.
|
* status message.
|
||||||
*/
|
*/
|
||||||
function getDriveStatuses(drive: DrivelistTarget): TargetStatus[] {
|
function getDriveStatuses(
|
||||||
return getDriveImageCompatibilityStatuses(drive, getImage());
|
drive: DrivelistTarget,
|
||||||
|
image: Image,
|
||||||
|
): TargetStatus[] {
|
||||||
|
return getDriveImageCompatibilityStatuses(drive, image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ScrollableFlex = styled(Flex)`
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const TargetsTable = styled(({ refFn, ...props }) => {
|
const TargetsTable = styled(({ refFn, ...props }) => {
|
||||||
return <BaseTable<DrivelistTarget> ref={refFn} {...props}></BaseTable>;
|
return (
|
||||||
|
<div>
|
||||||
|
<Table<DrivelistTarget> ref={refFn} {...props} />
|
||||||
|
</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 {
|
||||||
@ -114,17 +140,6 @@ function badgeShadeFromStatus(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatuses(statuses: TargetStatus[]) {
|
|
||||||
return _.map(statuses, (status) => {
|
|
||||||
const badgeShade = badgeShadeFromStatus(status.message);
|
|
||||||
return (
|
|
||||||
<Badge key={status.message} shade={badgeShade}>
|
|
||||||
{status.message}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const InitProgress = styled(
|
const InitProgress = styled(
|
||||||
({
|
({
|
||||||
value,
|
value,
|
||||||
@ -152,8 +167,127 @@ const InitProgress = styled(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function renderProgress(progress: number) {
|
interface TableData extends DrivelistTarget {
|
||||||
if (progress) {
|
disabled: boolean;
|
||||||
|
extra: TargetStatus[] | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetSelectorModalProps extends Omit<ModalProps, 'done'> {
|
||||||
|
done: (targets: DrivelistTarget[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetSelectorModalState {
|
||||||
|
drives: any[];
|
||||||
|
image: Image;
|
||||||
|
missingDriversModal: { drive?: DriverlessDrive };
|
||||||
|
selectedList: any[];
|
||||||
|
showSystemDrives: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TargetSelectorModal extends React.Component<
|
||||||
|
TargetSelectorModalProps,
|
||||||
|
TargetSelectorModalState
|
||||||
|
> {
|
||||||
|
unsubscribe: () => void;
|
||||||
|
tableColumns: Array<TableColumn<TableData>>;
|
||||||
|
|
||||||
|
constructor(props: TargetSelectorModalProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const defaultMissingDriversModalState: { drive?: DriverlessDrive } = {};
|
||||||
|
const selectedList = getSelectedDrives();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
drives: getDrives(),
|
||||||
|
image: getImage(),
|
||||||
|
missingDriversModal: defaultMissingDriversModalState,
|
||||||
|
selectedList,
|
||||||
|
showSystemDrives: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tableColumns = [
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
label: 'Name',
|
||||||
|
render: (description: string, drive: DrivelistTarget) => {
|
||||||
|
return drive.isSystem ? (
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
style={{ color: '#fca321' }}
|
||||||
|
icon={faExclamationTriangle}
|
||||||
|
/>
|
||||||
|
<Txt ml={8}>{description}</Txt>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Txt>{description}</Txt>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'size',
|
||||||
|
label: 'Size',
|
||||||
|
render: bytesToClosestUnit,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'link',
|
||||||
|
label: 'Location',
|
||||||
|
render: (link: string, drive: DrivelistTarget) => {
|
||||||
|
return link ? (
|
||||||
|
<Txt>
|
||||||
|
{drive.displayName} -{' '}
|
||||||
|
<b>
|
||||||
|
<a onClick={() => this.installMissingDrivers(drive)}>
|
||||||
|
{drive.linkCTA}
|
||||||
|
</a>
|
||||||
|
</b>
|
||||||
|
</Txt>
|
||||||
|
) : (
|
||||||
|
<Txt>{drive.displayName}</Txt>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'extra',
|
||||||
|
label: ' ',
|
||||||
|
render: (extra: TargetStatus[] | number) => {
|
||||||
|
if (typeof extra === 'number') {
|
||||||
|
return this.renderProgress(extra);
|
||||||
|
}
|
||||||
|
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 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[] {
|
||||||
|
return drives
|
||||||
|
.filter(
|
||||||
|
(drive) => !isDriveValid(drive, image) || drive.progress !== undefined,
|
||||||
|
)
|
||||||
|
.map((drive) => drive.displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderProgress(progress: number) {
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<Txt fontSize={12}>Initializing device</Txt>
|
<Txt fontSize={12}>Initializing device</Txt>
|
||||||
@ -161,116 +295,24 @@ function renderProgress(progress: number) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
interface TableData extends DrivelistTarget {
|
private renderStatuses(statuses: TargetStatus[]) {
|
||||||
disabled: boolean;
|
return (
|
||||||
}
|
// the column render fn expects a single Element
|
||||||
|
<>
|
||||||
|
{statuses.map((status) => {
|
||||||
|
const badgeShade = badgeShadeFromStatus(status.message);
|
||||||
|
return (
|
||||||
|
<Badge key={status.message} shade={badgeShade}>
|
||||||
|
{status.message}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const TargetSelectorModal = ({
|
private installMissingDrivers(drive: {
|
||||||
close,
|
|
||||||
cancel,
|
|
||||||
}: {
|
|
||||||
close: (targets: DrivelistTarget[]) => void;
|
|
||||||
cancel: () => void;
|
|
||||||
}) => {
|
|
||||||
const defaultMissingDriversModalState: { drive?: DriverlessDrive } = {};
|
|
||||||
const [missingDriversModal, setMissingDriversModal] = React.useState(
|
|
||||||
defaultMissingDriversModalState,
|
|
||||||
);
|
|
||||||
const [drives, setDrives] = React.useState(getDrives());
|
|
||||||
const [selectedList, setSelected] = React.useState(getSelectedDrives());
|
|
||||||
const [showSystemDrives, setShowSystemDrives] = React.useState(false);
|
|
||||||
const image = getImage();
|
|
||||||
const hasStatus = hasListDriveImageCompatibilityStatus(selectedList, image);
|
|
||||||
|
|
||||||
const enrichedDrivesData = _.map(drives, (drive) => {
|
|
||||||
return {
|
|
||||||
...drive,
|
|
||||||
extra: drive.progress || getDriveStatuses(drive),
|
|
||||||
disabled: !isDriveValid(drive, image) || drive.progress,
|
|
||||||
highlighted: hasDriveImageCompatibilityStatus(drive, image),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const normalDrives = _.reject(
|
|
||||||
enrichedDrivesData,
|
|
||||||
(drive) => drive.isSystem && !isDriveSelected(drive.device),
|
|
||||||
);
|
|
||||||
const systemDrives = _.filter(enrichedDrivesData, 'isSystem');
|
|
||||||
const disabledRows = _.map(
|
|
||||||
_.filter(drives, (drive) => {
|
|
||||||
return !isDriveValid(drive, image) || drive.progress;
|
|
||||||
}),
|
|
||||||
'displayName',
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
field: 'description',
|
|
||||||
label: 'Name',
|
|
||||||
render: (description: string, drive: DrivelistTarget) => {
|
|
||||||
return drive.isSystem ? (
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
style={{ color: '#fca321' }}
|
|
||||||
icon={faExclamationTriangle}
|
|
||||||
/>
|
|
||||||
<Txt ml={8}>{description}</Txt>
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<Txt>{description}</Txt>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'size',
|
|
||||||
label: 'Size',
|
|
||||||
render: (size: number) => {
|
|
||||||
return bytesToClosestUnit(size);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'link',
|
|
||||||
label: 'Location',
|
|
||||||
render: (link: string, drive: DrivelistTarget) => {
|
|
||||||
return !link ? (
|
|
||||||
<Txt>{drive.displayName}</Txt>
|
|
||||||
) : (
|
|
||||||
<Txt>
|
|
||||||
{drive.displayName} -{' '}
|
|
||||||
<b>
|
|
||||||
<a onClick={() => installMissingDrivers(drive)}>
|
|
||||||
{drive.linkCTA}
|
|
||||||
</a>
|
|
||||||
</b>
|
|
||||||
</Txt>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'extra',
|
|
||||||
label: ' ',
|
|
||||||
render: (extra: TargetStatus[] | number) => {
|
|
||||||
if (typeof extra === 'number') {
|
|
||||||
return renderProgress(extra);
|
|
||||||
}
|
|
||||||
return renderStatuses(extra);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const unsubscribe = store.subscribe(() => {
|
|
||||||
setDrives(getDrives());
|
|
||||||
setSelected(getSelectedDrives());
|
|
||||||
});
|
|
||||||
return unsubscribe;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Prompt the user to install missing usbboot drivers
|
|
||||||
*/
|
|
||||||
function installMissingDrivers(drive: {
|
|
||||||
link: string;
|
link: string;
|
||||||
linkTitle: string;
|
linkTitle: string;
|
||||||
linkMessage: string;
|
linkMessage: string;
|
||||||
@ -278,131 +320,169 @@ export const TargetSelectorModal = ({
|
|||||||
if (drive.link) {
|
if (drive.link) {
|
||||||
analytics.logEvent('Open driver link modal', {
|
analytics.logEvent('Open driver link modal', {
|
||||||
url: drive.link,
|
url: drive.link,
|
||||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
|
||||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid,
|
|
||||||
});
|
});
|
||||||
setMissingDriversModal({ drive });
|
this.setState({ missingDriversModal: { drive } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
componentDidMount() {
|
||||||
<Modal
|
this.unsubscribe = store.subscribe(() => {
|
||||||
titleElement={
|
const drives = getDrives();
|
||||||
<Flex alignItems="baseline" mb={18}>
|
const image = getImage();
|
||||||
<Txt fontSize={24} align="left">
|
this.setState({
|
||||||
Select target
|
drives,
|
||||||
</Txt>
|
image,
|
||||||
<Txt
|
selectedList: getSelectedDrives(),
|
||||||
fontSize={11}
|
});
|
||||||
ml={12}
|
});
|
||||||
color="#5b82a7"
|
}
|
||||||
style={{ fontWeight: 600 }}
|
|
||||||
>
|
componentWillUnmount() {
|
||||||
{drives.length} found
|
this.unsubscribe();
|
||||||
</Txt>
|
}
|
||||||
</Flex>
|
|
||||||
}
|
render() {
|
||||||
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
const { cancel, done, ...props } = this.props;
|
||||||
cancel={cancel}
|
const {
|
||||||
done={() => close(selectedList)}
|
selectedList,
|
||||||
action="Continue"
|
showSystemDrives,
|
||||||
style={{
|
drives,
|
||||||
width: '780px',
|
image,
|
||||||
height: '420px',
|
missingDriversModal,
|
||||||
}}
|
} = this.state;
|
||||||
primaryButtonProps={{
|
|
||||||
primary: !hasStatus,
|
const targetsWithTableData = this.buildTableData(drives, image);
|
||||||
warning: hasStatus,
|
const displayedTargets = this.getDisplayedTargets(targetsWithTableData);
|
||||||
}}
|
const disabledTargets = this.getDisabledTargets(drives, image);
|
||||||
>
|
const numberOfSystemDrives = drives.filter((drive) => drive.isSystem)
|
||||||
<div>
|
.length;
|
||||||
{!hasAvailableDrives() ? (
|
const numberOfDisplayedSystemDrives = displayedTargets.filter(
|
||||||
<div style={{ textAlign: 'center', margin: '0 auto' }}>
|
(drive) => drive.isSystem,
|
||||||
<b>Plug a target drive</b>
|
).length;
|
||||||
</div>
|
const numberOfHiddenSystemDrives =
|
||||||
) : (
|
numberOfSystemDrives - numberOfDisplayedSystemDrives;
|
||||||
<Flex
|
const hasStatus = hasListDriveImageCompatibilityStatus(selectedList, image);
|
||||||
flexDirection="column"
|
|
||||||
style={{ maxHeight: !showSystemDrives ? 250 : 265 }}
|
return (
|
||||||
>
|
<Modal
|
||||||
<TargetsTable
|
titleElement={
|
||||||
refFn={(t: BaseTable<TableData>) => {
|
<Flex alignItems="baseline" mb={18}>
|
||||||
if (!_.isNull(t)) {
|
<Txt fontSize={24} align="left">
|
||||||
t.setRowSelection(selectedList);
|
Select target
|
||||||
}
|
</Txt>
|
||||||
}}
|
<Txt
|
||||||
columns={columns}
|
fontSize={11}
|
||||||
data={_.uniq(
|
ml={12}
|
||||||
showSystemDrives
|
color="#5b82a7"
|
||||||
? normalDrives.concat(systemDrives)
|
style={{ fontWeight: 600 }}
|
||||||
: normalDrives,
|
>
|
||||||
)}
|
{drives.length} found
|
||||||
disabledRows={disabledRows}
|
</Txt>
|
||||||
rowKey="displayName"
|
</Flex>
|
||||||
onCheck={(rows: TableData[]) => {
|
}
|
||||||
setSelected(rows);
|
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
||||||
}}
|
cancel={cancel}
|
||||||
onRowClick={(row: TableData) => {
|
done={() => done(selectedList)}
|
||||||
if (!row.disabled) {
|
action="Continue"
|
||||||
|
style={{
|
||||||
|
width: '780px',
|
||||||
|
height: '420px',
|
||||||
|
}}
|
||||||
|
primaryButtonProps={{
|
||||||
|
primary: !hasStatus,
|
||||||
|
warning: hasStatus,
|
||||||
|
disabled: !hasAvailableDrives(),
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Flex width="100%" height="100%">
|
||||||
|
{!hasAvailableDrives() ? (
|
||||||
|
<Flex justifyContent="center" alignItems="center" width="100%">
|
||||||
|
<b>Plug a target drive</b>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<ScrollableFlex
|
||||||
|
flexDirection="column"
|
||||||
|
width="100%"
|
||||||
|
height="calc(100% - 15px)"
|
||||||
|
>
|
||||||
|
<TargetsTable
|
||||||
|
refFn={(t: Table<TableData>) => {
|
||||||
|
if (t !== null) {
|
||||||
|
t.setRowSelection(selectedList);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
columns={this.tableColumns}
|
||||||
|
data={displayedTargets}
|
||||||
|
disabledRows={disabledTargets}
|
||||||
|
rowKey="displayName"
|
||||||
|
onCheck={(rows: TableData[]) => {
|
||||||
|
this.setState({
|
||||||
|
selectedList: rows,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onRowClick={(row: TableData) => {
|
||||||
|
if (row.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newList = [...selectedList];
|
||||||
const selectedIndex = selectedList.findIndex(
|
const selectedIndex = selectedList.findIndex(
|
||||||
(target) => target.device === row.device,
|
(target) => target.device === row.device,
|
||||||
);
|
);
|
||||||
if (selectedIndex === -1) {
|
if (selectedIndex === -1) {
|
||||||
selectedList.push(row);
|
newList.push(row);
|
||||||
setSelected(_.map(selectedList));
|
} else {
|
||||||
return;
|
// Deselect if selected
|
||||||
|
newList.splice(selectedIndex, 1);
|
||||||
}
|
}
|
||||||
// Deselect if selected
|
this.setState({
|
||||||
setSelected(
|
selectedList: newList,
|
||||||
_.reject(
|
});
|
||||||
selectedList,
|
}}
|
||||||
(drive) =>
|
/>
|
||||||
selectedList[selectedIndex].device === drive.device,
|
{!showSystemDrives && numberOfHiddenSystemDrives > 0 && (
|
||||||
),
|
<Link
|
||||||
);
|
mt={15}
|
||||||
}
|
mb={15}
|
||||||
}}
|
onClick={() => this.setState({ showSystemDrives: true })}
|
||||||
></TargetsTable>
|
>
|
||||||
{!showSystemDrives && (
|
<Flex alignItems="center">
|
||||||
<Link mt={16} onClick={() => setShowSystemDrives(true)}>
|
<FontAwesomeIcon icon={faChevronDown} />
|
||||||
<Flex alignItems="center">
|
<Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
</Flex>
|
||||||
<Txt ml={8}>
|
</Link>
|
||||||
Show {drives.length - normalDrives.length} hidden
|
)}
|
||||||
</Txt>
|
</ScrollableFlex>
|
||||||
</Flex>
|
)}
|
||||||
</Link>
|
</Flex>
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{missingDriversModal.drive !== undefined && (
|
{missingDriversModal.drive !== undefined && (
|
||||||
<Modal
|
<Modal
|
||||||
width={400}
|
width={400}
|
||||||
title={missingDriversModal.drive.linkTitle}
|
title={missingDriversModal.drive.linkTitle}
|
||||||
cancel={() => setMissingDriversModal({})}
|
cancel={() => this.setState({ missingDriversModal: {} })}
|
||||||
done={() => {
|
done={() => {
|
||||||
try {
|
try {
|
||||||
if (missingDriversModal.drive !== undefined) {
|
if (missingDriversModal.drive !== undefined) {
|
||||||
openExternal(missingDriversModal.drive.link);
|
openExternal(missingDriversModal.drive.link);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
analytics.logException(error);
|
||||||
|
} finally {
|
||||||
|
this.setState({ missingDriversModal: {} });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}}
|
||||||
analytics.logException(error);
|
action="Yes, continue"
|
||||||
} finally {
|
cancelButtonProps={{
|
||||||
setMissingDriversModal({});
|
children: 'Cancel',
|
||||||
|
}}
|
||||||
|
children={
|
||||||
|
missingDriversModal.drive.linkMessage ||
|
||||||
|
`Etcher will open ${missingDriversModal.drive.link} in your browser`
|
||||||
}
|
}
|
||||||
}}
|
/>
|
||||||
action={'Yes, continue'}
|
)}
|
||||||
cancelButtonProps={{
|
</Modal>
|
||||||
children: 'Cancel',
|
);
|
||||||
}}
|
}
|
||||||
children={
|
}
|
||||||
missingDriversModal.drive.linkMessage ||
|
|
||||||
`Etcher will open ${missingDriversModal.drive.link} in your browser`
|
|
||||||
}
|
|
||||||
></Modal>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
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';
|
||||||
@ -23,6 +22,7 @@ import {
|
|||||||
TargetSelectorModal,
|
TargetSelectorModal,
|
||||||
} from '../../components/target-selector/target-selector-modal';
|
} from '../../components/target-selector/target-selector-modal';
|
||||||
import {
|
import {
|
||||||
|
isDriveSelected,
|
||||||
getImage,
|
getImage,
|
||||||
getSelectedDrives,
|
getSelectedDrives,
|
||||||
deselectDrive,
|
deselectDrive,
|
||||||
@ -53,12 +53,11 @@ const StepBorder = styled.div<{
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const getDriveListLabel = () => {
|
const getDriveListLabel = () => {
|
||||||
return _.join(
|
return getSelectedDrives()
|
||||||
_.map(getSelectedDrives(), (drive: any) => {
|
.map((drive: any) => {
|
||||||
return `${drive.description} (${drive.displayName})`;
|
return `${drive.description} (${drive.displayName})`;
|
||||||
}),
|
})
|
||||||
'\n',
|
.join('\n');
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowDrivesButton = () => {
|
const shouldShowDrivesButton = () => {
|
||||||
@ -72,6 +71,33 @@ const getDriveSelectionStateSlice = () => ({
|
|||||||
image: getImage(),
|
image: getImage(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const selectAllTargets = (modalTargets: DrivelistTarget[]) => {
|
||||||
|
const selectedDrivesFromState = getSelectedDrives();
|
||||||
|
const deselected = selectedDrivesFromState.filter(
|
||||||
|
(drive) =>
|
||||||
|
!modalTargets.find((modalTarget) => modalTarget.device === drive.device),
|
||||||
|
);
|
||||||
|
// deselect drives
|
||||||
|
deselected.forEach((drive) => {
|
||||||
|
analytics.logEvent('Toggle drive', {
|
||||||
|
drive,
|
||||||
|
previouslySelected: true,
|
||||||
|
});
|
||||||
|
deselectDrive(drive.device);
|
||||||
|
});
|
||||||
|
// select drives
|
||||||
|
modalTargets.forEach((drive) => {
|
||||||
|
// Don't send events for drives that were already selected
|
||||||
|
if (!isDriveSelected(drive.device)) {
|
||||||
|
analytics.logEvent('Toggle drive', {
|
||||||
|
drive,
|
||||||
|
previouslySelected: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectDrive(drive.device);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
interface DriveSelectorProps {
|
interface DriveSelectorProps {
|
||||||
webviewShowing: boolean;
|
webviewShowing: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
@ -138,19 +164,8 @@ export const DriveSelector = ({
|
|||||||
{showTargetSelectorModal && (
|
{showTargetSelectorModal && (
|
||||||
<TargetSelectorModal
|
<TargetSelectorModal
|
||||||
cancel={() => setShowTargetSelectorModal(false)}
|
cancel={() => setShowTargetSelectorModal(false)}
|
||||||
close={(selectedTargets: DrivelistTarget[]) => {
|
done={(modalTargets) => {
|
||||||
const selectedDrives = getSelectedDrives();
|
selectAllTargets(modalTargets);
|
||||||
if (_.isEmpty(selectedTargets)) {
|
|
||||||
_.each(_.map(selectedDrives, 'device'), deselectDrive);
|
|
||||||
} else {
|
|
||||||
const deselected = _.reject(selectedDrives, (drive) =>
|
|
||||||
_.find(selectedTargets, (row) => row.device === drive.device),
|
|
||||||
);
|
|
||||||
// select drives
|
|
||||||
_.each(_.map(selectedTargets, 'device'), selectDrive);
|
|
||||||
// deselect drives
|
|
||||||
_.each(_.map(deselected, 'device'), deselectDrive);
|
|
||||||
}
|
|
||||||
setShowTargetSelectorModal(false);
|
setShowTargetSelectorModal(false);
|
||||||
}}
|
}}
|
||||||
></TargetSelectorModal>
|
></TargetSelectorModal>
|
||||||
|
@ -23,10 +23,7 @@ import * as constraints from '../../../../shared/drive-constraints';
|
|||||||
import * as messages from '../../../../shared/messages';
|
import * as messages from '../../../../shared/messages';
|
||||||
import { ProgressButton } from '../../components/progress-button/progress-button';
|
import { ProgressButton } from '../../components/progress-button/progress-button';
|
||||||
import { SourceOptions } from '../../components/source-selector/source-selector';
|
import { SourceOptions } from '../../components/source-selector/source-selector';
|
||||||
import {
|
import { TargetSelectorModal } from '../../components/target-selector/target-selector-modal';
|
||||||
TargetSelectorModal,
|
|
||||||
DrivelistTarget,
|
|
||||||
} from '../../components/target-selector/target-selector-modal';
|
|
||||||
import * as availableDrives from '../../models/available-drives';
|
import * as availableDrives from '../../models/available-drives';
|
||||||
import * as flashState from '../../models/flash-state';
|
import * as flashState from '../../models/flash-state';
|
||||||
import * as selection from '../../models/selection-state';
|
import * as selection from '../../models/selection-state';
|
||||||
@ -34,6 +31,7 @@ import * as analytics from '../../modules/analytics';
|
|||||||
import { scanner as driveScanner } from '../../modules/drive-scanner';
|
import { scanner as driveScanner } from '../../modules/drive-scanner';
|
||||||
import * as imageWriter from '../../modules/image-writer';
|
import * as imageWriter from '../../modules/image-writer';
|
||||||
import * as notification from '../../os/notification';
|
import * as notification from '../../os/notification';
|
||||||
|
import { selectAllTargets } from './DriveSelector';
|
||||||
|
|
||||||
import FlashSvg from '../../../assets/flash.svg';
|
import FlashSvg from '../../../assets/flash.svg';
|
||||||
|
|
||||||
@ -331,22 +329,8 @@ export class FlashStep extends React.PureComponent<
|
|||||||
{this.state.showDriveSelectorModal && (
|
{this.state.showDriveSelectorModal && (
|
||||||
<TargetSelectorModal
|
<TargetSelectorModal
|
||||||
cancel={() => this.setState({ showDriveSelectorModal: false })}
|
cancel={() => this.setState({ showDriveSelectorModal: false })}
|
||||||
close={(targets: DrivelistTarget[]) => {
|
done={(modalTargets) => {
|
||||||
const selectedDrives = selection.getSelectedDrives();
|
selectAllTargets(modalTargets);
|
||||||
if (_.isEmpty(targets)) {
|
|
||||||
_.each(
|
|
||||||
_.map(selectedDrives, 'device'),
|
|
||||||
selection.deselectDrive,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const deselected = _.reject(selectedDrives, (drive) =>
|
|
||||||
_.find(targets, (row) => row.device === drive.device),
|
|
||||||
);
|
|
||||||
// select drives
|
|
||||||
_.each(_.map(targets, 'device'), selection.selectDrive);
|
|
||||||
// deselect drives
|
|
||||||
_.each(_.map(deselected, 'device'), selection.deselectDrive);
|
|
||||||
}
|
|
||||||
this.setState({ showDriveSelectorModal: false });
|
this.setState({ showDriveSelectorModal: false });
|
||||||
}}
|
}}
|
||||||
></TargetSelectorModal>
|
></TargetSelectorModal>
|
||||||
|
@ -124,6 +124,7 @@ export const Modal = styled((props) => {
|
|||||||
})`
|
})`
|
||||||
> div {
|
> div {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
|
||||||
> h3 {
|
> h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user