mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-14 06:46:35 +00:00
Merge pull request #3325 from balena-io/new-success-screen-2
New success screen 2
This commit is contained in:
commit
a2a0f2ef41
@ -356,6 +356,16 @@ async function main() {
|
||||
ReactDOM.render(
|
||||
React.createElement(MainPage),
|
||||
document.getElementById('main'),
|
||||
// callback to set the correct zoomFactor for webviews as well
|
||||
async () => {
|
||||
const fullscreen = await settings.get('fullscreen');
|
||||
const width = fullscreen ? window.screen.width : window.outerWidth;
|
||||
try {
|
||||
electron.webFrame.setZoomFactor(width / settings.DEFAULT_WIDTH);
|
||||
} catch (err) {
|
||||
// noop
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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: 32%;
|
||||
}
|
||||
|
||||
> [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) {
|
||||
@ -393,6 +345,16 @@ export class DriveSelector extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
private deselectingAll(rows: DrivelistDrive[]) {
|
||||
return (
|
||||
rows.length > 0 &&
|
||||
rows.length === this.state.selectedList.length &&
|
||||
this.state.selectedList.every(
|
||||
(d) => rows.findIndex((r) => d.device === r.device) > -1,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubscribe = store.subscribe(() => {
|
||||
const drives = getDrives();
|
||||
@ -453,95 +415,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;
|
||||
}}
|
||||
checkedRowsNumber={selectedList.length}
|
||||
multipleSelection={this.props.multipleSelection}
|
||||
columns={this.tableColumns}
|
||||
data={displayedDrives}
|
||||
disabledRows={disabledDrives}
|
||||
getRowClass={(row: Drive) =>
|
||||
isDrivelistDrive(row) && row.isSystem ? ['system'] : []
|
||||
}
|
||||
rowKey="displayName"
|
||||
onCheck={(rows: Drive[]) => {
|
||||
let newSelection = rows.filter(isDrivelistDrive);
|
||||
if (this.props.multipleSelection) {
|
||||
if (this.deselectingAll(newSelection)) {
|
||||
newSelection = [];
|
||||
}
|
||||
this.setState({
|
||||
selectedList: newSelection.slice(newSelection.length - 1),
|
||||
selectedList: newSelection,
|
||||
});
|
||||
}}
|
||||
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: newSelection.slice(newSelection.length - 1),
|
||||
});
|
||||
}}
|
||||
onRowClick={(row: Drive) => {
|
||||
if (
|
||||
!isDrivelistDrive(row) ||
|
||||
this.driveShouldBeDisabled(row, image)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const index = selectedList.findIndex(
|
||||
(d) => d.device === row.device,
|
||||
);
|
||||
const newList = this.props.multipleSelection
|
||||
? [...selectedList]
|
||||
: [];
|
||||
if (index === -1) {
|
||||
newList.push(row);
|
||||
} else {
|
||||
// Deselect if selected
|
||||
newList.splice(index, 1);
|
||||
}
|
||||
this.setState({
|
||||
selectedList: newList,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{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
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Flex } from 'rendition';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
@ -23,13 +22,9 @@ import * as flashState from '../../models/flash-state';
|
||||
import * as selectionState from '../../models/selection-state';
|
||||
import { Actions, store } from '../../models/store';
|
||||
import * as analytics from '../../modules/analytics';
|
||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||
import { FlashAnother } from '../flash-another/flash-another';
|
||||
import { FlashResults } from '../flash-results/flash-results';
|
||||
|
||||
import EtcherSvg from '../../../assets/etcher.svg';
|
||||
import LoveSvg from '../../../assets/love.svg';
|
||||
import BalenaSvg from '../../../assets/balena.svg';
|
||||
import { FlashResults, FlashError } from '../flash-results/flash-results';
|
||||
import { SafeWebview } from '../safe-webview/safe-webview';
|
||||
|
||||
function restart(goToMain: () => void) {
|
||||
selectionState.deselectAllDrives();
|
||||
@ -44,22 +39,56 @@ function restart(goToMain: () => void) {
|
||||
goToMain();
|
||||
}
|
||||
|
||||
function formattedErrors() {
|
||||
const errors = _.map(
|
||||
_.get(flashState.getFlashResults(), ['results', 'errors']),
|
||||
(error) => {
|
||||
return `${error.device}: ${error.message || error.code}`;
|
||||
},
|
||||
);
|
||||
return errors.join('\n');
|
||||
}
|
||||
|
||||
function FinishPage({ goToMain }: { goToMain: () => void }) {
|
||||
const results = flashState.getFlashResults().results || {};
|
||||
const [webviewShowing, setWebviewShowing] = React.useState(false);
|
||||
const flashResults = flashState.getFlashResults();
|
||||
const errors: FlashError[] = (
|
||||
store.getState().toJS().failedDeviceErrors || []
|
||||
).map(([, error]: [string, FlashError]) => ({
|
||||
...error,
|
||||
}));
|
||||
const {
|
||||
averageSpeed,
|
||||
blockmappedSize,
|
||||
bytesWritten,
|
||||
failed,
|
||||
size,
|
||||
} = flashState.getFlashState();
|
||||
const {
|
||||
skip,
|
||||
results = {
|
||||
bytesWritten,
|
||||
sourceMetadata: {
|
||||
size,
|
||||
blockmappedSize,
|
||||
},
|
||||
averageFlashingSpeed: averageSpeed,
|
||||
devices: { failed, successful: 0 },
|
||||
},
|
||||
} = flashResults;
|
||||
return (
|
||||
<Flex flexDirection="column" width="100%" color="#fff">
|
||||
<Flex height="160px" alignItems="center" justifyContent="center">
|
||||
<FlashResults results={results} errors={formattedErrors()} />
|
||||
<Flex height="100%" justifyContent="space-between">
|
||||
<Flex
|
||||
width={webviewShowing ? '36.2vw' : '100vw'}
|
||||
height="100vh"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
|
||||
}}
|
||||
>
|
||||
<FlashResults
|
||||
image={selectionState.getImage()?.name}
|
||||
results={results}
|
||||
skip={skip}
|
||||
errors={errors}
|
||||
mb="32px"
|
||||
goToMain={goToMain}
|
||||
/>
|
||||
|
||||
<FlashAnother
|
||||
onClick={() => {
|
||||
@ -67,34 +96,18 @@ function FinishPage({ goToMain }: { goToMain: () => void }) {
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
height="320px"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex fontSize="28px" mt="40px">
|
||||
Thanks for using
|
||||
<EtcherSvg
|
||||
width="165px"
|
||||
style={{ margin: '0 10px', cursor: 'pointer' }}
|
||||
onClick={() =>
|
||||
openExternal('https://balena.io/etcher?ref=etcher_offline_banner')
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb="10px">
|
||||
made with
|
||||
<LoveSvg height="20px" style={{ margin: '0 10px' }} />
|
||||
by
|
||||
<BalenaSvg
|
||||
height="20px"
|
||||
style={{ margin: '0 10px', cursor: 'pointer' }}
|
||||
onClick={() => openExternal('https://balena.io?ref=etcher_success')}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<SafeWebview
|
||||
src="https://www.balena.io/etcher/success-banner?borderTop=false&darkBackground=true"
|
||||
onWebviewShow={setWebviewShowing}
|
||||
style={{
|
||||
display: webviewShowing ? 'flex' : 'none',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '63.8vw',
|
||||
height: '100vh',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export interface FlashAnotherProps {
|
||||
export const FlashAnother = (props: FlashAnotherProps) => {
|
||||
return (
|
||||
<BaseButton primary onClick={props.onClick}>
|
||||
Flash Another
|
||||
Flash another
|
||||
</BaseButton>
|
||||
);
|
||||
};
|
||||
|
@ -16,19 +16,106 @@
|
||||
|
||||
import CircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle.svg';
|
||||
import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/check-circle.svg';
|
||||
import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/times-circle.svg';
|
||||
import * as _ from 'lodash';
|
||||
import outdent from 'outdent';
|
||||
import * as React from 'react';
|
||||
import { Flex, Txt } from 'rendition';
|
||||
import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { progress } from '../../../../shared/messages';
|
||||
import { bytesToMegabytes } from '../../../../shared/units';
|
||||
|
||||
import FlashSvg from '../../../assets/flash.svg';
|
||||
import { getDrives } from '../../models/available-drives';
|
||||
import { resetState } from '../../models/flash-state';
|
||||
import * as selection from '../../models/selection-state';
|
||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||
import { Modal, Table } from '../../styled-components';
|
||||
|
||||
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
|
||||
&&& [data-display='table-head'],
|
||||
&&& [data-display='table-body'] {
|
||||
> [data-display='table-row'] {
|
||||
> [data-display='table-cell'] {
|
||||
&:first-child {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const DoneIcon = (props: {
|
||||
skipped: boolean;
|
||||
color: string;
|
||||
allFailed: boolean;
|
||||
}) => {
|
||||
const svgProps = {
|
||||
width: '28px',
|
||||
fill: props.color,
|
||||
style: {
|
||||
marginTop: '-25px',
|
||||
marginLeft: '13px',
|
||||
zIndex: 1,
|
||||
},
|
||||
};
|
||||
return props.allFailed && !props.skipped ? (
|
||||
<TimesCircleSvg {...svgProps} />
|
||||
) : (
|
||||
<CheckCircleSvg {...svgProps} />
|
||||
);
|
||||
};
|
||||
|
||||
export interface FlashError extends Error {
|
||||
description: string;
|
||||
device: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
function formattedErrors(errors: FlashError[]) {
|
||||
return errors
|
||||
.map((error) => `${error.device}: ${error.message || error.code}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
const columns: Array<TableColumn<FlashError>> = [
|
||||
{
|
||||
field: 'description',
|
||||
label: 'Target',
|
||||
},
|
||||
{
|
||||
field: 'device',
|
||||
label: 'Location',
|
||||
},
|
||||
{
|
||||
field: 'message',
|
||||
label: 'Error',
|
||||
render: (message: string, { code }: FlashError) => {
|
||||
return message ?? code;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function FlashResults({
|
||||
goToMain,
|
||||
image = '',
|
||||
errors,
|
||||
results,
|
||||
skip,
|
||||
...props
|
||||
}: {
|
||||
errors: string;
|
||||
goToMain: () => void;
|
||||
image?: string;
|
||||
errors: FlashError[];
|
||||
skip: boolean;
|
||||
results: {
|
||||
bytesWritten: number;
|
||||
sourceMetadata: {
|
||||
@ -38,8 +125,10 @@ export function FlashResults({
|
||||
averageFlashingSpeed: number;
|
||||
devices: { failed: number; successful: number };
|
||||
};
|
||||
}) {
|
||||
const allDevicesFailed = results.devices.successful === 0;
|
||||
} & FlexProps) {
|
||||
const [showErrorsInfo, setShowErrorsInfo] = React.useState(false);
|
||||
const allFailed = results.devices.successful === 0;
|
||||
const someFailed = results.devices.failed !== 0 || errors.length !== 0;
|
||||
const effectiveSpeed = _.round(
|
||||
bytesToMegabytes(
|
||||
results.sourceMetadata.size /
|
||||
@ -48,44 +137,55 @@ export function FlashResults({
|
||||
1,
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
mr="80px"
|
||||
height="90px"
|
||||
style={{
|
||||
position: 'relative',
|
||||
top: '25px',
|
||||
}}
|
||||
>
|
||||
<Flex alignItems="center">
|
||||
<CheckCircleSvg
|
||||
width="24px"
|
||||
fill={allDevicesFailed ? '#c6c8c9' : '#1ac135'}
|
||||
style={{
|
||||
margin: '0 15px 0 0',
|
||||
}}
|
||||
/>
|
||||
<Txt fontSize={24} color="#fff">
|
||||
<Flex flexDirection="column" {...props}>
|
||||
<Flex alignItems="center" flexDirection="column">
|
||||
<Flex
|
||||
alignItems="center"
|
||||
mt="50px"
|
||||
mb="32px"
|
||||
color="#7e8085"
|
||||
flexDirection="column"
|
||||
>
|
||||
<FlashSvg width="40px" height="40px" className="disabled" />
|
||||
<DoneIcon
|
||||
skipped={skip}
|
||||
allFailed={allFailed}
|
||||
color={allFailed || someFailed ? '#c6c8c9' : '#1ac135'}
|
||||
/>
|
||||
<Txt>{middleEllipsis(image, 24)}</Txt>
|
||||
</Flex>
|
||||
<Txt fontSize={24} color="#fff" mb="17px">
|
||||
Flash Complete!
|
||||
</Txt>
|
||||
{skip ? <Txt color="#7e8085">Validation has been skipped</Txt> : null}
|
||||
</Flex>
|
||||
<Flex flexDirection="column" mr="0" mb="0" ml="40px" color="#7e8085">
|
||||
{Object.entries(results.devices).map(([type, quantity]) => {
|
||||
return quantity ? (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
tooltip={type === 'failed' ? errors : undefined}
|
||||
>
|
||||
<CircleSvg
|
||||
width="14px"
|
||||
fill={type === 'failed' ? '#ff4444' : '#1ac135'}
|
||||
/>
|
||||
<Txt ml={10}>{quantity}</Txt>
|
||||
<Txt ml={10}>{progress[type](quantity)}</Txt>
|
||||
</Flex>
|
||||
) : null;
|
||||
})}
|
||||
{!allDevicesFailed && (
|
||||
<Flex flexDirection="column" color="#7e8085">
|
||||
{results.devices.successful !== 0 ? (
|
||||
<Flex alignItems="center">
|
||||
<CircleSvg width="14px" fill="#1ac135" />
|
||||
<Txt ml="10px" color="#fff">
|
||||
{results.devices.successful}
|
||||
</Txt>
|
||||
<Txt ml="10px">
|
||||
{progress.successful(results.devices.successful)}
|
||||
</Txt>
|
||||
</Flex>
|
||||
) : null}
|
||||
{errors.length !== 0 ? (
|
||||
<Flex alignItems="center">
|
||||
<CircleSvg width="14px" fill="#ff4444" />
|
||||
<Txt ml="10px" color="#fff">
|
||||
{errors.length}
|
||||
</Txt>
|
||||
<Txt ml="10px" tooltip={formattedErrors(errors)}>
|
||||
{progress.failed(errors.length)}
|
||||
</Txt>
|
||||
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
|
||||
more info
|
||||
</Link>
|
||||
</Flex>
|
||||
) : null}
|
||||
{!allFailed && (
|
||||
<Txt
|
||||
fontSize="10px"
|
||||
style={{
|
||||
@ -101,6 +201,36 @@ export function FlashResults({
|
||||
</Txt>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{showErrorsInfo && (
|
||||
<Modal
|
||||
titleElement={
|
||||
<Flex alignItems="baseline" mb={18}>
|
||||
<Txt fontSize={24} align="left">
|
||||
Failed targets
|
||||
</Txt>
|
||||
</Flex>
|
||||
}
|
||||
action="Retry failed targets"
|
||||
cancel={() => setShowErrorsInfo(false)}
|
||||
done={() => {
|
||||
setShowErrorsInfo(false);
|
||||
resetState();
|
||||
getDrives()
|
||||
.map((drive) => {
|
||||
selection.deselectDrive(drive.device);
|
||||
return drive.device;
|
||||
})
|
||||
.filter((driveDevice) =>
|
||||
errors.some((error) => error.device === driveDevice),
|
||||
)
|
||||
.forEach((driveDevice) => selection.selectDrive(driveDevice));
|
||||
goToMain();
|
||||
}}
|
||||
>
|
||||
<ErrorsTable columns={columns} data={errors} />
|
||||
</Modal>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ interface ProgressButtonProps {
|
||||
percentage: number;
|
||||
position: number;
|
||||
disabled: boolean;
|
||||
cancel: () => void;
|
||||
cancel: (type: string) => void;
|
||||
callback: () => void;
|
||||
warning?: boolean;
|
||||
}
|
||||
@ -60,11 +60,14 @@ const colors = {
|
||||
verifying: '#1ac135',
|
||||
} as const;
|
||||
|
||||
const CancelButton = styled((props) => (
|
||||
<Button plain {...props}>
|
||||
Cancel
|
||||
</Button>
|
||||
))`
|
||||
const CancelButton = styled(({ type, onClick, ...props }) => {
|
||||
const status = type === 'verifying' ? 'Skip' : 'Cancel';
|
||||
return (
|
||||
<Button plain onClick={() => onClick(status)} {...props}>
|
||||
{status}
|
||||
</Button>
|
||||
);
|
||||
})`
|
||||
font-weight: 600;
|
||||
&&& {
|
||||
width: auto;
|
||||
@ -75,11 +78,14 @@ const CancelButton = styled((props) => (
|
||||
|
||||
export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
||||
public render() {
|
||||
const percentage = this.props.percentage;
|
||||
const warning = this.props.warning;
|
||||
const { status, position } = fromFlashState({
|
||||
type: this.props.type,
|
||||
percentage,
|
||||
position: this.props.position,
|
||||
percentage: this.props.percentage,
|
||||
});
|
||||
const type = this.props.type || 'default';
|
||||
if (this.props.active) {
|
||||
return (
|
||||
<>
|
||||
@ -96,21 +102,24 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
||||
>
|
||||
<Flex>
|
||||
<Txt color="#fff">{status} </Txt>
|
||||
<Txt color={colors[this.props.type]}>{position}</Txt>
|
||||
<Txt color={colors[type]}>{position}</Txt>
|
||||
</Flex>
|
||||
<CancelButton onClick={this.props.cancel} color="#00aeef" />
|
||||
{type && (
|
||||
<CancelButton
|
||||
type={type}
|
||||
onClick={this.props.cancel}
|
||||
color="#00aeef"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<FlashProgressBar
|
||||
background={colors[this.props.type]}
|
||||
value={this.props.percentage}
|
||||
/>
|
||||
<FlashProgressBar background={colors[type]} value={percentage} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StepButton
|
||||
primary={!this.props.warning}
|
||||
warning={this.props.warning}
|
||||
primary={!warning}
|
||||
warning={warning}
|
||||
onClick={this.props.callback}
|
||||
disabled={this.props.disabled}
|
||||
style={{
|
||||
|
@ -61,7 +61,7 @@ async function getSettingsList(): Promise<Setting[]> {
|
||||
{
|
||||
name: 'updatesEnabled',
|
||||
label: 'Auto-updates enabled',
|
||||
hide: _.includes(['rpm', 'deb'], packageType),
|
||||
hide: ['rpm', 'deb'].includes(packageType),
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -121,9 +121,9 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||
done={() => toggleModal(false)}
|
||||
>
|
||||
<Flex flexDirection="column">
|
||||
{_.map(settingsList, (setting: Setting, i: number) => {
|
||||
{settingsList.map((setting: Setting, i: number) => {
|
||||
return setting.hide ? null : (
|
||||
<Flex key={setting.name}>
|
||||
<Flex key={setting.name} mb={14}>
|
||||
<Checkbox
|
||||
toggle
|
||||
tabIndex={6 + i}
|
||||
@ -135,12 +135,13 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||
);
|
||||
})}
|
||||
<Flex
|
||||
mt={28}
|
||||
mt={18}
|
||||
alignItems="center"
|
||||
color="#00aeef"
|
||||
style={{
|
||||
width: 'fit-content',
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
}}
|
||||
onClick={() =>
|
||||
openExternal(
|
||||
|
@ -116,10 +116,11 @@ const ModalText = styled.p`
|
||||
`;
|
||||
|
||||
function getState() {
|
||||
const image = selectionState.getImage();
|
||||
return {
|
||||
hasImage: selectionState.hasImage(),
|
||||
imageName: selectionState.getImageName(),
|
||||
imageSize: selectionState.getImageSize(),
|
||||
imageName: image?.name,
|
||||
imageSize: image?.size,
|
||||
};
|
||||
}
|
||||
|
||||
@ -213,22 +214,28 @@ interface Flow {
|
||||
}
|
||||
|
||||
const FlowSelector = styled(
|
||||
({ flow, ...props }: { flow: Flow; props?: ButtonProps }) => {
|
||||
return (
|
||||
<StepButton
|
||||
plain
|
||||
onClick={(evt) => flow.onClick(evt)}
|
||||
icon={flow.icon}
|
||||
{...props}
|
||||
>
|
||||
{flow.label}
|
||||
</StepButton>
|
||||
);
|
||||
},
|
||||
({ flow, ...props }: { flow: Flow } & ButtonProps) => (
|
||||
<StepButton
|
||||
plain={!props.primary}
|
||||
primary={props.primary}
|
||||
onClick={(evt: React.MouseEvent<Element, MouseEvent>) =>
|
||||
flow.onClick(evt)
|
||||
}
|
||||
icon={flow.icon}
|
||||
{...props}
|
||||
>
|
||||
{flow.label}
|
||||
</StepButton>
|
||||
),
|
||||
)`
|
||||
border-radius: 24px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
|
||||
:enabled:focus,
|
||||
:enabled:focus svg {
|
||||
color: ${colors.primary.foreground} !important;
|
||||
}
|
||||
|
||||
:enabled:hover {
|
||||
background-color: ${colors.primary.background};
|
||||
color: ${colors.primary.foreground};
|
||||
@ -269,6 +276,7 @@ interface SourceSelectorState {
|
||||
showImageDetails: boolean;
|
||||
showURLSelector: boolean;
|
||||
showDriveSelector: boolean;
|
||||
defaultFlowActive: boolean;
|
||||
}
|
||||
|
||||
export class SourceSelector extends React.Component<
|
||||
@ -285,7 +293,11 @@ export class SourceSelector extends React.Component<
|
||||
showImageDetails: false,
|
||||
showURLSelector: false,
|
||||
showDriveSelector: false,
|
||||
defaultFlowActive: true,
|
||||
};
|
||||
|
||||
// Bind `this` since it's used in an event's callback
|
||||
this.onSelectImage = this.onSelectImage.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
@ -519,7 +531,7 @@ export class SourceSelector extends React.Component<
|
||||
|
||||
private showSelectedImageDetails() {
|
||||
analytics.logEvent('Show selected image tooltip', {
|
||||
imagePath: selectionState.getImagePath(),
|
||||
imagePath: selectionState.getImage()?.path,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
@ -527,6 +539,10 @@ export class SourceSelector extends React.Component<
|
||||
});
|
||||
}
|
||||
|
||||
private setDefaultFlowActive(defaultFlowActive: boolean) {
|
||||
this.setState({ defaultFlowActive });
|
||||
}
|
||||
|
||||
// TODO add a visual change when dragging a file over the selector
|
||||
public render() {
|
||||
const { flashing } = this.props;
|
||||
@ -593,12 +609,15 @@ export class SourceSelector extends React.Component<
|
||||
) : (
|
||||
<>
|
||||
<FlowSelector
|
||||
primary={this.state.defaultFlowActive}
|
||||
key="Flash from file"
|
||||
flow={{
|
||||
onClick: () => this.openImageSelector(),
|
||||
label: 'Flash from file',
|
||||
icon: <FileSvg height="1em" fill="currentColor" />,
|
||||
}}
|
||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||
onMouseLeave={() => this.setDefaultFlowActive(true)}
|
||||
/>
|
||||
<FlowSelector
|
||||
key="Flash from URL"
|
||||
@ -607,6 +626,8 @@ export class SourceSelector extends React.Component<
|
||||
label: 'Flash from URL',
|
||||
icon: <LinkSvg height="1em" fill="currentColor" />,
|
||||
}}
|
||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||
onMouseLeave={() => this.setDefaultFlowActive(true)}
|
||||
/>
|
||||
<FlowSelector
|
||||
key="Clone drive"
|
||||
@ -615,6 +636,8 @@ export class SourceSelector extends React.Component<
|
||||
label: 'Clone drive',
|
||||
icon: <CopySvg height="1em" fill="currentColor" />,
|
||||
}}
|
||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||
onMouseLeave={() => this.setDefaultFlowActive(true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -75,14 +75,25 @@ export function setDevicePaths(devicePaths: string[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export function addFailedDevicePath(devicePath: string) {
|
||||
const failedDevicePathsSet = new Set(
|
||||
store.getState().toJS().failedDevicePaths,
|
||||
export function addFailedDeviceError({
|
||||
device,
|
||||
error,
|
||||
}: {
|
||||
device: sdk.scanner.adapters.DrivelistDrive;
|
||||
error: Error;
|
||||
}) {
|
||||
const failedDeviceErrorsMap = new Map(
|
||||
store.getState().toJS().failedDeviceErrors,
|
||||
);
|
||||
failedDevicePathsSet.add(devicePath);
|
||||
failedDeviceErrorsMap.set(device.device, {
|
||||
description: device.description,
|
||||
device: device.device,
|
||||
devicePath: device.devicePath,
|
||||
...error,
|
||||
});
|
||||
store.dispatch({
|
||||
type: Actions.SET_FAILED_DEVICE_PATHS,
|
||||
data: Array.from(failedDevicePathsSet),
|
||||
type: Actions.SET_FAILED_DEVICE_ERRORS,
|
||||
data: Array.from(failedDeviceErrorsMap),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -188,12 +188,15 @@ function stateObserver(state: typeof DEFAULT_STATE) {
|
||||
} else {
|
||||
selectedDrivesPaths = s.devicePaths;
|
||||
}
|
||||
const failedDevicePaths = s.failedDeviceErrors.map(
|
||||
([devicePath]: [string]) => devicePath,
|
||||
);
|
||||
const newLedsState = {
|
||||
step,
|
||||
sourceDrive: sourceDrivePath,
|
||||
availableDrives: availableDrivesPaths,
|
||||
selectedDrives: selectedDrivesPaths,
|
||||
failedDrives: s.failedDevicePaths,
|
||||
failedDrives: failedDevicePaths,
|
||||
};
|
||||
if (!_.isEqual(newLedsState, ledsState)) {
|
||||
updateLeds(newLedsState);
|
||||
|
@ -72,26 +72,6 @@ export function getImage(): SourceMetadata | undefined {
|
||||
return store.getState().toJS().selection.image;
|
||||
}
|
||||
|
||||
export function getImagePath() {
|
||||
return getImage()?.path;
|
||||
}
|
||||
|
||||
export function getImageSize() {
|
||||
return getImage()?.size;
|
||||
}
|
||||
|
||||
export function getImageName() {
|
||||
return getImage()?.name;
|
||||
}
|
||||
|
||||
export function getImageLogo() {
|
||||
return getImage()?.logo;
|
||||
}
|
||||
|
||||
export function getImageSupportUrl() {
|
||||
return getImage()?.supportUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Check if there is a selected drive
|
||||
*/
|
||||
|
@ -26,6 +26,9 @@ const debug = _debug('etcher:models:settings');
|
||||
|
||||
const JSON_INDENT = 2;
|
||||
|
||||
export const DEFAULT_WIDTH = 800;
|
||||
export const DEFAULT_HEIGHT = 480;
|
||||
|
||||
/**
|
||||
* @summary Userdata directory path
|
||||
* @description
|
||||
@ -35,12 +38,12 @@ const JSON_INDENT = 2;
|
||||
* - `~/Library/Application Support/etcher` on macOS
|
||||
* See https://electronjs.org/docs/api/app#appgetpathname
|
||||
*
|
||||
* NOTE: The ternary is due to this module being loaded both,
|
||||
* Electron's main process and renderer process
|
||||
* NOTE: We use the remote property when this module
|
||||
* is loaded in the Electron's renderer process
|
||||
*/
|
||||
const USER_DATA_DIR = electron.app
|
||||
? electron.app.getPath('userData')
|
||||
: electron.remote.app.getPath('userData');
|
||||
const app = electron.app || electron.remote.app;
|
||||
|
||||
const USER_DATA_DIR = app.getPath('userData');
|
||||
|
||||
const CONFIG_PATH = join(USER_DATA_DIR, 'config.json');
|
||||
|
||||
|
@ -62,7 +62,7 @@ export const DEFAULT_STATE = Immutable.fromJS({
|
||||
},
|
||||
isFlashing: false,
|
||||
devicePaths: [],
|
||||
failedDevicePaths: [],
|
||||
failedDeviceErrors: [],
|
||||
flashResults: {},
|
||||
flashState: {
|
||||
active: 0,
|
||||
@ -79,7 +79,7 @@ export const DEFAULT_STATE = Immutable.fromJS({
|
||||
*/
|
||||
export enum Actions {
|
||||
SET_DEVICE_PATHS,
|
||||
SET_FAILED_DEVICE_PATHS,
|
||||
SET_FAILED_DEVICE_ERRORS,
|
||||
SET_AVAILABLE_TARGETS,
|
||||
SET_FLASH_STATE,
|
||||
RESET_FLASH_STATE,
|
||||
@ -269,7 +269,7 @@ function storeReducer(
|
||||
.set('flashState', DEFAULT_STATE.get('flashState'))
|
||||
.set('flashResults', DEFAULT_STATE.get('flashResults'))
|
||||
.set('devicePaths', DEFAULT_STATE.get('devicePaths'))
|
||||
.set('failedDevicePaths', DEFAULT_STATE.get('failedDevicePaths'))
|
||||
.set('failedDeviceErrors', DEFAULT_STATE.get('failedDeviceErrors'))
|
||||
.set(
|
||||
'lastAverageFlashingSpeed',
|
||||
DEFAULT_STATE.get('lastAverageFlashingSpeed'),
|
||||
@ -295,6 +295,7 @@ function storeReducer(
|
||||
|
||||
_.defaults(action.data, {
|
||||
cancelled: false,
|
||||
skip: false,
|
||||
});
|
||||
|
||||
if (!_.isBoolean(action.data.cancelled)) {
|
||||
@ -335,6 +336,12 @@ function storeReducer(
|
||||
);
|
||||
}
|
||||
|
||||
if (action.data.skip) {
|
||||
return state
|
||||
.set('isFlashing', false)
|
||||
.set('flashResults', Immutable.fromJS(action.data));
|
||||
}
|
||||
|
||||
return state
|
||||
.set('isFlashing', false)
|
||||
.set('flashResults', Immutable.fromJS(action.data))
|
||||
@ -509,8 +516,8 @@ function storeReducer(
|
||||
return state.set('devicePaths', action.data);
|
||||
}
|
||||
|
||||
case Actions.SET_FAILED_DEVICE_PATHS: {
|
||||
return state.set('failedDevicePaths', action.data);
|
||||
case Actions.SET_FAILED_DEVICE_ERRORS: {
|
||||
return state.set('failedDeviceErrors', action.data);
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@ -131,6 +131,7 @@ function writerEnv() {
|
||||
}
|
||||
|
||||
interface FlashResults {
|
||||
skip?: boolean;
|
||||
cancelled?: boolean;
|
||||
}
|
||||
|
||||
@ -140,6 +141,7 @@ async function performWrite(
|
||||
onProgress: sdk.multiWrite.OnProgressFunction,
|
||||
): Promise<{ cancelled?: boolean }> {
|
||||
let cancelled = false;
|
||||
let skip = false;
|
||||
ipc.serve();
|
||||
const {
|
||||
unmountOnSuccess,
|
||||
@ -171,7 +173,7 @@ async function performWrite(
|
||||
|
||||
ipc.server.on('fail', ({ device, error }) => {
|
||||
if (device.devicePath) {
|
||||
flashState.addFailedDevicePath(device.devicePath);
|
||||
flashState.addFailedDeviceError({ device, error });
|
||||
}
|
||||
handleErrorLogging(error, analyticsData);
|
||||
});
|
||||
@ -188,6 +190,11 @@ async function performWrite(
|
||||
cancelled = true;
|
||||
});
|
||||
|
||||
ipc.server.on('skip', () => {
|
||||
terminateServer();
|
||||
skip = true;
|
||||
});
|
||||
|
||||
ipc.server.on('state', onProgress);
|
||||
|
||||
ipc.server.on('ready', (_data, socket) => {
|
||||
@ -213,6 +220,7 @@ async function performWrite(
|
||||
environment: env,
|
||||
});
|
||||
flashResults.cancelled = cancelled || results.cancelled;
|
||||
flashResults.skip = skip;
|
||||
} catch (error) {
|
||||
// This happens when the child is killed using SIGKILL
|
||||
const SIGKILL_EXIT_CODE = 137;
|
||||
@ -229,6 +237,7 @@ async function performWrite(
|
||||
// This likely means the child died halfway through
|
||||
if (
|
||||
!flashResults.cancelled &&
|
||||
!flashResults.skip &&
|
||||
!_.get(flashResults, ['results', 'bytesWritten'])
|
||||
) {
|
||||
reject(
|
||||
@ -286,8 +295,7 @@ export async function flash(
|
||||
} catch (error) {
|
||||
flashState.unsetFlashingFlag({ cancelled: false, errorCode: error.code });
|
||||
windowProgress.clear();
|
||||
let { results } = flashState.getFlashResults();
|
||||
results = results || {};
|
||||
const { results = {} } = flashState.getFlashResults();
|
||||
const eventData = {
|
||||
...analyticsData,
|
||||
errors: results.errors,
|
||||
@ -306,7 +314,7 @@ export async function flash(
|
||||
};
|
||||
analytics.logEvent('Elevation cancelled', eventData);
|
||||
} else {
|
||||
const { results } = flashState.getFlashResults();
|
||||
const { results = {} } = flashState.getFlashResults();
|
||||
const eventData = {
|
||||
...analyticsData,
|
||||
errors: results.errors,
|
||||
@ -322,17 +330,18 @@ export async function flash(
|
||||
/**
|
||||
* @summary Cancel write operation
|
||||
*/
|
||||
export async function cancel() {
|
||||
export async function cancel(type: string) {
|
||||
const status = type.toLowerCase();
|
||||
const drives = selectionState.getSelectedDevices();
|
||||
const analyticsData = {
|
||||
image: selectionState.getImagePath(),
|
||||
image: selectionState.getImage()?.path,
|
||||
drives,
|
||||
driveCount: drives.length,
|
||||
uuid: flashState.getFlashUuid(),
|
||||
flashInstanceUuid: flashState.getFlashUuid(),
|
||||
unmountOnSuccess: await settings.get('unmountOnSuccess'),
|
||||
validateWriteOnSuccess: await settings.get('validateWriteOnSuccess'),
|
||||
status: 'cancel',
|
||||
status,
|
||||
};
|
||||
analytics.logEvent('Cancel', analyticsData);
|
||||
|
||||
@ -342,7 +351,7 @@ export async function cancel() {
|
||||
// @ts-ignore (no Server.sockets in @types/node-ipc)
|
||||
const [socket] = ipc.server.sockets;
|
||||
if (socket !== undefined) {
|
||||
ipc.server.emit(socket, 'cancel');
|
||||
ipc.server.emit(socket, status);
|
||||
}
|
||||
} catch (error) {
|
||||
analytics.logException(error);
|
||||
|
@ -82,14 +82,12 @@ async function flashImageToDrive(
|
||||
try {
|
||||
await imageWriter.flash(image, drives);
|
||||
if (!flashState.wasLastFlashCancelled()) {
|
||||
const flashResults: any = flashState.getFlashResults();
|
||||
const {
|
||||
results = { devices: { successful: 0, failed: 0 } },
|
||||
} = flashState.getFlashResults();
|
||||
notification.send(
|
||||
'Flash complete!',
|
||||
messages.info.flashComplete(
|
||||
basename,
|
||||
drives as any,
|
||||
flashResults.results.devices,
|
||||
),
|
||||
messages.info.flashComplete(basename, drives as any, results.devices),
|
||||
iconPath,
|
||||
);
|
||||
goToSuccess();
|
||||
|
@ -25,7 +25,6 @@ import styled from 'styled-components';
|
||||
|
||||
import FinishPage from '../../components/finish/finish';
|
||||
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
|
||||
import { SafeWebview } from '../../components/safe-webview/safe-webview';
|
||||
import { SettingsModal } from '../../components/settings/settings';
|
||||
import {
|
||||
SourceMetadata,
|
||||
@ -48,6 +47,7 @@ import {
|
||||
import { FlashStep } from './Flash';
|
||||
|
||||
import EtcherSvg from '../../../assets/etcher.svg';
|
||||
import { SafeWebview } from '../../components/safe-webview/safe-webview';
|
||||
|
||||
const Icon = styled(BaseIcon)`
|
||||
margin-right: 20px;
|
||||
@ -132,12 +132,13 @@ export class MainPage extends React.Component<
|
||||
}
|
||||
|
||||
private stateHelper(): MainPageStateFromStore {
|
||||
const image = selectionState.getImage();
|
||||
return {
|
||||
isFlashing: flashState.isFlashing(),
|
||||
hasImage: selectionState.hasImage(),
|
||||
hasDrive: selectionState.hasDrive(),
|
||||
imageLogo: selectionState.getImageLogo(),
|
||||
imageSize: selectionState.getImageSize(),
|
||||
imageLogo: image?.logo,
|
||||
imageSize: image?.size,
|
||||
imageName: getImageBasename(selectionState.getImage()),
|
||||
driveTitle: getDrivesTitle(),
|
||||
driveLabel: getDriveListLabel(),
|
||||
@ -169,7 +170,104 @@ export class MainPage extends React.Component<
|
||||
const notFlashingOrSplitView =
|
||||
!this.state.isFlashing || !this.state.isWebviewShowing;
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{notFlashingOrSplitView && (
|
||||
<>
|
||||
<SourceSelector flashing={this.state.isFlashing} />
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldDriveStepBeDisabled} left />
|
||||
</Flex>
|
||||
<TargetSelector
|
||||
disabled={shouldDriveStepBeDisabled}
|
||||
hasDrive={this.state.hasDrive}
|
||||
flashing={this.state.isFlashing}
|
||||
/>
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldFlashStepBeDisabled} right />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.isFlashing && this.state.isWebviewShowing && (
|
||||
<Flex
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '36.2vw',
|
||||
height: '100vh',
|
||||
zIndex: 1,
|
||||
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
|
||||
}}
|
||||
>
|
||||
<ReducedFlashingInfos
|
||||
imageLogo={this.state.imageLogo}
|
||||
imageName={this.state.imageName}
|
||||
imageSize={
|
||||
typeof this.state.imageSize === 'number'
|
||||
? prettyBytes(this.state.imageSize)
|
||||
: ''
|
||||
}
|
||||
driveTitle={this.state.driveTitle}
|
||||
driveLabel={this.state.driveLabel}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
color: '#fff',
|
||||
left: 35,
|
||||
top: 72,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{this.state.isFlashing && this.state.featuredProjectURL && (
|
||||
<SafeWebview
|
||||
src={this.state.featuredProjectURL}
|
||||
onWebviewShow={(isWebviewShowing: boolean) => {
|
||||
this.setState({ isWebviewShowing });
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '63.8vw',
|
||||
height: '100vh',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FlashStep
|
||||
goToSuccess={() => this.setState({ current: 'success' })}
|
||||
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
||||
isFlashing={this.state.isFlashing}
|
||||
step={state.type}
|
||||
percentage={state.percentage}
|
||||
position={state.position}
|
||||
failed={state.failed}
|
||||
speed={state.speed}
|
||||
eta={state.eta}
|
||||
style={{ zIndex: 1 }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSuccess() {
|
||||
return (
|
||||
<FinishPage
|
||||
goToMain={() => {
|
||||
flashState.resetState();
|
||||
this.setState({ current: 'main' });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<ThemedProvider style={{ height: '100%', width: '100%' }}>
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
@ -213,7 +311,7 @@ export class MainPage extends React.Component<
|
||||
icon={<QuestionCircleSvg height="1em" fill="currentColor" />}
|
||||
onClick={() =>
|
||||
openExternal(
|
||||
selectionState.getImageSupportUrl() ||
|
||||
selectionState.getImage()?.supportUrl ||
|
||||
'https://github.com/balena-io/etcher/blob/master/SUPPORT.md',
|
||||
)
|
||||
}
|
||||
@ -233,117 +331,6 @@ export class MainPage extends React.Component<
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Flex
|
||||
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{notFlashingOrSplitView && (
|
||||
<>
|
||||
<SourceSelector flashing={this.state.isFlashing} />
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldDriveStepBeDisabled} left />
|
||||
</Flex>
|
||||
<TargetSelector
|
||||
disabled={shouldDriveStepBeDisabled}
|
||||
hasDrive={this.state.hasDrive}
|
||||
flashing={this.state.isFlashing}
|
||||
/>
|
||||
<Flex>
|
||||
<StepBorder disabled={shouldFlashStepBeDisabled} right />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.isFlashing && this.state.isWebviewShowing && (
|
||||
<Flex
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '36.2vw',
|
||||
height: '100vh',
|
||||
zIndex: 1,
|
||||
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
|
||||
}}
|
||||
>
|
||||
<ReducedFlashingInfos
|
||||
imageLogo={this.state.imageLogo}
|
||||
imageName={this.state.imageName}
|
||||
imageSize={
|
||||
typeof this.state.imageSize === 'number'
|
||||
? prettyBytes(this.state.imageSize)
|
||||
: ''
|
||||
}
|
||||
driveTitle={this.state.driveTitle}
|
||||
driveLabel={this.state.driveLabel}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
color: '#fff',
|
||||
left: 35,
|
||||
top: 72,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{this.state.isFlashing && this.state.featuredProjectURL && (
|
||||
<SafeWebview
|
||||
src={this.state.featuredProjectURL}
|
||||
onWebviewShow={(isWebviewShowing: boolean) => {
|
||||
this.setState({ isWebviewShowing });
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '63.8vw',
|
||||
height: '100vh',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FlashStep
|
||||
goToSuccess={() => this.setState({ current: 'success' })}
|
||||
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
||||
isFlashing={this.state.isFlashing}
|
||||
step={state.type}
|
||||
percentage={state.percentage}
|
||||
position={state.position}
|
||||
failed={state.failed}
|
||||
speed={state.speed}
|
||||
eta={state.eta}
|
||||
style={{ zIndex: 1 }}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSuccess() {
|
||||
return (
|
||||
<Flex flexDirection="column" alignItems="center" height="100%">
|
||||
<FinishPage
|
||||
goToMain={() => {
|
||||
flashState.resetState();
|
||||
this.setState({ current: 'main' });
|
||||
}}
|
||||
/>
|
||||
<SafeWebview
|
||||
src="https://www.balena.io/etcher/success-banner/"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '320px',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<ThemedProvider style={{ height: '100%', width: '100%' }}>
|
||||
{this.state.current === 'main'
|
||||
? this.renderMain()
|
||||
: this.renderSuccess()}
|
||||
|
@ -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,47 +124,38 @@ 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,
|
||||
header: {
|
||||
height: '50px',
|
||||
},
|
||||
layer: {
|
||||
extend: () => `
|
||||
${defaultTheme.layer.extend()}
|
||||
|
||||
> div:last-child {
|
||||
top: 0;
|
||||
}
|
||||
`,
|
||||
<ModalBase
|
||||
position="top"
|
||||
width="97vw"
|
||||
cancelButtonProps={{
|
||||
style: {
|
||||
marginRight: '20px',
|
||||
border: 'solid 1px #2a506f',
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
height: '87.5vh',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<ModalBase
|
||||
position="top"
|
||||
width="97vw"
|
||||
cancelButtonProps={{
|
||||
style: {
|
||||
marginRight: '20px',
|
||||
border: 'solid 1px #2a506f',
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
height: '87.5vh',
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Provider>
|
||||
<ScrollableFlex flexDirection="column" width="100%" height="90%">
|
||||
{...children}
|
||||
</ScrollableFlex>
|
||||
</ModalBase>
|
||||
);
|
||||
})`
|
||||
> div {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
|
||||
> div:first-child {
|
||||
height: 81%;
|
||||
padding: 24px 30px 0;
|
||||
}
|
||||
|
||||
> h3 {
|
||||
margin: 0;
|
||||
padding: 24px 30px 0;
|
||||
@ -188,11 +169,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 +227,99 @@ export const Alert = styled((props) => (
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface GenericTableProps<T> extends BaseTableProps<T> {
|
||||
refFn: (t: BaseTable<T>) => void;
|
||||
data: T[];
|
||||
checkedRowsNumber?: number;
|
||||
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')};
|
||||
|
||||
${(props) =>
|
||||
props.multipleSelection &&
|
||||
props.checkedRowsNumber !== 0 &&
|
||||
props.checkedRowsNumber !== props.data.length
|
||||
? `
|
||||
font-weight: 600;
|
||||
color: ${colors.primary.foreground};
|
||||
background: ${colors.primary.background};
|
||||
|
||||
::after {
|
||||
content: '–';
|
||||
}
|
||||
`
|
||||
: ''}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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} />;
|
||||
};
|
||||
|
@ -14,6 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { Theme } from 'rendition';
|
||||
|
||||
export const colors = {
|
||||
dark: {
|
||||
foreground: '#fff',
|
||||
@ -67,9 +70,12 @@ export const colors = {
|
||||
|
||||
const font = 'SourceSansPro';
|
||||
|
||||
export const theme = {
|
||||
export const theme = _.merge({}, Theme, {
|
||||
colors,
|
||||
font,
|
||||
header: {
|
||||
height: '40px',
|
||||
},
|
||||
global: {
|
||||
font: {
|
||||
family: font,
|
||||
@ -109,4 +115,11 @@ export const theme = {
|
||||
}
|
||||
`,
|
||||
},
|
||||
};
|
||||
layer: {
|
||||
extend: () => `
|
||||
> div:first-child {
|
||||
background-color: transparent;
|
||||
}
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
@ -122,8 +122,8 @@ interface AutoUpdaterConfig {
|
||||
|
||||
async function createMainWindow() {
|
||||
const fullscreen = Boolean(await settings.get('fullscreen'));
|
||||
const defaultWidth = 800;
|
||||
const defaultHeight = 480;
|
||||
const defaultWidth = settings.DEFAULT_WIDTH;
|
||||
const defaultHeight = settings.DEFAULT_HEIGHT;
|
||||
let width = defaultWidth;
|
||||
let height = defaultHeight;
|
||||
if (fullscreen) {
|
||||
|
@ -55,8 +55,9 @@ function log(message: string) {
|
||||
/**
|
||||
* @summary Terminate the child writer process
|
||||
*/
|
||||
function terminate(exitCode: number) {
|
||||
async function terminate(exitCode: number) {
|
||||
ipc.disconnect(IPC_SERVER_ID);
|
||||
await cleanupTmpFiles(Date.now());
|
||||
process.nextTick(() => {
|
||||
process.exit(exitCode || SUCCESS);
|
||||
});
|
||||
@ -68,17 +69,28 @@ function terminate(exitCode: number) {
|
||||
async function handleError(error: Error) {
|
||||
ipc.of[IPC_SERVER_ID].emit('error', toJSON(error));
|
||||
await delay(DISCONNECT_DELAY);
|
||||
terminate(GENERAL_ERROR);
|
||||
await terminate(GENERAL_ERROR);
|
||||
}
|
||||
|
||||
interface WriteResult {
|
||||
bytesWritten: number;
|
||||
devices: {
|
||||
export interface FlashError extends Error {
|
||||
description: string;
|
||||
device: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface WriteResult {
|
||||
bytesWritten?: number;
|
||||
devices?: {
|
||||
failed: number;
|
||||
successful: number;
|
||||
};
|
||||
errors: Array<Error & { device: string }>;
|
||||
sourceMetadata: sdk.sourceDestination.Metadata;
|
||||
errors: FlashError[];
|
||||
sourceMetadata?: sdk.sourceDestination.Metadata;
|
||||
}
|
||||
|
||||
export interface FlashResults extends WriteResult {
|
||||
skip?: boolean;
|
||||
cancelled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,8 +148,10 @@ async function writeAndValidate({
|
||||
sourceMetadata,
|
||||
};
|
||||
for (const [destination, error] of failures) {
|
||||
const err = error as Error & { device: string };
|
||||
err.device = (destination as sdk.sourceDestination.BlockDevice).device;
|
||||
const err = error as FlashError;
|
||||
const drive = destination as sdk.sourceDestination.BlockDevice;
|
||||
err.device = drive.device;
|
||||
err.description = drive.description;
|
||||
result.errors.push(err);
|
||||
}
|
||||
return result;
|
||||
@ -163,22 +177,22 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
||||
// no flashing information is available, then it will
|
||||
// assume that the child died halfway through.
|
||||
|
||||
process.once('SIGINT', () => {
|
||||
terminate(SUCCESS);
|
||||
process.once('SIGINT', async () => {
|
||||
await terminate(SUCCESS);
|
||||
});
|
||||
|
||||
process.once('SIGTERM', () => {
|
||||
terminate(SUCCESS);
|
||||
process.once('SIGTERM', async () => {
|
||||
await terminate(SUCCESS);
|
||||
});
|
||||
|
||||
// The IPC server failed. Abort.
|
||||
ipc.of[IPC_SERVER_ID].on('error', () => {
|
||||
terminate(SUCCESS);
|
||||
ipc.of[IPC_SERVER_ID].on('error', async () => {
|
||||
await terminate(SUCCESS);
|
||||
});
|
||||
|
||||
// The IPC server was disconnected. Abort.
|
||||
ipc.of[IPC_SERVER_ID].on('disconnect', () => {
|
||||
terminate(SUCCESS);
|
||||
ipc.of[IPC_SERVER_ID].on('disconnect', async () => {
|
||||
await terminate(SUCCESS);
|
||||
});
|
||||
|
||||
ipc.of[IPC_SERVER_ID].on('write', async (options: WriteOptions) => {
|
||||
@ -203,11 +217,20 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
||||
log('Abort');
|
||||
ipc.of[IPC_SERVER_ID].emit('abort');
|
||||
await delay(DISCONNECT_DELAY);
|
||||
terminate(exitCode);
|
||||
await terminate(exitCode);
|
||||
};
|
||||
|
||||
const onSkip = async () => {
|
||||
log('Skip validation');
|
||||
ipc.of[IPC_SERVER_ID].emit('skip');
|
||||
await delay(DISCONNECT_DELAY);
|
||||
await terminate(exitCode);
|
||||
};
|
||||
|
||||
ipc.of[IPC_SERVER_ID].on('cancel', onAbort);
|
||||
|
||||
ipc.of[IPC_SERVER_ID].on('skip', onSkip);
|
||||
|
||||
/**
|
||||
* @summary Failure handler (non-fatal errors)
|
||||
* @param {SourceDestination} destination - destination
|
||||
@ -275,7 +298,7 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
||||
});
|
||||
ipc.of[IPC_SERVER_ID].emit('done', { results });
|
||||
await delay(DISCONNECT_DELAY);
|
||||
terminate(exitCode);
|
||||
await terminate(exitCode);
|
||||
} catch (error) {
|
||||
log(`Error: ${error.message}`);
|
||||
exitCode = GENERAL_ERROR;
|
||||
|
@ -73,9 +73,7 @@ export function isSourceDrive(
|
||||
): boolean {
|
||||
if (selection) {
|
||||
if (selection.drive) {
|
||||
const sourcePath = selection.drive.devicePath || selection.drive.device;
|
||||
const drivePath = drive.devicePath || drive.device;
|
||||
return pathIsInside(sourcePath, drivePath);
|
||||
return selection.drive.device === drive.device;
|
||||
}
|
||||
if (selection.path) {
|
||||
return sourceIsInsideDrive(selection.path, drive);
|
||||
|
157
npm-shrinkwrap.json
generated
157
npm-shrinkwrap.json
generated
@ -1570,34 +1570,32 @@
|
||||
}
|
||||
},
|
||||
"@react-google-maps/api": {
|
||||
"version": "1.9.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-1.9.12.tgz",
|
||||
"integrity": "sha512-YpYZOMduxiQIt8+njdffoqD4fYdOugudoafnAD1N+mEUrVnFlslUPMQ+gOJwuYdlkTAR5NZUbCt80LJWEN+ZnA==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-1.10.1.tgz",
|
||||
"integrity": "sha512-hb8urUcwZw99Cu3yQnZWUbXjR1Ym/8C21kSX6B02I29l6DXNxDbJ5Jo/T5swhnizPKY7TNhR1oTctC/HY7SQWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@react-google-maps/infobox": "1.9.11",
|
||||
"@react-google-maps/marker-clusterer": "1.9.11",
|
||||
"acorn": "7.4.0",
|
||||
"acorn-jsx": "^5.2.0",
|
||||
"@react-google-maps/infobox": "1.10.0",
|
||||
"@react-google-maps/marker-clusterer": "1.10.0",
|
||||
"invariant": "2.2.4"
|
||||
}
|
||||
},
|
||||
"@react-google-maps/infobox": {
|
||||
"version": "1.9.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-1.9.11.tgz",
|
||||
"integrity": "sha512-22ewm+OpOh69ikypG29idsdRz2OWeFsN+8zvYBzSETxKP782rmUGqhSIvXXmHa8TOcktm7EaEqOWWvZwaxymag==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-1.10.0.tgz",
|
||||
"integrity": "sha512-MhT2nMmjeG7TCxRv/JdylDyNd/n66ggSQQhTWVjJJTtdB/xqd0T8BHCkBWDN9uF0i0yCZzMFl2P2Y1zJ+xppBg==",
|
||||
"dev": true
|
||||
},
|
||||
"@react-google-maps/marker-clusterer": {
|
||||
"version": "1.9.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-1.9.11.tgz",
|
||||
"integrity": "sha512-yIABKlkORju131efXUZs/tL7FCK9IXtvy2M9SQRZy/mwgoOIYeoJlPPaBjn81DQqZLRj6AdAocydk+MnjWqFiQ==",
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-1.10.0.tgz",
|
||||
"integrity": "sha512-3GLVgeXNStVcdiLMxzi3cBjr32ctlexLPPGQguwcYd6yPLaCcnVCwyzhV68KvL00xqOAD1c3aABV9EGgY8u6Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"@rjsf/core": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-2.3.0.tgz",
|
||||
"integrity": "sha512-OZKYHt9tjKhzOH4CvsPiCwepuIacqI++cNmnL2fsxh1IF+uEWGlo3NLDWhhSaBbOv9jps6a5YQcLbLtjNuSwug==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz",
|
||||
"integrity": "sha512-8zlydBkGldOxGXFEwNGFa1gzTxpcxaYn7ofegcu8XHJ7IKMCfpnU3ABg+H3eml1KZCX3FODmj1tHFJKuTmfynw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime-corejs2": "^7.8.7",
|
||||
@ -2180,9 +2178,9 @@
|
||||
}
|
||||
},
|
||||
"@types/react-native": {
|
||||
"version": "0.63.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.9.tgz",
|
||||
"integrity": "sha512-6ec/z9zjAkFH3rD1RYqbrA/Lj+jux6bumWCte4yRy3leyelTdqtmOd2Ph+86IXQQzsIArEMBwmraAbNQ0J3UAA==",
|
||||
"version": "0.63.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.18.tgz",
|
||||
"integrity": "sha512-WwEWqmHiqFn61M1FZR/+frj+E8e2o8i5cPqu9mjbjtZS/gBfCKVESF2ai/KAlaQECkkWkx/nMJeCc5eHMmLQgw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
@ -2237,9 +2235,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/styled-components": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.2.tgz",
|
||||
"integrity": "sha512-HNocYLfrsnNNm8NTS/W53OERSjRA8dx5Bn6wBd2rXXwt4Z3s+oqvY6/PbVt3e6sgtzI63GX//WiWiRhWur08qQ==",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.3.tgz",
|
||||
"integrity": "sha512-HGpirof3WOhiX17lb61Q/tpgqn48jxO8EfZkdJ8ueYqwLbK2AHQe/G08DasdA2IdKnmwOIP1s9X2bopxKXgjRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "*",
|
||||
@ -2692,18 +2690,6 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
|
||||
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
|
||||
"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
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
@ -5281,6 +5267,12 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
|
||||
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
@ -5496,15 +5488,6 @@
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"detab": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz",
|
||||
"integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"repeat-string": "^1.5.4"
|
||||
}
|
||||
},
|
||||
"detect-file": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
|
||||
@ -8939,9 +8922,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json-e": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-e/-/json-e-4.1.0.tgz",
|
||||
"integrity": "sha512-Jb8kMB1lICgjAAppv+q0EFFovOPdjE3htb7pt9+uE2j3J1W5ZCuBOmAdGi0OUetCZ4wqSO6qT/Np36XDRjHH7w==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/json-e/-/json-e-4.3.0.tgz",
|
||||
"integrity": "sha512-E3zcmx6pHsBgQ4ZztQNG4OAZHreBZfGBrg68kv9nGOkRqAdKfs792asP/wp9Fayfx1THDiHKYStqWJj/N7Bb9A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1"
|
||||
@ -9749,18 +9732,15 @@
|
||||
}
|
||||
},
|
||||
"mdast-util-to-hast": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-9.1.0.tgz",
|
||||
"integrity": "sha512-Akl2Vi9y9cSdr19/Dfu58PVwifPXuFt1IrHe7l+Crme1KvgUT+5z+cHLVcQVGCiNTZZcdqjnuv9vPkGsqWytWA==",
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-9.1.1.tgz",
|
||||
"integrity": "sha512-vpMWKFKM2mnle+YbNgDXxx95vv0CoLU0v/l3F5oFAG5DV7qwkZVWA206LsAdOnEVyf5vQcLnb3cWJywu7mUxsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/mdast": "^3.0.0",
|
||||
"@types/unist": "^2.0.3",
|
||||
"collapse-white-space": "^1.0.0",
|
||||
"detab": "^2.0.0",
|
||||
"mdast-util-definitions": "^3.0.0",
|
||||
"mdurl": "^1.0.0",
|
||||
"trim-lines": "^1.0.0",
|
||||
"unist-builder": "^2.0.0",
|
||||
"unist-util-generated": "^1.0.0",
|
||||
"unist-util-position": "^3.0.0",
|
||||
@ -9842,9 +9822,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-random-string": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.2.0.tgz",
|
||||
"integrity": "sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.3.0.tgz",
|
||||
"integrity": "sha512-teWAwfMb1d6brahYyKqcBEb5Yp8PJPvPOdOonXDnvaKOTmKDFNVE8E3Y2XQuzjNV/3XMwHbrX9fHWvrhRKt4Gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.8.1"
|
||||
@ -11897,9 +11877,9 @@
|
||||
}
|
||||
},
|
||||
"polished": {
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/polished/-/polished-3.6.5.tgz",
|
||||
"integrity": "sha512-VwhC9MlhW7O5dg/z7k32dabcAFW1VI2+7fSe8cE/kXcfL7mVdoa5UxciYGW2sJU78ldDLT6+ROEKIZKFNTnUXQ==",
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/polished/-/polished-3.6.6.tgz",
|
||||
"integrity": "sha512-yiB2ims2DZPem0kCD6V0wnhcVGFEhNh0Iw0axNpKU+oSAgFt6yx6HxIT23Qg0WWvgS379cS35zT4AOyZZRzpQQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
@ -12511,9 +12491,9 @@
|
||||
}
|
||||
},
|
||||
"react-notifications-component": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-notifications-component/-/react-notifications-component-2.4.0.tgz",
|
||||
"integrity": "sha512-0IhtgqAmsKSyjY1wBUxciUVXiYGRr5BRdn67pYDlkqq9ORF98NZekpG7/MNX0BzzfGvt9Wg7rFhT1BtwOvvLLg==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-notifications-component/-/react-notifications-component-2.4.1.tgz",
|
||||
"integrity": "sha512-RloHzm15egnuPihf8PvldIEvPQoT9+5BE9UxCNTt+GfsWeI3SEZKyaX9mq90v899boqteLiOI736Zd4tXtl7Tg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prop-types": "^15.6.2"
|
||||
@ -12660,6 +12640,21 @@
|
||||
"integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==",
|
||||
"dev": true
|
||||
},
|
||||
"regexp-match-indices": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz",
|
||||
"integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regexp-tree": "^0.1.11"
|
||||
}
|
||||
},
|
||||
"regexp-tree": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.21.tgz",
|
||||
"integrity": "sha512-kUUXjX4AnqnR8KRTCrayAo9PzYMRKmVoGgaz2tBuz0MF3g1ZbGebmtW0yFHfFK9CmBjQKeYIgoL22pFLBJY7sw==",
|
||||
"dev": true
|
||||
},
|
||||
"regexpu-core": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
|
||||
@ -12827,9 +12822,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"rendition": {
|
||||
"version": "18.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rendition/-/rendition-18.4.1.tgz",
|
||||
"integrity": "sha512-mV/0p+M8XR/Xa/ZFzgflZPHelpuONiTSa/CMMuHkmXR7vhF7tB2ORxLRc/DbymmdN6cWQwXAyA81t9TDAOhgVQ==",
|
||||
"version": "18.8.3",
|
||||
"resolved": "https://registry.npmjs.org/rendition/-/rendition-18.8.3.tgz",
|
||||
"integrity": "sha512-kDuXFheXY9KlSvIMdB4Er2OeAnwgj9aya5Xu43hwpXxC4KlFlNKqQNmcOvKLc/Fk9dyw04TKOr1SbXyM148yRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
||||
@ -12855,6 +12850,7 @@
|
||||
"color": "^3.1.2",
|
||||
"color-hash": "^1.0.3",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"date-fns": "^2.16.1",
|
||||
"grommet": "^2.14.0",
|
||||
"hast-util-sanitize": "^3.0.0",
|
||||
"json-e": "^4.1.0",
|
||||
@ -12869,6 +12865,7 @@
|
||||
"react-simplemde-editor": "^4.1.1",
|
||||
"recompose": "0.26.0",
|
||||
"regex-parser": "^2.2.7",
|
||||
"regexp-match-indices": "^1.0.2",
|
||||
"rehype-raw": "^4.0.2",
|
||||
"rehype-react": "^6.1.0",
|
||||
"rehype-sanitize": "^3.0.1",
|
||||
@ -12885,9 +12882,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz",
|
||||
"integrity": "sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==",
|
||||
"version": "13.13.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.20.tgz",
|
||||
"integrity": "sha512-1kx55tU3AvGX2Cjk2W4GMBxbgIz892V+X10S2gUreIAq8qCWgaQH+tZBOWc0bi2BKFhQt+CX0BTx28V9QPNa+A==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
@ -14745,12 +14742,6 @@
|
||||
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=",
|
||||
"dev": true
|
||||
},
|
||||
"trim-lines": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz",
|
||||
"integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==",
|
||||
"dev": true
|
||||
},
|
||||
"trim-trailing-lines": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
|
||||
@ -15035,9 +15026,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.10.2",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.2.tgz",
|
||||
"integrity": "sha512-GXCYNwqoo0MbLARghYjxVBxDCnU0tLqN7IPLdHHbibCb1NI5zBkU2EPcy/GaVxc0BtTjqyGXJCINe6JMR2Dpow==",
|
||||
"version": "3.10.4",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.4.tgz",
|
||||
"integrity": "sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw==",
|
||||
"dev": true
|
||||
},
|
||||
"unbzip2-stream": {
|
||||
@ -16466,9 +16457,9 @@
|
||||
}
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz",
|
||||
"integrity": "sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz",
|
||||
"integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
@ -16660,9 +16651,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"xterm": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.8.1.tgz",
|
||||
"integrity": "sha512-ax91ny4tI5eklqIfH79OUSGE2PUX2rGbwONmB6DfqpyhSZO8/cf++sqiaMWEVCMjACyMfnISW7C3gGMoNvNolQ==",
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.9.0.tgz",
|
||||
"integrity": "sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw==",
|
||||
"dev": true
|
||||
},
|
||||
"xterm-addon-fit": {
|
||||
@ -16774,4 +16765,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"redux": "^4.0.5",
|
||||
"rendition": "^18.4.1",
|
||||
"rendition": "^18.8.3",
|
||||
"resin-corvus": "^2.0.5",
|
||||
"semver": "^7.3.2",
|
||||
"simple-progress-webpack-plugin": "^1.1.2",
|
||||
|
@ -393,6 +393,7 @@ describe('Model: flashState', function () {
|
||||
|
||||
expect(flashResults).to.deep.equal({
|
||||
cancelled: false,
|
||||
skip: false,
|
||||
sourceChecksum: '1234',
|
||||
});
|
||||
});
|
||||
|
@ -33,26 +33,6 @@ describe('Model: selectionState', function () {
|
||||
expect(selectionState.getImage()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImagePath() should return undefined', function () {
|
||||
expect(selectionState.getImagePath()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageSize() should return undefined', function () {
|
||||
expect(selectionState.getImageSize()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageName() should return undefined', function () {
|
||||
expect(selectionState.getImageName()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageLogo() should return undefined', function () {
|
||||
expect(selectionState.getImageLogo()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageSupportUrl() should return undefined', function () {
|
||||
expect(selectionState.getImageSupportUrl()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('hasDrive() should return false', function () {
|
||||
const hasDrive = selectionState.hasDrive();
|
||||
expect(hasDrive).to.be.false;
|
||||
@ -379,43 +359,6 @@ describe('Model: selectionState', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getImagePath()', function () {
|
||||
it('should return the image path', function () {
|
||||
const imagePath = selectionState.getImagePath();
|
||||
expect(imagePath).to.equal('foo.img');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getImageSize()', function () {
|
||||
it('should return the image size', function () {
|
||||
const imageSize = selectionState.getImageSize();
|
||||
expect(imageSize).to.equal(999999999);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getImageName()', function () {
|
||||
it('should return the image name', function () {
|
||||
const imageName = selectionState.getImageName();
|
||||
expect(imageName).to.equal('Raspbian');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getImageLogo()', function () {
|
||||
it('should return the image logo', function () {
|
||||
const imageLogo = selectionState.getImageLogo();
|
||||
expect(imageLogo).to.equal(
|
||||
'<svg><text fill="red">Raspbian</text></svg>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getImageSupportUrl()', function () {
|
||||
it('should return the image support url', function () {
|
||||
const imageSupportUrl = selectionState.getImageSupportUrl();
|
||||
expect(imageSupportUrl).to.equal('https://www.raspbian.org/forums/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.hasImage()', function () {
|
||||
it('should return true', function () {
|
||||
const hasImage = selectionState.hasImage();
|
||||
@ -435,9 +378,9 @@ describe('Model: selectionState', function () {
|
||||
SourceType: File,
|
||||
});
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('bar.img');
|
||||
const imageSize = selectionState.getImageSize();
|
||||
const imageSize = selectionState.getImage()?.size;
|
||||
expect(imageSize).to.equal(999999999);
|
||||
});
|
||||
});
|
||||
@ -446,9 +389,9 @@ describe('Model: selectionState', function () {
|
||||
it('should clear the image', function () {
|
||||
selectionState.deselectImage();
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.be.undefined;
|
||||
const imageSize = selectionState.getImageSize();
|
||||
const imageSize = selectionState.getImage()?.size;
|
||||
expect(imageSize).to.be.undefined;
|
||||
});
|
||||
});
|
||||
@ -472,9 +415,9 @@ describe('Model: selectionState', function () {
|
||||
it('should be able to set an image', function () {
|
||||
selectionState.selectSource(image);
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('foo.img');
|
||||
const imageSize = selectionState.getImageSize();
|
||||
const imageSize = selectionState.getImage()?.size;
|
||||
expect(imageSize).to.equal(999999999);
|
||||
});
|
||||
|
||||
@ -485,7 +428,7 @@ describe('Model: selectionState', function () {
|
||||
archiveExtension: 'zip',
|
||||
});
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('foo.zip');
|
||||
});
|
||||
|
||||
@ -496,7 +439,7 @@ describe('Model: selectionState', function () {
|
||||
archiveExtension: 'xz',
|
||||
});
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('foo.xz');
|
||||
});
|
||||
|
||||
@ -507,7 +450,7 @@ describe('Model: selectionState', function () {
|
||||
archiveExtension: 'gz',
|
||||
});
|
||||
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('something.linux-x86-64.gz');
|
||||
});
|
||||
|
||||
@ -675,12 +618,12 @@ describe('Model: selectionState', function () {
|
||||
});
|
||||
|
||||
it('getImagePath() should return undefined', function () {
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageSize() should return undefined', function () {
|
||||
const imageSize = selectionState.getImageSize();
|
||||
const imageSize = selectionState.getImage()?.size;
|
||||
expect(imageSize).to.be.undefined;
|
||||
});
|
||||
|
||||
@ -700,12 +643,12 @@ describe('Model: selectionState', function () {
|
||||
});
|
||||
|
||||
it('getImagePath() should return the image path', function () {
|
||||
const imagePath = selectionState.getImagePath();
|
||||
const imagePath = selectionState.getImage()?.path;
|
||||
expect(imagePath).to.equal('foo.img');
|
||||
});
|
||||
|
||||
it('getImageSize() should return the image size', function () {
|
||||
const imageSize = selectionState.getImageSize();
|
||||
const imageSize = selectionState.getImage()?.size;
|
||||
expect(imageSize).to.equal(999999999);
|
||||
});
|
||||
|
||||
|
@ -700,11 +700,6 @@ describe('Shared: DriveConstraints', function () {
|
||||
});
|
||||
|
||||
it('should return false if the drive is not large enough and is a source drive', function () {
|
||||
console.log('YAYYY', {
|
||||
...image,
|
||||
path: path.join(this.mountpoint, 'rpi.img'),
|
||||
size: 5000000000,
|
||||
});
|
||||
expect(
|
||||
constraints.isDriveValid(this.drive, {
|
||||
...image,
|
||||
|
Loading…
x
Reference in New Issue
Block a user