Skip to content

Commit 8d838fc

Browse files
author
Akos Kitta
committed
PROEDITOR-7: Cloned the Library Manager layout.
To match with the official Arduino editor's UI. Signed-off-by: Akos Kitta <kittaakos@typefox.io>
1 parent cf44fe2 commit 8d838fc

19 files changed

+426
-138
lines changed

arduino-ide-extension/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@theia/workspace": "next",
1919
"@theia/navigator": "next",
2020
"@theia/terminal": "next",
21+
"css-element-queries": "^1.2.0",
2122
"p-queue": "^5.0.0"
2223
},
2324
"scripts": {

arduino-ide-extension/src/browser/arduino-frontend-module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-fac
5050
import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory';
5151
import { SelectBoardDialog, SelectBoardDialogProps } from './boards/select-board-dialog';
5252
import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget';
53+
const ElementQueries = require('css-element-queries/src/ElementQueries');
5354

5455
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
56+
ElementQueries.listen();
57+
ElementQueries.init();
58+
5559
// Commands and toolbar items
5660
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
5761
bind(CommandContribution).toService(ArduinoFrontendContribution);

arduino-ide-extension/src/browser/boards/list-widget.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
66
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
77
import { BoardsService, Board, BoardPackage } from '../../common/protocol/boards-service';
88
import { BoardsNotificationService } from '../boards-notification-service';
9+
import { LibraryService } from '../../common/protocol/library-service';
910

1011
@injectable()
1112
export abstract class ListWidget extends ReactWidget {
@@ -55,7 +56,7 @@ export abstract class ListWidget extends ReactWidget {
5556
getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(),
5657
selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board),
5758
getSelectBoard: () => boardsServiceDelegate.getSelectBoard(),
58-
search: (options: { query?: string }) => boardsServiceDelegate.search(options),
59+
search: (options: { query?: string, props?: LibraryService.Search.Props }) => boardsServiceDelegate.search(options),
5960
install: async (item: BoardPackage) => {
6061
await boardsServiceDelegate.install(item);
6162
this.boardsNotificationService.notifyBoardsInstalled();

arduino-ide-extension/src/browser/components/component-list/component-list-item.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
44

55
export class ComponentListItem extends React.Component<ComponentListItem.Props> {
66

7-
private onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
7+
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
88
const { target } = event.nativeEvent;
99
if (target instanceof HTMLAnchorElement) {
1010
this.props.windowService.openNewWindow(target.href);
1111
event.nativeEvent.preventDefault();
1212
}
1313
}
1414

15-
private async install(item: ArduinoComponent) {
15+
protected async install(item: ArduinoComponent): Promise<void> {
1616
await this.props.install(item);
1717
}
1818

arduino-ide-extension/src/browser/components/component-list/component-list.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
55

66
export class ComponentList extends React.Component<ComponentList.Props> {
77

8+
protected container?: HTMLElement;
9+
810
render(): React.ReactNode {
9-
return <div>
10-
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />)}
11+
return <div
12+
className={'items-container'}
13+
ref={element => this.container = element || undefined}>
14+
{this.props.items.map(item => this.createItem(item))}
1115
</div>;
1216
}
1317

18+
componentDidMount(): void {
19+
if (this.container && this.props.resolveContainer) {
20+
this.props.resolveContainer(this.container);
21+
}
22+
}
23+
24+
protected createItem(item: ArduinoComponent): React.ReactNode {
25+
return <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />
26+
}
27+
1428
}
1529

1630
export namespace ComponentList {
@@ -19,6 +33,7 @@ export namespace ComponentList {
1933
readonly items: ArduinoComponent[];
2034
readonly windowService: WindowService;
2135
readonly install: (comp: ArduinoComponent) => Promise<void>;
36+
readonly resolveContainer?: (element: HTMLElement) => void;
2237
}
2338

2439
}

arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx

+36-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
22
import { WindowService } from '@theia/core/lib/browser/window/window-service';
3-
import { ComponentList } from './component-list';
43
import { SearchBar } from './search-bar';
4+
import { ComponentList } from './component-list';
5+
import { LibraryService } from '../../../common/protocol/library-service';
56
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
67
import { InstallationProgressDialog } from '../installation-progress-dialog';
78

@@ -21,21 +22,37 @@ export class FilterableListContainer extends React.Component<FilterableListConta
2122
}
2223

2324
render(): React.ReactNode {
24-
return <div className={FilterableListContainer.Styles.FILTERABLE_LIST_CONTAINER_CLASS}>
25-
<SearchBar
26-
filterText={this.state.filterText}
27-
onFilterTextChanged={this.handleFilterTextChange}
28-
/>
29-
<ComponentList
30-
items={this.state.items}
31-
install={this.install.bind(this)}
32-
windowService={this.props.windowService}
33-
/>
25+
return <div className={'filterable-list-container'}>
26+
{this.renderSearchFilter()}
27+
{this.renderSearchBar()}
28+
{this.renderComponentList()}
3429
</div>
3530
}
3631

32+
protected renderSearchFilter(): React.ReactNode {
33+
return undefined;
34+
}
35+
36+
protected renderSearchBar(): React.ReactNode {
37+
return <SearchBar
38+
resolveFocus={this.props.resolveFocus}
39+
filterText={this.state.filterText}
40+
onFilterTextChanged={this.handleFilterTextChange}
41+
/>
42+
}
43+
44+
protected renderComponentList(): React.ReactNode {
45+
return <ComponentList
46+
items={this.state.items}
47+
install={this.install.bind(this)}
48+
windowService={this.props.windowService}
49+
resolveContainer={this.props.resolveContainer}
50+
/>
51+
}
52+
3753
private handleFilterTextChange(filterText: string): void {
38-
this.props.service.search({ query: filterText }).then(result => {
54+
const { props } = this.state;
55+
this.props.service.search({ query: filterText, props }).then(result => {
3956
const { items } = result;
4057
this.setState({
4158
filterText,
@@ -45,23 +62,16 @@ export class FilterableListContainer extends React.Component<FilterableListConta
4562
}
4663

4764
protected sort(items: ArduinoComponent[]): ArduinoComponent[] {
48-
return items.sort((a, b) => {
49-
if (a.name < b.name) {
50-
return -1;
51-
} else if (a.name === b.name) {
52-
return 0;
53-
} else {
54-
return 1;
55-
}
56-
});
65+
return items.sort((left, right) => left.name.localeCompare(right.name));
5766
}
5867

5968
protected async install(comp: ArduinoComponent): Promise<void> {
6069
const dialog = new InstallationProgressDialog(comp.name);
6170
dialog.open();
6271
try {
6372
await this.props.service.install(comp);
64-
const { items } = await this.props.service.search({ query: this.state.filterText });
73+
const { props } = this.state;
74+
const { items } = await this.props.service.search({ query: this.state.filterText, props });
6575
this.setState({ items: this.sort(items) });
6676
} finally {
6777
dialog.close();
@@ -75,19 +85,18 @@ export namespace FilterableListContainer {
7585
export interface Props {
7686
readonly service: ComponentSource;
7787
readonly windowService: WindowService;
88+
readonly resolveContainer?: (element: HTMLElement) => void;
89+
readonly resolveFocus?: (element: HTMLElement | undefined) => void;
7890
}
7991

8092
export interface State {
8193
filterText: string;
8294
items: ArduinoComponent[];
83-
}
84-
85-
export namespace Styles {
86-
export const FILTERABLE_LIST_CONTAINER_CLASS = 'filterable-list-container';
95+
props?: LibraryService.Search.Props;
8796
}
8897

8998
export interface ComponentSource {
90-
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
99+
search(req: { query: string, props?: LibraryService.Search.Props }): Promise<{ items: ArduinoComponent[] }>
91100
install(board: ArduinoComponent): Promise<void>;
92101
}
93102

arduino-ide-extension/src/browser/components/component-list/search-bar.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,22 @@ export class SearchBar extends React.Component<SearchBar.Props> {
99

1010
render(): React.ReactNode {
1111
return <input
12+
ref={this.setRef}
1213
className={SearchBar.Styles.SEARCH_BAR_CLASS}
1314
type='text'
14-
placeholder='Search'
15+
placeholder='Filter your search...'
1516
size={1}
1617
value={this.props.filterText}
1718
onChange={this.handleFilterTextChange}
1819
/>;
1920
}
2021

22+
private setRef = (element: HTMLElement | null) => {
23+
if (this.props.resolveFocus) {
24+
this.props.resolveFocus(element || undefined);
25+
}
26+
}
27+
2128
private handleFilterTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
2229
this.props.onFilterTextChanged(event.target.value);
2330
}
@@ -29,6 +36,7 @@ export namespace SearchBar {
2936
export interface Props {
3037
filterText: string;
3138
onFilterTextChanged(filterText: string): void;
39+
readonly resolveFocus?: (element: HTMLElement | undefined) => void;
3240
}
3341

3442
export namespace Styles {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as React from 'react';
2+
import { ComponentListItem } from '../components/component-list/component-list-item';
3+
4+
export class LibraryComponentListItem extends ComponentListItem {
5+
6+
render(): React.ReactNode {
7+
const { item } = this.props;
8+
9+
const name = <span className={'name'}>{item.name}</span>;
10+
const author = <span className={'author'}>by {item.author}</span>;
11+
const installedVersion = !!item.installedVersion && <div className={'version-info'}>
12+
<span className={'version'}>Version {item.installedVersion}</span>
13+
<span className={'installed'}>INSTALLED</span>
14+
</div>;
15+
16+
const summary = <div className={'summary'}>{item.summary}</div>;
17+
18+
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
19+
const install = this.props.install && item.installable && !item.installedVersion &&
20+
<button className={'install'} onClick={this.install.bind(this, item)}>INSTALL</button>;
21+
const versions = (() => {
22+
const { availableVersions } = item;
23+
if (availableVersions.length === 0) {
24+
return undefined;
25+
} else if (availableVersions.length === 1) {
26+
return <label>{availableVersions[0]}</label>
27+
} else {
28+
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
29+
}
30+
})();
31+
32+
return <div className={'component-list-item noselect'}>
33+
<div className={'header'}>
34+
<span>{name} {author}</span>
35+
{installedVersion}
36+
</div>
37+
<div className={'content'}>
38+
{summary}
39+
</div>
40+
<div className={'info'}>
41+
{moreInfo}
42+
</div>
43+
<div className={'footer'}>
44+
{install}
45+
{versions}
46+
</div>
47+
</div>;
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from 'react';
2+
import { ArduinoComponent } from '../../common/protocol/arduino-component';
3+
import { ComponentList } from '../components/component-list/component-list';
4+
import { LibraryComponentListItem } from './library-component-list-item';
5+
6+
export class LibraryComponentList extends ComponentList {
7+
8+
createItem(item: ArduinoComponent): React.ReactNode {
9+
return <LibraryComponentListItem
10+
key={item.name}
11+
item={item}
12+
windowService={this.props.windowService}
13+
install={this.props.install}
14+
/>
15+
}
16+
17+
}

0 commit comments

Comments
 (0)