Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../base/browser/dom.js';
7
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
8
import { Action, ActionRunner } from '../../../../base/common/actions.js';
9
import { Codicon } from '../../../../base/common/codicons.js';
10
import { Disposable, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
11
import * as strings from '../../../../base/common/strings.js';
12
import * as languages from '../../../../editor/common/languages.js';
13
import { IRange } from '../../../../editor/common/core/range.js';
14
import * as nls from '../../../../nls.js';
15
import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
16
import { IMenu, MenuItemAction, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
17
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
18
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
19
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
20
import { ThemeIcon } from '../../../../base/common/themables.js';
21
import { CommentMenus } from './commentMenus.js';
22
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
23
import { MarshalledId } from '../../../../base/common/marshallingIds.js';
24
import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
25
import { MarshalledCommentThread } from '../../../common/comments.js';
26
import { CommentCommandId } from '../common/commentCommandIds.js';
27
28
const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.'));
29
const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon);
30
const DELETE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(Codicon.trashcan);
31
32
function threadHasComments(comments: ReadonlyArray<languages.Comment> | undefined): comments is ReadonlyArray<languages.Comment> {
33
return !!comments && comments.length > 0;
34
}
35
36
export class CommentThreadHeader<T = IRange> extends Disposable {
37
private _headElement: HTMLElement;
38
private _headingLabel!: HTMLElement;
39
private _actionbarWidget!: ActionBar;
40
private _collapseAction!: Action;
41
private _contextMenuActionRunner: ActionRunner | undefined;
42
43
constructor(
44
container: HTMLElement,
45
private _delegate: { collapse: () => void },
46
private _commentMenus: CommentMenus,
47
private _commentThread: languages.CommentThread<T>,
48
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
49
@IInstantiationService private readonly _instantiationService: IInstantiationService,
50
@IContextMenuService private readonly _contextMenuService: IContextMenuService
51
) {
52
super();
53
this._headElement = <HTMLDivElement>dom.$('.head');
54
container.appendChild(this._headElement);
55
this._register(toDisposable(() => this._headElement.remove()));
56
this._fillHead();
57
}
58
59
protected _fillHead(): void {
60
const titleElement = dom.append(this._headElement, dom.$('.review-title'));
61
62
this._headingLabel = dom.append(titleElement, dom.$('span.filename'));
63
this.createThreadLabel();
64
65
const actionsContainer = dom.append(this._headElement, dom.$('.review-actions'));
66
this._actionbarWidget = new ActionBar(actionsContainer, {
67
actionViewItemProvider: createActionViewItem.bind(undefined, this._instantiationService)
68
});
69
70
this._register(this._actionbarWidget);
71
72
const collapseClass = threadHasComments(this._commentThread.comments) ? COLLAPSE_ACTION_CLASS : DELETE_ACTION_CLASS;
73
this._collapseAction = new Action(CommentCommandId.Hide, nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse());
74
if (!threadHasComments(this._commentThread.comments)) {
75
const commentsChanged: MutableDisposable<IDisposable> = this._register(new MutableDisposable());
76
commentsChanged.value = this._commentThread.onDidChangeComments(() => {
77
if (threadHasComments(this._commentThread.comments)) {
78
this._collapseAction.class = COLLAPSE_ACTION_CLASS;
79
commentsChanged.clear();
80
}
81
});
82
}
83
84
const menu = this._commentMenus.getCommentThreadTitleActions(this._contextKeyService);
85
this._register(menu);
86
this.setActionBarActions(menu);
87
88
this._register(menu);
89
this._register(menu.onDidChange(e => {
90
this.setActionBarActions(menu);
91
}));
92
93
this._register(dom.addDisposableListener(this._headElement, dom.EventType.CONTEXT_MENU, e => {
94
return this.onContextMenu(e);
95
}));
96
97
this._actionbarWidget.context = this._commentThread;
98
}
99
100
private setActionBarActions(menu: IMenu): void {
101
const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], <(MenuItemAction | SubmenuItemAction)[]>[]);
102
this._actionbarWidget.clear();
103
this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true });
104
}
105
106
updateCommentThread(commentThread: languages.CommentThread<T>) {
107
this._commentThread = commentThread;
108
109
this._actionbarWidget.context = this._commentThread;
110
this.createThreadLabel();
111
}
112
113
createThreadLabel() {
114
let label: string | undefined;
115
label = this._commentThread.label;
116
117
if (label === undefined) {
118
if (!(this._commentThread.comments && this._commentThread.comments.length)) {
119
label = nls.localize('startThread', "Start discussion");
120
}
121
}
122
123
if (label) {
124
this._headingLabel.textContent = strings.escape(label);
125
this._headingLabel.setAttribute('aria-label', label);
126
}
127
}
128
129
updateHeight(headHeight: number) {
130
this._headElement.style.height = `${headHeight}px`;
131
this._headElement.style.lineHeight = this._headElement.style.height;
132
}
133
134
private onContextMenu(e: MouseEvent) {
135
const actions = this._commentMenus.getCommentThreadTitleContextActions(this._contextKeyService);
136
if (!actions.length) {
137
return;
138
}
139
const event = new StandardMouseEvent(dom.getWindow(this._headElement), e);
140
if (!this._contextMenuActionRunner) {
141
this._contextMenuActionRunner = this._register(new ActionRunner());
142
}
143
this._contextMenuService.showContextMenu({
144
getAnchor: () => event,
145
getActions: () => actions,
146
actionRunner: this._contextMenuActionRunner,
147
getActionsContext: (): MarshalledCommentThread => {
148
return {
149
commentControlHandle: this._commentThread.controllerHandle,
150
commentThreadHandle: this._commentThread.commentThreadHandle,
151
$mid: MarshalledId.CommentThread
152
};
153
},
154
});
155
}
156
}
157
158