Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
1a55a5c560 build(deps): Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 19:05:27 +00:00
19 changed files with 267 additions and 70 deletions

View File

@@ -340,7 +340,7 @@ jobs:
- name: Install Python 3.x
if: fromJSON(matrix.config.container) == null && runner.name != 'WINDOWS-SIGN-PC'
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.11.x'

View File

@@ -92,7 +92,7 @@ jobs:
# See: https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites
- name: Install Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.11.x'

View File

@@ -46,9 +46,9 @@ See [**the contributor guide**](docs/CONTRIBUTING.md#contributor-guide) for more
See the [**development guide**](docs/development.md) for a technical overview of the application and instructions for building the code.
### Support the project
## Donations
This open source code was written by the Arduino team and is maintained on a daily basis with the help of the community. We invest a considerable amount of time in development, testing and optimization. Please consider [buying original Arduino boards](https://store.arduino.cc/) to support our work on the project.
This open source code was written by the Arduino team and is maintained on a daily basis with the help of the community. We invest a considerable amount of time in development, testing and optimization. Please consider [donating](https://www.arduino.cc/en/donate/) or [sponsoring](https://github.com/sponsors/arduino) to support our work, as well as [buying original Arduino boards](https://store.arduino.cc/) which is the best way to make sure our effort can continue in the long term.
## License

View File

@@ -67,6 +67,7 @@
"cross-fetch": "^3.1.5",
"dateformat": "^3.0.3",
"deepmerge": "^4.2.2",
"dompurify": "^2.4.7",
"drivelist": "^9.2.4",
"electron-updater": "^4.6.5",
"fast-deep-equal": "^3.1.3",

View File

@@ -368,6 +368,10 @@ import { DebugConfigurationWidget } from './theia/debug/debug-configuration-widg
import { DebugConfigurationWidget as TheiaDebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
import {
VersionWelcomeDialog,
VersionWelcomeDialogProps,
} from './dialogs/version-welcome-dialog';
import { TestViewContribution as TheiaTestViewContribution } from '@theia/test/lib/browser/view/test-view-contribution';
import { TestViewContribution } from './theia/test/test-view-contribution';
@@ -983,6 +987,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',

View File

@@ -8,6 +8,7 @@ import {
} 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 } from 'semver';
@@ -19,6 +20,9 @@ export class CheckForIDEUpdates extends Contribution {
@inject(IDEUpdaterDialog)
private readonly updaterDialog: IDEUpdaterDialog;
@inject(VersionWelcomeDialog)
private readonly versionWelcomeDialog: VersionWelcomeDialog;
@inject(LocalStorageService)
private readonly localStorage: LocalStorageService;
@@ -55,8 +59,13 @@ export class CheckForIDEUpdates extends Contribution {
return this.updater.checkForUpdates(true);
})
.then(async (updateInfo) => {
if (!updateInfo) return;
if (!updateInfo) {
const isNewVersion = await this.isNewStableVersion();
if (isNewVersion) {
this.versionWelcomeDialog.open();
}
return;
}
const versionToSkip = await this.localStorage.getData<string>(
SKIP_IDE_VERSION
);
@@ -77,11 +86,6 @@ export class CheckForIDEUpdates extends Contribution {
});
}
/**
* This value is set in localStorage but currently not used.
* We keep this logic running anyway for eventual future needs
* (eg. show a new version welcome dialog)
*/
private async setCurrentIDEVersion(): Promise<void> {
try {
const { appVersion } = await this.appService.info();
@@ -91,4 +95,29 @@ export class CheckForIDEUpdates extends Contribution {
// ignore invalid versions
}
}
/**
* Check if user is running a new IDE version for the first time.
* @returns true if the current IDE version is greater than the last used version
* and both are non-prerelease versions.
*/
private async isNewStableVersion(): Promise<boolean> {
try {
const { appVersion } = await this.appService.info();
const prevVersion = await this.localStorage.getData<string>(
LAST_USED_IDE_VERSION
);
const prevSemVer = new SemVer(prevVersion ?? '');
const currSemVer = new SemVer(appVersion ?? '');
if (prevSemVer.prerelease.length || currSemVer.prerelease.length) {
return false;
}
return currSemVer.compare(prevSemVer) === 1;
} catch (e) {
return false;
}
}
}

View File

@@ -17,6 +17,7 @@ import {
} from '../../../common/protocol/ide-updater';
import { LocalStorageService } from '@theia/core/lib/browser';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { sanitize } from 'dompurify';
@injectable()
export class IDEUpdaterDialogProps extends DialogProps {}
@@ -165,6 +166,51 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
goToDownloadPageButton.focus();
}
private appendDonateFooter() {
const footer = document.createElement('div');
footer.classList.add('ide-updater-dialog--footer');
const footerContent = document.createElement('div');
footerContent.classList.add('ide-updater-dialog--footer-content');
footer.appendChild(footerContent);
const footerLink = document.createElement('a');
footerLink.innerText = sanitize(
nls.localize('arduino/ide-updater/donateLinkText', 'donate to support us')
);
footerLink.classList.add('ide-updater-dialog--footer-link');
footerLink.onclick = () =>
this.openExternal('https://www.arduino.cc/en/donate');
const footerLinkIcon = document.createElement('span');
footerLinkIcon.title = nls.localize(
'arduino/ide-updater/donateLinkIconTitle',
'open donation page'
);
footerLinkIcon.classList.add('ide-updater-dialog--footer-link-icon');
footerLink.appendChild(footerLinkIcon);
const placeholderKey = '%%link%%';
const footerText = sanitize(
nls.localize(
'arduino/ide-updater/donateText',
'Open source is love, {0}',
placeholderKey
)
);
const placeholder = footerText.indexOf(placeholderKey);
if (placeholder !== -1) {
const parts = footerText.split(placeholderKey);
footerContent.appendChild(document.createTextNode(parts[0]));
footerContent.appendChild(footerLink);
footerContent.appendChild(document.createTextNode(parts[1]));
} else {
footerContent.appendChild(document.createTextNode(footerText));
footerContent.appendChild(footerLink);
}
this.controlPanel.insertAdjacentElement('afterend', footer);
}
private openDownloadPage(): void {
this.openExternal('https://www.arduino.cc/en/software');
this.close();
@@ -187,6 +233,7 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
downloadStarted: true,
});
this.clearButtons();
this.appendDonateFooter();
this.updater.downloadUpdate();
}

