mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-08 03:46:33 +00:00
Merge pull request #34 from bcmi-labs/PROEDITOR-7
PROEDITOR-7: Cloned the Library Manager layout.
This commit is contained in:
commit
87cf5c6fd7
@ -18,6 +18,7 @@
|
|||||||
"@theia/workspace": "next",
|
"@theia/workspace": "next",
|
||||||
"@theia/navigator": "next",
|
"@theia/navigator": "next",
|
||||||
"@theia/terminal": "next",
|
"@theia/terminal": "next",
|
||||||
|
"css-element-queries": "^1.2.0",
|
||||||
"p-queue": "^5.0.0"
|
"p-queue": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -50,8 +50,12 @@ import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-fac
|
|||||||
import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory';
|
import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory';
|
||||||
import { SelectBoardDialog, SelectBoardDialogProps } from './boards/select-board-dialog';
|
import { SelectBoardDialog, SelectBoardDialogProps } from './boards/select-board-dialog';
|
||||||
import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget';
|
import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget';
|
||||||
|
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||||
|
|
||||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||||
|
ElementQueries.listen();
|
||||||
|
ElementQueries.init();
|
||||||
|
|
||||||
// Commands and toolbar items
|
// Commands and toolbar items
|
||||||
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
|
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
|
||||||
bind(CommandContribution).toService(ArduinoFrontendContribution);
|
bind(CommandContribution).toService(ArduinoFrontendContribution);
|
||||||
|
@ -6,6 +6,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|||||||
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
||||||
import { BoardsService, Board, BoardPackage } from '../../common/protocol/boards-service';
|
import { BoardsService, Board, BoardPackage } from '../../common/protocol/boards-service';
|
||||||
import { BoardsNotificationService } from '../boards-notification-service';
|
import { BoardsNotificationService } from '../boards-notification-service';
|
||||||
|
import { LibraryService } from '../../common/protocol/library-service';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class ListWidget extends ReactWidget {
|
export abstract class ListWidget extends ReactWidget {
|
||||||
@ -55,7 +56,7 @@ export abstract class ListWidget extends ReactWidget {
|
|||||||
getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(),
|
getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(),
|
||||||
selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board),
|
selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board),
|
||||||
getSelectBoard: () => boardsServiceDelegate.getSelectBoard(),
|
getSelectBoard: () => boardsServiceDelegate.getSelectBoard(),
|
||||||
search: (options: { query?: string }) => boardsServiceDelegate.search(options),
|
search: (options: { query?: string, props?: LibraryService.Search.Props }) => boardsServiceDelegate.search(options),
|
||||||
install: async (item: BoardPackage) => {
|
install: async (item: BoardPackage) => {
|
||||||
await boardsServiceDelegate.install(item);
|
await boardsServiceDelegate.install(item);
|
||||||
this.boardsNotificationService.notifyBoardsInstalled();
|
this.boardsNotificationService.notifyBoardsInstalled();
|
||||||
|
@ -4,7 +4,7 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
|||||||
|
|
||||||
export class ComponentListItem extends React.Component<ComponentListItem.Props> {
|
export class ComponentListItem extends React.Component<ComponentListItem.Props> {
|
||||||
|
|
||||||
private onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
||||||
const { target } = event.nativeEvent;
|
const { target } = event.nativeEvent;
|
||||||
if (target instanceof HTMLAnchorElement) {
|
if (target instanceof HTMLAnchorElement) {
|
||||||
this.props.windowService.openNewWindow(target.href);
|
this.props.windowService.openNewWindow(target.href);
|
||||||
@ -12,7 +12,7 @@ export class ComponentListItem extends React.Component<ComponentListItem.Props>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async install(item: ArduinoComponent) {
|
protected async install(item: ArduinoComponent): Promise<void> {
|
||||||
await this.props.install(item);
|
await this.props.install(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,26 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
|||||||
|
|
||||||
export class ComponentList extends React.Component<ComponentList.Props> {
|
export class ComponentList extends React.Component<ComponentList.Props> {
|
||||||
|
|
||||||
|
protected container?: HTMLElement;
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <div>
|
return <div
|
||||||
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />)}
|
className={'items-container'}
|
||||||
|
ref={element => this.container = element || undefined}>
|
||||||
|
{this.props.items.map(item => this.createItem(item))}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
if (this.container && this.props.resolveContainer) {
|
||||||
|
this.props.resolveContainer(this.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createItem(item: ArduinoComponent): React.ReactNode {
|
||||||
|
return <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ComponentList {
|
export namespace ComponentList {
|
||||||
@ -19,6 +33,7 @@ export namespace ComponentList {
|
|||||||
readonly items: ArduinoComponent[];
|
readonly items: ArduinoComponent[];
|
||||||
readonly windowService: WindowService;
|
readonly windowService: WindowService;
|
||||||
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
||||||
|
readonly resolveContainer?: (element: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
import { ComponentList } from './component-list';
|
|
||||||
import { SearchBar } from './search-bar';
|
import { SearchBar } from './search-bar';
|
||||||
|
import { ComponentList } from './component-list';
|
||||||
|
import { LibraryService } from '../../../common/protocol/library-service';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { InstallationProgressDialog } from '../installation-progress-dialog';
|
import { InstallationProgressDialog } from '../installation-progress-dialog';
|
||||||
|
|
||||||
@ -21,21 +22,37 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <div className={FilterableListContainer.Styles.FILTERABLE_LIST_CONTAINER_CLASS}>
|
return <div className={'filterable-list-container'}>
|
||||||
<SearchBar
|
{this.renderSearchFilter()}
|
||||||
filterText={this.state.filterText}
|
{this.renderSearchBar()}
|
||||||
onFilterTextChanged={this.handleFilterTextChange}
|
{this.renderComponentList()}
|
||||||
/>
|
|
||||||
<ComponentList
|
|
||||||
items={this.state.items}
|
|
||||||
install={this.install.bind(this)}
|
|
||||||
windowService={this.props.windowService}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected renderSearchFilter(): React.ReactNode {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderSearchBar(): React.ReactNode {
|
||||||
|
return <SearchBar
|
||||||
|
resolveFocus={this.props.resolveFocus}
|
||||||
|
filterText={this.state.filterText}
|
||||||
|
onFilterTextChanged={this.handleFilterTextChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderComponentList(): React.ReactNode {
|
||||||
|
return <ComponentList
|
||||||
|
items={this.state.items}
|
||||||
|
install={this.install.bind(this)}
|
||||||
|
windowService={this.props.windowService}
|
||||||
|
resolveContainer={this.props.resolveContainer}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
private handleFilterTextChange(filterText: string): void {
|
private handleFilterTextChange(filterText: string): void {
|
||||||
this.props.service.search({ query: filterText }).then(result => {
|
const { props } = this.state;
|
||||||
|
this.props.service.search({ query: filterText, props }).then(result => {
|
||||||
const { items } = result;
|
const { items } = result;
|
||||||
this.setState({
|
this.setState({
|
||||||
filterText,
|
filterText,
|
||||||
@ -45,15 +62,7 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected sort(items: ArduinoComponent[]): ArduinoComponent[] {
|
protected sort(items: ArduinoComponent[]): ArduinoComponent[] {
|
||||||
return items.sort((a, b) => {
|
return items.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
if (a.name < b.name) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.name === b.name) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async install(comp: ArduinoComponent): Promise<void> {
|
protected async install(comp: ArduinoComponent): Promise<void> {
|
||||||
@ -61,7 +70,8 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
try {
|
try {
|
||||||
await this.props.service.install(comp);
|
await this.props.service.install(comp);
|
||||||
const { items } = await this.props.service.search({ query: this.state.filterText });
|
const { props } = this.state;
|
||||||
|
const { items } = await this.props.service.search({ query: this.state.filterText, props });
|
||||||
this.setState({ items: this.sort(items) });
|
this.setState({ items: this.sort(items) });
|
||||||
} finally {
|
} finally {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
@ -75,19 +85,18 @@ export namespace FilterableListContainer {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
readonly service: ComponentSource;
|
readonly service: ComponentSource;
|
||||||
readonly windowService: WindowService;
|
readonly windowService: WindowService;
|
||||||
|
readonly resolveContainer?: (element: HTMLElement) => void;
|
||||||
|
readonly resolveFocus?: (element: HTMLElement | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
items: ArduinoComponent[];
|
items: ArduinoComponent[];
|
||||||
}
|
props?: LibraryService.Search.Props;
|
||||||
|
|
||||||
export namespace Styles {
|
|
||||||
export const FILTERABLE_LIST_CONTAINER_CLASS = 'filterable-list-container';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComponentSource {
|
export interface ComponentSource {
|
||||||
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
|
search(req: { query: string, props?: LibraryService.Search.Props }): Promise<{ items: ArduinoComponent[] }>
|
||||||
install(board: ArduinoComponent): Promise<void>;
|
install(board: ArduinoComponent): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,15 +9,22 @@ export class SearchBar extends React.Component<SearchBar.Props> {
|
|||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <input
|
return <input
|
||||||
|
ref={this.setRef}
|
||||||
className={SearchBar.Styles.SEARCH_BAR_CLASS}
|
className={SearchBar.Styles.SEARCH_BAR_CLASS}
|
||||||
type='text'
|
type='text'
|
||||||
placeholder='Search'
|
placeholder='Filter your search...'
|
||||||
size={1}
|
size={1}
|
||||||
value={this.props.filterText}
|
value={this.props.filterText}
|
||||||
onChange={this.handleFilterTextChange}
|
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);
|
this.props.onFilterTextChanged(event.target.value);
|
||||||
}
|
}
|
||||||
@ -29,6 +36,7 @@ export namespace SearchBar {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
onFilterTextChanged(filterText: string): void;
|
onFilterTextChanged(filterText: string): void;
|
||||||
|
readonly resolveFocus?: (element: HTMLElement | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Styles {
|
export namespace Styles {
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ComponentListItem } from '../components/component-list/component-list-item';
|
||||||
|
|
||||||
|
export class LibraryComponentListItem extends ComponentListItem {
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
const { item } = this.props;
|
||||||
|
|
||||||
|
const name = <span className={'name'}>{item.name}</span>;
|
||||||
|
const author = <span className={'author'}>by {item.author}</span>;
|
||||||
|
const installedVersion = !!item.installedVersion && <div className={'version-info'}>
|
||||||
|
<span className={'version'}>Version {item.installedVersion}</span>
|
||||||
|
<span className={'installed'}>INSTALLED</span>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
const summary = <div className={'summary'}>{item.summary}</div>;
|
||||||
|
|
||||||
|
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
||||||
|
const install = this.props.install && item.installable && !item.installedVersion &&
|
||||||
|
<button className={'install'} onClick={this.install.bind(this, item)}>INSTALL</button>;
|
||||||
|
const versions = (() => {
|
||||||
|
const { availableVersions } = item;
|
||||||
|
if (availableVersions.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
} else if (availableVersions.length === 1) {
|
||||||
|
return <label>{availableVersions[0]}</label>
|
||||||
|
} else {
|
||||||
|
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return <div className={'component-list-item noselect'}>
|
||||||
|
<div className={'header'}>
|
||||||
|
<span>{name} {author}</span>
|
||||||
|
{installedVersion}
|
||||||
|
</div>
|
||||||
|
<div className={'content'}>
|
||||||
|
{summary}
|
||||||
|
</div>
|
||||||
|
<div className={'info'}>
|
||||||
|
{moreInfo}
|
||||||
|
</div>
|
||||||
|
<div className={'footer'}>
|
||||||
|
{install}
|
||||||
|
{versions}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ArduinoComponent } from '../../common/protocol/arduino-component';
|
||||||
|
import { ComponentList } from '../components/component-list/component-list';
|
||||||
|
import { LibraryComponentListItem } from './library-component-list-item';
|
||||||
|
|
||||||
|
export class LibraryComponentList extends ComponentList {
|
||||||
|
|
||||||
|
createItem(item: ArduinoComponent): React.ReactNode {
|
||||||
|
return <LibraryComponentListItem
|
||||||
|
key={item.name}
|
||||||
|
item={item}
|
||||||
|
windowService={this.props.windowService}
|
||||||
|
install={this.props.install}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
||||||
|
import { LibraryComponentList } from './library-component-list';
|
||||||
|
|
||||||
|
export class LibraryFilterableListContainer extends FilterableListContainer {
|
||||||
|
|
||||||
|
constructor(props: Readonly<FilterableListContainer.Props>) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
filterText: '',
|
||||||
|
items: [],
|
||||||
|
props: {
|
||||||
|
topic: this.topics[0],
|
||||||
|
type: this.types[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderSearchFilter(): React.ReactNode {
|
||||||
|
const types = this.types.map(type => <option value={type} key={type}>{type}</option>);
|
||||||
|
let type = this.types[0];
|
||||||
|
if (this.state.props) {
|
||||||
|
const currentType = this.types.find(t => t === this.state.props!.type) || this.types[0];
|
||||||
|
if (currentType) {
|
||||||
|
type = currentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const topics = this.topics.map(topic => <option value={topic} key={topic}>{topic}</option>);
|
||||||
|
let topic = this.topics[0];
|
||||||
|
if (this.state.props) {
|
||||||
|
const currentTopic = this.topics.find(t => t === this.state.props!.topic) || this.topics[0];
|
||||||
|
if (currentTopic) {
|
||||||
|
topic = currentTopic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <div className={'search-filters'}>
|
||||||
|
<div className={'filter'}>
|
||||||
|
<div className={'title'} style={{ minWidth: '32.088px' }}>Type</div> {/** TODO: do `minWidth` better! */}
|
||||||
|
<select
|
||||||
|
value={type}
|
||||||
|
onChange={this.onTypeChange}>
|
||||||
|
{types}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className={'filter'}>
|
||||||
|
<div className={'title'}>Topic</div>
|
||||||
|
<select
|
||||||
|
value={topic}
|
||||||
|
onChange={this.onTopicChange}>
|
||||||
|
{topics}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const type = event.target.value;
|
||||||
|
const props = { ...(this.state.props || {}), ...{ type } };
|
||||||
|
this.setState({
|
||||||
|
props
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onTopicChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const topic = event.target.value;
|
||||||
|
const props = { ...(this.state.props || {}), ...{ topic } };
|
||||||
|
this.setState({
|
||||||
|
props
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderComponentList(): React.ReactNode {
|
||||||
|
return <LibraryComponentList
|
||||||
|
items={this.state.items}
|
||||||
|
install={this.install.bind(this)}
|
||||||
|
windowService={this.props.windowService}
|
||||||
|
resolveContainer={this.props.resolveContainer}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
private get topics(): string[] {
|
||||||
|
return [
|
||||||
|
'All',
|
||||||
|
'Communication',
|
||||||
|
'Data Processing',
|
||||||
|
'Data Storage',
|
||||||
|
'Device Control',
|
||||||
|
'Display',
|
||||||
|
'Other',
|
||||||
|
'Sensor',
|
||||||
|
'Signal Input/Output',
|
||||||
|
'Timing',
|
||||||
|
'Uncategorized'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get types(): string[] {
|
||||||
|
return [
|
||||||
|
'All',
|
||||||
|
'Updatable',
|
||||||
|
'Installed',
|
||||||
|
'Arduino',
|
||||||
|
'Partner',
|
||||||
|
'Recommended',
|
||||||
|
'Contributed',
|
||||||
|
'Retired'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,88 @@
|
|||||||
import { ListWidget } from './list-widget';
|
import * as React from 'react';
|
||||||
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
|
import { Message } from '@phosphor/messaging';
|
||||||
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
|
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||||
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
import { LibraryFilterableListContainer } from './library-filterable-list-container';
|
||||||
|
import { LibraryService } from '../../common/protocol/library-service';
|
||||||
|
|
||||||
export class LibraryListWidget extends ListWidget {
|
@injectable()
|
||||||
|
export class LibraryListWidget extends ReactWidget {
|
||||||
|
|
||||||
static WIDGET_ID = 'library-list-widget';
|
static WIDGET_ID = 'library-list-widget';
|
||||||
static WIDGET_LABEL = 'Library Manager';
|
static WIDGET_LABEL = 'Library Manager';
|
||||||
|
|
||||||
protected widgetProps(): ListWidget.Props {
|
@inject(LibraryService)
|
||||||
return {
|
protected readonly libraryService: LibraryService;
|
||||||
id: LibraryListWidget.WIDGET_ID,
|
|
||||||
title: LibraryListWidget.WIDGET_LABEL,
|
@inject(WindowService)
|
||||||
iconClass: 'library-tab-icon'
|
protected readonly windowService: WindowService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||||
|
*/
|
||||||
|
protected focusNode: HTMLElement | undefined;
|
||||||
|
protected readonly deferredContainer = new Deferred<HTMLElement>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.id = LibraryListWidget.WIDGET_ID
|
||||||
|
this.title.label = LibraryListWidget.WIDGET_LABEL;
|
||||||
|
this.title.caption = LibraryListWidget.WIDGET_LABEL
|
||||||
|
this.title.iconClass = 'library-tab-icon';
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getScrollContainer(): MaybePromise<HTMLElement> {
|
||||||
|
return this.deferredContainer.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
(this.focusNode || this.node).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onUpdateRequest(msg: Message): void {
|
||||||
|
super.onUpdateRequest(msg);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||||
|
this.focusNode = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return <LibraryFilterableListContainer
|
||||||
|
resolveContainer={this.deferredContainer.resolve}
|
||||||
|
resolveFocus={this.onFocusResolved}
|
||||||
|
service={this.libraryService}
|
||||||
|
windowService={this.windowService}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ListWidget {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for customizing the abstract list widget.
|
||||||
|
*/
|
||||||
|
export interface Props {
|
||||||
|
readonly id: string;
|
||||||
|
readonly title: string;
|
||||||
|
readonly iconClass: string;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,22 +1,12 @@
|
|||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||||
import { ListWidget } from './list-widget';
|
|
||||||
import { LibraryListWidget } from './library-list-widget';
|
|
||||||
import { MenuModelRegistry } from '@theia/core';
|
import { MenuModelRegistry } from '@theia/core';
|
||||||
|
import { LibraryListWidget } from './library-list-widget';
|
||||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class ListWidgetFrontendContribution extends AbstractViewContribution<ListWidget> implements FrontendApplicationContribution {
|
export class LibraryListWidgetFrontendContribution extends AbstractViewContribution<LibraryListWidget> implements FrontendApplicationContribution {
|
||||||
|
|
||||||
async initializeLayout(): Promise<void> {
|
|
||||||
// await this.openView();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendContribution {
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -31,6 +21,10 @@ export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendCon
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeLayout(): void {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
registerMenus(menus: MenuModelRegistry): void {
|
registerMenus(menus: MenuModelRegistry): void {
|
||||||
if (this.toggleCommand) {
|
if (this.toggleCommand) {
|
||||||
menus.registerMenuAction(ArduinoMenus.SKETCH, {
|
menus.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { inject, injectable, postConstruct } from 'inversify';
|
|
||||||
import { Message } from '@phosphor/messaging';
|
|
||||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
||||||
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
|
||||||
import { LibraryService } from '../../common/protocol/library-service';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export abstract class ListWidget extends ReactWidget {
|
|
||||||
|
|
||||||
@inject(LibraryService)
|
|
||||||
protected readonly libraryService: LibraryService;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected readonly windowService: WindowService;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const { id, title, iconClass } = this.widgetProps();
|
|
||||||
this.id = id;
|
|
||||||
this.title.label = title;
|
|
||||||
this.title.caption = title;
|
|
||||||
this.title.iconClass = iconClass;
|
|
||||||
this.title.closable = true;
|
|
||||||
this.addClass(ListWidget.Styles.LIST_WIDGET_CLASS);
|
|
||||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract widgetProps(): ListWidget.Props;
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected init(): void {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onActivateRequest(msg: Message): void {
|
|
||||||
super.onActivateRequest(msg);
|
|
||||||
this.node.focus();
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onUpdateRequest(msg: Message): void {
|
|
||||||
super.onUpdateRequest(msg);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <FilterableListContainer
|
|
||||||
service={this.libraryService}
|
|
||||||
windowService={this.windowService}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ListWidget {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for customizing the abstract list widget.
|
|
||||||
*/
|
|
||||||
export interface Props {
|
|
||||||
readonly id: string;
|
|
||||||
readonly title: string;
|
|
||||||
readonly iconClass: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Styles {
|
|
||||||
export const LIST_WIDGET_CLASS = 'arduino-list-widget'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -18,8 +18,10 @@ is not optimized for dense, information rich UIs.
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Custom Theme Colors */
|
/* Custom Theme Colors */
|
||||||
--theia-arduino-light: #005C5F;
|
--theia-arduino-light: rgb(0, 102, 102);
|
||||||
--theia-arduino-light1: #00979D;
|
--theia-arduino-light1: rgb(0, 153, 153);
|
||||||
|
--theia-arduino-light2: rgb(218, 226, 228);
|
||||||
|
--theia-arduino-light3: rgb(237, 241, 242);
|
||||||
--theia-arduino-terminal: rgb(0, 0, 0);
|
--theia-arduino-terminal: rgb(0, 0, 0);
|
||||||
/* Borders: Width and color (bright to dark) */
|
/* Borders: Width and color (bright to dark) */
|
||||||
--theia-border-width: 1px;
|
--theia-border-width: 1px;
|
||||||
|
@ -8,7 +8,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-list-widget .search-bar {
|
.arduino-list-widget .search-bar {
|
||||||
margin: 0 10px 0 15px;
|
margin: 0px 10px 10px 15px;
|
||||||
|
border-color: var(--theia-border-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .search-filters {
|
||||||
|
margin: 0px 10px 0px 15px;
|
||||||
border-color: var(--theia-border-color3);
|
border-color: var(--theia-border-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,10 +21,44 @@
|
|||||||
border-color: var(--theia-accent-color3);
|
border-color: var(--theia-accent-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filterable-list-container .search-filters .filter {
|
||||||
|
margin: 0px 0px 10px 0px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filterable-list-container .search-filters .filter .title {
|
||||||
|
margin: 0px 10px 0px 0px;
|
||||||
|
align-self: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: var(--theia-ui-font-size0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filterable-list-container .search-filters .filter > select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.filterable-list-container {
|
.filterable-list-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
height: 100%; /* This has top be 100% down to the `scrollContainer`. */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterable-list-container .items-container {
|
||||||
|
height: 100%; /* This has to be propagated down from the widget. */
|
||||||
|
position: relative; /* To fix the `top` of the vertical toolbar. */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterable-list-container .items-container > div:nth-child(odd) {
|
||||||
|
background-color: var(--theia-layout-color2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterable-list-container .items-container > div:nth-child(even) {
|
||||||
|
background-color: var(--theia-layout-color0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterable-list-container .items-container > div:hover {
|
||||||
|
background-color: var(--theia-layout-color1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item {
|
.component-list-item {
|
||||||
@ -61,6 +100,10 @@
|
|||||||
color: var(--theia-ui-font-color2);
|
color: var(--theia-ui-font-color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.component-list-item:hover .header .author {
|
||||||
|
color: var(--theia-ui-font-color1);
|
||||||
|
}
|
||||||
|
|
||||||
.component-list-item .header .version {
|
.component-list-item .header .version {
|
||||||
color: var(--theia-ui-font-color2);
|
color: var(--theia-ui-font-color2);
|
||||||
}
|
}
|
||||||
@ -76,18 +119,35 @@
|
|||||||
color: var(--theia-inverse-ui-font-color0);
|
color: var(--theia-inverse-ui-font-color0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item .footer {
|
.component-list-item[min-width~="170px"] .footer {
|
||||||
padding-top: 5px;
|
padding: 5px 5px 0px 0px;
|
||||||
|
min-height: 26px; /* 21 + 5 from the footer margin top */
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item .footer a {
|
.component-list-item .footer {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list-item .footer > * {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list-item:hover .footer > * {
|
||||||
|
display: block;
|
||||||
|
margin: 5px 0px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list-item:hover .footer > label {
|
||||||
|
display: block;
|
||||||
|
align-self: center;
|
||||||
|
margin: 5px 0px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list-item .info a {
|
||||||
color: var(--theia-brand-color1);
|
color: var(--theia-brand-color1);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.component-list-item .footer .install {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item a:hover {
|
.component-list-item a:hover {
|
||||||
|
@ -96,3 +96,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theia-sidepanel-toolbar .theia-sidepanel-title {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
@ -3,10 +3,18 @@ import { ArduinoComponent } from "./arduino-component";
|
|||||||
export const LibraryServicePath = '/services/library-service';
|
export const LibraryServicePath = '/services/library-service';
|
||||||
export const LibraryService = Symbol('LibraryService');
|
export const LibraryService = Symbol('LibraryService');
|
||||||
export interface LibraryService {
|
export interface LibraryService {
|
||||||
search(options: { query?: string }): Promise<{ items: Library[] }>;
|
search(options: { query?: string, props?: LibraryService.Search.Props }): Promise<{ items: Library[] }>;
|
||||||
install(library: Library): Promise<void>;
|
install(library: Library): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace LibraryService {
|
||||||
|
export namespace Search {
|
||||||
|
export interface Props {
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Library extends ArduinoComponent {
|
export interface Library extends ArduinoComponent {
|
||||||
readonly builtIn?: boolean;
|
readonly builtIn?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ export class LibraryServiceImpl implements LibraryService {
|
|||||||
@inject(ToolOutputServiceServer)
|
@inject(ToolOutputServiceServer)
|
||||||
protected readonly toolOutputService: ToolOutputServiceServer;
|
protected readonly toolOutputService: ToolOutputServiceServer;
|
||||||
|
|
||||||
async search(options: { query?: string; }): Promise<{ items: Library[] }> {
|
async search(options: { query?: string, props: LibraryService.Search.Props }): Promise<{ items: Library[] }> {
|
||||||
const coreClient = await this.coreClientProvider.getClient();
|
const coreClient = await this.coreClientProvider.getClient();
|
||||||
if (!coreClient) {
|
if (!coreClient) {
|
||||||
return { items: [] };
|
return { items: [] };
|
||||||
|
@ -3611,6 +3611,11 @@ css-color-names@0.0.4:
|
|||||||
version "0.0.4"
|
version "0.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||||
|
|
||||||
|
css-element-queries@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.0.tgz#081373d7f30302adad46b7c29b83b85f8ef8f62c"
|
||||||
|
integrity sha512-4gaxpioSFueMcp9yj1TJFCLjfooGv38y6ZdwFUS3GuS+9NIVijdeiExXKwSIHoQDADfpgnaYSTzmJs+bV+Hehg==
|
||||||
|
|
||||||
css-loader@^0.28.1:
|
css-loader@^0.28.1:
|
||||||
version "0.28.11"
|
version "0.28.11"
|
||||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
|
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user