mirror of
https://github.com/Eugeny/tabby.git
synced 2024-09-11 13:13:59 +03:00
.
This commit is contained in:
parent
b7bac490d2
commit
c9ead5e93d
@ -4,6 +4,7 @@ appearance:
|
||||
dock: 'off'
|
||||
dockScreen: 'current'
|
||||
dockFill: 50
|
||||
tabsOnTop: true
|
||||
hotkeys:
|
||||
new-tab:
|
||||
- ['Ctrl-A', 'C']
|
||||
|
@ -8,5 +8,5 @@ html
|
||||
window.nodeRequire = require
|
||||
script(src='./preload.js')
|
||||
script(src='./bundle.js', defer)
|
||||
body(style='background: ; min-height: 100vh')
|
||||
body(style='background: ; min-height: 100vh; overflow: hidden')
|
||||
app-root
|
||||
|
@ -8,6 +8,7 @@
|
||||
"electron-config": "0.2.1",
|
||||
"electron-debug": "1.0.1",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"fs-promise": "^2.0.1",
|
||||
"node-pty": "0.6.3",
|
||||
"path": "0.12.7"
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ export class Tab {
|
||||
this.id = Tab.lastTabID++
|
||||
}
|
||||
|
||||
displayActivity () {
|
||||
displayActivity (): void {
|
||||
this.hasActivity = true
|
||||
}
|
||||
|
||||
@ -27,4 +27,7 @@ export class Tab {
|
||||
getRecoveryToken (): any {
|
||||
return null
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Tab } from './tab'
|
||||
|
||||
export abstract class TabRecoveryProvider {
|
||||
abstract recover (recoveryToken: any): Tab
|
||||
abstract async recover (recoveryToken: any): Promise<Tab>
|
||||
}
|
||||
|
@ -33,9 +33,15 @@
|
||||
@tab-border-radius: 4px;
|
||||
|
||||
|
||||
:host > .spacer {
|
||||
flex: 0 0 5px;
|
||||
.content {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
background: @title-bg;
|
||||
|
||||
&.tabs-on-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
|
@ -1,41 +1,44 @@
|
||||
title-bar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
|
||||
title-bar(*ngIf='!config.full().appearance.useNativeFrame && config.store.appearance.dock == "off"')
|
||||
|
||||
.spacer
|
||||
.content(
|
||||
[class.tabs-on-top]='config.full().appearance.tabsOnTop'
|
||||
)
|
||||
.tabs(
|
||||
[class.active-tab-0]='app.tabs[0] == app.activeTab',
|
||||
)
|
||||
button.btn.btn-secondary(
|
||||
*ngFor='let button of getToolbarButtons(false)',
|
||||
[title]='button.title',
|
||||
(click)='button.click()',
|
||||
)
|
||||
i.fa([class]='"fa fa-" + button.icon')
|
||||
tab-header(
|
||||
*ngFor='let tab of app.tabs; let idx = index; trackBy: tab?.id',
|
||||
[index]='idx',
|
||||
[model]='tab',
|
||||
[active]='tab == app.activeTab',
|
||||
[hasActivity]='tab.hasActivity',
|
||||
@animateTab,
|
||||
(click)='app.selectTab(tab)',
|
||||
(closeClicked)='app.closeTab(tab)',
|
||||
)
|
||||
button.btn.btn-secondary(
|
||||
*ngFor='let button of getToolbarButtons(true)',
|
||||
[title]='button.title',
|
||||
(click)='button.click()',
|
||||
)
|
||||
i.fa([class]='"fa fa-" + button.icon')
|
||||
|
||||
.tabs(class='active-tab-{{app.tabs.indexOf(app.activeTab)}}')
|
||||
button.btn.btn-secondary(
|
||||
*ngFor='let button of getToolbarButtons(false)',
|
||||
[title]='button.title',
|
||||
(click)='button.click()',
|
||||
)
|
||||
i.fa([class]='"fa fa-" + button.icon')
|
||||
tab-header(
|
||||
*ngFor='let tab of app.tabs; let idx = index; trackBy: tab?.id',
|
||||
[index]='idx',
|
||||
[model]='tab',
|
||||
[active]='tab == app.activeTab',
|
||||
[hasActivity]='tab.hasActivity',
|
||||
@animateTab,
|
||||
(click)='app.selectTab(tab)',
|
||||
(closeClicked)='app.closeTab(tab)',
|
||||
)
|
||||
button.btn.btn-secondary(
|
||||
*ngFor='let button of getToolbarButtons(true)',
|
||||
[title]='button.title',
|
||||
(click)='button.click()',
|
||||
)
|
||||
i.fa([class]='"fa fa-" + button.icon')
|
||||
.tabs-content
|
||||
tab-body(
|
||||
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
||||
[active]='tab == app.activeTab',
|
||||
[model]='tab',
|
||||
[class.scrollable]='tab.scrollable',
|
||||
)
|
||||
|
||||
.tabs-content
|
||||
tab-body(
|
||||
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
||||
[active]='tab == app.activeTab',
|
||||
[model]='tab',
|
||||
[class.scrollable]='tab.scrollable',
|
||||
)
|
||||
|
||||
// TODO
|
||||
//hotkey-hint
|
||||
// TODO
|
||||
//hotkey-hint
|
||||
|
||||
toaster-container([toasterconfig]="toasterconfig")
|
||||
template(ngbModalContainer)
|
||||
|
@ -80,20 +80,26 @@ export class AppService {
|
||||
)
|
||||
}
|
||||
|
||||
restoreTabs () {
|
||||
async restoreTabs (): Promise<void> {
|
||||
if (window.localStorage.tabsRecovery) {
|
||||
JSON.parse(window.localStorage.tabsRecovery).forEach((token) => {
|
||||
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||
let tab: Tab
|
||||
for (let provider of this.tabRecoveryProviders) {
|
||||
try {
|
||||
let tab = provider.recover(token)
|
||||
tab = await provider.recover(token)
|
||||
if (tab) {
|
||||
this.openTab(tab)
|
||||
return
|
||||
break
|
||||
}
|
||||
} catch (_) { }
|
||||
} catch (error) {
|
||||
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||
}
|
||||
}
|
||||
this.logger.warn('Cannot restore tab from the token:', token)
|
||||
})
|
||||
if (tab) {
|
||||
this.openTab(tab)
|
||||
} else {
|
||||
this.logger.warn('Cannot restore tab from the token:', token)
|
||||
}
|
||||
}
|
||||
this.saveTabs()
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ export interface IAppearanceData {
|
||||
dock: string
|
||||
dockScreen: string
|
||||
dockFill: number
|
||||
tabsOnTop: boolean
|
||||
}
|
||||
|
||||
export interface ITerminalData {
|
||||
|
@ -5,101 +5,124 @@ ngb-tabset(type='tabs')
|
||||
template(ngbTabTitle)
|
||||
| Application
|
||||
template(ngbTabContent)
|
||||
.form-group
|
||||
label Window frame
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.useNativeFrame'
|
||||
'(ngModelChange)'='config.save(); requestRestart()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='true'
|
||||
)
|
||||
| Native
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='false'
|
||||
)
|
||||
| Custom
|
||||
small.form-text.text-muted Whether a custom window or an OS native window should be used
|
||||
|
||||
.row
|
||||
.col.col-auto
|
||||
.col.col-sm-6
|
||||
.form-group
|
||||
label Dock the terminal
|
||||
label Show tabs
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.dock'
|
||||
'(ngModelChange)'='config.save(); docking.dock()'
|
||||
'[(ngModel)]'='config.store.appearance.tabsOnTop'
|
||||
'(ngModelChange)'='config.save()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"off"'
|
||||
[value]='true'
|
||||
)
|
||||
| Off
|
||||
| On top
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"top"'
|
||||
[value]='false'
|
||||
)
|
||||
| Top
|
||||
| At the bottom
|
||||
.col.col-sm-6
|
||||
.form-group
|
||||
label Window frame
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.useNativeFrame'
|
||||
'(ngModelChange)'='config.save(); requestRestart()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"left"'
|
||||
[value]='true'
|
||||
)
|
||||
| Left
|
||||
| Native
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"right"'
|
||||
[value]='false'
|
||||
)
|
||||
| Right
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"bottom"'
|
||||
)
|
||||
| Bottom
|
||||
| Custom
|
||||
small.form-text.text-muted Whether a custom window or an OS native window should be used
|
||||
|
||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
||||
label Display on
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.dockScreen'
|
||||
'(ngModelChange)'='config.save(); docking.dock()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"current"'
|
||||
.row
|
||||
.col.col-auto
|
||||
.form-group
|
||||
label Dock the terminal
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.dock'
|
||||
'(ngModelChange)'='config.save(); docking.dock()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
| Current
|
||||
label.btn.btn-secondary(*ngFor='let screen of docking.getScreens()')
|
||||
input(
|
||||
type='radio',
|
||||
[value]='screen.id'
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"off"'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"top"'
|
||||
)
|
||||
| Top
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"left"'
|
||||
)
|
||||
| Left
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"right"'
|
||||
)
|
||||
| Right
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"bottom"'
|
||||
)
|
||||
| Bottom
|
||||
|
||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
||||
label Display on
|
||||
br
|
||||
div(
|
||||
'[(ngModel)]'='config.store.appearance.dockScreen'
|
||||
'(ngModelChange)'='config.save(); docking.dock()'
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary
|
||||
input(
|
||||
type='radio',
|
||||
[value]='"current"'
|
||||
)
|
||||
| Current
|
||||
label.btn.btn-secondary(*ngFor='let screen of docking.getScreens()')
|
||||
input(
|
||||
type='radio',
|
||||
[value]='screen.id'
|
||||
)
|
||||
| {{screen.name}}
|
||||
.col.col-auto
|
||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
||||
label Docked terminal size
|
||||
br
|
||||
input(
|
||||
type='range',
|
||||
'[(ngModel)]'='config.store.appearance.dockFill',
|
||||
'(mouseup)'='config.save(); docking.dock()',
|
||||
min='0.05',
|
||||
max='1',
|
||||
step='0.01'
|
||||
)
|
||||
| {{screen.name}}
|
||||
.col.col-auto
|
||||
.form-group(*ngIf='config.store.appearance.dock != "off"')
|
||||
label Docked terminal size
|
||||
br
|
||||
input(
|
||||
type='range',
|
||||
'[(ngModel)]'='config.store.appearance.dockFill',
|
||||
'(mouseup)'='config.save(); docking.dock()',
|
||||
min='0.05',
|
||||
max='1',
|
||||
step='0.01'
|
||||
)
|
||||
|
||||
ngb-tab
|
||||
template(ngbTabTitle)
|
||||
|
@ -5,7 +5,7 @@ import { SettingsTab } from './tab'
|
||||
|
||||
@Injectable()
|
||||
export class RecoveryProvider extends TabRecoveryProvider {
|
||||
recover (recoveryToken: any): Tab {
|
||||
async recover (recoveryToken: any): Promise<Tab> {
|
||||
if (recoveryToken.type == 'app:settings') {
|
||||
return new SettingsTab()
|
||||
}
|
||||
|
@ -1,3 +1,18 @@
|
||||
export abstract class TerminalDecorator {
|
||||
abstract decorate (terminal): void
|
||||
}
|
||||
|
||||
export interface SessionOptions {
|
||||
name?: string,
|
||||
command?: string,
|
||||
args?: string[],
|
||||
cwd?: string,
|
||||
env?: any,
|
||||
recoveryId?: string
|
||||
}
|
||||
|
||||
export abstract class SessionPersistenceProvider {
|
||||
abstract async recoverSession (recoveryId: any): Promise<SessionOptions>
|
||||
abstract async createSession (options: SessionOptions): Promise<SessionOptions>
|
||||
abstract async terminateSession (recoveryId: string): Promise<void>
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
||||
return [{
|
||||
icon: 'plus',
|
||||
title: 'New terminal',
|
||||
click: () => {
|
||||
let session = this.sessions.createNewSession({ command: 'zsh' })
|
||||
click: async () => {
|
||||
let session = await this.sessions.createNewSession({ command: 'zsh' })
|
||||
this.app.openTab(new TerminalTab(session))
|
||||
}
|
||||
}]
|
||||
|
@ -6,8 +6,10 @@ import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
import { SessionsService } from './services/sessions'
|
||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { RecoveryProvider } from './recoveryProvider'
|
||||
import { SessionPersistenceProvider } from './api'
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -19,6 +21,7 @@ import { RecoveryProvider } from './recoveryProvider'
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||
SessionsService,
|
||||
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
|
||||
],
|
||||
entryComponents: [
|
||||
TerminalTabComponent,
|
||||
|
64
app/src/terminal/persistenceProviders.ts
Normal file
64
app/src/terminal/persistenceProviders.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import * as fs from 'fs-promise'
|
||||
const exec = require('child-process-promise').exec
|
||||
|
||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
||||
|
||||
|
||||
export class NullPersistenceProvider extends SessionPersistenceProvider {
|
||||
async recoverSession (_recoveryId: any): Promise<SessionOptions> {
|
||||
return null
|
||||
}
|
||||
|
||||
async createSession (_options: SessionOptions): Promise<SessionOptions> {
|
||||
return null
|
||||
}
|
||||
|
||||
async terminateSession (_recoveryId: string): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
list(): Promise<any[]> {
|
||||
return exec('screen -list').then((result) => {
|
||||
return result.stdout.split('\n')
|
||||
.filter((line) => /\bterm-tab-/.exec(line))
|
||||
.map((line) => line.trim().split('.')[0])
|
||||
}).catch(() => {
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
async recoverSession (recoveryId: any): Promise<SessionOptions> {
|
||||
// TODO check
|
||||
return {
|
||||
recoveryId,
|
||||
command: 'screen',
|
||||
args: ['-r', recoveryId],
|
||||
}
|
||||
}
|
||||
|
||||
async createSession (options: SessionOptions): Promise<SessionOptions> {
|
||||
let configPath = '/tmp/.termScreenConfig'
|
||||
await fs.writeFile(configPath, `
|
||||
escape ^^^
|
||||
vbell off
|
||||
term xterm-color
|
||||
bindkey "^[OH" beginning-of-line
|
||||
bindkey "^[OF" end-of-line
|
||||
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
||||
defhstatus "^Et"
|
||||
hardstatus off
|
||||
`, 'utf-8')
|
||||
let recoveryId = `term-tab-${Date.now()}`
|
||||
options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || [])
|
||||
options.command = 'screen'
|
||||
options.recoveryId = recoveryId
|
||||
return options
|
||||
}
|
||||
|
||||
async terminateSession (recoveryId: string): Promise<void> {
|
||||
await exec(`screen -S ${recoveryId} -X quit`)
|
||||
}
|
||||
}
|
@ -10,11 +10,12 @@ export class RecoveryProvider extends TabRecoveryProvider {
|
||||
super()
|
||||
}
|
||||
|
||||
recover (recoveryToken: any): Tab {
|
||||
async recover (recoveryToken: any): Promise<Tab> {
|
||||
if (recoveryToken.type == 'app:terminal') {
|
||||
const options = this.sessions.recoveryProvider.getRecoverySession(recoveryToken.recoveryId)
|
||||
let session = this.sessions.createSession(options)
|
||||
session.recoveryId = recoveryToken.recoveryId
|
||||
let session = await this.sessions.recover(recoveryToken.recoveryId)
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
return new TerminalTab(session)
|
||||
}
|
||||
return null
|
||||
|
@ -1,87 +1,9 @@
|
||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
const exec = require('child-process-promise').exec
|
||||
import * as nodePTY from 'node-pty'
|
||||
import * as fs from 'fs'
|
||||
|
||||
import { Injectable, EventEmitter } from '@angular/core'
|
||||
import { Logger, LogService } from 'services/log'
|
||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||
|
||||
export interface ISessionRecoveryProvider {
|
||||
list (): Promise<any[]>
|
||||
getRecoverySession (recoveryId: any): SessionOptions
|
||||
wrapNewSession (options: SessionOptions): SessionOptions
|
||||
terminateSession (recoveryId: string): Promise<any>
|
||||
}
|
||||
|
||||
export class NullSessionRecoveryProvider implements ISessionRecoveryProvider {
|
||||
async list (): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
getRecoverySession (_recoveryId: any): SessionOptions {
|
||||
return null
|
||||
}
|
||||
|
||||
wrapNewSession (options: SessionOptions): SessionOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
async terminateSession (_recoveryId: string): Promise<any> {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class ScreenSessionRecoveryProvider implements ISessionRecoveryProvider {
|
||||
list(): Promise<any[]> {
|
||||
return exec('screen -list').then((result) => {
|
||||
return result.stdout.split('\n')
|
||||
.filter((line) => /\bterm-tab-/.exec(line))
|
||||
.map((line) => line.trim().split('.')[0])
|
||||
}).catch(() => {
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
getRecoverySession (recoveryId: any): SessionOptions {
|
||||
return {
|
||||
command: 'screen',
|
||||
args: ['-r', recoveryId],
|
||||
}
|
||||
}
|
||||
|
||||
wrapNewSession (options: SessionOptions): SessionOptions {
|
||||
// TODO
|
||||
let configPath = '/tmp/.termScreenConfig'
|
||||
fs.writeFileSync(configPath, `
|
||||
escape ^^^
|
||||
vbell off
|
||||
term xterm-color
|
||||
bindkey "^[OH" beginning-of-line
|
||||
bindkey "^[OF" end-of-line
|
||||
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
||||
defhstatus "^Et"
|
||||
hardstatus off
|
||||
`, 'utf-8')
|
||||
let recoveryId = `term-tab-${Date.now()}`
|
||||
options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || [])
|
||||
options.command = 'screen'
|
||||
options.recoveryId = recoveryId
|
||||
return options
|
||||
}
|
||||
|
||||
async terminateSession (recoveryId: string): Promise<any> {
|
||||
return exec(`screen -S ${recoveryId} -X quit`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface SessionOptions {
|
||||
name?: string,
|
||||
command?: string,
|
||||
args?: string[],
|
||||
cwd?: string,
|
||||
env?: any,
|
||||
recoveryId?: string
|
||||
}
|
||||
|
||||
export class Session {
|
||||
open: boolean
|
||||
@ -96,6 +18,7 @@ export class Session {
|
||||
|
||||
constructor (options: SessionOptions) {
|
||||
this.name = options.name
|
||||
this.recoveryId = options.recoveryId
|
||||
console.log('Spawning', options.command)
|
||||
|
||||
let env = {
|
||||
@ -184,57 +107,44 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class SessionsService {
|
||||
sessions: {[id: string]: Session} = {}
|
||||
logger: Logger
|
||||
private lastID = 0
|
||||
recoveryProvider: ISessionRecoveryProvider
|
||||
|
||||
constructor(
|
||||
private zone: NgZone,
|
||||
private persistence: SessionPersistenceProvider,
|
||||
log: LogService,
|
||||
) {
|
||||
this.logger = log.create('sessions')
|
||||
this.recoveryProvider = new ScreenSessionRecoveryProvider()
|
||||
//this.recoveryProvider = new NullSessionRecoveryProvider()
|
||||
}
|
||||
|
||||
createNewSession (options: SessionOptions) : Session {
|
||||
options = this.recoveryProvider.wrapNewSession(options)
|
||||
let session = this.createSession(options)
|
||||
session.recoveryId = options.recoveryId
|
||||
async createNewSession (options: SessionOptions) : Promise<Session> {
|
||||
options = await this.persistence.createSession(options)
|
||||
let session = this.addSession(options)
|
||||
return session
|
||||
}
|
||||
|
||||
createSession (options: SessionOptions) : Session {
|
||||
addSession (options: SessionOptions) : Session {
|
||||
this.lastID++
|
||||
options.name = `session-${this.lastID}`
|
||||
let session = new Session(options)
|
||||
const destroySubscription = session.destroyed.subscribe(() => {
|
||||
delete this.sessions[session.name]
|
||||
this.persistence.terminateSession(session.recoveryId)
|
||||
destroySubscription.unsubscribe()
|
||||
})
|
||||
this.sessions[session.name] = session
|
||||
return session
|
||||
}
|
||||
|
||||
async destroySession (session: Session): Promise<any> {
|
||||
await session.gracefullyDestroy()
|
||||
await this.recoveryProvider.terminateSession(session.recoveryId)
|
||||
return null
|
||||
}
|
||||
|
||||
recoverAll () : Promise<Session[]> {
|
||||
return <Promise<Session[]>>(this.recoveryProvider.list().then((items) => {
|
||||
return this.zone.run(() => {
|
||||
return items.map((recoveryId) => {
|
||||
const options = this.recoveryProvider.getRecoverySession(recoveryId)
|
||||
let session = this.createSession(options)
|
||||
session.recoveryId = recoveryId
|
||||
return session
|
||||
})
|
||||
})
|
||||
}))
|
||||
async recover (recoveryId: string) : Promise<Session> {
|
||||
const options = await this.persistence.recoverSession(recoveryId)
|
||||
if (!options) {
|
||||
return null
|
||||
}
|
||||
return this.addSession(options)
|
||||
}
|
||||
}
|
||||
|
@ -20,4 +20,8 @@ export class TerminalTab extends Tab {
|
||||
recoveryId: this.session.recoveryId,
|
||||
}
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
this.session.gracefullyDestroy()
|
||||
}
|
||||
}
|
||||
|
@ -78,53 +78,107 @@ title-bar {
|
||||
}
|
||||
|
||||
|
||||
.tabs tab-header {
|
||||
background: $body-bg;
|
||||
.content-wrapper {
|
||||
background: $body-bg2;
|
||||
app-root .content {
|
||||
background: $body-bg2;
|
||||
|
||||
.index {
|
||||
color: #444;
|
||||
}
|
||||
.tabs {
|
||||
background: $body-bg;
|
||||
|
||||
button {
|
||||
color: $body-color;
|
||||
border: none;
|
||||
transition: 0.25s all;
|
||||
|
||||
&:hover { background: $button-hover-bg !important; }
|
||||
&:active { background: $button-active-bg !important; }
|
||||
}
|
||||
}
|
||||
|
||||
&.pre-selected, &:nth-last-child(1) {
|
||||
.content-wrapper {
|
||||
border-bottom-right-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&.post-selected {
|
||||
.content-wrapper {
|
||||
border-bottom-left-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $body-bg2;
|
||||
|
||||
.content-wrapper {
|
||||
border-top: 1px solid $blue;
|
||||
tab-header {
|
||||
background: $body-bg;
|
||||
border-top-left-radius: $tab-border-radius;
|
||||
border-top-right-radius: $tab-border-radius;
|
||||
|
||||
.content-wrapper {
|
||||
background: $body-bg2;
|
||||
|
||||
.index {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
button {
|
||||
color: $body-color;
|
||||
border: none;
|
||||
transition: 0.25s all;
|
||||
|
||||
&:hover { background: $button-hover-bg !important; }
|
||||
&:active { background: $button-active-bg !important; }
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $body-bg2;
|
||||
|
||||
.content-wrapper {
|
||||
background: $body-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-activity:not(.active) {
|
||||
.content-wrapper .index {
|
||||
background: $blue;
|
||||
color: white;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-activity:not(.active) {
|
||||
.content-wrapper .index {
|
||||
background: $blue;
|
||||
color: white;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
||||
&.tabs-on-top .tabs {
|
||||
margin-top: 3px;
|
||||
|
||||
tab-header {
|
||||
&.pre-selected, &:nth-last-child(1) {
|
||||
.content-wrapper {
|
||||
border-bottom-right-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&.post-selected {
|
||||
.content-wrapper {
|
||||
border-bottom-left-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
&.active .content-wrapper {
|
||||
border-top: 1px solid $blue;
|
||||
border-top-left-radius: $tab-border-radius;
|
||||
border-top-right-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.tabs-on-top) .tabs {
|
||||
margin-bottom: 3px;
|
||||
|
||||
tab-header {
|
||||
&.pre-selected, &:nth-last-child(1) {
|
||||
.content-wrapper {
|
||||
border-top-right-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&.post-selected {
|
||||
.content-wrapper {
|
||||
border-top-left-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
&.active .content-wrapper {
|
||||
border-bottom: 1px solid $blue;
|
||||
border-bottom-left-radius: $tab-border-radius;
|
||||
border-bottom-right-radius: $tab-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tab-body {
|
||||
background: $body-bg;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "term",
|
||||
"devDependencies": {
|
||||
"@types/fs-promise": "^1.0.1",
|
||||
"apply-loader": "^0.1.0",
|
||||
"autoprefixer": "^6.7.7",
|
||||
"awesome-typescript-loader": "3.0.8",
|
||||
|
Loading…
Reference in New Issue
Block a user