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 e74dc9eb60
commit 31409c61ca
4 changed files with 219 additions and 211 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 ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import * as sourceDestination from 'etcher-sdk/build/source-destination/'; import * as sourceDestination from 'etcher-sdk/build/source-destination/';
import * as React from 'react'; import * as React from 'react';
import { import { Flex, ModalProps, Txt, Badge, Link, TableColumn } from 'rendition';
Flex,
ModalProps,
Txt,
Badge,
Link,
Table,
TableColumn,
} from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
import { import {
@ -43,7 +35,12 @@ import { getImage, isDriveSelected } from '../../models/selection-state';
import { store } from '../../models/store'; import { store } from '../../models/store';
import { logEvent, logException } from '../../modules/analytics'; import { logEvent, logException } from '../../modules/analytics';
import { open as openExternal } from '../../os/open-external/services/open-external'; 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 DriveSVGIcon from '../../../assets/tgt.svg';
import { SourceMetadata } from '../source-selector/source-selector'; 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'; return typeof (drive as DrivelistDrive).size === 'number';
} }
const DrivesTable = styled(({ refFn, ...props }) => ( const DrivesTable = styled((props: GenericTableProps<Drive>) => (
<div> <Table<Drive> {...props} />
<Table<Drive> ref={refFn} {...props} />
</div>
))` ))`
[data-display='table-head'] [data-display='table-head'],
> [data-display='table-row'] [data-display='table-body'] {
> [data-display='table-cell'] { > [data-display='table-row'] > [data-display='table-cell'] {
position: sticky; &:nth-child(2) {
top: 0; width: 38%;
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-cell']:first-child { &:nth-child(3) {
box-shadow: none; 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) { function badgeShadeFromStatus(status: string) {
@ -453,95 +405,92 @@ export class DriveSelector extends React.Component<
}} }}
{...props} {...props}
> >
<Flex width="100%" height="90%"> {!hasAvailableDrives() ? (
{!hasAvailableDrives() ? ( <Flex
<Flex flexDirection="column"
flexDirection="column" justifyContent="center"
justifyContent="center" alignItems="center"
alignItems="center" width="100%"
width="100%" >
> <DriveSVGIcon width="40px" height="90px" />
<DriveSVGIcon width="40px" height="90px" /> <b>{this.props.emptyListLabel}</b>
<b>{this.props.emptyListLabel}</b> </Flex>
</Flex> ) : (
) : ( <>
<ScrollableFlex flexDirection="column" width="100%"> <DrivesTable
<DrivesTable refFn={(t) => {
refFn={(t: Table<Drive>) => { if (t !== null) {
if (t !== null) { t.setRowSelection(selectedList);
t.setRowSelection(selectedList);
}
}}
multipleSelection={this.props.multipleSelection}
columns={this.tableColumns}
data={displayedDrives}
disabledRows={disabledDrives}
getRowClass={(row: Drive) =>
isDrivelistDrive(row) && row.isSystem ? ['system'] : []
} }
rowKey="displayName" }}
onCheck={(rows: Drive[]) => { multipleSelection={this.props.multipleSelection}
const newSelection = rows.filter(isDrivelistDrive); columns={this.tableColumns}
if (this.props.multipleSelection) { data={displayedDrives}
this.setState({ disabledRows={disabledDrives}
selectedList: newSelection, getRowClass={(row: Drive) =>
}); isDrivelistDrive(row) && row.isSystem ? ['system'] : []
return; }
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({ this.setState({
selectedList: newSelection.slice(newSelection.length - 1), selectedList: newList,
}); });
}} return;
onRowClick={(row: Drive) => { }
if ( this.setState({
!isDrivelistDrive(row) || selectedList: [row],
this.driveShouldBeDisabled(row, image) });
) { }}
return; />
} {numberOfHiddenSystemDrives > 0 && (
if (this.props.multipleSelection) { <Link
const newList = [...selectedList]; mt={15}
const selectedIndex = selectedList.findIndex( mb={15}
(drive) => drive.device === row.device, fontSize="14px"
); onClick={() => this.setState({ showSystemDrives: true })}
if (selectedIndex === -1) { >
newList.push(row); <Flex alignItems="center">
} else { <ChevronDownSvg height="1em" fill="currentColor" />
// Deselect if selected <Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
newList.splice(selectedIndex, 1); </Flex>
} </Link>
this.setState({ )}
selectedList: newList, </>
}); )}
return; {this.props.showWarnings && hasSystemDrives ? (
} <Alert className="system-drive-alert" style={{ width: '67%' }}>
this.setState({ Selecting your system drive is dangerous and will erase your drive!
selectedList: [row], </Alert>
}); ) : null}
}}
/>
{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>
{missingDriversModal.drive !== undefined && ( {missingDriversModal.drive !== undefined && (
<Modal <Modal

View File

@ -20,7 +20,7 @@ import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/times-circl
import * as _ from 'lodash'; import * as _ from 'lodash';
import outdent from 'outdent'; import outdent from 'outdent';
import * as React from 'react'; 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 styled from 'styled-components';
import { progress } from '../../../../shared/messages'; import { progress } from '../../../../shared/messages';
@ -31,7 +31,7 @@ import { getDrives } from '../../models/available-drives';
import { resetState } from '../../models/flash-state'; import { resetState } from '../../models/flash-state';
import * as selection from '../../models/selection-state'; import * as selection from '../../models/selection-state';
import { middleEllipsis } from '../../utils/middle-ellipsis'; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { Modal } from '../../styled-components'; import { Modal, Table } from '../../styled-components';
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)` const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
&&& [data-display='table-head'], &&& [data-display='table-head'],
@ -99,7 +99,7 @@ const columns: Array<TableColumn<FlashError>> = [
field: 'message', field: 'message',
label: 'Error', label: 'Error',
render: (message: string, { code }: FlashError) => { render: (message: string, { code }: FlashError) => {
return message ? message : code; return message ?? code;
}, },
}, },
]; ];
@ -152,7 +152,7 @@ export function FlashResults({
allFailed={allFailed} allFailed={allFailed}
color={allFailed || someFailed ? '#c6c8c9' : '#1ac135'} color={allFailed || someFailed ? '#c6c8c9' : '#1ac135'}
/> />
<Txt>{middleEllipsis(image, 16)}</Txt> <Txt>{middleEllipsis(image, 24)}</Txt>
</Flex> </Flex>
<Txt fontSize={24} color="#fff" mb="17px"> <Txt fontSize={24} color="#fff" mb="17px">
Flash Complete! Flash Complete!

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import * as _ from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { import {
Alert as AlertBase, Alert as AlertBase,
@ -23,27 +24,16 @@ import {
ButtonProps, ButtonProps,
Modal as ModalBase, Modal as ModalBase,
Provider, Provider,
Table as BaseTable,
TableProps as BaseTableProps,
Txt, Txt,
Theme as renditionTheme,
} from 'rendition'; } from 'rendition';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { colors, theme } from './theme'; import { colors, theme } from './theme';
const defaultTheme = {
...renditionTheme,
...theme,
layer: {
extend: () => `
> div:first-child {
background-color: transparent;
}
`,
},
};
export const ThemedProvider = (props: any) => ( export const ThemedProvider = (props: any) => (
<Provider theme={defaultTheme} {...props}></Provider> <Provider theme={theme} {...props}></Provider>
); );
export const BaseButton = styled(Button)` export const BaseButton = styled(Button)`
@ -134,41 +124,27 @@ const modalFooterShadowCss = css`
background-attachment: local, local, scroll, scroll; background-attachment: local, local, scroll, scroll;
`; `;
export const Modal = styled(({ style, ...props }) => { export const Modal = styled(({ style, children, ...props }) => {
return ( return (
<Provider <ModalBase
theme={{ position="top"
...defaultTheme, width="97vw"
header: { cancelButtonProps={{
height: '50px', style: {
}, marginRight: '20px',
layer: { border: 'solid 1px #2a506f',
extend: () => `
${defaultTheme.layer.extend()}
> div:last-child {
top: 0;
}
`,
}, },
}} }}
style={{
height: '87.5vh',
...style,
}}
{...props}
> >
<ModalBase <ScrollableFlex flexDirection="column" width="100%" height="90%">
position="top" {...children}
width="97vw" </ScrollableFlex>
cancelButtonProps={{ </ModalBase>
style: {
marginRight: '20px',
border: 'solid 1px #2a506f',
},
}}
style={{
height: '87.5vh',
...style,
}}
{...props}
/>
</Provider>
); );
})` })`
> div { > div {
@ -188,11 +164,8 @@ export const Modal = styled(({ style, ...props }) => {
> div:nth-child(2) { > div:nth-child(2) {
height: 61%; height: 61%;
padding: 0 30px;
> div:not(.system-drive-alert) { ${modalFooterShadowCss}
padding: 0 30px;
${modalFooterShadowCss}
}
} }
> div:last-child { > div:last-child {
@ -249,3 +222,82 @@ export const Alert = styled((props) => (
display: none; 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

@ -115,4 +115,11 @@ export const theme = _.merge({}, Theme, {
} }
`, `,
}, },
layer: {
extend: () => `
> div:first-child {
background-color: transparent;
}
`,
},
}); });