Merge pull request #3284 from balena-io/105

105
This commit is contained in:
bulldozer-balena[bot] 2020-08-26 11:11:16 +00:00 committed by GitHub
commit 12cd8a39c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 237 additions and 281 deletions

View File

@ -1,73 +0,0 @@
/*
* Copyright 2016 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import * as settings from '../../models/settings';
import * as analytics from '../../modules/analytics';
import { SafeWebview } from '../safe-webview/safe-webview';
interface FeaturedProjectProps {
shouldShow: boolean;
onWebviewShow: (isWebviewShowing: boolean) => void;
style?: React.CSSProperties;
}
interface FeaturedProjectState {
endpoint: string | null;
show: boolean;
}
export class FeaturedProject extends React.Component<
FeaturedProjectProps,
FeaturedProjectState
> {
constructor(props: FeaturedProjectProps) {
super(props);
this.state = {
endpoint: null,
show: false,
};
}
public async componentDidMount() {
try {
const url = new URL(
(await settings.get('featuredProjectEndpoint')) ||
'https://assets.balena.io/etcher-featured/index.html',
);
url.searchParams.append('borderRight', 'false');
url.searchParams.append('darkBackground', 'true');
this.setState({ endpoint: url.toString() });
} catch (error) {
analytics.logException(error);
}
}
public render() {
const { style = {} } = this.props;
return this.state.endpoint ? (
<SafeWebview
src={this.state.endpoint}
style={{
display: this.state.show ? 'block' : 'none',
...style,
}}
{...this.props}
></SafeWebview>
) : null;
}
}

View File

@ -58,8 +58,6 @@ const API_VERSION = '2';
interface SafeWebviewProps { interface SafeWebviewProps {
// The website source URL // The website source URL
src: string; src: string;
// @summary Refresh the webview
refreshNow?: boolean;
// Webview lifecycle event // Webview lifecycle event
onWebviewShow?: (isWebviewShowing: boolean) => void; onWebviewShow?: (isWebviewShowing: boolean) => void;
style?: React.CSSProperties; style?: React.CSSProperties;

View File

@ -30,6 +30,7 @@ import {
Txt, Txt,
Card as BaseCard, Card as BaseCard,
Input, Input,
Spinner,
} from 'rendition'; } from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
@ -137,8 +138,9 @@ const URLSelector = ({
<Modal <Modal
cancel={cancel} cancel={cancel}
primaryButtonProps={{ primaryButtonProps={{
className: loading || !imageURL ? 'disabled' : '', disabled: loading || !imageURL,
}} }}
action={loading ? <Spinner /> : 'OK'}
done={async () => { done={async () => {
setLoading(true); setLoading(true);
const urlStrings = recentImages.map((url: URL) => url.href); const urlStrings = recentImages.map((url: URL) => url.href);
@ -288,7 +290,7 @@ export class SourceSelector extends React.Component<
await this.selectImageByPath({ await this.selectImageByPath({
imagePath, imagePath,
SourceType: isURL ? sourceDestination.Http : sourceDestination.File, SourceType: isURL ? sourceDestination.Http : sourceDestination.File,
}); }).promise;
} }
private reselectImage() { private reselectImage() {
@ -344,12 +346,24 @@ export class SourceSelector extends React.Component<
} }
} }
private async selectImageByPath({ imagePath, SourceType }: SourceOptions) { private selectImageByPath({
imagePath,
SourceType,
}: SourceOptions): { promise: Promise<void>; cancel: () => void } {
let cancelled = false;
return {
cancel: () => {
cancelled = true;
},
promise: (async () => {
try { try {
imagePath = await replaceWindowsNetworkDriveLetter(imagePath); imagePath = await replaceWindowsNetworkDriveLetter(imagePath);
} catch (error) { } catch (error) {
analytics.logException(error); analytics.logException(error);
} }
if (cancelled) {
return;
}
let source; let source;
if (SourceType === sourceDestination.File) { if (SourceType === sourceDestination.File) {
@ -375,13 +389,22 @@ export class SourceSelector extends React.Component<
try { try {
const innerSource = await source.getInnerSource(); const innerSource = await source.getInnerSource();
if (cancelled) {
return;
}
const metadata = (await innerSource.getMetadata()) as sourceDestination.Metadata & { const metadata = (await innerSource.getMetadata()) as sourceDestination.Metadata & {
hasMBR: boolean; hasMBR: boolean;
partitions: MBRPartition[] | GPTPartition[]; partitions: MBRPartition[] | GPTPartition[];
path: string; path: string;
extension: string; extension: string;
}; };
if (cancelled) {
return;
}
const partitionTable = await innerSource.getPartitionTable(); const partitionTable = await innerSource.getPartitionTable();
if (cancelled) {
return;
}
if (partitionTable) { if (partitionTable) {
metadata.hasMBR = true; metadata.hasMBR = true;
metadata.partitions = partitionTable.partitions; metadata.partitions = partitionTable.partitions;
@ -412,6 +435,8 @@ export class SourceSelector extends React.Component<
// Noop // Noop
} }
} }
})(),
};
} }
private async openImageSelector() { private async openImageSelector() {
@ -425,22 +450,22 @@ export class SourceSelector extends React.Component<
analytics.logEvent('Image selector closed'); analytics.logEvent('Image selector closed');
return; return;
} }
this.selectImageByPath({ await this.selectImageByPath({
imagePath, imagePath,
SourceType: sourceDestination.File, SourceType: sourceDestination.File,
}); }).promise;
} catch (error) { } catch (error) {
exceptionReporter.report(error); exceptionReporter.report(error);
} }
} }
private onDrop(event: React.DragEvent<HTMLDivElement>) { private async onDrop(event: React.DragEvent<HTMLDivElement>) {
const [file] = event.dataTransfer.files; const [file] = event.dataTransfer.files;
if (file) { if (file) {
this.selectImageByPath({ await this.selectImageByPath({
imagePath: file.path, imagePath: file.path,
SourceType: sourceDestination.File, SourceType: sourceDestination.File,
}); }).promise;
} }
} }
@ -484,6 +509,9 @@ export class SourceSelector extends React.Component<
const imageName = selectionState.getImageName(); const imageName = selectionState.getImageName();
const imageSize = selectionState.getImageSize(); const imageSize = selectionState.getImageSize();
const imageLogo = selectionState.getImageLogo(); const imageLogo = selectionState.getImageLogo();
let cancelURLSelection = () => {
// noop
};
return ( return (
<> <>
@ -585,6 +613,7 @@ export class SourceSelector extends React.Component<
{showURLSelector && ( {showURLSelector && (
<URLSelector <URLSelector
cancel={() => { cancel={() => {
cancelURLSelection();
this.setState({ this.setState({
showURLSelector: false, showURLSelector: false,
}); });
@ -594,16 +623,17 @@ export class SourceSelector extends React.Component<
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imageURL) { if (!imageURL) {
analytics.logEvent('URL selector closed'); analytics.logEvent('URL selector closed');
this.setState({ } else {
showURLSelector: false, let promise;
}); ({
return; promise,
} cancel: cancelURLSelection,
} = this.selectImageByPath({
await this.selectImageByPath({
imagePath: imageURL, imagePath: imageURL,
SourceType: sourceDestination.Http, SourceType: sourceDestination.Http,
}); }));
await promise;
}
this.setState({ this.setState({
showURLSelector: false, showURLSelector: false,
}); });

View File

@ -49,11 +49,6 @@ body {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
/* Allow window to be dragged from header */
#app-header {
-webkit-app-region: drag;
}
/* Prevent blue outline */ /* Prevent blue outline */
a:focus, a:focus,
input:focus, input:focus,

