Check for IDE update at startup (#797)

* Remove check for updates on startup setting

* Remove useless exported function

* Update template-package.json used to package IDE

* Add function to get channel file during packaging step

* Add updates check

* move ide updater on backend

* configure updater options

* add auto update preferences

* TMP check updates on start and download

* index on check-update-startup: fcb8f6e TMP check updates on start and download

* set version to skip on local storage

* add IDE setting to toggle update check on start-up

* comment out check for updates on startup and auto update settings

* Update Theia to 1.22.1

* updated CI

* download changelog and show it in IDE updater dialog

* remove useless file

* remove useless code

* add i18n to updater dialog

* fix i18n

* refactor UpdateInfo typing

* add macos zip to artifacts

* Simply use `--ignore-engines`

* Use correct --ignore-engines

* Fix semver#valid call

* Use C++17

* updated documentation

* add update channel preference

* update updater url

* updated documentation

* Fix the C++ version

* Build flag for cpp

* add disclaimer with correct node version

* Update `electron-builder`

* Fix `Electron.Menu` issue

* Skip electron rebuild

* Rebuild native dependencies beforehand

* Use resolutions section

* Update template-package.json as well

* move ide-updater to electron application

* refactor ide-updater service

* update yarn.lock

* update i18n

* Revert "Add gRPC user agent (#834)"

This reverts commit 5ab3a747a6.

* fix ide download url

* update latest file in CI

* fix i18n check

Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Mark Sujew <msujew@yahoo.de>
This commit is contained in:
Alberto Iannaccone
2022-02-15 18:01:19 +01:00
committed by GitHub
parent 9ecff86bbe
commit f660058c75
30 changed files with 1915 additions and 346 deletions

View File

@@ -68,8 +68,12 @@ import { ArduinoPreferences } from './arduino-preferences';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './contributions/save-as-sketch';
import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
import { IDEUpdater } from '../common/protocol/ide-updater';
const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages';
export const SKIP_IDE_VERSION = 'skipIDEVersion';
@injectable()
export class ArduinoFrontendContribution
@@ -78,8 +82,7 @@ export class ArduinoFrontendContribution
TabBarToolbarContribution,
CommandContribution,
MenuContribution,
ColorContribution
{
ColorContribution {
@inject(ILogger)
protected logger: ILogger;
@@ -157,6 +160,15 @@ export class ArduinoFrontendContribution
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(IDEUpdaterCommands)
protected readonly updater: IDEUpdaterCommands;
@inject(IDEUpdaterDialog)
protected readonly updaterDialog: IDEUpdaterDialog;
@inject(IDEUpdater)
protected readonly updaterService: IDEUpdater;
protected invalidConfigPopup:
| Promise<void | 'No' | 'Yes' | undefined>
| undefined;
@@ -251,7 +263,7 @@ export class ArduinoFrontendContribution
});
}
onStart(app: FrontendApplication): void {
async onStart(app: FrontendApplication): Promise<void> {
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
for (const viewContribution of [
this.fileNavigatorContributions,
@@ -266,6 +278,19 @@ export class ArduinoFrontendContribution
viewContribution.initializeLayout(app);
}
}
this.updaterService.init(
this.arduinoPreferences.get('arduino.ide.updateChannel')
);
this.updater.checkForUpdates().then(async (updateInfo) => {
if (!updateInfo) return;
const versionToSkip = await this.localStorageService.getData<string>(
SKIP_IDE_VERSION
);
if (versionToSkip === updateInfo.version) return;
this.updaterDialog.open(updateInfo);
});
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
if (selectedBoard) {
const { name, fqbn } = selectedBoard;

View File

@@ -262,6 +262,19 @@ import {
UserFieldsDialogWidget,
} from './dialogs/user-fields/user-fields-dialog';
import { nls } from '@theia/core/lib/common';
import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
import {
IDEUpdater,
IDEUpdaterClient,
IDEUpdaterPath,
} from '../common/protocol/ide-updater';
import { IDEUpdaterClientImpl } from './ide-updater/ide-updater-client-impl';
import {
IDEUpdaterDialog,
IDEUpdaterDialogProps,
IDEUpdaterDialogWidget,
} from './dialogs/ide-updater/ide-updater-dialog';
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -407,8 +420,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SerialService)
.toDynamicValue((context) => {
const connection = context.container.get(WebSocketConnectionProvider);
const client =
context.container.get<SerialServiceClient>(SerialServiceClient);
const client = context.container.get<SerialServiceClient>(
SerialServiceClient
);
return connection.createProxy(SerialServicePath, client);
})
.inSingletonScope();
@@ -472,12 +486,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
.inSingletonScope();
rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope();
rebind(TabBarToolbarFactory).toFactory(
({ container: parentContainer }) =>
() => {
const container = parentContainer.createChild();
container.bind(TabBarToolbar).toSelf().inSingletonScope();
return container.get(TabBarToolbar);
}
({ container: parentContainer }) => () => {
const container = parentContainer.createChild();
container.bind(TabBarToolbar).toSelf().inSingletonScope();
return container.get(TabBarToolbar);
}
);
bind(OutputWidget).toSelf().inSingletonScope();
rebind(TheiaOutputWidget).toService(OutputWidget);
@@ -642,13 +655,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Enable the dirty indicator on uncloseable widgets.
rebind(TabBarRendererFactory).toFactory((context) => () => {
const contextMenuRenderer =
context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
const contextMenuRenderer = context.container.get<ContextMenuRenderer>(
ContextMenuRenderer
);
const decoratorService = context.container.get<TabBarDecoratorService>(
TabBarDecoratorService
);
const iconThemeService =
context.container.get<IconThemeService>(IconThemeService);
const iconThemeService = context.container.get<IconThemeService>(
IconThemeService
);
return new TabBarRenderer(
contextMenuRenderer,
decoratorService,
@@ -756,9 +771,32 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
title: 'UploadCertificate',
});
bind(IDEUpdaterDialogWidget).toSelf().inSingletonScope();
bind(IDEUpdaterDialog).toSelf().inSingletonScope();
bind(IDEUpdaterDialogProps).toConstantValue({
title: 'IDEUpdater',
});
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
bind(UserFieldsDialog).toSelf().inSingletonScope();
bind(UserFieldsDialogProps).toConstantValue({
title: 'UserFields',
});
bind(IDEUpdaterCommands).toSelf().inSingletonScope();
bind(CommandContribution).toService(IDEUpdaterCommands);
// Frontend binding for the IDE Updater service
bind(IDEUpdaterClientImpl).toSelf().inSingletonScope();
bind(IDEUpdaterClient).toService(IDEUpdaterClientImpl);
bind(IDEUpdater)
.toDynamicValue((context) => {
const client = context.container.get(IDEUpdaterClientImpl);
return ElectronIpcConnectionProvider.createProxy(
context.container,
IDEUpdaterPath,
client
);
})
.inSingletonScope();
});

View File

@@ -9,6 +9,11 @@ import {
import { nls } from '@theia/core/lib/common';
import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
export enum UpdateChannel {
Stable = 'stable',
Nightly = 'nightly',
}
export const ArduinoConfigSchema: PreferenceSchema = {
type: 'object',
properties: {
@@ -64,13 +69,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
),
default: 0,
},
'arduino.ide.autoUpdate': {
type: 'boolean',
'arduino.ide.updateChannel': {
type: 'string',
enum: Object.values(UpdateChannel) as UpdateChannel[],
default: UpdateChannel.Stable,
description: nls.localize(
'arduino/preferences/ide.autoUpdate',
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.'
'arduino/preferences/ide.updateChannel',
"Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
),
default: true,
},
'arduino.board.certificates': {
type: 'string',
@@ -171,7 +177,7 @@ export interface ArduinoConfiguration {
'arduino.upload.verify': boolean;
'arduino.window.autoScale': boolean;
'arduino.window.zoomLevel': number;
'arduino.ide.autoUpdate': boolean;
'arduino.ide.updateChannel': UpdateChannel;
'arduino.board.certificates': string;
'arduino.sketchbook.showAllFiles': boolean;
'arduino.cloud.enabled': boolean;
@@ -188,16 +194,10 @@ export interface ArduinoConfiguration {
export const ArduinoPreferences = Symbol('ArduinoPreferences');
export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
export function createArduinoPreferences(
preferences: PreferenceService
): ArduinoPreferences {
return createPreferenceProxy(preferences, ArduinoConfigSchema);
}
export function bindArduinoPreferences(bind: interfaces.Bind): void {
bind(ArduinoPreferences).toDynamicValue((ctx) => {
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
return createArduinoPreferences(preferences);
return createPreferenceProxy(preferences, ArduinoConfigSchema);
});
bind(PreferenceContribution).toConstantValue({
schema: ArduinoConfigSchema,

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
export type ProgressBarProps = {
percent?: number;
showPercentage?: boolean;
};
export default function ProgressBar({
percent = 0,
showPercentage = false,
}: ProgressBarProps): React.ReactElement {
const roundedPercent = Math.round(percent);
return (
<div className="progress-bar">
<div className="progress-bar--outer">
<div
className="progress-bar--inner"
style={{ width: `${roundedPercent}%` }}
/>
</div>
{showPercentage && (
<div className="progress-bar--percentage">
<div className="progress-bar--percentage-text">{roundedPercent}%</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,159 @@
import { nls } from '@theia/core/lib/common';
import { shell } from 'electron';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ReactMarkdown from 'react-markdown';
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
import ProgressBar from '../../components/ProgressBar';
export type IDEUpdaterComponentProps = {
updateInfo: UpdateInfo;
downloadFinished?: boolean;
downloadStarted?: boolean;
progress?: ProgressInfo;
error?: Error;
onDownload: () => void;
onClose: () => void;
onSkipVersion: () => void;
onCloseAndInstall: () => void;
};
export const IDEUpdaterComponent = ({
updateInfo: { version, releaseNotes },
downloadStarted = false,
downloadFinished = false,
progress,
error,
onDownload,
onClose,
onSkipVersion,
onCloseAndInstall,
}: IDEUpdaterComponentProps): React.ReactElement => {
const changelogDivRef = React.useRef() as React.MutableRefObject<
HTMLDivElement
>;
React.useEffect(() => {
if (!!releaseNotes) {
let changelog: string;
if (typeof releaseNotes === 'string') changelog = releaseNotes;
else
changelog = releaseNotes.reduce((acc, item) => {
return item.note ? (acc += `${item.note}\n\n`) : acc;
}, '');
ReactDOM.render(
<ReactMarkdown
components={{
a: ({ href, children, ...props }) => (
<a onClick={() => href && shell.openExternal(href)} {...props}>
{children}
</a>
),
}}
>
{changelog}
</ReactMarkdown>,
changelogDivRef.current
);
}
}, [releaseNotes]);
const closeButton = (
<button onClick={onClose} type="button" className="theia-button secondary">
{nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
</button>
);
return (
<div className="ide-updater-dialog--content">
{downloadFinished ? (
<div className="ide-updater-dialog--downloaded">
<div>
{nls.localize(
'arduino/ide-updater/versionDownloaded',
'Arduino IDE {0} has been downloaded.',
version
)}
</div>
<div>
{nls.localize(
'arduino/ide-updater/closeToInstallNotice',
'Close the software and install the update on your machine.'
)}
</div>
<div className="buttons-container">
{closeButton}
<button
onClick={onCloseAndInstall}
type="button"
className="theia-button close-and-install"
>
{nls.localize(
'arduino/ide-updater/closeAndInstallButton',
'Close and Install'
)}
</button>
</div>
</div>
) : downloadStarted ? (
<div className="ide-updater-dialog--downloading">
<div>
{nls.localize(
'arduino/ide-updater/downloadingNotice',
'Downloading the latest version of the Arduino IDE.'
)}
</div>
<ProgressBar percent={progress?.percent} showPercentage />
</div>
) : (
<div className="ide-updater-dialog--pre-download">
<div className="ide-updater-dialog--logo-container">
<div className="ide-updater-dialog--logo"></div>
</div>
<div className="ide-updater-dialog--new-version-text dialogSection">
<div className="dialogRow">
<div className="bold">
{nls.localize(
'arduino/ide-updater/updateAvailable',
'Update Available'
)}
</div>
</div>
<div className="dialogRow">
{nls.localize(
'arduino/ide-updater/newVersionAvailable',
'A new version of Arduino IDE ({0}) is available for download.',
version
)}
</div>
{releaseNotes && (
<div className="dialogRow">
<div className="changelog-container" ref={changelogDivRef} />
</div>
)}
<div className="buttons-container">
<button
onClick={onSkipVersion}
type="button"
className="theia-button secondary skip-version"
>
{nls.localize(
'arduino/ide-updater/skipVersionButton',
'Skip Version'
)}
</button>
<div className="push"></div>
{closeButton}
<button
onClick={onDownload}
type="button"
className="theia-button primary"
>
{nls.localize('arduino/ide-updater/downloadButton', 'Download')}
</button>
</div>
</div>
</div>
)}
{!!error && <div className="error-container">{error}</div>}
</div>
);
};

View File

@@ -0,0 +1,166 @@
import * as React from 'react';
import { inject, injectable } from 'inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { nls } from '@theia/core';
import { IDEUpdaterComponent } from './ide-updater-component';
import { IDEUpdaterCommands } from '../../ide-updater/ide-updater-commands';
import {
IDEUpdaterClient,
ProgressInfo,
UpdateInfo,
} from '../../../common/protocol/ide-updater';
import { LocalStorageService } from '@theia/core/lib/browser';
import { SKIP_IDE_VERSION } from '../../arduino-frontend-contribution';
@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(IDEUpdaterCommands)
protected readonly updater: IDEUpdaterCommands;
@inject(IDEUpdaterClient)
protected readonly updaterClient: IDEUpdaterClient;
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
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();
});
}
async onSkipVersion(): Promise<void> {
this.localStorageService.setData<string>(
SKIP_IDE_VERSION,
this.updateInfo.version
);
this.close();
}
close(): void {
super.close();
this.onClose();
}
onDispose(): void {
if (this.downloadStarted && !this.downloadFinished)
this.updater.stopDownload();
}
async onDownload(): Promise<void> {
this.progressInfo = undefined;
this.downloadStarted = true;
this.error = undefined;
this.updater.downloadUpdate();
this.update();
}
onCloseAndInstall(): void {
this.updater.quitAndInstall();
}
protected render(): React.ReactNode {
return !!this.updateInfo ? (
<form>
<IDEUpdaterComponent
updateInfo={this.updateInfo}
downloadStarted={this.downloadStarted}
downloadFinished={this.downloadFinished}
progress={this.progressInfo}
onClose={this.close.bind(this)}
onSkipVersion={this.onSkipVersion.bind(this)}
onDownload={this.onDownload.bind(this)}
onCloseAndInstall={this.onCloseAndInstall.bind(this)}
/>
</form>
) : null;
}
}
@injectable()
export class IDEUpdaterDialogProps extends DialogProps {}
@injectable()
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
@inject(IDEUpdaterDialogWidget)
protected readonly widget: IDEUpdaterDialogWidget;
constructor(
@inject(IDEUpdaterDialogProps)
protected readonly props: IDEUpdaterDialogProps
) {
super({
title: nls.localize(
'arduino/updater/ideUpdaterDialog',
'Software Update'
),
});
this.contentNode.classList.add('ide-updater-dialog');
this.acceptButton = undefined;
}
get value(): UpdateInfo {
return this.widget.updateInfo;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
super.onAfterAttach(msg);
this.update();
}
async open(
data: UpdateInfo | undefined = undefined
): Promise<UpdateInfo | undefined> {
if (data && data.version) {
this.widget.init(data, this.close.bind(this));
return super.open();
}
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
close(): void {
this.widget.dispose();
super.close();
}
}

View File

@@ -260,18 +260,6 @@ export class SettingsComponent extends React.Component<
'Verify code after upload'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
checked={this.state.checkForUpdates}
onChange={this.checkForUpdatesDidChange}
disabled={true}
/>
{nls.localize(
'arduino/preferences/checkForUpdates',
'Check for updates on startup'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
@@ -444,7 +432,9 @@ export class SettingsComponent extends React.Component<
);
}
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
protected noopKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -454,7 +444,7 @@ export class SettingsComponent extends React.Component<
protected numbersOnlyKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
) => {
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -466,7 +456,7 @@ export class SettingsComponent extends React.Component<
}
};
protected browseSketchbookDidClick = async () => {
protected browseSketchbookDidClick = async (): Promise<void> => {
const uri = await this.props.fileDialogService.showOpenDialog({
title: nls.localize(
'arduino/preferences/newSketchbookLocation',
@@ -483,7 +473,7 @@ export class SettingsComponent extends React.Component<
}
};
protected editAdditionalUrlDidClick = async () => {
protected editAdditionalUrlDidClick = async (): Promise<void> => {
const additionalUrls = await new AdditionalUrlsDialog(
this.state.additionalUrls,
this.props.windowService
@@ -495,7 +485,7 @@ export class SettingsComponent extends React.Component<
protected editorFontSizeDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
if (value) {
this.setState({ editorFontSize: parseInt(value, 10) });
@@ -504,7 +494,7 @@ export class SettingsComponent extends React.Component<
protected additionalUrlsDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({
additionalUrls: event.target.value.split(',').map((url) => url.trim()),
});
@@ -512,13 +502,13 @@ export class SettingsComponent extends React.Component<
protected autoScaleInterfaceDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoScaleInterface: event.target.checked });
};
protected interfaceScaleDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
const percentage = parseInt(value, 10);
if (isNaN(percentage)) {
@@ -532,31 +522,25 @@ export class SettingsComponent extends React.Component<
protected verifyAfterUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verifyAfterUpload: event.target.checked });
};
protected checkForUpdatesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
this.setState({ checkForUpdates: event.target.checked });
};
protected sketchbookShowAllFilesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ sketchbookShowAllFiles: event.target.checked });
};
protected autoSaveDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
};
protected quickSuggestionsOtherDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
// need to persist react events through lifecycle https://reactjs.org/docs/events.html#event-pooling
const newVal = event.target.checked ? true : false;
@@ -570,7 +554,9 @@ export class SettingsComponent extends React.Component<
});
};
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
protected themeDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
): void => {
const { selectedIndex } = event.target.options;
const theme = ThemeService.get().getThemes()[selectedIndex];
if (theme) {
@@ -580,14 +566,14 @@ export class SettingsComponent extends React.Component<
protected languageDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const selectedLanguage = event.target.value;
this.setState({ currentLanguage: selectedLanguage });
};
protected compilerWarningsDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const { selectedIndex } = event.target.options;
const compilerWarnings = CompilerWarningLiterals[selectedIndex];
if (compilerWarnings) {
@@ -597,26 +583,28 @@ export class SettingsComponent extends React.Component<
protected verboseOnCompileDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnCompile: event.target.checked });
};
protected verboseOnUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnUpload: event.target.checked });
};
protected sketchpathDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const sketchbookPath = event.target.value;
if (sketchbookPath) {
this.setState({ sketchbookPath });
}
};
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected noProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (event.target.checked) {
this.setState({ network: 'none' });
} else {
@@ -626,7 +614,7 @@ export class SettingsComponent extends React.Component<
protected manualProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (event.target.checked) {
this.setState({ network: Network.Default() });
} else {
@@ -636,7 +624,7 @@ export class SettingsComponent extends React.Component<
protected httpProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'http' : 'socks';
@@ -646,7 +634,7 @@ export class SettingsComponent extends React.Component<
protected socksProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'socks' : 'http';
@@ -656,7 +644,7 @@ export class SettingsComponent extends React.Component<
protected hostnameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.hostname = event.target.value;
@@ -664,7 +652,9 @@ export class SettingsComponent extends React.Component<
}
};
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected portDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.port = event.target.value;
@@ -674,7 +664,7 @@ export class SettingsComponent extends React.Component<
protected usernameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.username = event.target.value;
@@ -684,7 +674,7 @@ export class SettingsComponent extends React.Component<
protected passwordDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.password = event.target.value;

