mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
Convert target-selector.jsx to typescript
Also fix showing the drive compatibility warnings Change-type: patch
This commit is contained in:
parent
950b764ff1
commit
90921a74ea
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 balena.io
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable no-magic-numbers */
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const React = require('react')
|
|
||||||
const propTypes = require('prop-types')
|
|
||||||
const { default: styled } = require('styled-components')
|
|
||||||
const {
|
|
||||||
ChangeButton,
|
|
||||||
DetailsText,
|
|
||||||
StepButton,
|
|
||||||
StepNameButton
|
|
||||||
} = require('./../../styled-components')
|
|
||||||
const { Txt } = require('rendition')
|
|
||||||
const { middleEllipsis } = require('./../../utils/middle-ellipsis')
|
|
||||||
const { bytesToClosestUnit } = require('./../../../../shared/units')
|
|
||||||
|
|
||||||
const TargetDetail = styled((props) => (
|
|
||||||
<Txt.span {...props}>
|
|
||||||
</Txt.span>
|
|
||||||
)) `
|
|
||||||
float: ${({ float }) => float}
|
|
||||||
`
|
|
||||||
|
|
||||||
const TargetDisplayText = ({
|
|
||||||
description,
|
|
||||||
size,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Txt.span {...props}>
|
|
||||||
<TargetDetail
|
|
||||||
float='left'>
|
|
||||||
{description}
|
|
||||||
</TargetDetail>
|
|
||||||
<TargetDetail
|
|
||||||
float='right'
|
|
||||||
>
|
|
||||||
{size}
|
|
||||||
</TargetDetail>
|
|
||||||
</Txt.span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const TargetSelector = (props) => {
|
|
||||||
const targets = props.selection.getSelectedDrives()
|
|
||||||
|
|
||||||
if (targets.length === 1) {
|
|
||||||
const target = targets[0]
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<StepNameButton
|
|
||||||
plain
|
|
||||||
tooltip={props.tooltip}
|
|
||||||
>
|
|
||||||
{/* eslint-disable no-magic-numbers */}
|
|
||||||
{ middleEllipsis(target.description, 20) }
|
|
||||||
</StepNameButton>
|
|
||||||
{!props.flashing &&
|
|
||||||
<ChangeButton
|
|
||||||
plain
|
|
||||||
mb={14}
|
|
||||||
onClick={props.reselectDrive}
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</ChangeButton>
|
|
||||||
}
|
|
||||||
<DetailsText>
|
|
||||||
{ props.constraints.hasListDriveImageCompatibilityStatus(targets, props.image) &&
|
|
||||||
<Txt.span className='glyphicon glyphicon-exclamation-sign'
|
|
||||||
ml={2}
|
|
||||||
tooltip={
|
|
||||||
props.constraints.getListDriveImageCompatibilityStatuses(targets, props.image)[0].message
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{ bytesToClosestUnit(target.size) }
|
|
||||||
</DetailsText>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targets.length > 1) {
|
|
||||||
const targetsTemplate = []
|
|
||||||
for (const target of targets) {
|
|
||||||
targetsTemplate.push((
|
|
||||||
<DetailsText
|
|
||||||
key={target.device}
|
|
||||||
tooltip={
|
|
||||||
`${target.description} ${target.displayName} ${bytesToClosestUnit(target.size)}`
|
|
||||||
}
|
|
||||||
px={21}
|
|
||||||
>
|
|
||||||
<TargetDisplayText
|
|
||||||
description={middleEllipsis(target.description, 14)}
|
|
||||||
size={bytesToClosestUnit(target.size)}
|
|
||||||
>
|
|
||||||
</TargetDisplayText>
|
|
||||||
</DetailsText>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<StepNameButton
|
|
||||||
plain
|
|
||||||
tooltip={props.tooltip}
|
|
||||||
>
|
|
||||||
{targets.length} Targets
|
|
||||||
</StepNameButton>
|
|
||||||
{ !props.flashing &&
|
|
||||||
<ChangeButton
|
|
||||||
plain
|
|
||||||
onClick={props.reselectDrive}
|
|
||||||
mb={14}
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</ChangeButton>
|
|
||||||
}
|
|
||||||
{targetsTemplate}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StepButton
|
|
||||||
tabindex={(targets.length > 0) ? -1 : 2 }
|
|
||||||
disabled={props.disabled}
|
|
||||||
onClick={props.openDriveSelector}
|
|
||||||
>
|
|
||||||
Select target
|
|
||||||
</StepButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
TargetSelector.propTypes = {
|
|
||||||
targets: propTypes.array,
|
|
||||||
disabled: propTypes.bool,
|
|
||||||
openDriveSelector: propTypes.func,
|
|
||||||
selection: propTypes.object,
|
|
||||||
reselectDrive: propTypes.func,
|
|
||||||
flashing: propTypes.bool,
|
|
||||||
constraints: propTypes.object,
|
|
||||||
show: propTypes.bool,
|
|
||||||
tooltip: propTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = TargetSelector
|
|
143
lib/gui/app/components/drive-selector/target-selector.tsx
Normal file
143
lib/gui/app/components/drive-selector/target-selector.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 balena.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Drive as DrivelistDrive } from 'drivelist';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Txt } from 'rendition';
|
||||||
|
import { default as styled } from 'styled-components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDriveImageCompatibilityStatuses,
|
||||||
|
Image,
|
||||||
|
} from '../../../../shared/drive-constraints';
|
||||||
|
import { bytesToClosestUnit } from '../../../../shared/units';
|
||||||
|
import { getSelectedDrives } from '../../models/selection-state';
|
||||||
|
import {
|
||||||
|
ChangeButton,
|
||||||
|
DetailsText,
|
||||||
|
StepButton,
|
||||||
|
StepNameButton,
|
||||||
|
} from '../../styled-components';
|
||||||
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
|
||||||
|
const TargetDetail = styled(props => <Txt.span {...props}></Txt.span>)`
|
||||||
|
float: ${({ float }) => float};
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface TargetSelectorProps {
|
||||||
|
targets: any[];
|
||||||
|
disabled: boolean;
|
||||||
|
openDriveSelector: () => any;
|
||||||
|
reselectDrive: () => any;
|
||||||
|
flashing: boolean;
|
||||||
|
show: boolean;
|
||||||
|
tooltip: string;
|
||||||
|
image: Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DriveCompatibilityWarning(props: {
|
||||||
|
drive: DrivelistDrive;
|
||||||
|
image: Image;
|
||||||
|
}) {
|
||||||
|
const compatibilityWarnings = getDriveImageCompatibilityStatuses(
|
||||||
|
props.drive,
|
||||||
|
props.image,
|
||||||
|
);
|
||||||
|
if (compatibilityWarnings.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const messages = _.map(compatibilityWarnings, 'message');
|
||||||
|
return (
|
||||||
|
<Txt.span
|
||||||
|
className="glyphicon glyphicon-exclamation-sign"
|
||||||
|
ml={2}
|
||||||
|
tooltip={messages.join(', ')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TargetSelector(props: TargetSelectorProps) {
|
||||||
|
const targets = getSelectedDrives();
|
||||||
|
|
||||||
|
if (targets.length === 1) {
|
||||||
|
const target = targets[0];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StepNameButton plain tooltip={props.tooltip}>
|
||||||
|
{middleEllipsis(target.description, 20)}
|
||||||
|
</StepNameButton>
|
||||||
|
{!props.flashing && (
|
||||||
|
<ChangeButton plain mb={14} onClick={props.reselectDrive}>
|
||||||
|
Change
|
||||||
|
</ChangeButton>
|
||||||
|
)}
|
||||||
|
<DetailsText>
|
||||||
|
<DriveCompatibilityWarning drive={target} image={props.image} />
|
||||||
|
{bytesToClosestUnit(target.size)}
|
||||||
|
</DetailsText>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targets.length > 1) {
|
||||||
|
const targetsTemplate = [];
|
||||||
|
for (const target of targets) {
|
||||||
|
targetsTemplate.push(
|
||||||
|
<DetailsText
|
||||||
|
key={target.device}
|
||||||
|
tooltip={`${target.description} ${
|
||||||
|
target.displayName
|
||||||
|
} ${bytesToClosestUnit(target.size)}`}
|
||||||
|
px={21}
|
||||||
|
>
|
||||||
|
<Txt.span>
|
||||||
|
<DriveCompatibilityWarning drive={target} image={props.image} />
|
||||||
|
<TargetDetail float="left">
|
||||||
|
{middleEllipsis(target.description, 14)}
|
||||||
|
</TargetDetail>
|
||||||
|
<TargetDetail float="right">
|
||||||
|
{bytesToClosestUnit(target.size)}
|
||||||
|
</TargetDetail>
|
||||||
|
</Txt.span>
|
||||||
|
</DetailsText>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StepNameButton plain tooltip={props.tooltip}>
|
||||||
|
{targets.length} Targets
|
||||||
|
</StepNameButton>
|
||||||
|
{!props.flashing && (
|
||||||
|
<ChangeButton plain onClick={props.reselectDrive} mb={14}>
|
||||||
|
Change
|
||||||
|
</ChangeButton>
|
||||||
|
)}
|
||||||
|
{targetsTemplate}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StepButton
|
||||||
|
tabindex={targets.length > 0 ? -1 : 2}
|
||||||
|
disabled={props.disabled}
|
||||||
|
onClick={props.openDriveSelector}
|
||||||
|
>
|
||||||
|
Select target
|
||||||
|
</StepButton>
|
||||||
|
);
|
||||||
|
}
|
@ -17,11 +17,10 @@
|
|||||||
import * as _ from 'lodash';
|
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 * as driveConstraints from '../../../../shared/drive-constraints';
|
|
||||||
import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx';
|
import * as DriveSelectorModal from '../../components/drive-selector/DriveSelectorModal.jsx';
|
||||||
import * as TargetSelector from '../../components/drive-selector/target-selector.jsx';
|
import { TargetSelector } from '../../components/drive-selector/target-selector';
|
||||||
import { SVGIcon } from '../../components/svg-icon/svg-icon';
|
import { SVGIcon } from '../../components/svg-icon/svg-icon';
|
||||||
import * as selectionState from '../../models/selection-state';
|
import { getImage, getSelectedDrives } from '../../models/selection-state';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
import { observe, store } from '../../models/store';
|
import { observe, store } from '../../models/store';
|
||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
@ -46,7 +45,7 @@ const StepBorder = styled.div<{
|
|||||||
|
|
||||||
const getDriveListLabel = () => {
|
const getDriveListLabel = () => {
|
||||||
return _.join(
|
return _.join(
|
||||||
_.map(selectionState.getSelectedDrives(), (drive: any) => {
|
_.map(getSelectedDrives(), (drive: any) => {
|
||||||
return `${drive.description} (${drive.displayName})`;
|
return `${drive.description} (${drive.displayName})`;
|
||||||
}),
|
}),
|
||||||
'\n',
|
'\n',
|
||||||
@ -60,7 +59,8 @@ const shouldShowDrivesButton = () => {
|
|||||||
const getDriveSelectionStateSlice = () => ({
|
const getDriveSelectionStateSlice = () => ({
|
||||||
showDrivesButton: shouldShowDrivesButton(),
|
showDrivesButton: shouldShowDrivesButton(),
|
||||||
driveListLabel: getDriveListLabel(),
|
driveListLabel: getDriveListLabel(),
|
||||||
targets: selectionState.getSelectedDrives(),
|
targets: getSelectedDrives(),
|
||||||
|
image: getImage(),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface DriveSelectorProps {
|
interface DriveSelectorProps {
|
||||||
@ -80,7 +80,7 @@ export const DriveSelector = ({
|
|||||||
}: DriveSelectorProps) => {
|
}: DriveSelectorProps) => {
|
||||||
// TODO: inject these from redux-connector
|
// TODO: inject these from redux-connector
|
||||||
const [
|
const [
|
||||||
{ showDrivesButton, driveListLabel, targets },
|
{ showDrivesButton, driveListLabel, targets, image },
|
||||||
setStateSlice,
|
setStateSlice,
|
||||||
] = React.useState(getDriveSelectionStateSlice());
|
] = React.useState(getDriveSelectionStateSlice());
|
||||||
const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState(
|
const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState(
|
||||||
@ -113,7 +113,6 @@ export const DriveSelector = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
show={!hasDrive && showDrivesButton}
|
show={!hasDrive && showDrivesButton}
|
||||||
tooltip={driveListLabel}
|
tooltip={driveListLabel}
|
||||||
selection={selectionState}
|
|
||||||
openDriveSelector={() => {
|
openDriveSelector={() => {
|
||||||
setShowDriveSelectorModal(true);
|
setShowDriveSelectorModal(true);
|
||||||
}}
|
}}
|
||||||
@ -127,8 +126,8 @@ export const DriveSelector = ({
|
|||||||
setShowDriveSelectorModal(true);
|
setShowDriveSelectorModal(true);
|
||||||
}}
|
}}
|
||||||
flashing={flashing}
|
flashing={flashing}
|
||||||
constraints={driveConstraints}
|
|
||||||
targets={targets}
|
targets={targets}
|
||||||
|
image={image}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -43,6 +43,14 @@ export function isSystemDrive(drive: DrivelistDrive): boolean {
|
|||||||
return Boolean(_.get(drive, ['isSystem'], false));
|
return Boolean(_.get(drive, ['isSystem'], false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Image {
|
||||||
|
path: string;
|
||||||
|
isSizeEstimated?: boolean;
|
||||||
|
compressedSize?: number;
|
||||||
|
recommendedDriveSize?: number;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Check if a drive is source drive
|
* @summary Check if a drive is source drive
|
||||||
*
|
*
|
||||||
@ -50,10 +58,7 @@ export function isSystemDrive(drive: DrivelistDrive): boolean {
|
|||||||
* In the context of Etcher, a source drive is a drive
|
* In the context of Etcher, a source drive is a drive
|
||||||
* containing the image.
|
* containing the image.
|
||||||
*/
|
*/
|
||||||
export function isSourceDrive(
|
export function isSourceDrive(drive: DrivelistDrive, image: Image): boolean {
|
||||||
drive: DrivelistDrive,
|
|
||||||
image: { path: string },
|
|
||||||
): boolean {
|
|
||||||
const mountpoints = _.get(drive, ['mountpoints'], []);
|
const mountpoints = _.get(drive, ['mountpoints'], []);
|
||||||
const imagePath = _.get(image, ['path']);
|
const imagePath = _.get(image, ['path']);
|
||||||
|
|
||||||
@ -73,7 +78,7 @@ export function isSourceDrive(
|
|||||||
*/
|
*/
|
||||||
export function isDriveLargeEnough(
|
export function isDriveLargeEnough(
|
||||||
drive: DrivelistDrive | undefined,
|
drive: DrivelistDrive | undefined,
|
||||||
image: { compressedSize?: number; size?: number },
|
image: Image,
|
||||||
): boolean {
|
): boolean {
|
||||||
const driveSize = _.get(drive, 'size') || UNKNOWN_SIZE;
|
const driveSize = _.get(drive, 'size') || UNKNOWN_SIZE;
|
||||||
|
|
||||||
@ -106,10 +111,7 @@ export function isDriveDisabled(drive: DrivelistDrive): boolean {
|
|||||||
/**
|
/**
|
||||||
* @summary Check if a drive is valid, i.e. not locked and large enough for an image
|
* @summary Check if a drive is valid, i.e. not locked and large enough for an image
|
||||||
*/
|
*/
|
||||||
export function isDriveValid(
|
export function isDriveValid(drive: DrivelistDrive, image: Image): boolean {
|
||||||
drive: DrivelistDrive,
|
|
||||||
image: { compressedSize?: number; size?: number; path: string },
|
|
||||||
): boolean {
|
|
||||||
return (
|
return (
|
||||||
!isDriveLocked(drive) &&
|
!isDriveLocked(drive) &&
|
||||||
isDriveLargeEnough(drive, image) &&
|
isDriveLargeEnough(drive, image) &&
|
||||||
@ -126,7 +128,7 @@ export function isDriveValid(
|
|||||||
*/
|
*/
|
||||||
export function isDriveSizeRecommended(
|
export function isDriveSizeRecommended(
|
||||||
drive: DrivelistDrive | undefined,
|
drive: DrivelistDrive | undefined,
|
||||||
image: { recommendedDriveSize?: number },
|
image: Image,
|
||||||
): boolean {
|
): boolean {
|
||||||
const driveSize = _.get(drive, 'size') || UNKNOWN_SIZE;
|
const driveSize = _.get(drive, 'size') || UNKNOWN_SIZE;
|
||||||
return driveSize >= _.get(image, ['recommendedDriveSize'], UNKNOWN_SIZE);
|
return driveSize >= _.get(image, ['recommendedDriveSize'], UNKNOWN_SIZE);
|
||||||
@ -168,7 +170,7 @@ export const COMPATIBILITY_STATUS_TYPES = {
|
|||||||
*/
|
*/
|
||||||
export function getDriveImageCompatibilityStatuses(
|
export function getDriveImageCompatibilityStatuses(
|
||||||
drive: DrivelistDrive,
|
drive: DrivelistDrive,
|
||||||
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
|
image: Image,
|
||||||
) {
|
) {
|
||||||
const statusList = [];
|
const statusList = [];
|
||||||
|
|
||||||
@ -231,7 +233,7 @@ export function getDriveImageCompatibilityStatuses(
|
|||||||
*/
|
*/
|
||||||
export function getListDriveImageCompatibilityStatuses(
|
export function getListDriveImageCompatibilityStatuses(
|
||||||
drives: DrivelistDrive[],
|
drives: DrivelistDrive[],
|
||||||
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
|
image: Image,
|
||||||
) {
|
) {
|
||||||
return _.flatMap(drives, drive => {
|
return _.flatMap(drives, drive => {
|
||||||
return getDriveImageCompatibilityStatuses(drive, image);
|
return getDriveImageCompatibilityStatuses(drive, image);
|
||||||
@ -246,7 +248,7 @@ export function getListDriveImageCompatibilityStatuses(
|
|||||||
*/
|
*/
|
||||||
export function hasDriveImageCompatibilityStatus(
|
export function hasDriveImageCompatibilityStatus(
|
||||||
drive: DrivelistDrive,
|
drive: DrivelistDrive,
|
||||||
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
|
image: Image,
|
||||||
) {
|
) {
|
||||||
return Boolean(getDriveImageCompatibilityStatuses(drive, image).length);
|
return Boolean(getDriveImageCompatibilityStatuses(drive, image).length);
|
||||||
}
|
}
|
||||||
@ -270,7 +272,7 @@ export function hasDriveImageCompatibilityStatus(
|
|||||||
*/
|
*/
|
||||||
export function hasListDriveImageCompatibilityStatus(
|
export function hasListDriveImageCompatibilityStatus(
|
||||||
drives: DrivelistDrive[],
|
drives: DrivelistDrive[],
|
||||||
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
|
image: Image,
|
||||||
) {
|
) {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
exports.getListDriveImageCompatibilityStatuses(drives, image).length,
|
exports.getListDriveImageCompatibilityStatuses(drives, image).length,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user