import * as React from 'react'; import { injectable, postConstruct, inject } from 'inversify'; import { Message } from '@phosphor/messaging'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { Emitter } from '@theia/core/lib/common/event'; import { MaybePromise } from '@theia/core/lib/common/types'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { Installable } from '../../../common/protocol/installable'; import { Searchable } from '../../../common/protocol/searchable'; import { ArduinoComponent } from '../../../common/protocol/arduino-component'; import { FilterableListContainer } from './filterable-list-container'; import { ListItemRenderer } from './list-item-renderer'; import { NotificationCenter } from '../../notification-center'; @injectable() export abstract class ListWidget extends ReactWidget { @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; /** * 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(); protected readonly filterTextChangeEmitter = new Emitter(); constructor(protected options: ListWidget.Options) { super(); const { id, label, iconClass } = options; this.id = id; this.title.label = label; this.title.caption = label; 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 } this.toDispose.push(this.filterTextChangeEmitter); } @postConstruct() 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)) ]); } protected getScrollContainer(): MaybePromise { 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 container={this} resolveContainer={this.deferredContainer.resolve} resolveFocus={this.onFocusResolved} searchable={this.options.searchable} installable={this.options.installable} itemLabel={this.options.itemLabel} itemRenderer={this.options.itemRenderer} filterTextChangeEvent={this.filterTextChangeEmitter.event} />; } /** * If `filterText` is defined, sets the filter text to the argument. * 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)); } updateScrollBar(): void { if (this.scrollBar) { this.scrollBar.update(); } } } export namespace ListWidget { export interface Options { readonly id: string; readonly label: string; readonly iconClass: string; readonly installable: Installable; readonly searchable: Searchable; readonly itemLabel: (item: T) => string; readonly itemRenderer: ListItemRenderer; } }