mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-12 07:29:59 +00:00
splits API
This commit is contained in:
parent
d25751abe7
commit
2c4c094941
@ -1,5 +1,5 @@
|
|||||||
import { Subscription } from 'rxjs'
|
import { Observable, Subject, Subscription } from 'rxjs'
|
||||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef } from '@angular/core'
|
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, OnInit, OnDestroy } from '@angular/core'
|
||||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
||||||
import { TabsService } from '../services/tabs.service'
|
import { TabsService } from '../services/tabs.service'
|
||||||
@ -18,11 +18,11 @@ export class SplitContainer {
|
|||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
|
|
||||||
allTabs () {
|
getAllTabs () {
|
||||||
let r = []
|
let r = []
|
||||||
for (let child of this.children) {
|
for (let child of this.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
r = r.concat(child.allTabs())
|
r = r.concat(child.getAllTabs())
|
||||||
} else {
|
} else {
|
||||||
r.push(child)
|
r.push(child)
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ export class SplitContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpannerInfo {
|
export interface SplitSpannerInfo {
|
||||||
container: SplitContainer
|
container: SplitContainer
|
||||||
index: number
|
index: number
|
||||||
}
|
}
|
||||||
@ -100,22 +100,31 @@ interface SpannerInfo {
|
|||||||
template: `
|
template: `
|
||||||
<ng-container #vc></ng-container>
|
<ng-container #vc></ng-container>
|
||||||
<split-tab-spanner
|
<split-tab-spanner
|
||||||
*ngFor='let spanner of spanners'
|
*ngFor='let spanner of _spanners'
|
||||||
[container]='spanner.container'
|
[container]='spanner.container'
|
||||||
[index]='spanner.index'
|
[index]='spanner.index'
|
||||||
(change)='layout()'
|
(change)='onSpannerAdjusted(spanner)'
|
||||||
></split-tab-spanner>
|
></split-tab-spanner>
|
||||||
`,
|
`,
|
||||||
styles: [require('./splitTab.component.scss')],
|
styles: [require('./splitTab.component.scss')],
|
||||||
})
|
})
|
||||||
export class SplitTabComponent extends BaseTabComponent {
|
export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
|
||||||
root: SplitContainer
|
|
||||||
viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
|
||||||
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
||||||
hotkeysSubscription: Subscription
|
root: SplitContainer
|
||||||
focusedTab: BaseTabComponent
|
_recoveredState: any
|
||||||
recoveredState: any
|
_spanners: SplitSpannerInfo[] = []
|
||||||
spanners: SpannerInfo[] = []
|
private focusedTab: BaseTabComponent
|
||||||
|
private hotkeysSubscription: Subscription
|
||||||
|
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
|
|
||||||
|
protected tabAdded = new Subject<BaseTabComponent>()
|
||||||
|
protected tabRemoved = new Subject<BaseTabComponent>()
|
||||||
|
protected splitAdjusted = new Subject<SplitSpannerInfo>()
|
||||||
|
protected focusChanged = new Subject<BaseTabComponent>()
|
||||||
|
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
|
||||||
|
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
|
||||||
|
get splitAdjusted$ (): Observable<SplitSpannerInfo> { return this.splitAdjusted }
|
||||||
|
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
@ -127,10 +136,10 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
this.setTitle('')
|
this.setTitle('')
|
||||||
|
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.allTabs().forEach(x => x.emitFocused())
|
this.getAllTabs().forEach(x => x.emitFocused())
|
||||||
this.focus(this.focusedTab)
|
this.focus(this.focusedTab)
|
||||||
})
|
})
|
||||||
this.blurred$.subscribe(() => this.allTabs().forEach(x => x.emitBlurred()))
|
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
@ -166,11 +175,11 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
if (this.recoveredState) {
|
if (this._recoveredState) {
|
||||||
await this.recoverContainer(this.root, this.recoveredState)
|
await this.recoverContainer(this.root, this._recoveredState)
|
||||||
this.layout()
|
this.layout()
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.allTabs().forEach(x => x.emitFocused())
|
this.getAllTabs().forEach(x => x.emitFocused())
|
||||||
this.focusAnyIn(this.root)
|
this.focusAnyIn(this.root)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -180,19 +189,24 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
this.hotkeysSubscription.unsubscribe()
|
this.hotkeysSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
allTabs () {
|
getAllTabs () {
|
||||||
return [...this.root.allTabs()]
|
return this.root.getAllTabs()
|
||||||
|
}
|
||||||
|
|
||||||
|
getFocusedTab (): BaseTabComponent {
|
||||||
|
return this.focusedTab
|
||||||
}
|
}
|
||||||
|
|
||||||
focus (tab: BaseTabComponent) {
|
focus (tab: BaseTabComponent) {
|
||||||
this.focusedTab = tab
|
this.focusedTab = tab
|
||||||
for (let x of this.allTabs()) {
|
for (let x of this.getAllTabs()) {
|
||||||
if (x !== tab) {
|
if (x !== tab) {
|
||||||
x.emitBlurred()
|
x.emitBlurred()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tab) {
|
if (tab) {
|
||||||
tab.emitFocused()
|
tab.emitFocused()
|
||||||
|
this.focusChanged.next(tab)
|
||||||
}
|
}
|
||||||
this.layout()
|
this.layout()
|
||||||
}
|
}
|
||||||
@ -208,8 +222,8 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insert (tab: BaseTabComponent, relative: BaseTabComponent, dir: SplitDirection) {
|
addTab (tab: BaseTabComponent, relative: BaseTabComponent, dir: SplitDirection) {
|
||||||
let target = this.getParent(relative) || this.root
|
let target = this.getParentOf(relative) || this.root
|
||||||
let insertIndex = target.children.indexOf(relative)
|
let insertIndex = target.children.indexOf(relative)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -238,56 +252,40 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
target.children.splice(insertIndex, 0, tab)
|
target.children.splice(insertIndex, 0, tab)
|
||||||
|
|
||||||
this.recoveryStateChangedHint.next()
|
this.recoveryStateChangedHint.next()
|
||||||
this.addTab(tab)
|
this.attachTabView(tab)
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.layout()
|
this.layout()
|
||||||
|
this.tabAdded.next(tab)
|
||||||
this.focus(tab)
|
this.focus(tab)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
remove (tab: BaseTabComponent) {
|
removeTab (tab: BaseTabComponent) {
|
||||||
let parent = this.getParent(tab)
|
let parent = this.getParentOf(tab)
|
||||||
let index = parent.children.indexOf(tab)
|
let index = parent.children.indexOf(tab)
|
||||||
parent.ratios.splice(index, 1)
|
parent.ratios.splice(index, 1)
|
||||||
parent.children.splice(index, 1)
|
parent.children.splice(index, 1)
|
||||||
|
|
||||||
let ref = this.viewRefs.get(tab)
|
this.detachTabView(tab)
|
||||||
this.viewRefs.delete(tab)
|
|
||||||
this.viewContainer.remove(this.viewContainer.indexOf(ref))
|
|
||||||
|
|
||||||
this.layout()
|
this.layout()
|
||||||
|
|
||||||
|
this.tabRemoved.next(tab)
|
||||||
|
|
||||||
if (this.root.children.length === 0) {
|
if (this.root.children.length === 0) {
|
||||||
this.destroy()
|
this.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
tab.progress$.subscribe(p => this.setProgress(p))
|
|
||||||
if (tab.title) {
|
|
||||||
this.setTitle(tab.title)
|
|
||||||
}
|
|
||||||
tab.destroyed$.subscribe(() => {
|
|
||||||
this.remove(tab)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate (dir: SplitDirection) {
|
navigate (dir: SplitDirection) {
|
||||||
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||||
let parent = this.getParent(rel)
|
let parent = this.getParentOf(rel)
|
||||||
let orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
let orientation = ['l', 'r'].includes(dir) ? 'h' : 'v'
|
||||||
|
|
||||||
while (parent !== this.root && parent.orientation !== orientation) {
|
while (parent !== this.root && parent.orientation !== orientation) {
|
||||||
rel = parent
|
rel = parent
|
||||||
parent = this.getParent(rel)
|
parent = this.getParentOf(rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.orientation !== orientation) {
|
if (parent.orientation !== orientation) {
|
||||||
@ -308,14 +306,14 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
||||||
let newTab = await this.tabsService.duplicate(tab)
|
let newTab = await this.tabsService.duplicate(tab)
|
||||||
this.insert(newTab, tab, dir)
|
this.addTab(newTab, tab, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
getParent (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer {
|
getParentOf (tab: BaseTabComponent | SplitContainer, root?: SplitContainer): SplitContainer {
|
||||||
root = root || this.root
|
root = root || this.root
|
||||||
for (let child of root.children) {
|
for (let child of root.children) {
|
||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
let r = this.getParent(tab, child)
|
let r = this.getParentOf(tab, child)
|
||||||
if (r) {
|
if (r) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -328,7 +326,7 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async canClose (): Promise<boolean> {
|
async canClose (): Promise<boolean> {
|
||||||
return !(await Promise.all(this.allTabs().map(x => x.canClose()))).some(x => !x)
|
return !(await Promise.all(this.getAllTabs().map(x => x.canClose()))).some(x => !x)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecoveryToken (): Promise<any> {
|
async getRecoveryToken (): Promise<any> {
|
||||||
@ -336,12 +334,40 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentProcess (): Promise<BaseTabProcess> {
|
async getCurrentProcess (): Promise<BaseTabProcess> {
|
||||||
return (await Promise.all(this.allTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
|
return (await Promise.all(this.getAllTabs().map(x => x.getCurrentProcess()))).find(x => !!x)
|
||||||
|
}
|
||||||
|
|
||||||
|
onSpannerAdjusted (spanner: SplitSpannerInfo) {
|
||||||
|
this.layout()
|
||||||
|
this.splitAdjusted.next(spanner)
|
||||||
|
}
|
||||||
|
|
||||||
|
private attachTabView (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())
|
||||||
|
tab.progress$.subscribe(p => this.setProgress(p))
|
||||||
|
if (tab.title) {
|
||||||
|
this.setTitle(tab.title)
|
||||||
|
}
|
||||||
|
tab.destroyed$.subscribe(() => {
|
||||||
|
this.removeTab(tab)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private detachTabView (tab: BaseTabComponent) {
|
||||||
|
let ref = this.viewRefs.get(tab)
|
||||||
|
this.viewRefs.delete(tab)
|
||||||
|
this.viewContainer.remove(this.viewContainer.indexOf(ref))
|
||||||
}
|
}
|
||||||
|
|
||||||
private layout () {
|
private layout () {
|
||||||
this.root.normalize()
|
this.root.normalize()
|
||||||
this.spanners = []
|
this._spanners = []
|
||||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +401,7 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
offset += sizes[i]
|
offset += sizes[i]
|
||||||
|
|
||||||
if (i !== 0) {
|
if (i !== 0) {
|
||||||
this.spanners.push({
|
this._spanners.push({
|
||||||
container: root,
|
container: root,
|
||||||
index: i,
|
index: i,
|
||||||
})
|
})
|
||||||
@ -398,7 +424,7 @@ export class SplitTabComponent extends BaseTabComponent {
|
|||||||
if (recovered) {
|
if (recovered) {
|
||||||
let tab = this.tabsService.create(recovered.type, recovered.options)
|
let tab = this.tabsService.create(recovered.type, recovered.options)
|
||||||
children.push(tab)
|
children.push(tab)
|
||||||
this.addTab(tab)
|
this.attachTabView(tab)
|
||||||
} else {
|
} else {
|
||||||
state.ratios.splice(state.children.indexOf(childState), 0)
|
state.ratios.splice(state.children.indexOf(childState), 0)
|
||||||
}
|
}
|
||||||
@ -413,7 +439,7 @@ export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
|||||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
||||||
return {
|
return {
|
||||||
type: SplitTabComponent,
|
type: SplitTabComponent,
|
||||||
options: { recoveredState: recoveryToken },
|
options: { _recoveredState: recoveryToken },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -118,7 +118,7 @@ export class AppService {
|
|||||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||||
let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
let splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
||||||
let tab = this.tabsService.create(type, inputs)
|
let tab = this.tabsService.create(type, inputs)
|
||||||
splitTab.insert(tab, null, 'r')
|
splitTab.addTab(tab, null, 'r')
|
||||||
this.addTabRaw(splitTab)
|
this.addTabRaw(splitTab)
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user