eclipse-theia / theia

Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.
http://theia-ide.org
Eclipse Public License 2.0
19.87k stars 2.49k forks source link

Custom FileTreeWidget not displaying anything #4412

Open duemaster opened 5 years ago

duemaster commented 5 years ago

I am using trying to create a custom widget extending FileTreeWidget. However, it seems that the root of the TreeModel is always null, hence, there nothing rendered on the widget.


@injectable()
export class CustomFileExplorerWidget extends FileTreeWidget {

    @inject(CorePreferences) protected readonly corePreferences: CorePreferences;

    constructor(
        @inject(TreeProps) readonly props: TreeProps,
        @inject(FileNavigatorModel) readonly model: FileNavigatorModel,
        @inject(ContextMenuRenderer) readonly contextMenuRender: ContextMenuRenderer,
        @inject(SelectionService) protected readonly selectionService: SelectionService,
        @inject(ApplicationShell) protected readonly shell: ApplicationShell,
        @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService

    ) {
        super(props, model, contextMenuRender);
        this.initialize();
    }

    @postConstruct()
    protected init(): void {
        super.init();
    }

    protected async initialize(): Promise<void> {
        await this.model.updateRoot();
    }
}

This is how I am doing my bindings.


export default new ContainerModule((bind, unbind) => {
... other code

    bind(CustomFileExplorerWidget).toDynamicValue(ctx => createCustomFileExplorer(ctx.container));
})

function createCustomFileExplorer(parent: interfaces.Container): CustomFileExplorerWidget {
    const child = createFileTreeContainer(parent);

    child.unbind(FileTreeModel);
    child.bind(FileNavigatorModel).toSelf();
    child.rebind(TreeModel).toService(FileNavigatorModel);

    child.unbind(FileTree);
    child.bind(FileNavigatorTree).toSelf();
    child.rebind(Tree).toService(FileNavigatorTree);

    child.unbind(FileTreeWidget);
    child.bind(CustomFileExplorerWidget).toSelf();

    return child.get(CustomFileExplorerWidget);
}

This is a widget appearing inside another widget, hence the lack of a WIDGET_ID. I tried looking at how the FileNavigatorWidget works, but still could not figure out what I am doing wrong here.

kittaakos commented 5 years ago

Sharing your entire code would help a lot, so I am just guessing now:

duemaster commented 5 years ago

@kittaakos

I tried removing

    child.unbind(FileTree);
    child.bind(FileNavigatorTree).toSelf();
    child.rebind(Tree).toService(FileNavigatorTree);

and I got this error: screenshot from 2019-02-25 18-37-13

Removing both of them:

   child.unbind(FileTreeModel);
    child.bind(FileNavigatorModel).toSelf();
    child.rebind(TreeModel).toService(FileNavigatorModel);

    child.unbind(FileTree);
    child.bind(FileNavigatorTree).toSelf();
    child.rebind(Tree).toService(FileNavigatorTree);

Gives me this error

screenshot from 2019-02-25 18-33-04

I got these bindings from navigator-container.ts which seems to resolve the two errors I shown above.

kittaakos commented 5 years ago

If you share code, I try to help to resolve your issue.

duemaster commented 5 years ago

@kittaakos

This is my parent widget code.

@injectable()
export class TestFileExplorerWidget extends BaseWidget {

    protected contentNode: HTMLElement;

    protected fileExplorerWidgetNode: HTMLElement;
    protected fileExplorerWidgetNode2: HTMLElement;

    constructor(
        @inject(FileSystem) private readonly fileSystem: FileSystem,
        @inject(WorkspaceService) private readonly workspaceService: WorkspaceService,
        @inject(MessageService) private readonly messageService: MessageService,
        @inject(CustomFileExplorerWidget) private readonly customFileExplorerWidget: CustomFileExplorerWidget,
        @inject(CustomFileExplorerWidget) private readonly widget2: CustomFileExplorerWidget
    ) {
        super();
    }

    @postConstruct()
    init() {
        this.id = TEST_EXPLORER_WIDGET_ID;
        this.title.label = TEST_EXPLORER_WIDGET_LABEL;
        this.title.caption = TEST_EXPLORER_WIDGET_LABEL;
        this.title.closable = true;

        this.contentNode = document.createElement('div');
        this.contentNode.classList.add('parent-container');
        this.fileExplorerWidgetNode = document.createElement('div');

        this.fileExplorerWidgetNode2 = document.createElement('div');

        this.contentNode.appendChild(this.fileExplorerWidgetNode);
        this.contentNode.appendChild(this.fileExplorerWidgetNode2);

        this.node.appendChild(this.contentNode);
    }

