diff --git a/lib/gui/app/components/featured-project/featured-project.tsx b/lib/gui/app/components/featured-project/featured-project.tsx
deleted file mode 100644
index 9ed36346..00000000
--- a/lib/gui/app/components/featured-project/featured-project.tsx
+++ /dev/null
@@ -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 ? (
-
- ) : null;
- }
-}
diff --git a/lib/gui/app/components/safe-webview/safe-webview.tsx b/lib/gui/app/components/safe-webview/safe-webview.tsx
index 961acc00..614ae11d 100644
--- a/lib/gui/app/components/safe-webview/safe-webview.tsx
+++ b/lib/gui/app/components/safe-webview/safe-webview.tsx
@@ -58,8 +58,6 @@ const API_VERSION = '2';
interface SafeWebviewProps {
// The website source URL
src: string;
- // @summary Refresh the webview
- refreshNow?: boolean;
// Webview lifecycle event
onWebviewShow?: (isWebviewShowing: boolean) => void;
style?: React.CSSProperties;
diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx
index 2e15443e..7d8fa78f 100644
--- a/lib/gui/app/components/source-selector/source-selector.tsx
+++ b/lib/gui/app/components/source-selector/source-selector.tsx
@@ -30,6 +30,7 @@ import {
Txt,
Card as BaseCard,
Input,
+ Spinner,
} from 'rendition';
import styled from 'styled-components';
@@ -137,8 +138,9 @@ const URLSelector = ({
: 'OK'}
done={async () => {
setLoading(true);
const urlStrings = recentImages.map((url: URL) => url.href);
@@ -288,7 +290,7 @@ export class SourceSelector extends React.Component<
await this.selectImageByPath({
imagePath,
SourceType: isURL ? sourceDestination.Http : sourceDestination.File,
- });
+ }).promise;
}
private reselectImage() {
@@ -344,74 +346,97 @@ export class SourceSelector extends React.Component<
}
}
- private async selectImageByPath({ imagePath, SourceType }: SourceOptions) {
- try {
- imagePath = await replaceWindowsNetworkDriveLetter(imagePath);
- } catch (error) {
- analytics.logException(error);
- }
+ private selectImageByPath({
+ imagePath,
+ SourceType,
+ }: SourceOptions): { promise: Promise; cancel: () => void } {
+ let cancelled = false;
+ return {
+ cancel: () => {
+ cancelled = true;
+ },
+ promise: (async () => {
+ try {
+ imagePath = await replaceWindowsNetworkDriveLetter(imagePath);
+ } catch (error) {
+ analytics.logException(error);
+ }
+ if (cancelled) {
+ return;
+ }
- let source;
- if (SourceType === sourceDestination.File) {
- source = new sourceDestination.File({
- path: imagePath,
- });
- } else {
- if (
- !imagePath.startsWith('https://') &&
- !imagePath.startsWith('http://')
- ) {
- const invalidImageError = errors.createUserError({
- title: 'Unsupported protocol',
- description: messages.error.unsupportedProtocol(),
- });
+ let source;
+ if (SourceType === sourceDestination.File) {
+ source = new sourceDestination.File({
+ path: imagePath,
+ });
+ } else {
+ if (
+ !imagePath.startsWith('https://') &&
+ !imagePath.startsWith('http://')
+ ) {
+ const invalidImageError = errors.createUserError({
+ title: 'Unsupported protocol',
+ description: messages.error.unsupportedProtocol(),
+ });
- osDialog.showError(invalidImageError);
- analytics.logEvent('Unsupported protocol', { path: imagePath });
- return;
- }
- source = new sourceDestination.Http({ url: imagePath });
- }
+ osDialog.showError(invalidImageError);
+ analytics.logEvent('Unsupported protocol', { path: imagePath });
+ return;
+ }
+ source = new sourceDestination.Http({ url: imagePath });
+ }
- try {
- const innerSource = await source.getInnerSource();
- const metadata = (await innerSource.getMetadata()) as sourceDestination.Metadata & {
- hasMBR: boolean;
- partitions: MBRPartition[] | GPTPartition[];
- path: string;
- extension: string;
- };
- const partitionTable = await innerSource.getPartitionTable();
- if (partitionTable) {
- metadata.hasMBR = true;
- metadata.partitions = partitionTable.partitions;
- } else {
- metadata.hasMBR = false;
- }
- metadata.path = imagePath;
- metadata.extension = path.extname(imagePath).slice(1);
- this.selectImage(metadata);
- this.afterSelected({
- imagePath,
- SourceType,
- });
- } catch (error) {
- const imageError = errors.createUserError({
- title: 'Error opening image',
- description: messages.error.openImage(
- path.basename(imagePath),
- error.message,
- ),
- });
- osDialog.showError(imageError);
- analytics.logException(error);
- } finally {
- try {
- await source.close();
- } catch (error) {
- // Noop
- }
- }
+ try {
+ const innerSource = await source.getInnerSource();
+ if (cancelled) {
+ return;
+ }
+ const metadata = (await innerSource.getMetadata()) as sourceDestination.Metadata & {
+ hasMBR: boolean;
+ partitions: MBRPartition[] | GPTPartition[];
+ path: string;
+ extension: string;
+ };
+ if (cancelled) {
+ return;
+ }
+ const partitionTable = await innerSource.getPartitionTable();
+ if (cancelled) {
+ return;
+ }
+ if (partitionTable) {
+ metadata.hasMBR = true;
+ metadata.partitions = partitionTable.partitions;
+ } else {
+ metadata.hasMBR = false;
+ }
+ metadata.path = imagePath;
+ metadata.extension = path.extname(imagePath).slice(1);
+ this.selectImage(metadata);
+ this.afterSelected({
+ imagePath,
+ SourceType,
+ });
+ } catch (error) {
+ const imageError = errors.createUserError({
+ title: 'Error opening image',
+ description: messages.error.openImage(
+ path.basename(imagePath),
+ error.message,
+ ),
+ });
+ osDialog.showError(imageError);
+ analytics.logException(error);
+ } finally {
+ try {
+ await source.close();
+ } catch (error) {
+ // Noop
+ }
+ }
+ })(),
+ };
}
private async openImageSelector() {
@@ -425,22 +450,22 @@ export class SourceSelector extends React.Component<
analytics.logEvent('Image selector closed');
return;
}
- this.selectImageByPath({
+ await this.selectImageByPath({
imagePath,
SourceType: sourceDestination.File,
- });
+ }).promise;
} catch (error) {
exceptionReporter.report(error);
}
}
- private onDrop(event: React.DragEvent) {
+ private async onDrop(event: React.DragEvent) {
const [file] = event.dataTransfer.files;
if (file) {
- this.selectImageByPath({
+ await this.selectImageByPath({
imagePath: file.path,
SourceType: sourceDestination.File,
- });
+ }).promise;
}
}
@@ -484,6 +509,9 @@ export class SourceSelector extends React.Component<
const imageName = selectionState.getImageName();
const imageSize = selectionState.getImageSize();
const imageLogo = selectionState.getImageLogo();
+ let cancelURLSelection = () => {
+ // noop
+ };
return (
<>
@@ -585,6 +613,7 @@ export class SourceSelector extends React.Component<
{showURLSelector && (
{
+ cancelURLSelection();
this.setState({
showURLSelector: false,
});
@@ -594,16 +623,17 @@ export class SourceSelector extends React.Component<
// if no file was resolved from the dialog.
if (!imageURL) {
analytics.logEvent('URL selector closed');
- this.setState({
- showURLSelector: false,
- });
- return;
+ } else {
+ let promise;
+ ({
+ promise,
+ cancel: cancelURLSelection,
+ } = this.selectImageByPath({
+ imagePath: imageURL,
+ SourceType: sourceDestination.Http,
+ }));
+ await promise;
}
-
- await this.selectImageByPath({
- imagePath: imageURL,
- SourceType: sourceDestination.Http,
- });
this.setState({
showURLSelector: false,
});
diff --git a/lib/gui/app/css/main.css b/lib/gui/app/css/main.css
index 03ab634d..b5350722 100644
--- a/lib/gui/app/css/main.css
+++ b/lib/gui/app/css/main.css
@@ -49,11 +49,6 @@ body {
-webkit-overflow-scrolling: touch;
}
-/* Allow window to be dragged from header */
-#app-header {
- -webkit-app-region: drag;
-}
-
/* Prevent blue outline */
a:focus,
input:focus,
diff --git a/lib/gui/app/pages/main/Flash.tsx b/lib/gui/app/pages/main/Flash.tsx
index 34f89ccb..24c6e9c6 100644
--- a/lib/gui/app/pages/main/Flash.tsx
+++ b/lib/gui/app/pages/main/Flash.tsx
@@ -146,7 +146,6 @@ interface FlashStepProps {
goToSuccess: () => void;
source: SourceOptions;
isFlashing: boolean;
- isWebviewShowing: boolean;
style?: React.CSSProperties;
// TODO: factorize
step: 'decompressing' | 'flashing' | 'verifying';
diff --git a/lib/gui/app/pages/main/MainPage.tsx b/lib/gui/app/pages/main/MainPage.tsx
index efecb712..d4577175 100644
--- a/lib/gui/app/pages/main/MainPage.tsx
+++ b/lib/gui/app/pages/main/MainPage.tsx
@@ -24,7 +24,6 @@ import * as React from 'react';
import { Flex } from 'rendition';
import styled from 'styled-components';
-import { FeaturedProject } from '../../components/featured-project/featured-project';
import FinishPage from '../../components/finish/finish';
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import { SafeWebview } from '../../components/safe-webview/safe-webview';
@@ -114,6 +113,7 @@ interface MainPageState {
isWebviewShowing: boolean;
hideSettings: boolean;
source: SourceOptions;
+ featuredProjectURL?: string;
}
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(() => {
this.setState(this.stateHelper());
});
+ this.setState({ featuredProjectURL: await this.getFeaturedProjectURL() });
}
private renderMain() {
@@ -163,41 +174,42 @@ export class MainPage extends React.Component<
return (
<>