From 10b21ee085e710edfc18c86f58c78e1ff1e8063f Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sun, 1 Mar 2020 12:15:02 +0100 Subject: [PATCH] added serial port detection --- terminus-core/src/api/index.ts | 1 + terminus-core/src/api/selector.ts | 5 ++ .../components/selectorModal.component.pug | 18 +++++++ .../src/components/selectorModal.component.ts | 47 +++++++++++++++++++ terminus-core/src/index.ts | 3 ++ terminus-core/src/services/app.service.ts | 12 +++++ terminus-serial/src/api.ts | 9 ++++ .../editConnectionModal.component.pug | 6 ++- .../editConnectionModal.component.ts | 21 ++++++++- .../src/components/serialModal.component.pug | 8 ++++ .../src/components/serialModal.component.ts | 15 +++++- .../src/services/serial.service.ts | 10 ++-- 12 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 terminus-core/src/api/selector.ts create mode 100644 terminus-core/src/components/selectorModal.component.pug create mode 100644 terminus-core/src/components/selectorModal.component.ts diff --git a/terminus-core/src/api/index.ts b/terminus-core/src/api/index.ts index 83286cfc..1c861070 100644 --- a/terminus-core/src/api/index.ts +++ b/terminus-core/src/api/index.ts @@ -7,6 +7,7 @@ export { ConfigProvider } from './configProvider' export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider' export { Theme } from './theme' export { TabContextMenuItemProvider } from './tabContextMenuProvider' +export { SelectorOption } from './selector' export { AppService } from '../services/app.service' export { ConfigService } from '../services/config.service' diff --git a/terminus-core/src/api/selector.ts b/terminus-core/src/api/selector.ts new file mode 100644 index 00000000..8aa078d7 --- /dev/null +++ b/terminus-core/src/api/selector.ts @@ -0,0 +1,5 @@ +export interface SelectorOption { + name: string + description?: string + result: T +} diff --git a/terminus-core/src/components/selectorModal.component.pug b/terminus-core/src/components/selectorModal.component.pug new file mode 100644 index 00000000..e99e5c7f --- /dev/null +++ b/terminus-core/src/components/selectorModal.component.pug @@ -0,0 +1,18 @@ +.modal-body + input.form-control( + type='text', + [(ngModel)]='filter', + autofocus, + [placeholder]='name', + (ngModelChange)='onFilterChange()', + (keyup.enter)='onFilterEnter()', + (keyup.escape)='close()' + ) + + .list-group.mt-3(*ngIf='filteredOptions.length') + a.list-group-item.list-group-item-action.d-flex.align-items-center( + (click)='selectOption(option)', + *ngFor='let option of filteredOptions' + ) + .mr-2 {{option.name}} + .text-muted {{option.description}} diff --git a/terminus-core/src/components/selectorModal.component.ts b/terminus-core/src/components/selectorModal.component.ts new file mode 100644 index 00000000..af39fccc --- /dev/null +++ b/terminus-core/src/components/selectorModal.component.ts @@ -0,0 +1,47 @@ +import { Component, Input } from '@angular/core' +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { SelectorOption } from '../api/selector' + +/** @hidden */ +@Component({ + template: require('./selectorModal.component.pug'), + // styles: [require('./selectorModal.component.scss')], +}) +export class SelectorModalComponent { + @Input() options: SelectorOption[] + @Input() filteredOptions: SelectorOption[] + @Input() filter = '' + @Input() name: string + + constructor ( + public modalInstance: NgbActiveModal, + ) { } + + ngOnInit () { + this.onFilterChange() + } + + onFilterChange () { + const f = this.filter.trim().toLowerCase() + if (!f) { + this.filteredOptions = this.options + } else { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + this.filteredOptions = this.options.filter(x => (x.name + (x.description || '')).toLowerCase().includes(f)) + } + } + + onFilterEnter () { + if (this.filteredOptions.length === 1) { + this.selectOption(this.filteredOptions[0]) + } + } + + selectOption (option: SelectorOption) { + this.modalInstance.close(option.result) + } + + close () { + this.modalInstance.dismiss() + } +} diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index f1e691e8..258693ae 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -16,6 +16,7 @@ import { TitleBarComponent } from './components/titleBar.component' import { ToggleComponent } from './components/toggle.component' import { WindowControlsComponent } from './components/windowControls.component' import { RenameTabModalComponent } from './components/renameTabModal.component' +import { SelectorModalComponent } from './components/selectorModal.component' import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component' import { SplitTabSpannerComponent } from './components/splitTabSpanner.component' import { WelcomeTabComponent } from './components/welcomeTab.component' @@ -82,6 +83,7 @@ const PROVIDERS = [ SafeModeModalComponent, AutofocusDirective, FastHtmlBindDirective, + SelectorModalComponent, SplitTabComponent, SplitTabSpannerComponent, WelcomeTabComponent, @@ -89,6 +91,7 @@ const PROVIDERS = [ entryComponents: [ RenameTabModalComponent, SafeModeModalComponent, + SelectorModalComponent, SplitTabComponent, WelcomeTabComponent, ], diff --git a/terminus-core/src/services/app.service.ts b/terminus-core/src/services/app.service.ts index 683a03d1..433fb911 100644 --- a/terminus-core/src/services/app.service.ts +++ b/terminus-core/src/services/app.service.ts @@ -2,9 +2,12 @@ import { Observable, Subject, AsyncSubject } from 'rxjs' import { takeUntil } from 'rxjs/operators' import { Injectable } from '@angular/core' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { BaseTabComponent } from '../components/baseTab.component' import { SplitTabComponent } from '../components/splitTab.component' +import { SelectorModalComponent } from '../components/selectorModal.component' +import { SelectorOption } from '../api/selector' import { ConfigService } from './config.service' import { HostAppService } from './hostApp.service' @@ -69,6 +72,7 @@ export class AppService { private hostApp: HostAppService, private tabRecovery: TabRecoveryService, private tabsService: TabsService, + private ngbModal: NgbModal, ) { if (hostApp.getWindow().id === 1) { if (config.store.terminal.recoverTabs) { @@ -315,4 +319,12 @@ export class AppService { stopObservingTabCompletion (tab: BaseTabComponent) { this.completionObservers.delete(tab) } + + showSelector (name: string, options: SelectorOption[]): Promise { + const modal = this.ngbModal.open(SelectorModalComponent) + const instance: SelectorModalComponent = modal.componentInstance + instance.name = name + instance.options = options + return modal.result as Promise + } } diff --git a/terminus-serial/src/api.ts b/terminus-serial/src/api.ts index fd3801d4..9e3ead80 100644 --- a/terminus-serial/src/api.ts +++ b/terminus-serial/src/api.ts @@ -26,6 +26,15 @@ export interface SerialConnection { color?: string } +export const BAUD_RATES = [ + 110, 150, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600, +] + +export interface SerialPortInfo { + name: string + description?: string +} + export class SerialSession extends BaseSession { scripts?: LoginScript[] serial: SerialPort diff --git a/terminus-serial/src/components/editConnectionModal.component.pug b/terminus-serial/src/components/editConnectionModal.component.pug index e179395f..44ac6c8f 100644 --- a/terminus-serial/src/components/editConnectionModal.component.pug +++ b/terminus-serial/src/components/editConnectionModal.component.pug @@ -24,14 +24,16 @@ input.form-control( type='text', [(ngModel)]='connection.port', + [ngbTypeahead]='portsAutocomplete', + [resultFormatter]='portsFormatter', ) .form-group label Baud Rate - input.form-control( - type='number', + select.form-control( [(ngModel)]='connection.baudrate', ) + option([value]='x', *ngFor='let x of baudRates') {{x}} ngb-tab(id='advanced') ng-template(ngbTabTitle) Advanced diff --git a/terminus-serial/src/components/editConnectionModal.component.ts b/terminus-serial/src/components/editConnectionModal.component.ts index b121bf75..236660be 100644 --- a/terminus-serial/src/components/editConnectionModal.component.ts +++ b/terminus-serial/src/components/editConnectionModal.component.ts @@ -1,7 +1,9 @@ import { Component } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { map } from 'rxjs/operators' import { ElectronService, HostAppService } from 'terminus-core' -import { SerialConnection, LoginScript } from '../api' +import { SerialConnection, LoginScript, SerialPortInfo, BAUD_RATES } from '../api' +import { SerialService } from '../services/serial.service' // import { PromptModalComponent } from './promptModal.component' /** @hidden */ @@ -10,17 +12,32 @@ import { SerialConnection, LoginScript } from '../api' }) export class EditConnectionModalComponent { connection: SerialConnection + foundPorts: SerialPortInfo[] + baudRates = BAUD_RATES constructor ( private modalInstance: NgbActiveModal, private electron: ElectronService, private hostApp: HostAppService, - // private ngbModal: NgbModal, + private serial: SerialService, ) { } + portsAutocomplete = text$ => text$.pipe(map(() => { + return this.foundPorts.map(x => x.name) + })) + + portsFormatter = port => { + const p = this.foundPorts.find(x => x.name === port) + if (p?.description) { + return `${port} (${p.description})` + } + return port + } + async ngOnInit () { this.connection.scripts = this.connection.scripts || [] + this.foundPorts = await this.serial.listPorts() } save () { diff --git a/terminus-serial/src/components/serialModal.component.pug b/terminus-serial/src/components/serialModal.component.pug index 1520934c..e7b66928 100644 --- a/terminus-serial/src/components/serialModal.component.pug +++ b/terminus-serial/src/components/serialModal.component.pug @@ -30,3 +30,11 @@ ) .mr-2 {{connection.name}} .text-muted {{connection.port}} + + .list-group.mt-3(*ngIf='foundPorts.length') + a.list-group-item.list-group-item-action.d-flex.align-items-center( + (click)='connectFoundPort(port)', + *ngFor='let port of foundPorts' + ) + .mr-2 {{port.name}} + .text-muted {{port.description}} diff --git a/terminus-serial/src/components/serialModal.component.ts b/terminus-serial/src/components/serialModal.component.ts index 94e117f3..bbc9490a 100644 --- a/terminus-serial/src/components/serialModal.component.ts +++ b/terminus-serial/src/components/serialModal.component.ts @@ -4,7 +4,7 @@ import { ToastrService } from 'ngx-toastr' import { ConfigService, AppService } from 'terminus-core' import { SettingsTabComponent } from 'terminus-settings' import { SerialService } from '../services/serial.service' -import { SerialConnection, SerialConnectionGroup } from '../api' +import { SerialConnection, SerialConnectionGroup, SerialPortInfo, BAUD_RATES } from '../api' /** @hidden */ @Component({ @@ -18,6 +18,7 @@ export class SerialModalComponent { lastConnection: SerialConnection|null = null childGroups: SerialConnectionGroup[] groupCollapsed: {[id: string]: boolean} = {} + foundPorts: SerialPortInfo[] = [] constructor ( public modalInstance: NgbActiveModal, @@ -27,12 +28,14 @@ export class SerialModalComponent { private toastr: ToastrService, ) { } - ngOnInit () { + async ngOnInit () { this.connections = this.config.store.serial.connections if (window.localStorage.lastSerialConnection) { this.lastConnection = JSON.parse(window.localStorage.lastSerialConnection) } this.refresh() + + this.foundPorts = await this.serial.listPorts() } quickConnect () { @@ -105,4 +108,12 @@ export class SerialModalComponent { group.connections.push(connection) } } + + async connectFoundPort (port: SerialPortInfo) { + const rate = await this.app.showSelector('Baud rate', BAUD_RATES.map(x => ({ + name: x.toString(), result: x, + }))) + this.quickTarget = `${port.name}@${rate}` + this.quickConnect() + } } diff --git a/terminus-serial/src/services/serial.service.ts b/terminus-serial/src/services/serial.service.ts index 3d8451b6..92d3abd2 100644 --- a/terminus-serial/src/services/serial.service.ts +++ b/terminus-serial/src/services/serial.service.ts @@ -2,19 +2,23 @@ import { Injectable, NgZone } from '@angular/core' import SerialPort from 'serialport' import { ToastrService } from 'ngx-toastr' import { AppService, LogService } from 'terminus-core' -import { SerialConnection, SerialSession } from '../api' +import { SerialConnection, SerialSession, SerialPortInfo } from '../api' import { SerialTabComponent } from '../components/serialTab.component' @Injectable({ providedIn: 'root' }) export class SerialService { - private constructor ( private log: LogService, private app: AppService, private zone: NgZone, private toastr: ToastrService, - ) { + ) { } + async listPorts (): Promise { + return (await SerialPort.list()).map(x => ({ + name: x.path, + description: x.manufacturer || x.serialNumber ? `${x.manufacturer || ''} ${x.serialNumber || ''}` : undefined, + })) } async openTab (connection: SerialConnection): Promise {