fix query result grid shortcut keys (#23206)

* fix query result grid shortcut keys

* add comment
This commit is contained in:
Alan Ren
2023-05-24 21:41:00 -07:00
committed by GitHub
parent 55cedb810d
commit e62486bee6
32 changed files with 358 additions and 296 deletions

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Table } from 'sql/base/browser/ui/table/table';
import { TableFilteringEnabledContextKey, InTableContextKey, InQueryResultGridContextKey } from 'sql/workbench/services/componentContext/browser/contextKeys';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IQueryResultGrid } from 'sql/workbench/contrib/query/browser/gridPanel';
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
export const SERVICE_ID = 'componentContextService';
export const IComponentContextService = createDecorator<IComponentContextService>(SERVICE_ID);
export interface ComponentRegistrationResult extends IDisposable {
componentContextKeyService: IContextKeyService;
}
/**
* Service to register components and find out the active component for keybindings.
*/
export interface IComponentContextService {
_serviceBrand: undefined;
/**
* Register a table
* @param table The table to register
* @param contextKeyService The context key service to use for the table. If not provided, the global context key service will be used.
*/
registerTable(table: Table<any>, contextKeyService?: IContextKeyService): ComponentRegistrationResult;
/**
* Get the table that has the focus.
*/
getActiveTable(): Table<any> | undefined;
/**
* Register a query result grid
* @param grid The grid to register
* @param contextKeyService The context key service to use for the grid. If not provided, the global context key service will be used.
*/
registerQueryResultGrid(grid: IQueryResultGrid, contextKeyService?: IContextKeyService): ComponentRegistrationResult;
/**
* Get the grid that has the focus.
*/
getActiveQueryResultGrid(): IQueryResultGrid | undefined;
}
enum ComponentType {
Table = 'Table',
QueryResultGrid = 'QueryResultGrid'
}
export class ComponentContextService implements IComponentContextService {
_serviceBrand: undefined;
private _components: Map<number, { type: ComponentType, component: any }> = new Map<number, { type: ComponentType, component: any }>();
private _currentId: number = 1;
constructor(@IContextKeyService private readonly _contextKeyService: IContextKeyService) { }
public registerTable(table: Table<any>, contextKeyService?: IContextKeyService): ComponentRegistrationResult {
return this.registerComponent(ComponentType.Table, table, table.grid.getContainerNode(), contextKeyService, (service) => {
InTableContextKey.bindTo(service).set(true);
TableFilteringEnabledContextKey.bindTo(service).set(table.grid.getPlugins().find(p => p instanceof HeaderFilter) !== undefined)
});
}
public getActiveTable(): Table<any> | undefined {
return this.getActiveComponent<Table<any>>(ComponentType.Table, (table) => table.grid.getContainerNode());
}
public registerQueryResultGrid(grid: IQueryResultGrid, contextKeyService?: IContextKeyService): ComponentRegistrationResult {
return this.registerComponent(ComponentType.QueryResultGrid, grid, grid.htmlElement, contextKeyService, (service) => {
InQueryResultGridContextKey.bindTo(service).set(true);
});
}
public getActiveQueryResultGrid(): IQueryResultGrid | undefined {
return this.getActiveComponent<IQueryResultGrid>(ComponentType.QueryResultGrid, (grid) => grid.htmlElement);
}
private registerComponent(type: ComponentType, component: any, htmlElement: HTMLElement, contextKeyService: IContextKeyService, contextSetter: (contextKeyService: IContextKeyService) => void): ComponentRegistrationResult {
const parentContextKeyService = contextKeyService ?? this._contextKeyService;
const id = this._currentId++;
this._components.set(id, { type, component });
const service = parentContextKeyService.createScoped(htmlElement);
contextSetter(service);
return {
componentContextKeyService: service,
dispose: () => {
this._components.delete(id);
service.dispose();
}
};
}
/**
* Get the component that has the focus.
* @param type type of the component to look for.
* @param elementGetter function to get the html element of the component.
* @returns the component that has the focus or undefined if no component has the focus.
*/
private getActiveComponent<T>(type: ComponentType, elementGetter: (component: T) => HTMLElement): T | undefined {
for (const item of this._components.values()) {
if (item.type === type && elementGetter(item.component).contains(document.activeElement)) {
return item.component;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
// Table
export const InTableContextKey = new RawContextKey<boolean>('inTable', true);
export const TableFilteringEnabledContextKey = new RawContextKey<boolean>('filteringEnabled', false);
// Query Result Grid
export const InQueryResultGridContextKey = new RawContextKey<boolean>('inQueryResultGrid', true);

View File

@@ -53,7 +53,7 @@ import { IDisposableDataProvider } from 'sql/base/common/dataProvider';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ITableService } from 'sql/workbench/services/table/browser/tableService';
import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService';
const labelDisplay = nls.localize("insights.item", "Item");
const valueDisplay = nls.localize("insights.value", "Value");
@@ -92,7 +92,7 @@ class InsightTableView extends ViewPane {
@ITelemetryService telemetryService: ITelemetryService,
@IAccessibilityService private _accessibilityService: IAccessibilityService,
@IQuickInputService private _quickInputService: IQuickInputService,
@ITableService private readonly _tableService: ITableService
@IComponentContextService private readonly _componentContextService: IComponentContextService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
}
@@ -102,7 +102,7 @@ class InsightTableView extends ViewPane {
columns: this.columns,
dataProvider: this.data
}, this.tableOptions);
this._register(this._tableService.registerTable(this._table));
this._register(this._componentContextService.registerTable(this._table));
}
protected override layoutBody(size: number): void {

View File

@@ -50,7 +50,7 @@ import { MediaDeviceType } from 'sql/workbench/contrib/backup/common/constants';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ITableService } from 'sql/workbench/services/table/browser/tableService';
import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService';
interface FileListElement {
logicalFileName: string;
@@ -163,7 +163,7 @@ export class RestoreDialog extends Modal {
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
@IAccessibilityService private _accessibilityService: IAccessibilityService,
@IQuickInputService private _quickInputService: IQuickInputService,
@ITableService private readonly _tableService: ITableService
@IComponentContextService private readonly _componentContextService: IComponentContextService
) {
super(localize('RestoreDialogTitle', "Restore database"), TelemetryKeys.ModalDialogName.Restore, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasErrors: true, width: 'wide', hasSpinner: true });
// view model
@@ -319,7 +319,7 @@ export class RestoreDialog extends Modal {
this._restorePlanTable.setTableTitle(localize('restorePlan', "Restore plan"));
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
this._register(this._tableService.registerTable(this._restorePlanTable));
this._register(this._componentContextService.registerTable(this._restorePlanTable));
// Content in general tab
const generalTab = DOM.$('.restore-dialog');
@@ -369,7 +369,7 @@ export class RestoreDialog extends Modal {
this._fileListTable = this._register(new Table<FileListElement>(this._fileListTableContainer, this._accessibilityService, this._quickInputService,
{ dataProvider: this._fileListData, columns }, { enableColumnReorder: false }));
this._fileListTable.setSelectionModel(new RowSelectionModel());
this._register(this._tableService.registerTable(this._fileListTable));
this._register(this._componentContextService.registerTable(this._fileListTable));
// Content in options tab
const optionsContentElement = DOM.$('.restore-dialog');

View File

@@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
import { Table } from 'sql/base/browser/ui/table/table';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const InTable = new RawContextKey<boolean>('inTable', true);
export const FilteringEnabled = new RawContextKey<boolean>('filteringEnabled', false);
export class TableContext implements IDisposable {
private _inTable: IContextKey<boolean>;
private _filteringEnabled: IContextKey<boolean>;
constructor(contextKeyService: IContextKeyService, table: Table<Slick.SlickData>) {
this._inTable = InTable.bindTo(contextKeyService);
this._filteringEnabled = FilteringEnabled.bindTo(contextKeyService);
this._inTable.set(true);
this._filteringEnabled.set(table.grid.getPlugins().find(p => p instanceof HeaderFilter) !== undefined);
}
dispose(): void {
}
}

View File

@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Table } from 'sql/base/browser/ui/table/table';
import { TableContext } from 'sql/workbench/services/table/browser/tableContext';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export const SERVICE_ID = 'tableService';
export const ITableService = createDecorator<ITableService>(SERVICE_ID);
/**
* Service to manage the table components used by the application.
*/
export interface ITableService {
_serviceBrand: undefined;
/**
* Register a table
*/
registerTable(table: Table<any>): IDisposable;
/**
* Get the table that has the focus.
*/
getActiveTable(): Table<any> | undefined;
}
export class TableService implements ITableService {
_serviceBrand: undefined;
private _tables: Map<number, Table<any>> = new Map<number, Table<any>>();
private _currentId: number = 1;
constructor(@IContextKeyService private readonly _contextKeyService: IContextKeyService) { }
registerTable(table: Table<any>): IDisposable {
const id = this._currentId++;
this._tables.set(id, table);
const service = this._contextKeyService.createScoped(table.grid.getContainerNode());
const context = new TableContext(service, table);
return {
dispose: () => {
this._tables.delete(id);
service.dispose();
context.dispose();
}
};
}
getActiveTable(): Table<any> | undefined {
for (const table of this._tables.values()) {
if (table?.grid.getContainerNode().contains(document.activeElement)) {
return table;
}
}
return undefined;
}
}