unify context menus (fixes #2054)

This commit is contained in:
Eugene Pankov 2020-01-28 21:39:02 +03:00
parent 286b6cfdde
commit f4f52321f5
7 changed files with 197 additions and 160 deletions

View File

@ -1,4 +1,5 @@
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
export { TabHeaderComponent } from '../components/tabHeader.component'
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'

View File

@ -17,39 +17,49 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
super()
}
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
return [
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
let items = [
{
label: 'Close',
click: () => this.zone.run(() => {
this.app.closeTab(tab, true)
}),
},
{
label: 'Close other tabs',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.filter(x => x !== tab)) {
this.app.closeTab(t, true)
}
}),
},
{
label: 'Close tabs to the right',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
this.app.closeTab(t, true)
}
}),
},
{
label: 'Close tabs to the left',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
this.app.closeTab(t, true)
if (this.app.tabs.includes(tab)) {
this.app.closeTab(tab, true)
} else {
tab.destroy()
}
}),
},
]
if (tabHeader) {
items = [
...items,
{
label: 'Close other tabs',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.filter(x => x !== tab)) {
this.app.closeTab(t, true)
}
}),
},
{
label: 'Close tabs to the right',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
this.app.closeTab(t, true)
}
}),
},
{
label: 'Close tabs to the left',
click: () => this.zone.run(() => {
for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
this.app.closeTab(t, true)
}
}),
},
]
}
return items
}
}
@ -76,28 +86,31 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
}
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
return [
{
label: 'Rename',
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
},
{
label: 'Duplicate',
click: () => this.zone.run(() => this.app.duplicateTab(tab)),
},
{
label: 'Color',
sublabel: COLORS.find(x => x.value === tab.color)!.name,
submenu: COLORS.map(color => ({
label: color.name,
type: 'radio',
checked: tab.color === color.value,
click: () => this.zone.run(() => {
tab.color = color.value
}),
})) as Electron.MenuItemConstructorOptions[],
},
]
if (tabHeader) {
return [
{
label: 'Rename',
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
},
{
label: 'Duplicate',
click: () => this.zone.run(() => this.app.duplicateTab(tab)),
},
{
label: 'Color',
sublabel: COLORS.find(x => x.value === tab.color)!.name,
submenu: COLORS.map(color => ({
label: color.name,
type: 'radio',
checked: tab.color === color.value,
click: () => this.zone.run(() => {
tab.color = color.value
}),
})) as Electron.MenuItemConstructorOptions[],
},
]
}
return []
}
}

View File