View File

@ -146,7 +146,6 @@ interface FlashStepProps {
goToSuccess: () => void; goToSuccess: () => void;
source: SourceOptions; source: SourceOptions;
isFlashing: boolean; isFlashing: boolean;
isWebviewShowing: boolean;
style?: React.CSSProperties; style?: React.CSSProperties;
// TODO: factorize // TODO: factorize
step: 'decompressing' | 'flashing' | 'verifying'; step: 'decompressing' | 'flashing' | 'verifying';

View File

@ -24,7 +24,6 @@ import * as React from 'react';
import { Flex } from 'rendition'; import { Flex } from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
import { FeaturedProject } from '../../components/featured-project/featured-project';
import FinishPage from '../../components/finish/finish'; import FinishPage from '../../components/finish/finish';
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos'; import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import { SafeWebview } from '../../components/safe-webview/safe-webview'; import { SafeWebview } from '../../components/safe-webview/safe-webview';
@ -114,6 +113,7 @@ interface MainPageState {
isWebviewShowing: boolean; isWebviewShowing: boolean;
hideSettings: boolean; hideSettings: boolean;
source: SourceOptions; source: SourceOptions;
featuredProjectURL?: string;
} }
export class MainPage extends React.Component< export class MainPage extends React.Component<
@ -147,10 +147,21 @@ export class MainPage extends React.Component<
}; };
} }
public componentDidMount() { private async getFeaturedProjectURL() {
const url = new URL(
(await settings.get('featuredProjectEndpoint')) ||
'https://assets.balena.io/etcher-featured/index.html',
);
url.searchParams.append('borderRight', 'false');
url.searchParams.append('darkBackground', 'true');
return url.toString();
}
public async componentDidMount() {
observe(() => { observe(() => {
this.setState(this.stateHelper()); this.setState(this.stateHelper());
}); });
this.setState({ featuredProjectURL: await this.getFeaturedProjectURL() });
} }
private renderMain() { private renderMain() {
@ -163,17 +174,19 @@ export class MainPage extends React.Component<
return ( return (
<> <>
<Flex <Flex
id="app-header" justifyContent="space-between"
justifyContent="center" alignItems="center"
paddingTop="14px"
style={{ style={{
width: '100%', // Allow window to be dragged from header
height: '50px', // @ts-ignore
padding: '13px 14px', '-webkit-app-region': 'drag',
textAlign: 'center',
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
}} }}
> >
<Flex width="100%" />
<Flex width="100%" alignItems="center" justifyContent="center">
<EtcherSvg <EtcherSvg
width="123px" width="123px"
height="22px" height="22px"
@ -185,19 +198,18 @@ export class MainPage extends React.Component<
} }
tabIndex={100} tabIndex={100}
/> />
</Flex>
<Flex <Flex width="100%" alignItems="center" justifyContent="flex-end">
style={{
float: 'right',
position: 'absolute',
right: 0,
}}
>
<Icon <Icon
icon={<CogSvg height="1em" fill="currentColor" />} icon={<CogSvg height="1em" fill="currentColor" />}
plain plain
tabIndex={5} tabIndex={5}
onClick={() => this.setState({ hideSettings: false })} onClick={() => this.setState({ hideSettings: false })}
style={{
// Make touch events click instead of dragging
'-webkit-app-region': 'no-drag',
}}
/> />
{!settings.getSync('disableExternalLinks') && ( {!settings.getSync('disableExternalLinks') && (
<Icon <Icon
@ -209,6 +221,10 @@ export class MainPage extends React.Component<
) )
} }
tabIndex={6} tabIndex={6}
style={{
// Make touch events click instead of dragging
'-webkit-app-region': 'no-drag',
}}
/> />
)} )}
</Flex> </Flex>
@ -226,36 +242,28 @@ export class MainPage extends React.Component<
justifyContent="space-between" justifyContent="space-between"
> >
{notFlashingOrSplitView && ( {notFlashingOrSplitView && (
<>
<SourceSelector <SourceSelector
flashing={this.state.isFlashing} flashing={this.state.isFlashing}
afterSelected={(source: SourceOptions) => afterSelected={(source: SourceOptions) =>
this.setState({ source }) this.setState({ source })
} }
/> />
)}
{notFlashingOrSplitView && (
<Flex> <Flex>
<StepBorder disabled={shouldDriveStepBeDisabled} left /> <StepBorder disabled={shouldDriveStepBeDisabled} left />
</Flex> </Flex>
)}
{notFlashingOrSplitView && (
<DriveSelector <DriveSelector
disabled={shouldDriveStepBeDisabled} disabled={shouldDriveStepBeDisabled}
hasDrive={this.state.hasDrive} hasDrive={this.state.hasDrive}
flashing={this.state.isFlashing} flashing={this.state.isFlashing}
/> />
)}
{notFlashingOrSplitView && (
<Flex> <Flex>
<StepBorder disabled={shouldFlashStepBeDisabled} right /> <StepBorder disabled={shouldFlashStepBeDisabled} right />
</Flex> </Flex>
</>
)} )}
{this.state.isFlashing && ( {this.state.isFlashing && this.state.isWebviewShowing && (
<>
<Flex <Flex
style={{ style={{
position: 'absolute', position: 'absolute',
@ -265,7 +273,6 @@ export class MainPage extends React.Component<
height: '100vh', height: '100vh',
zIndex: 1, zIndex: 1,
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)', boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
display: this.state.isWebviewShowing ? 'block' : 'none',
}} }}
> >
<ReducedFlashingInfos <ReducedFlashingInfos
@ -286,8 +293,10 @@ export class MainPage extends React.Component<
}} }}
/> />
</Flex> </Flex>
<FeaturedProject )}
shouldShow={this.state.isWebviewShowing} {this.state.isFlashing && this.state.featuredProjectURL && (
<SafeWebview
src={this.state.featuredProjectURL}
onWebviewShow={(isWebviewShowing: boolean) => { onWebviewShow={(isWebviewShowing: boolean) => {
this.setState({ isWebviewShowing }); this.setState({ isWebviewShowing });
}} }}
@ -299,7 +308,6 @@ export class MainPage extends React.Component<
height: '100vh', height: '100vh',
}} }}
/> />
</>
)} )}
<FlashStep <FlashStep
@ -307,7 +315,6 @@ export class MainPage extends React.Component<
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
source={this.state.source} source={this.state.source}
isFlashing={this.state.isFlashing} isFlashing={this.state.isFlashing}
isWebviewShowing={this.state.isWebviewShowing}
step={state.type} step={state.type}
percentage={state.percentage} percentage={state.percentage}
position={state.position} position={state.position}