View File

@@ -0,0 +1,107 @@
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';
import { sanitize } from 'dompurify';
@injectable()
export class VersionWelcomeDialogProps extends DialogProps {}
@injectable()
export class VersionWelcomeDialog extends ReactDialog<void> {
@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 (
<div>
<p>
{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.'
)}
</p>
<p className="bold">
{nls.localize(
'arduino/versionWelcome/donateMessage2',
'Please consider supporting our work on the free open source Arduino IDE.'
)}
</p>
</div>
);
}
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.onDonateButtonClick.bind(this), 'click');
this.controlPanel.appendChild(donateButton);
donateButton.focus();
}
private onDonateButtonClick(): void {
this.openDonationPage();
this.close();
}
private readonly openDonationPage = () => {
const url = 'https://www.arduino.cc/en/donate';
this.windowService.openNewWindow(url, { external: true });
};
private async updateTitleVersion(): Promise<void> {
const appInfo = await this.appService.info();
const { appVersion } = appInfo;
if (appVersion) {
this.titleNode.innerText = sanitize(
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);
}
}

View File

@@ -67,7 +67,3 @@ export function truncateLines(
}
return [lines, charCount];
}
export function joinLines(lines: Line[]): string {
return lines.map((line: Line) => line.message).join('');
}

View File