@ -3,7 +3,7 @@ import { first } from 'rxjs/operators'
import { ToastrService } from 'ngx-toastr'
import { NgZone, OnInit, OnDestroy, Inject, Injector, Optional, ViewChild, HostBinding, Input, ElementRef } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform, LogService, Logger } from 'terminus-core'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform, LogService, Logger, TabContextMenuItemProvider } from 'terminus-core'
import { BaseSession, SessionsService } from '../services/sessions.service'
import { TerminalFrontendService } from '../services/terminalFrontend.service'
@ -11,7 +11,6 @@ import { TerminalFrontendService } from '../services/terminalFrontend.service'
import { Frontend } from '../frontends/frontend'
import { ResizeEvent } from './interfaces'
import { TerminalDecorator } from './decorator'
import { TerminalContextMenuItemProvider } from './contextMenuProvider'
/** @hidden */
@ -88,7 +87,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
@Inject(ToastrService) protected toastr: ToastrServiceProxy,
protected log: LogService,
@Optional() @Inject(TerminalDecorator) protected decorators: TerminalDecorator[],
@Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[],
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
) {
super()
this.logger = log.create('baseTerminalTab')

View File

@ -2,6 +2,7 @@ import { BaseTerminalTabComponent } from './baseTerminalTab.component'
/**
* Extend to add more terminal context menu items
* @deprecated
*/
export abstract class TerminalContextMenuItemProvider {
weight: number

View File

@ -1,98 +0,0 @@
import { NgZone, Injectable } from '@angular/core'
import { ToastrService } from 'ngx-toastr'
import { ConfigService } from 'terminus-core'
import { UACService } from './services/uac.service'
import { TerminalService } from './services/terminal.service'
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
/** @hidden */
@Injectable()
export class NewTabContextMenu extends TerminalContextMenuItemProvider {
weight = 0
constructor (
public config: ConfigService,
private zone: NgZone,
private terminalService: TerminalService,
private uac: UACService,
) {
super()
}
async getItems (tab: BaseTerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
const profiles = await this.terminalService.getProfiles()
const items: Electron.MenuItemConstructorOptions[] = [
{
label: 'New terminal',
click: () => this.zone.run(() => {
this.terminalService.openTabWithOptions((tab as any).sessionOptions)
}),
},
{
label: 'New with profile',
submenu: profiles.map(profile => ({
label: profile.name,
click: () => this.zone.run(async () => {
const workingDirectory = this.config.store.terminal.alwaysUseWorkingDirectory === true ?
this.config.store.terminal.workingDirectory : await tab.session.getWorkingDirectory()
await this.terminalService.openTab(profile, workingDirectory)
}),
})),
},
]
if (this.uac.isAvailable) {
items.push({
label: 'New admin tab',
submenu: profiles.map(profile => ({
label: profile.name,
click: () => this.zone.run(async () => {
this.terminalService.openTabWithOptions({
...profile.sessionOptions,
runAsAdministrator: true,
})
}),
})),
})
}
return items
}
}
/** @hidden */
@Injectable()
export class CopyPasteContextMenu extends TerminalContextMenuItemProvider {
weight = 1
constructor (
private zone: NgZone,
private toastr: ToastrService,
) {
super()
}
async getItems (tab: BaseTerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
return [
{
label: 'Copy',
click: () => {
this.zone.run(() => {
setTimeout(() => {
tab.frontend.copySelection()
this.toastr.info('Copied')
})
})
},
},
{
label: 'Paste',
click: () => {
this.zone.run(() => tab.paste())
},
},
]
}
}

View File

@ -35,8 +35,7 @@ import { PathDropDecorator } from './pathDrop'
import { TerminalConfigProvider } from './config'
import { TerminalHotkeyProvider } from './hotkeys'
import { HyperColorSchemes } from './colorSchemes'
import { NewTabContextMenu, CopyPasteContextMenu } from './contextMenu'
import { SaveAsProfileContextMenu } from './tabContextMenu'
import { NewTabContextMenu, CopyPasteContextMenu, SaveAsProfileContextMenu, LegacyContextMenu } from './tabContextMenu'
import { ZModemDecorator } from './zmodem'
import { CmderShellProvider } from './shells/cmder'
@ -92,10 +91,10 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
{ provide: TerminalContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
{ provide: TerminalContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: LegacyContextMenu, multi: true },
// For WindowsDefaultShellProvider
PowerShellCoreShellProvider,

View File

@ -1,9 +1,11 @@
import { Injectable, NgZone } from '@angular/core'
import { Injectable, NgZone, Optional, Inject } from '@angular/core'
import { ToastrService } from 'ngx-toastr'
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider } from 'terminus-core'
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent } from 'terminus-core'
import { TerminalTabComponent } from './components/terminalTab.component'
import { UACService } from './services/uac.service'
import { TerminalService } from './services/terminal.service'
import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
/** @hidden */
@Injectable()
@ -12,13 +14,11 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
private config: ConfigService,
private zone: NgZone,
private toastr: ToastrService,
private uac: UACService,
private terminalService: TerminalService,
) {
super()
}
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
if (!(tab instanceof TerminalTabComponent)) {
return []
}
@ -43,18 +43,140 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
},
]
return items
}
}
/** @hidden */
@Injectable()
export class NewTabContextMenu extends TabContextMenuItemProvider {
weight = 10
constructor (
public config: ConfigService,
private zone: NgZone,
private terminalService: TerminalService,
private uac: UACService,
) {
super()
}
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
const profiles = await this.terminalService.getProfiles()
const items: Electron.MenuItemConstructorOptions[] = [
{
label: 'New terminal',
click: () => this.zone.run(() => {
this.terminalService.openTabWithOptions((tab as any).sessionOptions)
}),
},
{
label: 'New with profile',
submenu: profiles.map(profile => ({
label: profile.name,
click: () => this.zone.run(async () => {
let workingDirectory = this.config.store.terminal.workingDirectory
if (this.config.store.terminal.alwaysUseWorkingDirectory !== true && tab instanceof TerminalTabComponent) {
workingDirectory = await tab.session.getWorkingDirectory()
}
await this.terminalService.openTab(profile, workingDirectory)
}),
})),
},
]
if (this.uac.isAvailable) {
items.push({
label: 'New admin tab',
submenu: profiles.map(profile => ({
label: profile.name,
click: () => this.zone.run(async () => {
this.terminalService.openTabWithOptions({
...profile.sessionOptions,
runAsAdministrator: true,
})
}),
})),
})
}
if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) {
items.push({
label: 'Duplicate as administrator',
click: () => this.zone.run(async () => {
this.terminalService.openTabWithOptions({
...tab.sessionOptions,
...(tab as TerminalTabComponent).sessionOptions,
runAsAdministrator: true,
})
}),
})
}
return items
}
}
/** @hidden */
@Injectable()
export class CopyPasteContextMenu extends TabContextMenuItemProvider {
weight = 1
constructor (
private zone: NgZone,
private toastr: ToastrService,
) {
super()
}
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
if (tabHeader) {
return []
}
if (tab instanceof BaseTerminalTabComponent) {
return [
{
label: 'Copy',
click: () => {
this.zone.run(() => {
setTimeout(() => {
(tab as BaseTerminalTabComponent).frontend.copySelection()
this.toastr.info('Copied')
})
})
},
},
{
label: 'Paste',
click: () => {
this.zone.run(() => (tab as BaseTerminalTabComponent).paste())
},
},
]
}
return []
}
}
/** @hidden */
@Injectable()
export class LegacyContextMenu extends TabContextMenuItemProvider {
weight = 1
constructor (
@Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[],
) {
super()
}
async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
if (tab instanceof BaseTerminalTabComponent) {
let items: Electron.MenuItemConstructorOptions[] = []
for (const p of this.contextMenuProviders) {
items = items.concat(await p.getItems(tab))
}
return items
}
return []
}
}