Convert target-selector.jsx to typescript

Also fix showing the drive compatibility warnings

Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-01-15 13:22:36 +01:00
parent 950b764ff1
commit 90921a74ea
4 changed files with 166 additions and 186 deletions

View File

@ -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

View 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>
);
}

View File

@ -17,11 +17,10 @@
import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import * as driveConstraints from '../../../../shared/drive-constraints';
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 * as selectionState from '../../models/selection-state';
import { getImage, getSelectedDrives } from '../../models/selection-state';
import * as settings from '../../models/settings';
import { observe, store } from '../../models/store';
import * as analytics from '../../modules/analytics';
@ -46,7 +45,7 @@ const StepBorder = styled.div<{
const getDriveListLabel = () => {
return _.join(
_.map(selectionState.getSelectedDrives(), (drive: any) => {
_.map(getSelectedDrives(), (drive: any) => {
return `${drive.description} (${drive.displayName})`;
}),
'\n',
@ -60,7 +59,8 @@ const shouldShowDrivesButton = () => {
const getDriveSelectionStateSlice = () => ({
showDrivesButton: shouldShowDrivesButton(),
driveListLabel: getDriveListLabel(),
targets: selectionState.getSelectedDrives(),
targets: getSelectedDrives(),
image: getImage(),
});
interface DriveSelectorProps {
@ -80,7 +80,7 @@ export const DriveSelector = ({
}: DriveSelectorProps) => {
// TODO: inject these from redux-connector
const [
{ showDrivesButton, driveListLabel, targets },
{ showDrivesButton, driveListLabel, targets, image },
setStateSlice,
] = React.useState(getDriveSelectionStateSlice());
const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState(
@ -113,7 +113,6 @@ export const DriveSelector = ({
disabled={disabled}
show={!hasDrive && showDrivesButton}
tooltip={driveListLabel}
selection={selectionState}
openDriveSelector={() => {
setShowDriveSelectorModal(true);
}}
@ -127,8 +126,8 @@ export const DriveSelector = ({
setShowDriveSelectorModal(true);
}}
flashing={flashing}
constraints={driveConstraints}
targets={targets}
image={image}
/>
</div>

View File

@ -43,6 +43,14 @@ export function isSystemDrive(drive: DrivelistDrive): boolean {
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
*
@ -50,10 +58,7 @@ export function isSystemDrive(drive: DrivelistDrive): boolean {
* In the context of Etcher, a source drive is a drive
* containing the image.
*/
export function isSourceDrive(
drive: DrivelistDrive,
image: { path: string },
): boolean {
export function isSourceDrive(drive: DrivelistDrive, image: Image): boolean {
const mountpoints = _.get(drive, ['mountpoints'], []);
const imagePath = _.get(image, ['path']);
@ -73,7 +78,7 @@ export function isSourceDrive(
*/
export function isDriveLargeEnough(
drive: DrivelistDrive | undefined,
image: { compressedSize?: number; size?: number },
image: Image,
): boolean {
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
*/
export function isDriveValid(
drive: DrivelistDrive,
image: { compressedSize?: number; size?: number; path: string },
): boolean {
export function isDriveValid(drive: DrivelistDrive, image: Image): boolean {
return (
!isDriveLocked(drive) &&
isDriveLargeEnough(drive, image) &&
@ -126,7 +128,7 @@ export function isDriveValid(
*/
export function isDriveSizeRecommended(
drive: DrivelistDrive | undefined,
image: { recommendedDriveSize?: number },
image: Image,
): boolean {
const driveSize = _.get(drive, 'size') || UNKNOWN_SIZE;
return driveSize >= _.get(image, ['recommendedDriveSize'], UNKNOWN_SIZE);
@ -168,7 +170,7 @@ export const COMPATIBILITY_STATUS_TYPES = {
*/
export function getDriveImageCompatibilityStatuses(
drive: DrivelistDrive,
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
image: Image,
) {
const statusList = [];
@ -231,7 +233,7 @@ export function getDriveImageCompatibilityStatuses(
*/
export function getListDriveImageCompatibilityStatuses(
drives: DrivelistDrive[],
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
image: Image,
) {
return _.flatMap(drives, drive => {
return getDriveImageCompatibilityStatuses(drive, image);
@ -246,7 +248,7 @@ export function getListDriveImageCompatibilityStatuses(
*/
export function hasDriveImageCompatibilityStatus(
drive: DrivelistDrive,
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
image: Image,
) {
return Boolean(getDriveImageCompatibilityStatuses(drive, image).length);
}
@ -270,7 +272,7 @@ export function hasDriveImageCompatibilityStatus(
*/
export function hasListDriveImageCompatibilityStatus(
drives: DrivelistDrive[],
image: { isSizeEstimated?: boolean; compressedSize?: number; size?: number },
image: Image,
) {
return Boolean(
exports.getListDriveImageCompatibilityStatuses(drives, image).length,