@@ -52,9 +52,6 @@ export namespace SerialMonitor {
},
'vscode/output.contribution/clearOutput.label'
);
export const COPY_OUTPUT = {
id: 'serial-monitor-copy-output',
};
}
}
@@ -152,12 +149,6 @@ export class MonitorViewContribution
'Clear Output'
),
});
registry.registerItem({
id: SerialMonitor.Commands.COPY_OUTPUT.id,
command: SerialMonitor.Commands.COPY_OUTPUT.id,
icon: codicon('copy'),
tooltip: nls.localize('arduino/serial/copyOutput', 'Copy Output'),
});
}
override registerCommands(commands: CommandRegistry): void {
@@ -170,15 +161,6 @@ export class MonitorViewContribution
}
},
});
commands.registerCommand(SerialMonitor.Commands.COPY_OUTPUT, {
isEnabled: (widget) => widget instanceof MonitorWidget,
isVisible: (widget) => widget instanceof MonitorWidget,
execute: (widget) => {
if (widget instanceof MonitorWidget) {
widget.copyOutput();
}
},
});
if (this.toggleCommand) {
commands.registerCommand(this.toggleCommand, {
execute: () => this.toggle(),

View File

@@ -28,7 +28,6 @@ import {
import { MonitorModel } from '../../monitor-model';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { serialMonitorWidgetLabel } from '../../../common/nls';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
@injectable()
export class MonitorWidget extends ReactWidget {
@@ -48,7 +47,6 @@ export class MonitorWidget extends ReactWidget {
*/
protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>();
protected readonly copyOutputEmitter = new Emitter<void>();
@inject(MonitorModel)
private readonly monitorModel: MonitorModel;
@@ -58,8 +56,6 @@ export class MonitorWidget extends ReactWidget {
private readonly boardsServiceProvider: BoardsServiceProvider;
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
@inject(ClipboardService)
private readonly clipboardService: ClipboardService;
private readonly toDisposeOnReset: DisposableCollection;
@@ -106,10 +102,6 @@ export class MonitorWidget extends ReactWidget {
this.clearOutputEmitter.fire(undefined);
this.update();
}
copyOutput(): void {
this.copyOutputEmitter.fire();
}
override dispose(): void {
this.toDisposeOnReset.dispose();
@@ -255,8 +247,6 @@ export class MonitorWidget extends ReactWidget {
monitorModel={this.monitorModel}
monitorManagerProxy={this.monitorManagerProxy}
clearConsoleEvent={this.clearOutputEmitter.event}
copyOutputEvent={this.copyOutputEmitter.event}
clipboardService={this.clipboardService}
height={Math.floor(this.widgetHeight - 50)}
/>
</div>

View File

@@ -3,10 +3,9 @@ import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window';
import dateFormat from 'dateformat';
import { messagesToLines, truncateLines, joinLines } from './monitor-utils';
import { messagesToLines, truncateLines } from './monitor-utils';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
export type Line = { message: string; timestamp?: Date; lineLen: number };
@@ -75,9 +74,6 @@ export class SerialMonitorOutput extends React.Component<
this.props.clearConsoleEvent(() =>
this.setState({ lines: [], charCount: 0 })
),
this.props.copyOutputEvent(() =>
this.props.clipboardService.writeText(joinLines(this.state.lines))
),
this.props.monitorModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.monitorModel;
@@ -134,8 +130,6 @@ export namespace SerialMonitorOutput {
readonly monitorModel: MonitorModel;
readonly monitorManagerProxy: MonitorManagerProxyClient;
readonly clearConsoleEvent: Event<void>;
readonly copyOutputEvent: Event<void>;
readonly clipboardService: ClipboardService;
readonly height: number;
}

View File

@@ -34,6 +34,37 @@
min-width: 0;
}
.ide-updater-dialog--footer {
display: inline-block;
margin-top: -16px;
padding: 12px 0 24px 0;
border-top: 1px solid var(--theia-editorWidget-border);
}
.ide-updater-dialog--footer-content {
float: right;
}
.ide-updater-dialog--footer-link {
display: inline-block;
color: var(--theia-textLink-foreground);
font-weight: 500;
line-height: 13px;
}
.ide-updater-dialog--footer-link:hover {
color: var(--theia-textLink-foreground);
cursor: pointer;
}
.ide-updater-dialog--footer-link-icon {
display: inline-block;
-webkit-mask: url(../icons/link-open-icon.svg) center no-repeat;
background-color: var(--theia-textLink-foreground);
height: 12px;
width: 12px;
cursor: pointer;
transform: translateY(2px);
margin-left: 4px;
}
.ide-updater-dialog .changelog {
color: var(--theia-editor-foreground);
background-color: var(--theia-editor-background);
@@ -109,6 +140,7 @@
max-height: 100%;
overflow: hidden;
display: flex;
padding-bottom: 20px !important;
}
#ide-updater-dialog-container .skip-version-button {

View File

@@ -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";

View File

@@ -0,0 +1,7 @@
#version-welcome-dialog-container > .dialogBlock {
width: 546px;
.bold {
font-weight: bold;
}
}

View File

@@ -2,7 +2,6 @@ import { expect } from 'chai';
import {
messagesToLines,
truncateLines,
joinLines,
} from '../../browser/serial/monitor/monitor-utils';
import { Line } from '../../browser/serial/monitor/serial-monitor-send-output';
import { set, reset } from 'mockdate';
@@ -16,7 +15,6 @@ type TestLine = {
charCount: number;
maxCharacters?: number;
};
expectedJoined?: string;
};
const date = new Date();
@@ -24,7 +22,6 @@ const testLines: TestLine[] = [
{
messages: ['Hello'],
expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 },
expectedJoined: 'Hello',
},
{
messages: ['Hello', 'Dog!'],
@@ -39,7 +36,6 @@ const testLines: TestLine[] = [
],
charCount: 10,
},
expectedJoined: 'Hello\nDog!'
},
{
messages: ['Dog!'],
@@ -71,7 +67,6 @@ const testLines: TestLine[] = [
{ message: "You're a good boy!", lineLen: 8 },
],
},
expectedJoined: "Hello Dog!\n Who's a good boy?\nYou're a good boy!",
},
{
messages: ['boy?\n', "You're a good boy!"],
@@ -121,7 +116,6 @@ const testLines: TestLine[] = [
{ message: 'Yo', lineLen: 2 },
],
},
expectedJoined: "Hello Dog!\nWho's a good boy?\nYo",
},
];
@@ -171,10 +165,6 @@ describe('Monitor Utils', () => {
});
expect(totalCharCount).to.equal(charCount);
}
if (testLine.expectedJoined) {
const joined_str = joinLines(testLine.expected.lines);
expect(joined_str).to.equal(testLine.expectedJoined);
}
});
});
});

