How can I prevent certain widgets from being loaded by default at startup?

I’m building a version of Theia targeted to students, and am trying to make the interface as minimalist as possible.

When theia starts up, it has several widgets added to the interface by default, such as outline, git, extensions, etc. How can I make sure these are hidden by default?

@bendavis the current default layout is aligned with expectations from vscode, the default widgets are present when a workspace is first opened, and are defined through the initliazeLayout implementation:

Ex (for search-in-workspace):

You have two approaches if you do not want this functionality, you can either rebind each contribution you do not want with a stub implementation, or you can rebind the FrontendApplication to not do anything when setting a default layout.

@vince-fugnitto I dug into the theia source a bit. I couldn’t find anywhere in ApplicationShell that was responsible for setting up the default layout, however I found FrontendApplication.createDefaultLayout, which appears to be what I should be overriding if I want to customize how the default layout is created. Does that sound right?

@bendavis sorry yes :slight_smile:

Ok, here’s the solution I came up with:

import { inject, injectable } from 'inversify';
import { ILogger } from '@theia/core';
import { FrontendApplication } from '@theia/core/lib/browser';

const DEFAULT_CONTRIBUTIONS = [
    'FileNavigatorContribution',
    'SearchInWorkspaceFrontendContribution'
]

/**
 * Overrides FrontendApplication to customize the contributions included in the default layout.
 */
@injectable()
export class FrontendApplicationOverride extends FrontendApplication {
    @inject(ILogger) private readonly logger: ILogger;

    protected async createDefaultLayout(): Promise<void> {
        for (const contribution of this.contributions.getContributions()) {
            const name = contribution.constructor.name;
            if (contribution.initializeLayout && DEFAULT_CONTRIBUTIONS.indexOf(name) !== -1) {
                await this.measure(name + '.initializeLayout',
                    () => contribution.initializeLayout!(this)
                );
            }
        }
    }
}

In the container module:

    bind(FrontendApplicationOverride).toSelf().inSingletonScope();
    rebind(FrontendApplication).toService(FrontendApplicationOverride);
2 Likes

Thanks @bendavis for this advice, found it after noticing the createDefaultLayout has a comment encouraging overrides.

I noticed the approach used of using contribution.constructor.name doesn’t seem to work in my setup (Theia 1.31 dependencies, theia-blueprint setup) - all the names end up being (minified?) single characters, so I reworked it to be more like this and operate on contribution.options.widgetName:

import { injectable } from 'inversify';
import { FrontendApplication } from '@theia/core/lib/browser';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';

const DEFAULT_REMOVED_WIDGET_NAMES = [
    'Source Control',
    'Debug',
    'Search',
    'Extensions'
]

/**
 * Overrides FrontendApplication to customize the contributions included in the default layout.
 */
@injectable()
export class FrontendApplicationOverride extends FrontendApplication {

    protected async createDefaultLayout(): Promise<void> {
        console.info('Default layout override');

        for (const contribution of this.contributions.getContributions()) {
            if(contribution instanceof AbstractViewContribution) {
                console.info('Default layout filtering is AbstractViewContribution');

                // @ts-ignore
                const widgetName = contribution.options.widgetName;
                console.info('Default layout filtering check: ' + widgetName);
                console.info('Default layout filtering checkname: ' + contribution.constructor.name);

                if(DEFAULT_REMOVED_WIDGET_NAMES.indexOf(widgetName) !== -1) {
                    console.info('Default layout filtering: ' + widgetName);
                    continue;
                }
            }

            if (contribution.initializeLayout) {
                await this.measure(contribution.constructor.name + '.initializeLayout',
                    () => contribution.initializeLayout!(this)
                );
            }
        }
    }
}

The container module (e.g. your-frontend-module.ts) lines are the same:

    bind(FrontendApplicationOverride).toSelf().inSingletonScope();
    rebind(FrontendApplication).toService(FrontendApplicationOverride);

(Note, I also tried to use viewContributionID which in theory should also work but it kept appearing as undefined when actually coding things that way.

Some alternative approaches (e.g. removing extensions completely, filtering frontend contributions) are discussed in: