Allow reordering tabs (fixes #82)

This commit is contained in:
Eugene Pankov 2018-08-07 08:51:19 +02:00
parent 0419900e1d
commit 538b5c4c28
15 changed files with 81 additions and 30 deletions

View file

@ -9,6 +9,8 @@ html
script(src='./preload.js')
script(src='./bundle.js', defer)
style#custom-css
style.
body { transition: 0.5s background; }
body(style='min-height: 100vh; overflow: hidden')
app-root
.preload-logo

View file

@ -66,7 +66,3 @@
[ngbradiogroup] input[type="radio"] {
display: none;
}
body {
background: #131d27;
}

View file

@ -25,6 +25,7 @@
"bootstrap": "4.0.0-alpha.6",
"core-js": "^2.4.1",
"electron-updater": "^2.8.9",
"ng2-dnd": "^5.0.2",
"ngx-perfect-scrollbar": "^6.0.0"
},
"peerDependencies": {

View file

@ -10,16 +10,25 @@ title-bar(
*ngIf='!hostApp.isFullScreen',
)
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
.tabs
.tabs(
dnd-sortable-container,
[sortableData]='app.tabs',
)
tab-header(
*ngFor='let tab of app.tabs; let idx = index',
dnd-sortable,
[sortableIndex]='idx',
(onDragStart)='onTabDragStart()',
(onDragEnd)='onTabDragEnd()',
[index]='idx',
[tab]='tab',
[active]='tab == app.activeTab',
[hasActivity]='tab.hasActivity',
[class.drag-region]='hostApp.platform == Platform.macOS',
@animateTab,
(click)='app.selectTab(tab)',
[class.fully-draggable]='hostApp.platform != Platform.macOS',
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
)
.btn-group.background
@ -54,10 +63,9 @@ title-bar(
start-page(*ngIf='ready && app.tabs.length == 0')
tab-body(
*ngFor='let tab of app.tabs; trackBy: tab?.id',
*ngFor='let tab of unsortedTabs',
[active]='tab == app.activeTab',
[tab]='tab',
[scrollable]='tab.scrollable',
)
ng-template(ngbModalContainer)

View file

@ -13,6 +13,7 @@ import { ThemesService } from '../services/themes.service'
import { UpdaterService, Update } from '../services/updater.service'
import { TouchbarService } from '../services/touchbar.service'
import { BaseTabComponent } from './baseTab.component'
import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
@ -55,6 +56,8 @@ export class AppRootComponent {
@Input() leftToolbarButtons: IToolbarButton[]
@Input() rightToolbarButtons: IToolbarButton[]
@HostBinding('class') hostClass = `platform-${process.platform}`
tabsDragging = false
unsortedTabs: BaseTabComponent[] = []
private logger: Logger
private appUpdate: Update
@ -129,6 +132,11 @@ export class AppRootComponent {
config.changed$.subscribe(() => this.updateVibrancy())
this.updateVibrancy()
this.app.tabOpened$.subscribe(tab => this.unsortedTabs.push(tab))
this.app.tabClosed$.subscribe(tab => {
this.unsortedTabs = this.unsortedTabs.filter(x => x !== tab)
})
}
onGlobalHotkey () {
@ -183,6 +191,17 @@ export class AppRootComponent {
this.electron.shell.openExternal(this.appUpdate.url)
}
onTabDragStart () {
this.tabsDragging = true
}
onTabDragEnd () {
setTimeout(() => {
this.tabsDragging = false
this.app.emitTabsChanged()
})
}
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
let buttons: IToolbarButton[] = []
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {

View file

@ -7,7 +7,6 @@ export abstract class BaseTabComponent {
title: string
titleChange$ = new Subject<string>()
customTitle: string
scrollable: boolean
hasActivity = false
focused$ = new Subject<void>()
blurred$ = new Subject<void>()

View file

@ -4,10 +4,6 @@
position: relative;
overflow: hidden;
&.scrollable {
overflow-y: auto;
}
&.active {
display: flex;

View file

@ -1,29 +1,36 @@
import { Component, Input, ViewChild, HostBinding, ViewContainerRef } from '@angular/core'
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component'
@Component({
selector: 'tab-body',
template: `
<perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
<!--perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
<ng-template #scrollablePlaceholder></ng-template>
</perfect-scrollbar>
<ng-template #nonScrollablePlaceholder *ngIf="!scrollable"></ng-template>
</perfect-scrollbar-->
<ng-template #placeholder></ng-template>
`,
styles: [
require('./tabBody.component.scss'),
require('./tabBody.deep.component.css'),
],
})
export class TabBodyComponent {
export class TabBodyComponent implements OnChanges {
@Input() @HostBinding('class.active') active: boolean
@Input() tab: BaseTabComponent
@Input() scrollable: boolean
@ViewChild('scrollablePlaceholder', {read: ViewContainerRef}) scrollablePlaceholder: ViewContainerRef
@ViewChild('nonScrollablePlaceholder', {read: ViewContainerRef}) nonScrollablePlaceholder: ViewContainerRef
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
ngAfterViewInit () {
setImmediate(() => {
(this.scrollable ? this.scrollablePlaceholder : this.nonScrollablePlaceholder).insert(this.tab.hostView)
})
ngOnChanges (changes) {
if (changes.tab) {
if (this.placeholder) {
this.placeholder.detach()
}
setImmediate(() => {
this.placeholder.insert(this.tab.hostView)
})
}
}
ngOnDestroy () {
this.placeholder.detach()
}
}

View file

@ -1,3 +1,3 @@
.index {{index + 1}}
.index(#handle) {{index + 1}}
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
button((click)='app.closeTab(tab, true)') &times;

View file

@ -17,6 +17,8 @@ $tabs-height: 36px;
.index {
flex: none;
font-weight: bold;
-webkit-app-region: no-drag;
cursor: grab;
margin-left: 10px;
width: 20px;
@ -67,4 +69,8 @@ $tabs-height: 36px;
&.drag-region {
-webkit-app-region: drag;
}
&.fully-draggable {
cursor: grab;
}
}

View file

@ -1,9 +1,11 @@
import { Component, Input, HostBinding, HostListener, NgZone } from '@angular/core'
import { Component, Input, HostBinding, HostListener, NgZone, ViewChild, ElementRef } from '@angular/core'
import { SortableComponent } from 'ng2-dnd'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseTabComponent } from './baseTab.component'
import { RenameTabModalComponent } from './renameTabModal.component'
import { ElectronService } from '../services/electron.service'
import { AppService } from '../services/app.service'
import { HostAppService, Platform } from '../services/hostApp.service'
@Component({
selector: 'tab-header',
@ -15,13 +17,16 @@ export class TabHeaderComponent {
@Input() @HostBinding('class.active') active: boolean
@Input() @HostBinding('class.has-activity') hasActivity: boolean
@Input() tab: BaseTabComponent
@ViewChild('handle') handle: ElementRef
private contextMenu: any
constructor (
zone: NgZone,
electron: ElectronService,
public app: AppService,
private hostApp: HostAppService,
private ngbModal: NgbModal,
private parentDraggable: SortableComponent,
) {
this.contextMenu = electron.remote.Menu.buildFromTemplate([
{
@ -65,6 +70,12 @@ export class TabHeaderComponent {
])
}
ngOnInit () {
if (this.hostApp.platform !== Platform.macOS) {
this.parentDraggable.setDragHandle(this.handle.nativeElement)
}
}
@HostListener('dblclick') onDoubleClick (): void {
let modal = this.ngbModal.open(RenameTabModalComponent)
modal.componentInstance.value = this.tab.customTitle || this.tab.title

View file

@ -4,6 +4,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
import { DndModule } from 'ng2-dnd'
import { AppService } from './services/app.service'
import { ConfigService } from './services/config.service'
@ -35,6 +36,7 @@ import { StandardTheme, StandardCompactTheme } from './theme'
import { CoreConfigProvider } from './config'
import 'perfect-scrollbar/css/perfect-scrollbar.css'
import 'ng2-dnd/bundles/style.css'
const PROVIDERS = [
AppService,
@ -62,6 +64,7 @@ const PROVIDERS = [
FormsModule,
NgbModule.forRoot(),
PerfectScrollbarModule,
DndModule.forRoot(),
],
declarations: [
AppRootComponent,

View file

@ -98,6 +98,10 @@ export class AppService {
}
}
emitTabsChanged () {
this.tabsChanged$.next()
}
async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
if (!this.tabs.includes(tab)) {
return
@ -105,9 +109,9 @@ export class AppService {
if (checkCanClose && !await tab.canClose()) {
return
}
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
this.tabs = this.tabs.filter((x) => x !== tab)
tab.destroy()
let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
if (tab === this.activeTab) {
this.selectTab(this.tabs[newIndex])
}

View file

@ -111,7 +111,7 @@ body {
background: $body-bg;
&.vibrant {
background: rgba($body-bg, 0.65);
background: rgba(0,0,0,.4);
}
}

View file

@ -30,7 +30,6 @@ export class SettingsTabComponent extends BaseTabComponent {
super()
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.setTitle('Settings')
this.scrollable = true
this.screens = this.docking.getScreens()
this.settingsProviders = config.enabledServices(this.settingsProviders)
this.themes = config.enabledServices(this.themes)