8000 Layout improvements of the filter for boards and libs manager by francescospissu · Pull Request #1369 · arduino/arduino-ide · GitHub
[go: up one dir, main page]

Skip to content

Layout improvements of the filter for boards and libs manager #1369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
initial filter UI
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Aug 28, 2022
commit d769858235c0edb0a66ca985f1917c27ee68d52d
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/
import { PreferencesEditorWidget } from './theia/preferences/preference-editor-widget';
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
import { createPreferencesWidgetContainer } from '@theia/preferences/lib/browser/views/preference-widget-bindings';
import {
BoardsFilterRenderer,
LibraryFilterRenderer,
} from './widgets/component-list/filter-renderer';

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

// Renderer for both the library and the core widgets.
bind(ListItemRenderer).toSelf().inSingletonScope();
bind(LibraryFilterRenderer).toSelf().inSingletonScope();
bind(BoardsFilterRenderer).toSelf().inSingletonScope();

// Library service
bind(LibraryService)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import {
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
import { BoardsFilterRenderer } from '../widgets/component-list/filter-renderer';

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

constructor(
@inject(BoardsService) protected service: BoardsService,
@inject(ListItemRenderer)
protected itemRenderer: ListItemRenderer<BoardsPackage>
@inject(BoardsService) service: BoardsService,
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<BoardsPackage>,
@inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer
) {
super({
id: BoardsListWidget.WIDGET_ID,
Expand All @@ -31,6 +32,8 @@ export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
itemLabel: (item: BoardsPackage) => item.name,
itemDeprecated: (item: BoardsPackage) => item.deprecated,
itemRenderer,
filterRenderer,
defaultSearchOptions: { query: '', type: 'All' },
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ListWidget } from '../widgets/component-list/list-widget';
import { Installable } from '../../common/protocol';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';

@injectable()
export class LibraryListWidget extends ListWidget<
Expand All @@ -29,9 +30,9 @@ export class LibraryListWidget extends ListWidget<
);

constructor(
@inject(LibraryService) protected service: LibraryService,
@inject(ListItemRenderer)
protected itemRenderer: ListItemRenderer<LibraryPackage>
@inject(LibraryService) private service: LibraryService,
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<LibraryPackage>,
@inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer
) {
super({
id: LibraryListWidget.WIDGET_ID,
Expand All @@ -42,6 +43,8 @@ export class LibraryListWidget extends ListWidget<
itemLabel: (item: LibraryPackage) => item.name,
itemDeprecated: (item: LibraryPackage) => item.deprecated,
itemRenderer,
filterRenderer,
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
});
}

Expand Down
7 changes: 6 additions & 1 deletion arduino-ide-extension/src/browser/style/list-widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
}

.arduino-list-widget .search-bar {
margin: 0px 10px 10px 15px;
margin: 0px 10px 5px 15px;
}

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

.arduino-list-widget .filter-bar {
display: flex;
margin: 0px 10px 5px 15px;
}

.filterable-list-container {
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { injectable } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import {
BoardSearch,
LibrarySearch,
Searchable,
} from '../../../common/protocol';
import { firstToUpperCase } from '../../../common/utils';

@injectable()
export abstract class FilterRenderer<S extends Searchable.Options> {
render(
options: S,
handlePropChange: (prop: keyof S, value: S[keyof S]) => void
): React.ReactNode {
const props = this.props();
return (
<div className="filter-bar">
{Object.entries(options)
.filter(([prop]) => props.includes(prop as keyof S))
.map(([prop, value]) => (
<div key={prop}>
{firstToUpperCase(prop)}:
<select
className="theia-select"
value={value}
onChange={(event) =>
handlePropChange(prop as keyof S, event.target.value as any)
}
>
{this.options(prop as keyof S).map((key) => (
<option key={key} value={key}>
{key}
</option>
))}
</select>
</div>
))}
</div>
);
}
protected abstract props(): (keyof S)[];
protected abstract options(key: keyof S): string[];
}

@injectable()
export class BoardsFilterRenderer extends FilterRenderer<BoardSearch> {
protected props(): (keyof BoardSearch)[] {
return ['type'];
}
protected options(key: keyof BoardSearch): string[] {
switch (key) {
case 'type':
return BoardSearch.TypeLiterals as any;
default:
throw new Error(`Unexpected key: ${key}`);
}
}
}

@injectable()
export class LibraryFilterRenderer extends FilterRenderer<LibrarySearch> {
protected props(): (keyof LibrarySearch)[] {
return ['type', 'topic'];
}
protected options(key: keyof LibrarySearch): string[] {
switch (key) {
case 'type':
return LibrarySearch.TypeLiterals as any;
case 'topic':
return LibrarySearch.TopicLiterals as any;
default:
throw new Error(`Unexpected key: ${key}`);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ComponentList } from './component-list';
import { ListItemRenderer } from './list-item-renderer';
import { ResponseServiceClient } from '../../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { FilterRenderer } from './filter-renderer';

export class FilterableListContainer<
T extends ArduinoComponent,
Expand All @@ -25,15 +26,15 @@ export class FilterableListContainer<
constructor(props: Readonly<FilterableListContainer.Props<T, S>>) {
super(props);
this.state = {
searchOptions: { query: '' } as S,
searchOptions: props.defaultSearchOptions,
items: [],
};
}

override componentDidMount(): void {
this.search = debounce(this.search, 500);
this.handleQueryChange('');
this.props.filterTextChangeEvent(this.handleQueryChange.bind(this));
this.search(this.state.searchOptions);
this.props.filterTextChangeEvent(this.handlePropChange.bind(this));
}

override componentDidUpdate(): void {
Expand All @@ -45,23 +46,32 @@ export class FilterableListContainer<
override render(): React.ReactNode {
return (
<div className={'filterable-list-container'}>
{this.renderSearchFilter()}
{this.renderSearchBar()}
{this.renderSearchFilter()}
{this.renderComponentList()}
</div>
);
}

protected renderSearchFilter(): React.ReactNode {
return undefined;
return (
<>
{this.props.filterRenderer.render(
this.state.searchOptions,
this.handlePropChange.bind(this)
)}
</>
);
}

protected renderSearchBar(): React.ReactNode {
return (
<SearchBar
resolveFocus={this.props.resolveFocus}
filterText={this.state.searchOptions.query ?? ''}
onFilterTextChanged={this.handleQueryChange}
onFilterTextChanged={(query) =>
this.handlePropChange('query', query as S['query'])
}
/>
);
}
Expand All @@ -80,15 +90,12 @@ export class FilterableListContainer<
);
}

protected handleQueryChange = (
query: string = this.state.searchOptions.query ?? ''
): void => {
const newSearchOptions = {
protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => {
const searchOptions = {
...this.state.searchOptions,
query,
[prop]: value,
};
this.setState({ searchOptions: newSearchOptions });
this.search(newSearchOptions);
this.setState({ searchOptions }, () => this.search(searchOptions));
};

protected search(searchOptions: S): void {
Expand Down Expand Up @@ -160,12 +167,13 @@ export namespace FilterableListContainer {
T extends ArduinoComponent,
S extends Searchable.Options
> {
readonly defaultSearchOptions: S;
readonly container: ListWidget<T, S>;
readonly searchable: Searchable<T, S>;
readonly itemLabel: (item: T) => string;
readonly itemDeprecated: (item: T) => boolean;
readonly itemRenderer: ListItemRenderer<T>;
// readonly resolveContainer: (element: HTMLElement) => void;
readonly filterRenderer: FilterRenderer<S>;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
readonly filterTextChangeEvent: Event<string | undefined>;
readonly messageService: MessageService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
import { NotificationCenter } from '../../notification-center';
import { FilterRenderer } from './filter-renderer';

@injectable()
export abstract class ListWidget<
Expand Down Expand Up @@ -131,6 +132,7 @@ export abstract class ListWidget<
render(): React.ReactNode {
return (
<FilterableListContainer<T, S>
defaultSearchOptions={this.options.defaultSearchOptions}
container={this}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
Expand All @@ -139,6 +141,7 @@ export abstract class ListWidget<
itemLabel={this.options.itemLabel}
itemDeprecated={this.options.itemDeprecated}
itemRenderer={this.options.itemRenderer}
filterRenderer={this.options.filterRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event}
messageService={this.messageService}
commandService={this.commandService}
Expand Down Expand Up @@ -175,5 +178,7 @@ export namespace ListWidget {
readonly itemLabel: (item: T) => string;
readonly itemDeprecated: (item: T) => boolean;
readonly itemRenderer: ListItemRenderer<T>;
readonly filterRenderer: FilterRenderer<S>;
readonly defaultSearchOptions: S;
}
}
0