mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-29 17:57:19 +00:00

Use Arduino CLI revision `38479dc`
Closes #43
Closes #82
Closes #1319
Closes #1366
Closes #2143
Closes #2158
Ref: 38479dc706
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
353 lines
10 KiB
TypeScript
353 lines
10 KiB
TypeScript
import { TabBarToolbar } from '@theia/core/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar';
|
|
import { codicon } from '@theia/core/lib/browser/widgets/widget';
|
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
|
import {
|
|
Disposable,
|
|
DisposableCollection,
|
|
} from '@theia/core/lib/common/disposable';
|
|
import { nls } from '@theia/core/lib/common/nls';
|
|
import React from '@theia/core/shared/react';
|
|
import ReactDOM from '@theia/core/shared/react-dom';
|
|
import classNames from 'classnames';
|
|
import { boardIdentifierLabel, Port } from '../../common/protocol';
|
|
import { BoardListItemUI } from '../../common/protocol/board-list';
|
|
import { assertUnreachable } from '../../common/utils';
|
|
import type {
|
|
BoardListUI,
|
|
BoardsServiceProvider,
|
|
} from './boards-service-provider';
|
|
|
|
export interface BoardsDropDownListCoords {
|
|
readonly top: number;
|
|
readonly left: number;
|
|
readonly width: number;
|
|
readonly paddingTop: number;
|
|
}
|
|
|
|
export namespace BoardsDropDown {
|
|
export interface Props {
|
|
readonly coords: BoardsDropDownListCoords | 'hidden';
|
|
readonly boardList: BoardListUI;
|
|
readonly openBoardsConfig: () => void;
|
|
readonly hide: () => void;
|
|
}
|
|
}
|
|
|
|
export class BoardListDropDown extends React.Component<BoardsDropDown.Props> {
|
|
private dropdownElement: HTMLElement;
|
|
private listRef: React.RefObject<HTMLDivElement>;
|
|
|
|
constructor(props: BoardsDropDown.Props) {
|
|
super(props);
|
|
this.listRef = React.createRef();
|
|
let list = document.getElementById('boards-dropdown-container');
|
|
if (!list) {
|
|
list = document.createElement('div');
|
|
list.id = 'boards-dropdown-container';
|
|
document.body.appendChild(list);
|
|
this.dropdownElement = list;
|
|
}
|
|
}
|
|
|
|
override componentDidUpdate(prevProps: BoardsDropDown.Props): void {
|
|
if (prevProps.coords === 'hidden' && this.listRef.current) {
|
|
this.listRef.current.focus();
|
|
}
|
|
}
|
|
|
|
override render(): React.ReactNode {
|
|
return ReactDOM.createPortal(
|
|
this.renderBoardListItems(),
|
|
this.dropdownElement
|
|
);
|
|
}
|
|
|
|
private renderBoardListItems(): React.ReactNode {
|
|
const { coords, boardList } = this.props;
|
|
if (coords === 'hidden') {
|
|
return '';
|
|
}
|
|
const footerLabel = nls.localize(
|
|
'arduino/board/openBoardsConfig',
|
|
'Select other board and port…'
|
|
);
|
|
return (
|
|
<div
|
|
className="arduino-boards-dropdown-list"
|
|
style={{
|
|
position: 'absolute',
|
|
...coords,
|
|
}}
|
|
ref={this.listRef}
|
|
tabIndex={0}
|
|
>
|
|
<div className="arduino-boards-dropdown-list--items-container">
|
|
{boardList.items.map((item, index) =>
|
|
this.renderBoardListItem({
|
|
item,
|
|
selected: index === boardList.selectedIndex,
|
|
})
|
|
)}
|
|
</div>
|
|
<div
|
|
key={footerLabel}
|
|
tabIndex={0}
|
|
className="arduino-boards-dropdown-item arduino-board-dropdown-footer"
|
|
onClick={() => this.props.openBoardsConfig()}
|
|
>
|
|
<div>{footerLabel}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
private readonly onDefaultAction = (item: BoardListItemUI): unknown => {
|
|
const { boardList, hide } = this.props;
|
|
const { type, params } = item.defaultAction;
|
|
hide();
|
|
switch (type) {
|
|
case 'select-boards-config': {
|
|
return boardList.select(params);
|
|
}
|
|
case 'edit-boards-config': {
|
|
return boardList.edit(params);
|
|
}
|
|
default:
|
|
return assertUnreachable(type);
|
|
}
|
|
};
|
|
|
|
private renderBoardListItem({
|
|
item,
|
|
selected,
|
|
}: {
|
|
item: BoardListItemUI;
|
|
selected: boolean;
|
|
}): React.ReactNode {
|
|
const { boardLabel, portLabel, portProtocol, tooltip } = item.labels;
|
|
const port = item.port;
|
|
const onKeyUp = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
this.onDefaultAction(item);
|
|
}
|
|
};
|
|
return (
|
|
<div
|
|
key={`board-item--${Port.keyOf(port)}`}
|
|
className={classNames('arduino-boards-dropdown-item', {
|
|
'arduino-boards-dropdown-item--selected': selected,
|
|
})}
|
|
onClick={() => this.onDefaultAction(item)}
|
|
onKeyUp={onKeyUp}
|
|
tabIndex={0}
|
|
>
|
|
<div
|
|
className={classNames(
|
|
'arduino-boards-dropdown-item--protocol',
|
|
'fa',
|
|
iconNameFromProtocol(portProtocol)
|
|
)}
|
|
/>
|
|
<div className="arduino-boards-dropdown-item--label" title={tooltip}>
|
|
<div className="arduino-boards-dropdown-item--board-header">
|
|
<div className="arduino-boards-dropdown-item--board-label noWrapInfo noselect">
|
|
{boardLabel}
|
|
</div>
|
|
</div>
|
|
<div className="arduino-boards-dropdown-item--port-label noWrapInfo noselect">
|
|
{portLabel}
|
|
</div>
|
|
</div>
|
|
{this.renderActions(item)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
private renderActions(item: BoardListItemUI): React.ReactNode {
|
|
const { boardList, hide } = this.props;
|
|
const { revert, edit } = item.otherActions;
|
|
if (!edit && !revert) {
|
|
return undefined;
|
|
}
|
|
const handleOnClick = (
|
|
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
|
callback: () => void
|
|
) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
hide();
|
|
callback();
|
|
};
|
|
return (
|
|
<div className={TabBarToolbar.Styles.TAB_BAR_TOOLBAR}>
|
|
{edit && (
|
|
<div
|
|
className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} enabled`}
|
|
>
|
|
{
|
|
<div
|
|
id="edit"
|
|
className={codicon('pencil', true)}
|
|
title={nls.localize(
|
|
'arduino/board/editBoardsConfig',
|
|
'Edit Board and Port...'
|
|
)}
|
|
onClick={(event) =>
|
|
handleOnClick(event, () => boardList.edit(edit.params))
|
|
}
|
|
/>
|
|
}
|
|
</div>
|
|
)}
|
|
{revert && (
|
|
<div
|
|
className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} enabled`}
|
|
>
|
|
{
|
|
<div
|
|
id="revert"
|
|
className={codicon('discard', true)}
|
|
title={nls.localize(
|
|
'arduino/board/revertBoardsConfig',
|
|
"Use '{0}' discovered on '{1}'",
|
|
boardIdentifierLabel(revert.params.selectedBoard),
|
|
item.labels.portLabel
|
|
)}
|
|
onClick={(event) =>
|
|
handleOnClick(event, () => boardList.select(revert.params))
|
|
}
|
|
/>
|
|
}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BoardsToolBarItem extends React.Component<
|
|
BoardsToolBarItem.Props,
|
|
BoardsToolBarItem.State
|
|
> {
|
|
static TOOLBAR_ID: 'boards-toolbar';
|
|
|
|
private readonly toDispose: DisposableCollection;
|
|
|
|
constructor(props: BoardsToolBarItem.Props) {
|
|
super(props);
|
|
const { boardList } = props.boardsServiceProvider;
|
|
this.state = {
|
|
boardList,
|
|
coords: 'hidden',
|
|
};
|
|
const listener = () => this.setState({ coords: 'hidden' });
|
|
document.addEventListener('click', listener);
|
|
this.toDispose = new DisposableCollection(
|
|
Disposable.create(() => document.removeEventListener('click', listener))
|
|
);
|
|
}
|
|
|
|
override componentDidMount(): void {
|
|
this.toDispose.push(
|
|
this.props.boardsServiceProvider.onBoardListDidChange((boardList) =>
|
|
this.setState({ boardList })
|
|
)
|
|
);
|
|
}
|
|
|
|
override componentWillUnmount(): void {
|
|
this.toDispose.dispose();
|
|
}
|
|
|
|
private readonly show = (event: React.MouseEvent<HTMLElement>): void => {
|
|
const { currentTarget: element } = event;
|
|
if (element instanceof HTMLElement) {
|
|
if (this.state.coords === 'hidden') {
|
|
const rect = element.getBoundingClientRect();
|
|
this.setState({
|
|
coords: {
|
|
top: rect.top,
|
|
left: rect.left,
|
|
width: rect.width,
|
|
paddingTop: rect.height,
|
|
},
|
|
});
|
|
} else {
|
|
this.setState({ coords: 'hidden' });
|
|
}
|
|
}
|
|
event.stopPropagation();
|
|
event.nativeEvent.stopImmediatePropagation();
|
|
};
|
|
|
|
private readonly hide = () => {
|
|
this.setState({ coords: 'hidden' });
|
|
};
|
|
|
|
override render(): React.ReactNode {
|
|
const { coords, boardList } = this.state;
|
|
const { boardLabel, selected, portProtocol, tooltip } = boardList.labels;
|
|
const protocolIcon = portProtocol
|
|
? iconNameFromProtocol(portProtocol)
|
|
: null;
|
|
const protocolIconClassNames = classNames(
|
|
'arduino-boards-toolbar-item--protocol',
|
|
'fa',
|
|
protocolIcon
|
|
);
|
|
return (
|
|
<React.Fragment>
|
|
<div
|
|
className="arduino-boards-toolbar-item-container"
|
|
title={tooltip}
|
|
onClick={this.show}
|
|
>
|
|
{protocolIcon && <div className={protocolIconClassNames} />}
|
|
<div
|
|
className={classNames(
|
|
'arduino-boards-toolbar-item--label',
|
|
'noWrapInfo',
|
|
'noselect',
|
|
{ 'arduino-boards-toolbar-item--label-connected': selected }
|
|
)}
|
|
>
|
|
{boardLabel}
|
|
</div>
|
|
<div className="fa fa-caret-down caret" />
|
|
</div>
|
|
<BoardListDropDown
|
|
coords={coords}
|
|
boardList={boardList}
|
|
openBoardsConfig={() => boardList.edit({ query: '' })}
|
|
hide={this.hide}
|
|
/>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
export namespace BoardsToolBarItem {
|
|
export interface Props {
|
|
readonly boardsServiceProvider: BoardsServiceProvider;
|
|
readonly commands: CommandRegistry;
|
|
}
|
|
|
|
export interface State {
|
|
boardList: BoardListUI;
|
|
coords: BoardsDropDownListCoords | 'hidden';
|
|
}
|
|
}
|
|
|
|
function iconNameFromProtocol(protocol: string): string {
|
|
switch (protocol) {
|
|
case 'serial':
|
|
return 'fa-arduino-technology-usb';
|
|
case 'network':
|
|
return 'fa-arduino-technology-connection';
|
|
// it is fine to assign dedicated icons to the protocols used by the official boards,
|
|
// but other than that it is best to avoid implementing any special handling
|
|
// for specific protocols in the IDE codebase.
|
|
default:
|
|
return 'fa-arduino-technology-3dimensionscube';
|
|
}
|
|
}
|