mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-12-01 04:47:15 +00:00
fix: propagate monitor errors to the frontend
- Handle when the board's platform is not installed (Closes #1974) - UX: Smoother monitor widget reset (Closes #1985) - Fixed monitor <input> readOnly state (Closes #1984) - Set monitor widget header color (Ref #682) Closes #1508 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
|
||||
import { ArduinoMenus } from '../../menu/arduino-menus';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { Event } from '@theia/core/lib/common/event';
|
||||
import { MonitorModel } from '../../monitor-model';
|
||||
import { MonitorManagerProxyClient } from '../../../common/protocol';
|
||||
|
||||
@@ -84,13 +85,13 @@ export class MonitorViewContribution
|
||||
id: 'monitor-autoscroll',
|
||||
render: () => this.renderAutoScrollButton(),
|
||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
|
||||
});
|
||||
registry.registerItem({
|
||||
id: 'monitor-timestamp',
|
||||
render: () => this.renderTimestampButton(),
|
||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
|
||||
});
|
||||
registry.registerItem({
|
||||
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||
@@ -143,8 +144,7 @@ export class MonitorViewContribution
|
||||
protected async reset(): Promise<void> {
|
||||
const widget = this.tryGetWidget();
|
||||
if (widget) {
|
||||
widget.dispose();
|
||||
await this.openView({ activate: true, reveal: true });
|
||||
widget.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
injectable,
|
||||
inject,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
ReactWidget,
|
||||
Message,
|
||||
@@ -13,9 +20,13 @@ import { SerialMonitorSendInput } from './serial-monitor-send-input';
|
||||
import { SerialMonitorOutput } from './serial-monitor-send-output';
|
||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { MonitorManagerProxyClient } from '../../../common/protocol';
|
||||
import {
|
||||
MonitorEOL,
|
||||
MonitorManagerProxyClient,
|
||||
} from '../../../common/protocol';
|
||||
import { MonitorModel } from '../../monitor-model';
|
||||
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
|
||||
@injectable()
|
||||
export class MonitorWidget extends ReactWidget {
|
||||
@@ -40,40 +51,46 @@ export class MonitorWidget extends ReactWidget {
|
||||
protected closing = false;
|
||||
protected readonly clearOutputEmitter = new Emitter<void>();
|
||||
|
||||
constructor(
|
||||
@inject(MonitorModel)
|
||||
protected readonly monitorModel: MonitorModel,
|
||||
@inject(MonitorModel)
|
||||
private readonly monitorModel: MonitorModel;
|
||||
@inject(MonitorManagerProxyClient)
|
||||
private readonly monitorManagerProxy: MonitorManagerProxyClient;
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(FrontendApplicationStateService)
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
@inject(MonitorManagerProxyClient)
|
||||
protected readonly monitorManagerProxy: MonitorManagerProxyClient,
|
||||
private readonly toDisposeOnReset: DisposableCollection;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
this.id = MonitorWidget.ID;
|
||||
this.title.label = MonitorWidget.LABEL;
|
||||
this.title.iconClass = 'monitor-tab-icon';
|
||||
this.title.closable = true;
|
||||
this.scrollOptions = undefined;
|
||||
this.toDisposeOnReset = new DisposableCollection();
|
||||
this.toDispose.push(this.clearOutputEmitter);
|
||||
this.toDispose.push(
|
||||
Disposable.create(() => this.monitorManagerProxy.disconnect())
|
||||
);
|
||||
}
|
||||
|
||||
protected override onBeforeAttach(msg: Message): void {
|
||||
this.update();
|
||||
this.toDispose.push(this.monitorModel.onChange(() => this.update()));
|
||||
this.getCurrentSettings().then(this.onMonitorSettingsDidChange.bind(this));
|
||||
this.monitorManagerProxy.onMonitorSettingsDidChange(
|
||||
this.onMonitorSettingsDidChange.bind(this)
|
||||
);
|
||||
|
||||
this.monitorManagerProxy.startMonitor();
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDisposeOnReset.dispose();
|
||||
this.toDisposeOnReset.pushAll([
|
||||
Disposable.create(() => this.monitorManagerProxy.disconnect()),
|
||||
this.monitorModel.onChange(() => this.update()),
|
||||
this.monitorManagerProxy.onMonitorSettingsDidChange((event) =>
|
||||
this.updateSettings(event)
|
||||
),
|
||||
]);
|
||||
this.startMonitor();
|
||||
}
|
||||
|
||||
onMonitorSettingsDidChange(settings: MonitorSettings): void {
|
||||
reset(): void {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private updateSettings(settings: MonitorSettings): void {
|
||||
this.settings = {
|
||||
...this.settings,
|
||||
pluggableMonitorSettings: {
|
||||
@@ -90,6 +107,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.toDisposeOnReset.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -122,7 +140,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||
protected onFocusResolved = (element: HTMLElement | undefined): void => {
|
||||
if (this.closing || !this.isAttached) {
|
||||
return;
|
||||
}
|
||||
@@ -132,7 +150,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
);
|
||||
};
|
||||
|
||||
protected get lineEndings(): SerialMonitorOutput.SelectOption<MonitorModel.EOL>[] {
|
||||
protected get lineEndings(): SerialMonitorOutput.SelectOption<MonitorEOL>[] {
|
||||
return [
|
||||
{
|
||||
label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'),
|
||||
@@ -156,11 +174,23 @@ export class MonitorWidget extends ReactWidget {
|
||||
];
|
||||
}
|
||||
|
||||
private getCurrentSettings(): Promise<MonitorSettings> {
|
||||
private async startMonitor(): Promise<void> {
|
||||
await this.appStateService.reachedState('ready');
|
||||
await this.boardsServiceProvider.reconciled;
|
||||
await this.syncSettings();
|
||||
await this.monitorManagerProxy.startMonitor();
|
||||
}
|
||||
|
||||
private async syncSettings(): Promise<void> {
|
||||
const settings = await this.getCurrentSettings();
|
||||
this.updateSettings(settings);
|
||||
}
|
||||
|
||||
private async getCurrentSettings(): Promise<MonitorSettings> {
|
||||
const board = this.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||
const port = this.boardsServiceProvider.boardsConfig.selectedPort;
|
||||
if (!board || !port) {
|
||||
return Promise.resolve(this.settings || {});
|
||||
return this.settings || {};
|
||||
}
|
||||
return this.monitorManagerProxy.getCurrentSettings(board, port);
|
||||
}
|
||||
@@ -171,7 +201,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
: undefined;
|
||||
|
||||
const baudrateOptions = baudrate?.values.map((b) => ({
|
||||
label: b + ' baud',
|
||||
label: nls.localize('arduino/monitor/baudRate', '{0} baud', b),
|
||||
value: b,
|
||||
}));
|
||||
const baudrateSelectedOption = baudrateOptions?.find(
|
||||
@@ -181,7 +211,7 @@ export class MonitorWidget extends ReactWidget {
|
||||
const lineEnding =
|
||||
this.lineEndings.find(
|
||||
(item) => item.value === this.monitorModel.lineEnding
|
||||
) || this.lineEndings[1]; // Defaults to `\n`.
|
||||
) || MonitorEOL.DEFAULT;
|
||||
|
||||
return (
|
||||
<div className="serial-monitor">
|
||||
@@ -228,13 +258,13 @@ export class MonitorWidget extends ReactWidget {
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly onSend = (value: string) => this.doSend(value);
|
||||
protected async doSend(value: string): Promise<void> {
|
||||
protected readonly onSend = (value: string): void => this.doSend(value);
|
||||
protected doSend(value: string): void {
|
||||
this.monitorManagerProxy.send(value);
|
||||
}
|
||||
|
||||
protected readonly onChangeLineEnding = (
|
||||
option: SerialMonitorOutput.SelectOption<MonitorModel.EOL>
|
||||
option: SerialMonitorOutput.SelectOption<MonitorEOL>
|
||||
): void => {
|
||||
this.monitorModel.lineEnding = option.value;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,10 @@ import { DisposableCollection, nls } from '@theia/core/lib/common';
|
||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||
import { MonitorModel } from '../../monitor-model';
|
||||
import { Unknown } from '../../../common/nls';
|
||||
import {
|
||||
isMonitorConnectionError,
|
||||
MonitorConnectionStatus,
|
||||
} from '../../../common/protocol';
|
||||
|
||||
class HistoryList {
|
||||
private readonly items: string[] = [];
|
||||
@@ -62,7 +66,7 @@ export namespace SerialMonitorSendInput {
|
||||
}
|
||||
export interface State {
|
||||
text: string;
|
||||
connected: boolean;
|
||||
connectionStatus: MonitorConnectionStatus;
|
||||
history: HistoryList;
|
||||
}
|
||||
}
|
||||
@@ -75,18 +79,27 @@ export class SerialMonitorSendInput extends React.Component<
|
||||
|
||||
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
|
||||
super(props);
|
||||
this.state = { text: '', connected: true, history: new HistoryList() };
|
||||
this.state = {
|
||||
text: '',
|
||||
connectionStatus: 'not-connected',
|
||||
history: new HistoryList(),
|
||||
};
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onSend = this.onSend.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
override componentDidMount(): void {
|
||||
this.setState({ connected: this.props.monitorModel.connected });
|
||||
this.setState({
|
||||
connectionStatus: this.props.monitorModel.connectionStatus,
|
||||
});
|
||||
this.toDisposeBeforeUnmount.push(
|
||||
this.props.monitorModel.onChange(({ property }) => {
|
||||
if (property === 'connected')
|
||||
this.setState({ connected: this.props.monitorModel.connected });
|
||||
if (property === 'connected' || property === 'connectionStatus') {
|
||||
this.setState({
|
||||
connectionStatus: this.props.monitorModel.connectionStatus,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -97,44 +110,83 @@ export class SerialMonitorSendInput extends React.Component<
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
const status = this.state.connectionStatus;
|
||||
const input = this.renderInput(status);
|
||||
if (status !== 'connecting') {
|
||||
return input;
|
||||
}
|
||||
return <label>{input}</label>;
|
||||
}
|
||||
|
||||
private renderInput(status: MonitorConnectionStatus): React.ReactNode {
|
||||
const inputClassName = this.inputClassName(status);
|
||||
const placeholder = this.placeholder;
|
||||
const readOnly = Boolean(inputClassName);
|
||||
return (
|
||||
<input
|
||||
ref={this.setRef}
|
||||
type="text"
|
||||
className={`theia-input ${this.shouldShowWarning() ? 'warning' : ''}`}
|
||||
placeholder={this.placeholder}
|
||||
value={this.state.text}
|
||||
className={`theia-input ${inputClassName}`}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
title={placeholder}
|
||||
value={readOnly ? '' : this.state.text} // always show the placeholder if cannot edit the <input>
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private inputClassName(
|
||||
status: MonitorConnectionStatus
|
||||
): 'error' | 'warning' | '' {
|
||||
if (isMonitorConnectionError(status)) {
|
||||
return 'error';
|
||||
}
|
||||
if (status === 'connected') {
|
||||
return '';
|
||||
}
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
protected shouldShowWarning(): boolean {
|
||||
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
||||
return !this.state.connected || !board || !port;
|
||||
return !this.state.connectionStatus || !board || !port;
|
||||
}
|
||||
|
||||
protected get placeholder(): string {
|
||||
if (this.shouldShowWarning()) {
|
||||
const status = this.state.connectionStatus;
|
||||
if (isMonitorConnectionError(status)) {
|
||||
return status.errorMessage;
|
||||
}
|
||||
if (status === 'not-connected') {
|
||||
return nls.localize(
|
||||
'arduino/serial/notConnected',
|
||||
'Not connected. Select a board and a port to connect automatically.'
|
||||
);
|
||||
}
|
||||
|
||||
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
||||
const boardLabel = board
|
||||
? Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})
|
||||
: Unknown;
|
||||
const portLabel = port ? port.address : Unknown;
|
||||
if (status === 'connecting') {
|
||||
return nls.localize(
|
||||
'arduino/serial/connecting',
|
||||
"Connecting to '{0}' on '{1}'...",
|
||||
boardLabel,
|
||||
portLabel
|
||||
);
|
||||
}
|
||||
return nls.localize(
|
||||
'arduino/serial/message',
|
||||
"Message (Enter to send message to '{0}' on '{1}')",
|
||||
board
|
||||
? Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})
|
||||
: Unknown,
|
||||
port ? port.address : Unknown
|
||||
boardLabel,
|
||||
portLabel
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user