Save query result selection/scroll when switching tabs (#2052)

This commit is contained in:
Matt Irvine
2018-07-27 14:01:34 -07:00
committed by GitHub
parent 10eeb5374f
commit e9ef95ef1f
5 changed files with 88 additions and 5 deletions

View File

@@ -10,9 +10,9 @@
<span> {{LocalizedConstants.resultPaneLabel}} </span> <span> {{LocalizedConstants.resultPaneLabel}} </span>
<span class="queryResultsShortCut"> {{resultShortcut}} </span> <span class="queryResultsShortCut"> {{resultShortcut}} </span>
</div> </div>
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable" <div #resultsScrollBox id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
(onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive" (onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive"
(focusin)="onGridFocus()" (focusout)="onGridFocusout()"> (focusin)="onGridFocus()" (focusout)="onGridFocusout()">
<div class="boxRow content horzBox slickgrid" *ngFor="let dataSet of renderedDataSets; let i = index" <div class="boxRow content horzBox slickgrid" *ngFor="let dataSet of renderedDataSets; let i = index"
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight"> [style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions" <slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"

View File

@@ -15,7 +15,7 @@ import {
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
ViewChildren, forwardRef, EventEmitter, Input, ViewChild ViewChildren, forwardRef, EventEmitter, Input, ViewChild
} from '@angular/core'; } from '@angular/core';
import { IGridDataRow, SlickGrid, VirtualizedCollection } from 'angular2-slickgrid'; import { IGridDataRow, SlickGrid, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants'; import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
import * as Services from 'sql/parts/grid/services/sharedServices'; import * as Services from 'sql/parts/grid/services/sharedServices';
@@ -161,6 +161,13 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>(); public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>();
public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>(); public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>();
private savedViewState: {
gridSelections: ISlickRange[][];
resultsScroll: number;
messagePaneScroll: number;
slickGridScrolls: { vertical: number; horizontal: number }[];
};
@Input() public queryParameters: IQueryComponentParams; @Input() public queryParameters: IQueryComponentParams;
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>; @ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
@@ -168,6 +175,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
@ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef; @ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef;
@ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef; @ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef;
@ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef; @ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef;
@ViewChild('resultsScrollBox', { read: ElementRef }) private _resultsScrollBox: ElementRef;
@ViewChildren('slickgrid', { read: ElementRef }) private _slickgridElements: QueryList<ElementRef>;
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
@@ -225,6 +234,10 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
} }
self._cd.detectChanges(); self._cd.detectChanges();
}); });
this.queryParameters.onSaveViewState(() => this.saveViewState());
this.queryParameters.onRestoreViewState(() => this.restoreViewState());
this.dataService.onAngularLoaded(); this.dataService.onAngularLoaded();
} }
@@ -651,6 +664,43 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
} }
} }
private saveViewState(): void {
let gridSelections = this.slickgrids.map(grid => grid.getSelectedRanges());
let resultsScrollElement = (this._resultsScrollBox.nativeElement as HTMLElement);
let resultsScroll = resultsScrollElement.scrollTop;
let messagePaneScroll = (this._messagesContainer.nativeElement as HTMLElement).scrollTop;
let slickGridScrolls = this._slickgridElements.map(element => {
// Get the slick grid's viewport element and save its scroll position
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
return {
vertical: scrollElement.scrollTop,
horizontal: scrollElement.scrollLeft
};
});
this.savedViewState = {
gridSelections,
messagePaneScroll,
resultsScroll,
slickGridScrolls
};
}
private restoreViewState(): void {
if (this.savedViewState) {
this.slickgrids.forEach((grid, index) => grid.selection = this.savedViewState.gridSelections[index]);
(this._resultsScrollBox.nativeElement as HTMLElement).scrollTop = this.savedViewState.resultsScroll;
(this._messagesContainer.nativeElement as HTMLElement).scrollTop = this.savedViewState.messagePaneScroll;
this._slickgridElements.forEach((element, index) => {
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
let savedScroll = this.savedViewState.slickGridScrolls[index];
scrollElement.scrollTop = savedScroll.vertical;
scrollElement.scrollLeft = savedScroll.horizontal;
});
this.savedViewState = undefined;
}
}
layout() { layout() {
this.resizeGrids(); this.resizeGrids();
} }

View File

