From 538b5c4c285763dafdfe99bdc13117e0eadc2040 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Tue, 7 Aug 2018 08:51:19 +0200 Subject: [PATCH] Allow reordering tabs (fixes #82) --- app/index.pug | 2 ++ app/src/preload.scss | 4 --- terminus-core/package.json | 1 + .../src/components/appRoot.component.pug | 16 +++++++--- .../src/components/appRoot.component.ts | 19 ++++++++++++ .../src/components/baseTab.component.ts | 1 - .../src/components/tabBody.component.scss | 4 --- .../src/components/tabBody.component.ts | 31 ++++++++++++------- .../src/components/tabHeader.component.pug | 2 +- .../src/components/tabHeader.component.scss | 6 ++++ .../src/components/tabHeader.component.ts | 13 +++++++- terminus-core/src/index.ts | 3 ++ terminus-core/src/services/app.service.ts | 6 +++- terminus-core/src/theme.scss | 2 +- .../src/components/settingsTab.component.ts | 1 - 15 files changed, 81 insertions(+), 30 deletions(-) diff --git a/app/index.pug b/app/index.pug index 5f95f5ac..45eb5085 100644 --- a/app/index.pug +++ b/app/index.pug @@ -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 diff --git a/app/src/preload.scss b/app/src/preload.scss index 191e4a59..c9b65145 100644 --- a/app/src/preload.scss +++ b/app/src/preload.scss @@ -66,7 +66,3 @@ [ngbradiogroup] input[type="radio"] { display: none; } - -body { - background: #131d27; -} diff --git a/terminus-core/package.json b/terminus-core/package.json index f1a3c289..eb65354e 100644 --- a/terminus-core/package.json +++ b/terminus-core/package.json @@ -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": { diff --git a/terminus-core/src/components/appRoot.component.pug b/terminus-core/src/components/appRoot.component.pug index caa16f7f..e398256e 100644 --- a/terminus-core/src/components/appRoot.component.pug +++ b/terminus-core/src/components/appRoot.component.pug @@ -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) diff --git a/terminus-core/src/components/appRoot.component.ts b/terminus-core/src/components/appRoot.component.ts index adbe7529..c9dffa91 100644 --- a/terminus-core/src/components/appRoot.component.ts +++ b/terminus-core/src/components/appRoot.component.ts @@ -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 => { diff --git a/terminus-core/src/components/baseTab.component.ts b/terminus-core/src/components/baseTab.component.ts index 1708f631..04bec98b 100644 --- a/terminus-core/src/components/baseTab.component.ts +++ b/terminus-core/src/components/baseTab.component.ts @@ -7,7 +7,6 @@ export abstract class BaseTabComponent { title: string titleChange$ = new Subject() customTitle: string - scrollable: boolean hasActivity = false focused$ = new Subject() blurred$ = new Subject() diff --git a/terminus-core/src/components/tabBody.component.scss b/terminus-core/src/components/tabBody.component.scss index 8af4f6b0..3b169ad5 100644 --- a/terminus-core/src/components/tabBody.component.scss +++ b/terminus-core/src/components/tabBody.component.scss @@ -4,10 +4,6 @@ position: relative; overflow: hidden; - &.scrollable { - overflow-y: auto; - } - &.active { display: flex; diff --git a/terminus-core/src/components/tabBody.component.ts b/terminus-core/src/components/tabBody.component.ts index 2756e4dd..016bb5a5 100644 --- a/terminus-core/src/components/tabBody.component.ts +++ b/terminus-core/src/components/tabBody.component.ts @@ -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: ` - + + `, 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() } } diff --git a/terminus-core/src/components/tabHeader.component.pug b/terminus-core/src/components/tabHeader.component.pug index c735fca0..fa22a4cf 100644 --- a/terminus-core/src/components/tabHeader.component.pug +++ b/terminus-core/src/components/tabHeader.component.pug @@ -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)') × diff --git a/terminus-core/src/components/tabHeader.component.scss b/terminus-core/src/components/tabHeader.component.scss index a8f18c33..5dccfa0c 100644 --- a/terminus-core/src/components/tabHeader.component.scss +++ b/terminus-core/src/components/tabHeader.component.scss @@ -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; + } } diff --git a/terminus-core/src/components/tabHeader.component.ts b/terminus-core/src/components/tabHeader.component.ts index be54e722..02e60d38 100644 --- a/terminus-core/src/components/tabHeader.component.ts +++ b/terminus-core/src/components/tabHeader.component.ts @@ -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 diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index 52f45cdb..f9fe6348 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -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, diff --git a/terminus-core/src/services/app.service.ts b/terminus-core/src/services/app.service.ts index 2f4e647d..03c45304 100644 --- a/terminus-core/src/services/app.service.ts +++ b/terminus-core/src/services/app.service.ts @@ -98,6 +98,10 @@ export class AppService { } } + emitTabsChanged () { + this.tabsChanged$.next() + } + async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise { 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]) } diff --git a/terminus-core/src/theme.scss b/terminus-core/src/theme.scss index 3c52bf2d..2137ba57 100644 --- a/terminus-core/src/theme.scss +++ b/terminus-core/src/theme.scss @@ -111,7 +111,7 @@ body { background: $body-bg; &.vibrant { - background: rgba($body-bg, 0.65); + background: rgba(0,0,0,.4); } } diff --git a/terminus-settings/src/components/settingsTab.component.ts b/terminus-settings/src/components/settingsTab.component.ts index 689c580b..b7c74a0d 100644 --- a/terminus-settings/src/components/settingsTab.component.ts +++ b/terminus-settings/src/components/settingsTab.component.ts @@ -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)