Use drive-selector's table for flash errors table

Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
This commit is contained in:
Lorenzo Alberto Maria Ambrosi 2020-09-18 09:43:12 +02:00
parent 27695babfd
commit 78aca6a19f
5 changed files with 242 additions and 220 deletions

View File

@ -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 }) => (
<div>
<Table<Drive> ref={refFn} {...props} />
</div>
const DrivesTable = styled((props: GenericTableProps<Drive>) => (
<Table<Drive> {...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}
>
<Flex width="100%" height="90%">
{!hasAvailableDrives() ? (
<Flex
flexDirection="column"
justifyContent="center"
alignItems="center"
width="100%"
>
<DriveSVGIcon width="40px" height="90px" />
<b>{this.props.emptyListLabel}</b>
</Flex>
) : (
<ScrollableFlex flexDirection="column" width="100%">
<DrivesTable
refFn={(t: Table<Drive>) => {
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() ? (
<Flex
flexDirection="column"
justifyContent="center"
alignItems="center"
width="100%"
>
<DriveSVGIcon width="40px" height="90px" />
<b>{this.props.emptyListLabel}</b>
</Flex>
) : (
<>
<DrivesTable
refFn={(t) => {
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 && (
<Link
mt={15}
mb={15}
fontSize="14px"
onClick={() => this.setState({ showSystemDrives: true })}
>
<Flex alignItems="center">
<ChevronDownSvg height="1em" fill="currentColor" />
<Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
</Flex>
</Link>
)}
</ScrollableFlex>
)}
{this.props.showWarnings && hasSystemDrives ? (
<Alert className="system-drive-alert" style={{ width: '67%' }}>
Selecting your system drive is dangerous and will erase your
drive!
</Alert>
) : null}
</Flex>
return;
}
this.setState({
selectedList: [row],
});
}}
/>
{numberOfHiddenSystemDrives > 0 && (
<Link
mt={15}
mb={15}
fontSize="14px"
onClick={() => this.setState({ showSystemDrives: true })}
>
<Flex alignItems="center">
<ChevronDownSvg height="1em" fill="currentColor" />
<Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
</Flex>
</Link>
)}
</>
)}
{this.props.showWarnings && hasSystemDrives ? (
<Alert className="system-drive-alert" style={{ width: '67%' }}>
Selecting your system drive is dangerous and will erase your drive!
</Alert>
) : null}
{missingDriversModal.drive !== undefined && (
<Modal

View File

@ -42,7 +42,7 @@ function restart(goToMain: () => 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]) => ({

View File

@ -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 (
<div>
<Table<FlashError> ref={refFn} {...props} />
</div>
);
})`
[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) => <Table<FlashError> {...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 ? (
<TimesCircleSvg {...svgProps} />
) : (
<CheckCircleSvg {...svgProps} />
@ -107,7 +101,7 @@ const columns: Array<TableColumn<FlashError>> = [
field: 'message',
label: 'Error',
render: (message: string, { code }: FlashError) => {
return message ? message : code;
return message ?? code;
},
},
];
@ -155,10 +149,11 @@ export function FlashResults({
>
<FlashSvg width="40px" height="40px" className="disabled" />
<DoneIcon
skipped={skip}
allFailed={allFailed}
someFailed={results.devices.failed !== 0}
/>
<Txt>{middleEllipsis(image, 16)}</Txt>
<Txt>{middleEllipsis(image, 24)}</Txt>
</Flex>
<Txt fontSize={24} color="#fff" mb="17px">
Flash Complete!

View File

@ -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) => (
<Provider theme={defaultTheme} {...props}></Provider>
<Provider theme={theme} {...props}></Provider>
);
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 (
<Provider
theme={{
...defaultTheme,
theme={_.merge({}, theme, {
header: {
height: '50px',
},
layer: {
extend: () => `
${defaultTheme.layer.extend()}
${theme.layer.extend()}
> div:last-child {
top: 0;
}
`,
> div:last-child {
top: 0;
}
`,
},
}}
})}
>
<ModalBase
position="top"
@ -167,7 +156,11 @@ export const Modal = styled(({ style, ...props }) => {
...style,
}}
{...props}
/>
>
<ScrollableFlex flexDirection="column" width="100%" height="90%">
{...children}
</ScrollableFlex>
</ModalBase>
</Provider>
);
})`
@ -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<T> extends BaseTableProps<T> {
refFn: (t: BaseTable<T>) => void;
multipleSelection: boolean;
showWarnings?: boolean;
}
const GenericTable: <T>(
props: GenericTableProps<T>,
) => React.ReactElement<GenericTableProps<T>> = <T extends {}>({
refFn,
...props
}: GenericTableProps<T>) => (
<div>
<BaseTable<T> ref={refFn} {...props} />
</div>
);
function StyledTable<T>() {
return styled((props: GenericTableProps<T>) => (
<GenericTable<T> {...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 = <T extends {}>(props: GenericTableProps<T>) => {
const TypedStyledFunctional = StyledTable<T>();
return <TypedStyledFunctional {...props} />;
};

View File

@ -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;
}
`,
},
});