    onAfterAttach() {
        Widget.attach(this.customFileExplorerWidget, this.fileExplorerWidgetNode);
        Widget.attach(this.widget2, this.fileExplorerWidgetNode);
    }
}
@injectable()
export class TestFileExplorerContribution extends AbstractViewContribution<TestFileExplorerWidget> implements FrontendApplicationContribution {

    /**
     * `AbstractViewContribution` handles the creation and registering
     *  of the widget including commands, menus, and keybindings.
     * 
     * We can pass `defaultWidgetOptions` which define widget properties such as 
     * its location `area` (`main`, `left`, `right`, `bottom`), `mode`, and `ref`.
     * 
     */
    constructor(
        @inject(FrontendApplicationStateService) private readonly stateService: FrontendApplicationStateService
    ) {
        super({
            widgetId: TEST_EXPLORER_WIDGET_ID,
            widgetName: TEST_EXPLORER_WIDGET_LABEL,
            defaultWidgetOptions: { area: 'left', rank: 6 },
            toggleCommandId: TestExplorerCommand.id
        });
    }

    async initializeLayout() {
        await this.openView();
    }

    async onStart(app: FrontendApplication) {
        this.stateService.reachedState('ready').then(
            async (a) => {
                await this.openView({ activate: true, reveal: true });
            }
        );
    }
}

Please let me know if you need more of which parts of my code.

duemaster commented 5 years ago

Basically, I am trying to create "child-widgets" which are modifications of a Navigator Widget where instead of showing the directory of the Workspace Root, instead show the directory of a customized location (Subset of workspace).

I will then display these "child-widgets" inside of a "parent-widget".

duemaster commented 5 years ago

@kittaakos I found out that the tree model's root is not null after updating. However, the file tree is still not being displayed inside the widget.

I have created a repository here that replicates the issue if it helps.

kittaakos commented 5 years ago

Thanks for the link, I will find some time to look into it today. Bear with me.

duemaster commented 5 years ago

Thanks!

kittaakos commented 5 years ago

I could not get this running although the model looks good. Can you please try to customize the FileTree instead of the FileNavigatorTree? We might have an issue with the navigator or I have overlooked something. Please look into the FileDialog. Internally, it uses a FileDialogWidget.

I do not have much time to look into this issue now.

duemaster commented 5 years ago

@kittaakos Do you mean to extend FileTreeModel instead of FileNavigatorModel?

kittaakos commented 5 years ago

Yes, and for the tree container binding, this should be sufficient:

function createCustomFileExplorer(parent: interfaces.Container): CustomFileExplorerWidget {
    const child = createFileTreeContainer(parent);

    child.bind(CustomFileNavigatorModel).toSelf();
    child.rebind(TreeModel).toService(CustomFileNavigatorModel);

    child.unbind(FileTreeWidget);
    child.bind(CustomFileExplorerWidget).toSelf();

    return child.get(CustomFileExplorerWidget);
}
duemaster commented 5 years ago

@kittaakos I tried changing to FileTreeModel and still receives the same result.

Then I tried changing the code for createRoot() for the navigator-model.ts (in theia master branch) to the one I showed you in the link.

The previous createRoot() looks like this:

protected async createRoot(): Promise<TreeNode | undefined> {
        if (this.workspaceService.opened) {
            const workspaceNode = WorkspaceNode.createRoot();
            const roots = await this.workspaceService.roots;
            for (const root of roots) {
                workspaceNode.children.push(
                    await this.tree.createWorkspaceRoot(root, workspaceNode)
                );
            }

            return workspaceNode;
        }
    }

when I changed it to

    protected async createRoot(): Promise<TreeNode | undefined> {
        if (this.workspaceService.opened) {
            const workspaceNode = WorkspaceNode.createRoot();

            const customFilePath = await this.fileSystem.getCurrentUserHome();
            if (customFilePath) {
                workspaceNode.children.push(
                    await this.tree.createWorkspaceRoot(customFilePath, workspaceNode)
                );
            }

            return workspaceNode;
        }
    }

The default FileNavigator widget still works but now directed at my home directory, so it is werid that it wouldn't work.

@jbicker mentioned about FileNavigator being injected as a Singleton in #4385 , could this be the reason why it wasn't appearing for my use case?

hyy215 commented 3 years ago

do you resolve it? @duemaster