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 2b170dd7b..d6779c302 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -387,6 +387,10 @@ import { import { TreeViewDecoratorService } from '@theia/plugin-ext/lib/main/browser/view/tree-view-decorator-service'; import { PLUGIN_VIEW_DATA_FACTORY_ID } from '@theia/plugin-ext/lib/main/browser/view/plugin-view-registry'; import { TreeViewWidget } from './theia/plugin-ext/tree-view-widget'; +import { + VersionWelcomeDialog, + VersionWelcomeDialogProps, +} from './dialogs/version-welcome-dialog'; // Hack to fix copy/cut/paste issue after electron version update in Theia. // https://github.com/eclipse-theia/theia/issues/12487 @@ -1014,6 +1018,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { title: 'IDEUpdater', }); + bind(VersionWelcomeDialog).toSelf().inSingletonScope(); + bind(VersionWelcomeDialogProps).toConstantValue({ + title: 'VersionWelcomeDialog', + }); + bind(UserFieldsDialog).toSelf().inSingletonScope(); bind(UserFieldsDialogProps).toConstantValue({ title: 'UserFields', diff --git a/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts index ab5f62ac2..d29106f04 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts @@ -3,10 +3,14 @@ import { LocalStorageService } from '@theia/core/lib/browser/storage-service'; import { inject, injectable } from '@theia/core/shared/inversify'; import { IDEUpdater, + LAST_USED_IDE_VERSION, SKIP_IDE_VERSION, } from '../../common/protocol/ide-updater'; import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog'; import { Contribution } from './contribution'; +import { VersionWelcomeDialog } from '../dialogs/version-welcome-dialog'; +import { AppService } from '../app-service'; +import { SemVer, valid as validSemVer } from 'semver'; @injectable() export class CheckForIDEUpdates extends Contribution { @@ -16,9 +20,15 @@ export class CheckForIDEUpdates extends Contribution { @inject(IDEUpdaterDialog) private readonly updaterDialog: IDEUpdaterDialog; + @inject(VersionWelcomeDialog) + private readonly versionWelcomeDialog: VersionWelcomeDialog; + @inject(LocalStorageService) private readonly localStorage: LocalStorageService; + @inject(AppService) + private readonly appService: AppService; + override onStart(): void { this.preferences.onPreferenceChanged( ({ preferenceName, newValue, oldValue }) => { @@ -36,7 +46,9 @@ export class CheckForIDEUpdates extends Contribution { ); } - override onReady(): void { + override async onReady(): Promise { + await this.setLastUsedIDEVersion(); + this.updater .init( this.preferences.get('arduino.ide.updateChannel'), @@ -49,7 +61,14 @@ export class CheckForIDEUpdates extends Contribution { return this.updater.checkForUpdates(true); }) .then(async (updateInfo) => { - if (!updateInfo) return; + if (!updateInfo) { + const isNewVersion = await this.isRunningNewIDEVersion(); + if (isNewVersion) { + this.setLastUsedIDEVersion(true); + this.versionWelcomeDialog.open(); + } + return; + } const versionToSkip = await this.localStorage.getData( SKIP_IDE_VERSION ); @@ -66,4 +85,26 @@ export class CheckForIDEUpdates extends Contribution { ); }); } + + private async setLastUsedIDEVersion(force = false): Promise { + const { appVersion } = await this.appService.info(); + const lastUsedIDEVersion = await this.localStorage.getData( + LAST_USED_IDE_VERSION + ); + if (validSemVer(appVersion) && (!lastUsedIDEVersion || force)) { + this.localStorage.setData(LAST_USED_IDE_VERSION, appVersion); + } + } + + private async isRunningNewIDEVersion(): Promise { + const { appVersion } = await this.appService.info(); + const prevVersion = await this.localStorage.getData( + LAST_USED_IDE_VERSION + ); + try { + return !!prevVersion && new SemVer(appVersion).compare(prevVersion) === 1; + } catch (e) { + return false; + } + } } diff --git a/arduino-ide-extension/src/browser/dialogs/version-welcome-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/version-welcome-dialog.tsx new file mode 100644 index 000000000..1e725ed6d --- /dev/null +++ b/arduino-ide-extension/src/browser/dialogs/version-welcome-dialog.tsx @@ -0,0 +1,99 @@ +import React from '@theia/core/shared/react'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { Message } from '@theia/core/shared/@phosphor/messaging'; +import { ReactDialog } from '../theia/dialogs/dialogs'; +import { nls } from '@theia/core'; +import { DialogProps } from '@theia/core/lib/browser'; +import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { AppService } from '../app-service'; + +@injectable() +export class VersionWelcomeDialogProps extends DialogProps {} + +@injectable() +export class VersionWelcomeDialog extends ReactDialog { + @inject(AppService) + private readonly appService: AppService; + + @inject(WindowService) + private readonly windowService: WindowService; + + constructor( + @inject(VersionWelcomeDialogProps) + protected override readonly props: VersionWelcomeDialogProps + ) { + super({ + title: nls.localize( + 'arduino/versionWelcome/title', + 'Welcome to a new version of the Arduino IDE!' + ), + }); + this.node.id = 'version-welcome-dialog-container'; + this.contentNode.classList.add('version-welcome-dialog'); + } + + protected render(): React.ReactNode { + return ( +
+

+ {nls.localize( + 'arduino/versionWelcome/donateMessage', + 'Arduino is committed to keeping software free and open-source for everyone. Your donation helps us develop new features, improve libraries, and support millions of users worldwide.' + )} +

+

+ {nls.localize( + 'arduino/versionWelcome/donateMessage2', + 'Please consider supporting our work on the free open source Arduino IDE.' + )} +

+
+ ); + } + + override get value(): void { + return; + } + + private appendButtons(): void { + const cancelButton = this.createButton( + nls.localize('arduino/versionWelcome/cancelButton', 'Maybe later') + ); + cancelButton.classList.add('secondary'); + cancelButton.classList.add('cancel-button'); + this.addAction(cancelButton, this.close.bind(this), 'click'); + this.controlPanel.appendChild(cancelButton); + + const donateButton = this.createButton( + nls.localize('arduino/versionWelcome/donateButton', 'Donate now') + ); + this.addAction(donateButton, this.openDonationPage.bind(this), 'click'); + this.controlPanel.appendChild(donateButton); + donateButton.focus(); + } + + private readonly openDonationPage = () => { + const url = 'https://www.arduino.cc/en/donate'; + this.windowService.openNewWindow(url, { external: true }); + }; + + private async updateTitleVersion(): Promise { + const appInfo = await this.appService.info(); + const { appVersion } = appInfo; + + if (appVersion) { + this.titleNode.innerHTML = nls.localize( + 'arduino/versionWelcome/titleWithVersion', + 'Welcome to the new Arduino IDE {0}!', + appVersion + ); + } + } + + protected override onAfterAttach(msg: Message): void { + this.update(); + this.appendButtons(); + this.updateTitleVersion(); + super.onAfterAttach(msg); + } +} diff --git a/arduino-ide-extension/src/browser/style/index.css b/arduino-ide-extension/src/browser/style/index.css index fd7887ae1..593cf1eaf 100644 --- a/arduino-ide-extension/src/browser/style/index.css +++ b/arduino-ide-extension/src/browser/style/index.css @@ -10,6 +10,7 @@ @import "./settings-dialog.css"; @import "./firmware-uploader-dialog.css"; @import "./ide-updater-dialog.css"; +@import "./version-welcome-dialog.css"; @import "./certificate-uploader-dialog.css"; @import "./user-fields-dialog.css"; @import "./debug.css"; diff --git a/arduino-ide-extension/src/browser/style/version-welcome-dialog.css b/arduino-ide-extension/src/browser/style/version-welcome-dialog.css new file mode 100644 index 000000000..80d6f71e4 --- /dev/null +++ b/arduino-ide-extension/src/browser/style/version-welcome-dialog.css @@ -0,0 +1,7 @@ +#version-welcome-dialog-container > .dialogBlock { + width: 546px; + + .bold { + font-weight: bold; + } +} diff --git a/arduino-ide-extension/src/common/protocol/ide-updater.ts b/arduino-ide-extension/src/common/protocol/ide-updater.ts index 5608e63be..ebc8bfef2 100644 --- a/arduino-ide-extension/src/common/protocol/ide-updater.ts +++ b/arduino-ide-extension/src/common/protocol/ide-updater.ts @@ -71,3 +71,4 @@ export interface IDEUpdaterClient { } export const SKIP_IDE_VERSION = 'skipIDEVersion'; +export const LAST_USED_IDE_VERSION = 'lastUsedIDEVersion'; diff --git a/i18n/en.json b/i18n/en.json index d93719b9f..f114e1867 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -528,6 +528,14 @@ "renameSketchFolderMessage": "The sketch '{0}' cannot be used. {1} To get rid of this message, rename the sketch. Do you want to rename the sketch now?", "renameSketchFolderTitle": "Invalid sketch name" }, + "versionWelcome": { + "cancelButton": "Maybe later", + "donateButton": "Donate now", + "donateMessage": "Arduino is committed to keeping software free and open-source for everyone. Your donation helps us develop new features, improve libraries, and support millions of users worldwide.", + "donateMessage2": "Please consider supporting our work on the free open source Arduino IDE.", + "title": "Welcome to a new version of the Arduino IDE!", + "titleWithVersion": "Welcome to the new Arduino IDE {0}!" + }, "workspace": { "alreadyExists": "'{0}' already exists." }