Skip to content

Commit 19bf879

Browse files
feat: introduce VersionWelcomeDialog
Show donate dialog after the first time a first IDE version is loaded
1 parent 4fab7f4 commit 19bf879

File tree

7 files changed

+186
-2
lines changed

7 files changed

+186
-2
lines changed

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+9
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ import {
387387
import { TreeViewDecoratorService } from '@theia/plugin-ext/lib/main/browser/view/tree-view-decorator-service';
388388
import { PLUGIN_VIEW_DATA_FACTORY_ID } from '@theia/plugin-ext/lib/main/browser/view/plugin-view-registry';
389389
import { TreeViewWidget } from './theia/plugin-ext/tree-view-widget';
390+
import {
391+
VersionWelcomeDialog,
392+
VersionWelcomeDialogProps,
393+
} from './dialogs/version-welcome-dialog';
390394

391395
// Hack to fix copy/cut/paste issue after electron version update in Theia.
392396
// https://github.com/eclipse-theia/theia/issues/12487
@@ -1014,6 +1018,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
10141018
title: 'IDEUpdater',
10151019
});
10161020

1021+
bind(VersionWelcomeDialog).toSelf().inSingletonScope();
1022+
bind(VersionWelcomeDialogProps).toConstantValue({
1023+
title: 'VersionWelcomeDialog',
1024+
});
1025+
10171026
bind(UserFieldsDialog).toSelf().inSingletonScope();
10181027
bind(UserFieldsDialogProps).toConstantValue({
10191028
title: 'UserFields',

arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts

+56-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
33
import { inject, injectable } from '@theia/core/shared/inversify';
44
import {
55
IDEUpdater,
6+
LAST_USED_IDE_VERSION,
67
SKIP_IDE_VERSION,
78
} from '../../common/protocol/ide-updater';
89
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
910
import { Contribution } from './contribution';
11+
import { VersionWelcomeDialog } from '../dialogs/version-welcome-dialog';
12+
import { AppService } from '../app-service';
13+
import { SemVer } from 'semver';
1014

1115
@injectable()
1216
export class CheckForIDEUpdates extends Contribution {
@@ -16,9 +20,15 @@ export class CheckForIDEUpdates extends Contribution {
1620
@inject(IDEUpdaterDialog)
1721
private readonly updaterDialog: IDEUpdaterDialog;
1822

23+
@inject(VersionWelcomeDialog)
24+
private readonly versionWelcomeDialog: VersionWelcomeDialog;
25+
1926
@inject(LocalStorageService)
2027
private readonly localStorage: LocalStorageService;
2128

29+
@inject(AppService)
30+
private readonly appService: AppService;
31+
2232
override onStart(): void {
2333
this.preferences.onPreferenceChanged(
2434
({ preferenceName, newValue, oldValue }) => {
@@ -36,7 +46,7 @@ export class CheckForIDEUpdates extends Contribution {
3646
);
3747
}
3848

39-
override onReady(): void {
49+
override async onReady(): Promise<void> {
4050
this.updater
4151
.init(
4252
this.preferences.get('arduino.ide.updateChannel'),
@@ -49,7 +59,13 @@ export class CheckForIDEUpdates extends Contribution {
4959
return this.updater.checkForUpdates(true);
5060
})
5161
.then(async (updateInfo) => {
52-
if (!updateInfo) return;
62+
if (!updateInfo) {
63+
const isNewVersion = await this.isNewStableVersion();
64+
if (isNewVersion) {
65+
this.versionWelcomeDialog.open();
66+
}
67+
return;
68+
}
5369
const versionToSkip = await this.localStorage.getData<string>(
5470
SKIP_IDE_VERSION
5571
);
@@ -64,6 +80,44 @@ export class CheckForIDEUpdates extends Contribution {
6480
e.message
6581
)
6682
);
83+
})
84+
.finally(() => {
85+
this.setCurrentIDEVersion();
6786
});
6887
}
88+
89+
private async setCurrentIDEVersion(): Promise<void> {
90+
try {
91+
const { appVersion } = await this.appService.info();
92+
const currSemVer = new SemVer(appVersion ?? '');
93+
this.localStorage.setData(LAST_USED_IDE_VERSION, currSemVer.format());
94+
} catch {
95+
// ignore invalid versions
96+
}
97+
}
98+
99+
/**
100+
* Check if user is running a new IDE version for the first time.
101+
* @returns true if the current IDE version is greater than the last used version
102+
* and both are non-prerelease versions.
103+
*/
104+
private async isNewStableVersion(): Promise<boolean> {
105+
try {
106+
const { appVersion } = await this.appService.info();
107+
const prevVersion = await this.localStorage.getData<string>(
108+
LAST_USED_IDE_VERSION
109+
);
110+
111+
const prevSemVer = new SemVer(prevVersion ?? '');
112+
const currSemVer = new SemVer(appVersion ?? '');
113+
114+
if (prevSemVer.prerelease.length || currSemVer.prerelease.length) {
115+
return false;
116+
}
117+
118+
return currSemVer.compare(prevSemVer) === 1;
119+
} catch (e) {
120+
return false;
121+
}
122+
}
69123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React from '@theia/core/shared/react';
2+
import { inject, injectable } from '@theia/core/shared/inversify';
3+
import { Message } from '@theia/core/shared/@phosphor/messaging';
4+
import { ReactDialog } from '../theia/dialogs/dialogs';
5+
import { nls } from '@theia/core';
6+
import { DialogProps } from '@theia/core/lib/browser';
7+
import { WindowService } from '@theia/core/lib/browser/window/window-service';
8+
import { AppService } from '../app-service';
9+
10+
@injectable()
11+
export class VersionWelcomeDialogProps extends DialogProps {}
12+
13+
@injectable()
14+
export class VersionWelcomeDialog extends ReactDialog<void> {
15+
@inject(AppService)
16+
private readonly appService: AppService;
17+
18+
@inject(WindowService)
19+
private readonly windowService: WindowService;
20+
21+
constructor(
22+
@inject(VersionWelcomeDialogProps)
23+
protected override readonly props: VersionWelcomeDialogProps
24+
) {
25+
super({
26+
title: nls.localize(
27+
'arduino/versionWelcome/title',
28+
'Welcome to a new version of the Arduino IDE!'
29+
),
30+
});
31+
this.node.id = 'version-welcome-dialog-container';
32+
this.contentNode.classList.add('version-welcome-dialog');
33+
}
34+
35+
protected render(): React.ReactNode {
36+
return (
37+
<div>
38+
<p>
39+
{nls.localize(
40+
'arduino/versionWelcome/donateMessage',
41+
'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.'
42+
)}
43+
</p>
44+
<p className="bold">
45+
{nls.localize(
46+
'arduino/versionWelcome/donateMessage2',
47+
'Please consider supporting our work on the free open source Arduino IDE.'
48+
)}
49+
</p>
50+
</div>
51+
);
52+
}
53+
54+
override get value(): void {
55+
return;
56+
}
57+
58+
private appendButtons(): void {
59+
const cancelButton = this.createButton(
60+
nls.localize('arduino/versionWelcome/cancelButton', 'Maybe later')
61+
);
62+
cancelButton.classList.add('secondary');
63+
cancelButton.classList.add('cancel-button');
64+
this.addAction(cancelButton, this.close.bind(this), 'click');
65+
this.controlPanel.appendChild(cancelButton);
66+
67+
const donateButton = this.createButton(
68+
nls.localize('arduino/versionWelcome/donateButton', 'Donate now')
69+
);
70+
this.addAction(donateButton, this.onDonateButtonClick.bind(this), 'click');
71+
this.controlPanel.appendChild(donateButton);
72+
donateButton.focus();
73+
}
74+
75+
private onDonateButtonClick(): void {
76+
this.openDonationPage();
77+
this.close();
78+
}
79+
80+
private readonly openDonationPage = () => {
81+
const url = 'https://www.arduino.cc/en/donate';
82+
this.windowService.openNewWindow(url, { external: true });
83+
};
84+
85+
private async updateTitleVersion(): Promise<void> {
86+
const appInfo = await this.appService.info();
87+
const { appVersion } = appInfo;
88+
89+
if (appVersion) {
90+
this.titleNode.innerHTML = nls.localize(
91+
'arduino/versionWelcome/titleWithVersion',
92+
'Welcome to the new Arduino IDE {0}!',
93+
appVersion
94+
);
95+
}
96+
}
97+
98+
protected override onAfterAttach(msg: Message): void {
99+
this.update();
100+
this.appendButtons();
101+
this.updateTitleVersion();
102+
super.onAfterAttach(msg);
103+
}
104+
}

arduino-ide-extension/src/browser/style/index.css

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
@import "./settings-dialog.css";
1111
@import "./firmware-uploader-dialog.css";
1212
@import "./ide-updater-dialog.css";
13+
@import "./version-welcome-dialog.css";
1314
@import "./certificate-uploader-dialog.css";
1415
@import "./user-fields-dialog.css";
1516
@import "./debug.css";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#version-welcome-dialog-container > .dialogBlock {
2+
width: 546px;
3+
4+
.bold {
5+
font-weight: bold;
6+
}
7+
}

arduino-ide-extension/src/common/protocol/ide-updater.ts

+1
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ export interface IDEUpdaterClient {
7171
}
7272

7373
export const SKIP_IDE_VERSION = 'skipIDEVersion';
74+
export const LAST_USED_IDE_VERSION = 'lastUsedIDEVersion';

i18n/en.json

+8
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@
528528
"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?",
529529
"renameSketchFolderTitle": "Invalid sketch name"
530530
},
531+
"versionWelcome": {
532+
"cancelButton": "Maybe later",
533+
"donateButton": "Donate now",
534+
"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.",
535+
"donateMessage2": "Please consider supporting our work on the free open source Arduino IDE.",
536+
"title": "Welcome to a new version of the Arduino IDE!",
537+
"titleWithVersion": "Welcome to the new Arduino IDE {0}!"
538+
},
531539
"workspace": {
532540
"alreadyExists": "'{0}' already exists."
533541
}

0 commit comments

Comments
 (0)