mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-28 07:19:54 +00:00
expose combined context menu in tab header and add visual context menu button - fixes #6966
This commit is contained in:
parent
3226a3d70f
commit
fbea7db188
@ -13,13 +13,17 @@ profile-icon(
|
|||||||
|
|
||||||
.name(
|
.name(
|
||||||
[title]='tab.customTitle || tab.title',
|
[title]='tab.customTitle || tab.title',
|
||||||
[class.no-hover]='config.store.terminal.hideCloseButton'
|
[class.no-hover]='config.store.terminal.hideCloseButton && config.store.terminal.hideTabOptionsButton'
|
||||||
cdkDrag,
|
cdkDrag,
|
||||||
cdkDragRootElement='tab-header',
|
cdkDragRootElement='tab-header',
|
||||||
[cdkDragData]='tab',
|
[cdkDragData]='tab',
|
||||||
(cdkDragStarted)='onTabDragStart(tab)',
|
(cdkDragStarted)='onTabDragStart(tab)',
|
||||||
(cdkDragEnded)='onTabDragEnd()',
|
(cdkDragEnded)='onTabDragEnd()',
|
||||||
) {{tab.customTitle || tab.title}}
|
) {{tab.customTitle || tab.title}}
|
||||||
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
|
||||||
|
.buttons
|
||||||
|
button(*ngIf='!config.store.terminal.hideTabOptionsButton',(click)='onContextMenu($event)') !{require('../icons/tab-options.svg')}
|
||||||
|
|
||||||
|
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||||
|
|
||||||
ng-content
|
ng-content
|
||||||
|
@ -59,41 +59,49 @@ $tabs-height: 38px;
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.buttons {
|
||||||
display: block;
|
display: flex;
|
||||||
flex: none;
|
|
||||||
background: transparent;
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
top: 2px;
|
||||||
|
right: 3px;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
$button-size: 26px;
|
button {
|
||||||
width: $button-size;
|
display: flex;
|
||||||
height: $button-size;
|
align-items: center;
|
||||||
border-radius: $button-size / 6;
|
flex: none;
|
||||||
line-height: $button-size;
|
justify-content: center;
|
||||||
align-self: center;
|
|
||||||
|
|
||||||
text-align: center;
|
background: transparent;
|
||||||
font-size: 20px;
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
&:focus {
|
$button-size: 26px;
|
||||||
outline: 0;
|
width: $button-size;
|
||||||
|
height: $button-size;
|
||||||
|
border-radius: $button-size / 6;
|
||||||
|
line-height: $button-size;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .name:not(.no-hover) {
|
&:hover .name:not(.no-hover) {
|
||||||
-webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, black 100%);
|
-webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, transparent 50%, black 100%);
|
||||||
-webkit-mask-size: calc(100% - 60px) auto, 60px auto;
|
-webkit-mask-size: calc(100% - 80px) auto, 80px auto;
|
||||||
-webkit-mask-repeat: no-repeat;
|
-webkit-mask-repeat: no-repeat;
|
||||||
-webkit-mask-position: left, right;
|
-webkit-mask-position: left, right;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover button {
|
&:hover .buttons {
|
||||||
transition: 0.25s opacity;
|
transition: 0.25s opacity;
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { auditTime } from 'rxjs'
|
|||||||
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
|
import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider'
|
||||||
import { BaseTabComponent } from './baseTab.component'
|
import { BaseTabComponent } from './baseTab.component'
|
||||||
import { RenameTabModalComponent } from './renameTabModal.component'
|
import { RenameTabModalComponent } from './renameTabModal.component'
|
||||||
|
import { SplitTabComponent } from './splitTab.component'
|
||||||
import { HotkeysService } from '../services/hotkeys.service'
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
import { HostAppService, Platform } from '../api/hostApp'
|
import { HostAppService, Platform } from '../api/hostApp'
|
||||||
@ -69,10 +70,24 @@ export class TabHeaderComponent extends BaseComponent {
|
|||||||
|
|
||||||
async buildContextMenu (): Promise<MenuItemOptions[]> {
|
async buildContextMenu (): Promise<MenuItemOptions[]> {
|
||||||
let items: MenuItemOptions[] = []
|
let items: MenuItemOptions[] = []
|
||||||
|
// Top-level tab menu
|
||||||
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
|
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
|
||||||
items.push({ type: 'separator' })
|
items.push({ type: 'separator' })
|
||||||
items = items.concat(section)
|
items = items.concat(section)
|
||||||
}
|
}
|
||||||
|
if (this.tab instanceof SplitTabComponent) {
|
||||||
|
const tab = this.tab.getFocusedTab()
|
||||||
|
if (tab) {
|
||||||
|
for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(tab, this)))) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
section = section.filter(item => !items.some(ex => ex.label === item.label))
|
||||||
|
if (section.length) {
|
||||||
|
items.push({ type: 'separator' })
|
||||||
|
items = items.concat(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return items.slice(1)
|
return items.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
tabby-core/src/icons/tab-options.svg
Normal file
1
tabby-core/src/icons/tab-options.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M352 256C352 238.3 366.3 224 384 224C401.7 224 416 238.3 416 256C416 273.7 401.7 288 384 288C366.3 288 352 273.7 352 256zM192 256C192 238.3 206.3 224 224 224C241.7 224 256 238.3 256 256C256 273.7 241.7 288 224 288C206.3 288 192 273.7 192 256zM96 256C96 273.7 81.67 288 64 288C46.33 288 32 273.7 32 256C32 238.3 46.33 224 64 224C81.67 224 96 238.3 96 256z"/></svg>
|
After Width: | Height: | Size: 623 B |
@ -41,7 +41,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if (tabHeader) {
|
if (!tab.parent) {
|
||||||
items = [
|
items = [
|
||||||
...items,
|
...items,
|
||||||
{
|
{
|
||||||
@ -69,24 +69,22 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else {
|
} else if (tab.parent instanceof SplitTabComponent) {
|
||||||
if (tab.parent instanceof SplitTabComponent) {
|
const directions: SplitDirection[] = ['r', 'b', 'l', 't']
|
||||||
const directions: SplitDirection[] = ['r', 'b', 'l', 't']
|
items.push({
|
||||||
items.push({
|
label: this.translate.instant('Split'),
|
||||||
label: this.translate.instant('Split'),
|
submenu: directions.map(dir => ({
|
||||||
submenu: directions.map(dir => ({
|
label: {
|
||||||
label: {
|
r: this.translate.instant('Right'),
|
||||||
r: this.translate.instant('Right'),
|
b: this.translate.instant('Down'),
|
||||||
b: this.translate.instant('Down'),
|
l: this.translate.instant('Left'),
|
||||||
l: this.translate.instant('Left'),
|
t: this.translate.instant('Up'),
|
||||||
t: this.translate.instant('Up'),
|
}[dir],
|
||||||
}[dir],
|
click: () => {
|
||||||
click: () => {
|
(tab.parent as SplitTabComponent).splitTab(tab, dir)
|
||||||
(tab.parent as SplitTabComponent).splitTab(tab, dir)
|
},
|
||||||
},
|
})) as MenuItemOptions[],
|
||||||
})) as MenuItemOptions[],
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@ -273,9 +271,9 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider {
|
|||||||
tab.destroy()
|
tab.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
|
||||||
|
|
||||||
if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
|
if (tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: this.translate.instant('Switch profile'),
|
label: this.translate.instant('Switch profile'),
|
||||||
|
@ -295,6 +295,15 @@ h3.mt-4(translate) Tabs
|
|||||||
(ngModelChange)='config.save();',
|
(ngModelChange)='config.save();',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title(translate) Hide tab options button
|
||||||
|
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.terminal.hideTabOptionsButton',
|
||||||
|
(ngModelChange)='config.save();',
|
||||||
|
)
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title(translate) Hide tab close button
|
.title(translate) Hide tab close button
|
||||||
|
@ -22,6 +22,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
hideTabIndex: false,
|
hideTabIndex: false,
|
||||||
showTabProfileIcon: false,
|
showTabProfileIcon: false,
|
||||||
hideCloseButton: false,
|
hideCloseButton: false,
|
||||||
|
hideTabOptionsButton: false,
|
||||||
rightClick: 'menu',
|
rightClick: 'menu',
|
||||||
pasteOnMiddleClick: true,
|
pasteOnMiddleClick: true,
|
||||||
copyOnSelect: false,
|
copyOnSelect: false,
|
||||||
|
@ -48,6 +48,14 @@ export class MiscContextMenu extends TabContextMenuItemProvider {
|
|||||||
constructor (private translate: TranslateService) { super() }
|
constructor (private translate: TranslateService) { super() }
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
|
async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
|
||||||
|
if (tab instanceof BaseTerminalTabComponent && tab.enableToolbar && !tab.pinToolbar) {
|
||||||
|
return [{
|
||||||
|
label: this.translate.instant('Show toolbar'),
|
||||||
|
click: () => {
|
||||||
|
tab.pinToolbar = true
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
if (tab instanceof BaseTerminalTabComponent && tab.session?.supportsWorkingDirectory()) {
|
if (tab instanceof BaseTerminalTabComponent && tab.session?.supportsWorkingDirectory()) {
|
||||||
return [{
|
return [{
|
||||||
label: this.translate.instant('Copy current path'),
|
label: this.translate.instant('Copy current path'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user