splitting tabs (fixes #49)

This commit is contained in:
Eugene Pankov
2019-03-04 20:39:05 +01:00
parent 7279ba13ac
commit 27fb611166
7 changed files with 160 additions and 6 deletions

View File

@@ -1,4 +1,5 @@
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
export { ConfigProvider } from './configProvider'

View File

@@ -0,0 +1,5 @@
:host {
display: block;
position: relative;
flex: auto;
}

View File

@@ -13,6 +13,10 @@ export class SplitContainer {
orientation: SplitOrientation = 'h'
children: (BaseTabComponent | SplitContainer)[] = []
ratios: number[] = []
x: number
y: number
w: number
h: number
allTabs () {
let r = []
@@ -60,6 +64,14 @@ export class SplitContainer {
this.ratios = this.ratios.map(x => x / s)
}
getOffsetRatio (index: number): number {
let s = 0
for (let i = 0; i < index; i++) {
s += this.ratios[i]
}
return s
}
async serialize () {
let children = []
for (let child of this.children) {
@@ -78,12 +90,23 @@ export class SplitContainer {
}
}
interface SpannerInfo {
container: SplitContainer
index: number
}
@Component({
selector: 'splitTab',
template: '<ng-container #vc></ng-container>',
styles: [
':host { position: relative; flex: auto; display: block; }'
]
selector: 'split-tab',
template: `
<ng-container #vc></ng-container>
<split-tab-spanner
*ngFor='let spanner of spanners'
[container]='spanner.container'
[index]='spanner.index'
(change)='layout()'
></split-tab-spanner>
`,
styles: [require('./splitTab.component.scss')],
})
export class SplitTabComponent extends BaseTabComponent {
root: SplitContainer
@@ -92,6 +115,7 @@ export class SplitTabComponent extends BaseTabComponent {
hotkeysSubscription: Subscription
focusedTab: BaseTabComponent
recoveredState: any
spanners: SpannerInfo[] = []
constructor (
private hotkeys: HotkeysService,
@@ -297,6 +321,7 @@ export class SplitTabComponent extends BaseTabComponent {
private layout () {
this.root.normalize()
this.spanners = []
this.layoutInternal(this.root, 0, 0, 100, 100)
}
@@ -304,6 +329,11 @@ export class SplitTabComponent extends BaseTabComponent {
let size = (root.orientation === 'v') ? h : w
let sizes = root.ratios.map(x => x * size)
root.x = x
root.y = y
root.w = w
root.h = h
let offset = 0
root.children.forEach((child, i) => {
let childX = (root.orientation === 'v') ? x : (x + offset)
@@ -323,6 +353,13 @@ export class SplitTabComponent extends BaseTabComponent {
element.style.opacity = (child === this.focusedTab) ? 1 : 0.75
}
offset += sizes[i]
if (i !== 0) {
this.spanners.push({
container: root,
index: i,
})
}
})
}

View File

@@ -0,0 +1,22 @@
:host {
display: block;
position: absolute;
z-index: 5;
transition: 0.125s background;
&.v {
cursor: ns-resize;
height: 10px;
margin-top: -5px;
}
&.h {
cursor: ew-resize;
width: 10px;
margin-left: -5px;
}
&:hover, &.active {
background: rgba(255, 255, 255, .125);
}
}

View File

@@ -0,0 +1,87 @@
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
import { SplitContainer } from './splitTab.component'
@Component({
selector: 'split-tab-spanner',
template: '',
styles: [require('./splitTabSpanner.component.scss')],
})
export class SplitTabSpannerComponent {
@Input() container: SplitContainer
@Input() index: number
@Output() change = new EventEmitter<void>()
@HostBinding('class.active') isActive = false
@HostBinding('class.h') isHorizontal = false
@HostBinding('class.v') isVertical = true
@HostBinding('style.left') cssLeft: string
@HostBinding('style.top') cssTop: string
@HostBinding('style.width') cssWidth: string
@HostBinding('style.height') cssHeight: string
private marginOffset = -5
constructor (private element: ElementRef) { }
ngAfterViewInit () {
this.element.nativeElement.addEventListener('mousedown', e => {
this.isActive = true
let start = this.isVertical ? e.pageY : e.pageX
let current = start
let oldPosition = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
const dragHandler = e => {
current = this.isVertical ? e.pageY : e.pageX
let newPosition = oldPosition + (current - start)
if (this.isVertical) {
this.element.nativeElement.style.top = `${newPosition - this.marginOffset}px`
} else {
this.element.nativeElement.style.left = `${newPosition - this.marginOffset}px`
}
}
const offHandler = () => {
this.isActive = false
document.removeEventListener('mouseup', offHandler)
this.element.nativeElement.parentElement.removeEventListener('mousemove', dragHandler)
let diff = (current - start) / (this.isVertical ? this.element.nativeElement.parentElement.clientHeight : this.element.nativeElement.parentElement.clientWidth)
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
this.container.ratios[this.index - 1] += diff
this.container.ratios[this.index] -= diff
this.change.emit()
}
document.addEventListener('mouseup', offHandler)
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
})
}
ngOnChanges () {
this.isHorizontal = this.container.orientation === 'h'
this.isVertical = this.container.orientation === 'v'
if (this.isVertical) {
this.setDimensions(
this.container.x,
this.container.y + this.container.h * this.container.getOffsetRatio(this.index),
this.container.w,
null
)
} else {
this.setDimensions(
this.container.x + this.container.w * this.container.getOffsetRatio(this.index),
this.container.y,
null,
this.container.h
)
}
}
private setDimensions (x: number, y: number, w: number, h: number) {
this.cssLeft = `${x}%`
this.cssTop = `${y}%`
this.cssWidth = w ? `${w}%` : null
this.cssHeight = h ? `${h}%` : null
}
}

View File

@@ -19,6 +19,7 @@ import { ToggleComponent } from './components/toggle.component'
import { WindowControlsComponent } from './components/windowControls.component'
import { RenameTabModalComponent } from './components/renameTabModal.component'
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
import { AutofocusDirective } from './directives/autofocus.directive'
@@ -70,6 +71,7 @@ const PROVIDERS = [
SafeModeModalComponent,
AutofocusDirective,
SplitTabComponent,
SplitTabSpannerComponent,
],
entryComponents: [
RenameTabModalComponent,

View File

@@ -37,7 +37,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
if (settingsTab) {
this.app.selectTab(settingsTab)
} else {
this.app.openNewTab(SettingsTabComponent)
this.app.openNewTabRaw(SettingsTabComponent)
}
}
}