diff --git a/src/sql/parts/grid/views/query/query.component.html b/src/sql/parts/grid/views/query/query.component.html index 150dee3124..b7a6124cc3 100644 --- a/src/sql/parts/grid/views/query/query.component.html +++ b/src/sql/parts/grid/views/query/query.component.html @@ -10,9 +10,9 @@ {{LocalizedConstants.resultPaneLabel}} {{resultShortcut}} -
+
= new EventEmitter(); public goToNextQueryOutputTabRequested: EventEmitter = new EventEmitter(); + private savedViewState: { + gridSelections: ISlickRange[][]; + resultsScroll: number; + messagePaneScroll: number; + slickGridScrolls: { vertical: number; horizontal: number }[]; + }; + @Input() public queryParameters: IQueryComponentParams; @ViewChildren('slickgrid') slickgrids: QueryList; @@ -168,6 +175,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes @ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef; @ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef; @ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef; + @ViewChild('resultsScrollBox', { read: ElementRef }) private _resultsScrollBox: ElementRef; + @ViewChildren('slickgrid', { read: ElementRef }) private _slickgridElements: QueryList; constructor( @Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef, @@ -225,6 +234,10 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes } self._cd.detectChanges(); }); + + this.queryParameters.onSaveViewState(() => this.saveViewState()); + this.queryParameters.onRestoreViewState(() => this.restoreViewState()); + 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() { this.resizeGrids(); } diff --git a/src/sql/parts/query/editor/queryEditor.ts b/src/sql/parts/query/editor/queryEditor.ts index 633dbddb06..8619622ff5 100644 --- a/src/sql/parts/query/editor/queryEditor.ts +++ b/src/sql/parts/query/editor/queryEditor.ts @@ -32,6 +32,7 @@ import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { Emitter } from 'vs/base/common/event'; import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; import { QueryInput } from 'sql/parts/query/common/queryInput'; @@ -90,6 +91,7 @@ export class QueryEditor extends BaseEditor { private _parseSyntaxAction: ParseSyntaxAction; private _savedViewStates = new Map(); + private _resultViewStateChangeEmitters = new Map; onRestoreViewState: Emitter }>(); constructor( @ITelemetryService _telemetryService: ITelemetryService, @@ -509,6 +511,11 @@ export class QueryEditor extends BaseEditor { } if (oldInput) { + let resultViewStateChangeEmitters = this._resultViewStateChangeEmitters.get(oldInput.results); + if (resultViewStateChangeEmitters) { + resultViewStateChangeEmitters.onSaveViewState.fire(); + } + this._disposeEditors(); } @@ -583,6 +590,9 @@ export class QueryEditor extends BaseEditor { .then(onEditorsCreated) .then(doLayout) .then(() => { + if (this._resultViewStateChangeEmitters.has(newInput.results)) { + this._resultViewStateChangeEmitters.get(newInput.results).onRestoreViewState.fire(); + } if (this._savedViewStates.has(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 { this._resultsEditor = resultsEditor; + if (!this._resultViewStateChangeEmitters.has(resultsInput)) { + this._resultViewStateChangeEmitters.set(resultsInput, { + onRestoreViewState: new Emitter(), + onSaveViewState: new Emitter() + }); + } + let emitters = this._resultViewStateChangeEmitters.get(resultsInput); + this._resultsEditor.setViewStateChangeEvents(emitters.onRestoreViewState.event, emitters.onSaveViewState.event); return this._resultsEditor.setInput(resultsInput, options); } diff --git a/src/sql/parts/query/editor/queryResultsEditor.ts b/src/sql/parts/query/editor/queryResultsEditor.ts index 34df4508bb..6c9af8ce54 100644 --- a/src/sql/parts/query/editor/queryResultsEditor.ts +++ b/src/sql/parts/query/editor/queryResultsEditor.ts @@ -26,6 +26,7 @@ import { IQueryComponentParams } from 'sql/services/bootstrap/bootstrapParams'; import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module'; import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; export const RESULTS_GRID_DEFAULTS = { cellPadding: [6, 10, 5], @@ -95,6 +96,8 @@ export class QueryResultsEditor extends BaseEditor { public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer'; protected _rawOptions: BareResultsGridInfo; protected _input: QueryResultsInput; + private _restoreViewStateEvent: Event; + private _saveViewStateEvent: Event; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -149,6 +152,11 @@ export class QueryResultsEditor extends BaseEditor { return TPromise.wrap(null); } + public setViewStateChangeEvents(onRestoreViewStateEvent: Event, onSaveViewStateEvent: Event) { + this._restoreViewStateEvent = onRestoreViewStateEvent; + this._saveViewStateEvent = onSaveViewStateEvent; + } + /** * 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. // Otherwise many components will be left around and be subscribed // 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, QueryOutputModule, this.getContainer(), diff --git a/src/sql/services/bootstrap/bootstrapParams.ts b/src/sql/services/bootstrap/bootstrapParams.ts index d3f9d9521a..4c4f25b6fa 100644 --- a/src/sql/services/bootstrap/bootstrapParams.ts +++ b/src/sql/services/bootstrap/bootstrapParams.ts @@ -8,9 +8,12 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; import { IBootstrapParams } from './bootstrapService'; +import { Event } from 'vs/base/common/event'; export interface IQueryComponentParams extends IBootstrapParams { dataService: DataService; + onSaveViewState: Event; + onRestoreViewState: Event; } export interface IEditDataComponentParams extends IBootstrapParams {