mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-06 03:19:54 +00:00
split tab recovery (#49)
This commit is contained in:
parent
70b463b086
commit
7279ba13ac
@ -18,6 +18,7 @@ export abstract class BaseTabComponent {
|
||||
protected progress = new Subject<number>()
|
||||
protected activity = new Subject<boolean>()
|
||||
protected destroyed = new Subject<void>()
|
||||
protected recoveryStateChangedHint = new Subject<void>()
|
||||
|
||||
private progressClearTimeout: number
|
||||
|
||||
@ -27,6 +28,7 @@ export abstract class BaseTabComponent {
|
||||
get progress$ (): Observable<number> { return this.progress }
|
||||
get activity$ (): Observable<boolean> { return this.activity }
|
||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
||||
|
||||
constructor () {
|
||||
this.focused$.subscribe(() => {
|
||||
@ -91,6 +93,7 @@ export abstract class BaseTabComponent {
|
||||
this.blurred.complete()
|
||||
this.titleChange.complete()
|
||||
this.progress.complete()
|
||||
this.recoveryStateChangedHint.complete()
|
||||
this.destroyed.next()
|
||||
this.destroyed.complete()
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Subscription } from 'rxjs'
|
||||
import { Component, ViewChild, ViewContainerRef, EmbeddedViewRef } from '@angular/core'
|
||||
import { BaseTabComponent } from './baseTab.component'
|
||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef } from '@angular/core'
|
||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
||||
import { TabsService } from '../services/tabs.service'
|
||||
import { HotkeysService } from '../services/hotkeys.service'
|
||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||
|
||||
export declare type SplitOrientation = 'v' | 'h'
|
||||
export declare type SplitDirection = 'r' | 't' | 'b' | 'l'
|
||||
@ -57,6 +59,23 @@ export class SplitContainer {
|
||||
}
|
||||
this.ratios = this.ratios.map(x => x / s)
|
||||
}
|
||||
|
||||
async serialize () {
|
||||
let children = []
|
||||
for (let child of this.children) {
|
||||
if (child instanceof SplitContainer) {
|
||||
children.push(await child.serialize())
|
||||
} else {
|
||||
children.push(await child.getRecoveryToken())
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'app:split-tab',
|
||||
ratios: this.ratios,
|
||||
orientation: this.orientation,
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -72,10 +91,12 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
||||
hotkeysSubscription: Subscription
|
||||
focusedTab: BaseTabComponent
|
||||
recoveredState: any
|
||||
|
||||
constructor (
|
||||
protected hotkeys: HotkeysService,
|
||||
private hotkeys: HotkeysService,
|
||||
private tabsService: TabsService,
|
||||
private tabRecovery: TabRecoveryService,
|
||||
) {
|
||||
super()
|
||||
this.root = new SplitContainer()
|
||||
@ -120,6 +141,17 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
})
|
||||
}
|
||||
|
||||
async ngOnInit () {
|
||||
if (this.recoveredState) {
|
||||
await this.recoverContainer(this.root, this.recoveredState)
|
||||
this.layout()
|
||||
setImmediate(() => {
|
||||
this.allTabs().forEach(x => x.emitFocused())
|
||||
this.focusAnyIn(this.root)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.hotkeysSubscription.unsubscribe()
|
||||
}
|
||||
@ -181,15 +213,20 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
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))
|
||||
this.recoveryStateChangedHint.next()
|
||||
this.addTab(tab)
|
||||
|
||||
setImmediate(() => {
|
||||
this.layout()
|
||||
this.focus(tab)
|
||||
})
|
||||
}
|
||||
|
||||
addTab (tab: BaseTabComponent) {
|
||||
let ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any>
|
||||
this.viewRefs.set(tab, ref)
|
||||
|
||||
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
|
||||
|
||||
tab.titleChange$.subscribe(t => this.setTitle(t))
|
||||
tab.activity$.subscribe(a => a ? this.displayActivity() : this.clearActivity())
|
||||
@ -250,6 +287,14 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
return !(await Promise.all(this.allTabs().map(x => x.canClose()))).some(x => !x)
|
||||
}
|
||||
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
return this.root.serialize()
|
||||
}
|
||||
|
||||
async getCurrentProcess (): Promise<BaseTabProcess> {
|
||||
return (await Promise.all(this.allTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
|
||||
}
|
||||
|
||||
private layout () {
|
||||
this.root.normalize()
|
||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||
@ -280,4 +325,40 @@ export class SplitTabComponent extends BaseTabComponent {
|
||||
offset += sizes[i]
|
||||
})
|
||||
}
|
||||
|
||||
private async recoverContainer (root: SplitContainer, state: any) {
|
||||
let children: (SplitContainer | BaseTabComponent)[] = []
|
||||
root.orientation = state.orientation
|
||||
root.ratios = state.ratios
|
||||
root.children = children
|
||||
for (let childState of state.children) {
|
||||
if (childState.type === 'app:split-tab') {
|
||||
let child = new SplitContainer()
|
||||
await this.recoverContainer(child, childState)
|
||||
children.push(child)
|
||||
} else {
|
||||
let recovered = await this.tabRecovery.recoverTab(childState)
|
||||
if (recovered) {
|
||||
let tab = this.tabsService.create(recovered.type, recovered.options)
|
||||
children.push(tab)
|
||||
this.addTab(tab)
|
||||
} else {
|
||||
state.ratios.splice(state.children.indexOf(childState), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
||||
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
||||
return {
|
||||
type: SplitTabComponent,
|
||||
options: { recoveredState: recoveryToken },
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import { TitleBarComponent } from './components/titleBar.component'
|
||||
import { ToggleComponent } from './components/toggle.component'
|
||||
import { WindowControlsComponent } from './components/windowControls.component'
|
||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||
import { SplitTabComponent } from './components/splitTab.component'
|
||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||
|
||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||
|
||||
@ -26,6 +26,7 @@ import { HotkeyProvider } from './api/hotkeyProvider'
|
||||
import { ConfigProvider } from './api/configProvider'
|
||||
import { Theme } from './api/theme'
|
||||
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||
import { TabRecoveryProvider } from './api/tabRecovery'
|
||||
|
||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||
import { CoreConfigProvider } from './config'
|
||||
@ -43,6 +44,7 @@ const PROVIDERS = [
|
||||
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
|
||||
]
|
||||
|
||||
|
@ -68,11 +68,9 @@ export class AppService {
|
||||
|
||||
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
|
||||
|
||||
/*this.tabRecovery.recoverTabs().then(tabs => {
|
||||
for (let
|
||||
this.openNewTab(tab.type, tab.options)
|
||||
tab of tabs) {
|
||||
this.openNewTab(tab.type, tab.options)
|
||||
this.tabRecovery.recoverTabs().then(tabs => {
|
||||
for (let tab of tabs) {
|
||||
this.openNewTabRaw(tab.type, tab.options)
|
||||
}
|
||||
|
||||
this.tabsChanged$.subscribe(() => {
|
||||
@ -81,7 +79,7 @@ export class AppService {
|
||||
setInterval(() => {
|
||||
tabRecovery.saveTabs(this.tabs)
|
||||
}, 30000)
|
||||
})*/
|
||||
})
|
||||
}
|
||||
|
||||
addTabRaw (tab: BaseTabComponent) {
|
||||
@ -90,6 +88,10 @@ export class AppService {
|
||||
this.tabsChanged.next()
|
||||
this.tabOpened.next(tab)
|
||||
|
||||
tab.recoveryStateChangedHint$.subscribe(() => {
|
||||
this.tabRecovery.saveTabs(this.tabs)
|
||||
})
|
||||
|
||||
tab.titleChange$.subscribe(title => {
|
||||
if (tab === this.activeTab) {
|
||||
this.hostApp.setTitle(title)
|
||||
|
@ -10,9 +10,7 @@ export class TabsService {
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private injector: Injector,
|
||||
private tabRecovery: TabRecoveryService,
|
||||
) {
|
||||
|
||||
}
|
||||
) { }
|
||||
|
||||
create (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||
|
Loading…
x
Reference in New Issue
Block a user