[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #54285 adds webview/context contribution #154524

Merged
merged 8 commits into from
Jul 21, 2022

Conversation

eamodio
Copy link
Contributor
@eamodio eamodio commented Jul 8, 2022

This PR fixes #54285

@eamodio
Copy link
Contributor Author
eamodio commented Jul 8, 2022

@mjbvz I've just started on this PR to address #54285 please let me know what you think.

I'm not sure if plumbing the viewType in the init args is good or not (seems ok), but I didn't see another good way to get at that for the context -- although I think the name viewType is not ideal for this. Maybe friendlyId or webviewId or something, since viewType is only for editor-based webviews, while webview views could just use the view id, and for other webview types we'd need to think about what/if there is a friendly/extension-provided id that make sense.

With these changes I was able to get modified version of GitLens to add custom menu items to a specific webview (e.g. the welcome view) with the following contribution:

"webview/context": [
	{
		"command": "gitlens.showSettingsPage",
		"when": "webview == gitlens.welcome"
	}
],

Which looks like:
image

When executed, the command callback gets an arg which looks like { webview: 'gitlens.welcome; } (where gitlens.welcome is the viewType of that webview)

With this contribution, extensions could then use their own context keys to hide and show the desired commands.

The challenges left here are:

  • Figure out a good way to provide an additional context value (in addition to webview) that provides something about the element being clicked on
    • Maybe we add something like a data-vscode-menu-context attribute on elements and when right-clicking on an element, we could search the ancestors for that attribute and provide that value as an additional element context key that would help scope commands more specifically depending on what is right-clicked on.
  • Decide on what the viewType (or friendlyId) should be used for other webview types
  • Come up with a way to conditionally hide/show the cut/copy/paste actions.

@Tyriar
Copy link
Member
Tyriar commented Jul 8, 2022

Come up with a way to conditionally hide/show the cut/copy/paste actions.

👍 this is a requirement on my end

@eamodio
Copy link
Contributor Author
eamodio commented Jul 8, 2022

Come up with a way to conditionally hide/show the cut/copy/paste actions.

👍 this is a requirement on my end

@Tyriar do you think this could just be an all or nothing thing? Basically a flag on the webview that says that the extension will control the context menu completely, so cut/copy/paste won't be added/shown for that webview? If the extension needs it, they can manually add those commands themselves.

@Tyriar
Copy link
Member
Tyriar commented Jul 8, 2022

I'd be fine with all or nothing, I agree it should be easy to add themselves.

@eamodio
Copy link
Contributor Author
eamodio commented Jul 11, 2022

@mjbvz @Tyriar

I'm going to rename viewType to providedWebviewId for clarity because viewType is too specific for the editor case. I'm also very open to other suggestions -- I've thought about webviewName but that felt like it would be too confusing.

I'm also going to start implementing:

Maybe we add something like a data-vscode-menu-context attribute on elements and when right-clicking on an element, we could search the ancestors for that attribute and provide that value as an additional element context key that would help scope commands more specifically depending on what is right-clicked on.

Any preference on the data attribute name? My current plan is for data-vscode-context-menu-item and then in the when clause that value will be exposed as webviewItem. So in the when extensions will have access to webview and webviewItem context keys, similar to view and viewItem for views today.

@eamodio
Copy link
Contributor Author
eamodio commented Jul 15, 2022

@mjbvz @Tyriar I think this PR is basically complete outside of any desired naming changes. It works for normal webviews, webview views, and custom editors -- other webviews should be uneffected except for menu contributions that are always available (since they have no provided context)

I've added a new preventDefaultContextMenuItems property on WebviewPanelOptions and on the webviewOptions object we pass to registerWebviewViewProvider which allows a webview to opt-out of the default cut/copy/paste (or any future) menu items.

I've also provided 3 context keys for the context menu when clause:

  • webview — the extension provided id of the webview
    • for webview panels and custom editors it is the viewType
    • for webview views it is the view id
  • webviewItem — an optional value that an extension can provide in the DOM via a data-vscode-context-menu-item attribute, by searching for closest element to the element that triggered the context menu
  • webviewItemElement — an optional value that an extension can provide in the DOM via adata-vscode-context-menu-item-element attribute, by searching for closest element to the element that triggered the context menu (but only within the scope of the found item element

Here's an example using the GitLens Rebase Editor:

"webview/context": [
	{
		"command": "gitlens.rebase.help",
		"when": "webview == gitlens.rebase",
		"group": "9_gitlens@1"
	},
	{
		"command": "gitlens.rebase.copySha",
		"when": "webview == gitlens.rebase && webviewItem == commit && webviewItemElement == sha",
		"group": "1_gitlens@1"
	},
	{
		"command": "gitlens.rebase.copyMessage",
		"when": "webview == gitlens.rebase && webviewItem == commit && webviewItemElement == message",
		"group": "1_gitlens@1"
	},
	{
		"command": "gitlens.rebase.showCommitDetails",
		"when": "webview == gitlens.rebase && webviewItem == commit",
		"group": "2_gitlens@1"
	}
],

Here's the DOM -- I have data-vscode-context-menu-item="commit" on each of the commit rows, and additional data-vscode-context-menu-item-element attrs on the message and sha the with "message" and "sha" values respectively
image

Here's the context menus in action:
image
image
image
image

@eamodio eamodio marked this pull request as ready for review July 15, 2022 00:14
Adds `webViewItem` read from nearest `data-vscode-context-menu-item`
Adds `webViewItemElement` read from `data-vscode-context-menu-item-element`
This allows webviews to opt-out of the default (cut/copy/paste) context menu items
@eamodio
Copy link
Contributor Author
eamodio commented Jul 15, 2022

I also added an additional data-vscode-context-menu-item-value attribute that can be specified (on the same element as data-vscode-context-menu-item) to provide a value specifically for the command callback. So in my examples above, the data-vscode-context-menu-item="commit" could have data-vscode-context-menu-item-value="<commit-sha>" (or even a string-ified object with details about the item). The item value is not provided for the when clauses only for the callbacks

@eamodio
Copy link
Contributor Author
eamodio commented Jul 18, 2022

@mjbvz any comments on the direction/naming/etc? 😉

@hawkticehurst
Copy link
Member

Hey @eamodio! Checking back in on this PR (thank you again for contributing this 🙏) and I have two questions after catching up on everything.

Render context menus on click?
It seems like when associating context menus with DOM elements (via the data-vscode-context-menu-item) the context menu is triggered/rendered when the DOM element is right-clicked (or at least that's my assumption). Assuming that is correct, I'm wondering if it would be possible to give the option so that context menus can be triggered/rendered on a normal click?

This would enable the UI/UX scenario where clicking an icon button will render a menu (anchored to the button). This has been a highly requested bit of UI for the webview toolkit and it would be awesome if we could provide this to extension authors.

vscode-context-menu

Sub question: While writing this I just realized the above UI pattern will also probably require that extension authors are provided a reference to the context menu DOM in order to correctly anchor it to an icon button (a la something like Popper). Would exposing that be possible (or maybe it's already possible to get a reference to a context menu DOM)?

Render native macOS context menus?
Since your screenshots above are (seemingly) from Windows I wanted to double-check that when rendering a context menu on macOS it will be rendered as a native menu?

@eamodio
Copy link
Contributor Author
eamodio commented Jul 20, 2022

@hawkticehurst

Render native macOS context menus?

Yes they should as this is just building on the cut/copy/paste context menu that @mjbvz already added to the webviews

Render context menus on click?

For this, while I would love to have it, I would do that in a separate PR/contribution, since this PR doesn't really add any new API or API surface-area (other than the additional "webview/context" menu point). It just provides a hook and context to play in the existing system.

Although because the webview is in charge of the DOM, I believe you could just simulate a right-click (e.g. generate a contextmenu DOM event) and that would cause our hooks to fire and everything in theory would just work -- though I haven't tested that.

@hawkticehurst
Copy link
Member

Yes they should as this is just building on the cut/copy/paste context menu that @mjbvz already added to the webviews

Nice :) Thanks for clarifying

I would do that in a separate PR/contribution, since this PR doesn't really add any new API or API surface-area

Totally understandable! I'd be happy to open an issue to track this if desired. But I suppose I'll double check with @mjbvz that this is an API surface you would be open to adding in the first place?

Although because the webview is in charge of the DOM, I believe you could just simulate a right-click (e.g. generate a contextmenu DOM event) and that would cause our hooks to fire and everything in theory would just work -- though I haven't tested that.

Great thought/good to know there may be a way to (in theory) still implement this behavior in the meantime.

Copy link
Collaborator
@mjbvz mjbvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking into this! This is promising but I want to make sure we have great support for showing different context menus in different parts of the webview.

While it seems that this is kind of possible with the current approach, what if instead of just setting the key for a single item/element, we were to traverse up from the event target to find all ancestor contexts keys in the webview dom and set those? This is more like how VS Code handles contexts for the editor itself

For example, for the below html:

<div class="menu" data-vscode-context='{"webviewSection": "menu", "countOfThing": 123}'>
    ...
    <div class="nav" data-vscode-context='{"webviewSection: "nav", "vscodeSupressDefaultContextMenuItems": true}'>
        ...
    </div>
</div>

When right clicking inside nav, the resulting context keys would be:

  • webviewSection = "nav" (overrides the value from the . menu element)
  • countOfThing = 123 (inherited from the parent .menu element)
  • vscodeSupressDefaultContextMenuItems = true

I'm not sure if a json format the above example is best but this approach does seem promising. It also would let us remove preventDefaultContextMenuItems from vscode.d.ts and instead let the webview specify this itself by setting vscodeSupressDefaultContextMenuItems somewhere in the ancestors of the clicked element (potentially even at the root of the document)

What do you think?

src/vscode-dts/vscode.proposed.webviewContextMenus.d.ts Outdated Show resolved Hide resolved
@eamodio
Copy link
Contributor Author
eamodio commented Jul 21, 2022

@mjbvz I think that sounds like a good idea -- initially I was a little concerned since it would a) be a little more overhead and b) there would be no standardization for how to deal with webview context, and c) packing JSON into the DOM felt odd.

But a) shouldn't be much of a big deal at all and b) I'm not sure if that is good or bad -- this way is certainly more flexible and allows for much better control over the context data for both the whens and the callback. And as for c) I couldn't think of a better alternative that was as succinct and flexible.

