mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-10 10:31:51 +00:00
new toolbar design
This commit is contained in:
@@ -132,6 +132,10 @@ app-root {
|
|||||||
|
|
||||||
tab-body {
|
tab-body {
|
||||||
background: $content-bg;
|
background: $content-bg;
|
||||||
|
|
||||||
|
.terminal-toolbar .btn, .toolbar-pin-button {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
multi-hotkey-input {
|
multi-hotkey-input {
|
||||||
|
@@ -31,3 +31,26 @@ export function wrapPromise <T> (zone: NgZone, promise: Promise<T>): Promise<T>
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ResettableTimeout {
|
||||||
|
private fn: () => void
|
||||||
|
private timeout: number
|
||||||
|
private id: any
|
||||||
|
|
||||||
|
constructor (fn: () => void, timeout: number) {
|
||||||
|
this.fn = fn
|
||||||
|
this.timeout = timeout
|
||||||
|
this.id = null
|
||||||
|
}
|
||||||
|
|
||||||
|
set (timeout?: number): void {
|
||||||
|
this.clear()
|
||||||
|
this.id = setTimeout(this.fn, timeout ?? this.timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear (): void {
|
||||||
|
if (this.id) {
|
||||||
|
clearTimeout(this.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
.tab-toolbar([class.show]='!session || !session.open')
|
.terminal-toolbar(
|
||||||
.btn.btn-outline-secondary.reveal-button
|
(mouseenter)='showToolbar()',
|
||||||
i.fas.fa-ellipsis-h
|
(mouseleave)='hideToolbar()'
|
||||||
.toolbar
|
)
|
||||||
i.fas.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
i.fas.fa-xs.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
||||||
i.fas.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
i.fas.fa-xs.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
||||||
strong {{profile.options.port}} ({{profile.options.baudrate}})
|
strong {{profile.options.port}} ({{profile.options.baudrate}})
|
||||||
|
|
||||||
.mr-auto
|
.mr-auto
|
||||||
|
|
||||||
button.btn.btn-secondary.mr-3((click)='changeBaudRate()', *ngIf='session && session.open')
|
button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open')
|
||||||
span Change baud rate
|
span Change baud rate
|
||||||
|
|
||||||
button.btn.btn-info((click)='reconnect()', *ngIf='!session || !session.open')
|
button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
|
||||||
i.fas.fa-reload
|
i.fas.fa-redo
|
||||||
span Reconnect
|
span Reconnect
|
||||||
|
@@ -14,6 +14,7 @@ import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class SerialTabComponent extends BaseTerminalTabComponent {
|
export class SerialTabComponent extends BaseTerminalTabComponent {
|
||||||
|
enableToolbar = true
|
||||||
profile?: SerialProfile
|
profile?: SerialProfile
|
||||||
session: SerialSession|null = null
|
session: SerialSession|null = null
|
||||||
serialPort: any
|
serialPort: any
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
.tab-toolbar([class.show]='!session || !session.open')
|
.terminal-toolbar(
|
||||||
.btn.btn-outline-secondary.reveal-button
|
(mouseenter)='showToolbar()',
|
||||||
i.fas.fa-ellipsis-h
|
(mouseleave)='hideToolbar()'
|
||||||
.toolbar
|
)
|
||||||
i.fas.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
i.fas.fa-xs.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
||||||
i.fas.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
i.fas.fa-xs.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
||||||
strong.mr-auto {{profile.options.user}}@{{profile.options.host}}:{{profile.options.port}}
|
strong.mr-auto {{profile.options.user}}@{{profile.options.host}}:{{profile.options.port}}
|
||||||
|
|
||||||
button.btn.btn-secondary.mr-2((click)='reconnect()', [class.btn-info]='!session || !session.open')
|
button.btn.btn-sm.btn-link.mr-2((click)='reconnect()')
|
||||||
span Reconnect
|
i.fas.fa-redo
|
||||||
|
span Reconnect
|
||||||
|
|
||||||
button.btn.btn-secondary.mr-2((click)='openSFTP()', *ngIf='session && session.open')
|
button.btn.btn-sm.btn-link.mr-2((click)='openSFTP()', *ngIf='session && session.open')
|
||||||
span SFTP
|
i.far.fa-folder-open
|
||||||
span.badge.badge-info.ml-2
|
span SFTP
|
||||||
i.fas.fa-flask
|
|
||||||
span Experimental
|
button.btn.btn-sm.btn-link(
|
||||||
|
*ngIf='session && session.open && hostApp.platform !== Platform.Web',
|
||||||
|
(click)='showPortForwarding()'
|
||||||
|
)
|
||||||
|
i.fas.fa-plug
|
||||||
|
span Ports
|
||||||
|
|
||||||
button.btn.btn-secondary(
|
|
||||||
*ngIf='session && session.open && hostApp.platform !== Platform.Web',
|
|
||||||
(click)='showPortForwarding()'
|
|
||||||
)
|
|
||||||
i.fas.fa-plug
|
|
||||||
span Ports
|
|
||||||
|
|
||||||
sftp-panel.bg-dark(
|
sftp-panel.bg-dark(
|
||||||
@panelSlide,
|
@panelSlide,
|
||||||
|
@@ -1,76 +1,3 @@
|
|||||||
:host {
|
|
||||||
flex: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&> .content {
|
|
||||||
flex: auto;
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .tab-toolbar {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 4;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
.reveal-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 35px;
|
|
||||||
padding: 0;
|
|
||||||
height: 35px;
|
|
||||||
line-height: 35px;
|
|
||||||
transition: 0.125s opacity;
|
|
||||||
opacity: .5;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .reveal-button {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .toolbar {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
opacity: 0;
|
|
||||||
background: rgba(0, 0, 0, .75);
|
|
||||||
padding: 10px 20px;
|
|
||||||
transition: 0.25s opacity;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 1;
|
|
||||||
will-change: transform;
|
|
||||||
transform: translate(0, -100px);
|
|
||||||
transition: 0.25s transform ease-out;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
.reveal-button {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sftp-panel {
|
sftp-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
|
@@ -17,6 +17,7 @@ import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.compon
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class SSHTabComponent extends BaseTerminalTabComponent {
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
|
enableToolbar = true
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
profile?: SSHProfile
|
profile?: SSHProfile
|
||||||
session: SSHSession|null = null
|
session: SSHSession|null = null
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
.tab-toolbar([class.show]='!session || !session.open')
|
.terminal-toolbar(
|
||||||
.btn.btn-outline-secondary.reveal-button
|
(mouseenter)='showToolbar()',
|
||||||
i.fas.fa-ellipsis-h
|
(mouseleave)='hideToolbar()'
|
||||||
.toolbar
|
)
|
||||||
i.fas.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
i.fas.fa-xs.fa-circle.text-success.mr-2(*ngIf='session && session.open')
|
||||||
i.fas.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
i.fas.fa-xs.fa-circle.text-danger.mr-2(*ngIf='!session || !session.open')
|
||||||
strong.mr-auto {{profile.options.host}}:{{profile.options.port}}
|
strong.mr-auto {{profile.options.host}}:{{profile.options.port}}
|
||||||
|
|
||||||
button.btn.btn-secondary.mr-2((click)='reconnect()', [class.btn-info]='!session || !session.open')
|
button.btn.btn-sm.btn-link.mr-2((click)='reconnect()')
|
||||||
span Reconnect
|
i.fas.fa-redo
|
||||||
|
span Reconnect
|
||||||
|
@@ -14,6 +14,7 @@ import { TelnetProfile, TelnetSession } from '../session'
|
|||||||
animations: BaseTerminalTabComponent.animations,
|
animations: BaseTerminalTabComponent.animations,
|
||||||
})
|
})
|
||||||
export class TelnetTabComponent extends BaseTerminalTabComponent {
|
export class TelnetTabComponent extends BaseTerminalTabComponent {
|
||||||
|
enableToolbar = true
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
profile?: TelnetProfile
|
profile?: TelnetProfile
|
||||||
session: TelnetSession|null = null
|
session: TelnetSession|null = null
|
||||||
|
@@ -3,7 +3,7 @@ import { Spinner } from 'cli-spinner'
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
|
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
|
||||||
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
|
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
|
||||||
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService } from 'tabby-core'
|
import { AppService, ConfigService, BaseTabComponent, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer, MenuItemOptions, PlatformService, HostWindowService, ResettableTimeout } from 'tabby-core'
|
||||||
|
|
||||||
import { BaseSession } from '../session'
|
import { BaseSession } from '../session'
|
||||||
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||||
@@ -75,6 +75,15 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
/** @hidden */
|
/** @hidden */
|
||||||
@HostBinding('class.top-padded') topPadded: boolean
|
@HostBinding('class.top-padded') topPadded: boolean
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@HostBinding('class.toolbar-enabled') enableToolbar = false
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@HostBinding('class.toolbar-pinned') pinToolbar = false
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@HostBinding('class.toolbar-revealed') revealToolbar = false
|
||||||
|
|
||||||
frontend?: Frontend
|
frontend?: Frontend
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -125,6 +134,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
private spinnerActive = false
|
private spinnerActive = false
|
||||||
|
private toolbarRevealTimeout = new ResettableTimeout(() => {
|
||||||
|
this.revealToolbar = false
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
get input$ (): Observable<Buffer> {
|
get input$ (): Observable<Buffer> {
|
||||||
if (!this.frontend) {
|
if (!this.frontend) {
|
||||||
@@ -260,6 +272,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.bellPlayer.src = require('../bell.ogg').default
|
this.bellPlayer.src = require('../bell.ogg').default
|
||||||
|
|
||||||
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
|
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
|
||||||
|
|
||||||
|
this.pinToolbar = (window.localStorage.pinTerminalToolbar ?? 'true') === 'true'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -646,6 +660,20 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.sessionChanged.next(session)
|
this.sessionChanged.next(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showToolbar (): void {
|
||||||
|
this.revealToolbar = true
|
||||||
|
this.toolbarRevealTimeout.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
hideToolbar (): void {
|
||||||
|
this.toolbarRevealTimeout.set()
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePinToolbar (): void {
|
||||||
|
this.pinToolbar = !this.pinToolbar
|
||||||
|
window.localStorage.pinTerminalToolbar = this.pinToolbar
|
||||||
|
}
|
||||||
|
|
||||||
protected attachSessionHandler <T> (observable: Observable<T>, handler: (v: T) => void): void {
|
protected attachSessionHandler <T> (observable: Observable<T>, handler: (v: T) => void): void {
|
||||||
this.sessionHandlers.subscribe(observable, handler)
|
this.sessionHandlers.subscribe(observable, handler)
|
||||||
}
|
}
|
||||||
|
@@ -5,3 +5,14 @@ search-panel(
|
|||||||
[frontend]='frontend',
|
[frontend]='frontend',
|
||||||
(close)='showSearchPanel = false'
|
(close)='showSearchPanel = false'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
button.btn.btn-sm.btn-link.toolbar-pin-button(
|
||||||
|
*ngIf='enableToolbar',
|
||||||
|
(click)='togglePinToolbar()',
|
||||||
|
(mouseenter)='showToolbar()',
|
||||||
|
(mouseleave)='hideToolbar()'
|
||||||
|
)
|
||||||
|
i.fas.fa-thumbtack(*ngIf='revealToolbar || pinToolbar')
|
||||||
|
i.fas.fa-wrench.mr-3(*ngIf='!revealToolbar && !pinToolbar')
|
||||||
|
span(*ngIf='pinToolbar') Unpin
|
||||||
|
span(*ngIf='!pinToolbar && revealToolbar') Pin
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&.top-padded {
|
&.top-padded {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
@@ -22,4 +23,47 @@
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$toolbarHeight: 40px;
|
||||||
|
|
||||||
|
&>.terminal-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 4;
|
||||||
|
height: $toolbarHeight;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
background: rgba(0, 0, 0, .75);
|
||||||
|
padding: 5px 85px 5px 15px;
|
||||||
|
transition: 0.25s opacity;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 3;
|
||||||
|
will-change: transform;
|
||||||
|
transform: translate(0, -100px);
|
||||||
|
transition: 0.25s transform ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toolbar-revealed, &.toolbar-pinned {
|
||||||
|
> .terminal-toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>.toolbar-pin-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 2px;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toolbar-pinned {
|
||||||
|
.content {
|
||||||
|
margin-top: 15px + $toolbarHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user