Adding inline actions to OE (#23101)

This commit is contained in:
Aasim Khan
2023-05-13 09:24:49 -07:00
committed by GitHub
parent 31a88cc9eb
commit 2beba9ac08
18 changed files with 279 additions and 66 deletions

View File

@@ -24,6 +24,7 @@ import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/comm
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
const DefaultConnectionIconClass = 'server-page';
export interface ConnectionProfileGroupDisplayOptions {
@@ -35,11 +36,13 @@ class ConnectionProfileGroupTemplate extends Disposable {
private _icon: HTMLElement;
private _labelContainer: HTMLElement;
private _label: ResourceLabel;
private _actionBar: ActionBar;
constructor(
container: HTMLElement,
private _option: ConnectionProfileGroupDisplayOptions,
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) {
super();
container.parentElement!.classList.add('async-server-group');
@@ -48,6 +51,9 @@ class ConnectionProfileGroupTemplate extends Disposable {
this._icon = dom.append(this._root, dom.$('div.icon'));
this._labelContainer = dom.append(this._root, dom.$('span.name'));
this._label = this._instantiationService.createInstance(ResourceLabel, this._labelContainer, { supportHighlights: true });
const actionsContainer = dom.append(this._label.element.element, dom.$('.actions'));
this._actionBar = new ActionBar(actionsContainer, {
});
}
set(element: ConnectionProfileGroup, filterData: FuzzyScore) {
@@ -63,6 +69,13 @@ class ConnectionProfileGroupTemplate extends Disposable {
this._label.element.setLabel(element.name, '', {
matches: createMatches(filterData)
});
const actionProvider = this._objectExplorerService.getServerTreeView().treeActionProvider;
const tree = this._objectExplorerService.getServerTreeView().tree;
const actions = actionProvider.getActions(tree, element, true);
this._actionBar.context = this._objectExplorerService.getServerTreeView().getActionContext(element);
this._actionBar.clear();
this._actionBar.pushAction(actions, { icon: true, label: false });
}
}
@@ -91,6 +104,8 @@ class ConnectionProfileTemplate extends Disposable {
private _connectionStatusBadge: HTMLElement;
private _labelContainer: HTMLElement;
private _label: ResourceLabel;
private _actionBar: ActionBar;
/**
* _isCompact is used to render connections tiles with and without the action buttons.
* When set to true, like in the connection dialog recent connections tree, the connection
@@ -110,6 +125,9 @@ class ConnectionProfileTemplate extends Disposable {
this._connectionStatusBadge = dom.append(this._icon, dom.$('div.connection-status-badge'));
this._labelContainer = dom.append(this._root, dom.$('div.label'));
this._label = this._instantiationService.createInstance(ResourceLabel, this._labelContainer, { supportHighlights: true });
const actionsContainer = dom.append(this._label.element.element, dom.$('.actions'));
this._actionBar = new ActionBar(actionsContainer, {
});
}
set(element: ConnectionProfile, filterData: FuzzyScore) {
@@ -132,6 +150,19 @@ class ConnectionProfileTemplate extends Disposable {
matches: createMatches(filterData)
});
this._root.title = labelText;
const actionProvider = this._objectExplorerService.getServerTreeView().treeActionProvider;
if (!this._isCompact) {
const tree = this._objectExplorerService.getServerTreeView().tree;
const actions = actionProvider.getActions(tree, element, true);
this._actionBar.context = this._objectExplorerService.getServerTreeView().getActionContext(element);
this._actionBar.clear();
this._actionBar.pushAction(actions, { icon: true, label: false });
} else {
const actions = actionProvider.getRecentConnectionActions(element);
this._actionBar.context = undefined;
this._actionBar.clear();
this._actionBar.pushAction(actions, { icon: true, label: false });
}
}
}
@@ -159,16 +190,21 @@ class TreeNodeTemplate extends Disposable {
private _icon: HTMLElement;
private _labelContainer: HTMLElement;
private _label: ResourceLabel;
private _actionBar: ActionBar;
constructor(
container: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) {
super();
this._root = dom.append(container, dom.$('.object-element-container'));
this._icon = dom.append(this._root, dom.$('div.object-icon'));
this._labelContainer = dom.append(this._root, dom.$('div.label'));
this._label = this._instantiationService.createInstance(ResourceLabel, this._labelContainer, { supportHighlights: true });
const actionsContainer = dom.append(this._label.element.element, dom.$('.actions'));
this._actionBar = new ActionBar(actionsContainer, {
});
}
set(element: TreeNode, filterData: FuzzyScore) {
@@ -211,6 +247,12 @@ class TreeNodeTemplate extends Disposable {
matches: createMatches(filterData)
});
this._root.title = labelText;
const tree = this._objectExplorerService.getServerTreeView().tree;
const actionProvider = this._objectExplorerService.getServerTreeView().treeActionProvider;
const actions = actionProvider.getActions(tree, element, true);
this._actionBar.context = this._objectExplorerService.getServerTreeView().getActionContext(element);
this._actionBar.clear();
this._actionBar.pushAction(actions, { icon: true, label: false });
}
}

View File

@@ -21,6 +21,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
import { SqlIconId } from 'sql/base/common/codicons';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Codicon } from 'vs/base/common/codicons';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
@@ -44,7 +45,7 @@ export class RefreshAction extends Action {
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@ILogService private _logService: ILogService
) {
super(id, label);
super(id, label, Codicon.refresh.classNames);
}
public override async run(): Promise<void> {
let treeNode: TreeNode | undefined = undefined;
@@ -103,8 +104,7 @@ export class EditConnectionAction extends Action {
private _connectionProfile: ConnectionProfile,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
this.class = 'edit-server-action';
super(id, label, Codicon.edit.classNames);
}
public override async run(): Promise<void> {
@@ -124,7 +124,7 @@ export class DisconnectConnectionAction extends Action {
private _connectionProfile: ConnectionProfile,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
super(id, label, Codicon.debugDisconnect.classNames);
}
override async run(actionContext: ObjectExplorerActionsContext): Promise<any> {
@@ -223,8 +223,7 @@ export class EditServerGroupAction extends Action {
private _group: ConnectionProfileGroup,
@IServerGroupController private readonly serverGroupController: IServerGroupController
) {
super(id, label);
this.class = 'edit-server-group-action';
super(id, label, Codicon.edit.classNames);
}
public override run(): Promise<void> {
@@ -277,8 +276,7 @@ export class DeleteConnectionAction extends Action {
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IDialogService private _dialogService: IDialogService
) {
super(id, label);
this.class = 'delete-connection-action';
super(id, label, Codicon.trash.classNames);
if (element instanceof ConnectionProfileGroup && element.id === UNSAVED_GROUP_ID) {
this.enabled = false;
}
@@ -322,14 +320,19 @@ export class FilterChildrenAction extends Action {
label: string,
private _node: TreeNode,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService) {
super(id, label);
super(id, label, getFilterActionIconClass(_node));
}
public override async run(): Promise<void> {
await this._objectExplorerService.getServerTreeView().filterElementChildren(this._node);
this.class = getFilterActionIconClass(this._node);
}
}
function getFilterActionIconClass(node: TreeNode): string {
return node.filters.length > 0 ? Codicon.filterFilled.classNames : Codicon.filter.classNames;
}
export class RemoveFilterAction extends Action {
public static ID = 'objectExplorer.removeFilter';
public static LABEL = localize('objectExplorer.removeFilter', "Remove Filter");
@@ -343,7 +346,7 @@ export class RemoveFilterAction extends Action {
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
) {
super(id, label);
super(id, label, SqlIconId.removeFilter);
}
public override async run(): Promise<void> {
@@ -373,3 +376,23 @@ export class RemoveFilterAction extends Action {
}).send();
}
}
export class DeleteRecentConnectionsAction extends Action {
public static ID = 'registeredServers.clearRecentConnections';
public static LABEL = localize('registeredServers.clearRecentConnections', "Delete");
constructor(
id: string,
label: string,
private _connectionProfile: ConnectionProfile,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label, Codicon.trash.classNames);
}
public override async run(): Promise<void> {
if (this._connectionProfile) {
this._connectionManagementService.clearRecentConnection(this._connectionProfile);
}
}
}

View File

@@ -56,6 +56,7 @@ export interface IServerTreeView {
layout(size: number): void;
showFilteredTree(view: ServerTreeViewView): void;
filterElementChildren(node: TreeNode): Promise<void>;
getActionContext(element: ServerTreeElement): any;
view: ServerTreeViewView;
}

View File

@@ -10,7 +10,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import {
DisconnectConnectionAction, EditConnectionAction,
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction, FilterChildrenAction, RemoveFilterAction
DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction, FilterChildrenAction, RemoveFilterAction, DeleteRecentConnectionsAction
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
@@ -52,9 +52,9 @@ export class ServerTreeActionProvider {
/**
* Return actions given an element in the tree
*/
public getActions(tree: AsyncServerTree | ITree, element: ServerTreeElement): IAction[] {
public getActions(tree: AsyncServerTree | ITree, element: ServerTreeElement, inlineOnly: boolean = false): IAction[] {
if (element instanceof ConnectionProfile) {
return this.getConnectionActions(tree, element);
return this.getConnectionActions(tree, element, inlineOnly);
}
if (element instanceof ConnectionProfileGroup) {
return this.getConnectionProfileGroupActions(element);
@@ -66,7 +66,7 @@ export class ServerTreeActionProvider {
tree: tree,
profile,
treeNode: element
});
}, inlineOnly);
}
}
return [];
@@ -95,10 +95,18 @@ export class ServerTreeActionProvider {
return undefined;
}
public getRecentConnectionActions(element: ConnectionProfile): IAction[] {
return [
this._instantiationService.createInstance(EditConnectionAction, EditConnectionAction.ID, EditConnectionAction.LABEL, element),
this._instantiationService.createInstance(DeleteRecentConnectionsAction, DeleteRecentConnectionsAction.ID, DeleteRecentConnectionsAction.LABEL, element)
]
}
/**
* Return actions for connection elements
*/
private getConnectionActions(tree: AsyncServerTree | ITree, profile: ConnectionProfile): IAction[] {
private getConnectionActions(tree: AsyncServerTree | ITree, profile: ConnectionProfile, inlineOnly: boolean = false): IAction[] {
let node = new TreeNode(NodeType.Server, NodeType.Server, '', false, '', '', '', '', undefined, undefined, undefined, undefined);
// Only update password and not access tokens to avoid login prompts when opening context menu.
this._connectionManagementService.addSavedPassword(profile, true);
@@ -107,10 +115,11 @@ export class ServerTreeActionProvider {
tree: tree,
profile: profile,
treeNode: node
}, (context) => this.getBuiltinConnectionActions(context));
}, (context) => this.getBuiltinConnectionActions(context),
inlineOnly);
}
private getAllActions(context: ObjectExplorerContext, getDefaultActions: (context: ObjectExplorerContext) => IAction[]) {
private getAllActions(context: ObjectExplorerContext, getDefaultActions: (context: ObjectExplorerContext) => IAction[], inlineOnly: boolean = false) {
// Create metadata needed to get a useful set of actions
let scopedContextService = this.getContextKeyService(context);
let menu = this.menuService.createMenu(MenuId.ObjectExplorerItemContext, scopedContextService);
@@ -118,8 +127,11 @@ export class ServerTreeActionProvider {
// Fill in all actions
const builtIn = getDefaultActions(context);
const actions: IAction[] = [];
const options = { arg: undefined, shouldForwardArgs: true };
const groups = menu.getActions(options);
const options = {
arg: undefined, shouldForwardArgs: true
};
let groups = menu.getActions(options);
let insertIndex: number | undefined = 0;
const queryIndex = groups.findIndex(v => {
if (v[0] === '0_query') {
@@ -132,24 +144,36 @@ export class ServerTreeActionProvider {
}
});
insertIndex = queryIndex > -1 ? insertIndex + groups[queryIndex][1].length : undefined;
fillInActions(groups, actions, false);
if (insertIndex) {
if (!(actions[insertIndex] instanceof Separator) && builtIn.length > 0) {
builtIn.unshift(new Separator());
}
actions?.splice(insertIndex, 0, ...builtIn);
} else {
if (actions.length > 0 && builtIn.length > 0) {
builtIn.push(new Separator());
}
if (inlineOnly) {
groups = groups.filter(g => g[0].includes('inline'));
fillInActions(groups, actions, false);
actions.unshift(...builtIn);
// Moving refresh action to the end of the list
const refreshIndex = actions.findIndex(f => {
return f instanceof RefreshAction;
});
if (refreshIndex > -1) {
actions.push(actions.splice(refreshIndex, 1)[0]);
}
} else {
fillInActions(groups, actions, false);
if (insertIndex) {
if (!(actions[insertIndex] instanceof Separator) && builtIn.length > 0 && !inlineOnly) {
builtIn.unshift(new Separator());
}
actions?.splice(insertIndex, 0, ...builtIn);
} else {
if (actions.length > 0 && builtIn.length > 0) {
builtIn.push(new Separator());
}
actions.unshift(...builtIn);
}
}
// Cleanup
menu.dispose();
return actions;
}
private getBuiltinConnectionActions(context: ObjectExplorerContext): IAction[] {
@@ -209,8 +233,8 @@ export class ServerTreeActionProvider {
/**
* Return actions for OE elements
*/
private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] {
return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context));
private getObjectExplorerNodeActions(context: ObjectExplorerContext, inlineOnly: boolean = false): IAction[] {
return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context), inlineOnly);
}
private getBuiltInNodeActions(context: ObjectExplorerContext): IAction[] {
@@ -226,16 +250,15 @@ export class ServerTreeActionProvider {
}
// Contribute refresh action for scriptable objects via contribution
if (!this.isScriptableObject(context)) {
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.treeNode || context.profile));
// Adding filter action if the node has filter properties
if (treeNode?.filterProperties?.length > 0 && this._configurationService.getValue<boolean>(CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES)) {
actions.push(this._instantiationService.createInstance(FilterChildrenAction, FilterChildrenAction.ID, FilterChildrenAction.LABEL, context.treeNode));
}
// Adding remove filter action if the node has filters applied to it.
// Adding remove filter action if the node has filters applied to it and the action is not inline only.
if (treeNode?.filters?.length > 0) {
actions.push(this._instantiationService.createInstance(RemoveFilterAction, RemoveFilterAction.ID, RemoveFilterAction.LABEL, context.treeNode, context.tree, undefined));
}
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.treeNode || context.profile));
}
return actions;
}