Skip to content

Commit d769858

Browse files
author
Akos Kitta
committed
initial filter UI
Signed-off-by: Akos Kitta <[email protected]>
1 parent 2e8ce5b commit d769858

File tree

7 files changed

+127
-21
lines changed

7 files changed

+127
-21
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/
327327
import { PreferencesEditorWidget } from './theia/preferences/preference-editor-widget';
328328
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
329329
import { createPreferencesWidgetContainer } from '@theia/preferences/lib/browser/views/preference-widget-bindings';
330+
import {
331+
BoardsFilterRenderer,
332+
LibraryFilterRenderer,
333+
} from './widgets/component-list/filter-renderer';
330334

331335
const registerArduinoThemes = () => {
332336
const themes: MonacoThemeJson[] = [
@@ -368,6 +372,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
368372

369373
// Renderer for both the library and the core widgets.
370374
bind(ListItemRenderer).toSelf().inSingletonScope();
375+
bind(LibraryFilterRenderer).toSelf().inSingletonScope();
376+
bind(BoardsFilterRenderer).toSelf().inSingletonScope();
371377

372378
// Library service
373379
bind(LibraryService)

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ import {
1111
import { ListWidget } from '../widgets/component-list/list-widget';
1212
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
1313
import { nls } from '@theia/core/lib/common';
14+
import { BoardsFilterRenderer } from '../widgets/component-list/filter-renderer';
1415

1516
@injectable()
1617
export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
1718
static WIDGET_ID = 'boards-list-widget';
1819
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
1920

2021
constructor(
21-
@inject(BoardsService) protected service: BoardsService,
22-
@inject(ListItemRenderer)
23-
protected itemRenderer: ListItemRenderer<BoardsPackage>
22+
@inject(BoardsService) service: BoardsService,
23+
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<BoardsPackage>,
24+
@inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer
2425
) {
2526
super({
2627
id: BoardsListWidget.WIDGET_ID,
@@ -31,6 +32,8 @@ export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
3132
itemLabel: (item: BoardsPackage) => item.name,
3233
itemDeprecated: (item: BoardsPackage) => item.deprecated,
3334
itemRenderer,
35+
filterRenderer,
36+
defaultSearchOptions: { query: '', type: 'All' },
3437
});
3538
}
3639

arduino-ide-extension/src/browser/library/library-list-widget.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ListWidget } from '../widgets/component-list/list-widget';
1616
import { Installable } from '../../common/protocol';
1717
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
1818
import { nls } from '@theia/core/lib/common';
19+
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
1920

2021
@injectable()
2122
export class LibraryListWidget extends ListWidget<
@@ -29,9 +30,9 @@ export class LibraryListWidget extends ListWidget<
2930
);
3031

3132
constructor(
32-
@inject(LibraryService) protected service: LibraryService,
33-
@inject(ListItemRenderer)
34-
protected itemRenderer: ListItemRenderer<LibraryPackage>
33+
@inject(LibraryService) private service: LibraryService,
34+
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<LibraryPackage>,
35+
@inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer
3536
) {
3637
super({
3738
id: LibraryListWidget.WIDGET_ID,
@@ -42,6 +43,8 @@ export class LibraryListWidget extends ListWidget<
4243
itemLabel: (item: LibraryPackage) => item.name,
4344
itemDeprecated: (item: LibraryPackage) => item.deprecated,
4445
itemRenderer,
46+
filterRenderer,
47+
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
4548
});
4649
}
4750

arduino-ide-extension/src/browser/style/list-widget.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@
88
}
99

1010
.arduino-list-widget .search-bar {
11-
margin: 0px 10px 10px 15px;
11+
margin: 0px 10px 5px 15px;
1212
}
1313

1414
.arduino-list-widget .search-bar:focus {
1515
border-color: var(--theia-focusBorder);
1616
}
1717

