mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 09:35:38 -05:00
Add support for "More Actions" column in Resource Viewer (#13093)
* Add support for "More Actions" column in resource viewer * update provider * remove import * Use menu contribution and make actions column always show * cleanup * move context menu anchor * Comments
This commit is contained in:
@@ -3,34 +3,44 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vs/nls';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDataGridProviderService } from 'sql/workbench/services/dataGridProvider/common/dataGridProviderService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
|
||||
import { getDataGridFormatter } from 'sql/workbench/services/dataGridProvider/browser/dataGridProviderUtils';
|
||||
|
||||
export interface ColumnDefinition extends Slick.Column<Slick.SlickData> {
|
||||
export interface ColumnDefinition extends Slick.Column<azdata.DataGridItem> {
|
||||
name: string;
|
||||
type: string;
|
||||
// actions is a special internal type for the More Actions column
|
||||
type: azdata.DataGridColumnType | 'actions';
|
||||
filterable?: boolean;
|
||||
}
|
||||
|
||||
export class ResourceViewerInput extends EditorInput {
|
||||
|
||||
public static ID: string = 'workbench.editorInput.resourceViewerInput';
|
||||
private _data: Slick.SlickData[] = [];
|
||||
private _data: azdata.DataGridItem[] = [];
|
||||
private _columns: ColumnDefinition[] = [];
|
||||
|
||||
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
|
||||
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
|
||||
private _onColumnsChanged = new Emitter<Slick.Column<azdata.DataGridItem>[]>();
|
||||
public actionsColumn: ButtonColumn<azdata.DataGridItem>;
|
||||
public onColumnsChanged: Event<Slick.Column<azdata.DataGridItem>[]> = this._onColumnsChanged.event;
|
||||
|
||||
private _onDataChanged = new Emitter<void>();
|
||||
public onDataChanged: Event<void> = this._onDataChanged.event;
|
||||
|
||||
constructor(private _providerId: string, @IDataGridProviderService private _dataGridProvider: IDataGridProviderService) {
|
||||
constructor(private _providerId: string,
|
||||
@IDataGridProviderService private _dataGridProviderService: IDataGridProviderService) {
|
||||
super();
|
||||
this.actionsColumn = new ButtonColumn<azdata.DataGridItem>({
|
||||
id: 'actions',
|
||||
iconCssClass: 'toggle-more',
|
||||
title: nls.localize('resourceViewer.showActions', "Show Actions"),
|
||||
sortable: false
|
||||
});
|
||||
this.refresh().catch(err => onUnexpectedError(err));
|
||||
}
|
||||
|
||||
@@ -42,7 +52,7 @@ export class ResourceViewerInput extends EditorInput {
|
||||
return nls.localize('resourceViewerInput.resourceViewer', "Resource Viewer");
|
||||
}
|
||||
|
||||
public get data(): Slick.SlickData[] {
|
||||
public get data(): azdata.DataGridItem[] {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
@@ -70,9 +80,13 @@ export class ResourceViewerInput extends EditorInput {
|
||||
]);
|
||||
}
|
||||
|
||||
public get plugins(): Slick.Plugin<azdata.DataGridItem>[] {
|
||||
return [this.actionsColumn];
|
||||
}
|
||||
|
||||
private async fetchColumns(): Promise<void> {
|
||||
const columns = await this._dataGridProvider.getDataGridColumns(this._providerId);
|
||||
this.columns = columns.map(col => {
|
||||
const columns = await this._dataGridProviderService.getDataGridColumns(this._providerId);
|
||||
const columnDefinitions: ColumnDefinition[] = columns.map(col => {
|
||||
return {
|
||||
name: col.name,
|
||||
field: col.field,
|
||||
@@ -86,10 +100,15 @@ export class ResourceViewerInput extends EditorInput {
|
||||
type: col.type
|
||||
};
|
||||
});
|
||||
|
||||
// Now add in the actions column definition at the end
|
||||
const actionsColumnDef: ColumnDefinition = Object.assign({}, this.actionsColumn.definition, { type: 'actions', filterable: false }) as ColumnDefinition;
|
||||
columnDefinitions.push(actionsColumnDef);
|
||||
this.columns = columnDefinitions;
|
||||
}
|
||||
|
||||
private async fetchItems(): Promise<void> {
|
||||
const items = await this._dataGridProvider.getDataGridItems(this._providerId);
|
||||
const items = await this._dataGridProviderService.getDataGridItems(this._providerId);
|
||||
this._data = items;
|
||||
this._onDataChanged.fire();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/resourceViewerView';
|
||||
import * as azdata from 'azdata';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -17,6 +18,13 @@ import { ResourceViewerInput } from 'sql/workbench/browser/editor/resourceViewer
|
||||
import { ResourceViewerTable } from 'sql/workbench/contrib/resourceViewer/browser/resourceViewerTable';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { ResourceViewerEditColumns, ResourceViewerRefresh } from 'sql/workbench/contrib/resourceViewer/browser/resourceViewerActions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
|
||||
export type ContextMenuAnchor = HTMLElement | { x: number; y: number; width?: number; height?: number; };
|
||||
|
||||
export class ResourceViewerEditor extends EditorPane {
|
||||
public static readonly ID: string = 'workbench.editor.resource-viewer';
|
||||
@@ -30,7 +38,10 @@ export class ResourceViewerEditor extends EditorPane {
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@IMenuService private _menuService: IMenuService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(ResourceViewerEditor.ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
@@ -43,6 +54,10 @@ export class ResourceViewerEditor extends EditorPane {
|
||||
const header = this.createHeader();
|
||||
const tableContainer = this.createResourceViewerTable();
|
||||
|
||||
this._register(this._resourceViewerTable.onContextMenu(e => {
|
||||
this.showContextMenu(e.anchor, e.item);
|
||||
}));
|
||||
|
||||
this._container.appendChild(header);
|
||||
this._container.appendChild(tableContainer);
|
||||
}
|
||||
@@ -82,6 +97,16 @@ export class ResourceViewerEditor extends EditorPane {
|
||||
this._inputDisposables.clear();
|
||||
|
||||
this._resourceViewerTable.data = input.data;
|
||||
|
||||
input.plugins.forEach(plugin => {
|
||||
this._resourceViewerTable.registerPlugin(plugin);
|
||||
this._inputDisposables.add({
|
||||
dispose: () => {
|
||||
this._resourceViewerTable.unregisterPlugin(plugin);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._resourceViewerTable.columns = input.columns;
|
||||
this._inputDisposables.add(input.onColumnsChanged(columns => {
|
||||
this._resourceViewerTable.columns = columns;
|
||||
@@ -89,6 +114,9 @@ export class ResourceViewerEditor extends EditorPane {
|
||||
this._inputDisposables.add(input.onDataChanged(() => {
|
||||
this._resourceViewerTable.data = input.data;
|
||||
}));
|
||||
this._inputDisposables.add(input.actionsColumn.onClick(e => {
|
||||
this.showContextMenu(e.position, e.item);
|
||||
}));
|
||||
|
||||
this._actionBar.context = input;
|
||||
|
||||
@@ -99,4 +127,24 @@ export class ResourceViewerEditor extends EditorPane {
|
||||
this._container.style.width = dimension.width + 'px';
|
||||
this._container.style.height = dimension.height + 'px';
|
||||
}
|
||||
|
||||
private showContextMenu(anchor: ContextMenuAnchor, context: azdata.DataGridItem): void {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => this.getMenuActions(context)
|
||||
});
|
||||
}
|
||||
|
||||
private getMenuActions(context: azdata.DataGridItem): IAction[] {
|
||||
// Get the contributed menu action items. Note that this currently doesn't
|
||||
// have any item-level support for action filtering, that can be added to the scoped context as
|
||||
// needed in the future
|
||||
const scopedContext = this._contextKeyService.createScoped();
|
||||
const menu = this._menuService.createMenu(MenuId.DataGridItemContext, scopedContext);
|
||||
const options = { arg: context };
|
||||
const groups = menu.getActions(options);
|
||||
const actions: IAction[] = [];
|
||||
fillInActions(groups, actions, false);
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/resourceViewerTable';
|
||||
import * as azdata from 'azdata';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { attachTableStyler, attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { isHyperlinkCellValue, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters';
|
||||
import { HyperlinkCellValue, isHyperlinkCellValue, TextCellValue } from 'sql/base/browser/ui/table/formatters';
|
||||
import { HeaderFilter, CommandEventArgs, IExtendedColumn } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
@@ -19,11 +20,17 @@ import { isString } from 'vs/base/common/types';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ColumnDefinition } from 'sql/workbench/browser/editor/resourceViewer/resourceViewerInput';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { ContextMenuAnchor } from 'sql/workbench/contrib/resourceViewer/browser/resourceViewerEditor';
|
||||
|
||||
export class ResourceViewerTable extends Disposable {
|
||||
|
||||
private _resourceViewerTable!: Table<Slick.SlickData>;
|
||||
private _dataView: TableDataView<Slick.SlickData>;
|
||||
private _resourceViewerTable!: Table<azdata.DataGridItem>;
|
||||
private _dataView: TableDataView<azdata.DataGridItem>;
|
||||
|
||||
private _onContextMenu = new Emitter<{ anchor: ContextMenuAnchor, item: azdata.DataGridItem }>();
|
||||
public onContextMenu = this._onContextMenu.event;
|
||||
|
||||
constructor(parent: HTMLElement,
|
||||
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
|
||||
@@ -31,17 +38,17 @@ export class ResourceViewerTable extends Disposable {
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@INotificationService private _notificationService: INotificationService) {
|
||||
super();
|
||||
let filterFn = (data: Array<Slick.SlickData>): Array<Slick.SlickData> => {
|
||||
let filterFn = (data: Array<azdata.DataGridItem>): Array<azdata.DataGridItem> => {
|
||||
return data.filter(item => this.filter(item));
|
||||
};
|
||||
|
||||
this._dataView = new TableDataView<Slick.SlickData>(undefined, undefined, undefined, filterFn);
|
||||
this._dataView = new TableDataView<azdata.DataGridItem>(undefined, undefined, undefined, filterFn);
|
||||
this._resourceViewerTable = this._register(new Table(parent, {
|
||||
sorter: (args) => {
|
||||
this._dataView.sort(args);
|
||||
}
|
||||
}, {
|
||||
dataItemColumnValueExtractor: slickGridDataItemColumnValueExtractor,
|
||||
dataItemColumnValueExtractor: dataGridColumnValueExtractor,
|
||||
forceFitColumns: true
|
||||
}));
|
||||
this._resourceViewerTable.setSelectionModel(new RowSelectionModel());
|
||||
@@ -49,7 +56,12 @@ export class ResourceViewerTable extends Disposable {
|
||||
this._register(attachButtonStyler(filterPlugin, this._themeService));
|
||||
this._register(attachTableStyler(this._resourceViewerTable, this._themeService));
|
||||
this._register(this._resourceViewerTable.onClick(this.onTableClick, this));
|
||||
|
||||
this._register(this._resourceViewerTable.onContextMenu((e: ITableMouseEvent) => {
|
||||
this._onContextMenu.fire({
|
||||
anchor: e.anchor,
|
||||
item: this._dataView.getItem(e.cell.row)
|
||||
});
|
||||
}));
|
||||
filterPlugin.onFilterApplied.subscribe(() => {
|
||||
this._dataView.filter();
|
||||
this._resourceViewerTable.grid.invalidate();
|
||||
@@ -57,7 +69,7 @@ export class ResourceViewerTable extends Disposable {
|
||||
this._resourceViewerTable.grid.resetActiveCell();
|
||||
this._resourceViewerTable.grid.resizeCanvas();
|
||||
});
|
||||
filterPlugin.onCommand.subscribe((e, args: CommandEventArgs<Slick.SlickData>) => {
|
||||
filterPlugin.onCommand.subscribe((e, args: CommandEventArgs<azdata.DataGridItem>) => {
|
||||
// Convert filter command to SlickGrid sort args
|
||||
this._dataView.sort({
|
||||
grid: args.grid,
|
||||
@@ -71,7 +83,7 @@ export class ResourceViewerTable extends Disposable {
|
||||
this._resourceViewerTable.registerPlugin(filterPlugin);
|
||||
}
|
||||
|
||||
public set data(data: Slick.SlickData[]) {
|
||||
public set data(data: azdata.DataGridItem[]) {
|
||||
this._dataView.clear();
|
||||
this._dataView.push(data);
|
||||
this._resourceViewerTable.grid.setData(this._dataView, true);
|
||||
@@ -82,6 +94,14 @@ export class ResourceViewerTable extends Disposable {
|
||||
this._resourceViewerTable.columns = columns;
|
||||
}
|
||||
|
||||
public registerPlugin(plugin: Slick.Plugin<azdata.DataGridItem>): void {
|
||||
this._resourceViewerTable.registerPlugin(plugin);
|
||||
}
|
||||
|
||||
public unregisterPlugin(plugin: Slick.Plugin<azdata.DataGridItem>): void {
|
||||
this._resourceViewerTable.unregisterPlugin(plugin);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._resourceViewerTable.focus();
|
||||
}
|
||||
@@ -107,10 +127,10 @@ export class ResourceViewerTable extends Disposable {
|
||||
}
|
||||
|
||||
private async onTableClick(event: ITableMouseEvent): Promise<void> {
|
||||
const column = this._resourceViewerTable.columns[event.cell.cell];
|
||||
const column = this._resourceViewerTable.columns[event.cell.cell] as ColumnDefinition;
|
||||
if (column) {
|
||||
const row = this._dataView.getItem(event.cell.row);
|
||||
const value = row[column.field];
|
||||
const value = row.fieldValues[column.field];
|
||||
if (isHyperlinkCellValue(value)) {
|
||||
if (isString(value.linkOrCommand)) {
|
||||
try {
|
||||
@@ -118,7 +138,6 @@ export class ResourceViewerTable extends Disposable {
|
||||
} catch (err) {
|
||||
this._notificationService.error(localize('resourceViewerTable.openError', "Error opening link : {0}", err.message ?? err));
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
await this._commandService.executeCommand(value.linkOrCommand.id, ...(value.linkOrCommand.args ?? []));
|
||||
@@ -130,3 +149,18 @@ export class ResourceViewerTable extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the specified field into the expected object to be handled by SlickGrid and/or formatters as needed.
|
||||
*/
|
||||
function dataGridColumnValueExtractor(value: azdata.DataGridItem, columnDef: ColumnDefinition): TextCellValue | HyperlinkCellValue {
|
||||
const fieldValue = value.fieldValues[columnDef.field];
|
||||
if (columnDef.type === 'hyperlink') {
|
||||
return fieldValue as HyperlinkCellValue;
|
||||
} else {
|
||||
return <TextCellValue>{
|
||||
text: fieldValue,
|
||||
ariaLabel: fieldValue ? escape(fieldValue as string) : fieldValue
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user