Rapid Prototyping with Phosphor

phosphor lets you build desktop-style applications, with DOM provided by your favorite libraries like d3, react, and friends. If you're using JupyterLab... you're already using Phosphor! For the most part, the average user won't ever have to touch Phosphor code. But this is Jyve. Use these unsafe, loaded footguns to interactively learn about Phosphor development!

A Hack: There is no phosphor

While there is no phosphor package on npm, we've gone ahead and loaded one in the global namespace if you have installed @deathbeds/jyve-lyb-phosphor. Like Jyve, Phosphor is developed in a single repo, but distributed as different packages. Because we don't have a generalized import system figured out yet, we just make this one up for you. We're going to use algorithms, messaging and widgets.

🤔 How might we make "standard lib" JupyterLab libraries easy and reliable to use in Jyve kernels?

In [2]:
W = phosphor.widgets
M = phosphor.messaging
A = phosphor.algorithm
Out[2]:
[object Object]

Since we have phosphor locally, let's use it to build a nice little documentation browser, attaching directly the JupyterLab application shell.

In [3]:
docs = new W.DockPanel()
docs.title.label = 'PhosphorJS'
docs.title.closable = true
docs.title.iconClass = 'jp-QuestionMarkIcon jp-MaterialIcon'
docs.id = 'phosphor-docs'

keys = Object.keys(phosphor).filter((m) => m !== 'CSS')
keys.sort()

keys.map((m) => {
    let d = new W.Widget({node: document.createElement('iframe')})
    d.title.label = m
    d.title.iconClass = 'jyv-Lyb-Phosphor'  // not strictly required, but pretty easy!
    d.node.src = `https://phosphorjs.github.io/phosphor/api/${m}/globals.html`
    docs.layout.addWidget(d, {mode: 'tab-before'});
})
JupyterLab.shell.addToMainArea(docs, {mode: 'split-bottom'})
Out[3]:

That right there is probably a viable JupyterLab extension.

🤔 How might we make a Jyve Notebook into a JupyterLab extension?

Using Phosphor in a Jyve iframe

Interacting with JupyterLab is great, and if you have Jyve, you have JupyterLab. However, you might want to craft a simpler experience, but still use phosphor. Without the JupyterLab style machinery, you'll need to do some work.

A Hack: Phosphor CSS

The iframe starts as an empty DOM. Phosphor needs its own CSS, as well as some baseline CSS.

🤔 How might we conveniently make CSS available, even offline, for "framework" CSS?

In [4]:
sha = 'cc4052e5fda7e6d8e4dc4a78c0b2cde38b3c0e11'
styleRoot = `https://cdn.rawgit.com/phosphorjs/phosphor/${sha}/examples/example-dockpanel/style`

coreStyle = document.createElement('style')
coreStyle.innerHTML = phosphor.CSS.join("\n")
 
demoStyle = document.createElement('style')
demoStyle.innerHTML = ['index']
    .map((s) => `@import "${styleRoot}/${s}.css";`)
    .join("\n")

localStyle = document.createElement('style')
localStyle.innerHTML = `
body {
    display: flex;
    flex-direction: column;
    padding: 5px;
}
`

document.body.appendChild(coreStyle)
document.body.appendChild(demoStyle)
document.body.appendChild(localStyle)
Out[4]:
[object HTMLStyleElement]
In [5]:
main = new W.DockPanel()
main.id = 'main' // there's nothing special about main, but it's used in the demo CSS

window.onresize = () => { main.update(); }
Out[5]:
() => { main.update(); }

A Hack: Widget.attach

phosphor is loaded in the same context as JupyterLab, and is somewhat bound to the window object that hosts your Lab. Other problems will ensue, but this hack handles the basic case of getting all of the messaging set up inside the Jyve iframe.

🤔 How might we get a truly locally-hosted copy of phosphor in the iframe?

In [6]:
M.MessageLoop.sendMessage(main, W.Widget.Msg.BeforeAttach)
document.body.appendChild(main.node)
M.MessageLoop.sendMessage(main, W.Widget.Msg.AfterAttach)
Out[6]:

In [7]:
greet = new W.Widget()
greet.id = 'greeter'
greet.addClass('content')  // again, just because demo
greet.title.label = 'Hello'
greet.title.closable = true
main.layout.addWidget(greet)
Out[7]:

Interactively adding DOM

Here we're using the native browser API, but one could use d3, react or any other approach you can make available.

In [8]:
h1 = document.createElement('h1')
h1.textContent = `Hello World`
greet.node.appendChild(h1)
Out[8]:
[object HTMLHeadingElement]

Adding (really unsafe) interactivity

Because you're connected to all the phosphor machinery, you can seamlessly move DOM between a Jyve iframe and the main JupyterLab application.

In [9]:
btn = document.createElement('button')
btn.textContent = '⚠️ Do not press ⚠️'
btn.addEventListener('click', function(){
    if(greet.parent === main){
        JupyterLab.shell.addToMainArea(greet, {mode: 'split-right'})
    } else {
        main.layout.addWidget(greet)
    }
})
greet.node.appendChild(btn)
Out[9]:
[object HTMLButtonElement]
In [ ]: