Renaming query plan to execution plan (#18551)
265
src/sql/workbench/contrib/executionPlan/browser/constants.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
let imageBasePath = require.toUrl('./images/icons/');
|
||||
export let executionPlanNodeIconPaths =
|
||||
{
|
||||
// generic icons
|
||||
iteratorCatchAll: imageBasePath + 'iterator_catch_all.png',
|
||||
|
||||
cursorCatchAll: imageBasePath + 'cursor_catch_all.png',
|
||||
|
||||
languageConstructCatchAll: imageBasePath + 'language_construct_catch_all.png',
|
||||
|
||||
// operator icons
|
||||
adaptiveJoin: imageBasePath + 'adaptive_join.png',
|
||||
|
||||
assert: imageBasePath + 'assert.png',
|
||||
|
||||
bitmap: imageBasePath + 'bitmap.png',
|
||||
|
||||
clusteredIndexDelete: imageBasePath + 'clustered_index_delete.png',
|
||||
|
||||
clusteredIndexInsert: imageBasePath + 'clustered_index_insert.png',
|
||||
|
||||
clusteredIndexScan: imageBasePath + 'clustered_index_scan.png',
|
||||
|
||||
clusteredIndexSeek: imageBasePath + 'clustered_index_seek.png',
|
||||
|
||||
clusteredIndexUpdate: imageBasePath + 'clustered_index_update.png',
|
||||
|
||||
clusteredIndexMerge: imageBasePath + 'clustered_index_merge.png',
|
||||
|
||||
clusteredUpdate: imageBasePath + 'clustered_update.png',
|
||||
|
||||
collapse: imageBasePath + 'collapse.png',
|
||||
|
||||
computeScalar: imageBasePath + 'compute_scalar.png',
|
||||
|
||||
concatenation: imageBasePath + 'concatenation.png',
|
||||
|
||||
constantScan: imageBasePath + 'constant_scan.png',
|
||||
|
||||
deletedScan: imageBasePath + 'deleted_scan.png',
|
||||
|
||||
filter: imageBasePath + 'filter.png',
|
||||
|
||||
hashMatch: imageBasePath + 'hash_match.png',
|
||||
|
||||
indexDelete: imageBasePath + 'index_delete.png',
|
||||
|
||||
indexInsert: imageBasePath + 'index_insert.png',
|
||||
|
||||
indexScan: imageBasePath + 'index_scan.png',
|
||||
|
||||
columnstoreIndexDelete: imageBasePath + 'columnstore_index_delete.png',
|
||||
|
||||
columnstoreIndexInsert: imageBasePath + 'columnstore_index_insert.png',
|
||||
|
||||
columnstoreIndexMerge: imageBasePath + 'columnstore_index_merge.png',
|
||||
|
||||
columnstoreIndexScan: imageBasePath + 'columnstore_index_scan.png',
|
||||
|
||||
columnstoreIndexUpdate: imageBasePath + 'columnstore_index_update.png',
|
||||
|
||||
indexSeek: imageBasePath + 'index_seek.png',
|
||||
|
||||
indexSpool: imageBasePath + 'index_spool.png',
|
||||
|
||||
indexUpdate: imageBasePath + 'index_update.png',
|
||||
|
||||
insertedScan: imageBasePath + 'inserted_scan.png',
|
||||
|
||||
logRowScan: imageBasePath + 'log_row_scan.png',
|
||||
|
||||
mergeInterval: imageBasePath + 'merge_interval.png',
|
||||
|
||||
mergeJoin: imageBasePath + 'merge_join.png',
|
||||
|
||||
nestedLoops: imageBasePath + 'nested_loops.png',
|
||||
|
||||
parallelism: imageBasePath + 'parallelism.png',
|
||||
|
||||
parameterTableScan: imageBasePath + 'parameter_table_scan.png',
|
||||
|
||||
print: imageBasePath + 'print.png',
|
||||
|
||||
rank: imageBasePath + 'rank.png',
|
||||
|
||||
foreignKeyReferencesCheck: imageBasePath + 'foreign_key_references_check.png',
|
||||
|
||||
remoteDelete: imageBasePath + 'remote_delete.png',
|
||||
|
||||
remoteIndexScan: imageBasePath + 'remote_index_scan.png',
|
||||
|
||||
remoteIndexSeek: imageBasePath + 'remote_index_seek.png',
|
||||
|
||||
remoteInsert: imageBasePath + 'remote_insert.png',
|
||||
|
||||
remoteQuery: imageBasePath + 'remote_query.png',
|
||||
|
||||
remoteScan: imageBasePath + 'remote_scan.png',
|
||||
|
||||
remoteUpdate: imageBasePath + 'remote_update.png',
|
||||
|
||||
ridLookup: imageBasePath + 'rid_lookup.png',
|
||||
|
||||
rowCountSpool: imageBasePath + 'row_count_spool.png',
|
||||
|
||||
segment: imageBasePath + 'segment.png',
|
||||
|
||||
sequence: imageBasePath + 'sequence.png',
|
||||
|
||||
sequenceProject: imageBasePath + 'sequence_project.png',
|
||||
|
||||
sort: imageBasePath + 'sort.png',
|
||||
|
||||
split: imageBasePath + 'split.png',
|
||||
|
||||
streamAggregate: imageBasePath + 'stream_aggregate.png',
|
||||
|
||||
switchStatement: imageBasePath + 'switch.png',
|
||||
|
||||
tableValuedFunction: imageBasePath + 'table_valued_function.png',
|
||||
|
||||
tableDelete: imageBasePath + 'table_delete.png',
|
||||
|
||||
tableInsert: imageBasePath + 'table_insert.png',
|
||||
|
||||
tableScan: imageBasePath + 'table_scan.png',
|
||||
|
||||
tableSpool: imageBasePath + 'table_spool.png',
|
||||
|
||||
tableUpdate: imageBasePath + 'table_update.png',
|
||||
|
||||
tableMerge: imageBasePath + 'table_merge.png',
|
||||
|
||||
tfp: imageBasePath + 'predict.png',
|
||||
|
||||
top: imageBasePath + 'top.png',
|
||||
|
||||
udx: imageBasePath + 'udx.png',
|
||||
|
||||
batchHashTableBuild: imageBasePath + 'batch_hash_table_build.png',
|
||||
|
||||
windowSpool: imageBasePath + 'table_spool.png',
|
||||
|
||||
windowAggregate: imageBasePath + 'window_aggregate.png',
|
||||
|
||||
// cursor operators
|
||||
fetchQuery: imageBasePath + 'fetch_query.png',
|
||||
|
||||
populateQuery: imageBasePath + 'population_query.png',
|
||||
|
||||
refreshQuery: imageBasePath + 'refresh_query.png',
|
||||
|
||||
// shiloh operators
|
||||
result: imageBasePath + 'result.png',
|
||||
|
||||
aggregate: imageBasePath + 'aggregate.png',
|
||||
|
||||
assign: imageBasePath + 'assign.png',
|
||||
|
||||
arithmeticExpression: imageBasePath + 'arithmetic_expression.png',
|
||||
|
||||
bookmarkLookup: imageBasePath + 'bookmark_lookup.png',
|
||||
|
||||
convert: imageBasePath + 'convert.png',
|
||||
|
||||
declare: imageBasePath + 'declare.png',
|
||||
|
||||
deleteOperator: imageBasePath + 'delete.png',
|
||||
|
||||
dynamic: imageBasePath + 'dynamic.png',
|
||||
|
||||
hashMatchRoot: imageBasePath + 'hash_match_root.png',
|
||||
|
||||
hashMatchTeam: imageBasePath + 'hash_match_team.png',
|
||||
|
||||
ifOperator: imageBasePath + 'if.png',
|
||||
|
||||
insert: imageBasePath + 'insert.png',
|
||||
|
||||
intrinsic: imageBasePath + 'intrinsic.png',
|
||||
|
||||
keyset: imageBasePath + 'keyset.png',
|
||||
|
||||
locate: imageBasePath + 'locate.png',
|
||||
|
||||
populationQuery: imageBasePath + 'population_query.png',
|
||||
|
||||
setFunction: imageBasePath + 'set_function.png',
|
||||
|
||||
snapshot: imageBasePath + 'snapshot.png',
|
||||
|
||||
spool: imageBasePath + 'spool.png',
|
||||
|
||||
tsql: imageBasePath + 'sql.png',
|
||||
|
||||
update: imageBasePath + 'update.png',
|
||||
|
||||
// fake operators
|
||||
keyLookup: imageBasePath + 'bookmark_lookup.png',
|
||||
|
||||
// PDW operators
|
||||
apply: imageBasePath + 'apply.png',
|
||||
|
||||
broadcast: imageBasePath + 'broadcast.png',
|
||||
|
||||
computeToControlNode: imageBasePath + 'compute_to_control_node.png',
|
||||
|
||||
constTableGet: imageBasePath + 'const_table_get.png',
|
||||
|
||||
controlToComputeNodes: imageBasePath + 'control_to_compute_nodes.png',
|
||||
|
||||
externalBroadcast: imageBasePath + 'external_broadcast.png',
|
||||
|
||||
externalExport: imageBasePath + 'external_export.png',
|
||||
|
||||
externalLocalStreaming: imageBasePath + 'external_local_streaming.png',
|
||||
|
||||
externalRoundRobin: imageBasePath + 'external_round_robin.png',
|
||||
|
||||
externalShuffle: imageBasePath + 'external_shuffle.png',
|
||||
|
||||
get: imageBasePath + 'get.png',
|
||||
|
||||
groupByApply: imageBasePath + 'apply.png',
|
||||
|
||||
groupByAggregate: imageBasePath + 'group_by_aggregate.png',
|
||||
|
||||
join: imageBasePath + 'join.png',
|
||||
|
||||
localCube: imageBasePath + 'intrinsic.png',
|
||||
|
||||
project: imageBasePath + 'project.png',
|
||||
|
||||
shuffle: imageBasePath + 'shuffle.png',
|
||||
|
||||
singleSourceRoundRobin: imageBasePath + 'single_source_round_robin.png',
|
||||
|
||||
singleSourceShuffle: imageBasePath + 'single_source_shuffle.png',
|
||||
|
||||
trim: imageBasePath + 'trim.png',
|
||||
|
||||
union: imageBasePath + 'union.png',
|
||||
|
||||
unionAll: imageBasePath + 'union_all.png'
|
||||
};
|
||||
|
||||
const parentContainer = 'qps-container';
|
||||
export const savePlanIconClassNames = [parentContainer, 'save-plan-icon'].join(' ');
|
||||
export const openPropertiesIconClassNames = [parentContainer, 'open-properties-icon'].join(' ');
|
||||
export const openQueryIconClassNames = [parentContainer, 'open-query-icon'].join(' ');
|
||||
export const openPlanFileIconClassNames = [parentContainer, 'open-plan-file-icon'].join(' ');
|
||||
export const saveIconClassNames = [parentContainer, 'save-icon'].join(' ');
|
||||
export const searchIconClassNames = [parentContainer, 'search-icon'].join(' ');
|
||||
export const sortAlphabeticallyIconClassNames = [parentContainer, 'sort-alphabetically-icon'].join(' ');
|
||||
export const sortByDisplayOrderIconClassNames = [parentContainer, 'sort-display-order-icon'].join(' ');
|
||||
export const zoomInIconClassNames = [parentContainer, 'zoom-in-icon'].join(' ');
|
||||
export const zoomOutIconClassNames = [parentContainer, 'zoom-out-icon'].join(' ');
|
||||
export const customZoomIconClassNames = [parentContainer, 'custom-zoom-icon'].join(' ');
|
||||
export const zoomToFitIconClassNames = [parentContainer, 'zoom-to-fit-icon'].join(' ');
|
||||
export const zoomIconClassNames = [parentContainer, 'zoom-icon'].join(' ');
|
||||
657
src/sql/workbench/contrib/executionPlan/browser/executionPlan.ts
Normal file
@@ -0,0 +1,657 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/executionPlan';
|
||||
import type * as azdata from 'azdata';
|
||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as azdataGraphModule from 'azdataGraph';
|
||||
import { customZoomIconClassNames, openPlanFileIconClassNames, openPropertiesIconClassNames, openQueryIconClassNames, executionPlanNodeIconPaths, savePlanIconClassNames, searchIconClassNames, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { PlanHeader } from 'sql/workbench/contrib/executionPlan/browser/planHeader';
|
||||
import { ExecutionPlanPropertiesView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions';
|
||||
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { contrastBorder, editorBackground, editorWidgetBackground, foreground, listHoverBackground, textLinkForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ISashEvent, ISashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
|
||||
import { Progress } from 'vs/platform/progress/common/progress';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController';
|
||||
import { CustomZoomWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget';
|
||||
import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget';
|
||||
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
|
||||
let azdataGraph = azdataGraphModule();
|
||||
|
||||
export interface InternalExecutionPlanNode extends azdata.ExecutionPlanNode {
|
||||
/**
|
||||
* Unique internal id given to graph node by ADS.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface InternalExecutionPlanEdge extends azdata.ExecutionPlanEdge {
|
||||
/**
|
||||
* Unique internal id given to graph edge by ADS.
|
||||
*/
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export class ExecutionPlanTab implements IPanelTab {
|
||||
public readonly title = localize('executionPlanTitle', "Query Plan (Preview)");
|
||||
public readonly identifier = 'ExecutionPlan2Tab';
|
||||
public readonly view: ExecutionPlanView;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
this.view = instantiationService.createInstance(ExecutionPlanView);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.view);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.view.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExecutionPlanView implements IPanelView {
|
||||
private _eps?: ExecutionPlan[] = [];
|
||||
private _graphs?: azdata.ExecutionPlanGraph[] = [];
|
||||
private _container = DOM.$('.eps-container');
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
) {
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.appendChild(this._container);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._container.remove();
|
||||
delete this._eps;
|
||||
delete this._graphs;
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this._eps = [];
|
||||
this._graphs = [];
|
||||
DOM.clearNode(this._container);
|
||||
}
|
||||
|
||||
public addGraphs(newGraphs: azdata.ExecutionPlanGraph[] | undefined) {
|
||||
if (newGraphs) {
|
||||
newGraphs.forEach(g => {
|
||||
const ep = this.instantiationService.createInstance(ExecutionPlan, this._container, this._eps.length + 1);
|
||||
ep.graphModel = g;
|
||||
this._eps.push(ep);
|
||||
this._graphs.push(g);
|
||||
this.updateRelativeCosts();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateRelativeCosts() {
|
||||
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
||||
return prevCost += cg.root.subTreeCost + cg.root.cost;
|
||||
}, 0);
|
||||
|
||||
if (sum > 0) {
|
||||
this._eps.forEach(ep => {
|
||||
ep.planHeader.relativeCost = ((ep.graphModel.root.subTreeCost + ep.graphModel.root.cost) / sum) * 100;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExecutionPlan implements ISashLayoutProvider {
|
||||
private _graphModel?: azdata.ExecutionPlanGraph;
|
||||
|
||||
private _container: HTMLElement;
|
||||
|
||||
private _actionBarContainer: HTMLElement;
|
||||
private _actionBar: ActionBar;
|
||||
|
||||
public planHeader: PlanHeader;
|
||||
private _planContainer: HTMLElement;
|
||||
private _planHeaderContainer: HTMLElement;
|
||||
|
||||
public propertiesView: ExecutionPlanPropertiesView;
|
||||
private _propContainer: HTMLElement;
|
||||
|
||||
private _planActionContainer: HTMLElement;
|
||||
public planActionView: ExecutionPlanWidgetController;
|
||||
|
||||
public azdataGraphDiagram: any;
|
||||
|
||||
public graphElementPropertiesSet: Set<string> = new Set();
|
||||
|
||||
private uniqueElementId: number = -1;
|
||||
|
||||
constructor(
|
||||
private _parent: HTMLElement,
|
||||
private _graphIndex: number,
|
||||
@IInstantiationService public readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IContextViewService public readonly contextViewService: IContextViewService,
|
||||
@IUntitledTextEditorService private readonly _untitledEditorService: IUntitledTextEditorService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@IFileDialogService public fileDialogService: IFileDialogService,
|
||||
@IFileService public fileService: IFileService,
|
||||
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
) {
|
||||
// parent container for query plan.
|
||||
this._container = DOM.$('.execution-plan');
|
||||
this._parent.appendChild(this._container);
|
||||
const sashContainer = DOM.$('.execution-plan-sash');
|
||||
this._parent.appendChild(sashContainer);
|
||||
|
||||
const sash = new Sash(sashContainer, this, { orientation: Orientation.HORIZONTAL });
|
||||
let originalHeight = this._container.offsetHeight;
|
||||
let originalTableHeight = 0;
|
||||
let change = 0;
|
||||
sash.onDidStart((e: ISashEvent) => {
|
||||
originalHeight = this._container.offsetHeight;
|
||||
originalTableHeight = this.propertiesView.tableHeight;
|
||||
});
|
||||
|
||||
/**
|
||||
* Using onDidChange for the smooth resizing of the graph diagram
|
||||
*/
|
||||
sash.onDidChange((evt: ISashEvent) => {
|
||||
change = evt.startY - evt.currentY;
|
||||
const newHeight = originalHeight - change;
|
||||
if (newHeight < 200) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Since the parent container is flex, we will have
|
||||
* to change the flex-basis property to change the height.
|
||||
*/
|
||||
this._container.style.minHeight = '200px';
|
||||
this._container.style.flex = `0 0 ${newHeight}px`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Resizing properties window table only once at the end as it is a heavy operation and worsens the smooth resizing experience
|
||||
*/
|
||||
sash.onDidEnd(() => {
|
||||
this.propertiesView.tableHeight = originalTableHeight - change;
|
||||
});
|
||||
|
||||
this._planContainer = DOM.$('.plan');
|
||||
this._container.appendChild(this._planContainer);
|
||||
|
||||
// container that holds plan header info
|
||||
this._planHeaderContainer = DOM.$('.header');
|
||||
|
||||
// Styling header text like the query editor
|
||||
this._planHeaderContainer.style.fontFamily = EDITOR_FONT_DEFAULTS.fontFamily;
|
||||
this._planHeaderContainer.style.fontSize = EDITOR_FONT_DEFAULTS.fontSize.toString();
|
||||
this._planHeaderContainer.style.fontWeight = EDITOR_FONT_DEFAULTS.fontWeight;
|
||||
|
||||
this._planContainer.appendChild(this._planHeaderContainer);
|
||||
this.planHeader = this._instantiationService.createInstance(PlanHeader, this._planHeaderContainer, {
|
||||
planIndex: this._graphIndex,
|
||||
});
|
||||
|
||||
// container properties
|
||||
this._propContainer = DOM.$('.properties');
|
||||
this._container.appendChild(this._propContainer);
|
||||
this.propertiesView = new ExecutionPlanPropertiesView(this._propContainer, this._themeService);
|
||||
|
||||
this._planActionContainer = DOM.$('.plan-action-container');
|
||||
this._planContainer.appendChild(this._planActionContainer);
|
||||
this.planActionView = new ExecutionPlanWidgetController(this._planActionContainer);
|
||||
|
||||
// container that holds actionbar icons
|
||||
this._actionBarContainer = DOM.$('.action-bar-container');
|
||||
this._container.appendChild(this._actionBarContainer);
|
||||
this._actionBar = new ActionBar(this._actionBarContainer, {
|
||||
orientation: ActionsOrientation.VERTICAL, context: this
|
||||
});
|
||||
|
||||
|
||||
const actions = [
|
||||
new SavePlanFile(),
|
||||
new OpenPlanFile(),
|
||||
new OpenQueryAction(),
|
||||
new SearchNodeAction(),
|
||||
new ZoomInAction(),
|
||||
new ZoomOutAction(),
|
||||
new ZoomToFitAction(),
|
||||
new CustomZoomAction(),
|
||||
new PropertiesAction(),
|
||||
];
|
||||
this._actionBar.pushAction(actions, { icon: true, label: false });
|
||||
|
||||
// Setting up context menu
|
||||
const self = this;
|
||||
this._container.oncontextmenu = (e: MouseEvent) => {
|
||||
if (actions) {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
return {
|
||||
x: e.x,
|
||||
y: e.y
|
||||
};
|
||||
},
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => (self)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getHorizontalSashTop(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getHorizontalSashLeft?(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
getHorizontalSashWidth?(sash: Sash): number {
|
||||
return this._container.clientWidth;
|
||||
}
|
||||
|
||||
private populate(node: InternalExecutionPlanNode, diagramNode: any): any {
|
||||
diagramNode.label = node.subtext.join(this.textResourcePropertiesService.getEOL(undefined));
|
||||
diagramNode.tooltipTitle = node.name;
|
||||
const nodeId = this.createGraphElementId();
|
||||
diagramNode.id = nodeId;
|
||||
node.id = nodeId;
|
||||
|
||||
if (node.properties && node.properties.length > 0) {
|
||||
diagramNode.metrics = this.populateProperties(node.properties);
|
||||
}
|
||||
|
||||
if (node.type) {
|
||||
diagramNode.icon = node.type;
|
||||
}
|
||||
|
||||
if (node.edges) {
|
||||
diagramNode.edges = [];
|
||||
for (let i = 0; i < node.edges.length; i++) {
|
||||
diagramNode.edges.push(this.populateEdges(node.edges[i], new Object()));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
diagramNode.children = [];
|
||||
for (let i = 0; i < node.children.length; ++i) {
|
||||
diagramNode.children.push(this.populate(node.children[i], new Object()));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.description) {
|
||||
diagramNode.description = node.description;
|
||||
}
|
||||
return diagramNode;
|
||||
}
|
||||
|
||||
private populateEdges(edge: InternalExecutionPlanEdge, diagramEdge: any) {
|
||||
diagramEdge.label = '';
|
||||
const edgeId = this.createGraphElementId();
|
||||
diagramEdge.id = edgeId;
|
||||
edge.id = edgeId;
|
||||
diagramEdge.metrics = this.populateProperties(edge.properties);
|
||||
diagramEdge.weight = Math.max(0.5, Math.min(0.5 + 0.75 * Math.log10(edge.rowCount), 6));
|
||||
return diagramEdge;
|
||||
}
|
||||
|
||||
private populateProperties(props: azdata.ExecutionPlanGraphElementProperty[]) {
|
||||
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
.map(e => {
|
||||
this.graphElementPropertiesSet.add(e.name);
|
||||
return {
|
||||
name: e.name,
|
||||
value: e.displayValue,
|
||||
isLongString: e.positionAtBottom
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private createGraphElementId(): string {
|
||||
this.uniqueElementId += 1;
|
||||
return `element-${this.uniqueElementId}`;
|
||||
}
|
||||
|
||||
private createPlanDiagram(container: HTMLElement) {
|
||||
let diagramRoot: any = new Object();
|
||||
let graphRoot: azdata.ExecutionPlanNode = this._graphModel.root;
|
||||
|
||||
this.populate(graphRoot, diagramRoot);
|
||||
this.azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, executionPlanNodeIconPaths);
|
||||
|
||||
this.azdataGraphDiagram.graph.setCellsMovable(false); // preventing drag and drop of graph nodes.
|
||||
this.azdataGraphDiagram.graph.setCellsDisconnectable(false); // preventing graph edges to be disconnected from source and target nodes.
|
||||
|
||||
this.azdataGraphDiagram.graph.addListener('click', (sender, evt) => {
|
||||
// Updating properties view table on node clicks
|
||||
const cell = evt.properties['cell'];
|
||||
if (cell) {
|
||||
this.propertiesView.graphElement = this.searchNodes(cell.id);
|
||||
} else if (!this.azdataGraphDiagram.graph.getSelectionCell()) {
|
||||
const root = this.azdataGraphDiagram.graph.model.getCell(diagramRoot.id);
|
||||
this.azdataGraphDiagram.graph.getSelectionModel().setCell(root);
|
||||
this.propertiesView.graphElement = this.searchNodes(diagramRoot.id);
|
||||
evt.consume();
|
||||
} else {
|
||||
evt.consume();
|
||||
}
|
||||
});
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const iconBackground = theme.getColor(editorBackground);
|
||||
if (iconBackground) {
|
||||
this.azdataGraphDiagram.setIconBackgroundColor(iconBackground);
|
||||
}
|
||||
|
||||
const iconLabelColor = theme.getColor(foreground);
|
||||
if (iconLabelColor) {
|
||||
this.azdataGraphDiagram.setTextFontColor(iconLabelColor);
|
||||
this.azdataGraphDiagram.setEdgeColor(iconLabelColor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public set graphModel(graph: azdata.ExecutionPlanGraph | undefined) {
|
||||
this._graphModel = graph;
|
||||
if (this._graphModel) {
|
||||
this.planHeader.graphIndex = this._graphIndex;
|
||||
this.planHeader.query = graph.query;
|
||||
if (graph.recommendations) {
|
||||
this.planHeader.recommendations = graph.recommendations;
|
||||
}
|
||||
let diagramContainer = DOM.$('.diagram');
|
||||
this.createPlanDiagram(diagramContainer);
|
||||
|
||||
/**
|
||||
* We do not want to scroll the diagram through mouse wheel.
|
||||
* Instead, we pass this event to parent control. So, when user
|
||||
* uses the scroll wheel, they scroll through graphs present in
|
||||
* the graph control. To scroll the individual graphs, users should
|
||||
* use the scroll bars.
|
||||
*/
|
||||
diagramContainer.addEventListener('wheel', e => {
|
||||
this._parent.scrollTop += e.deltaY;
|
||||
//Hiding all tooltips when we scroll.
|
||||
const element = document.getElementsByClassName('mxTooltip');
|
||||
for (let i = 0; i < element.length; i++) {
|
||||
(<HTMLElement>element[i]).style.visibility = 'hidden';
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
this._planContainer.appendChild(diagramContainer);
|
||||
|
||||
this.propertiesView.graphElement = this._graphModel.root;
|
||||
}
|
||||
}
|
||||
|
||||
public get graphModel(): azdata.ExecutionPlanGraph | undefined {
|
||||
return this._graphModel;
|
||||
}
|
||||
|
||||
public openQuery() {
|
||||
return this._instantiationService.invokeFunction(openNewQuery, undefined, this.graphModel.query, RunQueryOnConnectionMode.none).then();
|
||||
}
|
||||
|
||||
public async openGraphFile() {
|
||||
const input = this._untitledEditorService.create({ mode: this.graphModel.graphFile.graphFileType, initialValue: this.graphModel.graphFile.graphFileContent });
|
||||
await input.resolve();
|
||||
await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, input.textEditorModel, FormattingMode.Explicit, Progress.None, CancellationToken.None);
|
||||
input.setDirty(false);
|
||||
this.editorService.openEditor(input);
|
||||
}
|
||||
|
||||
|
||||
public searchNodes(searchId: string): InternalExecutionPlanNode | InternalExecutionPlanEdge | undefined {
|
||||
let stack: InternalExecutionPlanNode[] = [];
|
||||
stack.push(this._graphModel.root);
|
||||
while (stack.length !== 0) {
|
||||
const currentNode = stack.pop();
|
||||
if (currentNode.id === searchId) {
|
||||
return currentNode;
|
||||
}
|
||||
stack.push(...currentNode.children);
|
||||
const resultEdge = currentNode.edges.find(e => (<InternalExecutionPlanEdge>e).id === searchId);
|
||||
if (resultEdge) {
|
||||
return resultEdge;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class OpenQueryAction extends Action {
|
||||
public static ID = 'ep.OpenQueryAction';
|
||||
public static LABEL = localize('openQueryAction', "Open Query");
|
||||
|
||||
constructor() {
|
||||
super(OpenQueryAction.ID, OpenQueryAction.LABEL, openQueryIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.openQuery();
|
||||
}
|
||||
}
|
||||
|
||||
class PropertiesAction extends Action {
|
||||
public static ID = 'ep.propertiesAction';
|
||||
public static LABEL = localize('executionPlanPropertiesActionLabel', "Properties");
|
||||
|
||||
constructor() {
|
||||
super(PropertiesAction.ID, PropertiesAction.LABEL, openPropertiesIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.propertiesView.toggleVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomInAction extends Action {
|
||||
public static ID = 'ep.ZoomInAction';
|
||||
public static LABEL = localize('executionPlanZoomInActionLabel', "Zoom In");
|
||||
|
||||
constructor() {
|
||||
super(ZoomInAction.ID, ZoomInAction.LABEL, zoomInIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.azdataGraphDiagram.graph.zoomIn();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomOutAction extends Action {
|
||||
public static ID = 'ep.ZoomOutAction';
|
||||
public static LABEL = localize('executionPlanZoomOutActionLabel', "Zoom Out");
|
||||
|
||||
constructor() {
|
||||
super(ZoomOutAction.ID, ZoomOutAction.LABEL, zoomOutIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.azdataGraphDiagram.graph.zoomOut();
|
||||
}
|
||||
}
|
||||
|
||||
class ZoomToFitAction extends Action {
|
||||
public static ID = 'ep.FitGraph';
|
||||
public static LABEL = localize('executionPlanFitGraphLabel', "Zoom to fit");
|
||||
|
||||
constructor() {
|
||||
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, zoomToFitIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.azdataGraphDiagram.graph.fit();
|
||||
context.azdataGraphDiagram.graph.view.rendering = true;
|
||||
context.azdataGraphDiagram.graph.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
class SavePlanFile extends Action {
|
||||
public static ID = 'ep.saveXML';
|
||||
public static LABEL = localize('executionPlanSavePlanXML', "Save Plan File");
|
||||
|
||||
constructor() {
|
||||
super(SavePlanFile.ID, SavePlanFile.LABEL, savePlanIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
const workspaceFolders = await context.workspaceContextService.getWorkspace().folders;
|
||||
const defaultFileName = 'plan';
|
||||
let currentWorkSpaceFolder: URI;
|
||||
if (workspaceFolders.length !== 0) {
|
||||
currentWorkSpaceFolder = workspaceFolders[0].uri;
|
||||
currentWorkSpaceFolder = URI.joinPath(currentWorkSpaceFolder, defaultFileName); //appending default file name to workspace uri
|
||||
} else {
|
||||
currentWorkSpaceFolder = URI.parse(defaultFileName); // giving default name
|
||||
}
|
||||
const saveFileUri = await context.fileDialogService.showSaveDialog({
|
||||
filters: [
|
||||
{
|
||||
extensions: ['sqlplan'], //TODO: Get this extension from provider
|
||||
name: localize('executionPlan.SaveFileDescription', 'Execution Plan Files') //TODO: Get the names from providers.
|
||||
}
|
||||
],
|
||||
defaultUri: currentWorkSpaceFolder // If no workspaces are opened this will be undefined
|
||||
});
|
||||
if (saveFileUri) {
|
||||
await context.fileService.writeFile(saveFileUri, VSBuffer.fromString(context.graphModel.graphFile.graphFileContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomZoomAction extends Action {
|
||||
public static ID = 'ep.customZoom';
|
||||
public static LABEL = localize('executionPlanCustomZoom', "Custom Zoom");
|
||||
|
||||
constructor() {
|
||||
super(CustomZoomAction.ID, CustomZoomAction.LABEL, customZoomIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.planActionView.toggleWidget(context._instantiationService.createInstance(CustomZoomWidget, context));
|
||||
}
|
||||
}
|
||||
|
||||
class SearchNodeAction extends Action {
|
||||
public static ID = 'ep.searchNode';
|
||||
public static LABEL = localize('executionPlanSearchNodeAction', "Find Node");
|
||||
|
||||
constructor() {
|
||||
super(SearchNodeAction.ID, SearchNodeAction.LABEL, searchIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
context.planActionView.toggleWidget(context._instantiationService.createInstance(NodeSearchWidget, context));
|
||||
}
|
||||
}
|
||||
|
||||
class OpenPlanFile extends Action {
|
||||
public static ID = 'ep.openGraphFile';
|
||||
public static Label = localize('executionPlanOpenGraphFile', "Show Query Plan XML"); //TODO: add a contribution point for providers to set this text
|
||||
|
||||
constructor() {
|
||||
super(OpenPlanFile.ID, OpenPlanFile.Label, openPlanFileIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlan): Promise<void> {
|
||||
await context.openGraphFile();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const recommendationsColor = theme.getColor(textLinkForeground);
|
||||
if (recommendationsColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .execution-plan .plan .header .recommendations {
|
||||
color: ${recommendationsColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
const shadow = theme.getColor(widgetShadow);
|
||||
if (shadow) {
|
||||
collector.addRule(`
|
||||
.eps-container .execution-plan .plan .plan-action-container .child {
|
||||
box-shadow: 0 0 8px 2px ${shadow};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const menuBackgroundColor = theme.getColor(listHoverBackground);
|
||||
if (menuBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .execution-plan .plan .header,
|
||||
.eps-container .execution-plan .properties .title,
|
||||
.eps-container .execution-plan .properties .table-action-bar {
|
||||
background-color: ${menuBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const widgetBackgroundColor = theme.getColor(editorWidgetBackground);
|
||||
if (widgetBackgroundColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .execution-plan .plan .plan-action-container .child,
|
||||
.mxTooltip {
|
||||
background-color: ${widgetBackgroundColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const widgetBorderColor = theme.getColor(contrastBorder);
|
||||
if (widgetBorderColor) {
|
||||
collector.addRule(`
|
||||
.eps-container .execution-plan .plan .plan-action-container .child,
|
||||
.eps-container .execution-plan .plan .header,
|
||||
.eps-container .execution-plan .properties .title,
|
||||
.eps-container .execution-plan .properties .table-action-bar,
|
||||
.mxTooltip {
|
||||
border: 1px solid ${widgetBorderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const textColor = theme.getColor(foreground);
|
||||
if (textColor) {
|
||||
collector.addRule(`
|
||||
.mxTooltip {
|
||||
color: ${textColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,271 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import type * as azdata from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
|
||||
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { removeLineBreaks } from 'sql/base/common/strings';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
|
||||
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||
|
||||
|
||||
export class ExecutionPlanPropertiesView {
|
||||
|
||||
// Title bar with close button action
|
||||
private _propertiesTitle!: HTMLElement;
|
||||
private _titleText!: HTMLElement;
|
||||
private _titleActionBarContainer!: HTMLElement;
|
||||
private _titleActionBar: ActionBar;
|
||||
|
||||
// Div that holds the name of the element selected
|
||||
private _operationName!: HTMLElement;
|
||||
|
||||
// Action bar that contains sorting option for the table
|
||||
private _tableActionBarContainer!: HTMLElement;
|
||||
private _tableActionBar!: ActionBar;
|
||||
|
||||
// Properties table
|
||||
private _table: Table<Slick.SlickData>;
|
||||
private _dataView: TableDataView<Slick.SlickData>;
|
||||
private _data: { [key: string]: string }[];
|
||||
private _tableContainer!: HTMLElement;
|
||||
private _actualTable!: HTMLElement;
|
||||
|
||||
// Table dimensions.
|
||||
private _tableWidth = 485;
|
||||
private _tableHeight;
|
||||
|
||||
public constructor(
|
||||
private _parentContainer: HTMLElement,
|
||||
private _themeService: IThemeService,
|
||||
private _model: GraphElementPropertyViewData = <GraphElementPropertyViewData>{}
|
||||
) {
|
||||
this._parentContainer.style.display = 'none';
|
||||
|
||||
this._propertiesTitle = DOM.$('.title');
|
||||
this._parentContainer.appendChild(this._propertiesTitle);
|
||||
|
||||
this._titleText = DOM.$('h3');
|
||||
this._titleText.classList.add('text');
|
||||
this._titleText.innerText = localize('nodePropertyViewTitle', "Properties");
|
||||
this._propertiesTitle.appendChild(this._titleText);
|
||||
|
||||
this._titleActionBarContainer = DOM.$('.action-bar');
|
||||
this._propertiesTitle.appendChild(this._titleActionBarContainer);
|
||||
this._titleActionBar = new ActionBar(this._titleActionBarContainer, {
|
||||
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||
});
|
||||
this._titleActionBar.pushAction([new ClosePropertyViewAction()], { icon: true, label: false });
|
||||
|
||||
this._operationName = DOM.$('h3');
|
||||
this._operationName.classList.add('operation-name');
|
||||
this._parentContainer.appendChild(this._operationName);
|
||||
|
||||
this._tableActionBarContainer = DOM.$('.table-action-bar');
|
||||
this._parentContainer.appendChild(this._tableActionBarContainer);
|
||||
this._tableActionBar = new ActionBar(this._tableActionBarContainer, {
|
||||
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||
});
|
||||
this._tableActionBar.pushAction([new SortPropertiesByDisplayOrderAction(), new SortPropertiesAlphabeticallyAction()], { icon: true, label: false });
|
||||
|
||||
|
||||
this._tableContainer = DOM.$('.table-container');
|
||||
this._parentContainer.appendChild(this._tableContainer);
|
||||
|
||||
this._actualTable = DOM.$('.table');
|
||||
this._tableContainer.appendChild(this._actualTable);
|
||||
|
||||
this._dataView = new TableDataView();
|
||||
this._data = [];
|
||||
|
||||
const columns: Slick.Column<Slick.SlickData>[] = [
|
||||
{
|
||||
id: 'name',
|
||||
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
||||
field: 'name',
|
||||
width: 250,
|
||||
editor: Slick.Editors.Text,
|
||||
headerCssClass: 'prop-table-header',
|
||||
formatter: textFormatter
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
|
||||
field: 'value',
|
||||
width: 250,
|
||||
editor: Slick.Editors.Text,
|
||||
headerCssClass: 'prop-table-header',
|
||||
formatter: textFormatter
|
||||
}
|
||||
];
|
||||
|
||||
this._table = new Table(this._actualTable, {
|
||||
dataProvider: this._dataView, columns: columns
|
||||
}, {
|
||||
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
|
||||
forceFitColumns: true,
|
||||
defaultColumnWidth: 120
|
||||
});
|
||||
|
||||
new ResizeObserver((e) => {
|
||||
this.tableHeight = (this._parentContainer.getBoundingClientRect().height - 80);
|
||||
}).observe(this._parentContainer);
|
||||
|
||||
attachTableStyler(this._table, this._themeService);
|
||||
}
|
||||
|
||||
public set graphElement(element: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge) {
|
||||
this._model.graphElement = element;
|
||||
this.sortPropertiesByImportance();
|
||||
this.renderView();
|
||||
}
|
||||
|
||||
public sortPropertiesAlphabetically(): void {
|
||||
this._model.graphElement.properties = this._model.graphElement.properties.sort((a, b) => {
|
||||
if (!a?.name && !b?.name) {
|
||||
return 0;
|
||||
} else if (!a?.name) {
|
||||
return -1;
|
||||
} else if (!b?.name) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
});
|
||||
this.renderView();
|
||||
}
|
||||
|
||||
public sortPropertiesByImportance(): void {
|
||||
this._model.graphElement.properties = this._model.graphElement.properties.sort((a, b) => {
|
||||
if (!a?.displayOrder && !b?.displayOrder) {
|
||||
return 0;
|
||||
} else if (!a?.displayOrder) {
|
||||
return -1;
|
||||
} else if (!b?.displayOrder) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.displayOrder - b.displayOrder;
|
||||
}
|
||||
});
|
||||
this.renderView();
|
||||
}
|
||||
|
||||
public set tableHeight(value: number) {
|
||||
if (this.tableHeight !== value) {
|
||||
this._tableHeight = value;
|
||||
this.renderView();
|
||||
}
|
||||
}
|
||||
|
||||
public get tableHeight(): number {
|
||||
return this._tableHeight;
|
||||
}
|
||||
|
||||
public set tableWidth(value: number) {
|
||||
if (this._tableWidth !== value) {
|
||||
this._tableWidth = value;
|
||||
this.renderView();
|
||||
}
|
||||
}
|
||||
|
||||
public get tableWidth(): number {
|
||||
return this._tableWidth;
|
||||
}
|
||||
|
||||
private renderView(): void {
|
||||
if (this._model.graphElement) {
|
||||
const nodeName = (<azdata.ExecutionPlanNode>this._model.graphElement).name;
|
||||
this._operationName.innerText = nodeName ? removeLineBreaks(nodeName) : localize('executionPlanPropertiesEdgeOperationName', "Edge"); //since edges do not have names like node, we set the operation name to 'Edge'
|
||||
}
|
||||
this._tableContainer.scrollTo(0, 0);
|
||||
this._dataView.clear();
|
||||
this._data = this.convertPropertiesToTableRows(this._model.graphElement.properties, -1, 0);
|
||||
this._dataView.push(this._data);
|
||||
this._table.setData(this._dataView);
|
||||
this._table.autosizeColumns();
|
||||
this._table.updateRowCount();
|
||||
this.tableHeight = (this._parentContainer.getBoundingClientRect().height - 80); //80px is the space taken by the title and toolbar
|
||||
this._table.layout(new DOM.Dimension(this._tableWidth, this._tableHeight));
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
private convertPropertiesToTableRows(props: azdata.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||
if (!props) {
|
||||
return rows;
|
||||
}
|
||||
props.forEach((p, i) => {
|
||||
let row = {};
|
||||
rows.push(row);
|
||||
row['name'] = ' '.repeat(indent) + p.name;
|
||||
row['parent'] = parentIndex;
|
||||
if (!isString(p.value)) {
|
||||
row['value'] = removeLineBreaks(p.displayValue, ' ');
|
||||
this.convertPropertiesToTableRows(p.value, rows.length - 1, indent + 2, rows);
|
||||
} else {
|
||||
row['value'] = removeLineBreaks(p.value, ' ');
|
||||
row['tooltip'] = p.value;
|
||||
}
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
public toggleVisibility(): void {
|
||||
this._parentContainer.style.display = this._parentContainer.style.display === 'none' ? 'block' : 'none';
|
||||
this.renderView();
|
||||
}
|
||||
}
|
||||
|
||||
export interface GraphElementPropertyViewData {
|
||||
graphElement: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge;
|
||||
}
|
||||
|
||||
export class ClosePropertyViewAction extends Action {
|
||||
public static ID = 'ep.propertiesView.close';
|
||||
public static LABEL = localize('executionPlanPropertyViewClose', "Close");
|
||||
|
||||
constructor() {
|
||||
super(ClosePropertyViewAction.ID, ClosePropertyViewAction.LABEL, Codicon.close.classNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
|
||||
context.toggleVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
export class SortPropertiesAlphabeticallyAction extends Action {
|
||||
public static ID = 'ep.propertiesView.sortByAlphabet';
|
||||
public static LABEL = localize('executionPlanPropertyViewSortAlphabetically', "Alphabetical");
|
||||
|
||||
constructor() {
|
||||
super(SortPropertiesAlphabeticallyAction.ID, SortPropertiesAlphabeticallyAction.LABEL, sortAlphabeticallyIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
|
||||
context.sortPropertiesAlphabetically();
|
||||
}
|
||||
}
|
||||
|
||||
export class SortPropertiesByDisplayOrderAction extends Action {
|
||||
public static ID = 'ep.propertiesView.sortByDisplayOrder';
|
||||
public static LABEL = localize('executionPlanPropertyViewSortByDisplayOrder', "Categorized");
|
||||
|
||||
constructor() {
|
||||
super(SortPropertiesByDisplayOrderAction.ID, SortPropertiesByDisplayOrderAction.LABEL, sortByDisplayOrderIconClassNames);
|
||||
}
|
||||
|
||||
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
|
||||
context.sortPropertiesByImportance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export abstract class ExecutionPlanWidgetBase {
|
||||
/**
|
||||
*
|
||||
* @param container HTML Element that contains the UI for the plan action view.
|
||||
* @param identifier Uniquely identify the view to be added or removed. Note: Only 1 view with the same id can be added to the controller
|
||||
*/
|
||||
constructor(public container: HTMLElement, public identifier: string) {
|
||||
this.container = container;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the view is added to PlanActionView.
|
||||
* Generally, the view should focus the first input element in the view
|
||||
*/
|
||||
public abstract focus();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExecutionPlanWidgetBase as ExecutionPlanWidgetBase } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetBase';
|
||||
|
||||
export class ExecutionPlanWidgetController {
|
||||
private _executionPlanWidgetMap: Map<string, ExecutionPlanWidgetBase> = new Map();
|
||||
|
||||
constructor(private _parentContainer: HTMLElement) {
|
||||
|
||||
}
|
||||
|
||||
private addWidget(widget: ExecutionPlanWidgetBase) {
|
||||
if (widget.identifier && !this._executionPlanWidgetMap.has(widget.identifier)) {
|
||||
this._executionPlanWidgetMap.set(widget.identifier, widget);
|
||||
if (widget.container) {
|
||||
widget.container.classList.add('child');
|
||||
this._parentContainer.appendChild(widget.container);
|
||||
widget.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeWidget(widget: ExecutionPlanWidgetBase) {
|
||||
if (widget.identifier) {
|
||||
if (this._executionPlanWidgetMap.has(widget.identifier)) {
|
||||
this._parentContainer.removeChild(this._executionPlanWidgetMap.get(widget.identifier).container);
|
||||
this._executionPlanWidgetMap.delete(widget.identifier);
|
||||
} else {
|
||||
throw new Error('The view is not present in the container');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes view from the controller.
|
||||
* @param widget PlanActionView to be added.
|
||||
*/
|
||||
public toggleWidget(widget: ExecutionPlanWidgetBase) {
|
||||
if (!this._executionPlanWidgetMap.has(widget.identifier)) {
|
||||
this.addWidget(widget);
|
||||
} else {
|
||||
this.removeWidget(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#F6F6F6;}
|
||||
.st2{fill:#424242;}
|
||||
.st3{fill:#F0EFF1;}
|
||||
</style>
|
||||
<g id="canvas">
|
||||
<path class="st0" d="M16,16H0V0h16V16z"/>
|
||||
</g>
|
||||
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
|
||||
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
|
||||
C15.6,8.1,16,7,16,5.8z"/>
|
||||
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
|
||||
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
|
||||
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
|
||||
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
|
||||
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
|
||||
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#F6F6F6;}
|
||||
.st2{fill:#424242;}
|
||||
.st3{fill:#F0EFF1;}
|
||||
</style>
|
||||
<g id="canvas">
|
||||
<path class="st0" d="M16,16H0V0h16V16z"/>
|
||||
</g>
|
||||
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
|
||||
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
|
||||
C15.6,8.1,16,7,16,5.8z"/>
|
||||
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
|
||||
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
|
||||
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
|
||||
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
|
||||
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
|
||||
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;}
|
||||
.st1{fill:#424242;}
|
||||
.st2{fill:#00539C;}
|
||||
</style>
|
||||
<path id="outline" class="st0" d="M12.2,8L12.2,8h-0.5h-1.3H9H6.5L10,4.5L6.7,1.2L5.9,0.5L3.8,2.6L4.2,3H3.5C1.6,3,0,4.6,0,6.5
|
||||
S1.6,10,3.5,10h0.3l-2,2l4,4h0.1h0.4h1.3H9h2.7h0.5h0l3.8-3.8v-0.4L12.2,8z"/>
|
||||
<path id="iconBg" class="st1" d="M7.3,14.8l0.9,0.4l2.4-6L9.7,8.8L7.3,14.8z M6.1,9.1L5.2,10h0l-2,2l2.9,2.9l0.7-0.7L4.6,12l2-2h0
|
||||
l0.1-0.1L6.1,9.1z M12.8,10L12.8,10l-0.9-0.9l-0.7,0.7l0.1,0.1h0l2,2l-2.1,2.1l0.7,0.7l2.9-2.9L12.8,10z"/>
|
||||
<path id="colorAction" class="st2" d="M1,6.5C1,5.1,2.1,4,3.5,4h3.1L5.2,2.6l0.7-0.7l0,0l0,0l2.6,2.6L5.9,7.2l0,0L5.2,6.5L6.6,5H3.5
|
||||
C2.7,5,2,5.7,2,6.5S2.7,8,3.5,8H4v1H3.5C2.1,9,1,7.9,1,6.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;} .icon-vs-fg{fill:#F0EFF1;}</style><rect class="icon-canvas-transparent" viewBox="0 0 16 16" id="canvas"/><rect class="icon-vs-out" width="16" height="15" id="outline"/><path class="icon-vs-bg" d="M1 1v13h14v-13h-14zm13 12h-12v-11h12v11zm-4-8h-6v-1h6v1zm-6 1h8v1h-8v-1zm6 3h-6v-1h6v1zm-6 1h8v1h-8v-1z" id="iconBg"/><path class="icon-vs-fg" d="M2 2v11h12v-11h-12zm2 2h6v1h-6v-1zm0 4h6v1h-6v-1zm8 3h-8v-1h8v1zm0-4h-8v-1h8v1z" id="iconFg"/></svg>
|
||||
|
After Width: | Height: | Size: 630 B |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#F6F6F6;}
|
||||
.st2{fill:#F0EFF1;}
|
||||
.st3{fill:#424242;}
|
||||
.st4{fill:#00539C;}
|
||||
</style>
|
||||
<path id="canvas" class="st0" d="M16,16H0V0h16V16z"/>
|
||||
<path id="outline" class="st1" d="M7.9,0H6.5h-1h-1H2v2.4C1.7,2.5,1.3,2.7,1.1,3H0v13h14v-3h2V0H7.9z"/>
|
||||
<path id="iconFg" class="st2" d="M9.2,2.8L10,3.5l-4.1,4L5,6.7V9H3.5C2.9,9,2.3,8.8,1.8,8.6v5.7h10.5v-3h2V2.8H9.2z"/>
|
||||
<path id="iconBg" class="st3" d="M11,8H5v1h6V8z M7.4,1l2,2H14v8h-1V4H9.5l-2,2H12v5H4V9H3.5C3.3,9,3.2,9,3,8.9V12h9v2H2V8.6
|
||||
C1.6,8.5,1.3,8.2,1,7.9V15h12v-3h2V1H7.4z"/>
|
||||
<path id="colorAction" class="st4" d="M1,5.5C1,4.1,2.1,3,3.5,3h3.1L5.2,1.6l0.7-0.7l0,0l0,0l2.6,2.6L5.9,6.2l0,0L5.2,5.5L6.6,4H3.5
|
||||
C2.7,4,2,4.7,2,5.5S2.7,7,3.5,7H4v1H3.5C2.1,8,1,6.9,1,5.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>Save_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,2V16H2.586L0,13.414V2A2,2,0,0,1,2,0H14A2,2,0,0,1,16,2Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M6,12H8v3H6ZM15,2V15H12V10H4v5H3L1,13V2A1,1,0,0,1,2,1H14A1,1,0,0,1,15,2ZM13,3H3V7H13Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 570 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#f0eff1;}.icon-vs-bg{fill:#424242;}</style></defs><title>Search_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,5.833a5.84,5.84,0,0,1-5.833,5.834,5.688,5.688,0,0,1-2.913-.8L2.561,15.561A1.5,1.5,0,0,1,.439,13.439L5.133,8.745a5.694,5.694,0,0,1-.8-2.912A5.834,5.834,0,0,1,16,5.833Z"/></g><g id="iconFg"><path class="icon-vs-fg" d="M14,5.833A3.834,3.834,0,1,1,10.167,2,3.838,3.838,0,0,1,14,5.833Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M10.167,1A4.839,4.839,0,0,0,5.333,5.833a4.786,4.786,0,0,0,1.1,3.029L1.147,14.146a.5.5,0,0,0,.707.708L7.138,9.569a4.783,4.783,0,0,0,3.029,1.1,4.834,4.834,0,0,0,0-9.667Zm0,8.667A3.834,3.834,0,1,1,14,5.833,3.838,3.838,0,0,1,10.167,9.667Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 956 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.icon-canvas-transparent{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-white{fill:#fff;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>SortAscending_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="cls-1" d="M16,9.983l-4.467,4.484-3.3-3.3L6.449,13H8v3H1V13.586L2.586,12H1V9H0V0H9V7.692l1.018,1.014V3.027h3v5.7l1.148-1.145L16,9.412Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M2,11H5L2,14v1H7V14H4.013L7,11.051,6.952,11H7V10H2ZM4.5,3.25,5.2,5H3.8ZM8,1V8H1V1ZM7,7,5,2H4L2,7H3l.4-1H5.6L6,7Z"/></g><g id="iconFg"><path class="icon-white" d="M6,7H7L5,2H4L2,7H3l.4-1H5.6ZM3.8,5l.7-1.75L5.2,5Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M14.154,8.968l.707.707-3.34,3.352L8.169,9.675l.707-.707,2.13,2.122V4h1v7.1Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 938 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-blue{fill:#1ba1e2}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M5 4v1.6c.2.1.3.3.4.4H15v3H6v2h9v3H5.6c0 .3-.1.5-.2.6-.1.3-.3.6-.5.8-.2.2-.5.4-.8.5-.2 0-.4.1-.6.1H2.2c-.1 0-.3-.1-.4-.1-.2-.1-.4-.2-.5-.3l-.3-.2V8.9S1.6 8 2.1 8H1V6.3c0-.5.3-.9 1-1.1V2.7l-1 .2V1l2.8-1H5v1h10v3H5z" id="outline"/><path class="icon-vs-bg" d="M14 3H6V2h8v1zm0 4H6v1h8V7zm0 5H6v1h8v-1z" id="iconBg"/><path class="icon-vs-blue" d="M4.5 5V4H4V1l-1.6.4v.7l.6-.2V4h-.4v1h1.9zM3.4 9.1s1-1.2 1.1-1.3c.1-.1.1-.2.1-.3v-.3c0-.2 0-.3-.1-.4 0-.3 0-.4-.2-.5-.1-.1-.2-.2-.3-.2-.2-.1-.4-.1-.6-.1H3c-.1 0-1 .3-1 .3v1c0-.1.6-.3.7-.4.1 0 .2-.1.3-.1h.6c.1 0 .2.2.2.4 0 .1-.1.4-.3.6C3.3 7.9 2 9.3 2 9.3v.7h2.6v-.9H3.4zm1.2 4.2c-.2-.4-.6-.5-.6-.5.7-.1.6-.9.6-1 0-.1 0-.2-.1-.4 0-.1-.1-.2-.2-.3-.1-.1-.2-.2-.4-.2-.2-.1-.4-.1-.6-.1H3c-.1 0-.2 0-.3.1-.1 0-.2 0-.2.1-.3-.1-.5 0-.5 0v.7c.5-.3 1.1-.2 1.2-.2.5.1.2.8.1.8s-.1.1-.3.1H2.4v.7h.8c.1 0 .2.1.3.1.1 0 .1.1.2.2 0 .1.1.2.1.3 0 .2-.1.3-.2.4-.1.1-.3.2-.6.2h-.2c-.1 0-.2 0-.3-.1-.1 0-.2-.1-.2-.1-.2 0-.2-.1-.3-.1v.8c.1 0 .1.1.2.1s.2 0 .3.1h.6c.3 0 .5 0 .7-.1.2-.1.4-.2.5-.3.1-.1.2-.2.3-.4 0 0 .1-.5 0-.9z" id="colorImportance"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#F6F6F6;}
|
||||
.st2{fill:#424242;}
|
||||
</style>
|
||||
<g id="canvas">
|
||||
<path class="st0" d="M16,0v16H0V0H16z"/>
|
||||
</g>
|
||||
<g id="outline">
|
||||
<path class="st1" d="M14.5,16c-0.4,0-0.8-0.2-1.1-0.4L9.9,12C6.8,13.9,2.8,13,1,9.9S0,2.8,3.1,1s7.1-0.9,9,2.1
|
||||
c1.3,2.1,1.3,4.7,0,6.8l3.5,3.5c0.6,0.6,0.6,1.5,0,2.1C15.3,15.8,14.9,16,14.5,16z"/>
|
||||
</g>
|
||||
<g id="iconBg">
|
||||
<path class="st2" d="M14.9,14.1L10.7,10c1.9-2.3,1.6-5.8-0.7-7.8S4.2,0.6,2.3,3S0.6,8.8,3,10.7c2,1.7,5,1.7,7.1,0l4.1,4.1
|
||||
c0.2,0.2,0.5,0.2,0.7,0S15,14.3,14.9,14.1z M6.5,11C4,11,2,9,2,6.5S4,2,6.5,2S11,4,11,6.5C11,9,9,11,6.5,11z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 993 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>ZoomIn_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14.5,16a1.494,1.494,0,0,1-1.061-.439L9.909,12.03A6.51,6.51,0,1,1,12.03,9.909l3.531,3.53A1.5,1.5,0,0,1,14.5,16Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M14.854,14.146l-4.13-4.129a5.509,5.509,0,1,0-.707.707l4.129,4.13a.5.5,0,0,0,.708-.708ZM6.5,11A4.5,4.5,0,1,1,11,6.5,4.505,4.505,0,0,1,6.5,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M9,6V7H7V9H6V7H4V6H6V4H7V6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 788 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>ZoomOut_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14.5,16a1.494,1.494,0,0,1-1.061-.439L9.909,12.03A6.51,6.51,0,1,1,12.03,9.909l3.531,3.53A1.5,1.5,0,0,1,14.5,16Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M14.854,14.146l-4.13-4.129a5.509,5.509,0,1,0-.707.707l4.129,4.13a.5.5,0,0,0,.708-.708ZM6.5,11A4.5,4.5,0,1,1,11,6.5,4.505,4.505,0,0,1,6.5,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M9,6V7H4V6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 773 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>ZoomToFit_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="icon-vs-out" d="M12.977,15s1.03.98,1.053,1h1.944c.008-.007.018-.009.026-.016V13.755l-1-.972V3H13V0H2V3H0V13H2v2Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15.349,14.517l-4.257-4.139A4.581,4.581,0,0,0,12.125,7.5,4.625,4.625,0,1,0,7.5,12.126a4.579,4.579,0,0,0,2.885-1.04l4.266,4.147a.5.5,0,0,0,.7-.716ZM3.875,7.5A3.625,3.625,0,1,1,7.5,11.126,3.629,3.629,0,0,1,3.875,7.5ZM8,7h2V8H8v2H7V8H5V7H7V5H8ZM1,4H2v8H1ZM12,2H3V1h9ZM3,13h9v1H3Zm11-1H13V4h1Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 800 B |
|
After Width: | Height: | Size: 846 B |
|
After Width: | Height: | Size: 851 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 867 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 908 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 980 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 966 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 843 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 783 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |