import * as React from 'react'; import { DisposableCollection } from '@theia/core'; import { BoardsService, Board, AttachedSerialBoard } from '../../common/protocol/boards-service'; import { BoardsServiceClientImpl } from './boards-service-client-impl'; export namespace BoardsConfig { export interface Config { selectedBoard?: Board; selectedPort?: string; } export interface Props { readonly boardsService: BoardsService; readonly boardsServiceClient: BoardsServiceClientImpl; readonly onConfigChange: (config: Config) => void; readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; } export interface State extends Config { searchResults: Board[]; knownPorts: string[]; } } export abstract class Item extends React.Component<{ item: T, name: string, selected: boolean, onClick: (item: T) => void, missing?: boolean }> { render(): React.ReactNode { const { selected, name, missing } = this.props; const classNames = ['item']; if (selected) { classNames.push('selected'); } if (missing === true) { classNames.push('missing') } return
{name} {selected ? : ''}
; } protected onClick = () => { this.props.onClick(this.props.item); } } export class BoardsConfig extends React.Component { protected toDispose = new DisposableCollection(); constructor(props: BoardsConfig.Props) { super(props); const { boardsConfig } = props.boardsServiceClient; this.state = { searchResults: [], knownPorts: [], ...boardsConfig } } componentDidMount() { this.updateBoards(); this.props.boardsService.getAttachedBoards().then(({ boards }) => this.updatePorts(boards)); const { boardsServiceClient: client } = this.props; this.toDispose.pushAll([ client.onBoardsChanged(event => this.updatePorts(event.newState.boards)), client.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => { this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged()); }) ]); } componentWillUnmount(): void { this.toDispose.dispose(); } protected fireConfigChanged() { const { selectedBoard, selectedPort } = this.state; this.props.onConfigChange({ selectedBoard, selectedPort }); } protected updateBoards = (eventOrQuery: React.ChangeEvent | string = '') => { const query = (typeof eventOrQuery === 'string' ? eventOrQuery : eventOrQuery.target.value.toLowerCase() ).trim(); this.queryBoards({ query }).then(({ searchResults }) => this.setState({ searchResults })); } protected updatePorts = (boards: Board[] = []) => { this.queryPorts(Promise.resolve({ boards })).then(({ knownPorts }) => { let { selectedPort } = this.state; if (!!selectedPort && knownPorts.indexOf(selectedPort) === -1) { selectedPort = undefined; } this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged()); }); } protected queryBoards = (options: { query?: string } = {}): Promise<{ searchResults: Board[] }> => { const { boardsService } = this.props; const query = (options.query || '').toLocaleLowerCase(); return new Promise<{ searchResults: Board[] }>(resolve => { boardsService.search(options) .then(({ items }) => items .map(item => item.boards) .reduce((acc, curr) => acc.concat(curr), []) .filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1) .sort(Board.compare)) .then(searchResults => resolve({ searchResults })); }); } protected get attachedBoards(): Promise<{ boards: Board[] }> { return this.props.boardsService.getAttachedBoards(); } protected queryPorts = (attachedBoards: Promise<{ boards: Board[] }> = this.attachedBoards) => { return new Promise<{ knownPorts: string[] }>(resolve => { attachedBoards .then(({ boards }) => boards .filter(AttachedSerialBoard.is) .map(({ port }) => port) .sort()) .then(knownPorts => resolve({ knownPorts })); }); } protected selectPort = (selectedPort: string | undefined) => { this.setState({ selectedPort }, () => this.fireConfigChanged()); } protected selectBoard = (selectedBoard: Board | undefined) => { this.setState({ selectedBoard }, () => this.fireConfigChanged()); } protected focusNodeSet = (element: HTMLElement | null) => { this.props.onFocusNodeSet(element || undefined); } render(): React.ReactNode { return
{this.renderContainer('boards', this.renderBoards.bind(this))} {this.renderContainer('ports', this.renderPorts.bind(this))}
; } protected renderContainer(title: string, contentRenderer: () => React.ReactNode): React.ReactNode { return
{title}
{contentRenderer()}
; } protected renderBoards(): React.ReactNode { const { selectedBoard } = this.state; return
{this.state.searchResults.map((board, index) => key={`${board.name}-${index}`} item={board} name={board.name} selected={!!selectedBoard && Board.equals(board, selectedBoard)} onClick={this.selectBoard} missing={!Board.installed(board)} />)}
; } protected renderPorts(): React.ReactNode { return !this.state.knownPorts.length ? (
No ports discovered
) : (
{this.state.knownPorts.map(port => key={port} item={port} name={port} selected={this.state.selectedPort === port} onClick={this.selectPort} />)}
); } } export namespace BoardsConfig { export namespace Config { export function sameAs(config: Config, other: Config | AttachedSerialBoard): boolean { const { selectedBoard, selectedPort } = config; if (AttachedSerialBoard.is(other)) { return !!selectedBoard && Board.equals(other, selectedBoard) && selectedPort === other.port; } return sameAs(config, other); } export function equals(left: Config, right: Config): boolean { return left.selectedBoard === right.selectedBoard && left.selectedPort === right.selectedPort; } export function toString(config: Config, options: { default: string } = { default: '' }): string { const { selectedBoard, selectedPort: port } = config; if (!selectedBoard) { return options.default; } const { name } = selectedBoard; return `${name}${port ? ' at ' + port : ''}`; } } }