18+
.arduino-list-widget .filter-bar {
19+
display: flex;
20+
margin: 0px 10px 5px 15px;
21+
}
22+
1823
.filterable-list-container {
1924
display: flex;
2025
flex-direction: column;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import * as React from '@theia/core/shared/react';
3+
import {
4+
BoardSearch,
5+
LibrarySearch,
6+
Searchable,
7+
} from '../../../common/protocol';
8+
import { firstToUpperCase } from '../../../common/utils';
9+
10+
@injectable()
11+
export abstract class FilterRenderer<S extends Searchable.Options> {
12+
render(
13+
options: S,
14+
handlePropChange: (prop: keyof S, value: S[keyof S]) => void
15+
): React.ReactNode {
16+
const props = this.props();
17+
return (
18+
<div className="filter-bar">
19+
{Object.entries(options)
20+
.filter(([prop]) => props.includes(prop as keyof S))
21+
.map(([prop, value]) => (
22+
<div key={prop}>
23+
{firstToUpperCase(prop)}:
24+
<select
25+
className="theia-select"
26+
value={value}
27+
onChange={(event) =>
28+
handlePropChange(prop as keyof S, event.target.value as any)
29+
}
30+
>
31+
{this.options(prop as keyof S).map((key) => (
32+
<option key={key} value={key}>
33+
{key}
34+
</option>
35+
))}
36+
</select>
37+
</div>
38+
))}
39+
</div>
40+
);
41+
}
42+
protected abstract props(): (keyof S)[];
43+
protected abstract options(key: keyof S): string[];
44+
}
45+
46+
@injectable()
47+
export class BoardsFilterRenderer extends FilterRenderer<BoardSearch> {
48+
protected props(): (keyof BoardSearch)[] {
49+
return ['type'];
50+
}
51+
protected options(key: keyof BoardSearch): string[] {
52+
switch (key) {
53+
case 'type':
54+
return BoardSearch.TypeLiterals as any;
55+
default:
56+
throw new Error(`Unexpected key: ${key}`);
57+
}
58+
}
59+
}
60+
61+
@injectable()
62+
export class LibraryFilterRenderer extends FilterRenderer<LibrarySearch> {
63+
protected props(): (keyof LibrarySearch)[] {
64+
return ['type', 'topic'];
65+
}
66+
protected options(key: keyof LibrarySearch): string[] {
67+
switch (key) {
68+
case 'type':
69+
return LibrarySearch.TypeLiterals as any;
70+
case 'topic':
71+
return LibrarySearch.TopicLiterals as any;
72+
default:
73+
throw new Error(`Unexpected key: ${key}`);
74+
}
75+
}
76+
}

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ComponentList } from './component-list';
1414
import { ListItemRenderer } from './list-item-renderer';
1515
import { ResponseServiceClient } from '../../../common/protocol';
1616
import { nls } from '@theia/core/lib/common';
17+
import { FilterRenderer } from './filter-renderer';
1718

1819
export class FilterableListContainer<
1920
T extends ArduinoComponent,
@@ -25,15 +26,15 @@ export class FilterableListContainer<
2526
constructor(props: Readonly<FilterableListContainer.Props<T, S>>) {
2627
super(props);
2728
this.state = {
28-
searchOptions: { query: '' } as S,
29+
searchOptions: props.defaultSearchOptions,
2930
items: [],
3031
};
3132
}
3233

3334
override componentDidMount(): void {
3435
this.search = debounce(this.search, 500);
35-
this.handleQueryChange('');
36-
this.props.filterTextChangeEvent(this.handleQueryChange.bind(this));
36+
this.search(this.state.searchOptions);
37+
this.props.filterTextChangeEvent(this.handlePropChange.bind(this));
3738
}
3839

