Merge branch 'master' of github.com:Eugeny/terminus

This commit is contained in:
Eugene Pankov
2017-04-30 13:42:32 +02:00
16 changed files with 123 additions and 75 deletions

View File

@@ -29,6 +29,6 @@ html
div div
h1.terminus-title Terminus h1.terminus-title Terminus
.progress .progress
.bar(style='width: 50%') .bar(style='width: 0%')

View File

@@ -21,7 +21,7 @@ if ((<any>global).require('electron-is-dev')) {
} }
findPlugins().then(async plugins => { findPlugins().then(async plugins => {
let pluginsModules = loadPlugins(plugins, (current, total) => { let pluginsModules = await loadPlugins(plugins, (current, total) => {
(<HTMLElement>document.querySelector('.progress .bar')).style.width = 100 * current / total + '%' (<HTMLElement>document.querySelector('.progress .bar')).style.width = 100 * current / total + '%'
}) })
let module = await getRootModule(pluginsModules) let module = await getRootModule(pluginsModules)

View File

@@ -62,10 +62,11 @@ export async function findPlugins (): Promise<IPluginEntry[]> {
return foundPlugins return foundPlugins
} }
export function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCallback): any[] { export async function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCallback): Promise<any[]> {
let plugins: any[] = [] let plugins: any[] = []
progress(0, 1) progress(0, 1)
foundPlugins.forEach((foundPlugin, index) => { let index = 0
for (let foundPlugin of foundPlugins) {
console.info(`Loading ${foundPlugin.name}: ${(<any>global).require.resolve(foundPlugin.path)}`) console.info(`Loading ${foundPlugin.name}: ${(<any>global).require.resolve(foundPlugin.path)}`)
progress(index, foundPlugins.length) progress(index, foundPlugins.length)
try { try {
@@ -74,7 +75,9 @@ export function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCal
} catch (error) { } catch (error) {
console.error(`Could not load ${foundPlugin.name}:`, error) console.error(`Could not load ${foundPlugin.name}:`, error)
} }
}) await delay(1)
index++
}
progress(1, 1) progress(1, 1)
return plugins return plugins
} }

View File

@@ -1,9 +1,14 @@
title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appearance.dock == "off"') title-bar(
*ngIf='config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
[class.inset]='hostApp.platform == Platform.macOS'
)
.content( .content(
[class.tabs-on-top]='config.store.appearance.tabsOnTop' [class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
)
.tab-bar(
[class.inset]='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"'
) )
.tab-bar
.tabs .tabs
tab-header( tab-header(
*ngFor='let tab of app.tabs; let idx = index', *ngFor='let tab of app.tabs; let idx = index',
@@ -11,12 +16,14 @@ title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appeara
[tab]='tab', [tab]='tab',
[active]='tab == app.activeTab', [active]='tab == app.activeTab',
[hasActivity]='tab.hasActivity', [hasActivity]='tab.hasActivity',
[class.drag-region]='hostApp.platform == Platform.macOS',
@animateTab, @animateTab,
(click)='app.selectTab(tab)', (click)='app.selectTab(tab)',
(closeClicked)='app.closeTab(tab)', (closeClicked)='app.closeTab(tab)',
) )
button.btn.btn-secondary( .btn-group
button.btn.btn-secondary.btn-tab-bar(
*ngFor='let button of getLeftToolbarButtons()', *ngFor='let button of getLeftToolbarButtons()',
[title]='button.title', [title]='button.title',
(click)='button.click()', (click)='button.click()',
@@ -25,25 +32,26 @@ title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appeara
.drag-space .drag-space
button.btn.btn-secondary( .btn-group
button.btn.btn-secondary.btn-tab-bar(
*ngFor='let button of getRightToolbarButtons()', *ngFor='let button of getRightToolbarButtons()',
[title]='button.title', [title]='button.title',
(click)='button.click()', (click)='button.click()',
) )
i.fa([class]='"fa fa-" + button.icon') i.fa([class]='"fa fa-" + button.icon')
button.btn.btn-secondary.btn-minimize( .btn-group.window-controls(
*ngIf='config.store.appearance.frame == "thin"', *ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
)
button.btn.btn-secondary.btn-minimize.btn-tab-bar(
(click)='hostApp.minimize()', (click)='hostApp.minimize()',
) )
i.fa.fa-window-minimize i.fa.fa-window-minimize
button.btn.btn-secondary.btn-maximize( button.btn.btn-secondary.btn-maximize.btn-tab-bar(
*ngIf='config.store.appearance.frame == "thin"',
(click)='hostApp.toggleMaximize()', (click)='hostApp.toggleMaximize()',
) )
i.fa.fa-window-maximize i.fa.fa-window-maximize
button.btn.btn-secondary.btn-close( button.btn.btn-secondary.btn-close.btn-tab-bar(
*ngIf='config.store.appearance.frame == "thin"',
(click)='hostApp.quit()', (click)='hostApp.quit()',
) )
i.fa.fa-close i.fa.fa-close

View File

@@ -5,6 +5,7 @@
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
-webkit-user-select: none; -webkit-user-select: none;
-webkit-user-drag: none;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
cursor: default; cursor: default;
animation: 0.5s ease-out fadeIn; animation: 0.5s ease-out fadeIn;
@@ -29,7 +30,7 @@ $tab-border-radius: 4px;
height: $tabs-height; height: $tabs-height;
display: flex; display: flex;
&>button { .btn-tab-bar {
line-height: $tabs-height + 2px; line-height: $tabs-height + 2px;
cursor: pointer; cursor: pointer;
@@ -69,6 +70,14 @@ $tab-border-radius: 4px;
flex: 1 0 25%; flex: 1 0 25%;
-webkit-app-region: drag; -webkit-app-region: drag;
} }
.window-controls {
flex: 0 0 none;
}
&.inset {
padding-left: 85px;
}
} }
.tabs-content { .tabs-content {

View File

@@ -3,7 +3,7 @@ import { trigger, style, animate, transition, state } from '@angular/animations'
import { ToasterConfig } from 'angular2-toaster' import { ToasterConfig } from 'angular2-toaster'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
import { HostAppService } from '../services/hostApp.service' import { HostAppService, Platform } from '../services/hostApp.service'
import { HotkeysService } from '../services/hotkeys.service' import { HotkeysService } from '../services/hotkeys.service'
import { Logger, LogService } from '../services/log.service' import { Logger, LogService } from '../services/log.service'
import { QuitterService } from '../services/quitter.service' import { QuitterService } from '../services/quitter.service'
@@ -43,6 +43,7 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
}) })
export class AppRootComponent { export class AppRootComponent {
toasterConfig: ToasterConfig toasterConfig: ToasterConfig
Platform = Platform
private logger: Logger private logger: Logger
constructor( constructor(

View File

@@ -61,4 +61,8 @@ $tabs-height: 40px;
display: block; display: block;
opacity: 1; opacity: 1;
} }
&.drag-region {
-webkit-app-region: drag;
}
} }

