Add additional actions to CommentThread (#162750)

* # Add additional actions to CommentThread

* Add in forgotten property

* # missing declaration

* type change

* Fix merge conflict errors

* # add proposed changes + fix styling

* # Allow "secondary action only" buttons

* # Add dropdown button styling fixes

* # add default button styling

* # add better styling to the dropdown button

* Remove duplicate css rule

Co-authored-by: Alex Ross <alros@microsoft.com>
This commit is contained in:
Marcus Revaj
2022-11-17 11:16:06 +01:00
committed by GitHub
parent f675b2bfab
commit aaac6f9a99
14 changed files with 214 additions and 17 deletions

View File

@@ -69,6 +69,11 @@
.monaco-button-dropdown > .monaco-button.monaco-dropdown-button {
border-left-width: 0 !important;
border-radius: 0 2px 2px 0;
}
.monaco-button-dropdown > .monaco-button.monaco-text-button {
border-radius: 2px 0 0 2px;
}
.monaco-description-button {

View File

@@ -257,9 +257,10 @@ export class ButtonWithDropdown extends Disposable implements IButton {
this.separatorContainer.style.borderTop = '1px solid ' + border;
this.separatorContainer.style.borderBottom = '1px solid ' + border;
}
this.separatorContainer.style.backgroundColor = options.buttonBackground ?? '';
this.separator.style.backgroundColor = options.buttonSeparator ?? '';
const buttonBackground = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground;
this.separatorContainer.style.backgroundColor = buttonBackground ?? '';
this.separator.style.backgroundColor = options.buttonSeparator ?? '';
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');

View File

@@ -12,6 +12,7 @@ export const enum MarshalledId {
ScmProvider,
CommentController,
CommentThread,
CommentThreadInstance,
CommentThreadReply,
CommentNode,
CommentThreadNode,

View File

@@ -128,6 +128,7 @@ export class MenuId {
static readonly ViewTitleContext = new MenuId('ViewTitleContext');
static readonly CommentThreadTitle = new MenuId('CommentThreadTitle');
static readonly CommentThreadActions = new MenuId('CommentThreadActions');
static readonly CommentThreadAdditionalActions = new MenuId('CommentThreadAdditionalActions');
static readonly CommentThreadTitleContext = new MenuId('CommentThreadTitleContext');
static readonly CommentThreadCommentContext = new MenuId('CommentThreadCommentContext');
static readonly CommentTitle = new MenuId('CommentTitle');

View File

@@ -66,7 +66,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
}
return commentThread.value;
} else if (arg && arg.$mid === MarshalledId.CommentThreadReply) {
} else if (arg && (arg.$mid === MarshalledId.CommentThreadReply || arg.$mid === MarshalledId.CommentThreadInstance)) {
const commentController = this._commentControllers.get(arg.thread.commentControlHandle);
if (!commentController) {
@@ -79,6 +79,10 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
return arg;
}
if (arg.$mid === MarshalledId.CommentThreadInstance) {
return commentThread.value;
}
return {
thread: commentThread.value,
text: arg.text

View File

@@ -3,11 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Button } from 'vs/base/browser/ui/button/button';
import { Button, ButtonWithDropdown, IButton } from 'vs/base/browser/ui/button/button';
import { IAction } from 'vs/base/common/actions';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IMenu } from 'vs/platform/actions/common/actions';
import { IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
export class CommentFormActions implements IDisposable {
private _buttonElements: HTMLElement[] = [];
@@ -17,29 +18,49 @@ export class CommentFormActions implements IDisposable {
constructor(
private container: HTMLElement,
private actionHandler: (action: IAction) => void,
private contextMenuService?: IContextMenuService
) { }
setActions(menu: IMenu) {
setActions(menu: IMenu, hasOnlySecondaryActions: boolean = false) {
this._toDispose.clear();
this._buttonElements.forEach(b => b.remove());
const groups = menu.getActions({ shouldForwardArgs: true });
let isPrimary: boolean = true;
let isPrimary: boolean = !hasOnlySecondaryActions;
for (const group of groups) {
const [, actions] = group;
this._actions = actions;
for (const action of actions) {
const button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles });
const submenuAction = action as SubmenuItemAction;
// Use the first action from the submenu as the primary button.
const appliedAction: IAction = submenuAction.actions?.length > 0 ? submenuAction.actions[0] : action;
let button: IButton | undefined;
// Use dropdown only if submenu contains more than 1 action.
if (submenuAction.actions?.length > 1 && this.contextMenuService) {
button = new ButtonWithDropdown(this.container,
{
contextMenuProvider: this.contextMenuService,
actions: submenuAction.actions.slice(1),
addPrimaryActionToDropdown: false,
secondary: !isPrimary,
...defaultButtonStyles
});
} else {
button = new Button(this.container, { secondary: !isPrimary, ...defaultButtonStyles });
}
isPrimary = false;
this._buttonElements.push(button.element);
this._toDispose.add(button);
this._toDispose.add(button.onDidClick(() => this.actionHandler(action)));
this._toDispose.add(button.onDidClick(() => this.actionHandler(appliedAction)));
button.enabled = action.enabled;
button.label = action.label;
button.enabled = appliedAction.enabled;
button.label = appliedAction.label;
}
}
}

View File

@@ -23,6 +23,10 @@ export class CommentMenus implements IDisposable {
return this.getMenu(MenuId.CommentThreadActions, contextKeyService);
}
getCommentThreadAdditionalActions(contextKeyService: IContextKeyService): IMenu {
return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService);
}
getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IMenu {
return this.getMenu(MenuId.CommentTitle, contextKeyService);
}

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { IAction } from 'vs/base/common/actions';
import { IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { Disposable } from 'vs/base/common/lifecycle';
import { MarshalledId } from 'vs/base/common/marshallingIds';
import { IRange } from 'vs/editor/common/core/range';
import * as languages from 'vs/editor/common/languages';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions';
import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
export class CommentThreadAdditionalActions<T extends IRange | ICellRange> extends Disposable {
private _container: HTMLElement | null;
private _buttonBar: HTMLElement | null;
private _commentFormActions!: CommentFormActions;
constructor(
container: HTMLElement,
private _commentThread: languages.CommentThread<T>,
private _contextKeyService: IContextKeyService,
private _commentMenus: CommentMenus,
private _actionRunDelegate: (() => void) | null,
@IContextMenuService private contextMenuService: IContextMenuService,
) {
super();
this._container = dom.append(container, dom.$('.comment-additional-actions'));
dom.append(this._container, dom.$('.section-separator'));
this._buttonBar = dom.append(this._container, dom.$('.button-bar'));
this._createAdditionalActions(this._buttonBar);
}
private _showMenu() {
this._container?.classList.remove('hidden');
}
private _hideMenu() {
this._container?.classList.add('hidden');
}
private _enableDisableMenu(menu: IMenu) {
const groups = menu.getActions({ shouldForwardArgs: true });
// Show the menu if at least one action is enabled.
for (const group of groups) {
const [, actions] = group;
for (const action of actions) {
if (action.enabled) {
this._showMenu();
return;
}
for (const subAction of (action as SubmenuItemAction).actions ?? []) {
if (subAction.enabled) {
this._showMenu();
return;
}
}
}
}
this._hideMenu();
}
private _createAdditionalActions(container: HTMLElement) {
const menu = this._commentMenus.getCommentThreadAdditionalActions(this._contextKeyService);
this._register(menu);
this._register(menu.onDidChange(() => {
this._commentFormActions.setActions(menu);
this._enableDisableMenu(menu);
}));
this._commentFormActions = new CommentFormActions(container, async (action: IAction) => {
this._actionRunDelegate?.();
action.run({
thread: this._commentThread,
$mid: MarshalledId.CommentThreadInstance
});
}, this.contextMenuService);
this._register(this._commentFormActions);
this._commentFormActions.setActions(menu, /*hasOnlySecondaryActions*/ true);
this._enableDisableMenu(menu);
}
}

View File

@@ -17,6 +17,7 @@ import { CommentReply } from 'vs/workbench/contrib/comments/browser/commentReply
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
import { CommentThreadBody } from 'vs/workbench/contrib/comments/browser/commentThreadBody';
import { CommentThreadHeader } from 'vs/workbench/contrib/comments/browser/commentThreadHeader';
import { CommentThreadAdditionalActions } from 'vs/workbench/contrib/comments/browser/commentThreadAdditionalActions';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
@@ -35,6 +36,7 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
private _header!: CommentThreadHeader<T>;
private _body!: CommentThreadBody<T>;
private _commentReply?: CommentReply<T>;
private _additionalActions?: CommentThreadAdditionalActions<T>;
private _commentMenus: CommentMenus;
private _commentThreadDisposables: IDisposable[] = [];
private _threadIsEmpty: IContextKey<boolean>;
@@ -177,6 +179,7 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
if (this._commentThread.canReply) {
this._createCommentForm();
}
this._createAdditionalActions();
this._register(this._body.onDidResize(dimension => {
this._refresh(dimension);
@@ -239,6 +242,19 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
this._register(this._commentReply);
}
private _createAdditionalActions() {
this._additionalActions = this._scopedInstatiationService.createInstance(
CommentThreadAdditionalActions,
this._body.container,
this._commentThread,
this._contextKeyService,
this._commentMenus,
this._containerDelegate.actionRunner,
);
this._register(this._additionalActions);
}
getCommentCoords(commentUniqueId: number) {
return this._body.getCommentCoords(commentUniqueId);
}

View File

@@ -246,6 +246,42 @@
word-wrap: break-word;
}
.review-widget .body .comment-additional-actions {
margin: 10px 20px;
}
.review-widget .body .comment-additional-actions .section-separator {
border-top: 1px solid var(--vscode-menu-separatorBackground);
margin: 14px 0;
}
.review-widget .body .comment-additional-actions .button-bar {
display: flex;
white-space: nowrap;
}
.review-widget .body .comment-additional-actions .monaco-button,
.review-widget .body .comment-additional-actions .monaco-text-button,
.review-widget .body .comment-additional-actions .monaco-button-dropdown {
display: flex;
width: auto;
}
.review-widget .body .comment-additional-actions .button-bar>.monaco-text-button,
.review-widget .body .comment-additional-actions .button-bar>.monaco-button-dropdown {
margin: 0 10px 0 0;
}
.review-widget .body .comment-additional-actions .button-bar .monaco-text-button {
padding: 4px 10px;
}
.review-widget .body .comment-additional-actions .codicon-drop-down-button {
align-items: center;
}
.review-widget .body .comment-form.expand .review-thread-reply-button {
display: none;
}
@@ -295,7 +331,7 @@
.review-widget .body .comment-form .form-actions,
.review-widget .body .edit-container .form-actions {
overflow: auto;
padding: 10px 0;
margin: 10px 0;
}
.review-widget .body .edit-textarea {

View File

@@ -287,13 +287,8 @@
padding: 0 4px;
}
/* split commit button */
.scm-view .button-container > .monaco-button-dropdown > .monaco-dropdown-button.codicon-drop-down-button {
border-radius: 0 2px 2px 0;
}
.scm-view .button-container > .monaco-button-dropdown > .monaco-button.monaco-text-button {
border-radius: 2px 0 0 2px;
min-width: 0;
}

View File

@@ -159,6 +159,13 @@ const apiMenus: IAPIMenu[] = [
description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"),
supportsSubmenus: false
},
{
key: 'comments/commentThread/additionalActions',
id: MenuId.CommentThreadAdditionalActions,
description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"),
supportsSubmenus: true,
proposed: 'contribCommentThreadAdditionalMenu'
},
{
key: 'comments/commentThread/title/context',
id: MenuId.CommentThreadTitleContext,

View File

@@ -10,6 +10,7 @@ export const allApiProposals = Object.freeze({
codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts',
commentsResolvedState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts',
contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts',
contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts',
contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts',
contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts',
contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts',

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// empty placeholder for comment thread additional menus
// https://github.com/microsoft/vscode/issues/163281