I've pushed changes which implement the suggested change to use JSON in a single data-vscode-context attribute. I haven't yet reverted the change for preventDefaultContextMenuItems yet -- but I can certainly do that if we feel this way is better.

I also added a yarn run webview-generate-csp-hash command to easily generate the csp hash for the index.html

@eamodio
Copy link
Contributor Author
eamodio commented Jul 21, 2022

@hawkticehurst I tested out the contextmenu event simulation and it worked.

I setup a click handler on an element and had it call the following callback:

	private onClicked(e: MouseEvent) {
		const evt = new MouseEvent('contextmenu', {
			bubbles: true,
			clientX: e.clientX,
			clientY: e.clientY,
		});
		e.target?.dispatchEvent(evt);
		e.stopImmediatePropagation();
	}

And that opened the desired context menu in the correct placement.

This reverts commit 71f0180.

Also renames proposal from webviewContextMenus to contribWebviewContext to align with other empty contribution point proposals
@@ -329,12 +341,18 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
const elementBox = this.element.getBoundingClientRect();
contextMenuService.showContextMenu({
getActions: () => {
const contextKeyService = this._contextKeyService!.createOverlay([
...Object.entries(data.context),
['webview', this.providedId],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look into adding a generic activeWebviewId context key that is set whenever a webview is focused. This matches the activeCustomEditorId context key we already have

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that different than webview? That is currently the "id" of the webview that the context menu is being shown for

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or are you saying to usages outside of the context menu -- a more global context key?

Copy link
Collaborator
@mjbvz mjbvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Will make a few tweaks to the context names and then discuss this further in the August API syncs

@mjbvz mjbvz enabled auto-merge (squash) July 21, 2022 19:01
@eamodio
Copy link
Contributor Author
eamodio commented Jul 21, 2022

@mjbvz Woo! Thanks!

What are your thoughts on: #154524 (comment)

Should we maybe add something into the webview to wrap that up and make it more accessible? To be clear I'm not suggesting to wrap it up in this proposal, since I want/need this to land as soon as possible -- more if you feel it warrants another proposal/exploration

@mjbvz mjbvz merged commit 78da5e3 into microsoft:main Jul 21, 2022
@mjbvz
Copy link
Collaborator
mjbvz commented Jul 21, 2022

@eamodio Maybe this could live in the webview UI toolkit?

Not currently convinced its a common enough need to put into core VS COde

@eamodio
Copy link
Contributor Author
eamodio commented Jul 22, 2022

Yeah, if we offer a button that shows a context menu the component could provide that behavior.

@hawkticehurst
Copy link
Member

Oh yeah, to be clear, I was thinking the same thing when I posted my original comment!

We already have an icon button implemented in the webview UI toolkit that can be used and I'd be totally happy to create a context menu button type component that abstracts away a lot of the behavior of this new component.

My hope is that VS Code core would provide some slightly cleaner APIs for handling click events and the ability to anchor the menu to another component.

I think the code snippet that @eamodio provided above works well enough for synthetically creating a click event (but I would of course happily take a cleaner way of doing this). The big problem (as far as I can tell) is that the above code will render the context menu at the exact X/Y coordinates of the mouse click:

clientX: e.clientX,
clientY: e.clientY,

This could result in context menus being rendered in a way that overlaps the button depending on where it's been clicked and would likely be unacceptable for the toolkit since one of its primary goals is to provide a set of components that follow the design language of VS Code core UI (which in this case always renders menus to the bottom left of icon buttons).

Using Popper as an easy reference for possible syntax, it would be great if there was some ability to do this:

import { createPopper } from '@popperjs/core';
const vscode = acquireVSCodeAPI();

const button = document.querySelector('#toolkit-icon-button');
const menu = vscode.getReferenceToContextMenuAsDOMNode("my-context-menu"); 

createPopper(button, menu, {
  placement: 'bottom-start',
});

Specific APIs/syntax/implementation details aside, the main objective would be some way of being able to get the context menu to always render at the bottom left of an icon button.

@eamodio
Copy link
Contributor Author
eamodio commented Jul 22, 2022

Yeah to get the placement correct you'd have to just provide the right x/y coordinates, which you can get by getting the bounding box of the target element

@hawkticehurst
Copy link
Member

Ahhh fantastic (thank you). In that case, I'm totally happy to take a go at implementing all of this as a component in the toolkit once this API lands in core!

@github-actions github-actions bot locked and limited conversation to collaborators Sep 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Contributed webview context menu actions
6 participants