View File

@@ -31,7 +31,7 @@ $titlebar-height: 30px;
font-size: 12px; font-size: 12px;
} }
&.inset-titlebar { &.inset {
flex-basis: 36px; flex-basis: 36px;
.title { .title {

View File

@@ -1,5 +1,4 @@
import { Component, HostBinding } from '@angular/core' import { Component } from '@angular/core'
import { HostAppService, Platform } from '../services/hostApp.service'
@Component({ @Component({
selector: 'title-bar', selector: 'title-bar',
@@ -7,9 +6,4 @@ import { HostAppService, Platform } from '../services/hostApp.service'
styles: [require('./titleBar.component.scss')], styles: [require('./titleBar.component.scss')],
}) })
export class TitleBarComponent { export class TitleBarComponent {
@HostBinding('class.inset-titlebar') insetTitlebar = false
constructor (public hostApp: HostAppService) {
this.insetTitlebar = hostApp.platform == Platform.macOS
}
} }

View File

@@ -1,7 +1,7 @@
appearance: appearance:
dock: 'off' dock: off
dockScreen: 'current' dockScreen: current
dockFill: 50 dockFill: 50
tabsOnTop: true tabsLocation: top
theme: 'Standard' theme: Standard
frame: 'thin' frame: thin

View File

@@ -82,13 +82,14 @@ title-bar {
} }
} }
$border-color: #141414;
app-root { app-root {
&> .content { &> .content {
background: $body-bg2; background: $body-bg2;
.tab-bar { .tab-bar {
&>button { .btn-tab-bar {
&:not(:hover):not(:active) { &:not(:hover):not(:active) {
background: $body-bg2; background: $body-bg2;
} }
@@ -105,6 +106,8 @@ app-root {
tab-header { tab-header {
background: $body-bg2; background: $body-bg2;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
.index { .index {
color: #555; color: #555;
@@ -121,17 +124,22 @@ app-root {
&.active { &.active {
background: $body-bg; background: $body-bg;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
} }
} }
} }
} }
&.tabs-on-top .tab-bar { &.tabs-on-top .tab-bar {
border-bottom: 1px solid $border-color;
tab-header { tab-header {
border-top: 1px solid transparent; border-top: 1px solid transparent;
&.active { &.active {
border-top: 1px solid $teal; border-top: 1px solid $teal;
margin-bottom: -1px;
} }
&.has-activity:not(.active) { &.has-activity:not(.active) {
@@ -141,13 +149,14 @@ app-root {
} }
&:not(.tabs-on-top) .tab-bar { &:not(.tabs-on-top) .tab-bar {
margin-bottom: 3px; border-top: 1px solid $border-color;
tab-header { tab-header {
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
&.active { &.active {
border-bottom: 1px solid $teal; border-bottom: 1px solid $teal;
margin-top: -1px;
} }
&.has-activity:not(.active) { &.has-activity:not(.active) {

View File

@@ -19,20 +19,20 @@ ngb-tabset.vertical(type='tabs')
label Show tabs label Show tabs
br br
div( div(
'[(ngModel)]'='config.store.appearance.tabsOnTop', '[(ngModel)]'='config.store.appearance.tabsLocation',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
ngbRadioGroup ngbRadioGroup
) )
label.btn.btn-secondary label.btn.btn-secondary
input( input(
type='radio', type='radio',
[value]='true' [value]='"top"'
) )
| On the top | On the top
label.btn.btn-secondary label.btn.btn-secondary
input( input(
type='radio', type='radio',
[value]='false' [value]='"bottom"'
) )
| At the bottom | At the bottom
.col.col-lg-6 .col.col-lg-6

View File

@@ -1,3 +1,4 @@
import { Observable } from 'rxjs'
import { TerminalTabComponent } from './components/terminalTab.component' import { TerminalTabComponent } from './components/terminalTab.component'
export { TerminalTabComponent } export { TerminalTabComponent }
@@ -18,7 +19,7 @@ export interface SessionOptions {
cwd?: string cwd?: string
env?: any env?: any
recoveryId?: string recoveryId?: string
recoveredTruePID?: number recoveredTruePID$?: Observable<number>
} }
export abstract class SessionPersistenceProvider { export abstract class SessionPersistenceProvider {

View File

@@ -1,6 +1,8 @@
import * as fs from 'fs-promise' import * as fs from 'fs-promise'
import { exec, spawn } from 'mz/child_process' import { exec, spawn } from 'mz/child_process'
import { exec as execCallback } from 'child_process'
import { AsyncSubject } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Logger, LogService } from 'terminus-core' import { Logger, LogService } from 'terminus-core'
import { SessionOptions, SessionPersistenceProvider } from './api' import { SessionOptions, SessionPersistenceProvider } from './api'
@@ -36,12 +38,12 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
} }
async attachSession (recoveryId: any): Promise<SessionOptions> { async attachSession (recoveryId: any): Promise<SessionOptions> {
let lines: string[] let lines = await new Promise<string[]>(resolve => {
try { execCallback('screen -list', (_err, stdout) => {
lines = (await exec('screen -list'))[0].toString().split('\n') // returns an error code on macOS
} catch (result) { resolve(stdout.split('\n'))
lines = result.stdout.split('\n') })
} })
let screenPID = lines let screenPID = lines
.filter(line => line.indexOf('.' + recoveryId) !== -1) .filter(line => line.indexOf('.' + recoveryId) !== -1)
.map(line => parseInt(line.trim().split('.')[0]))[0] .map(line => parseInt(line.trim().split('.')[0]))[0]
@@ -50,25 +52,36 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
return null return null
} }
let child = (await listProcesses()).find(x => x.ppid === screenPID) let truePID_ = new AsyncSubject<number>()
if (!child) { this.extractShellPID(screenPID).then(pid => {
this.logger.error(`Could not find any children of the screen process (PID ${screenPID})!`) truePID_.next(pid)
} truePID_.complete()
if (child.command == 'login') { })
child = (await listProcesses()).find(x => x.ppid === child.pid)
}
let recoveredTruePID = child.pid
return { return {
recoveryId, recoveryId,
recoveredTruePID, recoveredTruePID$: truePID_.asObservable(),
command: 'screen', command: 'screen',
args: ['-r', recoveryId], args: ['-r', recoveryId],
} }
} }
async extractShellPID (screenPID: number): Promise<number> {
let child = (await listProcesses()).find(x => x.ppid === screenPID)
if (!child) {
throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
}
if (child.command == 'login') {
await delay(1000)
child = (await listProcesses()).find(x => x.ppid === child.pid)
}
return child.pid
}
async startSession (options: SessionOptions): Promise<any> { async startSession (options: SessionOptions): Promise<any> {
let configPath = '/tmp/.termScreenConfig' let configPath = '/tmp/.termScreenConfig'
await fs.writeFile(configPath, ` await fs.writeFile(configPath, `

View File

@@ -43,7 +43,13 @@ export class Session {
env: env, env: env,
}) })
this.truePID = options.recoveredTruePID || (<any>this.pty).pid if (options.recoveredTruePID$) {
options.recoveredTruePID$.subscribe(pid => {
this.truePID = pid
})
} else {
this.truePID = (<any>this.pty).pid
}
this.open = true this.open = true

View File

@@ -5,6 +5,6 @@ module.exports = [
require('./terminus-terminal/webpack.config.js'), require('./terminus-terminal/webpack.config.js'),
require('./terminus-clickable-links/webpack.config.js'), require('./terminus-clickable-links/webpack.config.js'),
require('./terminus-community-color-schemes/webpack.config.js'), require('./terminus-community-color-schemes/webpack.config.js'),
require('./terminus-plugin-manager/webpack.config.js'), //require('./terminus-plugin-manager/webpack.config.js'),
require('./terminus-theme-hype/webpack.config.js'), require('./terminus-theme-hype/webpack.config.js'),
] ]