-
Notifications
You must be signed in to change notification settings - Fork 30.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
testing: menu contribution points around messages (#190298)
- Implements the proposal in #190277 by adding a `contextValue` to TestMessages added to test runs. - Make the `FloatingClickMenu` reusable outside the editor, and uses it to implement a `testing/message/content` contribution point. With this extensions can do things like: ![](https://memes.peet.io/img/23-08-68e2f9db-abc4-4717-9da6-698b002c481c.png)
- Loading branch information
1 parent
1b87291
commit 2d9cc42
Showing
21 changed files
with
488 additions
and
244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { $, append, clearNode } from 'vs/base/browser/dom'; | ||
import { Widget } from 'vs/base/browser/ui/widget'; | ||
import { IAction } from 'vs/base/common/actions'; | ||
import { Emitter } from 'vs/base/common/event'; | ||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; | ||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; | ||
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; | ||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; | ||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; | ||
import { asCssVariable, asCssVariableWithDefault, buttonBackground, buttonForeground, contrastBorder, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; | ||
|
||
export class FloatingClickWidget extends Widget { | ||
|
||
private readonly _onClick = this._register(new Emitter<void>()); | ||
readonly onClick = this._onClick.event; | ||
|
||
private _domNode: HTMLElement; | ||
|
||
constructor(private label: string) { | ||
super(); | ||
|
||
this._domNode = $('.floating-click-widget'); | ||
this._domNode.style.padding = '6px 11px'; | ||
this._domNode.style.borderRadius = '2px'; | ||
this._domNode.style.cursor = 'pointer'; | ||
this._domNode.style.zIndex = '1'; | ||
} | ||
|
||
getDomNode(): HTMLElement { | ||
return this._domNode; | ||
} | ||
|
||
render() { | ||
clearNode(this._domNode); | ||
this._domNode.style.backgroundColor = asCssVariableWithDefault(buttonBackground, asCssVariable(editorBackground)); | ||
this._domNode.style.color = asCssVariableWithDefault(buttonForeground, asCssVariable(editorForeground)); | ||
this._domNode.style.border = `1px solid ${asCssVariable(contrastBorder)}`; | ||
|
||
append(this._domNode, $('')).textContent = this.label; | ||
|
||
this.onclick(this._domNode, () => this._onClick.fire()); | ||
} | ||
} | ||
|
||
export abstract class AbstractFloatingClickMenu extends Disposable { | ||
private readonly renderEmitter = new Emitter<FloatingClickWidget>(); | ||
protected readonly onDidRender = this.renderEmitter.event; | ||
private readonly menu: IMenu; | ||
|
||
constructor( | ||
menuId: MenuId, | ||
@IMenuService menuService: IMenuService, | ||
@IContextKeyService contextKeyService: IContextKeyService | ||
) { | ||
super(); | ||
this.menu = this._register(menuService.createMenu(menuId, contextKeyService)); | ||
} | ||
|
||
/** Should be called in implementation constructors after they initialized */ | ||
protected render() { | ||
const menuDisposables = this._register(new DisposableStore()); | ||
const renderMenuAsFloatingClickBtn = () => { | ||
menuDisposables.clear(); | ||
if (!this.isVisible()) { | ||
return; | ||
} | ||
const actions: IAction[] = []; | ||
createAndFillInActionBarActions(this.menu, { renderShortTitle: true, shouldForwardArgs: true }, actions); | ||
if (actions.length === 0) { | ||
return; | ||
} | ||
// todo@jrieken find a way to handle N actions, like showing a context menu | ||
const [first] = actions; | ||
const widget = this.createWidget(first, menuDisposables); | ||
menuDisposables.add(widget); | ||
menuDisposables.add(widget.onClick(() => first.run(this.getActionArg()))); | ||
widget.render(); | ||
}; | ||
this._register(this.menu.onDidChange(renderMenuAsFloatingClickBtn)); | ||
renderMenuAsFloatingClickBtn(); | ||
} | ||
|
||
protected abstract createWidget(action: IAction, disposables: DisposableStore): FloatingClickWidget; | ||
|
||
protected getActionArg(): unknown { | ||
return undefined; | ||
} | ||
|
||
protected isVisible() { | ||
return true; | ||
} | ||
} | ||
|
||
export class FloatingClickMenu extends AbstractFloatingClickMenu { | ||
|
||
constructor( | ||
private readonly options: { | ||
/** Element the menu should be rendered into. */ | ||
container: HTMLElement; | ||
/** Menu to show. If no actions are present, the button is hidden. */ | ||
menuId: MenuId; | ||
/** Argument provided to the menu action */ | ||
getActionArg: () => void; | ||
}, | ||
@IInstantiationService private readonly instantiationService: IInstantiationService, | ||
@IMenuService menuService: IMenuService, | ||
@IContextKeyService contextKeyService: IContextKeyService | ||
) { | ||
super(options.menuId, menuService, contextKeyService); | ||
this.render(); | ||
} | ||
|
||
protected override createWidget(action: IAction, disposable: DisposableStore): FloatingClickWidget { | ||
const w = this.instantiationService.createInstance(FloatingClickWidget, action.label); | ||
const node = w.getDomNode(); | ||
this.options.container.appendChild(node); | ||
disposable.add(toDisposable(() => this.options.container.removeChild(node))); | ||
return w; | ||
} | ||
|
||
protected override getActionArg(): unknown { | ||
return this.options.getActionArg(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.