mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-21 10:16:32 +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 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>
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user