Menu contributions depending on the backend

Hello there. I need to create menu elements based on the backend’s directory content, so that after a quick directory scan backend could tell the frontend how many menu contributions should be there and what are they exactly. I guess some specific binding type might help, but I can’t figure out how to achieve this just yet. Could you help me with this, please?

[original thread by Kristina Marchenko]

[Hanksha]

You could create a JSON RPC service in the backend that provides the data which would then be used by the frontend in a MenuContribution.

[Kristina Marchenko]

@hanksha thank you for the response. This is exactly what I’m trying to do. However, I face the following problem: the backend service returns a Promise, but it seems like in order to bind MenuContribution to my contribution (and to do so for an exact number of instances that backend tells me) I need to know the values, not their Promises. Not sure about the get around.

[Hanksha]

Here is a working example:

@injectable()
export class AsyncMenuContribution implements MenuContribution {
    @inject(FileSystem)
    private fileSystem: FileSystem;
    @inject(CommandRegistry)
    private commands: CommandRegistry;

    async registerMenus(menus: MenuModelRegistry): Promise<void> {
        const menuPath = ["navigator-context-menu", "dynamic-sub-menu"];
        // get test directory
        const fileStat = await this.fileSystem.getFileStat("file:/Users/vivienjovet/Desktop/test");
        // register sub menu to show our dynamic menu actions
        menus.registerSubmenu(menuPath, "Dynamic Sub Menu");
        // register a menu action for each child directory
        fileStat?.children
            ?.filter(stat => stat.isDirectory)
            .forEach(dir => {
                const uri = new URI(dir.uri);
                // register a command for that directory
                this.commands.registerCommand(
                    {
                        id: uri.displayName,
                        label: uri.displayName
                    },
                    {
                        execute: () => alert(uri.displayName)
                    }
                );
                // register a menu for the command
                menus.registerMenuAction(menuPath, {
                    commandId: uri.displayName
                });
            });
    }
}

[Hanksha]

And in the frontend module:
bind(MenuContribution).to(AsyncMenuContribution).inSingletonScope();

[Hanksha]

[Hanksha]

Since it’s dynamic it only works for context menus (or anything that’s not created only on startup)

[Hanksha]

Another limitation is that you have to create a command for each dynamic entry, it would have been better to use one command which accepts an argument from the menu action. Is there any way to achieve that @anton-kosyakov ?

[Hanksha]

It would be also nice to be able to register menu actions without a corresponding command and just an execute function, I can see that being used for sub menus like “Open Recent”.

[Kristina Marchenko]

@hanksha thank you so much for an example with a detailed explanation, that helped me a lot!