3940
override componentDidUpdate(): void {
@@ -45,23 +46,32 @@ export class FilterableListContainer<
4546
override render(): React.ReactNode {
4647
return (
4748
<div className={'filterable-list-container'}>
48-
{this.renderSearchFilter()}
4949
{this.renderSearchBar()}
50+
{this.renderSearchFilter()}
5051
{this.renderComponentList()}
5152
</div>
5253
);
5354
}
5455

5556
protected renderSearchFilter(): React.ReactNode {
56-
return undefined;
57+
return (
58+
<>
59+
{this.props.filterRenderer.render(
60+
this.state.searchOptions,
61+
this.handlePropChange.bind(this)
62+
)}
63+
</>
64+
);
5765
}
5866

5967
protected renderSearchBar(): React.ReactNode {
6068
return (
6169
<SearchBar
6270
resolveFocus={this.props.resolveFocus}
6371
filterText={this.state.searchOptions.query ?? ''}
64-
onFilterTextChanged={this.handleQueryChange}
72+
onFilterTextChanged={(query) =>
73+
this.handlePropChange('query', query as S['query'])
74+
}
6575
/>
6676
);
6777
}
@@ -80,15 +90,12 @@ export class FilterableListContainer<
8090
);
8191
}
8292

83-
protected handleQueryChange = (
84-
query: string = this.state.searchOptions.query ?? ''
85-
): void => {
86-
const newSearchOptions = {
93+
protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => {
94+
const searchOptions = {
8795
...this.state.searchOptions,
88-
query,
96+
[prop]: value,
8997
};
90-
this.setState({ searchOptions: newSearchOptions });
91-
this.search(newSearchOptions);
98+
this.setState({ searchOptions }, () => this.search(searchOptions));
9299
};
93100

94101
protected search(searchOptions: S): void {
@@ -160,12 +167,13 @@ export namespace FilterableListContainer {
160167
T extends ArduinoComponent,
161168
S extends Searchable.Options
162169
> {
170+
readonly defaultSearchOptions: S;
163171
readonly container: ListWidget<T, S>;
164172
readonly searchable: Searchable<T, S>;
165173
readonly itemLabel: (item: T) => string;
166174
readonly itemDeprecated: (item: T) => boolean;
167175
readonly itemRenderer: ListItemRenderer<T>;
168-
// readonly resolveContainer: (element: HTMLElement) => void;
176+
readonly filterRenderer: FilterRenderer<S>;
169177
readonly resolveFocus: (element: HTMLElement | undefined) => void;
170178
readonly filterTextChangeEvent: Event<string | undefined>;
171179
readonly messageService: MessageService;

arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { FilterableListContainer } from './filterable-list-container';
2020
import { ListItemRenderer } from './list-item-renderer';
2121
import { NotificationCenter } from '../../notification-center';
22+
import { FilterRenderer } from './filter-renderer';
2223

2324
@injectable()
2425
export abstract class ListWidget<
@@ -131,6 +132,7 @@ export abstract class ListWidget<
131132
render(): React.ReactNode {
132133
return (
133134
<FilterableListContainer<T, S>
135+
defaultSearchOptions={this.options.defaultSearchOptions}
134136
container={this}
135137
resolveFocus={this.onFocusResolved}
136138
searchable={this.options.searchable}
@@ -139,6 +141,7 @@ export abstract class ListWidget<
139141
itemLabel={this.options.itemLabel}
140142
itemDeprecated={this.options.itemDeprecated}
141143
itemRenderer={this.options.itemRenderer}
144+
filterRenderer={this.options.filterRenderer}
142145
filterTextChangeEvent={this.filterTextChangeEmitter.event}
143146
messageService={this.messageService}
144147
commandService={this.commandService}
@@ -175,5 +178,7 @@ export namespace ListWidget {
175178
readonly itemLabel: (item: T) => string;
176179
readonly itemDeprecated: (item: T) => boolean;
177180
readonly itemRenderer: ListItemRenderer<T>;
181+
readonly filterRenderer: FilterRenderer<S>;
182+
readonly defaultSearchOptions: S;
178183
}
179184
}

0 commit comments

Comments
 (0)