From 559c6751645eff03f366847745e6894cab821eb7 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 31 May 2019 11:18:33 -0700 Subject: [PATCH] Hide results tab when there are none (#5763) * wip * add behavior around hiding results when there are none * fix strict null access --- src/sql/base/browser/ui/panel/panel.ts | 43 ++++++++++++------- .../parts/query/browser/queryResultsView.ts | 38 +++++++++++++++- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index eaab021b1e..f9a1d61cc6 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -13,6 +13,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import * as map from 'vs/base/common/map'; export interface ITabbedPanelStyles { titleActiveForeground?: Color; @@ -39,7 +41,8 @@ export interface IPanelTab { view: IPanelView; } -interface IInternalPanelTab extends IPanelTab { +interface IInternalPanelTab { + tab: IPanelTab; header: HTMLElement; disposables: IDisposable[]; label: HTMLElement; @@ -108,11 +111,11 @@ export class TabbedPanel extends Disposable { return this._tabMap.has(tab.identifier); } - public pushTab(tab: IPanelTab): PanelTabIdentifier { - let internalTab = tab as IInternalPanelTab; + public pushTab(tab: IPanelTab, index?: number): PanelTabIdentifier { + let internalTab = { tab } as IInternalPanelTab; internalTab.disposables = []; this._tabMap.set(tab.identifier, internalTab); - this._createTab(internalTab); + this._createTab(internalTab, index); if (!this._shownTabId) { this.showTab(tab.identifier); } @@ -132,26 +135,31 @@ export class TabbedPanel extends Disposable { this._actionbar.context = context; } - private _createTab(tab: IInternalPanelTab): void { + private _createTab(tab: IInternalPanelTab, index?: number): void { let tabHeaderElement = DOM.$('.tab-header'); tabHeaderElement.setAttribute('tabindex', '0'); tabHeaderElement.setAttribute('role', 'tab'); tabHeaderElement.setAttribute('aria-selected', 'false'); - tabHeaderElement.setAttribute('aria-controls', tab.identifier); + tabHeaderElement.setAttribute('aria-controls', tab.tab.identifier); let tabElement = DOM.$('.tab'); tabHeaderElement.appendChild(tabElement); let tabLabel = DOM.$('a.tabLabel'); - tabLabel.innerText = tab.title; + tabLabel.innerText = tab.tab.title; tabElement.appendChild(tabLabel); - tab.disposables.push(DOM.addDisposableListener(tabHeaderElement, DOM.EventType.CLICK, e => this.showTab(tab.identifier))); + tab.disposables.push(DOM.addDisposableListener(tabHeaderElement, DOM.EventType.CLICK, e => this.showTab(tab.tab.identifier))); tab.disposables.push(DOM.addDisposableListener(tabHeaderElement, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter)) { - this.showTab(tab.identifier); + this.showTab(tab.tab.identifier); e.stopImmediatePropagation(); } })); - this.tabList.appendChild(tabHeaderElement); + const insertBefore = !isUndefinedOrNull(index) ? this.tabList.children.item(index) : undefined; + if (insertBefore) { + this.tabList.insertBefore(tabHeaderElement, insertBefore); + } else { + this.tabList.append(tabHeaderElement); + } tab.header = tabHeaderElement; tab.label = tabLabel; } @@ -178,10 +186,10 @@ export class TabbedPanel extends Disposable { tab.body = DOM.$('.tab-container'); tab.body.style.width = '100%'; tab.body.style.height = '100%'; - tab.view.render(tab.body); + tab.tab.view.render(tab.body); } this.body.appendChild(tab.body); - this.body.setAttribute('aria-labelledby', tab.identifier); + this.body.setAttribute('aria-labelledby', tab.tab.identifier); DOM.addClass(tab.label, 'active'); DOM.addClass(tab.header, 'active'); tab.header.setAttribute('aria-selected', 'true'); @@ -197,8 +205,8 @@ export class TabbedPanel extends Disposable { if (!actualTab) { return; } - if (actualTab.view && actualTab.view.remove) { - actualTab.view.remove(); + if (actualTab.tab.view && actualTab.tab.view.remove) { + actualTab.tab.view.remove(); } if (actualTab.header && actualTab.header.remove) { actualTab.header.remove(); @@ -218,6 +226,9 @@ export class TabbedPanel extends Disposable { } } } + if (!this._shownTabId && this._tabMap.size > 0) { + this.showTab(map.values(this._tabMap)[0].tab.identifier); + } } if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) { @@ -302,7 +313,7 @@ export class TabbedPanel extends Disposable { if (tab) { tab.body.style.width = dimension.width + 'px'; tab.body.style.height = dimension.height + 'px'; - tab.view.layout(dimension); + tab.tab.view.layout(dimension); } } } @@ -311,7 +322,7 @@ export class TabbedPanel extends Disposable { if (this._shownTabId) { const tab = this._tabMap.get(this._shownTabId); if (tab) { - tab.view.focus(); + tab.tab.view.focus(); } } } diff --git a/src/sql/workbench/parts/query/browser/queryResultsView.ts b/src/sql/workbench/parts/query/browser/queryResultsView.ts index 39e738bf61..50eca21400 100644 --- a/src/sql/workbench/parts/query/browser/queryResultsView.ts +++ b/src/sql/workbench/parts/query/browser/queryResultsView.ts @@ -181,7 +181,7 @@ export class QueryResultsView extends Disposable { this.resultsTab = this._register(new ResultsTab(instantiationService)); this.messagesTab = this._register(new MessagesTab(instantiationService)); this.chartTab = this._register(new ChartTab(instantiationService)); - this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false })); + this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: true })); this._register(attachTabbedPanelStyler(this._panelView, themeService)); this.qpTab = this._register(new QueryPlanTab()); this.topOperationsTab = this._register(new TopOperationsTab(instantiationService)); @@ -195,17 +195,40 @@ export class QueryResultsView extends Disposable { })); } + private hasResults(runner: QueryRunner): boolean { + let hasResults = false; + for (const batch of runner.batchSets) { + if (batch.resultSetSummaries.length > 0) { + hasResults = true; + break; + } + } + return hasResults; + } + private setQueryRunner(runner: QueryRunner) { + if (runner.hasCompleted && !this.hasResults(runner)) { + this.hideResults(); + } else { + this.showResults(); + } this.resultsTab.queryRunner = runner; this.messagesTab.queryRunner = runner; this.chartTab.queryRunner = runner; this.runnerDisposables.push(runner.onQueryStart(e => { + this.showResults(); this.hideChart(); this.hidePlan(); this.hideDynamicViewModelTabs(); this.input.state.visibleTabs = new Set(); this.input.state.activeTab = this.resultsTab.identifier; })); + this.runnerDisposables.push(runner.onQueryEnd(() => { + if (!this.hasResults(runner)) { + this.hideResults(); + } + })); + if (this.input.state.visibleTabs.has(this.chartTab.identifier) && !this._panelView.contains(this.chartTab)) { this._panelView.pushTab(this.chartTab); } else if (!this.input.state.visibleTabs.has(this.chartTab.identifier) && this._panelView.contains(this.chartTab)) { @@ -311,6 +334,19 @@ export class QueryResultsView extends Disposable { } } + public hideResults() { + if (this._panelView.contains(this.resultsTab)) { + this._panelView.removeTab(this.resultsTab.identifier); + } + } + + public showResults() { + if (!this._panelView.contains(this.resultsTab)) { + this._panelView.pushTab(this.resultsTab, 0); + } + this._panelView.showTab(this.resultsTab.identifier); + } + public showPlan(xml: string) { this.input.state.visibleTabs.add(this.qpTab.identifier); if (!this._panelView.contains(this.qpTab)) {