From fbea7db188ce1986b330fc44b4daeb3869d24452 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 5 Sep 2022 00:24:21 +0200 Subject: [PATCH] expose combined context menu in tab header and add visual context menu button - fixes #6966 --- .../src/components/tabHeader.component.pug | 8 ++- .../src/components/tabHeader.component.scss | 52 +++++++++++-------- .../src/components/tabHeader.component.ts | 15 ++++++ tabby-core/src/icons/tab-options.svg | 1 + tabby-core/src/tabContextMenu.ts | 40 +++++++------- .../windowSettingsTab.component.pug | 9 ++++ tabby-terminal/src/config.ts | 1 + tabby-terminal/src/tabContextMenu.ts | 8 +++ 8 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 tabby-core/src/icons/tab-options.svg diff --git a/tabby-core/src/components/tabHeader.component.pug b/tabby-core/src/components/tabHeader.component.pug index 9a6a91dc..17037998 100644 --- a/tabby-core/src/components/tabHeader.component.pug +++ b/tabby-core/src/components/tabHeader.component.pug @@ -13,13 +13,17 @@ profile-icon( .name( [title]='tab.customTitle || tab.title', - [class.no-hover]='config.store.terminal.hideCloseButton' + [class.no-hover]='config.store.terminal.hideCloseButton && config.store.terminal.hideTabOptionsButton' cdkDrag, cdkDragRootElement='tab-header', [cdkDragData]='tab', (cdkDragStarted)='onTabDragStart(tab)', (cdkDragEnded)='onTabDragEnd()', ) {{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 diff --git a/tabby-core/src/components/tabHeader.component.scss b/tabby-core/src/components/tabHeader.component.scss index f09eff52..12683f62 100644 --- a/tabby-core/src/components/tabHeader.component.scss +++ b/tabby-core/src/components/tabHeader.component.scss @@ -59,41 +59,49 @@ $tabs-height: 38px; margin-left: 10px; } - button { - display: block; - flex: none; - background: transparent; - opacity: 0; - -webkit-app-region: no-drag; - + .buttons { + display: flex; position: absolute; - right: 0; + align-items: center; + height: 100%; + top: 2px; + right: 3px; + opacity: 0; - $button-size: 26px; - width: $button-size; - height: $button-size; - border-radius: $button-size / 6; - line-height: $button-size; - align-self: center; + button { + display: flex; + align-items: center; + flex: none; + justify-content: center; - text-align: center; - font-size: 20px; + background: transparent; + -webkit-app-region: no-drag; - &:focus { - outline: 0; + $button-size: 26px; + 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) { - -webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, black 100%); - -webkit-mask-size: calc(100% - 60px) auto, 60px auto; + -webkit-mask-image: linear-gradient(black 0 0), linear-gradient(to left, transparent 0%, transparent 50%, black 100%); + -webkit-mask-size: calc(100% - 80px) auto, 80px auto; -webkit-mask-repeat: no-repeat; -webkit-mask-position: left, right; } - &:hover button { + &:hover .buttons { transition: 0.25s opacity; - display: block; opacity: 1; } diff --git a/tabby-core/src/components/tabHeader.component.ts b/tabby-core/src/components/tabHeader.component.ts index dff41661..0239fdb3 100644 --- a/tabby-core/src/components/tabHeader.component.ts +++ b/tabby-core/src/components/tabHeader.component.ts @@ -5,6 +5,7 @@ import { auditTime } from 'rxjs' import { TabContextMenuItemProvider } from '../api/tabContextMenuProvider' import { BaseTabComponent } from './baseTab.component' import { RenameTabModalComponent } from './renameTabModal.component' +import { SplitTabComponent } from './splitTab.component' import { HotkeysService } from '../services/hotkeys.service' import { AppService } from '../services/app.service' import { HostAppService, Platform } from '../api/hostApp' @@ -69,10 +70,24 @@ export class TabHeaderComponent extends BaseComponent { async buildContextMenu (): Promise { let items: MenuItemOptions[] = [] + // Top-level tab menu for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) { items.push({ type: 'separator' }) 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) } diff --git a/tabby-core/src/icons/tab-options.svg b/tabby-core/src/icons/tab-options.svg new file mode 100644 index 00000000..aa7f8d78 --- /dev/null +++ b/tabby-core/src/icons/tab-options.svg @@ -0,0 +1 @@ + diff --git a/tabby-core/src/tabContextMenu.ts b/tabby-core/src/tabContextMenu.ts index d45b65e0..63b785a7 100644 --- a/tabby-core/src/tabContextMenu.ts +++ b/tabby-core/src/tabContextMenu.ts @@ -41,7 +41,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider { }, }, ] - if (tabHeader) { + if (!tab.parent) { items = [ ...items, { @@ -69,24 +69,22 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider { }, }, ] - } else { - if (tab.parent instanceof SplitTabComponent) { - const directions: SplitDirection[] = ['r', 'b', 'l', 't'] - items.push({ - label: this.translate.instant('Split'), - submenu: directions.map(dir => ({ - label: { - r: this.translate.instant('Right'), - b: this.translate.instant('Down'), - l: this.translate.instant('Left'), - t: this.translate.instant('Up'), - }[dir], - click: () => { - (tab.parent as SplitTabComponent).splitTab(tab, dir) - }, - })) as MenuItemOptions[], - }) - } + } else if (tab.parent instanceof SplitTabComponent) { + const directions: SplitDirection[] = ['r', 'b', 'l', 't'] + items.push({ + label: this.translate.instant('Split'), + submenu: directions.map(dir => ({ + label: { + r: this.translate.instant('Right'), + b: this.translate.instant('Down'), + l: this.translate.instant('Left'), + t: this.translate.instant('Up'), + }[dir], + click: () => { + (tab.parent as SplitTabComponent).splitTab(tab, dir) + }, + })) as MenuItemOptions[], + }) } return items } @@ -273,9 +271,9 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider { tab.destroy() } - async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + async getItems (tab: BaseTabComponent): Promise { - if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) { + if (tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) { return [ { label: this.translate.instant('Switch profile'), diff --git a/tabby-settings/src/components/windowSettingsTab.component.pug b/tabby-settings/src/components/windowSettingsTab.component.pug index b90752f1..bf46bacf 100644 --- a/tabby-settings/src/components/windowSettingsTab.component.pug +++ b/tabby-settings/src/components/windowSettingsTab.component.pug @@ -295,6 +295,15 @@ h3.mt-4(translate) Tabs (ngModelChange)='config.save();', ) +.form-line + .header + .title(translate) Hide tab options button + + toggle( + [(ngModel)]='config.store.terminal.hideTabOptionsButton', + (ngModelChange)='config.save();', + ) + .form-line .header .title(translate) Hide tab close button diff --git a/tabby-terminal/src/config.ts b/tabby-terminal/src/config.ts index 4b8b9954..0d6e0c8a 100644 --- a/tabby-terminal/src/config.ts +++ b/tabby-terminal/src/config.ts @@ -22,6 +22,7 @@ export class TerminalConfigProvider extends ConfigProvider { hideTabIndex: false, showTabProfileIcon: false, hideCloseButton: false, + hideTabOptionsButton: false, rightClick: 'menu', pasteOnMiddleClick: true, copyOnSelect: false, diff --git a/tabby-terminal/src/tabContextMenu.ts b/tabby-terminal/src/tabContextMenu.ts index ac4ab296..7ac90eea 100644 --- a/tabby-terminal/src/tabContextMenu.ts +++ b/tabby-terminal/src/tabContextMenu.ts @@ -48,6 +48,14 @@ export class MiscContextMenu extends TabContextMenuItemProvider { constructor (private translate: TranslateService) { super() } async getItems (tab: BaseTabComponent): Promise { + 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()) { return [{ label: this.translate.instant('Copy current path'),