From f8cc7c36b4888babf65e65ba6f622e28306505aa Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Thu, 4 Jun 2020 14:21:39 +0200 Subject: [PATCH 01/20] Add warning color to Flash! button Change-type: patch Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../progress-button/progress-button.tsx | 4 +++- lib/gui/app/pages/main/Flash.tsx | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 541e98f3..97258d67 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -50,6 +50,7 @@ interface ProgressButtonProps { disabled: boolean; cancel: () => void; callback: () => void; + warning?: boolean; } const colors = { @@ -97,7 +98,8 @@ export class ProgressButton extends React.PureComponent { } return ( diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 42d7743e..a2fd2690 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -197,6 +197,13 @@ export class FlashStep extends React.PureComponent< } } + private hasListWarnings(drives: any[], image: any) { + if (drives.length === 0 || flashState.isFlashing()) { + return; + } + return constraints.hasListDriveImageCompatibilityStatus(drives, image); + } + private async tryFlash() { const devices = selection.getSelectedDevices(); const image = selection.getImage(); @@ -209,10 +216,7 @@ export class FlashStep extends React.PureComponent< if (drives.length === 0 || this.props.isFlashing) { return; } - const hasDangerStatus = constraints.hasListDriveImageCompatibilityStatus( - drives, - image, - ); + const hasDangerStatus = this.hasListWarnings(drives, image); if (hasDangerStatus) { this.setState({ warningMessages: getWarningMessages(drives, image) }); return; @@ -245,6 +249,10 @@ export class FlashStep extends React.PureComponent< position={this.props.position} disabled={this.props.shouldFlashStepBeDisabled} cancel={imageWriter.cancel} + warning={this.hasListWarnings( + selection.getSelectedDrives(), + selection.getImage(), + )} callback={() => { this.tryFlash(); }} From 71c7fbd3a28b84821f23d34e190d9b0365e96be2 Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Fri, 13 Mar 2020 10:28:56 +0100 Subject: [PATCH 02/20] Rework target selector modal Change-type: patch Changelog-entry: Rework target selector modal Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../drive-selector/DriveSelectorModal.tsx | 280 ------------- .../styles/_drive-selector.scss | 113 ------ lib/gui/app/components/settings/settings.tsx | 19 +- .../target-selector-button.tsx} | 0 .../target-selector/target-selector-modal.tsx | 375 ++++++++++++++++++ lib/gui/app/pages/main/DriveSelector.tsx | 43 +- lib/gui/app/pages/main/Flash.tsx | 30 +- lib/gui/app/scss/main.scss | 1 - lib/gui/app/scss/modules/_bootstrap.scss | 17 + lib/gui/app/styled-components.tsx | 96 ++--- lib/gui/app/theme.ts | 39 ++ 11 files changed, 541 insertions(+), 472 deletions(-) delete mode 100644 lib/gui/app/components/drive-selector/DriveSelectorModal.tsx delete mode 100644 lib/gui/app/components/drive-selector/styles/_drive-selector.scss rename lib/gui/app/components/{drive-selector/target-selector.tsx => target-selector/target-selector-button.tsx} (100%) create mode 100644 lib/gui/app/components/target-selector/target-selector-modal.tsx diff --git a/lib/gui/app/components/drive-selector/DriveSelectorModal.tsx b/lib/gui/app/components/drive-selector/DriveSelectorModal.tsx deleted file mode 100644 index 477fb439..00000000 --- a/lib/gui/app/components/drive-selector/DriveSelectorModal.tsx +++ /dev/null @@ -1,280 +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. - */ - -import { Drive as DrivelistDrive } from 'drivelist'; -import * as _ from 'lodash'; -import * as React from 'react'; -import { Modal } from 'rendition'; - -import { - COMPATIBILITY_STATUS_TYPES, - getDriveImageCompatibilityStatuses, - hasListDriveImageCompatibilityStatus, - isDriveValid, -} from '../../../../shared/drive-constraints'; -import { bytesToClosestUnit } from '../../../../shared/units'; -import { getDrives, hasAvailableDrives } from '../../models/available-drives'; -import * as selectionState from '../../models/selection-state'; -import { store } from '../../models/store'; -import * as analytics from '../../modules/analytics'; -import { open as openExternal } from '../../os/open-external/services/open-external'; - -import RaspberrypiSvg from '../../../assets/raspberrypi.svg'; - -/** - * @summary Determine if we can change a drive's selection state - */ -function shouldChangeDriveSelectionState(drive: DrivelistDrive) { - return isDriveValid(drive, selectionState.getImage()); -} - -/** - * @summary Toggle a drive selection - */ -function toggleDrive(drive: DrivelistDrive) { - const canChangeDriveSelectionState = shouldChangeDriveSelectionState(drive); - - if (canChangeDriveSelectionState) { - analytics.logEvent('Toggle drive', { - drive, - previouslySelected: selectionState.isDriveSelected(drive.device), - }); - - selectionState.toggleDrive(drive.device); - } -} - -/** - * @summary Get a drive's compatibility status object(s) - * - * @description - * Given a drive, return its compatibility status with the selected image, - * containing the status type (ERROR, WARNING), and accompanying - * status message. - */ -function getDriveStatuses( - drive: DrivelistDrive, -): Array<{ type: number; message: string }> { - return getDriveImageCompatibilityStatuses(drive, selectionState.getImage()); -} - -function keyboardToggleDrive( - drive: DrivelistDrive, - event: React.KeyboardEvent, -) { - const ENTER = 13; - const SPACE = 32; - if (_.includes([ENTER, SPACE], event.keyCode)) { - toggleDrive(drive); - } -} - -interface DriverlessDrive { - link: string; - linkTitle: string; - linkMessage: string; -} - -export function DriveSelectorModal({ close }: { close: () => void }) { - const defaultMissingDriversModalState: { drive?: DriverlessDrive } = {}; - const [missingDriversModal, setMissingDriversModal] = React.useState( - defaultMissingDriversModalState, - ); - const [drives, setDrives] = React.useState(getDrives()); - - React.useEffect(() => { - const unsubscribe = store.subscribe(() => { - setDrives(getDrives()); - }); - return unsubscribe; - }); - - /** - * @summary Prompt the user to install missing usbboot drivers - */ - function installMissingDrivers(drive: { - link: string; - linkTitle: string; - linkMessage: string; - }) { - if (drive.link) { - analytics.logEvent('Open driver link modal', { - url: drive.link, - }); - setMissingDriversModal({ drive }); - } - } - - /** - * @summary Select a drive and close the modal - */ - async function selectDriveAndClose(drive: DrivelistDrive) { - const canChangeDriveSelectionState = await shouldChangeDriveSelectionState( - drive, - ); - - if (canChangeDriveSelectionState) { - selectionState.selectDrive(drive.device); - - analytics.logEvent('Drive selected (double click)'); - - close(); - } - } - - const hasStatus = hasListDriveImageCompatibilityStatus( - selectionState.getSelectedDrives(), - selectionState.getImage(), - ); - - return ( - -
    - {_.map(drives, (drive, index) => { - return ( -
  • attribute but used by css rule) - disabled={!isDriveValid(drive, selectionState.getImage())} - onDoubleClick={() => selectDriveAndClose(drive)} - onClick={() => toggleDrive(drive)} - > - {drive.icon === 'raspberrypi' && ( - - )} -
    keyboardToggleDrive(drive, evt)} - > -
    - {drive.description} - {drive.size && ( - - {' '} - - {bytesToClosestUnit(drive.size)} - - )} -
    - {!drive.link && ( -

    {drive.displayName}

    - )} - {drive.link && ( -

    - {drive.displayName} -{' '} - - installMissingDrivers(drive)}> - {drive.linkCTA} - - -

    - )} - -
    - {_.map(getDriveStatuses(drive), (status, idx) => { - const className = { - [COMPATIBILITY_STATUS_TYPES.WARNING]: 'label-warning', - [COMPATIBILITY_STATUS_TYPES.ERROR]: 'label-danger', - }; - return ( - - {status.message} - - ); - })} -
    - {Boolean(drive.progress) && ( - - )} -
    - - {isDriveValid(drive, selectionState.getImage()) && ( - attribute but used by css rule) - disabled={!selectionState.isDriveSelected(drive.device)} - > - )} -
  • - ); - })} - {!hasAvailableDrives() && ( -
  • -
    - Connect a drive! -
    No removable drive detected.
    -
    -
  • - )} -
