Use eslint&prettier for code linting&formatting

This commit is contained in:
Francesco Stasi
2021-06-16 15:08:48 +02:00
committed by Francesco Stasi
parent 2a3873a923
commit 0592199858
173 changed files with 8963 additions and 3841 deletions

View File

@@ -5,7 +5,6 @@ import { Props } from 'react-select/src/components';
import { ThemeConfig } from 'react-select/src/theme';
export class ArduinoSelect<T> extends Select<T> {
constructor(props: Readonly<Props<T, false>>) {
super(props);
}
@@ -13,51 +12,55 @@ export class ArduinoSelect<T> extends Select<T> {
render(): React.ReactNode {
const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`)
const styles: Styles<T, false> = {
control: styles => ({
control: (styles) => ({
...styles,
minWidth: 120,
color: 'var(--theia-foreground)'
color: 'var(--theia-foreground)',
}),
dropdownIndicator: styles => ({
dropdownIndicator: (styles) => ({
...styles,
padding: 0
padding: 0,
}),
indicatorSeparator: () => ({
display: 'none'
display: 'none',
}),
indicatorsContainer: () => ({
padding: '0px 5px'
padding: '0px 5px',
}),
menu: styles => ({
menu: (styles) => ({
...styles,
marginTop: 0
})
marginTop: 0,
}),
};
const theme: ThemeConfig = theme => ({
const theme: ThemeConfig = (theme) => ({
...theme,
borderRadius: 0,
spacing: {
controlHeight,
baseUnit: 2,
menuGutter: 4
}, colors: {
menuGutter: 4,
},
colors: {
...theme.colors,
// `primary50`??? it's crazy but apparently, without this, we would get a light-blueish
// color when selecting an option in the select by clicking and then not releasing the button.
// https://react-select.com/styles#overriding-the-theme
primary50: 'var(--theia-list-activeSelectionBackground)',
}
},
});
const DropdownIndicator = () => <span className='fa fa-caret-down caret' />;
return <Select
{...this.props}
className='theia-select'
components={{ DropdownIndicator }}
theme={theme}
styles={styles}
classNamePrefix='arduino-select'
isSearchable={false}
/>
const DropdownIndicator = () => (
<span className="fa fa-caret-down caret" />
);
return (
<Select
{...this.props}
className="theia-select"
components={{ DropdownIndicator }}
theme={theme}
styles={styles}
classNamePrefix="arduino-select"
isSearchable={false}
/>
);
}
}

View File

@@ -3,29 +3,34 @@ import { Installable } from '../../../common/protocol/installable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListItemRenderer } from './list-item-renderer';
export class ComponentListItem<T extends ArduinoComponent> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
export class ComponentListItem<
T extends ArduinoComponent
> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
constructor(props: ComponentListItem.Props<T>) {
super(props);
if (props.item.installable) {
const version = props.item.availableVersions.filter(version => version !== props.item.installedVersion)[0];
const version = props.item.availableVersions.filter(
(version) => version !== props.item.installedVersion
)[0];
this.state = {
selectedVersion: version
selectedVersion: version,
};
}
}
protected async install(item: T): Promise<void> {
const toInstall = this.state.selectedVersion;
const version = this.props.item.availableVersions.filter(version => version !== this.state.selectedVersion)[0];
const version = this.props.item.availableVersions.filter(
(version) => version !== this.state.selectedVersion
)[0];
this.setState({
selectedVersion: version
selectedVersion: version,
});
try {
await this.props.install(item, toInstall);
} catch {
this.setState({
selectedVersion: toInstall
selectedVersion: toInstall,
});
}
}
@@ -47,14 +52,15 @@ export class ComponentListItem<T extends ArduinoComponent> extends React.Compone
this.onVersionChange.bind(this)
);
}
}
export namespace ComponentListItem {
export interface Props<T extends ArduinoComponent> {
readonly item: T;
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly install: (
item: T,
version?: Installable.Version
) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly itemRenderer: ListItemRenderer<T>;
}
@@ -62,5 +68,4 @@ export namespace ComponentListItem {
export interface State {
selectedVersion?: Installable.Version;
}
}

View File

@@ -4,16 +4,17 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ComponentListItem } from './component-list-item';
import { ListItemRenderer } from './list-item-renderer';
export class ComponentList<T extends ArduinoComponent> extends React.Component<ComponentList.Props<T>> {
export class ComponentList<T extends ArduinoComponent> extends React.Component<
ComponentList.Props<T>
> {
protected container?: HTMLElement;
render(): React.ReactNode {
return <div
className={'items-container'}
ref={this.setRef}>
{this.props.items.map(item => this.createItem(item))}
</div>;
return (
<div className={'items-container'} ref={this.setRef}>
{this.props.items.map((item) => this.createItem(item))}
</div>
);
}
componentDidMount(): void {
@@ -24,29 +25,32 @@ export class ComponentList<T extends ArduinoComponent> extends React.Component<C
protected setRef = (element: HTMLElement | null) => {
this.container = element || undefined;
}
};
protected createItem(item: T): React.ReactNode {
return <ComponentListItem<T>
key={this.props.itemLabel(item)}
item={item}
itemRenderer={this.props.itemRenderer}
install={this.props.install}
uninstall={this.props.uninstall} />
return (
<ComponentListItem<T>
key={this.props.itemLabel(item)}
item={item}
itemRenderer={this.props.itemRenderer}
install={this.props.install}
uninstall={this.props.uninstall}
/>
);
}
}
export namespace ComponentList {
export interface Props<T extends ArduinoComponent> {
readonly items: T[];
readonly itemLabel: (item: T) => string;
readonly itemDeprecated: (item: T) => boolean;
readonly itemRenderer: ListItemRenderer<T>;
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly install: (
item: T,
version?: Installable.Version
) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly resolveContainer: (element: HTMLElement) => void;
}
}

View File

@@ -13,20 +13,26 @@ import { ComponentList } from './component-list';
import { ListItemRenderer } from './list-item-renderer';
import { ResponseServiceImpl } from '../../response-service-impl';
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
export class FilterableListContainer<
T extends ArduinoComponent
> extends React.Component<
FilterableListContainer.Props<T>,
FilterableListContainer.State<T>
> {
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
super(props);
this.state = {
filterText: '',
items: []
items: [],
};
}
componentDidMount(): void {
this.search = debounce(this.search, 500);
this.handleFilterTextChange('');
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
this.props.filterTextChangeEvent(
this.handleFilterTextChange.bind(this)
);
}
componentDidUpdate(): void {
@@ -36,11 +42,13 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
}
render(): React.ReactNode {
return <div className={'filterable-list-container'}>
{this.renderSearchFilter()}
{this.renderSearchBar()}
{this.renderComponentList()}
</div>
return (
<div className={'filterable-list-container'}>
{this.renderSearchFilter()}
{this.renderSearchBar()}
{this.renderComponentList()}
</div>
);
}
protected renderSearchFilter(): React.ReactNode {
@@ -48,56 +56,67 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
}
protected renderSearchBar(): React.ReactNode {
return <SearchBar
resolveFocus={this.props.resolveFocus}
filterText={this.state.filterText}
onFilterTextChanged={this.handleFilterTextChange}
/>
return (
<SearchBar
resolveFocus={this.props.resolveFocus}
filterText={this.state.filterText}
onFilterTextChanged={this.handleFilterTextChange}
/>
);
}
protected renderComponentList(): React.ReactNode {
const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } = this.props;
return <ComponentList<T>
items={this.state.items}
itemLabel={itemLabel}
itemDeprecated={itemDeprecated}
itemRenderer={itemRenderer}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
resolveContainer={resolveContainer}
/>
const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } =
this.props;
return (
<ComponentList<T>
items={this.state.items}
itemLabel={itemLabel}
itemDeprecated={itemDeprecated}
itemRenderer={itemRenderer}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
resolveContainer={resolveContainer}
/>
);
}
protected handleFilterTextChange = (filterText: string = this.state.filterText) => {
protected handleFilterTextChange = (
filterText: string = this.state.filterText
) => {
this.setState({ filterText });
this.search(filterText);
}
};
protected search(query: string): void {
const { searchable } = this.props;
searchable.search({ query: query.trim() }).then(items => this.setState({ items: this.sort(items) }));
searchable
.search({ query: query.trim() })
.then((items) => this.setState({ items: this.sort(items) }));
}
protected sort(items: T[]): T[] {
// debugger;
const { itemLabel, itemDeprecated } = this.props;
return items.sort((left, right) => {
// always put deprecated items at the bottom of the list
if (itemDeprecated(left)) {
return 1;
}
return itemLabel(left).localeCompare(itemLabel(right))
return itemLabel(left).localeCompare(itemLabel(right));
});
}
protected async install(item: T, version: Installable.Version): Promise<void> {
protected async install(
item: T,
version: Installable.Version
): Promise<void> {
const { install, searchable } = this.props;
await Installable.doWithProgress({
...this.props,
progressText: `Processing ${item.name}:${version}`,
run: ({ progressId }) => install({ item, progressId, version })
run: ({ progressId }) => install({ item, progressId, version }),
});
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
@@ -108,7 +127,7 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
title: 'Uninstall',
msg: `Do you want to uninstall ${item.name}?`,
ok: 'Yes',
cancel: 'No'
cancel: 'No',
}).open();
if (!ok) {
return;
@@ -116,17 +135,17 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
const { uninstall, searchable } = this.props;
await Installable.doWithProgress({
...this.props,
progressText: `Processing ${item.name}${item.installedVersion ? `:${item.installedVersion}` : ''}`,
run: ({ progressId }) => uninstall({ item, progressId })
progressText: `Processing ${item.name}${
item.installedVersion ? `:${item.installedVersion}` : ''
}`,
run: ({ progressId }) => uninstall({ item, progressId }),
});
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
}
}
export namespace FilterableListContainer {
export interface Props<T extends ArduinoComponent> {
readonly container: ListWidget<T>;
readonly searchable: Searchable<T>;
@@ -138,8 +157,22 @@ export namespace FilterableListContainer {
readonly filterTextChangeEvent: Event<string | undefined>;
readonly messageService: MessageService;
readonly responseService: ResponseServiceImpl;
readonly install: ({ item, progressId, version }: { item: T, progressId: string, version: Installable.Version }) => Promise<void>;
readonly uninstall: ({ item, progressId }: { item: T, progressId: string }) => Promise<void>;
readonly install: ({
item,
progressId,
version,
}: {
item: T;
progressId: string;
version: Installable.Version;
}) => Promise<void>;
readonly uninstall: ({
item,
progressId,
}: {
item: T;
progressId: string;
}) => Promise<void>;
readonly commandService: CommandService;
}
@@ -147,5 +180,4 @@ export namespace FilterableListContainer {
filterText: string;
items: T[];
}
}

View File

@@ -7,17 +7,18 @@ import { ComponentListItem } from './component-list-item';
@injectable()
export class ListItemRenderer<T extends ArduinoComponent> {
@inject(WindowService)
protected windowService: WindowService;
protected onMoreInfoClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
protected onMoreInfoClick = (
event: React.SyntheticEvent<HTMLAnchorElement, Event>
) => {
const { target } = event.nativeEvent;
if (target instanceof HTMLAnchorElement) {
this.windowService.openNewWindow(target.href, { external: true });
event.nativeEvent.preventDefault();
}
}
};
renderItem(
input: ComponentListItem.State & { item: T },
@@ -25,78 +26,98 @@ export class ListItemRenderer<T extends ArduinoComponent> {
uninstall: (item: T) => Promise<void>,
onVersionChange: (version: Installable.Version) => void
): React.ReactNode {
const { item } = input;
let nameAndAuthor: JSX.Element;
if (item.name && item.author) {
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>{item.author}</span>;
nameAndAuthor = <span>{name} by {author}</span>
const name = <span className="name">{item.name}</span>;
const author = <span className="author">{item.author}</span>;
nameAndAuthor = (
<span>
{name} by {author}
</span>
);
} else if (item.name) {
nameAndAuthor = <span className='name'>{item.name}</span>;
nameAndAuthor = <span className="name">{item.name}</span>;
} else if ((item as any).id) {
nameAndAuthor = <span className='name'>{(item as any).id}</span>;
nameAndAuthor = <span className="name">{(item as any).id}</span>;
} else {
nameAndAuthor = <span className='name'>Unknown</span>;
nameAndAuthor = <span className="name">Unknown</span>;
}
const onClickUninstall = () => uninstall(item);
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed' onClick={onClickUninstall} />
</div>;
const installedVersion = !!item.installedVersion && (
<div className="version-info">
<span className="version">Version {item.installedVersion}</span>
<span className="installed" onClick={onClickUninstall} />
</div>
);
const summary = <div className='summary'>{item.summary}</div>;
const description = <div className='summary'>{item.description}</div>;
const summary = <div className="summary">{item.summary}</div>;
const description = <div className="summary">{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
const moreInfo = !!item.moreInfoLink && (
<a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>
More info
</a>
);
const onClickInstall = () => install(item);
const installButton = item.installable &&
<button className='theia-button install' onClick={onClickInstall}>INSTALL</button>;
const installButton = item.installable && (
<button className="theia-button install" onClick={onClickInstall}>
INSTALL
</button>
);
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const onSelectChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
const version = event.target.value;
if (version) {
onVersionChange(version);
}
}
};
const versions = (() => {
const { availableVersions } = item;
if (availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
return <label>{availableVersions[0]}</label>;
} else {
return <select
className='theia-select'
value={input.selectedVersion}
onChange={onSelectChange}>
{
item.availableVersions
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
.map(version => <option value={version} key={version}>{version}</option>)
}
</select>;
return (
<select
className="theia-select"
value={input.selectedVersion}
onChange={onSelectChange}
>
{item.availableVersions
.filter(
(version) => version !== item.installedVersion
) // Filter the version that is currently installed.
.map((version) => (
<option value={version} key={version}>
{version}
</option>
))}
</select>
);
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
{nameAndAuthor}
{installedVersion}
return (
<div className="component-list-item noselect">
<div className="header">
{nameAndAuthor}
{installedVersion}
</div>
<div className="content">
{summary}
{description}
</div>
<div className="info">{moreInfo}</div>
<div className="footer">
{installButton}
{versions}
</div>
</div>
<div className='content'>
{summary}
{description}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
);
}
}

View File

@@ -5,13 +5,13 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListWidget } from './list-widget';
@injectable()
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
async initializeLayout(): Promise<void> {
}
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
extends AbstractViewContribution<ListWidget<T>>
implements FrontendApplicationContribution
{
async initializeLayout(): Promise<void> {}
registerMenus(): void {
// NOOP
}
}

View File

@@ -8,15 +8,20 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
import { Installable, Searchable, ArduinoComponent } from '../../../common/protocol';
import {
Installable,
Searchable,
ArduinoComponent,
} from '../../../common/protocol';
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
import { NotificationCenter } from '../../notification-center';
import { ResponseServiceImpl } from '../../response-service-impl';
@injectable()
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
export abstract class ListWidget<
T extends ArduinoComponent
> extends ReactWidget {
@inject(MessageService)
protected readonly messageService: MessageService;
@@ -34,7 +39,9 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
*/
protected focusNode: HTMLElement | undefined;
protected readonly deferredContainer = new Deferred<HTMLElement>();
protected readonly filterTextChangeEmitter = new Emitter<string | undefined>();
protected readonly filterTextChangeEmitter = new Emitter<
string | undefined
>();
constructor(protected options: ListWidget.Options<T>) {
super();
@@ -42,13 +49,13 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
this.id = id;
this.title.label = label;
this.title.caption = label;
this.title.iconClass = iconClass
this.title.iconClass = iconClass;
this.title.closable = true;
this.addClass('arduino-list-widget');
this.node.tabIndex = 0; // To be able to set the focus on the widget.
this.scrollOptions = {
suppressScrollX: true
}
suppressScrollX: true,
};
this.toDispose.push(this.filterTextChangeEmitter);
}
@@ -56,9 +63,15 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
protected init(): void {
this.update();
this.toDispose.pushAll([
this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)),
this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)),
this.notificationCenter.onDaemonStopped(() => this.refresh(undefined))
this.notificationCenter.onIndexUpdated(() =>
this.refresh(undefined)
),
this.notificationCenter.onDaemonStarted(() =>
this.refresh(undefined)
),
this.notificationCenter.onDaemonStopped(() =>
this.refresh(undefined)
),
]);
}
@@ -85,29 +98,46 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
this.focusNode = element;
};
protected async install({ item, progressId, version }: { item: T, progressId: string, version: Installable.Version }): Promise<void> {
protected async install({
item,
progressId,
version,
}: {
item: T;
progressId: string;
version: Installable.Version;
}): Promise<void> {
return this.options.installable.install({ item, progressId, version });
}
protected async uninstall({ item, progressId }: { item: T, progressId: string }): Promise<void> {
protected async uninstall({
item,
progressId,
}: {
item: T;
progressId: string;
}): Promise<void> {
return this.options.installable.uninstall({ item, progressId });
}
render(): React.ReactNode {
return <FilterableListContainer<T>
container={this}
resolveContainer={this.deferredContainer.resolve}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
itemLabel={this.options.itemLabel}
itemDeprecated={this.options.itemDeprecated}
itemRenderer={this.options.itemRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event}
messageService={this.messageService}
commandService={this.commandService}
responseService={this.responseService} />;
return (
<FilterableListContainer<T>
container={this}
resolveContainer={this.deferredContainer.resolve}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
itemLabel={this.options.itemLabel}
itemDeprecated={this.options.itemDeprecated}
itemRenderer={this.options.itemRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event}
messageService={this.messageService}
commandService={this.commandService}
responseService={this.responseService}
/>
);
}
/**
@@ -115,7 +145,9 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
*/
refresh(filterText: string | undefined): void {
this.deferredContainer.promise.then(() => this.filterTextChangeEmitter.fire(filterText));
this.deferredContainer.promise.then(() =>
this.filterTextChangeEmitter.fire(filterText)
);
}
updateScrollBar(): void {
@@ -123,7 +155,6 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
this.scrollBar.update();
}
}
}
export namespace ListWidget {

View File

@@ -1,38 +1,39 @@
import * as React from 'react';
export class SearchBar extends React.Component<SearchBar.Props> {
constructor(props: Readonly<SearchBar.Props>) {
super(props);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
}
render(): React.ReactNode {
return <input
return (
<input
ref={this.setRef}
className={`theia-input ${SearchBar.Styles.SEARCH_BAR_CLASS}`}
type='text'
placeholder='Filter your search...'
type="text"
placeholder="Filter your search..."
size={1}
value={this.props.filterText}
onChange={this.handleFilterTextChange}
/>;
/>
);
}
private setRef = (element: HTMLElement | null) => {
if (this.props.resolveFocus) {
this.props.resolveFocus(element || undefined);
}
}
};
private handleFilterTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
private handleFilterTextChange(
event: React.ChangeEvent<HTMLInputElement>
): void {
this.props.onFilterTextChanged(event.target.value);
}
}
export namespace SearchBar {
export interface Props {
filterText: string;
onFilterTextChanged(filterText: string): void;
@@ -42,5 +43,4 @@ export namespace SearchBar {
export namespace Styles {
export const SEARCH_BAR_CLASS = 'search-bar';
}
}