-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Refactor menu nodes #14676
base: master
Are you sure you want to change the base?
Refactor menu nodes #14676
Conversation
Fixes eclipse-theia#14217 Makes menu nodes active object that can decide on visibility, enablement, etc. themselves. Contributed on behalf of STMicroelectronics Signed-off-by: Thomas Mäder <[email protected]>
@dhuebner What are you trying to achieve through CompoundMenuNodeRole.flat? |
if (!ReactTabBarToolbarItem.is(item)) { | ||
toolbarItemClassNames = TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM; | ||
if (this.evaluateWhenClause(item.when)) { | ||
toolbarItemClassNames += ' enabled'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without adding 'enabled', toolbar items are rendered as disabled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is the responsibility of the toolbar item.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we are missing some implementation, cause the test is failing because workbench.action.showCommands
toolbar item is not enabled.
@dhuebner rigth now, I'm mostly looking for functional and architectural feedback. I can fix the tests once the dust settles. |
The previous effect was that if you click on three dots toolbar item in notebook cell the children of the |
@tsmaeder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks generally really good. I believe we can improve on the API. Feel free to request my review again once the tests are fixed and I'll do a more in-depth code review.
|
||
export type CompoundMenuNode = MenuNode & { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is there a specific reason to prefer type intersections to interfaces? I find interfaces a bit better to read, so I'd prefer them here.
protected override doRender(menuPath: MenuPath, menu: CompoundMenuNode, | ||
anchor: Anchor, | ||
contextMatcher: ContextMatcher, | ||
args?: any, | ||
context?: HTMLElement, | ||
onHide?: () => void | ||
): ContextMenuAccess { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Why not continue using a Options
object in here? Using a list of arguments make this more difficult to override and more prone to breakage.
if (!groupNode) { | ||
groupNode = new CompositeMenuNode(menuId, label, options, parent); | ||
disposable = this.changeEventOnDispose(parent.addNode(groupNode)); | ||
registerSubmenu(menuPath: MenuPath, label: string, sortString?: string, icon?: string, when?: string, contextKeyOverlay?: Record<string, string>): Disposable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: When refactoring the arguments of the method anyway, why not make it into one single object?
- I feel like most JS/TS APIs are moving/have moved to objects, especially if there are optional parameters to a function.
- It feels more readable to me, since you no longer need to rely on explicit ordering of arguments.
- It preserves better backwards compatibility, as adding a new field to an object is easier than adding a new parameter to a function. At least in my opinion, JS/TS itself doesn't care too much about that during the runtime/compiletime.
registerIndependentSubmenu(id: string, label: string, options?: SubMenuOptions): Disposable { | ||
if (this.independentSubmenus.has(id)) { | ||
console.debug(`Independent submenu with path ${id} registered, but given ID already exists.`); | ||
linkCompoundMenuNode(newParentPath: MenuPath, submenuPath: MenuPath, order?: string, when?: string): Disposable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: We should use objects, see reasoning above.
let nonEmptyNode = undefined; | ||
static removeSingleRootNode(fullMenuModel: CompoundMenuNode): CompoundMenuNode { | ||
|
||
let singleChild = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Implicitly typed as any
let singleChild = undefined; | |
let singleChild: CompoundMenuNode | undefined = undefined; |
*/ | ||
iconClass?: string; | ||
} | ||
export interface CommandMenu extends MenuNode, RenderedMenuNode, Action { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: On the other hand, if an interface is completely empty, I'd prefer an intersection type, but that's really only personal preference.
@@ -86,7 +92,7 @@ export class ElectronContextMenuRenderer extends BrowserContextMenuRenderer { | |||
protected useNativeStyle: boolean = true; | |||
|
|||
constructor(@inject(ElectronMainMenuFactory) private electronMenuFactory: ElectronMainMenuFactory) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: If we no longer need the factory in the constructor, I'd rather have it as a protected
field injector. That makes it much easier to override from an adopter perspective.
@@ -71,11 +66,28 @@ export type ElectronMenuItemRole = ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | | |||
'selectNextTab' | 'selectPreviousTab' | 'mergeAllWindows' | 'clearRecentDocuments' | | |||
'moveTabToNewWindow' | 'windowMenu'); | |||
|
|||
function traverseMenuDto(items: MenuDto[], callback: (item: MenuDto) => void): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: WDYT about making these protected methods of the class that uses these functions? Having those as local functions, makes overriding for adopters difficult.
menus.registerSubmenu([...PLUGIN_TEST_VIEW_TITLE_MENU, TestViewCommands.RUN_ALL_TESTS.id], '', undefined, undefined, undefined, { | ||
'testing.profile.context.group': 'run' | ||
}); | ||
|
||
menus.registerSubmenu([...PLUGIN_TEST_VIEW_TITLE_MENU, TestViewCommands.DEBUG_ALL_TESTS.id], '', undefined, undefined, undefined, { | ||
'testing.profile.context.group': 'debug' | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having undefined, undefined, undefined
here is a good point to support my argument in favor of objects :)
Signed-off-by: Thomas Mäder <[email protected]>
What it does
Fixes #14217
Makes menu nodes active object that can decide on visibility, enablement, etc. themselves. This should simplify handling of menus and toolbar items because a lot of special-case code can be moved to polymorphous implementations instead of being handled at each client site. This also allows the special handling of parameter transformations for plugin-contributed actions (toolbar/menu) to be confined to the plugin engine implementation.
How to test
Test that menu functionality still works, in particular actions contributed from plugins. That includes menus and toolbars everywhere for browser and electron cases.
Follow-ups
One strange thing is with the menu/toolbar support is that the
MenuRegistry
and associated classes are located in the "common" part of the core packages. If we decide that this is no longer that case, we could move the code to "browser". However, this would introduce another huge batch of changes and therefore has been left out of this PR.There are also a couple of renamings that would make the naming more consistent and logical that have been left out of the PR to keep the size of the PR manageable.
Breaking changes
This PR introduces breaking changes to be documented after a first round of reviews.
Attribution
Contributed on behalf of STMicroelectronics
Review checklist
Reminder for reviewers