diff --git a/.gitignore b/.gitignore index 301dac42..6707ec2f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ npm-debug.log builtin-plugins package-lock.json yarn-error.log + +docs/api diff --git a/package.json b/package.json index 01347b3a..de39e16a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "tslint": "^5.12.0", "tslint-config-standard": "^8.0.1", "tslint-eslint-rules": "^5.4.0", + "typedoc": "^0.14.2", "typescript": "^3.1.3", "url-loader": "^1.1.1", "val-loader": "0.5.0", diff --git a/terminus-core/README.md b/terminus-core/README.md new file mode 100644 index 00000000..84724ea4 --- /dev/null +++ b/terminus-core/README.md @@ -0,0 +1,29 @@ +Terminus Core Plugin +-------------------- + +* tabbed interface services +* toolbar UI +* config file management +* hotkeys +* tab recovery +* logging +* theming + +Using the API: + +```ts +import { AppService, TabContextMenuItemProvider } from 'terminus-core' +``` + +Exporting your subclasses: + +```ts +@NgModule({ + ... + providers: [ + ... + { provide: TabContextMenuItemProvider, useClass: MyContextMenu, multi: true }, + ... + ] +}) +``` diff --git a/terminus-core/src/api/configProvider.ts b/terminus-core/src/api/configProvider.ts index 9bafc874..7ed3b868 100644 --- a/terminus-core/src/api/configProvider.ts +++ b/terminus-core/src/api/configProvider.ts @@ -1,4 +1,37 @@ +/** + * Extend to add your own config options + */ export abstract class ConfigProvider { + /** + * Default values, e.g. + * + * ```ts + * defaults = { + * myPlugin: { + * foo: 1 + * } + * } + * ``` + */ defaults: any = {} - platformDefaults: any = {} + + /** + * [[Platform]] specific defaults, e.g. + * + * ```ts + * platformDefaults = { + * [Platform.Windows]: { + * myPlugin: { + * bar: true + * } + * }, + * [Platform.macOS]: { + * myPlugin: { + * bar: false + * } + * }, + * } + * ``` + */ + platformDefaults: {[platform: string]: any} = {} } diff --git a/terminus-core/src/api/hotkeyProvider.ts b/terminus-core/src/api/hotkeyProvider.ts index d1bb95b0..54bc6b0f 100644 --- a/terminus-core/src/api/hotkeyProvider.ts +++ b/terminus-core/src/api/hotkeyProvider.ts @@ -1,8 +1,12 @@ export interface IHotkeyDescription { - id: string, - name: string, + id: string + name: string } +/** + * Extend to provide your own hotkeys. A corresponding [[ConfigProvider]] + * must also provide the `hotkeys.foo` config options with the default values + */ export abstract class HotkeyProvider { hotkeys: IHotkeyDescription[] = [] diff --git a/terminus-core/src/api/tabContextMenuProvider.ts b/terminus-core/src/api/tabContextMenuProvider.ts index 69953fc0..e3581abf 100644 --- a/terminus-core/src/api/tabContextMenuProvider.ts +++ b/terminus-core/src/api/tabContextMenuProvider.ts @@ -1,6 +1,9 @@ import { BaseTabComponent } from '../components/baseTab.component' import { TabHeaderComponent } from '../components/tabHeader.component' +/** + * Extend to add items to the tab header's context menu + */ export abstract class TabContextMenuItemProvider { weight = 0 diff --git a/terminus-core/src/api/tabRecovery.ts b/terminus-core/src/api/tabRecovery.ts index b14fc454..63e7ec82 100644 --- a/terminus-core/src/api/tabRecovery.ts +++ b/terminus-core/src/api/tabRecovery.ts @@ -1,10 +1,38 @@ import { TabComponentType } from '../services/tabs.service' export interface RecoveredTab { - type: TabComponentType, - options?: any, + /** + * Component type to be instantiated + */ + type: TabComponentType + + /** + * Component instance inputs + */ + options?: any } +/** + * Extend to enable recovery for your custom tab. + * This works in conjunction with [[getRecoveryToken()]] + * + * Terminus will try to find any [[TabRecoveryProvider]] that is able to process + * the recovery token previously returned by [[getRecoveryToken]]. + * + * Recommended token format: + * + * ```json + * { + * type: 'my-tab-type', + * foo: 'bar', + * } + * ``` + */ export abstract class TabRecoveryProvider { + /** + * @param recoveryToken a recovery token found in the saved tabs list + * @returns [[RecoveredTab]] descriptor containing tab type and component inputs + * or `null` if this token is from a different tab type or is not supported + */ abstract async recover (recoveryToken: any): Promise } diff --git a/terminus-core/src/api/theme.ts b/terminus-core/src/api/theme.ts index 9f9792ed..d86577be 100644 --- a/terminus-core/src/api/theme.ts +++ b/terminus-core/src/api/theme.ts @@ -1,5 +1,13 @@ +/** + * Extend to add a custom CSS theme + */ export abstract class Theme { name: string + + /** + * Complete CSS stylesheet + */ css: string + terminalBackground: string } diff --git a/terminus-core/src/api/toolbarButtonProvider.ts b/terminus-core/src/api/toolbarButtonProvider.ts index 16189c90..9e598ebe 100644 --- a/terminus-core/src/api/toolbarButtonProvider.ts +++ b/terminus-core/src/api/toolbarButtonProvider.ts @@ -1,14 +1,34 @@ import { SafeHtml } from '@angular/platform-browser' +/** + * See [[ToolbarButtonProvider]] + */ export interface IToolbarButton { + /** + * Raw SVG icon code + */ icon: SafeHtml - touchBarNSImage?: string + title: string + + /** + * Optional Touch Bar icon ID + */ + touchBarNSImage?: string + + /** + * Optional Touch Bar button label + */ touchBarTitle?: string + weight?: number + click: () => void } +/** + * Extend to add buttons to the toolbar + */ export abstract class ToolbarButtonProvider { abstract provide (): IToolbarButton[] } diff --git a/terminus-core/src/components/appRoot.component.ts b/terminus-core/src/components/appRoot.component.ts index ccdb18e4..3eabb8f0 100644 --- a/terminus-core/src/components/appRoot.component.ts +++ b/terminus-core/src/components/appRoot.component.ts @@ -17,6 +17,7 @@ import { BaseTabComponent } from './baseTab.component' import { SafeModeModalComponent } from './safeModeModal.component' import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api' +/** @hidden */ @Component({ selector: 'app-root', template: require('./appRoot.component.pug'), @@ -126,6 +127,11 @@ export class AppRootComponent { this.onGlobalHotkey() }) + this.hostApp.windowCloseRequest$.subscribe(async () => { + await this.app.closeAllTabs() + this.hostApp.closeWindow() + }) + if (window['safeModeReason']) { ngbModal.open(SafeModeModalComponent) } diff --git a/terminus-core/src/components/baseTab.component.ts b/terminus-core/src/components/baseTab.component.ts index f3aca3f5..d291f014 100644 --- a/terminus-core/src/components/baseTab.component.ts +++ b/terminus-core/src/components/baseTab.component.ts @@ -1,26 +1,58 @@ import { Observable, Subject } from 'rxjs' import { ViewRef } from '@angular/core' +/** + * Represents an active "process" inside a tab, + * for example, a user process running inside a terminal tab + */ export interface BaseTabProcess { name: string } +/** + * Abstract base class for custom tab components + */ export abstract class BaseTabComponent { + /** + * Current tab title + */ title: string + + /** + * User-defined title override + */ customTitle: string - hasFocus = false + + /** + * Last tab activity state + */ hasActivity = false + + /** + * ViewRef to the tab DOM element + */ hostView: ViewRef + + /** + * CSS color override for the tab's header + */ color: string = null - protected titleChange = new Subject() - protected focused = new Subject() - protected blurred = new Subject() - protected progress = new Subject() - protected activity = new Subject() - protected destroyed = new Subject() + + protected hasFocus = false + + /** + * Ping this if your recovery state has been changed and you want + * your tab state to be saved sooner + */ protected recoveryStateChangedHint = new Subject() private progressClearTimeout: number + private titleChange = new Subject() + private focused = new Subject() + private blurred = new Subject() + private progress = new Subject() + private activity = new Subject() + private destroyed = new Subject() get focused$ (): Observable { return this.focused } get blurred$ (): Observable { return this.blurred } @@ -46,6 +78,11 @@ export abstract class BaseTabComponent { } } + /** + * Sets visual progressbar on the tab + * + * @param {type} progress: value between 0 and 1, or `null` to remove + */ setProgress (progress: number) { this.progress.next(progress) if (progress) { @@ -58,24 +95,43 @@ export abstract class BaseTabComponent { } } + /** + * Shows the acticity marker on the tab header + */ displayActivity (): void { this.hasActivity = true this.activity.next(true) } + /** + * Removes the acticity marker from the tab header + */ clearActivity (): void { this.hasActivity = false this.activity.next(false) } + /** + * Override this and implement a [[TabRecoveryProvider]] to enable recovery + * for your custom tab + * + * @return JSON serializable tab state representation + * for your [[TabRecoveryProvider]] to parse + */ async getRecoveryToken (): Promise { return null } + /** + * Override this to enable task completion notifications for the tab + */ async getCurrentProcess (): Promise { return null } + /** + * Return false to prevent the tab from being closed + */ async canClose (): Promise { return true } @@ -88,6 +144,9 @@ export abstract class BaseTabComponent { this.blurred.next() } + /** + * Called before the tab is closed + */ destroy (): void { this.focused.complete() this.blurred.complete() diff --git a/terminus-core/src/components/checkbox.component.ts b/terminus-core/src/components/checkbox.component.ts index 060f0e95..988b377c 100644 --- a/terminus-core/src/components/checkbox.component.ts +++ b/terminus-core/src/components/checkbox.component.ts @@ -1,6 +1,7 @@ import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' +/** @hidden */ @Component({ selector: 'checkbox', template: require('./checkbox.component.pug'), diff --git a/terminus-core/src/components/renameTabModal.component.ts b/terminus-core/src/components/renameTabModal.component.ts index 95993cef..e8e105b3 100644 --- a/terminus-core/src/components/renameTabModal.component.ts +++ b/terminus-core/src/components/renameTabModal.component.ts @@ -1,6 +1,7 @@ import { Component, Input, ElementRef, ViewChild } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +/** @hidden */ @Component({ selector: 'rename-tab-modal', template: require('./renameTabModal.component.pug'), diff --git a/terminus-core/src/components/safeModeModal.component.ts b/terminus-core/src/components/safeModeModal.component.ts index 33a5b605..a2dd39c0 100644 --- a/terminus-core/src/components/safeModeModal.component.ts +++ b/terminus-core/src/components/safeModeModal.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +/** @hidden */ @Component({ template: require('./safeModeModal.component.pug'), }) diff --git a/terminus-core/src/components/splitTab.component.ts b/terminus-core/src/components/splitTab.component.ts index 8e8b82c4..6877571a 100644 --- a/terminus-core/src/components/splitTab.component.ts +++ b/terminus-core/src/components/splitTab.component.ts @@ -9,15 +9,30 @@ import { TabRecoveryService } from '../services/tabRecovery.service' export declare type SplitOrientation = 'v' | 'h' export declare type SplitDirection = 'r' | 't' | 'b' | 'l' +/** + * Describes a horizontal or vertical split row or column + */ export class SplitContainer { orientation: SplitOrientation = 'h' + + /** + * Children could be tabs or other containers + */ children: (BaseTabComponent | SplitContainer)[] = [] + + /** + * Relative sizes of children, between 0 and 1. Total sum is 1 + */ ratios: number[] = [] + x: number y: number w: number h: number + /** + * @return Flat list of all tabs inside this container + */ getAllTabs () { let r = [] for (let child of this.children) { @@ -30,6 +45,9 @@ export class SplitContainer { return r } + /** + * Remove unnecessarily nested child containers and renormalizes [[ratios]] + */ normalize () { for (let i = 0; i < this.children.length; i++) { let child = this.children[i] @@ -64,6 +82,9 @@ export class SplitContainer { this.ratios = this.ratios.map(x => x / s) } + /** + * Gets the left/top side offset for the given element index (between 0 and 1) + */ getOffsetRatio (index: number): number { let s = 0 for (let i = 0; i < index; i++) { @@ -90,11 +111,22 @@ export class SplitContainer { } } +/** + * Represents a spanner (draggable border between two split areas) + */ export interface SplitSpannerInfo { container: SplitContainer + + /** + * Number of the right/bottom split in the container + */ index: number } +/** + * Split tab is a tab that contains other tabs and allows further splitting them + * You'll mainly encounter it inside [[AppService]].tabs + */ @Component({ selector: 'split-tab', template: ` @@ -109,23 +141,43 @@ export interface SplitSpannerInfo { styles: [require('./splitTab.component.scss')], }) export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy { + /** @hidden */ @ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef + + /** + * Top-level split container + */ root: SplitContainer + + /** @hidden */ _recoveredState: any + + /** @hidden */ _spanners: SplitSpannerInfo[] = [] + private focusedTab: BaseTabComponent private hotkeysSubscription: Subscription private viewRefs: Map> = new Map() - protected tabAdded = new Subject() - protected tabRemoved = new Subject() - protected splitAdjusted = new Subject() - protected focusChanged = new Subject() + private tabAdded = new Subject() + private tabRemoved = new Subject() + private splitAdjusted = new Subject() + private focusChanged = new Subject() + get tabAdded$ (): Observable { return this.tabAdded } get tabRemoved$ (): Observable { return this.tabRemoved } + + /** + * Fired when split ratio is changed for a given spanner + */ get splitAdjusted$ (): Observable { return this.splitAdjusted } + + /** + * Fired when a different sub-tab gains focus + */ get focusChanged$ (): Observable { return this.focusChanged } + /** @hidden */ constructor ( private hotkeys: HotkeysService, private tabsService: TabsService, @@ -174,6 +226,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes }) } + /** @hidden */ async ngOnInit () { if (this._recoveredState) { await this.recoverContainer(this.root, this._recoveredState) @@ -185,10 +238,12 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes } } + /** @hidden */ ngOnDestroy () { this.hotkeysSubscription.unsubscribe() } + /** @returns Flat list of all sub-tabs */ getAllTabs () { return this.root.getAllTabs() } @@ -211,6 +266,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes this.layout() } + /** + * Focuses the first available tab inside the given [[SplitContainer]] + */ focusAnyIn (parent: BaseTabComponent | SplitContainer) { if (!parent) { return @@ -222,13 +280,16 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes } } - addTab (tab: BaseTabComponent, relative: BaseTabComponent, dir: SplitDirection) { + /** + * Inserts a new `tab` to the `side` of the `relative` tab + */ + addTab (tab: BaseTabComponent, relative: BaseTabComponent, side: SplitDirection) { let target = this.getParentOf(relative) || this.root let insertIndex = target.children.indexOf(relative) if ( - (target.orientation === 'v' && ['l', 'r'].includes(dir)) || - (target.orientation === 'h' && ['t', 'b'].includes(dir)) + (target.orientation === 'v' && ['l', 'r'].includes(side)) || + (target.orientation === 'h' && ['t', 'b'].includes(side)) ) { let newContainer = new SplitContainer() newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v' @@ -242,7 +303,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes if (insertIndex === -1) { insertIndex = 0 } else { - insertIndex += (dir === 'l' || dir === 't') ? 0 : 1 + insertIndex += (side === 'l' || side === 't') ? 0 : 1 } for (let i = 0; i < target.children.length; i++) { @@ -278,6 +339,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes } } + /** + * Moves focus in the given direction + */ navigate (dir: SplitDirection) { let rel: BaseTabComponent | SplitContainer = this.focusedTab let parent = this.getParentOf(rel) @@ -309,6 +373,9 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes this.addTab(newTab, tab, dir) } + /** + * @returns the immediate parent of `tab` + */ getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer { root = root || this.root for (let child of root.children) { @@ -325,18 +392,22 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes return null } + /** @hidden */ async canClose (): Promise { return !(await Promise.all(this.getAllTabs().map(x => x.canClose()))).some(x => !x) } + /** @hidden */ async getRecoveryToken (): Promise { return this.root.serialize() } + /** @hidden */ async getCurrentProcess (): Promise { return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x) } + /** @hidden */ onSpannerAdjusted (spanner: SplitSpannerInfo) { this.layout() this.splitAdjusted.next(spanner) @@ -433,6 +504,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes } } +/** @hidden */ @Injectable() export class SplitTabRecoveryProvider extends TabRecoveryProvider { async recover (recoveryToken: any): Promise { diff --git a/terminus-core/src/components/splitTabSpanner.component.ts b/terminus-core/src/components/splitTabSpanner.component.ts index 04b78325..e942e8e4 100644 --- a/terminus-core/src/components/splitTabSpanner.component.ts +++ b/terminus-core/src/components/splitTabSpanner.component.ts @@ -1,6 +1,7 @@ import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core' import { SplitContainer } from './splitTab.component' +/** @hidden */ @Component({ selector: 'split-tab-spanner', template: '', diff --git a/terminus-core/src/components/startPage.component.ts b/terminus-core/src/components/startPage.component.ts index dde12e9e..124a19db 100644 --- a/terminus-core/src/components/startPage.component.ts +++ b/terminus-core/src/components/startPage.component.ts @@ -3,6 +3,7 @@ import { ConfigService } from '../services/config.service' import { HomeBaseService } from '../services/homeBase.service' import { IToolbarButton, ToolbarButtonProvider } from '../api' +/** @hidden */ @Component({ selector: 'start-page', template: require('./startPage.component.pug'), diff --git a/terminus-core/src/components/tabBody.component.ts b/terminus-core/src/components/tabBody.component.ts index 8ed241bd..0735ec4d 100644 --- a/terminus-core/src/components/tabBody.component.ts +++ b/terminus-core/src/components/tabBody.component.ts @@ -1,6 +1,7 @@ import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core' import { BaseTabComponent } from '../components/baseTab.component' +/** @hidden */ @Component({ selector: 'tab-body', template: ` diff --git a/terminus-core/src/components/tabHeader.component.ts b/terminus-core/src/components/tabHeader.component.ts index ae7458eb..d1eda806 100644 --- a/terminus-core/src/components/tabHeader.component.ts +++ b/terminus-core/src/components/tabHeader.component.ts @@ -9,6 +9,7 @@ import { ElectronService } from '../services/electron.service' import { AppService } from '../services/app.service' import { HostAppService, Platform } from '../services/hostApp.service' +/** @hidden */ @Component({ selector: 'tab-header', template: require('./tabHeader.component.pug'), diff --git a/terminus-core/src/components/titleBar.component.ts b/terminus-core/src/components/titleBar.component.ts index b53bc60e..70e390f1 100644 --- a/terminus-core/src/components/titleBar.component.ts +++ b/terminus-core/src/components/titleBar.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core' +/** @hidden */ @Component({ selector: 'title-bar', template: require('./titleBar.component.pug'), diff --git a/terminus-core/src/components/toggle.component.ts b/terminus-core/src/components/toggle.component.ts index 91fd5878..c07b40b0 100644 --- a/terminus-core/src/components/toggle.component.ts +++ b/terminus-core/src/components/toggle.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core' import { NG_VALUE_ACCESSOR } from '@angular/forms' import { CheckboxComponent } from './checkbox.component' +/** @hidden */ @Component({ selector: 'toggle', template: ` diff --git a/terminus-core/src/components/windowControls.component.pug b/terminus-core/src/components/windowControls.component.pug index ad14083f..84716e91 100644 --- a/terminus-core/src/components/windowControls.component.pug +++ b/terminus-core/src/components/windowControls.component.pug @@ -9,7 +9,7 @@ button.btn.btn-secondary.btn-maximize( svg(version='1.1', width='10', height='10') path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z') button.btn.btn-secondary.btn-close( - (click)='app.closeWindow()' + (click)='closeWindow()' ) svg(version='1.1', width='10', height='10') path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z') diff --git a/terminus-core/src/components/windowControls.component.ts b/terminus-core/src/components/windowControls.component.ts index 2b5188e7..152382da 100644 --- a/terminus-core/src/components/windowControls.component.ts +++ b/terminus-core/src/components/windowControls.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core' import { HostAppService } from '../services/hostApp.service' import { AppService } from '../services/app.service' +/** @hidden */ @Component({ selector: 'window-controls', template: require('./windowControls.component.pug'), @@ -9,4 +10,9 @@ import { AppService } from '../services/app.service' }) export class WindowControlsComponent { constructor (public hostApp: HostAppService, public app: AppService) { } + + async closeWindow () { + await this.app.closeAllTabs() + this.hostApp.closeWindow() + } } diff --git a/terminus-core/src/config.ts b/terminus-core/src/config.ts index 086ca350..a81ff957 100644 --- a/terminus-core/src/config.ts +++ b/terminus-core/src/config.ts @@ -1,6 +1,7 @@ import { ConfigProvider } from './api/configProvider' import { Platform } from './services/hostApp.service' +/** @hidden */ export class CoreConfigProvider extends ConfigProvider { platformDefaults = { [Platform.macOS]: require('./configDefaults.macos.yaml'), diff --git a/terminus-core/src/directives/autofocus.directive.ts b/terminus-core/src/directives/autofocus.directive.ts index 34b3625e..43743bf6 100644 --- a/terminus-core/src/directives/autofocus.directive.ts +++ b/terminus-core/src/directives/autofocus.directive.ts @@ -1,5 +1,6 @@ import { Directive, AfterViewInit, ElementRef } from '@angular/core' +/** @hidden */ @Directive({ selector: '[autofocus]' }) diff --git a/terminus-core/src/hotkeys.ts b/terminus-core/src/hotkeys.ts new file mode 100644 index 00000000..524ef8a1 --- /dev/null +++ b/terminus-core/src/hotkeys.ts @@ -0,0 +1,117 @@ +import { Injectable } from '@angular/core' +import { IHotkeyDescription, HotkeyProvider } from './api/hotkeyProvider' + +/** @hidden */ +@Injectable() +export class AppHotkeyProvider extends HotkeyProvider { + hotkeys: IHotkeyDescription[] = [ + { + id: 'new-window', + name: 'New window', + }, + { + id: 'toggle-window', + name: 'Toggle terminal window', + }, + { + id: 'toggle-fullscreen', + name: 'Toggle fullscreen mode', + }, + { + id: 'rename-tab', + name: 'Rename Tab', + }, + { + id: 'close-tab', + name: 'Close tab', + }, + { + id: 'toggle-last-tab', + name: 'Toggle last tab', + }, + { + id: 'next-tab', + name: 'Next tab', + }, + { + id: 'previous-tab', + name: 'Previous tab', + }, + { + id: 'tab-1', + name: 'Tab 1', + }, + { + id: 'tab-2', + name: 'Tab 2', + }, + { + id: 'tab-3', + name: 'Tab 3', + }, + { + id: 'tab-4', + name: 'Tab 4', + }, + { + id: 'tab-5', + name: 'Tab 5', + }, + { + id: 'tab-6', + name: 'Tab 6', + }, + { + id: 'tab-7', + name: 'Tab 7', + }, + { + id: 'tab-8', + name: 'Tab 8', + }, + { + id: 'tab-9', + name: 'Tab 9', + }, + { + id: 'tab-10', + name: 'Tab 10', + }, + { + id: 'split-right', + name: 'Split to the right', + }, + { + id: 'split-bottom', + name: 'Split to the bottom', + }, + { + id: 'split-left', + name: 'Split to the left', + }, + { + id: 'split-top', + name: 'Split to the top', + }, + { + id: 'split-nav-up', + name: 'Focus the pane above', + }, + { + id: 'split-nav-down', + name: 'Focus the pane below', + }, + { + id: 'split-nav-left', + name: 'Focus the pane on the left', + }, + { + id: 'split-nav-right', + name: 'Focus the pane on the right', + }, + ] + + async provide (): Promise { + return this.hotkeys + } +} diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index 5e617ca8..bafb5571 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -6,8 +6,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar' import { DndModule } from 'ng2-dnd' -import { AppHotkeyProvider } from './services/hotkeys.service' - import { AppRootComponent } from './components/appRoot.component' import { CheckboxComponent } from './components/checkbox.component' import { TabBodyComponent } from './components/tabBody.component' @@ -31,6 +29,7 @@ import { TabRecoveryProvider } from './api/tabRecovery' import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme' import { CoreConfigProvider } from './config' +import { AppHotkeyProvider } from './hotkeys' import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu' import 'perfect-scrollbar/css/perfect-scrollbar.css' @@ -49,6 +48,7 @@ const PROVIDERS = [ { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } } ] +/** @hidden */ @NgModule({ imports: [ BrowserModule, diff --git a/terminus-core/src/services/app.service.ts b/terminus-core/src/services/app.service.ts index cb320c74..6c9eef84 100644 --- a/terminus-core/src/services/app.service.ts +++ b/terminus-core/src/services/app.service.ts @@ -3,7 +3,6 @@ import { takeUntil } from 'rxjs/operators' import { Injectable } from '@angular/core' import { BaseTabComponent } from '../components/baseTab.component' import { SplitTabComponent } from '../components/splitTab.component' -import { Logger, LogService } from './log.service' import { ConfigService } from './config.service' import { HostAppService } from './hostApp.service' import { TabRecoveryService } from './tabRecovery.service' @@ -39,9 +38,11 @@ class CompletionObserver { @Injectable({ providedIn: 'root' }) export class AppService { tabs: BaseTabComponent[] = [] - activeTab: BaseTabComponent - lastTabIndex = 0 - logger: Logger + + get activeTab (): BaseTabComponent { return this._activeTab } + + private lastTabIndex = 0 + private _activeTab: BaseTabComponent private activeTabChange = new Subject() private tabsChanged = new Subject() @@ -55,19 +56,17 @@ export class AppService { get tabOpened$ (): Observable { return this.tabOpened } get tabsChanged$ (): Observable { return this.tabsChanged } get tabClosed$ (): Observable { return this.tabClosed } + + /** Fires once when the app is ready */ get ready$ (): Observable { return this.ready } + /** @hidden */ constructor ( private config: ConfigService, private hostApp: HostAppService, private tabRecovery: TabRecoveryService, private tabsService: TabsService, - log: LogService, ) { - this.logger = log.create('app') - - this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow()) - this.tabRecovery.recoverTabs().then(tabs => { for (let tab of tabs) { this.openNewTabRaw(tab.type, tab.options) @@ -82,7 +81,7 @@ export class AppService { }) } - addTabRaw (tab: BaseTabComponent) { + private addTabRaw (tab: BaseTabComponent) { this.tabs.push(tab) this.selectTab(tab) this.tabsChanged.next() @@ -93,7 +92,7 @@ export class AppService { }) tab.titleChange$.subscribe(title => { - if (tab === this.activeTab) { + if (tab === this._activeTab) { this.hostApp.setTitle(title) } }) @@ -101,7 +100,7 @@ export class AppService { tab.destroyed$.subscribe(() => { let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1) this.tabs = this.tabs.filter((x) => x !== tab) - if (tab === this.activeTab) { + if (tab === this._activeTab) { this.selectTab(this.tabs[newIndex]) } this.tabsChanged.next() @@ -109,12 +108,20 @@ export class AppService { }) } + /** + * Adds a new tab **without** wrapping it in a SplitTabComponent + * @param inputs Properties to be assigned on the new tab component instance + */ openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent { let tab = this.tabsService.create(type, inputs) this.addTabRaw(tab) return tab } + /** + * Adds a new tab while wrapping it in a SplitTabComponent + * @param inputs Properties to be assigned on the new tab component instance + */ openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent { let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent let tab = this.tabsService.create(type, inputs) @@ -124,29 +131,30 @@ export class AppService { } selectTab (tab: BaseTabComponent) { - if (this.activeTab === tab) { - this.activeTab.emitFocused() + if (this._activeTab === tab) { + this._activeTab.emitFocused() return } - if (this.tabs.includes(this.activeTab)) { - this.lastTabIndex = this.tabs.indexOf(this.activeTab) + if (this.tabs.includes(this._activeTab)) { + this.lastTabIndex = this.tabs.indexOf(this._activeTab) } else { this.lastTabIndex = null } - if (this.activeTab) { - this.activeTab.clearActivity() - this.activeTab.emitBlurred() + if (this._activeTab) { + this._activeTab.clearActivity() + this._activeTab.emitBlurred() } - this.activeTab = tab + this._activeTab = tab this.activeTabChange.next(tab) - if (this.activeTab) { + if (this._activeTab) { setImmediate(() => { - this.activeTab.emitFocused() + this._activeTab.emitFocused() }) - this.hostApp.setTitle(this.activeTab.title) + this.hostApp.setTitle(this._activeTab.title) } } + /** Switches between the current tab and the previously active one */ toggleLastTab () { if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) { this.lastTabIndex = 0 @@ -156,7 +164,7 @@ export class AppService { nextTab () { if (this.tabs.length > 1) { - let tabIndex = this.tabs.indexOf(this.activeTab) + let tabIndex = this.tabs.indexOf(this._activeTab) if (tabIndex < this.tabs.length - 1) { this.selectTab(this.tabs[tabIndex + 1]) } else if (this.config.store.appearance.cycleTabs) { @@ -167,7 +175,7 @@ export class AppService { previousTab () { if (this.tabs.length > 1) { - let tabIndex = this.tabs.indexOf(this.activeTab) + let tabIndex = this.tabs.indexOf(this._activeTab) if (tabIndex > 0) { this.selectTab(this.tabs[tabIndex - 1]) } else if (this.config.store.appearance.cycleTabs) { @@ -176,6 +184,7 @@ export class AppService { } } + /** @hidden */ emitTabsChanged () { this.tabsChanged.next() } @@ -197,7 +206,7 @@ export class AppService { } } - async closeWindow () { + async closeAllTabs () { for (let tab of this.tabs) { if (!await tab.canClose()) { return @@ -206,15 +215,19 @@ export class AppService { for (let tab of this.tabs) { tab.destroy() } - this.hostApp.closeWindow() } + /** @hidden */ emitReady () { this.ready.next(null) this.ready.complete() this.hostApp.emitReady() } + /** + * Returns an observable that fires once + * the tab's internal "process" (see [[BaseTabProcess]]) completes + */ observeTabCompletion (tab: BaseTabComponent): Observable { if (!this.completionObservers.has(tab)) { let observer = new CompletionObserver(tab) diff --git a/terminus-core/src/services/config.service.ts b/terminus-core/src/services/config.service.ts index e38af32e..4d6a9c9e 100644 --- a/terminus-core/src/services/config.service.ts +++ b/terminus-core/src/services/config.service.ts @@ -18,6 +18,7 @@ function isNonStructuralObjectMember (v) { return v instanceof Object && !(v instanceof Array) && v.__nonStructural } +/** @hidden */ export class ConfigProxy { constructor (real: any, defaults: any) { for (let key in defaults) { @@ -76,9 +77,21 @@ export class ConfigProxy { @Injectable({ providedIn: 'root' }) export class ConfigService { + /** + * Contains the actual config values + */ store: any + + /** + * Whether an app restart is required due to recent changes + */ restartRequested: boolean + + /** + * Full config file path + */ path: string + private changed = new Subject() private _store: any private defaults: any @@ -86,6 +99,7 @@ export class ConfigService { get changed$ (): Observable { return this.changed } + /** @hidden */ constructor ( electron: ElectronService, private hostApp: HostAppService, @@ -129,10 +143,16 @@ export class ConfigService { this.hostApp.broadcastConfigChange() } + /** + * Reads config YAML as string + */ readRaw (): string { return yaml.safeDump(this._store) } + /** + * Writes config YAML as string + */ writeRaw (data: string): void { this._store = yaml.safeLoad(data) this.save() @@ -140,7 +160,7 @@ export class ConfigService { this.emitChange() } - emitChange (): void { + private emitChange (): void { this.changed.next() } @@ -148,6 +168,12 @@ export class ConfigService { this.restartRequested = true } + /** + * Filters a list of Angular services to only include those provided + * by plugins that are enabled + * + * @typeparam T Base provider type + */ enabledServices (services: T[]): T[] { if (!this.servicesCache) { this.servicesCache = {} diff --git a/terminus-core/src/services/docking.service.ts b/terminus-core/src/services/docking.service.ts index b3826373..7f2e6f47 100644 --- a/terminus-core/src/services/docking.service.ts +++ b/terminus-core/src/services/docking.service.ts @@ -10,6 +10,7 @@ export interface IScreen { @Injectable({ providedIn: 'root' }) export class DockingService { + /** @hidden */ constructor ( private electron: ElectronService, private config: ConfigService, @@ -78,7 +79,7 @@ export class DockingService { }) } - repositionWindow () { + private repositionWindow () { let [x, y] = this.hostApp.getWindow().getPosition() for (let screen of this.electron.screen.getAllDisplays()) { let bounds = screen.bounds diff --git a/terminus-core/src/services/electron.service.ts b/terminus-core/src/services/electron.service.ts index 3a415cc4..745d5162 100644 --- a/terminus-core/src/services/electron.service.ts +++ b/terminus-core/src/services/electron.service.ts @@ -24,6 +24,7 @@ export class ElectronService { MenuItem: typeof MenuItem private electron: any + /** @hidden */ constructor () { this.electron = require('electron') this.remote = this.electron.remote @@ -42,18 +43,9 @@ export class ElectronService { this.MenuItem = this.remote.MenuItem } - remoteRequire (name: string): any { - return this.remote.require(name) - } - - remoteRequirePluginModule (plugin: string, module: string, globals: any): any { - return this.remoteRequire(this.remoteResolvePluginModule(plugin, module, globals)) - } - - remoteResolvePluginModule (plugin: string, module: string, globals: any): any { - return globals.require.resolve(`${plugin}/node_modules/${module}`) - } - + /** + * Removes OS focus from Terminus' window + */ loseFocus () { if (process.platform === 'darwin') { this.remote.Menu.sendActionToFirstResponder('hide:') diff --git a/terminus-core/src/services/homeBase.service.ts b/terminus-core/src/services/homeBase.service.ts index 75c72574..48ee2f1c 100644 --- a/terminus-core/src/services/homeBase.service.ts +++ b/terminus-core/src/services/homeBase.service.ts @@ -9,6 +9,7 @@ import uuidv4 = require('uuid/v4') export class HomeBaseService { appVersion: string + /** @hidden */ constructor ( private electron: ElectronService, private config: ConfigService, diff --git a/terminus-core/src/services/hostApp.service.ts b/terminus-core/src/services/hostApp.service.ts index c0f4f095..01fbdf9f 100644 --- a/terminus-core/src/services/hostApp.service.ts +++ b/terminus-core/src/services/hostApp.service.ts @@ -16,12 +16,19 @@ export interface Bounds { height: number } +/** + * Provides interaction with the main process + */ @Injectable({ providedIn: 'root' }) export class HostAppService { platform: Platform - nodePlatform: string + + /** + * Fired once the window is visible + */ shown = new EventEmitter() isFullScreen = false + private preferencesMenu = new Subject() private secondInstance = new Subject() private cliOpenDirectory = new Subject() @@ -35,29 +42,62 @@ export class HostAppService { private logger: Logger private windowId: number + /** + * Fired when Preferences is selected in the macOS menu + */ get preferencesMenu$ (): Observable { return this.preferencesMenu } + + /** + * Fired when a second instance of Terminus is launched + */ get secondInstance$ (): Observable { return this.secondInstance } + + /** + * Fired for the `terminus open` CLI command + */ get cliOpenDirectory$ (): Observable { return this.cliOpenDirectory } + + /** + * Fired for the `terminus run` CLI command + */ get cliRunCommand$ (): Observable { return this.cliRunCommand } + + /** + * Fired for the `terminus paste` CLI command + */ get cliPaste$ (): Observable { return this.cliPaste } + + /** + * Fired for the `terminus profile` CLI command + */ get cliOpenProfile$ (): Observable { return this.cliOpenProfile } + + /** + * Fired when another window modified the config file + */ get configChangeBroadcast$ (): Observable { return this.configChangeBroadcast } + + /** + * Fired when the window close button is pressed + */ get windowCloseRequest$ (): Observable { return this.windowCloseRequest } + get windowMoved$ (): Observable { return this.windowMoved } + get displayMetricsChanged$ (): Observable { return this.displayMetricsChanged } + /** @hidden */ constructor ( private zone: NgZone, private electron: ElectronService, log: LogService, ) { this.logger = log.create('hostApp') - this.nodePlatform = require('os').platform() this.platform = { win32: Platform.Windows, darwin: Platform.macOS, linux: Platform.Linux - }[this.nodePlatform] + }[process.platform] this.windowId = parseInt(location.search.substring(1)) this.logger.info('Window ID:', this.windowId) @@ -117,6 +157,9 @@ export class HostAppService { })) } + /** + * Returns the current remote [[BrowserWindow]] + */ getWindow () { return this.electron.BrowserWindow.fromId(this.windowId) } @@ -125,18 +168,6 @@ export class HostAppService { this.electron.ipcRenderer.send('app:new-window') } - getShell () { - return this.electron.shell - } - - getAppPath () { - return this.electron.app.getAppPath() - } - - getPath (type: string) { - return this.electron.app.getPath(type) - } - toggleFullscreen () { let window = this.getWindow() window.setFullScreen(!this.isFullScreen) @@ -174,6 +205,11 @@ export class HostAppService { this.electron.ipcRenderer.send('window-set-always-on-top', flag) } + /** + * Sets window vibrancy mode (Windows, macOS) + * + * @param type `null`, or `fluent` when supported (Windowd only) + */ setVibrancy (enable: boolean, type: string) { document.body.classList.toggle('vibrant', enable) if (this.platform === Platform.macOS) { @@ -196,6 +232,9 @@ export class HostAppService { this.electron.Menu.buildFromTemplate(menuDefinition).popup({}) } + /** + * Notifies other windows of config file changes + */ broadcastConfigChange () { this.electron.ipcRenderer.send('app:config-change') } diff --git a/terminus-core/src/services/hotkeys.service.ts b/terminus-core/src/services/hotkeys.service.ts index 85348660..e4a668e3 100644 --- a/terminus-core/src/services/hotkeys.service.ts +++ b/terminus-core/src/services/hotkeys.service.ts @@ -5,16 +5,16 @@ import { ConfigService } from '../services/config.service' import { ElectronService } from '../services/electron.service' export interface PartialHotkeyMatch { - id: string, - strokes: string[], - matchedLength: number, + id: string + strokes: string[] + matchedLength: number } const KEY_TIMEOUT = 2000 interface EventBufferEntry { - event: NativeKeyEvent, - time: number, + event: NativeKeyEvent + time: number } @Injectable({ providedIn: 'root' }) @@ -26,6 +26,7 @@ export class HotkeysService { private disabledLevel = 0 private hotkeyDescriptions: IHotkeyDescription[] = [] + /** @hidden */ constructor ( private zone: NgZone, private electron: ElectronService, @@ -51,11 +52,20 @@ export class HotkeysService { }) } + /** + * Adds a new key event to the buffer + * + * @param name DOM event name + * @param nativeEvent event object + */ pushKeystroke (name, nativeEvent) { nativeEvent.event = name this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() }) } + /** + * Check the buffer for new complete keystrokes + */ processKeystrokes () { if (this.isEnabled()) { this.zone.run(() => { @@ -84,7 +94,7 @@ export class HotkeysService { return stringifyKeySequence(this.currentKeystrokes.map(x => x.event)) } - registerGlobalHotkey () { + private registerGlobalHotkey () { this.electron.globalShortcut.unregisterAll() let value = this.config.store.hotkeys['toggle-window'] || [] if (typeof value === 'string') { @@ -103,11 +113,11 @@ export class HotkeysService { }) } - getHotkeysConfig () { + private getHotkeysConfig () { return this.getHotkeysConfigRecursive(this.config.store.hotkeys) } - getHotkeysConfigRecursive (branch) { + private getHotkeysConfigRecursive (branch) { let keys = {} for (let key in branch) { let value = branch[key] @@ -129,7 +139,7 @@ export class HotkeysService { return keys } - getCurrentFullyMatchedHotkey (): string { + private getCurrentFullyMatchedHotkey (): string { let currentStrokes = this.getCurrentKeystrokes() let config = this.getHotkeysConfig() for (let id in config) { @@ -199,117 +209,3 @@ export class HotkeysService { ).reduce((a, b) => a.concat(b)) } } - -@Injectable() -export class AppHotkeyProvider extends HotkeyProvider { - hotkeys: IHotkeyDescription[] = [ - { - id: 'new-window', - name: 'New window', - }, - { - id: 'toggle-window', - name: 'Toggle terminal window', - }, - { - id: 'toggle-fullscreen', - name: 'Toggle fullscreen mode', - }, - { - id: 'rename-tab', - name: 'Rename Tab', - }, - { - id: 'close-tab', - name: 'Close tab', - }, - { - id: 'toggle-last-tab', - name: 'Toggle last tab', - }, - { - id: 'next-tab', - name: 'Next tab', - }, - { - id: 'previous-tab', - name: 'Previous tab', - }, - { - id: 'tab-1', - name: 'Tab 1', - }, - { - id: 'tab-2', - name: 'Tab 2', - }, - { - id: 'tab-3', - name: 'Tab 3', - }, - { - id: 'tab-4', - name: 'Tab 4', - }, - { - id: 'tab-5', - name: 'Tab 5', - }, - { - id: 'tab-6', - name: 'Tab 6', - }, - { - id: 'tab-7', - name: 'Tab 7', - }, - { - id: 'tab-8', - name: 'Tab 8', - }, - { - id: 'tab-9', - name: 'Tab 9', - }, - { - id: 'tab-10', - name: 'Tab 10', - }, - { - id: 'split-right', - name: 'Split to the right', - }, - { - id: 'split-bottom', - name: 'Split to the bottom', - }, - { - id: 'split-left', - name: 'Split to the left', - }, - { - id: 'split-top', - name: 'Split to the top', - }, - { - id: 'split-nav-up', - name: 'Focus the pane above', - }, - { - id: 'split-nav-down', - name: 'Focus the pane below', - }, - { - id: 'split-nav-left', - name: 'Focus the pane on the left', - }, - { - id: 'split-nav-right', - name: 'Focus the pane on the right', - }, - ] - - async provide (): Promise { - return this.hotkeys - } -} diff --git a/terminus-core/src/services/hotkeys.util.ts b/terminus-core/src/services/hotkeys.util.ts index 01759c3f..b2769b65 100644 --- a/terminus-core/src/services/hotkeys.util.ts +++ b/terminus-core/src/services/hotkeys.util.ts @@ -11,13 +11,13 @@ export const altKeyName = { }[process.platform] export interface NativeKeyEvent { - event?: string, - altKey: boolean, - ctrlKey: boolean, - metaKey: boolean, - shiftKey: boolean, - key: string, - keyCode: string, + event?: string + altKey: boolean + ctrlKey: boolean + metaKey: boolean + shiftKey: boolean + key: string + keyCode: string } export function stringifyKeySequence (events: NativeKeyEvent[]): string[] { diff --git a/terminus-core/src/services/log.service.ts b/terminus-core/src/services/log.service.ts index 866a2559..5eb19339 100644 --- a/terminus-core/src/services/log.service.ts +++ b/terminus-core/src/services/log.service.ts @@ -39,7 +39,7 @@ export class Logger { private name: string, ) {} - doLog (level: string, ...args: any[]) { + private doLog (level: string, ...args: any[]) { console[level](`%c[${this.name}]`, 'color: #aaa', ...args) if (this.winstonLogger) { this.winstonLogger[level](...args) @@ -57,6 +57,7 @@ export class Logger { export class LogService { private log: any + /** @hidden */ constructor (electron: ElectronService) { this.log = initializeWinston(electron) } diff --git a/terminus-core/src/services/shellIntegration.service.ts b/terminus-core/src/services/shellIntegration.service.ts index cf6e55d2..e48472c3 100644 --- a/terminus-core/src/services/shellIntegration.service.ts +++ b/terminus-core/src/services/shellIntegration.service.ts @@ -37,7 +37,7 @@ export class ShellIntegrationService { this.updatePaths() } - async updatePaths (): Promise { + private async updatePaths (): Promise { // Update paths in case of an update if (this.hostApp.platform === Platform.Windows) { if (await this.isInstalled()) { diff --git a/terminus-core/src/services/tabRecovery.service.ts b/terminus-core/src/services/tabRecovery.service.ts index 371b961a..9c725dd6 100644 --- a/terminus-core/src/services/tabRecovery.service.ts +++ b/terminus-core/src/services/tabRecovery.service.ts @@ -4,6 +4,7 @@ import { BaseTabComponent } from '../components/baseTab.component' import { Logger, LogService } from '../services/log.service' import { ConfigService } from '../services/config.service' +/** @hidden */ @Injectable({ providedIn: 'root' }) export class TabRecoveryService { logger: Logger diff --git a/terminus-core/src/services/tabs.service.ts b/terminus-core/src/services/tabs.service.ts index c2345744..f35396fc 100644 --- a/terminus-core/src/services/tabs.service.ts +++ b/terminus-core/src/services/tabs.service.ts @@ -6,12 +6,16 @@ export declare type TabComponentType = new (...args: any[]) => BaseTabComponent @Injectable({ providedIn: 'root' }) export class TabsService { + /** @hidden */ constructor ( private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, private tabRecovery: TabRecoveryService, ) { } + /** + * Instantiates a tab component and assigns given inputs + */ create (type: TabComponentType, inputs?: any): BaseTabComponent { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type) let componentRef = componentFactory.create(this.injector) @@ -21,6 +25,9 @@ export class TabsService { return tab } + /** + * Duplicates an existing tab instance (using the tab recovery system) + */ async duplicate (tab: BaseTabComponent): Promise { let token = await tab.getRecoveryToken() if (!token) { @@ -32,5 +39,4 @@ export class TabsService { } return null } - } diff --git a/terminus-core/src/services/themes.service.ts b/terminus-core/src/services/themes.service.ts index 6a2958a0..24edea10 100644 --- a/terminus-core/src/services/themes.service.ts +++ b/terminus-core/src/services/themes.service.ts @@ -6,6 +6,7 @@ import { Theme } from '../api/theme' export class ThemesService { private styleElement: HTMLElement = null + /** @hidden */ constructor ( private config: ConfigService, @Inject(Theme) private themes: Theme[], @@ -34,7 +35,7 @@ export class ThemesService { document.querySelector('style#custom-css').innerHTML = this.config.store.appearance.css } - applyCurrentTheme (): void { + private applyCurrentTheme (): void { this.applyTheme(this.findCurrentTheme()) } } diff --git a/terminus-core/src/services/touchbar.service.ts b/terminus-core/src/services/touchbar.service.ts index 3545a095..83e80c69 100644 --- a/terminus-core/src/services/touchbar.service.ts +++ b/terminus-core/src/services/touchbar.service.ts @@ -6,6 +6,7 @@ import { ElectronService } from './electron.service' import { HostAppService, Platform } from './hostApp.service' import { IToolbarButton, ToolbarButtonProvider } from '../api' +/** @hidden */ @Injectable({ providedIn: 'root' }) export class TouchbarService { private tabsSegmentedControl: TouchBarSegmentedControl diff --git a/terminus-core/src/services/updater.service.ts b/terminus-core/src/services/updater.service.ts index c70b97cd..97db3db3 100644 --- a/terminus-core/src/services/updater.service.ts +++ b/terminus-core/src/services/updater.service.ts @@ -6,6 +6,7 @@ import { ElectronService } from './electron.service' const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest' +/** @hidden */ @Injectable({ providedIn: 'root' }) export class UpdaterService { private logger: Logger diff --git a/terminus-core/src/tabContextMenu.ts b/terminus-core/src/tabContextMenu.ts index d7de81c1..cf2b1b68 100644 --- a/terminus-core/src/tabContextMenu.ts +++ b/terminus-core/src/tabContextMenu.ts @@ -4,6 +4,7 @@ import { BaseTabComponent } from './components/baseTab.component' import { TabHeaderComponent } from './components/tabHeader.component' import { TabContextMenuItemProvider } from './api/tabContextMenuProvider' +/** @hidden */ @Injectable() export class CloseContextMenu extends TabContextMenuItemProvider { weight = -5 @@ -61,6 +62,7 @@ const COLORS = [ { name: 'Yellow', value: '#ffd500' }, ] +/** @hidden */ @Injectable() export class CommonOptionsContextMenu extends TabContextMenuItemProvider { weight = -1 @@ -98,6 +100,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider { } } +/** @hidden */ @Injectable() export class TaskCompletionContextMenu extends TabContextMenuItemProvider { constructor ( @@ -121,7 +124,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider { type: 'checkbox', checked: (tab as any).__completionNotificationEnabled, click: () => this.zone.run(() => { - ;(tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled + (tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled if ((tab as any).__completionNotificationEnabled) { this.app.observeTabCompletion(tab).subscribe(() => { diff --git a/terminus-core/src/theme.ts b/terminus-core/src/theme.ts index 52145010..eeba75a4 100644 --- a/terminus-core/src/theme.ts +++ b/terminus-core/src/theme.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core' import { Theme } from './api' +/** @hidden */ @Injectable() export class StandardTheme extends Theme { name = 'Standard' @@ -8,6 +9,7 @@ export class StandardTheme extends Theme { terminalBackground = '#222a33' } +/** @hidden */ @Injectable() export class StandardCompactTheme extends Theme { name = 'Compact' @@ -15,6 +17,7 @@ export class StandardCompactTheme extends Theme { terminalBackground = '#222a33' } +/** @hidden */ @Injectable() export class PaperTheme extends Theme { name = 'Paper' diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts index e8fd15cc..07055b0b 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts @@ -3,7 +3,7 @@ import { debounceTime, distinctUntilChanged, first, tap, flatMap } from 'rxjs/op import * as semver from 'semver' import { Component, Input } from '@angular/core' -import { ConfigService, HostAppService, ElectronService } from 'terminus-core' +import { ConfigService, ElectronService } from 'terminus-core' import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service' enum BusyState { Installing, Uninstalling } @@ -25,7 +25,6 @@ export class PluginsSettingsTabComponent { constructor ( private electron: ElectronService, private config: ConfigService, - private hostApp: HostAppService, public pluginManager: PluginManagerService ) { } @@ -51,7 +50,7 @@ export class PluginsSettingsTabComponent { } openPluginsFolder (): void { - this.hostApp.getShell().openItem(this.pluginManager.userPluginsPath) + this.electron.shell.openItem(this.pluginManager.userPluginsPath) } searchAvailable (query: string) { diff --git a/typedoc.js b/typedoc.js new file mode 100644 index 00000000..8cf31ec9 --- /dev/null +++ b/typedoc.js @@ -0,0 +1,7 @@ +module.exports = { + ignoreCompilerErrors: true, + excludeNotExported: true, + excludePrivate: true, + excludeExternals: true, + mode: 'file' +} diff --git a/yarn.lock b/yarn.lock index bb39c3ca..ed0a949a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,18 @@ resolved "https://registry.yarnpkg.com/@types/electron-debug/-/electron-debug-1.1.0.tgz#b9203bad33dccc5a4ea180a89a9dbcf1961f4c3c" integrity sha1-uSA7rTPczFpOoYComp288ZYfTDw= +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/fs-extra@^5.0.3": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" + integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== + dependencies: + "@types/node" "*" + "@types/fs-promise@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/fs-promise/-/fs-promise-1.0.1.tgz#a77e18c055d7757d44a34c1ed7e8bb505992f783" @@ -47,11 +59,47 @@ "@types/mz" "*" "@types/node" "*" +"@types/glob@*": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/handlebars@^4.0.38": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850" + integrity sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA== + dependencies: + handlebars "*" + +"@types/highlight.js@^9.12.3": + version "9.12.3" + resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca" + integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ== + "@types/js-yaml@^3.11.2": version "3.11.2" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.2.tgz#699ad86054cc20043c30d66a6fcde30bbf5d3d5e" integrity sha512-JRDtMPEqXrzfuYAdqbxLot1GvAr/QvicIZAnOAigZaj8xVMhuSJTg/xsv9E1TvyL+wujYhRLx9ZsQ0oFOSmwyA== +"@types/lodash@^4.14.110": + version "4.14.122" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.122.tgz#3e31394c38cf1e5949fb54c1192cbc406f152c6c" + integrity sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew== + +"@types/marked@^0.4.0": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f" + integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg== + +"@types/minimatch@*", "@types/minimatch@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + "@types/mz@*": version "0.0.32" resolved "https://registry.yarnpkg.com/@types/mz/-/mz-0.0.32.tgz#e8248b4e41424c052edc1725dd33650c313a3659" @@ -69,6 +117,14 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.26.tgz#2dec19f1f7981c95cb54bab8f618ecb5dc983d0e" integrity sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg== +"@types/shelljs@^0.8.0": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.3.tgz#f713f312dbae49ab5025290007e71ea32998e9a9" + integrity sha512-miY41hqc5SkRlsZDod3heDa4OS9xv8G77EMBQuSpqq86HBn66l7F+f8y9YKm+1PIuwC8QEZVwN8YxOOG7Y67fA== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/webpack-env@1.13.0": version "1.13.0" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.0.tgz#3044381647e11ee973c5af2e925323930f691d80" @@ -649,6 +705,13 @@ async@^2.0.0: dependencies: lodash "^4.17.10" +async@^2.5.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" + integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== + dependencies: + lodash "^4.17.11" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -3051,6 +3114,17 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +handlebars@*, handlebars@^4.0.6: + version "4.1.0" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a" + integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w== + dependencies: + async "^2.5.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -3181,6 +3255,11 @@ he@1.1.x: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +highlight.js@^9.13.1: + version "9.15.6" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4" + integrity sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ== + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4135,7 +4214,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -4242,6 +4321,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" + integrity sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw== + math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" @@ -4388,6 +4472,11 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + minipass@^2.2.1, minipass@^2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" @@ -5066,6 +5155,14 @@ opener@~1.4.3: resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg= +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + ora@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-1.4.0.tgz#884458215b3a5d4097592285f93321bb7a79e2e5" @@ -5709,6 +5806,11 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1, promise-inflight@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6671,6 +6773,15 @@ shelljs@0.7.7: interpret "^1.0.0" rechoir "^0.6.2" +shelljs@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" + integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -7472,6 +7583,39 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typedoc-default-themes@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" + integrity sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic= + +typedoc@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.14.2.tgz#769f457f4f9e4bdb8b5f3b177c86b6a31d8c3dc3" + integrity sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ== + dependencies: + "@types/fs-extra" "^5.0.3" + "@types/handlebars" "^4.0.38" + "@types/highlight.js" "^9.12.3" + "@types/lodash" "^4.14.110" + "@types/marked" "^0.4.0" + "@types/minimatch" "3.0.3" + "@types/shelljs" "^0.8.0" + fs-extra "^7.0.0" + handlebars "^4.0.6" + highlight.js "^9.13.1" + lodash "^4.17.10" + marked "^0.4.0" + minimatch "^3.0.0" + progress "^2.0.0" + shelljs "^0.8.2" + typedoc-default-themes "^0.5.0" + typescript "3.2.x" + +typescript@3.2.x: + version "3.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d" + integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg== + typescript@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.3.tgz#01b70247a6d3c2467f70c45795ef5ea18ce191d5" @@ -7495,6 +7639,14 @@ uglify-js@^2.6.1: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@^3.1.4: + version "3.4.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" + integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -7881,6 +8033,11 @@ wordwrap@0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + worker-farm@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"