View File

@@ -6,20 +6,22 @@ Thanks for your interest in contributing to this project!
There are several ways you can get involved:
| Type of contribution | Contribution method |
| ----------------------------------------- | ---------------------------------------------------------------- |
| - Support<br/>- Question<br/>- Discussion | Post on the [**Arduino Forum**][forum] |
| - Bug report<br/>- Feature request | Issue report (see the guide [**here**][issues]) |
| Testing | Beta testing, PR review (see the guide [**here**][beta-testing]) |
| Translation | See the guide [**here**][translate] |
| - Bug fix<br/>- Enhancement | Pull request (see the guide [**here**][prs]) |
| Monetary | [Buy official products][store] |
| Type of contribution | Contribution method |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| - Support<br/>- Question<br/>- Discussion | Post on the [**Arduino Forum**][forum] |
| - Bug report<br/>- Feature request | Issue report (see the guide [**here**][issues]) |
| Testing | Beta testing, PR review (see the guide [**here**][beta-testing]) |
| Translation | See the guide [**here**][translate] |
| - Bug fix<br/>- Enhancement | Pull request (see the guide [**here**][prs]) |
| Monetary | - [Donate][donate]<br/>- [Sponsor][sponsor]<br/>- [Buy official products][store] |
[forum]: https://forum.arduino.cc
[issues]: contributor-guide/issues.md#issue-report-guide
[beta-testing]: contributor-guide/beta-testing.md#beta-testing-guide
[translate]: contributor-guide/translation.md#translator-guide
[prs]: contributor-guide/pull-requests.md#pull-request-guide
[donate]: https://www.arduino.cc/en/donate/
[sponsor]: https://github.com/sponsors/arduino
[store]: https://store.arduino.cc
## Resources

View File

@@ -275,6 +275,9 @@
"checkForUpdates": "Check for Arduino IDE Updates",
"closeAndInstallButton": "Close and Install",
"closeToInstallNotice": "Close the software and install the update on your machine.",
"donateLinkIconTitle": "open donation page",
"donateLinkText": "donate to support us",
"donateText": "Open source is love, {0}",
"downloadButton": "Download",
"downloadingNotice": "Downloading the latest version of the Arduino IDE.",
"errorCheckingForUpdates": "Error while checking for Arduino IDE updates.\n{0}",
@@ -432,7 +435,6 @@
"autoscroll": "Autoscroll",
"carriageReturn": "Carriage Return",
"connecting": "Connecting to '{0}' on '{1}'...",
"copyOutput": "Copy Output",
"message": "Message (Enter to send message to '{0}' on '{1}')",
"newLine": "New Line",
"newLineCarriageReturn": "Both NL & CR",
@@ -520,6 +522,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."
}

View File

@@ -5912,7 +5912,7 @@ domexception@^4.0.0:
dependencies:
webidl-conversions "^7.0.0"
dompurify@^2.2.9:
dompurify@^2.2.9, dompurify@^2.4.7:
version "2.5.8"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.8.tgz#2809d89d7e528dc7a071dea440d7376df676f824"
integrity sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==