42
npm-shrinkwrap.json generated
View File

@ -2174,9 +2174,9 @@
} }
}, },
"@types/react-native": { "@types/react-native": {
"version": "0.63.8", "version": "0.63.9",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.8.tgz", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.9.tgz",
"integrity": "sha512-QRwGFRTyGafRVTUS+0GYyJrlpmS3boyBaFI0ULSc+mh/lQNxrzbdQvoL2k5X7+t9hxyqA4dTQAlP6l0ir/fNJQ==", "integrity": "sha512-6ec/z9zjAkFH3rD1RYqbrA/Lj+jux6bumWCte4yRy3leyelTdqtmOd2Ph+86IXQQzsIArEMBwmraAbNQ0J3UAA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
@ -6677,9 +6677,9 @@
"dev": true "dev": true
}, },
"etcher-sdk": { "etcher-sdk": {
"version": "4.1.24", "version": "4.1.26",
"resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.24.tgz", "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.26.tgz",
"integrity": "sha512-lRAKhXuXD7y68eA+bHJJn4MJpncbybeoH7FaIlXTcISuzdh9lr8NlguKnxvxkX35v1sWN7Ke6REloQY7oqqlvQ==", "integrity": "sha512-0Mb2EVoBn5dZdXer2sjINkvsccbfSYwm3Bgv0sYARyLazim+BljmsIFV9bNg8jmM8/h/Qxk+xLSJAyyOiSKyiA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@balena/udif": "^1.1.0", "@balena/udif": "^1.1.0",
@ -7800,9 +7800,9 @@
} }
}, },
"grommet": { "grommet": {
"version": "2.14.0", "version": "2.15.0",
"resolved": "https://registry.npmjs.org/grommet/-/grommet-2.14.0.tgz", "resolved": "https://registry.npmjs.org/grommet/-/grommet-2.15.0.tgz",
"integrity": "sha512-G/LTkr+uFri4NUNQGJMx8TtWWe9+KSpIHCXC9WgRaICI73R3+BvhLSNSzWMQ6YIQC+7PJFtruebeWjdUqR3Ykw==", "integrity": "sha512-5TVbiLrMpZOoB9oZAqWVttj6lO4rcKqBW1rWr4iovTuyyfYYOUQbuNfcFtUqp+MdB0fsQ1Vvci4PiTBvhRJqHA==",
"dev": true, "dev": true,
"requires": { "requires": {
"grommet-icons": "^4.2.0", "grommet-icons": "^4.2.0",
@ -12821,9 +12821,9 @@
"optional": true "optional": true
}, },
"rendition": { "rendition": {
"version": "18.1.0", "version": "18.4.1",
"resolved": "https://registry.npmjs.org/rendition/-/rendition-18.1.0.tgz", "resolved": "https://registry.npmjs.org/rendition/-/rendition-18.4.1.tgz",
"integrity": "sha512-B65e7VJadU+cxtXOn4eGBg8wql57I376NfUYWiu4zs4sf8l6/0h+P6FctoRdrZGX8RRRMToGLHnVC+3411Tmiw==", "integrity": "sha512-mV/0p+M8XR/Xa/ZFzgflZPHelpuONiTSa/CMMuHkmXR7vhF7tB2ORxLRc/DbymmdN6cWQwXAyA81t9TDAOhgVQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/fontawesome-svg-core": "^1.2.25",
@ -15030,9 +15030,9 @@
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "3.10.1", "version": "3.10.2",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.2.tgz",
"integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==", "integrity": "sha512-GXCYNwqoo0MbLARghYjxVBxDCnU0tLqN7IPLdHHbibCb1NI5zBkU2EPcy/GaVxc0BtTjqyGXJCINe6JMR2Dpow==",
"dev": true "dev": true
}, },
"unbzip2-stream": { "unbzip2-stream": {
@ -15079,9 +15079,9 @@
"dev": true "dev": true
}, },
"unified": { "unified": {
"version": "9.1.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/unified/-/unified-9.1.0.tgz", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz",
"integrity": "sha512-VXOv7Ic6twsKGJDeZQ2wwPqXs2hM0KNu5Hkg9WgAZbSD1pxhZ7p8swqg583nw1Je2fhwHy6U8aEjiI79x1gvag==", "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==",
"dev": true, "dev": true,
"requires": { "requires": {
"bail": "^1.0.0", "bail": "^1.0.0",
@ -15543,9 +15543,9 @@
} }
}, },
"vfile-location": { "vfile-location": {
"version": "3.0.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz",
"integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==",
"dev": true "dev": true
}, },
"vfile-message": { "vfile-message": {

View File

@ -77,7 +77,7 @@
"electron-notarize": "^1.0.0", "electron-notarize": "^1.0.0",
"electron-rebuild": "^1.11.0", "electron-rebuild": "^1.11.0",
"electron-updater": "^4.3.2", "electron-updater": "^4.3.2",
"etcher-sdk": "^4.1.24", "etcher-sdk": "^4.1.26",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"husky": "^4.2.5", "husky": "^4.2.5",
"immutable": "^3.8.1", "immutable": "^3.8.1",
@ -94,7 +94,7 @@
"react": "^16.8.5", "react": "^16.8.5",
"react-dom": "^16.8.5", "react-dom": "^16.8.5",
"redux": "^4.0.5", "redux": "^4.0.5",
"rendition": "^18.1.0", "rendition": "^18.4.1",
"resin-corvus": "^2.0.5", "resin-corvus": "^2.0.5",
"semver": "^7.3.2", "semver": "^7.3.2",
"simple-progress-webpack-plugin": "^1.1.2", "simple-progress-webpack-plugin": "^1.1.2",