- - {missingDriversModal.drive !== undefined && ( - setMissingDriversModal({})} - done={() => { - try { - if (missingDriversModal.drive !== undefined) { - openExternal(missingDriversModal.drive.link); - } - } catch (error) { - analytics.logException(error); - } finally { - setMissingDriversModal({}); - } - }} - action={'Yes, continue'} - cancelButtonProps={{ - children: 'Cancel', - }} - children={ - missingDriversModal.drive.linkMessage || - `Etcher will open ${missingDriversModal.drive.link} in your browser` - } - > - )} -
- ); -} diff --git a/lib/gui/app/components/drive-selector/styles/_drive-selector.scss b/lib/gui/app/components/drive-selector/styles/_drive-selector.scss deleted file mode 100644 index 809f693f..00000000 --- a/lib/gui/app/components/drive-selector/styles/_drive-selector.scss +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2016 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. - */ - -.modal-drive-selector-modal .modal-content { - width: 315px; - height: 320px; -} - -.modal-drive-selector-modal .modal-body { - padding-top: 0; - padding-bottom: 0; -} - -.modal-drive-selector-modal .list-group-item[disabled] { - cursor: not-allowed; -} - -.modal-drive-selector-modal { - - .list-group-item-footer:has(span) { - margin-top: 8px; - } - - .list-group-item-heading, - .list-group-item-text { - word-break: break-all; - } - - .list-group { - margin-bottom: 0; - } - - .list-group-item { - display: flex; - align-items: center; - border-left: 0; - border-right: 0; - border-radius: 0; - border-color: darken($palette-theme-light-background, 7%); - padding: 12px 0; - - .list-group-item-section-expanded { - flex-grow: 1; - margin-left: 15px; - } - - .list-group-item-section + .list-group-item-section { - margin-left: 10px; - display: inline-block; - vertical-align: middle; - } - - > .tick { - font-size: 11px; - } - - &:first-child { - border-top: 0; - } - - &[disabled] .list-group-item-heading { - color: $palette-theme-light-soft-foreground; - } - - .drive-init-progress { - appearance: none; - width: 100%; - height: 2.5px; - border: none; - border-radius: 50% 50%; - } - - .drive-init-progress::-webkit-progress-bar { - background-color: $palette-theme-default-background; - border: none; - outline: none; - } - - .drive-init-progress::-webkit-progress-value { - border-bottom: 1px solid darken($palette-theme-primary-background, 15); - background-color: $palette-theme-primary-background; - } - - } - - .list-group-item-heading { - font-size: 13px; - } - - .list-group-item-text { - line-height: 1; - font-size: 11px; - color: $palette-theme-light-soft-foreground; - } - - .word-keep { - word-break: keep-all; - } -} - diff --git a/lib/gui/app/components/settings/settings.tsx b/lib/gui/app/components/settings/settings.tsx index 1811d0e5..9b1bf1a2 100644 --- a/lib/gui/app/components/settings/settings.tsx +++ b/lib/gui/app/components/settings/settings.tsx @@ -19,7 +19,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as _ from 'lodash'; import * as os from 'os'; import * as React from 'react'; -import { Badge, Checkbox, Modal } from 'rendition'; +import { Checkbox, Modal } from 'rendition'; import { version } from '../../../../../package.json'; import * as settings from '../../models/settings'; @@ -92,23 +92,6 @@ async function getSettingsList(): Promise { name: 'updatesEnabled', label: 'Auto-updates enabled', }, - { - name: 'unsafeMode', - label: ( - - Unsafe mode{' '} - - Dangerous - - - ), - options: { - description: `Are you sure you want to turn this on? - You will be able to overwrite your system drives if you're not careful.`, - confirmLabel: 'Enable unsafe mode', - }, - hide: await settings.get('disableUnsafeMode'), - }, ]; } diff --git a/lib/gui/app/components/drive-selector/target-selector.tsx b/lib/gui/app/components/target-selector/target-selector-button.tsx similarity index 100% rename from lib/gui/app/components/drive-selector/target-selector.tsx rename to lib/gui/app/components/target-selector/target-selector-button.tsx diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx new file mode 100644 index 00000000..7fbe24c1 --- /dev/null +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -0,0 +1,375 @@ +/* + * 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 { Badge, Table as BaseTable, Txt, Flex } from 'rendition'; +import styled from 'styled-components'; + +import { + COMPATIBILITY_STATUS_TYPES, + getDriveImageCompatibilityStatuses, + hasListDriveImageCompatibilityStatus, + isDriveValid, + hasDriveImageCompatibilityStatus, +} from '../../../../shared/drive-constraints'; +import { bytesToClosestUnit } from '../../../../shared/units'; +import { getDrives, hasAvailableDrives } from '../../models/available-drives'; +import { getImage, getSelectedDrives } from '../../models/selection-state'; +import { store } from '../../models/store'; +import * as analytics from '../../modules/analytics'; +import { open as openExternal } from '../../os/open-external/services/open-external'; +import { Modal } from '../../styled-components'; + +export interface DrivelistTarget extends DrivelistDrive { + displayName: string; + progress: number; + device: string; + link: string; + linkTitle: string; + linkMessage: string; + linkCTA: string; +} + +/** + * @summary Get a drive's compatibility status object(s) + * + * @description + * Given a drive, return its compatibility status with the selected image, + * containing the status type (ERROR, WARNING), and accompanying + * status message. + */ +function getDriveStatuses( + drive: DrivelistTarget, +): Array<{ type: number; message: string }> { + return getDriveImageCompatibilityStatuses(drive, getImage()); +} + +const TargetsTable = styled(({ refFn, ...props }) => { + return ref={refFn} {...props}>; +})` + [data-display='table-head'] + [data-display='table-row'] + > [data-display='table-cell']:first-child { + padding-left: 15px; + } + + [data-display='table-head'] + [data-display='table-row'] + > [data-display='table-cell'] { + padding: 6px 8px; + color: #2a506f; + } + + [data-display='table-body'] + > [data-display='table-row'] + > [data-display='table-cell']:first-child { + padding-left: 15px; + } + + [data-display='table-body'] + > [data-display='table-row'] + > [data-display='table-cell'] { + padding: 6px 8px; + color: #2a506f; + } +`; + +interface DriverlessDrive { + link: string; + linkTitle: string; + linkMessage: string; +} + +interface TargetStatus { + message: string; + type: number; +} + +function renderStatuses(statuses: TargetStatus[]) { + return _.map(statuses, (status) => { + const badgeShade = + status.type === COMPATIBILITY_STATUS_TYPES.WARNING ? 14 : 5; + return ( + + {status.message} + + ); + }); +} + +const InitProgress = styled( + ({ + value, + ...props + }: { + value: number; + props?: React.ProgressHTMLAttributes; + }) => { + return ; + }, +)` + /* Reset the default appearance */ + -webkit-appearance: none; + appearance: none; + + ::-webkit-progress-bar { + width: 130px; + height: 4px; + background-color: #dde1f0; + border-radius: 14px; + } + + ::-webkit-progress-value { + background-color: #1496e1; + border-radius: 14px; + } +`; + +function renderProgress(progress: number) { + if (Boolean(progress)) { + return ( + + Initializing device + + + ); + } + return; +} + +interface TableData extends DrivelistTarget { + disabled: boolean; +} + +export const TargetSelectorModal = styled( + ({ + 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 [selected, setSelected] = React.useState(getSelectedDrives()); + const image = getImage(); + + const hasStatus = hasListDriveImageCompatibilityStatus(selected, image); + + const tableData = _.map(drives, (drive) => { + return { + ...drive, + extra: drive.progress || getDriveStatuses(drive), + disabled: !isDriveValid(drive, image) || drive.progress, + highlighted: hasDriveImageCompatibilityStatus(drive, image), + }; + }); + const disabledRows = _.map( + _.filter(drives, (drive) => { + return !isDriveValid(drive, image) || drive.progress; + }), + 'displayName', + ); + + const columns = [ + { + field: 'description', + label: 'Name', + }, + { + field: 'size', + label: 'Size', + render: (size: number) => { + return bytesToClosestUnit(size); + }, + }, + { + field: 'link', + label: 'Location', + render: (link: string, drive: DrivelistTarget) => { + return !link ? ( + {drive.displayName} + ) : ( + + {drive.displayName} -{' '} + + installMissingDrivers(drive)}> + {drive.linkCTA} + + + + ); + }, + }, + { + 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; + linkTitle: string; + linkMessage: string; + }) { + if (drive.link) { + analytics.logEvent('Open driver link modal', { + url: drive.link, + applicationSessionUuid: store.getState().toJS() + .applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, + }); + setMissingDriversModal({ drive }); + } + } + + return ( + + Select target + + } + titleDetails={{getDrives().length} found} + cancel={cancel} + done={() => close(selected)} + action="Continue" + style={{ + width: '780px', + height: '420px', + }} + primaryButtonProps={{ + primary: !hasStatus, + warning: hasStatus, + }} + > +
+ {!hasAvailableDrives() ? ( +
+ Plug a target drive +
+ ) : ( + ) => { + if (!_.isNull(t)) { + t.setRowSelection(selected); + } + }} + columns={columns} + data={tableData} + disabledRows={disabledRows} + rowKey="displayName" + onCheck={(rows: TableData[]) => { + setSelected(rows); + }} + onRowClick={(row: TableData) => { + if (!row.disabled) { + const selectedIndex = selected.findIndex( + (target) => target.device === row.device, + ); + if (selectedIndex === -1) { + selected.push(row); + setSelected(_.map(selected)); + return; + } + // Deselect if selected + setSelected( + _.reject( + selected, + (drive) => + selected[selectedIndex].device === drive.device, + ), + ); + } + }} + > + )} +
+ + {missingDriversModal.drive !== undefined && ( + setMissingDriversModal({})} + done={() => { + try { + if (missingDriversModal.drive !== undefined) { + openExternal(missingDriversModal.drive.link); + } + } catch (error) { + analytics.logException(error); + } finally { + setMissingDriversModal({}); + } + }} + action={'Yes, continue'} + cancelButtonProps={{ + children: 'Cancel', + }} + children={ + missingDriversModal.drive.linkMessage || + `Etcher will open ${missingDriversModal.drive.link} in your browser` + } + > + )} +
+ ); + }, +)` + > [data-display='table-head'] + > [data-display='table-row'] + > [data-display='table-cell']:first-child { + padding-left: 15px; + } + > [data-display='table-head'] + > [data-display='table-row'] + > [data-display='table-cell'] { + padding: 10px; + } + + > [data-display='table-body'] + > [data-display='table-row'] + > [data-display='table-cell']:first-child { + padding-left: 15px; + } + > [data-display='table-body'] + > [data-display='table-row'] + > [data-display='table-cell'] { + padding: 10px; + } +`; diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/pages/main/DriveSelector.tsx index 1cac4f82..2bf9bc00 100644 --- a/lib/gui/app/pages/main/DriveSelector.tsx +++ b/lib/gui/app/pages/main/DriveSelector.tsx @@ -17,9 +17,17 @@ import * as _ from 'lodash'; import * as React from 'react'; import styled from 'styled-components'; -import { DriveSelectorModal } from '../../components/drive-selector/DriveSelectorModal'; -import { TargetSelector } from '../../components/drive-selector/target-selector'; -import { getImage, getSelectedDrives } from '../../models/selection-state'; +import { TargetSelector } from '../../components/target-selector/target-selector-button'; +import { + DrivelistTarget, + TargetSelectorModal, +} from '../../components/target-selector/target-selector-modal'; +import { + getImage, + getSelectedDrives, + deselectDrive, + selectDrive, +} from '../../models/selection-state'; import * as settings from '../../models/settings'; import { observe } from '../../models/store'; import * as analytics from '../../modules/analytics'; @@ -84,7 +92,7 @@ export const DriveSelector = ({ { showDrivesButton, driveListLabel, targets, image }, setStateSlice, ] = React.useState(getDriveSelectionStateSlice()); - const [showDriveSelectorModal, setShowDriveSelectorModal] = React.useState( + const [showTargetSelectorModal, setShowTargetSelectorModal] = React.useState( false, ); @@ -115,11 +123,11 @@ export const DriveSelector = ({ show={!hasDrive && showDrivesButton} tooltip={driveListLabel} openDriveSelector={() => { - setShowDriveSelectorModal(true); + setShowTargetSelectorModal(true); }} reselectDrive={() => { analytics.logEvent('Reselect drive'); - setShowDriveSelectorModal(true); + setShowTargetSelectorModal(true); }} flashing={flashing} targets={targets} @@ -127,10 +135,25 @@ export const DriveSelector = ({ /> - {showDriveSelectorModal && ( - setShowDriveSelectorModal(false)} - > + {showTargetSelectorModal && ( + setShowTargetSelectorModal(false)} + close={(selectedTargets: DrivelistTarget[]) => { + const selectedDrives = getSelectedDrives(); + 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); + }} + > )} ); diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index a2fd2690..0c236491 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -21,9 +21,12 @@ import { Flex, Modal, Txt } from 'rendition'; import * as constraints from '../../../../shared/drive-constraints'; import * as messages from '../../../../shared/messages'; -import { DriveSelectorModal } from '../../components/drive-selector/DriveSelectorModal'; import { ProgressButton } from '../../components/progress-button/progress-button'; import { SourceOptions } from '../../components/source-selector/source-selector'; +import { + TargetSelectorModal, + DrivelistTarget, +} from '../../components/target-selector/target-selector-modal'; import * as availableDrives from '../../models/available-drives'; import * as flashState from '../../models/flash-state'; import * as selection from '../../models/selection-state'; @@ -325,11 +328,28 @@ export class FlashStep extends React.PureComponent< )} - {this.state.showDriveSelectorModal && ( - this.setState({ showDriveSelectorModal: false })} - /> + this.setState({ showDriveSelectorModal: false })} + close={(targets: DrivelistTarget[]) => { + const selectedDrives = selection.getSelectedDrives(); + 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 }); + }} + > )} ); diff --git a/lib/gui/app/scss/main.scss b/lib/gui/app/scss/main.scss index 972a34f6..b111f354 100644 --- a/lib/gui/app/scss/main.scss +++ b/lib/gui/app/scss/main.scss @@ -29,7 +29,6 @@ $disabled-opacity: 0.2; @import "./modules/space"; @import "./components/label"; @import "./components/tick"; -@import "../components/drive-selector/styles/drive-selector"; @import "../pages/main/styles/main"; @import "../pages/finish/styles/finish"; @import "./desktop"; diff --git a/lib/gui/app/scss/modules/_bootstrap.scss b/lib/gui/app/scss/modules/_bootstrap.scss index b49adddb..3ad54144 100644 --- a/lib/gui/app/scss/modules/_bootstrap.scss +++ b/lib/gui/app/scss/modules/_bootstrap.scss @@ -25,3 +25,20 @@ html { body { background-color: $palette-theme-dark-background; } + +// Fix slight checkbox vertical alignment issue +input[type="checkbox"] { + margin: 0; +} + +label { + margin: 0; +} + +[uib-tooltip] { + cursor: default; +} + +.tooltip { + word-wrap: break-word; +} diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index fd7ddd09..d7d91af0 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -15,56 +15,27 @@ */ import * as React from 'react'; -import { Button, ButtonProps, Provider, Txt } from 'rendition'; +import { + Button, + ButtonProps, + Flex, + Modal as ModalBase, + Provider, + Txt, +} from 'rendition'; import styled from 'styled-components'; import { space } from 'styled-system'; -import { colors } from './theme'; - -const font = 'SourceSansPro'; -const theme = { - font, - titleFont: font, - global: { - font: { - family: font, - }, - }, - colors, - button: { - border: { - width: '0', - radius: '24px', - }, - disabled: { - opacity: 1, - }, - extend: () => ` - && { - width: 200px; - height: 48px; - - &:disabled { - background-color: ${colors.dark.disabled.background}; - color: ${colors.dark.disabled.foreground}; - opacity: 1; - - &:hover { - background-color: ${colors.dark.disabled.background}; - color: ${colors.dark.disabled.foreground}; - } - } - } - `, - }, -}; +import { colors, theme } from './theme'; export const ThemedProvider = (props: any) => ( ); export const BaseButton = styled(Button)` + width: 200px; height: 48px; + font-size: 16px; `; export const IconButton = styled((props) => + ))` color: #ffffff; margin: auto; @@ -105,10 +76,9 @@ export const ChangeButton = styled(Button)` ${space} } `; -export const StepNameButton = styled(Button)` - border-radius: 24px; - margin: auto; - display: flex; + +export const StepNameButton = styled(BaseButton)` + display: inline-flex; justify-content: center; align-items: center; width: 100%; @@ -123,16 +93,52 @@ export const StepNameButton = styled(Button)` } } `; + +export const StepSelection = styled(Flex)` + flex-wrap: wrap; + justify-content: center; +`; + export const Footer = styled(Txt)` margin-top: 10px; color: ${colors.dark.disabled.foreground}; font-size: 10px; `; + export const Underline = styled(Txt.span)` border-bottom: 1px dotted; padding-bottom: 2px; `; + export const DetailsText = styled(Txt.p)` color: ${colors.dark.disabled.foreground}; margin-bottom: 0; `; + +export const Modal = styled((props) => { + return ( + + ); +})` + > div { + padding: 30px; + + > div:last-child { + height: 80px; + justify-content: center; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + box-shadow: 0 -2px 10px 0 rgba(221, 225, 240, 0.5), 0 -1px 0 0 #dde1f0; + } + } +`; diff --git a/lib/gui/app/theme.ts b/lib/gui/app/theme.ts index c12d22b4..a2184782 100644 --- a/lib/gui/app/theme.ts +++ b/lib/gui/app/theme.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +const font = 'SourceSansPro'; + export const colors = { dark: { foreground: '#fff', @@ -64,3 +66,40 @@ export const colors = { background: '#5fb835', }, }; + +export const theme = { + font, + titleFont: font, + global: { + font: { + family: font, + }, + }, + colors, + button: { + border: { + width: '0', + radius: '24px', + }, + disabled: { + opacity: 1, + }, + extend: () => ` + && { + width: 200px; + height: 48px; + + :disabled { + background-color: ${colors.dark.disabled.background}; + color: ${colors.dark.disabled.foreground}; + opacity: 1; + + :hover { + background-color: ${colors.dark.disabled.background}; + color: ${colors.dark.disabled.foreground}; + } + } + } + `, + }, +}; From b0c71b21b3a4e25bc062df60c6bba94ebd97170a Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Thu, 4 Jun 2020 19:16:01 +0200 Subject: [PATCH 03/20] Merge unsafe mode with new target selector Change-type: patch Changelog-entry: Merge unsafe mode with new target selector Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../target-selector/target-selector-modal.tsx | 417 ++++++++++-------- lib/gui/app/models/settings.ts | 1 - lib/gui/app/modules/drive-scanner.ts | 15 +- lib/gui/app/styled-components.tsx | 5 + lib/shared/drive-constraints.ts | 19 +- lib/shared/messages.ts | 6 +- npm-shrinkwrap.json | 2 +- tests/shared/drive-constraints.spec.ts | 16 +- 8 files changed, 258 insertions(+), 223 deletions(-) diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index 7fbe24c1..7573088e 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -14,26 +14,36 @@ * limitations under the License. */ +import { + faChevronDown, + faExclamationTriangle, +} from '@fortawesome/free-solid-svg-icons'; import { Drive as DrivelistDrive } from 'drivelist'; import * as _ from 'lodash'; import * as React from 'react'; -import { Badge, Table as BaseTable, Txt, Flex } from 'rendition'; +import { Badge, Table as BaseTable, Txt, Flex, Link } from 'rendition'; import styled from 'styled-components'; import { - COMPATIBILITY_STATUS_TYPES, getDriveImageCompatibilityStatuses, hasListDriveImageCompatibilityStatus, isDriveValid, hasDriveImageCompatibilityStatus, + TargetStatus, } from '../../../../shared/drive-constraints'; +import { compatibility } from '../../../../shared/messages'; import { bytesToClosestUnit } from '../../../../shared/units'; import { getDrives, hasAvailableDrives } from '../../models/available-drives'; -import { getImage, getSelectedDrives } from '../../models/selection-state'; +import { + getImage, + getSelectedDrives, + isDriveSelected, +} from '../../models/selection-state'; import { store } from '../../models/store'; import * as analytics from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { Modal } from '../../styled-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; export interface DrivelistTarget extends DrivelistDrive { displayName: string; @@ -53,9 +63,7 @@ export interface DrivelistTarget extends DrivelistDrive { * containing the status type (ERROR, WARNING), and accompanying * status message. */ -function getDriveStatuses( - drive: DrivelistTarget, -): Array<{ type: number; message: string }> { +function getDriveStatuses(drive: DrivelistTarget): TargetStatus[] { return getDriveImageCompatibilityStatuses(drive, getImage()); } @@ -95,15 +103,20 @@ interface DriverlessDrive { linkMessage: string; } -interface TargetStatus { - message: string; - type: number; +function badgeShadeFromStatus(status: string) { + switch (status) { + case compatibility.containsImage(): + return 16; + case compatibility.system(): + return 5; + default: + return 14; + } } function renderStatuses(statuses: TargetStatus[]) { return _.map(statuses, (status) => { - const badgeShade = - status.type === COMPATIBILITY_STATUS_TYPES.WARNING ? 14 : 5; + const badgeShade = badgeShadeFromStatus(status.message); return ( {status.message} @@ -124,7 +137,6 @@ const InitProgress = styled( }, )` /* Reset the default appearance */ - -webkit-appearance: none; appearance: none; ::-webkit-progress-bar { @@ -141,7 +153,7 @@ const InitProgress = styled( `; function renderProgress(progress: number) { - if (Boolean(progress)) { + if (progress) { return ( Initializing device @@ -149,149 +161,182 @@ function renderProgress(progress: number) { ); } - return; } interface TableData extends DrivelistTarget { disabled: boolean; } -export const TargetSelectorModal = styled( - ({ - 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 [selected, setSelected] = React.useState(getSelectedDrives()); - const image = getImage(); +export const TargetSelectorModal = ({ + 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 hasStatus = hasListDriveImageCompatibilityStatus(selected, 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 tableData = _.map(drives, (drive) => { - return { - ...drive, - extra: drive.progress || getDriveStatuses(drive), - disabled: !isDriveValid(drive, image) || drive.progress, - highlighted: hasDriveImageCompatibilityStatus(drive, image), - }; + const columns = [ + { + field: 'description', + label: 'Name', + render: (description: string, drive: DrivelistTarget) => { + return drive.isSystem ? ( + + + {description} + + ) : ( + {description} + ); + }, + }, + { + field: 'size', + label: 'Size', + render: (size: number) => { + return bytesToClosestUnit(size); + }, + }, + { + field: 'link', + label: 'Location', + render: (link: string, drive: DrivelistTarget) => { + return !link ? ( + {drive.displayName} + ) : ( + + {drive.displayName} -{' '} + + installMissingDrivers(drive)}> + {drive.linkCTA} + + + + ); + }, + }, + { + 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()); }); - const disabledRows = _.map( - _.filter(drives, (drive) => { - return !isDriveValid(drive, image) || drive.progress; - }), - 'displayName', - ); + return unsubscribe; + }); - const columns = [ - { - field: 'description', - label: 'Name', - }, - { - field: 'size', - label: 'Size', - render: (size: number) => { - return bytesToClosestUnit(size); - }, - }, - { - field: 'link', - label: 'Location', - render: (link: string, drive: DrivelistTarget) => { - return !link ? ( - {drive.displayName} - ) : ( - - {drive.displayName} -{' '} - - installMissingDrivers(drive)}> - {drive.linkCTA} - - - - ); - }, - }, - { - 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()); + /** + * @summary Prompt the user to install missing usbboot drivers + */ + function installMissingDrivers(drive: { + link: string; + linkTitle: string; + linkMessage: string; + }) { + if (drive.link) { + analytics.logEvent('Open driver link modal', { + url: drive.link, + applicationSessionUuid: store.getState().toJS().applicationSessionUuid, + flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, }); - return unsubscribe; - }); - - /** - * @summary Prompt the user to install missing usbboot drivers - */ - function installMissingDrivers(drive: { - link: string; - linkTitle: string; - linkMessage: string; - }) { - if (drive.link) { - analytics.logEvent('Open driver link modal', { - url: drive.link, - applicationSessionUuid: store.getState().toJS() - .applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, - }); - setMissingDriversModal({ drive }); - } + setMissingDriversModal({ drive }); } + } - return ( - Select target - } - titleDetails={{getDrives().length} found} - cancel={cancel} - done={() => close(selected)} - action="Continue" - style={{ - width: '780px', - height: '420px', - }} - primaryButtonProps={{ - primary: !hasStatus, - warning: hasStatus, - }} - > -
- {!hasAvailableDrives() ? ( -
- Plug a target drive -
- ) : ( + + {drives.length} found + + + } + titleDetails={{getDrives().length} found} + cancel={cancel} + done={() => close(selectedList)} + action="Continue" + style={{ + width: '780px', + height: '420px', + }} + primaryButtonProps={{ + primary: !hasStatus, + warning: hasStatus, + }} + > +
+ {!hasAvailableDrives() ? ( +
+ Plug a target drive +
+ ) : ( + ) => { if (!_.isNull(t)) { - t.setRowSelection(selected); + t.setRowSelection(selectedList); } }} columns={columns} - data={tableData} + data={_.uniq( + showSystemDrives + ? normalDrives.concat(systemDrives) + : normalDrives, + )} disabledRows={disabledRows} rowKey="displayName" onCheck={(rows: TableData[]) => { @@ -299,77 +344,65 @@ export const TargetSelectorModal = styled( }} onRowClick={(row: TableData) => { if (!row.disabled) { - const selectedIndex = selected.findIndex( + const selectedIndex = selectedList.findIndex( (target) => target.device === row.device, ); if (selectedIndex === -1) { - selected.push(row); - setSelected(_.map(selected)); + selectedList.push(row); + setSelected(_.map(selectedList)); return; } // Deselect if selected setSelected( _.reject( - selected, + selectedList, (drive) => - selected[selectedIndex].device === drive.device, + selectedList[selectedIndex].device === drive.device, ), ); } }} > - )} -
- - {missingDriversModal.drive !== undefined && ( - setMissingDriversModal({})} - done={() => { - try { - if (missingDriversModal.drive !== undefined) { - openExternal(missingDriversModal.drive.link); - } - } catch (error) { - analytics.logException(error); - } finally { - setMissingDriversModal({}); - } - }} - action={'Yes, continue'} - cancelButtonProps={{ - children: 'Cancel', - }} - children={ - missingDriversModal.drive.linkMessage || - `Etcher will open ${missingDriversModal.drive.link} in your browser` - } - > + {!showSystemDrives && ( + setShowSystemDrives(true)}> + + + + Show {drives.length - normalDrives.length} hidden + + + + )} + )} - - ); - }, -)` - > [data-display='table-head'] - > [data-display='table-row'] - > [data-display='table-cell']:first-child { - padding-left: 15px; - } - > [data-display='table-head'] - > [data-display='table-row'] - > [data-display='table-cell'] { - padding: 10px; - } +
- > [data-display='table-body'] - > [data-display='table-row'] - > [data-display='table-cell']:first-child { - padding-left: 15px; - } - > [data-display='table-body'] - > [data-display='table-row'] - > [data-display='table-cell'] { - padding: 10px; - } -`; + {missingDriversModal.drive !== undefined && ( + setMissingDriversModal({})} + done={() => { + try { + if (missingDriversModal.drive !== undefined) { + openExternal(missingDriversModal.drive.link); + } + } catch (error) { + analytics.logException(error); + } finally { + setMissingDriversModal({}); + } + }} + action={'Yes, continue'} + cancelButtonProps={{ + children: 'Cancel', + }} + children={ + missingDriversModal.drive.linkMessage || + `Etcher will open ${missingDriversModal.drive.link} in your browser` + } + > + )} +
+ ); +}; diff --git a/lib/gui/app/models/settings.ts b/lib/gui/app/models/settings.ts index a4b00eee..0d433dae 100644 --- a/lib/gui/app/models/settings.ts +++ b/lib/gui/app/models/settings.ts @@ -73,7 +73,6 @@ export async function writeConfigFile( } const DEFAULT_SETTINGS: _.Dictionary = { - unsafeMode: false, errorReporting: true, unmountOnSuccess: true, validateWriteOnSuccess: true, diff --git a/lib/gui/app/modules/drive-scanner.ts b/lib/gui/app/modules/drive-scanner.ts index bc550433..a76c2939 100644 --- a/lib/gui/app/modules/drive-scanner.ts +++ b/lib/gui/app/modules/drive-scanner.ts @@ -17,19 +17,10 @@ import * as sdk from 'etcher-sdk'; import { geteuid, platform } from 'process'; -import * as settings from '../models/settings'; - -/** - * @summary returns true if system drives should be shown - */ -function includeSystemDrives() { - return ( - settings.getSync('unsafeMode') && !settings.getSync('disableUnsafeMode') - ); -} - const adapters: sdk.scanner.adapters.Adapter[] = [ - new sdk.scanner.adapters.BlockDeviceAdapter({ includeSystemDrives }), + new sdk.scanner.adapters.BlockDeviceAdapter({ + includeSystemDrives: () => true, + }), ]; // Can't use permissions.isElevated() here as it returns a promise and we need to set diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index d7d91af0..50b4361d 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -131,8 +131,13 @@ export const Modal = styled((props) => { > div { padding: 30px; + > h3 { + margin: 0; + } + > div:last-child { height: 80px; + background-color: #fff; justify-content: center; position: absolute; bottom: 0; diff --git a/lib/shared/drive-constraints.ts b/lib/shared/drive-constraints.ts index b534933f..f68d27ed 100644 --- a/lib/shared/drive-constraints.ts +++ b/lib/shared/drive-constraints.ts @@ -172,12 +172,7 @@ export function getDriveImageCompatibilityStatuses( const statusList = []; // Mind the order of the if-statements if you modify. - if (isSourceDrive(drive, image)) { - statusList.push({ - type: COMPATIBILITY_STATUS_TYPES.ERROR, - message: messages.compatibility.containsImage(), - }); - } else if (isDriveLocked(drive)) { + if (isDriveLocked(drive)) { statusList.push({ type: COMPATIBILITY_STATUS_TYPES.ERROR, message: messages.compatibility.locked(), @@ -196,6 +191,13 @@ export function getDriveImageCompatibilityStatuses( message: messages.compatibility.tooSmall(prettyBytes(relativeBytes)), }); } else { + if (isSourceDrive(drive, image)) { + statusList.push({ + type: COMPATIBILITY_STATUS_TYPES.ERROR, + message: messages.compatibility.containsImage(), + }); + } + if (isSystemDrive(drive)) { statusList.push({ type: COMPATIBILITY_STATUS_TYPES.WARNING, @@ -273,3 +275,8 @@ export function hasListDriveImageCompatibilityStatus( ) { return Boolean(getListDriveImageCompatibilityStatuses(drives, image).length); } + +export interface TargetStatus { + message: string; + type: number; +} diff --git a/lib/shared/messages.ts b/lib/shared/messages.ts index 3e23d34d..ff32c937 100644 --- a/lib/shared/messages.ts +++ b/lib/shared/messages.ts @@ -65,16 +65,16 @@ export const compatibility = { }, system: () => { - return 'System Drive'; + return 'System drive'; }, containsImage: () => { - return 'Drive Mountpoint Contains Image'; + return 'Source drive'; }, // The drive is large and therefore likely not a medium you want to write to. largeDrive: () => { - return 'Large Drive'; + return 'Large drive'; }, } as const; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 836b7154..af46b9b6 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -18263,4 +18263,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/shared/drive-constraints.spec.ts b/tests/shared/drive-constraints.spec.ts index e22ca18f..e5ecf1de 100644 --- a/tests/shared/drive-constraints.spec.ts +++ b/tests/shared/drive-constraints.spec.ts @@ -1126,7 +1126,7 @@ describe('Shared: DriveConstraints', function () { }); describe('given the drive contains the image and the drive is locked', () => { - it('should return the contains-image drive error by precedence', function () { + it('should return the locked error by precedence', function () { this.drive.isReadOnly = true; this.image.path = path.join(this.mountpoint, 'rpi.img'); @@ -1135,7 +1135,7 @@ describe('Shared: DriveConstraints', function () { this.image, ); // @ts-ignore - const expectedTuples = [['ERROR', 'containsImage']]; + const expectedTuples = [['ERROR', 'locked']]; // @ts-ignore expectStatusTypesAndMessagesToBe(result, expectedTuples); @@ -1303,7 +1303,7 @@ describe('Shared: DriveConstraints', function () { ), ).to.deep.equal([ { - message: 'Drive Mountpoint Contains Image', + message: 'Source drive', type: 2, }, ]); @@ -1345,7 +1345,7 @@ describe('Shared: DriveConstraints', function () { ), ).to.deep.equal([ { - message: 'System Drive', + message: 'System drive', type: 1, }, ]); @@ -1359,7 +1359,7 @@ describe('Shared: DriveConstraints', function () { ), ).to.deep.equal([ { - message: 'Large Drive', + message: 'Large drive', type: 1, }, ]); @@ -1386,7 +1386,7 @@ describe('Shared: DriveConstraints', function () { constraints.getListDriveImageCompatibilityStatuses(drives, image), ).to.deep.equal([ { - message: 'Drive Mountpoint Contains Image', + message: 'Source drive', type: 2, }, { @@ -1398,11 +1398,11 @@ describe('Shared: DriveConstraints', function () { type: 2, }, { - message: 'System Drive', + message: 'System drive', type: 1, }, { - message: 'Large Drive', + message: 'Large drive', type: 1, }, { From af9d3ba9f120a6768535ba4f2f6f6e18f87c9679 Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Tue, 9 Jun 2020 17:04:51 +0200 Subject: [PATCH 04/20] Update rendition to v15.0.0 Change-type: patch Changelog-entry: Update rendition to v15.0.0 Signed-off-by: Lorenzo Alberto Maria Ambrosi --- npm-shrinkwrap.json | 131 ++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index af46b9b6..c09f5937 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2592,9 +2592,9 @@ } }, "@babel/runtime-corejs2": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.0.tgz", - "integrity": "sha512-sH02MTpZ4/f9glo0UNGXWQhl23e9gEB2MzjAqHZnKrztmY6eGxxPFJOniEHDOQisJwWZJlPAcBywi45lYc4h2w==", + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.2.tgz", + "integrity": "sha512-ZLwsFnNm3WpIARU1aLFtufjMHsmEnc8TjtrfAjmbgMbeoyR+LuQoyESoNdTfeDhL6IdY12SpeycXMgSgl8XGXA==", "dev": true, "requires": { "core-js": "^2.6.5", @@ -3545,6 +3545,15 @@ "@types/react": "*" } }, + "@types/react-helmet": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.0.0.tgz", + "integrity": "sha512-NBMPAxgjpaMooXa51cU1BTgrX6T+hQbMiLm77JhBbfOzPQea3RB5rNpPOD5xGWHIVpGXHd59cltEzIq0qglGcQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-jsonschema-form": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@types/react-jsonschema-form/-/react-jsonschema-form-1.7.3.tgz", @@ -3556,9 +3565,9 @@ } }, "@types/react-native": { - "version": "0.62.12", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.62.12.tgz", - "integrity": "sha512-EuM2QOx0LGwY3mKQ313+QcTYOwJhw5eggmE42GO4ElIKIfNK+zxxM6Pe9dT1Eq8eCJXY0oG327L7gUBWniwNNA==", + "version": "0.62.13", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.62.13.tgz", + "integrity": "sha512-hs4/tSABhcJx+J8pZhVoXHrOQD89WFmbs8QiDLNSA9zNrD46pityAuBWuwk1aMjPk9I3vC5ewkJroVRHgRIfdg==", "dev": true, "requires": { "@types/react": "*" @@ -6582,9 +6591,9 @@ "dev": true }, "d3-fetch": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", - "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", "dev": true, "requires": { "d3-dsv": "1" @@ -7858,9 +7867,9 @@ } }, "entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", - "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", "dev": true }, "env-paths": { @@ -11177,18 +11186,6 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", - "dev": true - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -11201,30 +11198,12 @@ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "dev": true }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, "lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=", "dev": true }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -13556,9 +13535,9 @@ "dev": true }, "polished": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.3.tgz", - "integrity": "sha512-QJ0q0b6gX1+0OJtPMfgVJxV0vg5XTa4im+Rca989dAtmsd/fEky3X+0A+V+OUXq1nyiDGplJwqD853dTS0gkPg==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.4.tgz", + "integrity": "sha512-21moJXCm/7EvjeKQz5w89QDDKNPCoimc83CqwZZGJluFdMXsFlMQl9lPA/OMRkoceZ19kU0anKlMgZmY7LJSJw==", "dev": true, "requires": { "@babel/runtime": "^7.9.2" @@ -13973,16 +13952,34 @@ "scheduler": "^0.19.1" } }, + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==", + "dev": true + }, "react-google-recaptcha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz", - "integrity": "sha512-4Y8awVnarn7+gdVpu8uvSmRJzzlMMoXqdhLoyToTOfVK6oM+NaChNI8NShnu75Q2YGHLvR1IA1FWZesuYHwn5w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz", + "integrity": "sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==", "dev": true, "requires": { "prop-types": "^15.5.0", "react-async-script": "^1.1.1" } }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -14028,10 +14025,16 @@ "prop-types": "^15.6.2" } }, + "react-side-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", + "integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==", + "dev": true + }, "react-simplemde-editor": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-simplemde-editor/-/react-simplemde-editor-4.1.1.tgz", - "integrity": "sha512-Mbe7xHmD154aLL7xES3GnJ+JUep1BoeWA9oEiHy8Fz9upgRnvGLbkHX9elqlEyyZYX8+o+GrXUSvZFjJ7sz1/Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-simplemde-editor/-/react-simplemde-editor-4.1.2.tgz", + "integrity": "sha512-0eEPhrOTRNNCxnuJMmUuPm/DHIijsm9H2aGrY9+nmMksccNHi7mfBM9ajoIzo9XzWZrXSvjBu+svzRdt48MjLw==", "dev": true, "requires": { "@types/codemirror": "^0.0.88", @@ -14232,9 +14235,9 @@ "dev": true }, "rendition": { - "version": "14.13.0", - "resolved": "https://registry.npmjs.org/rendition/-/rendition-14.13.0.tgz", - "integrity": "sha512-MBKuUZnBsOkZApmVrDYycMWhD7KypJZqa6ZSD+y2uIoYEtFBSQ+kr4iJLh3lsgBuFTkouBrjYWJeN+X2FWb5bQ==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/rendition/-/rendition-15.0.0.tgz", + "integrity": "sha512-fVJLfuo4ontLeLJna+gOQDpMcMUtZE61YQSo4OoYxSgQc2Vv6nU8ePKdWhO8AS4vHXXWVJkckjE0TB9YrtGhJA==", "dev": true, "requires": { "@fortawesome/fontawesome-svg-core": "^1.2.25", @@ -14247,6 +14250,7 @@ "@types/marked": "^0.7.2", "@types/node": "^13.13.4", "@types/prop-types": "^15.7.0", + "@types/react-helmet": "^6.0.0", "@types/react-jsonschema-form": "^1.3.2", "@types/recompose": "^0.26.2", "@types/sanitize-html": "^1.18.3", @@ -14267,6 +14271,7 @@ "mermaid": "8.4.0", "prop-types": "^15.7.2", "react-google-recaptcha": "^2.0.0-rc.1", + "react-helmet": "^6.0.0", "react-jsonschema-form": "^1.3.0", "react-notifications-component": "^2.2.3", "react-simplemde-editor": "^4.1.1", @@ -14281,9 +14286,9 @@ }, "dependencies": { "@types/node": { - "version": "13.13.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", - "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==", + "version": "13.13.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.11.tgz", + "integrity": "sha512-FX7mIFKfnGCfq10DGWNhfCNxhACEeqH5uulT6wRRA1KEt7zgLe0HdrAd9/QQkObDqp2Z0KEV3OAmNgs0lTx5tQ==", "dev": true }, "styled-system": { @@ -14690,18 +14695,14 @@ } }, "sanitize-html": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.24.0.tgz", - "integrity": "sha512-TAIFx39V/y06jDd4YUz7ntCdMUXN5Z28pSG7sTP2BCLXwHA9+ermacDpQs35Evo4p6YSgmaPdSbGiX4Fgptuuw==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.26.0.tgz", + "integrity": "sha512-xriDBT2FbfN0ZKCcX6H6svkh1bZpO2e5ny05RQGZY6vFOMAU13La2L5YYf3XpcjXSksCYXzPj7YPvyGp5wbaUA==", "dev": true, "requires": { "chalk": "^2.4.1", "htmlparser2": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.2", + "lodash": "^4.17.15", "postcss": "^7.0.27", "srcset": "^2.0.1", "xtend": "^4.0.1" diff --git a/package.json b/package.json index a1b81893..cacf92c1 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react": "^16.8.5", "react-dom": "^16.8.5", "redux": "^4.0.5", - "rendition": "^14.13.0", + "rendition": "^15.0.0", "request": "^2.81.0", "resin-corvus": "^2.0.5", "roboto-fontface": "^0.10.0", From 7aec8a4ae23b9b2646e840dd6547f07fd92801e2 Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Tue, 9 Jun 2020 17:05:31 +0200 Subject: [PATCH 05/20] Refactor styles Change-type: patch Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../flash-results/flash-results.tsx | 26 ++++++---- lib/gui/app/scss/components/_label.scss | 35 -------------- lib/gui/app/scss/components/_tick.scss | 47 ------------------- lib/gui/app/scss/main.scss | 13 +++-- lib/gui/app/scss/modules/_bootstrap.scss | 44 ----------------- lib/gui/app/styled-components.tsx | 6 --- lib/gui/app/theme.ts | 9 ---- 7 files changed, 25 insertions(+), 155 deletions(-) delete mode 100644 lib/gui/app/scss/components/_label.scss delete mode 100644 lib/gui/app/scss/components/_tick.scss delete mode 100644 lib/gui/app/scss/modules/_bootstrap.scss diff --git a/lib/gui/app/components/flash-results/flash-results.tsx b/lib/gui/app/components/flash-results/flash-results.tsx index a171d6bf..4c7000a5 100644 --- a/lib/gui/app/components/flash-results/flash-results.tsx +++ b/lib/gui/app/components/flash-results/flash-results.tsx @@ -14,10 +14,12 @@ * limitations under the License. */ +import { faCheckCircle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as _ from 'lodash'; import outdent from 'outdent'; import * as React from 'react'; -import { Txt } from 'rendition'; +import { Txt, Flex } from 'rendition'; import styled from 'styled-components'; import { left, position, space, top } from 'styled-system'; @@ -57,14 +59,20 @@ export function FlashResults({ ); return (
-
- -

Flash Complete!

-
+ + + + Flash Complete! + +
{_.map(results.devices, (quantity, type) => { return quantity ? ( diff --git a/lib/gui/app/scss/components/_label.scss b/lib/gui/app/scss/components/_label.scss deleted file mode 100644 index 19f76a83..00000000 --- a/lib/gui/app/scss/components/_label.scss +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016 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. - */ - -.label { - font-size: 9px; - margin-right: 4.5px; -} - -.label-big { - font-size: 11px; - padding: 8px 25px; -} - -.label-inset { - background-color: darken($palette-theme-dark-background, 10%); - color: darken($palette-theme-dark-foreground, 43%); -} - -.label-danger { - background-color: $palette-theme-danger-background; - color: $palette-theme-danger-foreground; -} diff --git a/lib/gui/app/scss/components/_tick.scss b/lib/gui/app/scss/components/_tick.scss deleted file mode 100644 index 9a9519ec..00000000 --- a/lib/gui/app/scss/components/_tick.scss +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016 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. - */ - -.tick { - @extend .glyphicon; - - display: inline-block; - border-radius: 50%; - padding: 3px; - font-size: 18px; - border: 2px solid; - - &[disabled] { - color: $palette-theme-dark-soft-foreground; - border-color: $palette-theme-dark-soft-foreground; - background-color: transparent; - } -} - -.tick--success { - @extend .glyphicon-ok; - - color: $palette-theme-success-foreground; - background-color: $palette-theme-success-background; - border-color: $palette-theme-success-background; -} - -.tick--error { - @extend .glyphicon-remove; - - color: $palette-theme-danger-foreground; - background-color: $palette-theme-danger-background; - border-color: $palette-theme-danger-background; -} diff --git a/lib/gui/app/scss/main.scss b/lib/gui/app/scss/main.scss index b111f354..ee934aa3 100644 --- a/lib/gui/app/scss/main.scss +++ b/lib/gui/app/scss/main.scss @@ -25,16 +25,13 @@ $disabled-opacity: 0.2; @import "../../../../node_modules/flexboxgrid/dist/flexboxgrid.css"; @import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; @import "./modules/theme"; -@import "./modules/bootstrap"; @import "./modules/space"; -@import "./components/label"; -@import "./components/tick"; @import "../pages/main/styles/main"; @import "../pages/finish/styles/finish"; @import "./desktop"; @font-face { - font-family: "SourceSansPro"; + font-family: "Source Sans Pro"; src: url("./fonts/SourceSansPro-Regular.ttf") format("truetype"); font-weight: 500; font-style: normal; @@ -42,14 +39,20 @@ $disabled-opacity: 0.2; } @font-face { - font-family: "SourceSansPro"; + font-family: "Source Sans Pro"; src: url("./fonts/SourceSansPro-SemiBold.ttf") format("truetype"); font-weight: 600; font-style: normal; font-display: block; } +// Prevent white flash when running application +html { + background-color: $palette-theme-dark-background; +} + body { + background-color: $palette-theme-dark-background; letter-spacing: 0.1px; display: flex; flex-direction: column; diff --git a/lib/gui/app/scss/modules/_bootstrap.scss b/lib/gui/app/scss/modules/_bootstrap.scss deleted file mode 100644 index 3ad54144..00000000 --- a/lib/gui/app/scss/modules/_bootstrap.scss +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2016 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. - */ - -// This file is meant to hold Bootstrap modifications -// that don't qualify as separate UI components. - -// Prevent white flash when running application -html { - background-color: $palette-theme-dark-background; -} - -body { - background-color: $palette-theme-dark-background; -} - -// Fix slight checkbox vertical alignment issue -input[type="checkbox"] { - margin: 0; -} - -label { - margin: 0; -} - -[uib-tooltip] { - cursor: default; -} - -.tooltip { - word-wrap: break-word; -} diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index 50b4361d..be711f99 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -18,7 +18,6 @@ import * as React from 'react'; import { Button, ButtonProps, - Flex, Modal as ModalBase, Provider, Txt, @@ -94,11 +93,6 @@ export const StepNameButton = styled(BaseButton)` } `; -export const StepSelection = styled(Flex)` - flex-wrap: wrap; - justify-content: center; -`; - export const Footer = styled(Txt)` margin-top: 10px; color: ${colors.dark.disabled.foreground}; diff --git a/lib/gui/app/theme.ts b/lib/gui/app/theme.ts index a2184782..46e744f9 100644 --- a/lib/gui/app/theme.ts +++ b/lib/gui/app/theme.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -const font = 'SourceSansPro'; - export const colors = { dark: { foreground: '#fff', @@ -68,13 +66,6 @@ export const colors = { }; export const theme = { - font, - titleFont: font, - global: { - font: { - family: font, - }, - }, colors, button: { border: { From 2dc359b19c34019c1fdeac3bcbfab1a339975d79 Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Tue, 9 Jun 2020 21:31:06 +0200 Subject: [PATCH 06/20] Make TargetSelectorModal a React.Component Change-type: patch Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../progress-button/progress-button.tsx | 15 +- .../target-selector/target-selector-modal.tsx | 566 ++++++++++-------- lib/gui/app/pages/main/DriveSelector.tsx | 53 +- lib/gui/app/pages/main/Flash.tsx | 24 +- lib/gui/app/styled-components.tsx | 1 + 5 files changed, 374 insertions(+), 285 deletions(-) diff --git a/lib/gui/app/components/progress-button/progress-button.tsx b/lib/gui/app/components/progress-button/progress-button.tsx index 97258d67..62fcff92 100644 --- a/lib/gui/app/components/progress-button/progress-button.tsx +++ b/lib/gui/app/components/progress-button/progress-button.tsx @@ -35,6 +35,7 @@ const FlashProgressBar = styled(ProgressBar)` width: 220px; height: 12px; + margin-bottom: 6px; border-radius: 14px; font-size: 16px; line-height: 48px; @@ -81,8 +82,16 @@ export class ProgressButton extends React.PureComponent { }); if (this.props.active) { return ( -
- + <> + {status}  {position} @@ -93,7 +102,7 @@ export class ProgressButton extends React.PureComponent { background={colors[this.props.type]} value={this.props.percentage} /> -
+ ); } return ( diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index 7573088e..dd533d8e 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -19,17 +19,24 @@ import { faExclamationTriangle, } from '@fortawesome/free-solid-svg-icons'; import { Drive as DrivelistDrive } from 'drivelist'; -import * as _ from 'lodash'; 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 { getDriveImageCompatibilityStatuses, hasListDriveImageCompatibilityStatus, isDriveValid, - hasDriveImageCompatibilityStatus, TargetStatus, + Image, } from '../../../../shared/drive-constraints'; import { compatibility } from '../../../../shared/messages'; import { bytesToClosestUnit } from '../../../../shared/units'; @@ -63,13 +70,32 @@ export interface DrivelistTarget extends DrivelistDrive { * containing the status type (ERROR, WARNING), and accompanying * status message. */ -function getDriveStatuses(drive: DrivelistTarget): TargetStatus[] { - return getDriveImageCompatibilityStatuses(drive, getImage()); +function getDriveStatuses( + drive: DrivelistTarget, + image: Image, +): TargetStatus[] { + return getDriveImageCompatibilityStatuses(drive, image); } +const ScrollableFlex = styled(Flex)` + overflow: auto; + + ::-webkit-scrollbar { + display: none; + } +`; + const TargetsTable = styled(({ refFn, ...props }) => { - return ref={refFn} {...props}>; + return ( +
+ ref={refFn} {...props} /> +
+ ); })` + > div { + overflow: visible; + } + [data-display='table-head'] [data-display='table-row'] > [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 ( - - {status.message} - - ); - }); -} - const InitProgress = styled( ({ value, @@ -152,8 +167,127 @@ const InitProgress = styled( } `; -function renderProgress(progress: number) { - if (progress) { +interface TableData extends DrivelistTarget { + disabled: boolean; + extra: TargetStatus[] | number; +} + +interface TargetSelectorModalProps extends Omit { + 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>; + + 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 ? ( + + + {description} + + ) : ( + {description} + ); + }, + }, + { + field: 'size', + label: 'Size', + render: bytesToClosestUnit, + }, + { + field: 'link', + label: 'Location', + render: (link: string, drive: DrivelistTarget) => { + return link ? ( + + {drive.displayName} -{' '} + + this.installMissingDrivers(drive)}> + {drive.linkCTA} + + + + ) : ( + {drive.displayName} + ); + }, + }, + { + 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 ( Initializing device @@ -161,116 +295,24 @@ function renderProgress(progress: number) { ); } -} -interface TableData extends DrivelistTarget { - disabled: boolean; -} + private renderStatuses(statuses: TargetStatus[]) { + return ( + // the column render fn expects a single Element + <> + {statuses.map((status) => { + const badgeShade = badgeShadeFromStatus(status.message); + return ( + + {status.message} + + ); + })} + + ); + } -export const TargetSelectorModal = ({ - 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 ? ( - - - {description} - - ) : ( - {description} - ); - }, - }, - { - field: 'size', - label: 'Size', - render: (size: number) => { - return bytesToClosestUnit(size); - }, - }, - { - field: 'link', - label: 'Location', - render: (link: string, drive: DrivelistTarget) => { - return !link ? ( - {drive.displayName} - ) : ( - - {drive.displayName} -{' '} - - installMissingDrivers(drive)}> - {drive.linkCTA} - - - - ); - }, - }, - { - 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: { + private installMissingDrivers(drive: { link: string; linkTitle: string; linkMessage: string; @@ -278,131 +320,169 @@ export const TargetSelectorModal = ({ if (drive.link) { analytics.logEvent('Open driver link modal', { url: drive.link, - applicationSessionUuid: store.getState().toJS().applicationSessionUuid, - flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, }); - setMissingDriversModal({ drive }); + this.setState({ missingDriversModal: { drive } }); } } - return ( - - - Select target - - - {drives.length} found - - - } - titleDetails={{getDrives().length} found} - cancel={cancel} - done={() => close(selectedList)} - action="Continue" - style={{ - width: '780px', - height: '420px', - }} - primaryButtonProps={{ - primary: !hasStatus, - warning: hasStatus, - }} - > -
- {!hasAvailableDrives() ? ( -
- Plug a target drive -
- ) : ( - - ) => { - if (!_.isNull(t)) { - t.setRowSelection(selectedList); - } - }} - columns={columns} - data={_.uniq( - showSystemDrives - ? normalDrives.concat(systemDrives) - : normalDrives, - )} - disabledRows={disabledRows} - rowKey="displayName" - onCheck={(rows: TableData[]) => { - setSelected(rows); - }} - onRowClick={(row: TableData) => { - if (!row.disabled) { + componentDidMount() { + this.unsubscribe = store.subscribe(() => { + const drives = getDrives(); + const image = getImage(); + this.setState({ + drives, + image, + selectedList: getSelectedDrives(), + }); + }); + } + + componentWillUnmount() { + this.unsubscribe(); + } + + render() { + const { cancel, done, ...props } = this.props; + const { + selectedList, + showSystemDrives, + drives, + image, + missingDriversModal, + } = this.state; + + const targetsWithTableData = this.buildTableData(drives, image); + const displayedTargets = this.getDisplayedTargets(targetsWithTableData); + const disabledTargets = this.getDisabledTargets(drives, image); + const numberOfSystemDrives = drives.filter((drive) => drive.isSystem) + .length; + const numberOfDisplayedSystemDrives = displayedTargets.filter( + (drive) => drive.isSystem, + ).length; + const numberOfHiddenSystemDrives = + numberOfSystemDrives - numberOfDisplayedSystemDrives; + const hasStatus = hasListDriveImageCompatibilityStatus(selectedList, image); + + return ( + + + Select target + + + {drives.length} found + + + } + titleDetails={{getDrives().length} found} + cancel={cancel} + done={() => done(selectedList)} + action="Continue" + style={{ + width: '780px', + height: '420px', + }} + primaryButtonProps={{ + primary: !hasStatus, + warning: hasStatus, + disabled: !hasAvailableDrives(), + }} + {...props} + > + + {!hasAvailableDrives() ? ( + + Plug a target drive + + ) : ( + + ) => { + 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( (target) => target.device === row.device, ); if (selectedIndex === -1) { - selectedList.push(row); - setSelected(_.map(selectedList)); - return; + newList.push(row); + } else { + // Deselect if selected + newList.splice(selectedIndex, 1); } - // Deselect if selected - setSelected( - _.reject( - selectedList, - (drive) => - selectedList[selectedIndex].device === drive.device, - ), - ); - } - }} - > - {!showSystemDrives && ( - setShowSystemDrives(true)}> - - - - Show {drives.length - normalDrives.length} hidden - - - - )} - - )} -
+ this.setState({ + selectedList: newList, + }); + }} + /> + {!showSystemDrives && numberOfHiddenSystemDrives > 0 && ( + this.setState({ showSystemDrives: true })} + > + + + Show {numberOfHiddenSystemDrives} hidden + + + )} + + )} + - {missingDriversModal.drive !== undefined && ( - setMissingDriversModal({})} - done={() => { - try { - if (missingDriversModal.drive !== undefined) { - openExternal(missingDriversModal.drive.link); + {missingDriversModal.drive !== undefined && ( + this.setState({ missingDriversModal: {} })} + done={() => { + try { + if (missingDriversModal.drive !== undefined) { + openExternal(missingDriversModal.drive.link); + } + } catch (error) { + analytics.logException(error); + } finally { + this.setState({ missingDriversModal: {} }); } - } catch (error) { - analytics.logException(error); - } finally { - setMissingDriversModal({}); + }} + action="Yes, continue" + cancelButtonProps={{ + children: 'Cancel', + }} + children={ + missingDriversModal.drive.linkMessage || + `Etcher will open ${missingDriversModal.drive.link} in your browser` } - }} - action={'Yes, continue'} - cancelButtonProps={{ - children: 'Cancel', - }} - children={ - missingDriversModal.drive.linkMessage || - `Etcher will open ${missingDriversModal.drive.link} in your browser` - } - > - )} - - ); -}; + /> + )} +
+ ); + } +} diff --git a/lib/gui/app/pages/main/DriveSelector.tsx b/lib/gui/app/pages/main/DriveSelector.tsx index 2bf9bc00..198ae5e1 100644 --- a/lib/gui/app/pages/main/DriveSelector.tsx +++ b/lib/gui/app/pages/main/DriveSelector.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import * as _ from 'lodash'; import * as React from 'react'; import styled from 'styled-components'; import { TargetSelector } from '../../components/target-selector/target-selector-button'; @@ -23,6 +22,7 @@ import { TargetSelectorModal, } from '../../components/target-selector/target-selector-modal'; import { + isDriveSelected, getImage, getSelectedDrives, deselectDrive, @@ -53,12 +53,11 @@ const StepBorder = styled.div<{ `; const getDriveListLabel = () => { - return _.join( - _.map(getSelectedDrives(), (drive: any) => { + return getSelectedDrives() + .map((drive: any) => { return `${drive.description} (${drive.displayName})`; - }), - '\n', - ); + }) + .join('\n'); }; const shouldShowDrivesButton = () => { @@ -72,6 +71,33 @@ const getDriveSelectionStateSlice = () => ({ 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 { webviewShowing: boolean; disabled: boolean; @@ -138,19 +164,8 @@ export const DriveSelector = ({ {showTargetSelectorModal && ( setShowTargetSelectorModal(false)} - close={(selectedTargets: DrivelistTarget[]) => { - const selectedDrives = getSelectedDrives(); - 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); - } + done={(modalTargets) => { + selectAllTargets(modalTargets); setShowTargetSelectorModal(false); }} > diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx index 0c236491..4eeac6af 100644 --- a/lib/gui/app/pages/main/Flash.tsx +++ b/lib/gui/app/pages/main/Flash.tsx @@ -23,10 +23,7 @@ import * as constraints from '../../../../shared/drive-constraints'; import * as messages from '../../../../shared/messages'; import { ProgressButton } from '../../components/progress-button/progress-button'; import { SourceOptions } from '../../components/source-selector/source-selector'; -import { - TargetSelectorModal, - DrivelistTarget, -} from '../../components/target-selector/target-selector-modal'; +import { TargetSelectorModal } from '../../components/target-selector/target-selector-modal'; import * as availableDrives from '../../models/available-drives'; import * as flashState from '../../models/flash-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 * as imageWriter from '../../modules/image-writer'; import * as notification from '../../os/notification'; +import { selectAllTargets } from './DriveSelector'; import FlashSvg from '../../../assets/flash.svg'; @@ -331,22 +329,8 @@ export class FlashStep extends React.PureComponent< {this.state.showDriveSelectorModal && ( this.setState({ showDriveSelectorModal: false })} - close={(targets: DrivelistTarget[]) => { - const selectedDrives = selection.getSelectedDrives(); - 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); - } + done={(modalTargets) => { + selectAllTargets(modalTargets); this.setState({ showDriveSelectorModal: false }); }} > diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index be711f99..f2260398 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -124,6 +124,7 @@ export const Modal = styled((props) => { })` > div { padding: 30px; + height: calc(100% - 80px); > h3 { margin: 0; From e39fed1f258f53d19a7e03d44f65eedec1e5263a Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Fri, 12 Jun 2020 15:43:19 +0200 Subject: [PATCH 07/20] Fix source-selector image height Change-type: patch Changelog-entry: Fix source-selector image height Signed-off-by: Lorenzo Alberto Maria Ambrosi --- lib/gui/app/components/source-selector/source-selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index f9d89177..0ec6b82d 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -473,7 +473,7 @@ export class SourceSelector extends React.Component<
} + fallback={} />
From d63f5eca0d35164dab69ba1a4d57743442a08f09 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Wed, 17 Jun 2020 19:09:05 +0200 Subject: [PATCH 08/20] Update rendition to 15.2.1 Changelog-entry: Update rendition to 15.2.1 --- npm-shrinkwrap.json | 226 +++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 140 insertions(+), 88 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c09f5937..535bc439 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -218,12 +218,31 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", + "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "dev": true + }, + "@babel/types": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", + "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -794,12 +813,31 @@ } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz", + "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "dev": true + }, + "@babel/types": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", + "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-module-transforms": { @@ -2599,14 +2637,6 @@ "requires": { "core-js": "^2.6.5", "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - } } }, "@babel/template": { @@ -2842,12 +2872,20 @@ } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz", - "integrity": "sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.1.tgz", + "integrity": "sha512-sSeaqqmv2ovA5LKcrbh3VnEDZHVhaxijWKm4R0AdT0eG21pgxNsJbStD8lW9z6bgSuWXRNHhbhOmARuRCLS8tw==", "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.28" + "@fortawesome/fontawesome-common-types": "^0.2.29" + }, + "dependencies": { + "@fortawesome/fontawesome-common-types": { + "version": "0.2.29", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.29.tgz", + "integrity": "sha512-cY+QfDTbZ7XVxzx7jxbC98Oxr/zc7R2QpTLqTxqlfyXDrAJjzi/xUIqAUsygELB62JIrbsWxtSRhayKFkGI7MA==", + "dev": true + } } }, "@fortawesome/free-solid-svg-icons": { @@ -2894,6 +2932,45 @@ "fastq": "^1.6.0" } }, + "@react-google-maps/api": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-1.9.7.tgz", + "integrity": "sha512-o51/1RaN67R9I9atfwAbshh5xs1hxzvrTiysNWi8rnkUSF29Kip+1YMQ1wys+fWUmjAPCjfqzYvaJuBIkgu8mw==", + "dev": true, + "requires": { + "@react-google-maps/infobox": "1.9.6", + "@react-google-maps/marker-clusterer": "1.9.6", + "acorn": "7.3.1", + "acorn-jsx": "^5.2.0", + "invariant": "2.2.4" + }, + "dependencies": { + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + } + } + }, + "@react-google-maps/infobox": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-1.9.6.tgz", + "integrity": "sha512-fR+zAC673BijGd9IJuV8sD0zEy1cLddxgyrT/SHFlNRPsnyprbH5Ox4zmfNU6UTkrQ7ULedFK7hUrgLVYKpc+Q==", + "dev": true + }, + "@react-google-maps/marker-clusterer": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-1.9.6.tgz", + "integrity": "sha512-bliu3CfMWLxMJaW8UK7+6Q1iPQoXbP9BK14yvid2pZL+OgqKgmnY6O9pazIi5Ca9NVTviNcN8WCz1TicUiABQw==", + "dev": true + }, "@ronomon/direct-io": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@ronomon/direct-io/-/direct-io-3.0.1.tgz", @@ -3437,9 +3514,9 @@ } }, "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", "dev": true }, "@types/lodash": { @@ -3608,9 +3685,9 @@ } }, "@types/sanitize-html": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-1.23.0.tgz", - "integrity": "sha512-yl0HvhWOZWzmkGEBQLKYf+BtotvbGLFhCejuBvwyM1Q23RliLXNfviuOMLQ/nCRFWR2yI0LRRiaH/Oz7CIpCGw==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-1.23.2.tgz", + "integrity": "sha512-s00omSXGjemcNGXChE44grxYLPCkxdp/rdxGCb4FcGyH0aOjFOacrnP0P394Ktp+IKeBk1Q7VsGJ+cOa2GZ5hw==", "dev": true, "requires": { "htmlparser2": "^4.1.0" @@ -6111,9 +6188,9 @@ } }, "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true }, "core-js-compat": { @@ -8621,6 +8698,14 @@ "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true + } } }, "fd-slicer": { @@ -9493,43 +9578,17 @@ } }, "grommet": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/grommet/-/grommet-2.13.0.tgz", - "integrity": "sha512-C2asZEacDjr9stnVw2XoYVezPdbLM3qxQrxdLfnfdlj1pTb/JNfwqpQZatMW5G8NU3S596juVAnTPefmMXcwyQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/grommet/-/grommet-2.14.0.tgz", + "integrity": "sha512-G/LTkr+uFri4NUNQGJMx8TtWWe9+KSpIHCXC9WgRaICI73R3+BvhLSNSzWMQ6YIQC+7PJFtruebeWjdUqR3Ykw==", "dev": true, "requires": { - "css": "^2.2.3", "grommet-icons": "^4.2.0", "hoist-non-react-statics": "^3.2.0", - "markdown-to-jsx": "^6.9.1", + "markdown-to-jsx": "^6.11.4", "polished": "^3.4.1", "prop-types": "^15.7.2", - "react-desc": "^4.1.2", - "recompose": "^0.30.0" - }, - "dependencies": { - "recompose": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "change-emitter": "^0.1.2", - "fbjs": "^0.8.1", - "hoist-non-react-statics": "^2.3.1", - "react-lifecycles-compat": "^3.0.2", - "symbol-observable": "^1.0.4" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", - "dev": true - } - } - } + "react-desc": "^4.1.2" } }, "grommet-icons": { @@ -9716,9 +9775,9 @@ "dev": true }, "highlight.js": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.0.3.tgz", - "integrity": "sha512-9FG7SSzv9yOY5CGGxfI6NDm7xLYtMOjKtPBxw7Zff3t5UcRcUNTGEeS8lNjhceL34KeetLMoGMFTGoaa83HwyQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz", + "integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg==", "dev": true }, "hmac-drbg": { @@ -13535,9 +13594,9 @@ "dev": true }, "polished": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.4.tgz", - "integrity": "sha512-21moJXCm/7EvjeKQz5w89QDDKNPCoimc83CqwZZGJluFdMXsFlMQl9lPA/OMRkoceZ19kU0anKlMgZmY7LJSJw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.5.tgz", + "integrity": "sha512-VwhC9MlhW7O5dg/z7k32dabcAFW1VI2+7fSe8cE/kXcfL7mVdoa5UxciYGW2sJU78ldDLT6+ROEKIZKFNTnUXQ==", "dev": true, "requires": { "@babel/runtime": "^7.9.2" @@ -14000,14 +14059,6 @@ "react-is": "^16.8.4", "react-lifecycles-compat": "^3.0.4", "shortid": "^2.2.14" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - } } }, "react-lifecycles-compat": { @@ -14235,15 +14286,16 @@ "dev": true }, "rendition": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/rendition/-/rendition-15.0.0.tgz", - "integrity": "sha512-fVJLfuo4ontLeLJna+gOQDpMcMUtZE61YQSo4OoYxSgQc2Vv6nU8ePKdWhO8AS4vHXXWVJkckjE0TB9YrtGhJA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/rendition/-/rendition-15.2.1.tgz", + "integrity": "sha512-dwSiy43c3EEw+eZDz7qNNPQZ1zH7iTlAYd5AdHevmYwZdvnFNFQen0EdhAr9Z8o4ra6TYZpNJod4Vp9eZ1hufQ==", "dev": true, "requires": { "@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/free-regular-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/react-fontawesome": "^0.1.5", + "@react-google-maps/api": "^1.9.7", "@types/color": "^3.0.0", "@types/json-schema": "^7.0.3", "@types/lodash": "^4.14.77", @@ -14263,7 +14315,7 @@ "color": "^3.1.2", "color-hash": "^1.0.3", "copy-to-clipboard": "^3.0.8", - "grommet": "^2.13.0", + "grommet": "^2.14.0", "highlight.js": "^10.0.3", "jellyschema": "^0.11.9", "lodash": "^4.17.11", @@ -14286,9 +14338,9 @@ }, "dependencies": { "@types/node": { - "version": "13.13.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.11.tgz", - "integrity": "sha512-FX7mIFKfnGCfq10DGWNhfCNxhACEeqH5uulT6wRRA1KEt7zgLe0HdrAd9/QQkObDqp2Z0KEV3OAmNgs0lTx5tQ==", + "version": "13.13.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz", + "integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==", "dev": true }, "styled-system": { @@ -14695,9 +14747,9 @@ } }, "sanitize-html": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.26.0.tgz", - "integrity": "sha512-xriDBT2FbfN0ZKCcX6H6svkh1bZpO2e5ny05RQGZY6vFOMAU13La2L5YYf3XpcjXSksCYXzPj7YPvyGp5wbaUA==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.27.0.tgz", + "integrity": "sha512-U1btucGeYVpg0GoK43jPpe/bDCV4cBOGuxzv5NBd0bOjyZdMKY0n98S/vNlO1wVwre0VCj8H3hbzE7gD2+RjKA==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -15786,9 +15838,9 @@ "dev": true }, "styled-components": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.0.tgz", - "integrity": "sha512-0Qs2wEkFBXHFlysz6CV831VG6HedcrFUwChjnWylNivsx14MtmqQsohi21rMHZxzuTba063dEyoe/SR6VGJI7Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.1.tgz", + "integrity": "sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", diff --git a/package.json b/package.json index cacf92c1..43e42d81 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react": "^16.8.5", "react-dom": "^16.8.5", "redux": "^4.0.5", - "rendition": "^15.0.0", + "rendition": "^15.2.1", "request": "^2.81.0", "resin-corvus": "^2.0.5", "roboto-fontface": "^0.10.0", From 9444f0e1b121bf8ac65f6b77ca92be26b06a38e1 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Wed, 17 Jun 2020 20:15:13 +0200 Subject: [PATCH 09/20] Stricter types in target-selector-modal.tsx Change-type: patch --- .../target-selector/target-selector-modal.tsx | 208 +++++++++--------- lib/gui/app/pages/main/DriveSelector.tsx | 11 +- 2 files changed, 107 insertions(+), 112 deletions(-) diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index dd533d8e..03b025b1 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -18,7 +18,8 @@ import { faChevronDown, faExclamationTriangle, } 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 { Badge, @@ -47,34 +48,37 @@ import { isDriveSelected, } from '../../models/selection-state'; 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 { Modal } from '../../styled-components'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -export interface DrivelistTarget extends DrivelistDrive { - displayName: string; +interface UsbbootDrive extends sourceDestination.UsbbootDrive { progress: number; - device: string; +} + +interface DriverlessDrive { + displayName: string; // added in app.ts + description: string; link: string; linkTitle: string; linkMessage: string; linkCTA: string; } -/** - * @summary Get a drive's compatibility status object(s) - * - * @description - * Given a drive, return its compatibility status with the selected image, - * containing the status type (ERROR, WARNING), and accompanying - * status message. - */ -function getDriveStatuses( - drive: DrivelistTarget, - image: Image, -): TargetStatus[] { - return getDriveImageCompatibilityStatuses(drive, image); +type Target = scanner.adapters.DrivelistDrive | DriverlessDrive | UsbbootDrive; + +function isUsbbootDrive(drive: Target): drive is UsbbootDrive { + return (drive as UsbbootDrive).progress !== undefined; +} + +function isDriverlessDrive(drive: Target): drive is DriverlessDrive { + return (drive as DriverlessDrive).link !== undefined; +} + +function isDrivelistDrive( + drive: Target, +): drive is scanner.adapters.DrivelistDrive { + return typeof (drive as scanner.adapters.DrivelistDrive).size === 'number'; } const ScrollableFlex = styled(Flex)` @@ -88,14 +92,10 @@ const ScrollableFlex = styled(Flex)` const TargetsTable = styled(({ refFn, ...props }) => { return (
- ref={refFn} {...props} /> + ref={refFn} {...props} />
); })` - > div { - overflow: visible; - } - [data-display='table-head'] [data-display='table-row'] > [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) { switch (status) { case compatibility.containsImage(): @@ -148,7 +142,7 @@ const InitProgress = styled( value: number; props?: React.ProgressHTMLAttributes; }) => { - return ; + return ; }, )` /* Reset the default appearance */ @@ -167,20 +161,15 @@ const InitProgress = styled( } `; -interface TableData extends DrivelistTarget { - disabled: boolean; - extra: TargetStatus[] | number; -} - interface TargetSelectorModalProps extends Omit { - done: (targets: DrivelistTarget[]) => void; + done: (targets: scanner.adapters.DrivelistDrive[]) => void; } interface TargetSelectorModalState { - drives: any[]; + drives: Target[]; image: Image; missingDriversModal: { drive?: DriverlessDrive }; - selectedList: any[]; + selectedList: scanner.adapters.DrivelistDrive[]; showSystemDrives: boolean; } @@ -189,7 +178,7 @@ export class TargetSelectorModal extends React.Component< TargetSelectorModalState > { unsubscribe: () => void; - tableColumns: Array>; + tableColumns: Array>; constructor(props: TargetSelectorModalProps) { super(props); @@ -209,8 +198,8 @@ export class TargetSelectorModal extends React.Component< { field: 'description', label: 'Name', - render: (description: string, drive: DrivelistTarget) => { - return drive.isSystem ? ( + render: (description: string, drive: Target) => { + return isDrivelistDrive(drive) && drive.isSystem ? ( { + if (isDrivelistDrive(drive) && drive.size !== null) { + return bytesToClosestUnit(drive.size); + } + }, }, { - field: 'link', + field: 'description', + key: 'link', label: 'Location', - render: (link: string, drive: DrivelistTarget) => { - return link ? ( + render: (_description: string, drive: Target) => { + return ( - {drive.displayName} -{' '} - - this.installMissingDrivers(drive)}> - {drive.linkCTA} - - + {drive.displayName} + {isDriverlessDrive(drive) && ( + <> + {' '} + -{' '} + + this.installMissingDrivers(drive)}> + {drive.linkCTA} + + + + )} - ) : ( - {drive.displayName} ); }, }, { - field: 'extra', - label: ' ', - render: (extra: TargetStatus[] | number) => { - if (typeof extra === 'number') { - return this.renderProgress(extra); + field: 'description', + key: 'extra', + label: '', + render: (_description: string, drive: Target) => { + 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) { - return drives.map((drive) => { - return { - ...drive, - extra: - drive.progress !== undefined - ? drive.progress - : getDriveStatuses(drive, image), - disabled: !isDriveValid(drive, image) || drive.progress !== undefined, - }; + private driveShouldBeDisabled(drive: Target, image: any) { + return ( + isUsbbootDrive(drive) || + isDriverlessDrive(drive) || + !isDriveValid(drive, image) + ); + } + + 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[]) { - return enrichedDrivesData.filter((drive) => { - const showIfSystemDrive = this.state.showSystemDrives || !drive.isSystem; - return isDriveSelected(drive.device) || showIfSystemDrive; - }); - } - - private getDisabledTargets(drives: any[], image: any): TableData[] { + private getDisabledTargets(drives: Target[], image: any): string[] { return drives - .filter( - (drive) => !isDriveValid(drive, image) || drive.progress !== undefined, - ) + .filter((drive) => this.driveShouldBeDisabled(drive, image)) .map((drive) => drive.displayName); } @@ -312,13 +313,9 @@ export class TargetSelectorModal extends React.Component< ); } - private installMissingDrivers(drive: { - link: string; - linkTitle: string; - linkMessage: string; - }) { + private installMissingDrivers(drive: DriverlessDrive) { if (drive.link) { - analytics.logEvent('Open driver link modal', { + logEvent('Open driver link modal', { url: drive.link, }); this.setState({ missingDriversModal: { drive } }); @@ -343,21 +340,15 @@ export class TargetSelectorModal extends React.Component< render() { const { cancel, done, ...props } = this.props; - const { - selectedList, - showSystemDrives, - drives, - image, - missingDriversModal, - } = this.state; + const { selectedList, drives, image, missingDriversModal } = this.state; - const targetsWithTableData = this.buildTableData(drives, image); - const displayedTargets = this.getDisplayedTargets(targetsWithTableData); + const displayedTargets = this.getDisplayedTargets(drives); const disabledTargets = this.getDisabledTargets(drives, image); - const numberOfSystemDrives = drives.filter((drive) => drive.isSystem) - .length; + const numberOfSystemDrives = drives.filter( + (drive) => isDrivelistDrive(drive) && drive.isSystem, + ).length; const numberOfDisplayedSystemDrives = displayedTargets.filter( - (drive) => drive.isSystem, + (drive) => isDrivelistDrive(drive) && drive.isSystem, ).length; const numberOfHiddenSystemDrives = numberOfSystemDrives - numberOfDisplayedSystemDrives; @@ -407,7 +398,7 @@ export class TargetSelectorModal extends React.Component< height="calc(100% - 15px)" > ) => { + refFn={(t: Table) => { if (t !== null) { t.setRowSelection(selectedList); } @@ -416,13 +407,16 @@ export class TargetSelectorModal extends React.Component< data={displayedTargets} disabledRows={disabledTargets} rowKey="displayName" - onCheck={(rows: TableData[]) => { + onCheck={(rows: Target[]) => { this.setState({ - selectedList: rows, + selectedList: rows.filter(isDrivelistDrive), }); }} - onRowClick={(row: TableData) => { - if (row.disabled) { + onRowClick={(row: Target) => { + if ( + !isDrivelistDrive(row) || + this.driveShouldBeDisabled(row, image) + ) { return; } const newList = [...selectedList]; @@ -440,7 +434,7 @@ export class TargetSelectorModal extends React.Component< }); }} /> - {!showSystemDrives && numberOfHiddenSystemDrives > 0 && ( + {numberOfHiddenSystemDrives > 0 && ( ({ image: getImage(), }); -export const selectAllTargets = (modalTargets: DrivelistTarget[]) => { +export const selectAllTargets = ( + modalTargets: scanner.adapters.DrivelistDrive[], +) => { const selectedDrivesFromState = getSelectedDrives(); const deselected = selectedDrivesFromState.filter( (drive) => From 6554ccf0f8f90dfe9aefefcb512b275cee8650c9 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Wed, 17 Jun 2020 14:12:00 +0200 Subject: [PATCH 10/20] Sticky header in target selection table Changelog-entry: Sticky header in target selection table Change-type: patch --- .../target-selector/target-selector-modal.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index 03b025b1..91c5a25f 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -87,6 +87,11 @@ const ScrollableFlex = styled(Flex)` ::-webkit-scrollbar { display: none; } + + > div > div { + /* This is required for the sticky table header in TargetsTable */ + overflow-x: visible; + } `; const TargetsTable = styled(({ refFn, ...props }) => { @@ -96,28 +101,21 @@ const TargetsTable = styled(({ refFn, ...props }) => {
); })` - [data-display='table-head'] - [data-display='table-row'] - > [data-display='table-cell']:first-child { + [data-display='table-head'] [data-display='table-cell'] { + position: sticky; + top: 0; + background-color: ${(props) => props.theme.colors.quartenary.light}; + } + + [data-display='table-cell']:first-child { padding-left: 15px; } - [data-display='table-head'] - [data-display='table-row'] - > [data-display='table-cell'] { - padding: 6px 8px; - color: #2a506f; + [data-display='table-cell']:last-child { + width: 150px; } - [data-display='table-body'] - > [data-display='table-row'] - > [data-display='table-cell']:first-child { - padding-left: 15px; - } - - [data-display='table-body'] - > [data-display='table-row'] - > [data-display='table-cell'] { + && [data-display='table-row'] > [data-display='table-cell'] { padding: 6px 8px; color: #2a506f; } @@ -248,7 +246,8 @@ export class TargetSelectorModal extends React.Component< { field: 'description', key: 'extra', - label: '', + // Space as empty string would use the field name as label + label: ' ', render: (_description: string, drive: Target) => { if (isUsbbootDrive(drive)) { return this.renderProgress(drive.progress); From 92cd3d688d0492f961e6214e9ad20790774ab631 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Wed, 17 Jun 2020 20:18:56 +0200 Subject: [PATCH 11/20] Update etcher-sdk to v4.1.15 Changelog-entry: Update etcher-sdk to v4.1.15 Change-type: patch --- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 535bc439..c99fa18b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8295,9 +8295,9 @@ "dev": true }, "etcher-sdk": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.13.tgz", - "integrity": "sha512-ITzBgoy8AGI6/DHyS43wix2TNa9kVeILsr/vQl75Hbbt5gM4C2ygwqABG7V+MGTaNphWc2OstSP9k6xQpGfttw==", + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.15.tgz", + "integrity": "sha512-PZ/OLbAmtQXQlla33FVwvyLL5rzpYG5gQLpPiz1zCzNDWykaVIM9lXQ8m0DxTp7/m11tM3lw1gT/IPCUKtKfdA==", "dev": true, "requires": { "@ronomon/direct-io": "^3.0.1", diff --git a/package.json b/package.json index 43e42d81..013881e6 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "electron-notarize": "^0.3.0", "electron-rebuild": "^1.11.0", "electron-updater": "^4.3.2", - "etcher-sdk": "^4.1.13", + "etcher-sdk": "^4.1.15", "file-loader": "^6.0.0", "flexboxgrid": "^6.3.0", "husky": "^4.2.5", From a360370c4e861a1b60e174790acfa82f795fb868 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Wed, 17 Jun 2020 20:21:34 +0200 Subject: [PATCH 12/20] Update electron to v9.0.4 Changelog-entry: Update electron to v9.0.4 Change-type: patch --- npm-shrinkwrap.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c99fa18b..db2c420b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7346,9 +7346,9 @@ } }, "electron": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.3.tgz", - "integrity": "sha512-rY59wy50z0oWp/q69zq0UIzvtcM5j2BJbLAwEoLfVNS3DLt9wDZqRqSIBvLEBl+xWbafCnRA9haEqi7ssM94GA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.4.tgz", + "integrity": "sha512-QzkeZNAiNB7KxcdoQKSoaiVT/GQdB4Vt0/ZZOuU8tIKABAsni2I7ztiAbUzxcsnQsqEBSfChuPuDQ5A4VbbzPg==", "dev": true, "requires": { "@electron/get": "^1.0.1", diff --git a/package.json b/package.json index 013881e6..a2f2505b 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "css-loader": "^3.5.3", "d3": "^4.13.0", "debug": "^4.2.0", - "electron": "9.0.3", + "electron": "9.0.4", "electron-builder": "^22.7.0", "electron-mocha": "^8.2.0", "electron-notarize": "^0.3.0", From 07fde0d73ffd38b05315d3fa4f953f9bb97922b8 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 18 Jun 2020 13:32:00 +0200 Subject: [PATCH 13/20] Don't mutate usbboot drives when updating progress Change-type: patch --- lib/gui/app/app.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gui/app/app.ts b/lib/gui/app/app.ts index 5a4e3bec..1119ae34 100644 --- a/lib/gui/app/app.ts +++ b/lib/gui/app/app.ts @@ -264,7 +264,8 @@ function updateDriveProgress( // @ts-ignore const driveInMap = drives[drive.device]; if (driveInMap) { - driveInMap.progress = progress; + // @ts-ignore + drives[drive.device] = { ...driveInMap, progress }; setDrives(drives); } } From 7165a8190b4a7d57dbfaeb7748fb28826f4a8cd1 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 18 Jun 2020 13:37:26 +0200 Subject: [PATCH 14/20] Update electron-notarize to v1.0.0 Changelog-entry: Update electron-notarize to v1.0.0 Change-type: patch --- npm-shrinkwrap.json | 34 ++++++++++------------------------ package.json | 2 +- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index db2c420b..97cb50fa 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7500,40 +7500,26 @@ } }, "electron-notarize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.3.0.tgz", - "integrity": "sha512-tuDw8H0gcDOalNLv6RM2CwGvUXU60MPGZRDEmd0ppX+yP5XqL8Ec2DuXyz9J7WQSA3aRCfzIgH8C5CAivDYWMw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.0.0.tgz", + "integrity": "sha512-dsib1IAquMn0onCrNMJ6gtEIZn/azG8hZMCYOuZIMVMUeRMgBYHK1s5TK9P8xAcrAjh/2aN5WYHzgVSWX314og==", "dev": true, "requires": { "debug": "^4.1.1", - "fs-extra": "^8.1.0" + "fs-extra": "^9.0.1" }, "dependencies": { "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true } } }, diff --git a/package.json b/package.json index a2f2505b..cce6cbaa 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "electron": "9.0.4", "electron-builder": "^22.7.0", "electron-mocha": "^8.2.0", - "electron-notarize": "^0.3.0", + "electron-notarize": "^1.0.0", "electron-rebuild": "^1.11.0", "electron-updater": "^4.3.2", "etcher-sdk": "^4.1.15", From 129e7e20e8bba1381be071c80abfeb0dde25e517 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 18 Jun 2020 13:40:52 +0200 Subject: [PATCH 15/20] Update mocha to v8.0.1 Changelog-entry: Update mocha to v8.0.1 Change-type: patch --- npm-shrinkwrap.json | 453 ++++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 396 insertions(+), 59 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 97cb50fa..e50f9396 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4436,6 +4436,18 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -7488,6 +7500,200 @@ "yargs": "^15.3.1" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7496,6 +7702,27 @@ "requires": { "isexe": "^2.0.0" } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -7986,6 +8213,35 @@ "string.prototype.trimright": "^2.1.1" } }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -10316,6 +10572,12 @@ } } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", @@ -10461,6 +10723,12 @@ "is-path-inside": "^3.0.1" } }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", @@ -10552,12 +10820,24 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -10631,6 +10911,22 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, "jake": { "version": "10.7.1", "resolved": "https://registry.npmjs.org/jake/-/jake-10.7.1.tgz", @@ -12007,49 +12303,59 @@ "dev": true }, "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.0.1.tgz", + "integrity": "sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg==", "dev": true, "requires": { - "ansi-colors": "3.2.3", + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.3.0", + "chokidar": "3.3.1", "debug": "3.2.6", - "diff": "3.5.0", + "diff": "4.0.2", "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", + "find-up": "4.1.0", + "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "3.13.1", "log-symbols": "3.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", + "ms": "2.1.2", "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", + "promise.allsettled": "1.0.2", + "serialize-javascript": "3.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", "wide-align": "1.1.3", + "workerpool": "6.0.0", "yargs": "13.3.2", "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" }, "dependencies": { - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -12070,40 +12376,17 @@ "ms": "^2.1.1" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -12121,12 +12404,6 @@ "path-exists": "^3.0.0" } }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -12142,6 +12419,21 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, + "serialize-javascript": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", + "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -12162,13 +12454,28 @@ "ansi-regex": "^4.1.0" } }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" } }, "wrap-ansi": { @@ -12198,6 +12505,17 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } } }, "yargs-parser": { @@ -13760,6 +14078,19 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -17897,6 +18228,12 @@ "errno": "~0.1.7" } }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index cce6cbaa..3929270b 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "lodash": "^4.17.10", "mime-types": "^2.1.18", "mini-css-extract-plugin": "^0.9.0", - "mocha": "^7.0.1", + "mocha": "^8.0.1", "nan": "^2.14.0", "native-addon-loader": "^2.0.1", "node-gyp": "^7.0.0", From 5a45f8b122046ebfb8a29af4b49d0bb74f2b8afe Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Thu, 18 Jun 2020 16:32:35 +0200 Subject: [PATCH 16/20] Update target selector ok button label to show the number of selected devices Change-type: patch --- .../app/components/target-selector/target-selector-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index 91c5a25f..f0d0b56b 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -373,7 +373,7 @@ export class TargetSelectorModal extends React.Component< titleDetails={{getDrives().length} found} cancel={cancel} done={() => done(selectedList)} - action="Continue" + action={`Select (${selectedList.length})`} style={{ width: '780px', height: '420px', From 406955ca3eb948b6be7c56dea79e4166a6c88738 Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Fri, 19 Jun 2020 16:54:17 +0200 Subject: [PATCH 17/20] Add .vhd to the list of supported extensions, allow opening any file Changelog-entry: Add .vhd to the list of supported extensions, allow opening any file Change-type: patch --- lib/gui/app/os/dialog.ts | 8 +++- lib/shared/supported-formats.ts | 56 +++++++++----------------- package.json | 1 - tests/shared/supported-formats.spec.ts | 47 --------------------- 4 files changed, 26 insertions(+), 86 deletions(-) diff --git a/lib/gui/app/os/dialog.ts b/lib/gui/app/os/dialog.ts index e3233cf7..aea6b821 100644 --- a/lib/gui/app/os/dialog.ts +++ b/lib/gui/app/os/dialog.ts @@ -18,7 +18,7 @@ import * as electron from 'electron'; import * as _ from 'lodash'; import * as errors from '../../../shared/errors'; -import { getAllExtensions } from '../../../shared/supported-formats'; +import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats'; /** * @summary Open an image selection dialog @@ -40,7 +40,11 @@ export async function selectImage(): Promise { filters: [ { name: 'OS Images', - extensions: [...getAllExtensions()].sort(), + extensions: SUPPORTED_EXTENSIONS, + }, + { + name: 'All', + extensions: ['*'], }, ], }; diff --git a/lib/shared/supported-formats.ts b/lib/shared/supported-formats.ts index 4dee3ba5..c2b0c6dc 100644 --- a/lib/shared/supported-formats.ts +++ b/lib/shared/supported-formats.ts @@ -14,44 +14,28 @@ * limitations under the License. */ -import * as sdk from 'etcher-sdk'; -import * as mime from 'mime-types'; -import * as path from 'path'; +import { basename } from 'path'; -export function getCompressedExtensions(): string[] { - const result = []; - for (const [ - mimetype, - cls, - // @ts-ignore (mimetypes is private) - ] of sdk.sourceDestination.SourceDestination.mimetypes.entries()) { - if (cls.prototype instanceof sdk.sourceDestination.CompressedSource) { - const extension = mime.extension(mimetype); - if (extension) { - result.push(extension); - } - } - } - return result; -} - -export function getNonCompressedExtensions(): string[] { - return sdk.sourceDestination.SourceDestination.imageExtensions; -} - -export function getArchiveExtensions(): string[] { - return ['zip', 'etch']; -} - -export function getAllExtensions(): string[] { - return [ - ...getArchiveExtensions(), - ...getNonCompressedExtensions(), - ...getCompressedExtensions(), - ]; -} +export const SUPPORTED_EXTENSIONS = [ + 'bin', + 'bz2', + 'dmg', + 'dsk', + 'etch', + 'gz', + 'hddimg', + 'img', + 'iso', + 'raw', + 'rpi-sdimg', + 'sdcard', + 'vhd', + 'wic', + 'xz', + 'zip', +]; export function looksLikeWindowsImage(imagePath: string): boolean { const regex = /windows|win7|win8|win10|winxp/i; - return regex.test(path.basename(imagePath)); + return regex.test(basename(imagePath)); } diff --git a/package.json b/package.json index 3929270b..382e420e 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "inactivity-timer": "^1.0.0", "lint-staged": "^10.2.2", "lodash": "^4.17.10", - "mime-types": "^2.1.18", "mini-css-extract-plugin": "^0.9.0", "mocha": "^8.0.1", "nan": "^2.14.0", diff --git a/tests/shared/supported-formats.spec.ts b/tests/shared/supported-formats.spec.ts index 368321b1..17bb856a 100644 --- a/tests/shared/supported-formats.spec.ts +++ b/tests/shared/supported-formats.spec.ts @@ -20,53 +20,6 @@ import * as _ from 'lodash'; import * as supportedFormats from '../../lib/shared/supported-formats'; describe('Shared: SupportedFormats', function () { - describe('.getCompressedExtensions()', function () { - it('should return the supported compressed extensions', function () { - const extensions = supportedFormats.getCompressedExtensions().sort(); - expect(extensions).to.deep.equal(['bz2', 'gz', 'xz'].sort()); - }); - }); - - describe('.getNonCompressedExtensions()', function () { - it('should return the supported non compressed extensions', function () { - const extensions = supportedFormats.getNonCompressedExtensions(); - expect(extensions).to.deep.equal([ - 'img', - 'iso', - 'bin', - 'dsk', - 'hddimg', - 'raw', - 'dmg', - 'sdcard', - 'rpi-sdimg', - 'wic', - ]); - }); - }); - - describe('.getArchiveExtensions()', function () { - it('should return the supported archive extensions', function () { - const extensions = supportedFormats.getArchiveExtensions(); - expect(extensions).to.deep.equal(['zip', 'etch']); - }); - }); - - describe('.getAllExtensions()', function () { - it('should return the union of all compressed, uncompressed, and archive extensions', function () { - const archiveExtensions = supportedFormats.getArchiveExtensions(); - const compressedExtensions = supportedFormats.getCompressedExtensions(); - const nonCompressedExtensions = supportedFormats.getNonCompressedExtensions(); - const expected = _.union( - archiveExtensions, - compressedExtensions, - nonCompressedExtensions, - ).sort(); - const extensions = supportedFormats.getAllExtensions(); - expect(extensions.sort()).to.deep.equal(expected); - }); - }); - describe('.looksLikeWindowsImage()', function () { _.each( [ From 14e4cbf749b40664eb30f6678cfcd9fc28f7b140 Mon Sep 17 00:00:00 2001 From: Lorenzo Alberto Maria Ambrosi Date: Fri, 19 Jun 2020 17:08:54 +0200 Subject: [PATCH 18/20] Add icon to plug targets in targets modal Change-type: patch Signed-off-by: Lorenzo Alberto Maria Ambrosi --- .../target-selector/target-selector-modal.tsx | 10 +++++++++- lib/gui/assets/tgt.svg | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 lib/gui/assets/tgt.svg diff --git a/lib/gui/app/components/target-selector/target-selector-modal.tsx b/lib/gui/app/components/target-selector/target-selector-modal.tsx index f0d0b56b..ccff228c 100644 --- a/lib/gui/app/components/target-selector/target-selector-modal.tsx +++ b/lib/gui/app/components/target-selector/target-selector-modal.tsx @@ -52,6 +52,8 @@ import { logEvent, logException } from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; import { Modal } from '../../styled-components'; +import TargetSVGIcon from '../../../assets/tgt.svg'; + interface UsbbootDrive extends sourceDestination.UsbbootDrive { progress: number; } @@ -387,7 +389,13 @@ export class TargetSelectorModal extends React.Component< > {!hasAvailableDrives() ? ( - + + Plug a target drive ) : ( diff --git a/lib/gui/assets/tgt.svg b/lib/gui/assets/tgt.svg new file mode 100644 index 00000000..3193c769 --- /dev/null +++ b/lib/gui/assets/tgt.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From b32c4ee728adcb00d38f286beb456c3d8ecb6b8f Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Mon, 22 Jun 2020 15:07:16 +0200 Subject: [PATCH 19/20] Update partitioninfo to 5.3.5 Changelog-entry: Update partitioninfo to 5.3.5 Change-type: patch --- npm-shrinkwrap.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e50f9396..bd5fdbf6 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -13675,9 +13675,9 @@ "dev": true }, "partitioninfo": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/partitioninfo/-/partitioninfo-5.3.4.tgz", - "integrity": "sha512-cmf+S4LvtrgPlC8LbY2qI96Zf1z6j1v9rFBik23AxoE3R1EH9QYWdrCHJf5uwEp8iJUdRhZjQIaRvfLH5hPJPA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/partitioninfo/-/partitioninfo-5.3.5.tgz", + "integrity": "sha512-lZIt5D9wsOVnixePPmVyHQs1m1eDICgDecBmV00W5tpDoOwHdkoseW0p+YjBlWd4LIbUqJXJIrfjPq/12uAVSg==", "dev": true, "requires": { "bluebird": "^3.5.3", From ba16995070491690f3060b3b75a7ca07e70ead9a Mon Sep 17 00:00:00 2001 From: Alexis Svinartchouk Date: Mon, 22 Jun 2020 16:53:44 +0200 Subject: [PATCH 20/20] Show system drives last Change-type: patch --- lib/gui/app/models/store.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gui/app/models/store.ts b/lib/gui/app/models/store.ts index d66e9a73..fe1b3fff 100644 --- a/lib/gui/app/models/store.ts +++ b/lib/gui/app/models/store.ts @@ -134,6 +134,8 @@ function storeReducer( } drives = _.sortBy(drives, [ + // System drives last + (d) => !!d.isSystem, // Devices with no devicePath first (usbboot) (d) => !!d.devicePath, // Then sort by devicePath (only available on Linux with udev) or device