diff --git a/lib/gui/app/components/drive-selector/drive-selector.tsx b/lib/gui/app/components/drive-selector/drive-selector.tsx index 8bd3daef..7cb5196a 100644 --- a/lib/gui/app/components/drive-selector/drive-selector.tsx +++ b/lib/gui/app/components/drive-selector/drive-selector.tsx @@ -18,15 +18,7 @@ import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exc import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg'; import * as sourceDestination from 'etcher-sdk/build/source-destination/'; import * as React from 'react'; -import { - Flex, - ModalProps, - Txt, - Badge, - Link, - Table, - TableColumn, -} from 'rendition'; +import { Flex, ModalProps, Txt, Badge, Link, TableColumn } from 'rendition'; import styled from 'styled-components'; import { @@ -43,7 +35,12 @@ import { getImage, isDriveSelected } from '../../models/selection-state'; import { store } from '../../models/store'; import { logEvent, logException } from '../../modules/analytics'; import { open as openExternal } from '../../os/open-external/services/open-external'; -import { Alert, Modal, ScrollableFlex } from '../../styled-components'; +import { + Alert, + GenericTableProps, + Modal, + Table, +} from '../../styled-components'; import DriveSVGIcon from '../../../assets/tgt.svg'; import { SourceMetadata } from '../source-selector/source-selector'; @@ -75,74 +72,29 @@ function isDrivelistDrive(drive: Drive): drive is DrivelistDrive { return typeof (drive as DrivelistDrive).size === 'number'; } -const DrivesTable = styled(({ refFn, ...props }) => ( -
- ref={refFn} {...props} /> -
+const DrivesTable = styled((props: GenericTableProps) => ( + {...props} /> ))` - [data-display='table-head'] - > [data-display='table-row'] - > [data-display='table-cell'] { - position: sticky; - top: 0; - background-color: ${(props) => props.theme.colors.quartenary.light}; - - input[type='checkbox'] + div { - display: ${({ multipleSelection }) => - multipleSelection ? 'flex' : 'none'}; - } - - &:first-child { - padding-left: 15px; - } - - &:nth-child(2) { - width: 38%; - } - - &:nth-child(3) { - width: 15%; - } - - &:nth-child(4) { - width: 15%; - } - - &:nth-child(5) { - width: 32%; - } - } - - [data-display='table-body'] > [data-display='table-row'] { - > [data-display='table-cell']:first-child { - padding-left: 15px; - } - - > [data-display='table-cell']:last-child { - padding-right: 0; - } - - &[data-highlight='true'] { - &.system { - background-color: ${(props) => - props.showWarnings ? '#fff5e6' : '#e8f5fc'}; + [data-display='table-head'], + [data-display='table-body'] { + > [data-display='table-row'] > [data-display='table-cell'] { + &:nth-child(2) { + width: 38%; } - > [data-display='table-cell']:first-child { - box-shadow: none; + &:nth-child(3) { + width: 15%; + } + + &:nth-child(4) { + width: 15%; + } + + &:nth-child(5) { + width: 32%; } } } - - && [data-display='table-row'] > [data-display='table-cell'] { - padding: 6px 8px; - color: #2a506f; - } - - input[type='checkbox'] + div { - border-radius: ${({ multipleSelection }) => - multipleSelection ? '4px' : '50%'}; - } `; function badgeShadeFromStatus(status: string) { @@ -453,95 +405,92 @@ export class DriveSelector extends React.Component< }} {...props} > - - {!hasAvailableDrives() ? ( - - - {this.props.emptyListLabel} - - ) : ( - - ) => { - if (t !== null) { - t.setRowSelection(selectedList); - } - }} - multipleSelection={this.props.multipleSelection} - columns={this.tableColumns} - data={displayedDrives} - disabledRows={disabledDrives} - getRowClass={(row: Drive) => - isDrivelistDrive(row) && row.isSystem ? ['system'] : [] + {!hasAvailableDrives() ? ( + + + {this.props.emptyListLabel} + + ) : ( + <> + { + if (t !== null) { + t.setRowSelection(selectedList); } - rowKey="displayName" - onCheck={(rows: Drive[]) => { - const newSelection = rows.filter(isDrivelistDrive); - if (this.props.multipleSelection) { - this.setState({ - selectedList: newSelection, - }); - return; + }} + multipleSelection={this.props.multipleSelection} + columns={this.tableColumns} + data={displayedDrives} + disabledRows={disabledDrives} + getRowClass={(row: Drive) => + isDrivelistDrive(row) && row.isSystem ? ['system'] : [] + } + rowKey="displayName" + onCheck={(rows: Drive[]) => { + const newSelection = rows.filter(isDrivelistDrive); + if (this.props.multipleSelection) { + this.setState({ + selectedList: newSelection, + }); + return; + } + this.setState({ + selectedList: newSelection.slice(newSelection.length - 1), + }); + }} + onRowClick={(row: Drive) => { + if ( + !isDrivelistDrive(row) || + this.driveShouldBeDisabled(row, image) + ) { + return; + } + if (this.props.multipleSelection) { + const newList = [...selectedList]; + const selectedIndex = selectedList.findIndex( + (drive) => drive.device === row.device, + ); + if (selectedIndex === -1) { + newList.push(row); + } else { + // Deselect if selected + newList.splice(selectedIndex, 1); } this.setState({ - selectedList: newSelection.slice(newSelection.length - 1), + selectedList: newList, }); - }} - onRowClick={(row: Drive) => { - if ( - !isDrivelistDrive(row) || - this.driveShouldBeDisabled(row, image) - ) { - return; - } - if (this.props.multipleSelection) { - const newList = [...selectedList]; - const selectedIndex = selectedList.findIndex( - (drive) => drive.device === row.device, - ); - if (selectedIndex === -1) { - newList.push(row); - } else { - // Deselect if selected - newList.splice(selectedIndex, 1); - } - this.setState({ - selectedList: newList, - }); - return; - } - this.setState({ - selectedList: [row], - }); - }} - /> - {numberOfHiddenSystemDrives > 0 && ( - this.setState({ showSystemDrives: true })} - > - - - Show {numberOfHiddenSystemDrives} hidden - - - )} - - )} - {this.props.showWarnings && hasSystemDrives ? ( - - Selecting your system drive is dangerous and will erase your - drive! - - ) : null} - + return; + } + this.setState({ + selectedList: [row], + }); + }} + /> + {numberOfHiddenSystemDrives > 0 && ( + this.setState({ showSystemDrives: true })} + > + + + Show {numberOfHiddenSystemDrives} hidden + + + )} + + )} + {this.props.showWarnings && hasSystemDrives ? ( + + Selecting your system drive is dangerous and will erase your drive! + + ) : null} {missingDriversModal.drive !== undefined && ( void) { function FinishPage({ goToMain }: { goToMain: () => void }) { const [webviewShowing, setWebviewShowing] = React.useState(false); const flashResults = flashState.getFlashResults(); - let errors = flashResults?.results?.errors; + let errors: FlashError[] = flashResults.results?.errors; if (errors === undefined) { errors = (store.getState().toJS().failedDevicePaths || []).map( ([, error]: [string, FlashError]) => ({ diff --git a/lib/gui/app/components/flash-results/flash-results.tsx b/lib/gui/app/components/flash-results/flash-results.tsx index 5360f0c6..562984be 100644 --- a/lib/gui/app/components/flash-results/flash-results.tsx +++ b/lib/gui/app/components/flash-results/flash-results.tsx @@ -20,7 +20,7 @@ import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/times-circl import * as _ from 'lodash'; import outdent from 'outdent'; import * as React from 'react'; -import { Flex, FlexProps, Link, Table, TableColumn, Txt } from 'rendition'; +import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition'; import styled from 'styled-components'; import { progress } from '../../../../shared/messages'; @@ -30,37 +30,31 @@ import FlashSvg from '../../../assets/flash.svg'; import { resetState } from '../../models/flash-state'; import * as selection from '../../models/selection-state'; import { middleEllipsis } from '../../utils/middle-ellipsis'; -import { Modal } from '../../styled-components'; +import { Modal, Table } from '../../styled-components'; -const ErrorsTable = styled(({ refFn, ...props }) => { - return ( -
- ref={refFn} {...props} /> -
- ); -})` - [data-display='table-head'] [data-display='table-cell'] { - width: 50%; - position: sticky; - top: 0; - background-color: ${(props) => props.theme.colors.quartenary.light}; - } +const ErrorsTable = styled((props) => {...props} />)` + [data-display='table-head'], + [data-display='table-body'] { + [data-display='table-cell'] { + &:first-child { + width: 30%; + } - [data-display='table-cell']:first-child { - padding-left: 15px; - } + &:nth-child(2) { + width: 20%; + } - [data-display='table-cell']:last-child { - width: 150px; - } - - && [data-display='table-row'] > [data-display='table-cell'] { - padding: 6px 8px; - color: #2a506f; - } + &:last-child { + width: 50%; + } + } `; -const DoneIcon = (props: { allFailed: boolean; someFailed: boolean }) => { +const DoneIcon = (props: { + skipped: boolean; + allFailed: boolean; + someFailed: boolean; +}) => { const { allFailed, someFailed } = props; const someOrAllFailed = allFailed || someFailed; const svgProps = { @@ -75,7 +69,7 @@ const DoneIcon = (props: { allFailed: boolean; someFailed: boolean }) => { color: someOrAllFailed ? '#c6c8c9' : '#1ac135', }, }; - return allFailed ? ( + return allFailed && !props.skipped ? ( ) : ( @@ -107,7 +101,7 @@ const columns: Array> = [ field: 'message', label: 'Error', render: (message: string, { code }: FlashError) => { - return message ? message : code; + return message ?? code; }, }, ]; @@ -155,10 +149,11 @@ export function FlashResults({ > - {middleEllipsis(image, 16)} + {middleEllipsis(image, 24)} Flash Complete! diff --git a/lib/gui/app/styled-components.tsx b/lib/gui/app/styled-components.tsx index 7ecd0487..c9af1786 100644 --- a/lib/gui/app/styled-components.tsx +++ b/lib/gui/app/styled-components.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import * as _ from 'lodash'; import * as React from 'react'; import { Alert as AlertBase, @@ -23,27 +24,16 @@ import { ButtonProps, Modal as ModalBase, Provider, + Table as BaseTable, + TableProps as BaseTableProps, Txt, - Theme as renditionTheme, } from 'rendition'; import styled, { css } from 'styled-components'; import { colors, theme } from './theme'; -const defaultTheme = { - ...renditionTheme, - ...theme, - layer: { - extend: () => ` - > div:first-child { - background-color: transparent; - } - `, - }, -}; - export const ThemedProvider = (props: any) => ( - + ); export const BaseButton = styled(Button)` @@ -134,24 +124,23 @@ const modalFooterShadowCss = css` background-attachment: local, local, scroll, scroll; `; -export const Modal = styled(({ style, ...props }) => { +export const Modal = styled(({ style, children, ...props }) => { return ( ` - ${defaultTheme.layer.extend()} + ${theme.layer.extend()} - > div:last-child { - top: 0; - } - `, + > div:last-child { + top: 0; + } + `, }, - }} + })} > { ...style, }} {...props} - /> + > + + {...children} + + ); })` @@ -188,11 +181,8 @@ export const Modal = styled(({ style, ...props }) => { > div:nth-child(2) { height: 61%; - - > div:not(.system-drive-alert) { - padding: 0 30px; - ${modalFooterShadowCss} - } + padding: 0 30px; + ${modalFooterShadowCss} } > div:last-child { @@ -249,3 +239,82 @@ export const Alert = styled((props) => ( display: none; } `; + +export interface GenericTableProps extends BaseTableProps { + refFn: (t: BaseTable) => void; + multipleSelection: boolean; + showWarnings?: boolean; +} + +const GenericTable: ( + props: GenericTableProps, +) => React.ReactElement> = ({ + refFn, + ...props +}: GenericTableProps) => ( +
+ ref={refFn} {...props} /> +
+); + +function StyledTable() { + return styled((props: GenericTableProps) => ( + {...props} /> + ))` + [data-display='table-head'] + > [data-display='table-row'] + > [data-display='table-cell'] { + position: sticky; + background-color: #f8f9fd; + top: 0; + z-index: 1; + + input[type='checkbox'] + div { + display: ${(props) => (props.multipleSelection ? 'flex' : 'none')}; + } + } + + [data-display='table-head'] > [data-display='table-row'], + [data-display='table-body'] > [data-display='table-row'] { + > [data-display='table-cell']:first-child { + padding-left: 15px; + width: 6%; + } + + > [data-display='table-cell']:last-child { + padding-right: 0; + } + } + + [data-display='table-body'] > [data-display='table-row'] { + &:nth-of-type(2n) { + background: transparent; + } + + &[data-highlight='true'] { + &.system { + background-color: ${(props) => + props.showWarnings ? '#fff5e6' : '#e8f5fc'}; + } + + > [data-display='table-cell']:first-child { + box-shadow: none; + } + } + } + + && [data-display='table-row'] > [data-display='table-cell'] { + padding: 6px 8px; + color: #2a506f; + } + + input[type='checkbox'] + div { + border-radius: ${(props) => (props.multipleSelection ? '4px' : '50%')}; + } + `; +} + +export const Table = (props: GenericTableProps) => { + const TypedStyledFunctional = StyledTable(); + return ; +}; diff --git a/lib/gui/app/theme.ts b/lib/gui/app/theme.ts index e6a4ae95..9339034c 100644 --- a/lib/gui/app/theme.ts +++ b/lib/gui/app/theme.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import * as _ from 'lodash'; +import { Theme } from 'rendition'; + export const colors = { dark: { foreground: '#fff', @@ -67,8 +70,7 @@ export const colors = { const font = 'SourceSansPro'; -export const theme = { - colors, +export const theme = _.merge({}, Theme, { font, global: { font: { @@ -109,4 +111,11 @@ export const theme = { } `, }, -}; + layer: { + extend: () => ` + > div:first-child { + background-color: transparent; + } + `, + }, +});