mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-25 01:26:08 +00:00
splits WIP (#49)
This commit is contained in:
@@ -16,3 +16,4 @@ export { HotkeysService } from '../services/hotkeys.service'
|
|||||||
export { HostAppService, Platform } from '../services/hostApp.service'
|
export { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
|
export { TabsService } from '../services/tabs.service'
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { TabComponentType } from '../services/app.service'
|
import { TabComponentType } from '../services/tabs.service'
|
||||||
|
|
||||||
export interface RecoveredTab {
|
export interface RecoveredTab {
|
||||||
type: TabComponentType,
|
type: TabComponentType,
|
||||||
|
@@ -6,8 +6,6 @@ export interface BaseTabProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent {
|
||||||
private static lastTabID = 0
|
|
||||||
id: number
|
|
||||||
title: string
|
title: string
|
||||||
customTitle: string
|
customTitle: string
|
||||||
hasFocus = false
|
hasFocus = false
|
||||||
@@ -31,7 +29,6 @@ export abstract class BaseTabComponent {
|
|||||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.id = BaseTabComponent.lastTabID++
|
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.hasFocus = true
|
this.hasFocus = true
|
||||||
})
|
})
|
||||||
|
283
terminus-core/src/components/splitTab.component.ts
Normal file
283
terminus-core/src/components/splitTab.component.ts
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { Component, ViewChild, ViewContainerRef, EmbeddedViewRef } from '@angular/core'
|
||||||
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
|
import { TabsService } from '../services/tabs.service'
|
||||||
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
|
|
||||||
|
export declare type SplitOrientation = 'v' | 'h'
|
||||||
|
export declare type SplitDirection = 'r' | 't' | 'b' | 'l'
|
||||||
|
|
||||||
|
export class SplitContainer {
|
||||||
|
orientation: SplitOrientation = 'h'
|
||||||
|
children: (BaseTabComponent | SplitContainer)[] = []
|
||||||
|
ratios: number[] = []
|
||||||
|
|
||||||
|
allTabs () {
|
||||||
|
let r = []
|
||||||
|
for (let child of this.children) {
|
||||||
|
if (child instanceof SplitContainer) {
|
||||||
|
r = r.concat(child.allTabs())
|
||||||
|
} else {
|
||||||
|
r.push(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize () {
|
||||||
|
for (let i = 0; i < this.children.length; i++) {
|
||||||
|
let child = this.children[i]
|
||||||
|
|
||||||
|
if (child instanceof SplitContainer) {
|
||||||
|
child.normalize()
|
||||||
|
|
||||||
|
if (child.children.length === 0) {
|
||||||
|
this.children.splice(i, 1)
|
||||||
|
this.ratios.splice(i, 1)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
} else if (child.children.length === 1) {
|
||||||
|
this.children[i] = child.children[0]
|
||||||
|
} else if (child.orientation === this.orientation) {
|
||||||
|
let ratio = this.ratios[i]
|
||||||
|
this.children.splice(i, 1)
|
||||||
|
this.ratios.splice(i, 1)
|
||||||
|
for (let j = 0; j < child.children.length; j++) {
|
||||||
|
this.children.splice(i, 0, child.children[j])
|
||||||
|
this.ratios.splice(i, 0, child.ratios[j] * ratio)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = 0
|
||||||
|
for (let x of this.ratios) {
|
||||||
|
s += x
|
||||||
|
}
|
||||||
|
this.ratios = this.ratios.map(x => x / s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'splitTab',
|
||||||
|
template: '<ng-container #vc></ng-container>',
|
||||||
|
styles: [
|
||||||
|
':host { position: relative; flex: auto; display: block; }'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SplitTabComponent extends BaseTabComponent {
|
||||||
|
root: SplitContainer
|
||||||
|
viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
|
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
||||||
|
hotkeysSubscription: Subscription
|
||||||
|
focusedTab: BaseTabComponent
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
protected hotkeys: HotkeysService,
|
||||||
|
private tabsService: TabsService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.root = new SplitContainer()
|
||||||
|
this.setTitle('')
|
||||||
|
|
||||||
|
this.focused$.subscribe(() => {
|
||||||
|
this.allTabs().forEach(x => x.emitFocused())
|
||||||
|
this.focus(this.focusedTab)
|
||||||
|
})
|
||||||
|
this.blurred$.subscribe(() => this.allTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
|
if (!this.hasFocus) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (hotkey) {
|
||||||
|
case 'split-right':
|
||||||
|
this.splitTab(this.focusedTab, 'r')
|
||||||
|
break
|
||||||
|
case 'split-bottom':
|
||||||
|
this.splitTab(this.focusedTab, 'b')
|
||||||
|
break
|
||||||
|
case 'split-top':
|
||||||
|
this.splitTab(this.focusedTab, 't')
|
||||||
|
break
|
||||||
|
case 'split-left':
|
||||||
|
this.splitTab(this.focusedTab, 'l')
|
||||||
|
break
|
||||||
|
case 'split-nav-left':
|
||||||
|
this.navigate('l')
|
||||||
|
break
|
||||||
|
case 'split-nav-right':
|
||||||
|
this.navigate('r')
|
||||||
|
break
|
||||||
|
case 'split-nav-up':
|
||||||
|
this.navigate('t')
|
||||||
|
break
|
||||||
|
case 'split-nav-down':
|
||||||
|
this.navigate('b')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy () {
|
||||||
|
this.hotkeysSubscription.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
allTabs () {
|
||||||
|
return [...this.root.allTabs()]
|
||||||
|
}
|
||||||
|
|
||||||
|
focus (tab: BaseTabComponent) {
|
||||||
|
this.focusedTab = tab
|
||||||
|
for (let x of this.allTabs()) {
|
||||||
|
if (x !== tab) {
|
||||||
|
x.emitBlurred()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tab) {
|
||||||
|
tab.emitFocused()
|
||||||
|
}
|
||||||
|
this.layout()
|
||||||
|
}
|
||||||
|
|
||||||
|
focusAnyIn (parent: BaseTabComponent | SplitContainer) {
|
||||||
|
if (!parent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (parent instanceof SplitContainer) {
|
||||||
|
this.focusAnyIn(parent.children[0])
|
||||||
|
} else {
|
||||||
|
this.focus(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert (tab: BaseTabComponent, relative: BaseTabComponent, dir: SplitDirection) {
|
||||||
|
let target = this.getParent(relative) || this.root
|
||||||
|
let insertIndex = target.children.indexOf(relative)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(target.orientation === 'v' && ['l', 'r'].includes(dir)) ||
|
||||||
|
(target.orientation === 'h' && ['t', 'b'].includes(dir))
|
||||||
|
) {
|
||||||
|
let newContainer = new SplitContainer()
|
||||||
|
newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v'
|
||||||
|
newContainer.children = [relative]
|
||||||
|
newContainer.ratios = [1]
|
||||||
|
target.children[insertIndex] = newContainer
|
||||||
|
target = newContainer
|
||||||
|
insertIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertIndex === -1) {
|
||||||
|
insertIndex = 0
|
||||||
|
} else {
|
||||||
|
insertIndex += (dir === 'l' || dir === 't') ? 0 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < target.children.length; i++) {
|
||||||
|
target.ratios[i] *= target.children.length / (target.children.length + 1)
|
||||||
|
}
|
||||||
|
target.ratios.splice(insertIndex, 0, 1 / (target.children.length + 1))
|
||||||
|
target.children.splice(insertIndex, 0, tab)
|
||||||
|
|
||||||
|
let ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any>
|
||||||
|
this.viewRefs.set(tab, ref)
|
||||||
|
|
||||||
|
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
this.layout()
|
||||||
|
this.focus(tab)
|
||||||
|
})
|
||||||
|
|
||||||
|
tab.titleChange$.subscribe(t => this.setTitle(t))
|
||||||
|
tab.activity$.subscribe(a => a ? this.displayActivity() : this.clearActivity())
|
||||||
|
tab.progress$.subscribe(p => this.setProgress(p))
|
||||||
|
if (tab.title) {
|
||||||
|
this.setTitle(tab.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate (dir: SplitDirection) {
|
||||||
|
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||||
|
let parent = this.getParent(rel)
|
||||||
|
let orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
||||||
|
|
||||||
|
while (parent !== this.root && parent.orientation !== orientation) {
|
||||||
|
rel = parent
|
||||||
|
parent = this.getParent(rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.orientation !== orientation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = parent.children.indexOf(rel)
|
||||||
|
if (['l', 't'].includes(dir)) {
|
||||||
|
if (index > 0) {
|
||||||
|
this.focusAnyIn(parent.children[index - 1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (index < parent.children.length - 1) {
|
||||||
|
this.focusAnyIn(parent.children[index + 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
||||||
|
let newTab = await this.tabsService.duplicate(tab)
|
||||||
|
this.insert(newTab, tab, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
getParent (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer {
|
||||||
|
root = root || this.root
|
||||||
|
for (let child of root.children) {
|
||||||
|
if (child instanceof SplitContainer) {
|
||||||
|
let r = this.getParent(tab, child)
|
||||||
|
if (r) {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (child === tab) {
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async canClose (): Promise<boolean> {
|
||||||
|
return !(await Promise.all(this.allTabs().map(x => x.canClose()))).some(x => !x)
|
||||||
|
}
|
||||||
|
|
||||||
|
private layout () {
|
||||||
|
this.root.normalize()
|
||||||
|
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
||||||
|
let size = (root.orientation === 'v') ? h : w
|
||||||
|
let sizes = root.ratios.map(x => x * size)
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
root.children.forEach((child, i) => {
|
||||||
|
let childX = (root.orientation === 'v') ? x : (x + offset)
|
||||||
|
let childY = (root.orientation === 'v') ? (y + offset) : y
|
||||||
|
let childW = (root.orientation === 'v') ? w : sizes[i]
|
||||||
|
let childH = (root.orientation === 'v') ? sizes[i] : h
|
||||||
|
if (child instanceof SplitContainer) {
|
||||||
|
this.layoutInternal(child, childX, childY, childW, childH)
|
||||||
|
} else {
|
||||||
|
let element = this.viewRefs.get(child).rootNodes[0]
|
||||||
|
element.style.position = 'absolute'
|
||||||
|
element.style.left = `${childX}%`
|
||||||
|
element.style.top = `${childY}%`
|
||||||
|
element.style.width = `${childW}%`
|
||||||
|
element.style.height = `${childH}%`
|
||||||
|
|
||||||
|
element.style.opacity = (child === this.focusedTab) ? 1 : 0.75
|
||||||
|
}
|
||||||
|
offset += sizes[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -36,4 +36,18 @@ hotkeys:
|
|||||||
- 'Alt-9'
|
- 'Alt-9'
|
||||||
tab-10:
|
tab-10:
|
||||||
- 'Alt-0'
|
- 'Alt-0'
|
||||||
|
split-right:
|
||||||
|
- 'Ctrl-Shift-E'
|
||||||
|
split-bottom:
|
||||||
|
- 'Ctrl-Shift-D'
|
||||||
|
split-left: []
|
||||||
|
split-top: []
|
||||||
|
split-nav-right:
|
||||||
|
- 'Ctrl-Alt-ArrowRight'
|
||||||
|
split-nav-down:
|
||||||
|
- 'Ctrl-Alt-ArrowDown'
|
||||||
|
split-nav-up:
|
||||||
|
- 'Ctrl-Alt-ArrowUp'
|
||||||
|
split-nav-left:
|
||||||
|
- 'Ctrl-Alt-ArrowLeft'
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -34,4 +34,18 @@ hotkeys:
|
|||||||
- '⌘-9'
|
- '⌘-9'
|
||||||
tab-10:
|
tab-10:
|
||||||
- '⌘-0'
|
- '⌘-0'
|
||||||
|
split-right:
|
||||||
|
- '⌘-Shift-D'
|
||||||
|
split-bottom:
|
||||||
|
- '⌘-D'
|
||||||
|
split-left: []
|
||||||
|
split-top: []
|
||||||
|
split-nav-right:
|
||||||
|
- '⌘-⌥-ArrowRight'
|
||||||
|
split-nav-down:
|
||||||
|
- '⌘-⌥-ArrowDown'
|
||||||
|
split-nav-up:
|
||||||
|
- '⌘-⌥-ArrowUp'
|
||||||
|
split-nav-left:
|
||||||
|
- '⌘-⌥-ArrowLeft'
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -36,4 +36,18 @@ hotkeys:
|
|||||||
- 'Alt-9'
|
- 'Alt-9'
|
||||||
tab-10:
|
tab-10:
|
||||||
- 'Alt-0'
|
- 'Alt-0'
|
||||||
|
split-right:
|
||||||
|
- 'Ctrl-Shift-E'
|
||||||
|
split-bottom:
|
||||||
|
- 'Ctrl-Shift-D'
|
||||||
|
split-left: []
|
||||||
|
split-top: []
|
||||||
|
split-nav-right:
|
||||||
|
- 'Ctrl-Alt-ArrowRight'
|
||||||
|
split-nav-down:
|
||||||
|
- 'Ctrl-Alt-ArrowDown'
|
||||||
|
split-nav-up:
|
||||||
|
- 'Ctrl-Alt-ArrowUp'
|
||||||
|
split-nav-left:
|
||||||
|
- 'Ctrl-Alt-ArrowLeft'
|
||||||
pluginBlacklist: []
|
pluginBlacklist: []
|
||||||
|
@@ -18,6 +18,7 @@ import { TitleBarComponent } from './components/titleBar.component'
|
|||||||
import { ToggleComponent } from './components/toggle.component'
|
import { ToggleComponent } from './components/toggle.component'
|
||||||
import { WindowControlsComponent } from './components/windowControls.component'
|
import { WindowControlsComponent } from './components/windowControls.component'
|
||||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||||
|
import { SplitTabComponent } from './components/splitTab.component'
|
||||||
|
|
||||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||||
|
|
||||||
@@ -66,10 +67,12 @@ const PROVIDERS = [
|
|||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
|
SplitTabComponent,
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
RenameTabModalComponent,
|
RenameTabModalComponent,
|
||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
|
SplitTabComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CheckboxComponent,
|
CheckboxComponent,
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||||
import { takeUntil } from 'rxjs/operators'
|
import { takeUntil } from 'rxjs/operators'
|
||||||
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
import { SplitTabComponent } from '../components/splitTab.component'
|
||||||
import { Logger, LogService } from './log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
import { TabRecoveryService } from './tabRecovery.service'
|
import { TabRecoveryService } from './tabRecovery.service'
|
||||||
|
import { TabsService, TabComponentType } from './tabs.service'
|
||||||
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
|
||||||
|
|
||||||
class CompletionObserver {
|
class CompletionObserver {
|
||||||
get done$ (): Observable<void> { return this.done }
|
get done$ (): Observable<void> { return this.done }
|
||||||
@@ -58,19 +58,20 @@ export class AppService {
|
|||||||
get ready$ (): Observable<void> { return this.ready }
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private injector: Injector,
|
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
|
private tabsService: TabsService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('app')
|
this.logger = log.create('app')
|
||||||
|
|
||||||
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
|
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
|
||||||
|
|
||||||
this.tabRecovery.recoverTabs().then(tabs => {
|
/*this.tabRecovery.recoverTabs().then(tabs => {
|
||||||
for (let tab of tabs) {
|
for (let
|
||||||
|
this.openNewTab(tab.type, tab.options)
|
||||||
|
tab of tabs) {
|
||||||
this.openNewTab(tab.type, tab.options)
|
this.openNewTab(tab.type, tab.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,16 +81,10 @@ export class AppService {
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
tabRecovery.saveTabs(this.tabs)
|
tabRecovery.saveTabs(this.tabs)
|
||||||
}, 30000)
|
}, 30000)
|
||||||
})
|
})*/
|
||||||
}
|
}
|
||||||
|
|
||||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
addTabRaw (tab: BaseTabComponent) {
|
||||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
|
||||||
let componentRef = componentFactory.create(this.injector)
|
|
||||||
let tab = componentRef.instance
|
|
||||||
tab.hostView = componentRef.hostView
|
|
||||||
Object.assign(tab, inputs || {})
|
|
||||||
|
|
||||||
this.tabs.push(tab)
|
this.tabs.push(tab)
|
||||||
this.selectTab(tab)
|
this.selectTab(tab)
|
||||||
this.tabsChanged.next()
|
this.tabsChanged.next()
|
||||||
@@ -100,6 +95,19 @@ export class AppService {
|
|||||||
this.hostApp.setTitle(title)
|
this.hostApp.setTitle(title)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||||
|
let tab = this.tabsService.create(type, inputs)
|
||||||
|
this.addTabRaw(tab)
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
|
||||||
|
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||||
|
let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
||||||
|
let tab = this.tabsService.create(type, inputs)
|
||||||
|
splitTab.insert(tab, null, 'r')
|
||||||
|
this.addTabRaw(splitTab)
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,13 +186,9 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async duplicateTab (tab: BaseTabComponent) {
|
async duplicateTab (tab: BaseTabComponent) {
|
||||||
let token = await tab.getRecoveryToken()
|
let dup = await this.tabsService.duplicate(tab)
|
||||||
if (!token) {
|
if (dup) {
|
||||||
return
|
this.addTabRaw(dup)
|
||||||
}
|
|
||||||
let recoveredTab = await this.tabRecovery.recoverTab(token)
|
|
||||||
if (recoveredTab) {
|
|
||||||
this.openNewTab(recoveredTab.type, recoveredTab.options)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -275,6 +275,38 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'tab-10',
|
id: 'tab-10',
|
||||||
name: '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[]> {
|
async provide (): Promise<IHotkeyDescription[]> {
|
||||||
|
38
terminus-core/src/services/tabs.service.ts
Normal file
38
terminus-core/src/services/tabs.service.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
|
||||||
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
import { TabRecoveryService } from './tabRecovery.service'
|
||||||
|
|
||||||
|
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class TabsService {
|
||||||
|
constructor (
|
||||||
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private injector: Injector,
|
||||||
|
private tabRecovery: TabRecoveryService,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
create (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||||
|
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||||
|
let componentRef = componentFactory.create(this.injector)
|
||||||
|
let tab = componentRef.instance
|
||||||
|
tab.hostView = componentRef.hostView
|
||||||
|
Object.assign(tab, inputs || {})
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
|
||||||
|
async duplicate (tab: BaseTabComponent): Promise<BaseTabComponent> {
|
||||||
|
let token = await tab.getRecoveryToken()
|
||||||
|
if (!token) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let dup = await this.tabRecovery.recoverTab(token)
|
||||||
|
if (dup) {
|
||||||
|
return this.create(dup.type, dup.options)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -46,11 +46,11 @@
|
|||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
"node-pty": "^0.8.0",
|
"node-pty": "^0.8.0",
|
||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
"runes": "^0.4.2",
|
"runes": "^0.4.2"
|
||||||
"windows-native-registry": "^1.0.6"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"macos-native-processlist": "^1.0.0",
|
"macos-native-processlist": "^1.0.0",
|
||||||
|
"windows-native-registry": "^1.0.6",
|
||||||
"windows-process-tree": "^0.2.3"
|
"windows-process-tree": "^0.2.3"
|
||||||
},
|
},
|
||||||
"false": {}
|
"false": {}
|
||||||
|
@@ -79,6 +79,9 @@ export class XTermFrontend extends Frontend {
|
|||||||
host.addEventListener('mousedown', event => this.mouseEvent.next(event as MouseEvent))
|
host.addEventListener('mousedown', event => this.mouseEvent.next(event as MouseEvent))
|
||||||
host.addEventListener('mouseup', event => this.mouseEvent.next(event as MouseEvent))
|
host.addEventListener('mouseup', event => this.mouseEvent.next(event as MouseEvent))
|
||||||
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
|
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
|
||||||
|
|
||||||
|
let ro = new window['ResizeObserver'](() => this.resizeHandler())
|
||||||
|
ro.observe(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
detach (host: HTMLElement): void {
|
detach (host: HTMLElement): void {
|
||||||
|
Reference in New Issue
Block a user