prepared terminus-core for typedocs

This commit is contained in:
Eugene Pankov
2019-03-07 01:51:15 +01:00
parent cf5e31be79
commit 2ed35cb400
47 changed files with 766 additions and 219 deletions

View File

@@ -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<BaseTabComponent>()
private tabsChanged = new Subject<void>()
@@ -55,19 +56,17 @@ export class AppService {
get tabOpened$ (): Observable<BaseTabComponent> { return this.tabOpened }
get tabsChanged$ (): Observable<void> { return this.tabsChanged }
get tabClosed$ (): Observable<BaseTabComponent> { return this.tabClosed }
/** Fires once when the app is ready */
get ready$ (): Observable<void> { 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<void> {
if (!this.completionObservers.has(tab)) {
let observer = new CompletionObserver(tab)

View File

@@ -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<void>()
private _store: any
private defaults: any
@@ -86,6 +99,7 @@ export class ConfigService {
get changed$ (): Observable<void> { 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<T> (services: T[]): T[] {
if (!this.servicesCache) {
this.servicesCache = {}

View File

@@ -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

View File

@@ -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:')

View File

@@ -9,6 +9,7 @@ import uuidv4 = require('uuid/v4')
export class HomeBaseService {
appVersion: string
/** @hidden */
constructor (
private electron: ElectronService,
private config: ConfigService,

View File

@@ -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<any>()
isFullScreen = false
private preferencesMenu = new Subject<void>()
private secondInstance = new Subject<void>()
private cliOpenDirectory = new Subject<string>()
@@ -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<void> { return this.preferencesMenu }
/**
* Fired when a second instance of Terminus is launched
*/
get secondInstance$ (): Observable<void> { return this.secondInstance }
/**
* Fired for the `terminus open` CLI command
*/
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
/**
* Fired for the `terminus run` CLI command
*/
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
/**
* Fired for the `terminus paste` CLI command
*/
get cliPaste$ (): Observable<string> { return this.cliPaste }
/**
* Fired for the `terminus profile` CLI command
*/
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
/**
* Fired when another window modified the config file
*/
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
/**
* Fired when the window close button is pressed
*/
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
get windowMoved$ (): Observable<void> { return this.windowMoved }
get displayMetricsChanged$ (): Observable<void> { 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')
}

View File

@@ -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<IHotkeyDescription[]> {
return this.hotkeys
}
}

View File

@@ -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[] {

View File

@@ -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)
}

View File

@@ -37,7 +37,7 @@ export class ShellIntegrationService {
this.updatePaths()
}
async updatePaths (): Promise<void> {
private async updatePaths (): Promise<void> {
// Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) {

View File

@@ -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

View File

@@ -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<BaseTabComponent> {
let token = await tab.getRecoveryToken()
if (!token) {
@@ -32,5 +39,4 @@ export class TabsService {
}
return null
}
}

View File

@@ -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())
}
}

View File

@@ -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

View File

@@ -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