@@ -32,6 +32,7 @@ import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { Emitter } from 'vs/base/common/event';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput'; import { QueryInput } from 'sql/parts/query/common/queryInput';
@@ -90,6 +91,7 @@ export class QueryEditor extends BaseEditor {
private _parseSyntaxAction: ParseSyntaxAction; private _parseSyntaxAction: ParseSyntaxAction;
private _savedViewStates = new Map<IEditorInput, IEditorViewState>(); private _savedViewStates = new Map<IEditorInput, IEditorViewState>();
private _resultViewStateChangeEmitters = new Map<QueryResultsInput, { onSaveViewState: Emitter<void>; onRestoreViewState: Emitter<void> }>();
constructor( constructor(
@ITelemetryService _telemetryService: ITelemetryService, @ITelemetryService _telemetryService: ITelemetryService,
@@ -509,6 +511,11 @@ export class QueryEditor extends BaseEditor {
} }
if (oldInput) { if (oldInput) {
let resultViewStateChangeEmitters = this._resultViewStateChangeEmitters.get(oldInput.results);
if (resultViewStateChangeEmitters) {
resultViewStateChangeEmitters.onSaveViewState.fire();
}
this._disposeEditors(); this._disposeEditors();
} }
@@ -583,6 +590,9 @@ export class QueryEditor extends BaseEditor {
.then(onEditorsCreated) .then(onEditorsCreated)
.then(doLayout) .then(doLayout)
.then(() => { .then(() => {
if (this._resultViewStateChangeEmitters.has(newInput.results)) {
this._resultViewStateChangeEmitters.get(newInput.results).onRestoreViewState.fire();
}
if (this._savedViewStates.has(newInput.sql)) { if (this._savedViewStates.has(newInput.sql)) {
this._sqlEditor.getControl().restoreViewState(this._savedViewStates.get(newInput.sql)); this._sqlEditor.getControl().restoreViewState(this._savedViewStates.get(newInput.sql));
} }
@@ -617,6 +627,14 @@ export class QueryEditor extends BaseEditor {
*/ */
private _onResultsEditorCreated(resultsEditor: QueryResultsEditor, resultsInput: QueryResultsInput, options: EditorOptions): TPromise<void> { private _onResultsEditorCreated(resultsEditor: QueryResultsEditor, resultsInput: QueryResultsInput, options: EditorOptions): TPromise<void> {
this._resultsEditor = resultsEditor; this._resultsEditor = resultsEditor;
if (!this._resultViewStateChangeEmitters.has(resultsInput)) {
this._resultViewStateChangeEmitters.set(resultsInput, {
onRestoreViewState: new Emitter<void>(),
onSaveViewState: new Emitter<void>()
});
}
let emitters = this._resultViewStateChangeEmitters.get(resultsInput);
this._resultsEditor.setViewStateChangeEvents(emitters.onRestoreViewState.event, emitters.onSaveViewState.event);
return this._resultsEditor.setInput(resultsInput, options); return this._resultsEditor.setInput(resultsInput, options);
} }

View File

@@ -26,6 +26,7 @@ import { IQueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module'; import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module';
import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component'; import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
export const RESULTS_GRID_DEFAULTS = { export const RESULTS_GRID_DEFAULTS = {
cellPadding: [6, 10, 5], cellPadding: [6, 10, 5],
@@ -95,6 +96,8 @@ export class QueryResultsEditor extends BaseEditor {
public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer'; public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer';
protected _rawOptions: BareResultsGridInfo; protected _rawOptions: BareResultsGridInfo;
protected _input: QueryResultsInput; protected _input: QueryResultsInput;
private _restoreViewStateEvent: Event<void>;
private _saveViewStateEvent: Event<void>;
constructor( constructor(
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@@ -149,6 +152,11 @@ export class QueryResultsEditor extends BaseEditor {
return TPromise.wrap<void>(null); return TPromise.wrap<void>(null);
} }
public setViewStateChangeEvents(onRestoreViewStateEvent: Event<void>, onSaveViewStateEvent: Event<void>) {
this._restoreViewStateEvent = onRestoreViewStateEvent;
this._saveViewStateEvent = onSaveViewStateEvent;
}
/** /**
* Load the angular components and record for this input that we have done so * Load the angular components and record for this input that we have done so
*/ */
@@ -169,7 +177,11 @@ export class QueryResultsEditor extends BaseEditor {
// Note: pass in input so on disposal this is cleaned up. // Note: pass in input so on disposal this is cleaned up.
// Otherwise many components will be left around and be subscribed // Otherwise many components will be left around and be subscribed
// to events from the backing data service // to events from the backing data service
let params: IQueryComponentParams = { dataService: dataService }; let params: IQueryComponentParams = {
dataService: dataService,
onSaveViewState: this._saveViewStateEvent,
onRestoreViewState: this._restoreViewStateEvent
};
bootstrapAngular(this._instantiationService, bootstrapAngular(this._instantiationService,
QueryOutputModule, QueryOutputModule,
this.getContainer(), this.getContainer(),

View File

@@ -8,9 +8,12 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
import { IBootstrapParams } from './bootstrapService'; import { IBootstrapParams } from './bootstrapService';
import { Event } from 'vs/base/common/event';
export interface IQueryComponentParams extends IBootstrapParams { export interface IQueryComponentParams extends IBootstrapParams {
dataService: DataService; dataService: DataService;
onSaveViewState: Event<void>;
onRestoreViewState: Event<void>;
} }
export interface IEditDataComponentParams extends IBootstrapParams { export interface IEditDataComponentParams extends IBootstrapParams {