import * as React from '@theia/core/shared/react'; 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, UpdateProgress } from './ide-updater-component'; import { IDEUpdater, IDEUpdaterClient, 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'; @injectable() export class IDEUpdaterDialogWidget extends ReactWidget { private _updateInfo: UpdateInfo; private _updateProgress: UpdateProgress = {}; setUpdateInfo(updateInfo: UpdateInfo): void { this._updateInfo = updateInfo; this.update(); } mergeUpdateProgress(updateProgress: UpdateProgress): void { this._updateProgress = { ...this._updateProgress, ...updateProgress }; this.update(); } get updateInfo(): UpdateInfo { return this._updateInfo; } get updateProgress(): UpdateProgress { return this._updateProgress; } protected render(): React.ReactNode { return !!this._updateInfo ? ( <IDEUpdaterComponent updateInfo={this._updateInfo} updateProgress={this._updateProgress} /> ) : null; } } @injectable() export class IDEUpdaterDialogProps extends DialogProps {} @injectable() export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> { @inject(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) protected override readonly props: IDEUpdaterDialogProps ) { super({ title: nls.localize( 'arduino/ide-updater/ideUpdaterDialog', '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; } protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } Widget.attach(this.widget, this.contentNode); this.appendInitialButtons(); super.onAfterAttach(msg); } 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<string>( 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<UpdateInfo | undefined> { if (data && data.version) { this.widget.mergeUpdateProgress({ progressInfo: undefined, downloadStarted: false, downloadFinished: false, error: undefined, }); this.widget.setUpdateInfo(data); return super.open(); } } protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } override close(): void { this.widget.dispose(); if ( this.widget.updateProgress?.downloadStarted && !this.widget.updateProgress?.downloadFinished ) { this.updater.stopDownload(); } super.close(); } }