mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Adding properties grid enhancements in execution plan (#20208)
* init push * Fixing properties in plan comparison * Add long Text Cell viewer * Disabling auto edit by default * Removing text editor
This commit is contained in:
@@ -7,6 +7,7 @@ import { escape } from 'sql/base/common/strings';
|
|||||||
import { getIconCellValue, IconColumnOptions, TableColumn } from 'sql/base/browser/ui/table/plugins/tableColumn';
|
import { getIconCellValue, IconColumnOptions, TableColumn } from 'sql/base/browser/ui/table/plugins/tableColumn';
|
||||||
|
|
||||||
export interface TextWithIconColumnOptions extends IconColumnOptions {
|
export interface TextWithIconColumnOptions extends IconColumnOptions {
|
||||||
|
editor?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextWithIconColumn<T extends Slick.SlickData> implements TableColumn<T> {
|
export class TextWithIconColumn<T extends Slick.SlickData> implements TableColumn<T> {
|
||||||
@@ -26,7 +27,8 @@ export class TextWithIconColumn<T extends Slick.SlickData> implements TableColum
|
|||||||
width: this.options.width,
|
width: this.options.width,
|
||||||
name: this.options.name,
|
name: this.options.name,
|
||||||
cssClass: 'slick-icon-cell',
|
cssClass: 'slick-icon-cell',
|
||||||
headerCssClass: this.options.headerCssClass
|
headerCssClass: this.options.headerCssClass,
|
||||||
|
editor: this.options.editor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
|
|
||||||
if (this.activeBottomPlanDiagram) {
|
if (this.activeBottomPlanDiagram) {
|
||||||
const element = this.activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
const element = this.activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||||
if (similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
if (this.activeBottomPlanDiagram.getSelectedElement() && similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
|
|
||||||
if (this.activeTopPlanDiagram) {
|
if (this.activeTopPlanDiagram) {
|
||||||
const element = this.activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
const element = this.activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||||
if (similarNode.matchingNodesId.find(m => this.activeTopPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
if (this.activeTopPlanDiagram.getSelectedElement() && similarNode.matchingNodesId.find(m => this.activeTopPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPla
|
|||||||
import { executionPlanComparisonPropertiesDifferent, executionPlanComparisonPropertiesUpArrow, executionPlanComparisonPropertiesDownArrow } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
import { executionPlanComparisonPropertiesDifferent, executionPlanComparisonPropertiesUpArrow, executionPlanComparisonPropertiesDownArrow } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||||
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
|
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
|
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
export enum ExecutionPlanCompareOrientation {
|
export enum ExecutionPlanCompareOrientation {
|
||||||
Horizontal = 'horizontal',
|
Horizontal = 'horizontal',
|
||||||
@@ -37,8 +39,10 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
public constructor(
|
public constructor(
|
||||||
parentContainer: HTMLElement,
|
parentContainer: HTMLElement,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService
|
||||||
) {
|
) {
|
||||||
super(parentContainer, themeService);
|
super(parentContainer, themeService, instantiationService, contextMenuService);
|
||||||
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
|
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
|
||||||
this._parentContainer.style.display = 'none';
|
this._parentContainer.style.display = 'none';
|
||||||
const header = DOM.$('.compare-operation-name');
|
const header = DOM.$('.compare-operation-name');
|
||||||
@@ -128,30 +132,27 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
||||||
field: 'name',
|
field: 'name',
|
||||||
width: 200,
|
width: 200,
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
formatter: textFormatter
|
formatter: textFormatter
|
||||||
});
|
});
|
||||||
columns.push({
|
columns.push({
|
||||||
id: 'value',
|
id: 'value1',
|
||||||
name: getPropertyViewNameValueColumnTopHeaderForOrientation(this._orientation),
|
name: getPropertyViewNameValueColumnTopHeaderForOrientation(this._orientation),
|
||||||
field: 'primary',
|
field: 'primary',
|
||||||
width: 150,
|
width: 150,
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
formatter: textFormatter
|
formatter: textFormatter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this._model.bottomElement) {
|
if (this._model.bottomElement) {
|
||||||
columns.push(new TextWithIconColumn({
|
columns.push(new TextWithIconColumn({
|
||||||
id: 'value',
|
id: 'value2',
|
||||||
name: getPropertyViewNameValueColumnBottomHeaderForOrientation(this._orientation),
|
name: getPropertyViewNameValueColumnBottomHeaderForOrientation(this._orientation),
|
||||||
field: 'secondary',
|
field: 'secondary',
|
||||||
width: 150,
|
width: 150,
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
}).definition);
|
}).definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +314,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,16 @@ export class ExecutionPlanEditor extends EditorPane {
|
|||||||
public layout(dimension: DOM.Dimension): void {
|
public layout(dimension: DOM.Dimension): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override clearInput(): void {
|
||||||
|
const currentInput = this.input as ExecutionPlanInput;
|
||||||
|
// clearing old input view if present in the editor
|
||||||
|
if (currentInput?._executionPlanFileViewUUID) {
|
||||||
|
const oldView = this._viewCache.executionPlanFileViewMap.get(currentInput._executionPlanFileViewUUID);
|
||||||
|
oldView.onHide(this._parentContainer);
|
||||||
|
}
|
||||||
|
super.clearInput();
|
||||||
|
}
|
||||||
|
|
||||||
public override async setInput(newInput: ExecutionPlanInput, options: IEditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
public override async setInput(newInput: ExecutionPlanInput, options: IEditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||||
const oldInput = this.input as ExecutionPlanInput;
|
const oldInput = this.input as ExecutionPlanInput;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { removeLineBreaks } from 'sql/base/common/strings';
|
|||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||||
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
|
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase {
|
export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase {
|
||||||
// Div that holds the name of the element selected
|
// Div that holds the name of the element selected
|
||||||
@@ -19,9 +21,11 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
parentContainer: HTMLElement,
|
parentContainer: HTMLElement,
|
||||||
themeService: IThemeService
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
) {
|
) {
|
||||||
super(parentContainer, themeService);
|
super(parentContainer, themeService, instantiationService, contextMenuService);
|
||||||
this._model = <ExecutionPlanPropertiesView>{};
|
this._model = <ExecutionPlanPropertiesView>{};
|
||||||
this._operationName = DOM.$('h3');
|
this._operationName = DOM.$('h3');
|
||||||
this._operationName.classList.add('operation-name');
|
this._operationName.classList.add('operation-name');
|
||||||
@@ -91,7 +95,6 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
||||||
field: 'name',
|
field: 'name',
|
||||||
width: 250,
|
width: 250,
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
formatter: textFormatter
|
formatter: textFormatter
|
||||||
},
|
},
|
||||||
@@ -100,7 +103,6 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
|
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
|
||||||
field: 'value',
|
field: 'value',
|
||||||
width: 250,
|
width: 250,
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
headerCssClass: 'prop-table-header',
|
headerCssClass: 'prop-table-header',
|
||||||
formatter: textFormatter
|
formatter: textFormatter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
|
|||||||
import { contrastBorder, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
import { contrastBorder, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { TreeGrid } from 'sql/base/browser/ui/table/treeGrid';
|
import { TreeGrid } from 'sql/base/browser/ui/table/treeGrid';
|
||||||
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||||
|
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
|
import { isString } from 'vs/base/common/types';
|
||||||
|
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||||
|
|
||||||
export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLayoutProvider {
|
export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLayoutProvider {
|
||||||
// Title bar with close button action
|
// Title bar with close button action
|
||||||
@@ -42,9 +48,13 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
|
|||||||
|
|
||||||
public resizeSash: Sash;
|
public resizeSash: Sash;
|
||||||
|
|
||||||
|
private _selectionModel: CellSelectionModel<Slick.SlickData>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public _parentContainer: HTMLElement,
|
public _parentContainer: HTMLElement,
|
||||||
private _themeService: IThemeService
|
private _themeService: IThemeService,
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
|
@IContextMenuService private _contextMenuService: IContextMenuService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const sashContainer = DOM.$('.properties-sash');
|
const sashContainer = DOM.$('.properties-sash');
|
||||||
@@ -103,14 +113,38 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
|
|||||||
const table = DOM.$('.table');
|
const table = DOM.$('.table');
|
||||||
this._tableContainer.appendChild(table);
|
this._tableContainer.appendChild(table);
|
||||||
|
|
||||||
|
this._selectionModel = new CellSelectionModel<Slick.SlickData>();
|
||||||
|
|
||||||
this._tableComponent = new TreeGrid(table, {
|
this._tableComponent = new TreeGrid(table, {
|
||||||
columns: []
|
columns: []
|
||||||
}, {
|
}, {
|
||||||
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
|
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
|
||||||
forceFitColumns: true,
|
forceFitColumns: true,
|
||||||
defaultColumnWidth: 120
|
defaultColumnWidth: 120,
|
||||||
|
editable: true,
|
||||||
|
autoEdit: false
|
||||||
});
|
});
|
||||||
attachTableStyler(this._tableComponent, this._themeService);
|
attachTableStyler(this._tableComponent, this._themeService);
|
||||||
|
this._tableComponent.setSelectionModel(this._selectionModel);
|
||||||
|
|
||||||
|
const contextMenuAction = [
|
||||||
|
this._instantiationService.createInstance(CopyTableData),
|
||||||
|
];
|
||||||
|
|
||||||
|
this._tableComponent.onContextMenu(e => {
|
||||||
|
this._contextMenuService.showContextMenu({
|
||||||
|
getAnchor: () => e.anchor,
|
||||||
|
getActions: () => contextMenuAction,
|
||||||
|
getActionsContext: () => this.getCopyString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let copyHandler = new CopyKeybind<any>();
|
||||||
|
this._tableComponent.registerPlugin(copyHandler);
|
||||||
|
|
||||||
|
copyHandler.onCopy(e => {
|
||||||
|
this._instantiationService.createInstance(CopyTableData).run(this.getCopyString());
|
||||||
|
});
|
||||||
|
|
||||||
new ResizeObserver((e) => {
|
new ResizeObserver((e) => {
|
||||||
this.resizeSash.layout();
|
this.resizeSash.layout();
|
||||||
@@ -118,6 +152,33 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
|
|||||||
}).observe(_parentContainer);
|
}).observe(_parentContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public getCopyString(): string {
|
||||||
|
const selectedDataRange = this._selectionModel.getSelectedRanges()[0];
|
||||||
|
let csvString = '';
|
||||||
|
if (selectedDataRange) {
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
||||||
|
const dataRow = this._tableComponent.getData().getItem(rowIndex);
|
||||||
|
const row = [];
|
||||||
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
|
const dataItem = dataRow[this._tableComponent.grid.getColumns()[colIndex].field];
|
||||||
|
if (dataItem) {
|
||||||
|
row.push(isString(dataItem) ? dataItem : dataItem.displayText ?? dataItem.text ?? dataItem.title);
|
||||||
|
} else {
|
||||||
|
row.push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
csvString = data.map(row =>
|
||||||
|
row.map(x => `${x}`).join('\t')
|
||||||
|
).join('\n');
|
||||||
|
}
|
||||||
|
return csvString;
|
||||||
|
}
|
||||||
|
|
||||||
getVerticalSashLeft(sash: Sash): number {
|
getVerticalSashLeft(sash: Sash): number {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -273,3 +334,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export class CopyTableData extends Action {
|
||||||
|
public static ID = 'ep.CopyTableData';
|
||||||
|
public static LABEL = localize('ep.topOperationsCopyTableData', "Copy");
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IClipboardService private _clipboardService: IClipboardService
|
||||||
|
) {
|
||||||
|
super(CopyTableData.ID, CopyTableData.LABEL, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: string): Promise<void> {
|
||||||
|
|
||||||
|
this._clipboardService.writeText(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
|
|||||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
|
||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||||
@@ -75,7 +74,6 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
private _executionPlanFileView: ExecutionPlanFileView,
|
private _executionPlanFileView: ExecutionPlanFileView,
|
||||||
private _queryResultsView: QueryResultsView,
|
private _queryResultsView: QueryResultsView,
|
||||||
@IInstantiationService public readonly _instantiationService: IInstantiationService,
|
@IInstantiationService public readonly _instantiationService: IInstantiationService,
|
||||||
@IThemeService private readonly _themeService: IThemeService,
|
|
||||||
@IContextViewService public readonly contextViewService: IContextViewService,
|
@IContextViewService public readonly contextViewService: IContextViewService,
|
||||||
@IUntitledTextEditorService private readonly _untitledEditorService: IUntitledTextEditorService,
|
@IUntitledTextEditorService private readonly _untitledEditorService: IUntitledTextEditorService,
|
||||||
@IEditorService private readonly editorService: IEditorService,
|
@IEditorService private readonly editorService: IEditorService,
|
||||||
@@ -144,7 +142,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
// container properties
|
// container properties
|
||||||
this._propContainer = DOM.$('.properties');
|
this._propContainer = DOM.$('.properties');
|
||||||
this.container.appendChild(this._propContainer);
|
this.container.appendChild(this._propContainer);
|
||||||
this.propertiesView = new ExecutionPlanPropertiesView(this._propContainer, this._themeService);
|
this.propertiesView = this._instantiationService.createInstance(ExecutionPlanPropertiesView, this._propContainer);
|
||||||
|
|
||||||
this._widgetContainer = DOM.$('.plan-action-container');
|
this._widgetContainer = DOM.$('.plan-action-container');
|
||||||
this._planContainer.appendChild(this._widgetContainer);
|
this._planContainer.appendChild(this._widgetContainer);
|
||||||
@@ -197,7 +195,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
|
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.container.oncontextmenu = (e: MouseEvent) => {
|
this._planContainer.oncontextmenu = (e: MouseEvent) => {
|
||||||
if (contextMenuAction) {
|
if (contextMenuAction) {
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => {
|
getAnchor: () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user