View File

@@ -18,24 +18,22 @@ import {
import { nls } from '@theia/core/lib/common';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
const EDITOR_SETTING = 'editor';
const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
const ARDUINO_SETTING = 'arduino';
const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
// const IDE_SETTING = `${ARDUINO_SETTING}.ide`;
const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
// const AUTO_UPDATE_SETTING = `${IDE_SETTING}.autoUpdate`;
const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export const EDITOR_SETTING = 'editor';
export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
export const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
export const ARDUINO_SETTING = 'arduino';
export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
export const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
export const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
export const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
export const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
export const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
export const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
export const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
export const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
export const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
export const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export interface Settings extends Index {
editorFontSize: number; // `editor.fontSize`
@@ -48,7 +46,6 @@ export interface Settings extends Index {
autoScaleInterface: boolean; // `arduino.window.autoScale`
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
verboseOnCompile: boolean; // `arduino.compile.verbose`
compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
verboseOnUpload: boolean; // `arduino.upload.verbose`
@@ -93,7 +90,6 @@ export class SettingsService {
@postConstruct()
protected async init(): Promise<void> {
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
const settings = await this.loadSettings();
this._settings = deepClone(settings);
this.ready.resolve();
@@ -110,7 +106,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -135,7 +130,6 @@ export class SettingsService {
}),
this.preferenceService.get<boolean>(AUTO_SCALE_SETTING, true),
this.preferenceService.get<number>(ZOOM_LEVEL_SETTING, 0),
// this.preferenceService.get<string>(AUTO_UPDATE_SETTING, true),
this.preferenceService.get<boolean>(COMPILE_VERBOSE_SETTING, true),
this.preferenceService.get<any>(COMPILE_WARNINGS_SETTING, 'None'),
this.preferenceService.get<boolean>(UPLOAD_VERBOSE_SETTING, true),
@@ -154,7 +148,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -234,7 +227,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -284,7 +276,6 @@ export class SettingsService {
interfaceScale,
PreferenceScope.User
),
// this.preferenceService.set(AUTO_UPDATE_SETTING, checkForUpdates, PreferenceScope.User),
this.preferenceService.set(
COMPILE_VERBOSE_SETTING,
verboseOnCompile,

View File

@@ -0,0 +1,40 @@
import { Emitter } from '@theia/core';
import { injectable } from '@theia/core/shared/inversify';
import { UpdateInfo, ProgressInfo } from 'electron-updater';
import { IDEUpdaterClient } from '../../common/protocol/ide-updater';
@injectable()
export class IDEUpdaterClientImpl implements IDEUpdaterClient {
protected readonly onErrorEmitter = new Emitter<Error>();
protected readonly onCheckingForUpdateEmitter = new Emitter<void>();
protected readonly onUpdateAvailableEmitter = new Emitter<UpdateInfo>();
protected readonly onUpdateNotAvailableEmitter = new Emitter<UpdateInfo>();
protected readonly onDownloadProgressEmitter = new Emitter<ProgressInfo>();
protected readonly onDownloadFinishedEmitter = new Emitter<UpdateInfo>();
readonly onError = this.onErrorEmitter.event;
readonly onCheckingForUpdate = this.onCheckingForUpdateEmitter.event;
readonly onUpdateAvailable = this.onUpdateAvailableEmitter.event;
readonly onUpdateNotAvailable = this.onUpdateNotAvailableEmitter.event;
readonly onDownloadProgressChanged = this.onDownloadProgressEmitter.event;
readonly onDownloadFinished = this.onDownloadFinishedEmitter.event;
notifyError(message: Error): void {
this.onErrorEmitter.fire(message);
}
notifyCheckingForUpdate(message: void): void {
this.onCheckingForUpdateEmitter.fire(message);
}
notifyUpdateAvailable(message: UpdateInfo): void {
this.onUpdateAvailableEmitter.fire(message);
}
notifyUpdateNotAvailable(message: UpdateInfo): void {
this.onUpdateNotAvailableEmitter.fire(message);
}
notifyDownloadProgressChanged(message: ProgressInfo): void {
this.onDownloadProgressEmitter.fire(message);
}
notifyDownloadFinished(message: UpdateInfo): void {
this.onDownloadFinishedEmitter.fire(message);
}
}

View File

@@ -0,0 +1,71 @@
import {
Command,
CommandContribution,
CommandRegistry,
MessageService,
} from '@theia/core';
import { injectable, inject } from 'inversify';
import { IDEUpdater, UpdateInfo } from '../../common/protocol/ide-updater';
@injectable()
export class IDEUpdaterCommands implements CommandContribution {
constructor(
@inject(IDEUpdater)
private readonly updater: IDEUpdater,
@inject(MessageService)
protected readonly messageService: MessageService
) {}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(IDEUpdaterCommands.CHECK_FOR_UPDATES, {
execute: this.checkForUpdates.bind(this),
});
registry.registerCommand(IDEUpdaterCommands.DOWNLOAD_UPDATE, {
execute: this.downloadUpdate.bind(this),
});
registry.registerCommand(IDEUpdaterCommands.STOP_DOWNLOAD, {
execute: this.stopDownload.bind(this),
});
registry.registerCommand(IDEUpdaterCommands.INSTALL_UPDATE, {
execute: this.quitAndInstall.bind(this),
});
}
async checkForUpdates(): Promise<UpdateInfo | void> {
return await this.updater.checkForUpdates();
}
async downloadUpdate(): Promise<void> {
await this.updater.downloadUpdate();
}
async stopDownload(): Promise<void> {
await this.updater.stopDownload();
}
quitAndInstall(): void {
this.updater.quitAndInstall();
}
}
export namespace IDEUpdaterCommands {
export const CHECK_FOR_UPDATES: Command = {
id: 'arduino-ide-check-for-updates',
category: 'Arduino',
label: 'Check for Arduino IDE updates',
};
export const DOWNLOAD_UPDATE: Command = {
id: 'arduino-ide-download-update',
category: 'Arduino',
label: 'Download Arduino IDE updates',
};
export const STOP_DOWNLOAD: Command = {
id: 'arduino-ide-stop-download',
category: 'Arduino',
label: 'Stop download of Arduino IDE updates',
};
export const INSTALL_UPDATE: Command = {
id: 'arduino-ide-install-update',
category: 'Arduino',
label: 'Install Arduino IDE updates',
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,67 @@
.ide-updater-dialog {
width: 546px;
}
.ide-updater-dialog .bold {
font-weight: bold;
}
.ide-updater-dialog--pre-download {
display: flex;
}
.ide-updater-dialog--logo-container {
margin-right: 28px;
}
.ide-updater-dialog--logo {
background: url('./ide-logo.png') round;
width: 52px;
height: 52px;
}
.dialogContent.ide-updater-dialog
.ide-updater-dialog--content
.ide-updater-dialog--new-version-text.dialogSection {
margin-top: 0;
}
.ide-updater-dialog .changelog-container {
background: white;
border: 1px solid #dae3e3;
border-radius: 2px;
font-size: 12px;
height: 180px;
overflow: auto;
padding: 0 12px;
cursor: text;
}
.ide-updater-dialog .changelog-container a {
color: #018184;
}
.ide-updater-dialog .changelog-container a:hover {
text-decoration: underline;
cursor: pointer;
}
.ide-updater-dialog .changelog-container code {
background: #ecf1f1;
border-radius: 2px;
padding: 0 2px;
}
.ide-updater-dialog .changelog-container a code {
color: #018184;
}
.ide-updater-dialog .buttons-container {
display: flex;
justify-content: flex-end;
margin-top: 28px;
}
.ide-updater-dialog .buttons-container .push {
margin-right: auto;
}

View File

@@ -9,6 +9,7 @@
@import './editor.css';
@import './settings-dialog.css';
@import './firmware-uploader-dialog.css';
@import './ide-updater-dialog.css';
@import './certificate-uploader-dialog.css';
@import './user-fields-dialog.css';
@import './debug.css';
@@ -16,6 +17,7 @@
@import './cloud-sketchbook.css';
@import './fonts.css';
@import './custom-codicon.css';
@import './progress-bar.css';
.theia-input.warning:focus {
outline-width: 1px;

View File

@@ -0,0 +1,32 @@
.progress-bar {
margin-top: 20px;
}
.progress-bar--outer {
background: #e5e5e5;
border-radius: 11px;
height: 6px;
position: relative;
overflow: hidden;
}
.progress-bar--inner {
transition: width 1s;
height: 100%;
background: #008184;
border-radius: 11px;
}
.progress-bar--percentage {
align-items: flex-end;
display: flex;
height: 40px;
justify-content: center;
margin-top: 10px;
width: 100%;
}
.progress-bar--percentage-text {
font-size: 14px;
line-height: 24px;
}