Run a PhosphorJS DockerPanel with Widgets INSIDE a React component

Viewed 1169

I have a react app, and in one of the components I would like to render a Phosphor desktop (http://phosphorjs.github.io).

Web sources suggest the other way around, running React component inside a Phosphor Widget, but I need to wrap the Phosphor desktop with widgets inside a React component instead. Not sure how to go about it.

This is my first attempt:

import React, { Component } from 'react';
import './App.css';

import { DockPanel } from 'phosphor-dockpanel';
import { Message } from 'phosphor-messaging';
import { TabPanel } from 'phosphor-tabs';
import { ResizeMessage, Widget } from 'phosphor-widget';


class MyWidget extends Widget {

  static createNode() {
    console.log('widget:createNode');
    var node = document.createElement('div');
    var app = document.createElement('div');
    app.className = 'todoapp';
    node.appendChild(app);
    return node;
  }

  constructor(model) {
    super();
    this.addClass('TodoWidget');
    this._model = model;
    console.log('widget:constructor');
  }

  get model() {
    console.log('widget:getModel');    
    return this._model;
  }

  onAfterAttach(msg) {
    console.log('onAfterAttach');
    //this._model.subscribe(() => this.update());
    this.update();
  }

  onUpdateRequest(msg) {
    console.log('widget:onUpdateRequest');    
    var data = { model: this._model };
    var host = this.node.firstChild;
    //React.render(React.createElement(app.TodoApp, data), host);
  }
}


class ReactDockPanel extends Component {
  constructor(props) {
    super(props);
  }

  componentWillMount() {
    this.panel = new DockPanel();
    this.panel.id = this.props.id;

    this.widget = new MyWidget();
    this.panel.insertLeft(this.widget);
  }

  componentDidMount() {
    this.panel.attach(this.node);
  }

  render() {
    return (
      <div ref={el=>this.node=el}>
      </div>  
    );
  }    
}

class App extends Component {

  render() {
    return (
      <ReactDockPanel id='main'>
      </ReactDockPanel>
    );
  }
}

export default App;

I'm not sure how to replace React.render(React.createElement(app.TodoApp, data), host); Phosphor docs are for reference but not for learning so I'm going by their only example code.

Ideally I'd like to be able to render like this:

<ReactDockPanel id='mydesktop'>
  <SomeWidget insert='left' />
  <SomeOtherWidget insert='right' />
</ReactDockPanel>

Not sure it can be done.

1 Answers

I was able to make something similar work with React portals. In my case I'm trying to render React Component children instead of Widget children but hopefully it's still helpful. The steps I used are

1) In the initial render of my outer React component, use ref to get a reference to the element.

2) In componentDidMount, create a new DockPanel and attach it to the element. Then, loop through the widgets you want to have and a) Add each widget to the DockPanel, and b) Build up a list of nodes in which you will render React components and do this.setState with the list.

3) In the next render call, loop over the list of nodes in this.state and create portals.

The code looks something like this:

import {DockPanel, Widget} from "@phosphor/widgets";
import {createPortal} from "react-dom";

interface IWidgetInfo {
  component: JSX.Element;
  node: HTMLElement;
}

interface IDockState {
  widgetInfos: IWidgetInfo[];
}

interface IDockProps {
  children: JSX.Element[];
}

class WrapperWidget extends Widget {
  constructor(name: string, node: HTMLElement) {
    super({node});
    this.setFlag(Widget.Flag.DisallowLayout);
    this.title.label = name;
  }
}

export class Dock extends React.PureComponent<IDockProps, IDockState> {
  elem: HTMLElement;
  dock: DockPanel;

  componentDidMount() {
    this.dock = new DockPanel(this.context.store);

    let widgetInfos = [];
    for (let component of this.props.children) {
      let node = document.createElement("div");
      let widget = new WrapperWidget("Widget Name", node);
      this.dock.addWidget(widget);
      widgetInfos.push({node, component});
    }
    this.setState({...this.state, widgetInfos});

    Widget.attach(this.dock, this.elem);
  }

  render() {
    return (
      <div ref={(c) => this.elem = c}>
          {this.state.widgetsInfos.map(widgetInfo => {
             return createPortal(widgetInfo.component, widgetInfo.node);
          })}
      </div>
    );
  }
}
Related