diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f02bea945acf..c443ddb37ed07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ ## v1.4.0 - [core] fixed handling of environment variables on Windows [#7973](https://github.com/eclipse-theia/theia/pull/7973) +- [core] added functionality for handling context menu for `tab-bars` without activating the shell tab-bar [#6965](https://github.com/eclipse-theia/theia/pull/6965) + +Breaking Changes: + +- [core] Context-menu for `tab-bars` requires an `Event` to be passed onto it to perform actions without activating the shell tab-bar [#6965](https://github.com/eclipse-theia/theia/pull/6965) + - Removed the logic from `handleContextMenuEvent()` that gives focus to the widget upon opening the context menu, instead functionality added to handle it without activating the widget + - This change triggers the context menu for a given shell tab-bar without the need to activate it + - While registering a command, `Event` should be passed down, if not passed, then the commands will not work correctly as they no longer rely on the activation of tab-bar +- [core] Moved `findTitle()` and `findTabBar()` from `common-frontend-contribution.ts` to `application-shell.ts` [#6965](https://github.com/eclipse-theia/theia/pull/6965) ## v1.3.0 diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index ed581ee9fa406..79002fbef9dcb 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -591,55 +591,56 @@ export class CommonFrontendContribution implements FrontendApplicationContributi }); commandRegistry.registerCommand(CommonCommands.CLOSE_TAB, { isEnabled: (event?: Event) => { - const tabBar = this.findTabBar(event); + const tabBar = this.shell.findTabBar(event); if (!tabBar) { return false; } - const currentTitle = this.findTitle(tabBar, event); + const currentTitle = this.shell.findTitle(tabBar, event); return currentTitle !== undefined && currentTitle.closable; }, execute: (event?: Event) => { - const tabBar = this.findTabBar(event)!; - const currentTitle = this.findTitle(tabBar, event); + const tabBar = this.shell.findTabBar(event)!; + const currentTitle = this.shell.findTitle(tabBar, event); this.shell.closeTabs(tabBar, title => title === currentTitle); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_TABS, { isEnabled: (event?: Event) => { - const tabBar = this.findTabBar(event); + const tabBar = this.shell.findTabBar(event); if (!tabBar) { return false; } - const currentTitle = this.findTitle(tabBar, event); + const currentTitle = this.shell.findTitle(tabBar, event); return tabBar.titles.some(title => title !== currentTitle && title.closable); }, execute: (event?: Event) => { - const tabBar = this.findTabBar(event)!; - const currentTitle = this.findTitle(tabBar, event); + const tabBar = this.shell.findTabBar(event)!; + const currentTitle = this.shell.findTitle(tabBar, event); this.shell.closeTabs(tabBar, title => title !== currentTitle && title.closable); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_RIGHT_TABS, { isEnabled: (event?: Event) => { - const tabBar = this.findTabBar(event); - return tabBar !== undefined && tabBar.titles.some((title, index) => index > tabBar.currentIndex && title.closable); + const tabBar = this.shell.findTabBar(event)!; + const currentIndex = this.targetTitleIndex(event); + return tabBar !== undefined && tabBar.titles.some((title, index) => index > currentIndex && title.closable); }, isVisible: (event?: Event) => { const area = this.findTabArea(event); return area !== undefined && area !== 'left' && area !== 'right'; }, execute: (event?: Event) => { - const tabBar = this.findTabBar(event)!; - const currentIndex = tabBar.currentIndex; + const tabBar = this.shell.findTabBar(event)!; + const currentIndex = this.targetTitleIndex(event); this.shell.closeTabs(tabBar, (title, index) => index > currentIndex && title.closable); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_TABS, { isEnabled: (event?: Event) => { - const tabBar = this.findTabBar(event); + const tabBar = this.shell.findTabBar(event); return tabBar !== undefined && tabBar.titles.some(title => title.closable); }, - execute: (event?: Event) => this.shell.closeTabs(this.findTabBar(event)!, title => title.closable) + execute: (event?: Event) => this.shell.closeTabs(this.shell.findTabBar(event)!, title => title.closable) }); commandRegistry.registerCommand(CommonCommands.CLOSE_MAIN_TAB, { isEnabled: () => { @@ -686,9 +687,9 @@ export class CommonFrontendContribution implements FrontendApplicationContributi } }); commandRegistry.registerCommand(CommonCommands.TOGGLE_MAXIMIZED, { - isEnabled: () => this.shell.canToggleMaximized(), - isVisible: () => this.shell.canToggleMaximized(), - execute: () => this.shell.toggleMaximized() + isEnabled: (event?: Event) => this.canToggleMaximized(event), + isVisible: (event?: Event) => this.canToggleMaximized(event), + execute: (event?: Event) => this.toggleMaximized(event) }); commandRegistry.registerCommand(CommonCommands.SAVE, { @@ -713,38 +714,62 @@ export class CommonFrontendContribution implements FrontendApplicationContributi }); } - private findTabBar(event?: Event): TabBar | undefined { - if (event && event.target) { - const tabBar = this.shell.findWidgetForElement(event.target as HTMLElement); - if (tabBar instanceof TabBar) { - return tabBar; - } - } - return this.shell.currentTabBar; - } - private findTabArea(event?: Event): ApplicationShell.Area | undefined { - const tabBar = this.findTabBar(event); + const tabBar = this.shell.findTabBar(event); if (tabBar) { return this.shell.getAreaFor(tabBar); } return this.shell.currentTabArea; } - private findTitle(tabBar: TabBar, event?: Event): Title | undefined { + /** + * Evaluates the currentIndex of the title in the array of titles. + * @param event: `event` to be used when searching for the title and the tab-bar. + * + * @returns `currentIndex` if the `targetTitle` is available in the array, else returns the index of currently-selected title. + */ + private targetTitleIndex(event?: Event): number { + const tabBar = this.shell.findTabBar(event)!; + const targetTitle = this.shell.findTitle(tabBar, event); + let currentIndex: number; + if (targetTitle) { + currentIndex = tabBar.titles.indexOf(targetTitle); + } else { + currentIndex = tabBar.currentIndex; + } + return currentIndex; + } + + private canToggleMaximized(event?: Event): boolean { if (event && event.target) { - let tabNode: HTMLElement | null = event.target as HTMLElement; - while (tabNode && !tabNode.classList.contains('p-TabBar-tab')) { - tabNode = tabNode.parentElement; + const widget = this.shell.findWidgetForElement(event.target as HTMLElement); + if (widget) { + return this.shell.mainPanel.contains(widget) || this.shell.bottomPanel.contains(widget); } - if (tabNode && tabNode.title) { - const title = tabBar.titles.find(t => t.label === tabNode!.title); + } + return this.shell.canToggleMaximized(); + } + + private toggleMaximized(event?: Event): void { + if (event && event.target) { + let title: Title | undefined; + const widget = this.shell.findWidgetForElement(event.target as HTMLElement); + if (widget) { + if (widget instanceof TabBar) { + title = this.shell.findTitle(widget, event); + } + if (this.shell.mainPanel.contains(widget)) { + this.shell.mainPanel.toggleMaximized(); + } else if (this.shell.bottomPanel.contains(widget)) { + this.shell.bottomPanel.toggleMaximized(); + } if (title) { - return title; + this.shell.revealWidget(title.owner.id); } } + } else { + this.shell.toggleMaximized(); } - return tabBar.currentTitle || undefined; } private isElectron(): boolean { diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index 89105291f7b69..930688d545a30 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -811,6 +811,36 @@ export class ApplicationShell extends Widget { return result; } + findTitle(tabBar: TabBar, event?: Event): Title | undefined { + if (event && event.target) { + let tabNode: HTMLElement | null = event.target as HTMLElement; + while (tabNode && !tabNode.classList.contains('p-TabBar-tab')) { + tabNode = tabNode.parentElement; + } + if (tabNode && tabNode.title) { + let title = tabBar.titles.find(t => t.caption === tabNode!.title); + if (title) { + return title; + } + title = tabBar.titles.find(t => t.label === tabNode!.title); + if (title) { + return title; + } + } + } + return tabBar.currentTitle || undefined; + } + + findTabBar(event?: Event): TabBar | undefined { + if (event && event.target) { + const tabBar = this.findWidgetForElement(event.target as HTMLElement); + if (tabBar instanceof TabBar) { + return tabBar; + } + } + return this.currentTabBar; + } + /** * The current widget in the application shell. The current widget is the last widget that * was active and not yet closed. See the remarks to `activeWidget` on what _active_ means. diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts index 4a13ab9204ff4..969f015e95fd5 100644 --- a/packages/core/src/browser/shell/tab-bars.ts +++ b/packages/core/src/browser/shell/tab-bars.ts @@ -424,18 +424,6 @@ export class TabBarRenderer extends TabBar.Renderer { if (this.contextMenuRenderer && this.contextMenuPath && event.currentTarget instanceof HTMLElement) { event.stopPropagation(); event.preventDefault(); - - if (this.tabBar) { - const id = event.currentTarget.id; - // eslint-disable-next-line no-null/no-null - const title = this.tabBar.titles.find(t => this.createTabId(t) === id) || null; - this.tabBar.currentTitle = title; - this.tabBar.activate(); - if (title) { - title.owner.activate(); - } - } - this.contextMenuRenderer.render(this.contextMenuPath, event); } }; diff --git a/packages/navigator/src/browser/navigator-contribution.ts b/packages/navigator/src/browser/navigator-contribution.ts index 9ee04c9dc1793..3ab5e3c605ac8 100644 --- a/packages/navigator/src/browser/navigator-contribution.ts +++ b/packages/navigator/src/browser/navigator-contribution.ts @@ -28,7 +28,8 @@ import { PreferenceService, SelectableTreeNode, SHELL_TABBAR_CONTEXT_MENU, - Widget + Widget, + Title } from '@theia/core/lib/browser'; import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution'; import { @@ -241,9 +242,18 @@ export class FileNavigatorContribution extends AbstractViewContribution this.openView({ activate: true }) }); registry.registerCommand(FileNavigatorCommands.REVEAL_IN_NAVIGATOR, { - execute: () => this.openView({ activate: true }).then(() => this.selectWidgetFileNode(this.shell.currentWidget)), - isEnabled: () => Navigatable.is(this.shell.currentWidget), - isVisible: () => Navigatable.is(this.shell.currentWidget) + execute: (event?: Event) => { + const widget = this.getTargetedWidget(event); + this.openView({ activate: true }).then(() => this.selectWidgetFileNode(widget || this.shell.currentWidget)); + }, + isEnabled: (event?: Event) => { + const widget = this.getTargetedWidget(event); + return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget); + }, + isVisible: (event?: Event) => { + const widget = this.getTargetedWidget(event); + return widget ? Navigatable.is(widget) : Navigatable.is(this.shell.currentWidget); + } }); registry.registerCommand(FileNavigatorCommands.TOGGLE_HIDDEN_FILES, { execute: () => { @@ -493,6 +503,22 @@ export class FileNavigatorContribution extends AbstractViewContribution | undefined; + if (event) { + const tab = this.shell.findTabBar(event); + title = tab && this.shell.findTitle(tab, event); + } + const widget = title && title.owner; + return widget; + } + /** * Reveals and selects node in the file navigator to which given widget is related. * Does nothing if given widget undefined or doesn't have related resource.