diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 1e9a82099..622819e67 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -453,7 +453,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); bind(BoardsConfigDialog).toSelf().inSingletonScope(); bind(BoardsConfigDialogProps).toConstantValue({ - title: nls.localize('arduino/common/selectBoard', 'Select Board'), + title: nls.localize( + 'arduino/board/boardConfigDialogTitle', + 'Select Other Board and Port' + ), }); // Core service diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts index d5db717c8..ffec830d1 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts @@ -1,4 +1,8 @@ -import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; +import { + injectable, + inject, + postConstruct, +} from '@theia/core/shared/inversify'; import { Message } from '@theia/core/shared/@phosphor/messaging'; import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser'; import { AbstractDialog } from '../theia/dialogs/dialogs'; @@ -28,7 +32,7 @@ export class BoardsConfigDialog extends AbstractDialog { @inject(BoardsConfigDialogProps) protected override readonly props: BoardsConfigDialogProps ) { - super(props); + super({ ...props, maxWidth: 500 }); this.contentNode.classList.add('select-board-dialog'); this.contentNode.appendChild(this.createDescription()); @@ -65,14 +69,6 @@ export class BoardsConfigDialog extends AbstractDialog { const head = document.createElement('div'); head.classList.add('head'); - const title = document.createElement('div'); - title.textContent = nls.localize( - 'arduino/board/configDialogTitle', - 'Select Other Board & Port' - ); - title.classList.add('title'); - head.appendChild(title); - const text = document.createElement('div'); text.classList.add('text'); head.appendChild(text); diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index 4449c5cf1..d32094705 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -258,14 +258,14 @@ export class BoardsConfig extends React.Component< override render(): React.ReactNode { return ( -
+ <> {this.renderContainer('boards', this.renderBoards.bind(this))} {this.renderContainer( 'ports', this.renderPorts.bind(this), this.renderPortsFooter.bind(this) )} -
+ ); } diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx index 21ec1586b..336a7b657 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx @@ -1,5 +1,9 @@ import * as React from '@theia/core/shared/react'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { AbstractDialog } from '../../theia/dialogs/dialogs'; import { Widget } from '@theia/core/shared/@phosphor/widgets'; @@ -153,6 +157,7 @@ export class UploadCertificateDialog extends AbstractDialog { 'Upload SSL Root Certificates' ), }); + this.node.id = 'certificate-uploader-dialog-container'; this.contentNode.classList.add('certificate-uploader-dialog'); this.acceptButton = undefined; } diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx index ff65b71a9..6273d321f 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx @@ -101,6 +101,7 @@ export class UploadFirmwareDialog extends AbstractDialog { protected override readonly props: UploadFirmwareDialogProps ) { super({ title: UploadFirmware.Commands.OPEN.label || '' }); + this.node.id = 'firmware-uploader-dialog-container'; this.contentNode.classList.add('firmware-uploader-dialog'); this.acceptButton = undefined; } diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx index 262aea518..93da416e5 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx @@ -1,4 +1,3 @@ -import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { nls } from '@theia/core/lib/common'; import { shell } from 'electron'; import * as React from '@theia/core/shared/react'; @@ -7,36 +6,32 @@ import ReactMarkdown from 'react-markdown'; import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater'; import ProgressBar from '../../components/ProgressBar'; -export type IDEUpdaterComponentProps = { - updateInfo: UpdateInfo; - windowService: WindowService; +export interface UpdateProgress { + progressInfo?: ProgressInfo | undefined; downloadFinished?: boolean; downloadStarted?: boolean; - progress?: ProgressInfo; error?: Error; - onDownload: () => void; - onClose: () => void; - onSkipVersion: () => void; - onCloseAndInstall: () => void; -}; +} + +export interface IDEUpdaterComponentProps { + updateInfo: UpdateInfo; + updateProgress: UpdateProgress; +} export const IDEUpdaterComponent = ({ - updateInfo: { version, releaseNotes }, - downloadStarted = false, - downloadFinished = false, - windowService, - progress, - error, - onDownload, - onClose, - onSkipVersion, - onCloseAndInstall, + updateInfo, + updateProgress: { + downloadStarted = false, + downloadFinished = false, + progressInfo, + error, + }, }: IDEUpdaterComponentProps): React.ReactElement => { - const changelogDivRef = React.useRef() as React.MutableRefObject< - HTMLDivElement - >; + const { version, releaseNotes } = updateInfo; + const changelogDivRef = + React.useRef() as React.MutableRefObject; React.useEffect(() => { - if (!!releaseNotes) { + if (!!releaseNotes && changelogDivRef.current) { let changelog: string; if (typeof releaseNotes === 'string') changelog = releaseNotes; else @@ -58,12 +53,7 @@ export const IDEUpdaterComponent = ({ changelogDivRef.current ); } - }, [releaseNotes]); - const closeButton = ( - - ); + }, [updateInfo]); const DownloadCompleted: () => React.ReactElement = () => (
@@ -80,19 +70,6 @@ export const IDEUpdaterComponent = ({ 'Close the software and install the update on your machine.' )}
-
- {closeButton} - -
); @@ -104,7 +81,7 @@ export const IDEUpdaterComponent = ({ 'Downloading the latest version of the Arduino IDE.' )} - + ); @@ -130,46 +107,14 @@ export const IDEUpdaterComponent = ({ )} {releaseNotes && ( -
-
+
+
)} -
- -
- {closeButton} - -
); - const onGoToDownloadClick = ( - event: React.SyntheticEvent - ) => { - const { target } = event.nativeEvent; - if (target instanceof HTMLAnchorElement) { - event.nativeEvent.preventDefault(); - windowService.openNewWindow(target.href, { external: true }); - onClose(); - } - }; - const GoToDownloadPage: () => React.ReactElement = () => (
@@ -178,19 +123,6 @@ export const IDEUpdaterComponent = ({ "An update for the Arduino IDE is available, but we're not able to download and install it automatically. Please go to the download page and download the latest version from there." )}
-
); diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx index 231687873..06e357e8c 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx @@ -1,113 +1,57 @@ import * as React from '@theia/core/shared/react'; -import { inject, injectable } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { AbstractDialog } from '../../theia/dialogs/dialogs'; import { Widget } from '@theia/core/shared/@phosphor/widgets'; import { Message } from '@theia/core/shared/@phosphor/messaging'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { nls } from '@theia/core'; -import { IDEUpdaterComponent } from './ide-updater-component'; - +import { IDEUpdaterComponent, UpdateProgress } from './ide-updater-component'; import { IDEUpdater, IDEUpdaterClient, - ProgressInfo, SKIP_IDE_VERSION, UpdateInfo, } from '../../../common/protocol/ide-updater'; import { LocalStorageService } from '@theia/core/lib/browser'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; +const DOWNLOAD_PAGE_URL = + 'https://www.arduino.cc/en/software#experimental-software'; + @injectable() export class IDEUpdaterDialogWidget extends ReactWidget { - protected isOpen = new Object(); - updateInfo: UpdateInfo; - progressInfo: ProgressInfo | undefined; - error: Error | undefined; - downloadFinished: boolean; - downloadStarted: boolean; - onClose: () => void; - - @inject(IDEUpdater) - protected readonly updater: IDEUpdater; - - @inject(IDEUpdaterClient) - protected readonly updaterClient: IDEUpdaterClient; - - @inject(LocalStorageService) - protected readonly localStorageService: LocalStorageService; - - @inject(WindowService) - protected windowService: WindowService; - - init(updateInfo: UpdateInfo, onClose: () => void): void { - this.updateInfo = updateInfo; - this.progressInfo = undefined; - this.error = undefined; - this.downloadStarted = false; - this.downloadFinished = false; - this.onClose = onClose; - - this.updaterClient.onError((e) => { - this.error = e; - this.update(); - }); - this.updaterClient.onDownloadProgressChanged((e) => { - this.progressInfo = e; - this.update(); - }); - this.updaterClient.onDownloadFinished((e) => { - this.downloadFinished = true; - this.update(); - }); - } + private _updateInfo: UpdateInfo; + private _updateProgress: UpdateProgress = {}; - async onSkipVersion(): Promise { - this.localStorageService.setData( - SKIP_IDE_VERSION, - this.updateInfo.version - ); - this.close(); - } - - override close(): void { - super.close(); - this.onClose(); + setUpdateInfo(updateInfo: UpdateInfo): void { + this._updateInfo = updateInfo; + this.update(); } - onDispose(): void { - if (this.downloadStarted && !this.downloadFinished) - this.updater.stopDownload(); + mergeUpdateProgress(updateProgress: UpdateProgress): void { + this._updateProgress = { ...this._updateProgress, ...updateProgress }; + this.update(); } - async onDownload(): Promise { - this.progressInfo = undefined; - this.downloadStarted = true; - this.error = undefined; - this.updater.downloadUpdate(); - this.update(); + get updateInfo(): UpdateInfo { + return this._updateInfo; } - onCloseAndInstall(): void { - this.updater.quitAndInstall(); + get updateProgress(): UpdateProgress { + return this._updateProgress; } protected render(): React.ReactNode { - return !!this.updateInfo ? ( -
- - + return !!this._updateInfo ? ( + ) : null; } } @@ -118,7 +62,19 @@ export class IDEUpdaterDialogProps extends DialogProps {} @injectable() export class IDEUpdaterDialog extends AbstractDialog { @inject(IDEUpdaterDialogWidget) - protected readonly widget: IDEUpdaterDialogWidget; + private readonly widget: IDEUpdaterDialogWidget; + + @inject(IDEUpdater) + private readonly updater: IDEUpdater; + + @inject(IDEUpdaterClient) + private readonly updaterClient: IDEUpdaterClient; + + @inject(LocalStorageService) + private readonly localStorageService: LocalStorageService; + + @inject(WindowService) + private readonly windowService: WindowService; constructor( @inject(IDEUpdaterDialogProps) @@ -130,10 +86,26 @@ export class IDEUpdaterDialog extends AbstractDialog { 'Software Update' ), }); + this.node.id = 'ide-updater-dialog-container'; this.contentNode.classList.add('ide-updater-dialog'); this.acceptButton = undefined; } + @postConstruct() + protected init(): void { + this.updaterClient.onUpdaterDidFail((error) => { + this.appendErrorButtons(); + this.widget.mergeUpdateProgress({ error }); + }); + this.updaterClient.onDownloadProgressDidChange((progressInfo) => { + this.widget.mergeUpdateProgress({ progressInfo }); + }); + this.updaterClient.onDownloadDidFinish(() => { + this.appendInstallButtons(); + this.widget.mergeUpdateProgress({ downloadFinished: true }); + }); + } + get value(): UpdateInfo { return this.widget.updateInfo; } @@ -143,24 +115,123 @@ export class IDEUpdaterDialog extends AbstractDialog { Widget.detach(this.widget); } Widget.attach(this.widget, this.contentNode); + this.appendInitialButtons(); super.onAfterAttach(msg); - this.update(); + } + + private clearButtons(): void { + while (this.controlPanel.firstChild) { + this.controlPanel.removeChild(this.controlPanel.firstChild); + } + this.closeButton = undefined; + } + + private appendNotNowButton(): void { + this.appendCloseButton( + nls.localize('arduino/ide-updater/notNowButton', 'Not now') + ); + if (this.closeButton) { + this.addCloseAction(this.closeButton, 'click'); + } + } + + private appendInitialButtons(): void { + this.clearButtons(); + + const skipVersionButton = this.createButton( + nls.localize('arduino/ide-updater/skipVersionButton', 'Skip Version') + ); + skipVersionButton.classList.add('secondary'); + skipVersionButton.classList.add('skip-version-button'); + this.addAction(skipVersionButton, this.skipVersion.bind(this), 'click'); + this.controlPanel.appendChild(skipVersionButton); + + this.appendNotNowButton(); + + const downloadButton = this.createButton( + nls.localize('arduino/ide-updater/downloadButton', 'Download') + ); + this.addAction(downloadButton, this.startDownload.bind(this), 'click'); + this.controlPanel.appendChild(downloadButton); + downloadButton.focus(); + } + + private appendInstallButtons(): void { + this.clearButtons(); + this.appendNotNowButton(); + + const closeAndInstallButton = this.createButton( + nls.localize( + 'arduino/ide-updater/closeAndInstallButton', + 'Close and Install' + ) + ); + this.addAction( + closeAndInstallButton, + this.closeAndInstall.bind(this), + 'click' + ); + this.controlPanel.appendChild(closeAndInstallButton); + closeAndInstallButton.focus(); + } + + private appendErrorButtons(): void { + this.clearButtons(); + this.appendNotNowButton(); + + const goToDownloadPageButton = this.createButton( + nls.localize('arduino/ide-updater/goToDownloadButton', 'Go To Download') + ); + this.addAction( + goToDownloadPageButton, + this.openDownloadPage.bind(this), + 'click' + ); + this.controlPanel.appendChild(goToDownloadPageButton); + goToDownloadPageButton.focus(); + } + + private openDownloadPage(): void { + this.windowService.openNewWindow(DOWNLOAD_PAGE_URL, { external: true }); + this.close(); + } + + private skipVersion(): void { + this.localStorageService.setData( + SKIP_IDE_VERSION, + this.widget.updateInfo.version + ); + this.close(); + } + + private startDownload(): void { + this.widget.mergeUpdateProgress({ + downloadStarted: true, + }); + this.clearButtons(); + this.updater.downloadUpdate(); + } + + private closeAndInstall() { + this.updater.quitAndInstall(); + this.close(); } override async open( data: UpdateInfo | undefined = undefined ): Promise { if (data && data.version) { - this.widget.init(data, this.close.bind(this)); + this.widget.mergeUpdateProgress({ + progressInfo: undefined, + downloadStarted: false, + downloadFinished: false, + error: undefined, + }); + this.widget.setUpdateInfo(data); return super.open(); } } - protected override onUpdateRequest(msg: Message): void { - super.onUpdateRequest(msg); - this.widget.update(); - } - protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); @@ -168,6 +239,12 @@ export class IDEUpdaterDialog extends AbstractDialog { override close(): void { this.widget.dispose(); + if ( + this.widget.updateProgress?.downloadStarted && + !this.widget.updateProgress?.downloadFinished + ) { + this.updater.stopDownload(); + } super.close(); } } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx index dc3d00252..cb0fb5a40 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx @@ -188,15 +188,17 @@ export class SettingsComponent extends React.Component< /> {nls.localize('arduino/preferences/automatic', 'Automatic')} - - % +
+ +