{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Rapid Prototyping with Phosphor\n", "[`phosphor`](http://phosphorjs.github.io/) 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!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Hack: There is no `phosphor`\n", "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`.\n", "\n", "> ### _🤔 How might we make \"standard lib\" JupyterLab libraries **easy** and **reliable** to use in Jyve kernels?_" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[object Object]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "W = phosphor.widgets\n", "M = phosphor.messaging\n", "A = phosphor.algorithm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we have `phosphor` locally, let's use it to build a nice little documentation browser, attaching directly the JupyterLab application shell." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "docs = new W.DockPanel()\n", "docs.title.label = 'PhosphorJS'\n", "docs.title.closable = true\n", "docs.title.iconClass = 'jp-QuestionMarkIcon jp-MaterialIcon'\n", "docs.id = 'phosphor-docs'\n", "\n", "keys = Object.keys(phosphor).filter((m) => m !== 'CSS')\n", "keys.sort()\n", "\n", "keys.map((m) => {\n", " let d = new W.Widget({node: document.createElement('iframe')})\n", " d.title.label = m\n", " d.title.iconClass = 'jyv-Lyb-Phosphor' // not strictly required, but pretty easy!\n", " d.node.src = `https://phosphorjs.github.io/phosphor/api/${m}/globals.html`\n", " docs.layout.addWidget(d, {mode: 'tab-before'});\n", "})\n", "JupyterLab.shell.addToMainArea(docs, {mode: 'split-bottom'})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That right there is probably a viable [JupyterLab extension](http://jupyterlab.readthedocs.io/en/stable/developer/extension_dev.html).\n", "\n", "> ### _🤔 How might we make a Jyve Notebook into a JupyterLab extension?_\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using Phosphor in a Jyve iframe\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Hack: Phosphor CSS\n", "The iframe starts as an empty DOM. Phosphor needs its own CSS, as well as some baseline CSS.\n", "\n", "> ### _🤔 How might we conveniently make CSS available, even offline, for \"framework\" CSS?_" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[object HTMLStyleElement]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sha = 'cc4052e5fda7e6d8e4dc4a78c0b2cde38b3c0e11'\n", "styleRoot = `https://cdn.rawgit.com/phosphorjs/phosphor/${sha}/examples/example-dockpanel/style`\n", "\n", "coreStyle = document.createElement('style')\n", "coreStyle.innerHTML = phosphor.CSS.join(\"\\n\")\n", " \n", "demoStyle = document.createElement('style')\n", "demoStyle.innerHTML = ['index']\n", " .map((s) => `@import \"${styleRoot}/${s}.css\";`)\n", " .join(\"\\n\")\n", "\n", "localStyle = document.createElement('style')\n", "localStyle.innerHTML = `\n", "body {\n", " display: flex;\n", " flex-direction: column;\n", " padding: 5px;\n", "}\n", "`\n", "\n", "document.body.appendChild(coreStyle)\n", "document.body.appendChild(demoStyle)\n", "document.body.appendChild(localStyle)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "() => { main.update(); }" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "main = new W.DockPanel()\n", "main.id = 'main' // there's nothing special about main, but it's used in the demo CSS\n", "\n", "window.onresize = () => { main.update(); }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Hack: `Widget.attach`\n", "`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`.\n", "\n", "> ### _🤔 How might we get a truly locally-hosted copy of `phosphor` in the iframe?_" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.MessageLoop.sendMessage(main, W.Widget.Msg.BeforeAttach)\n", "document.body.appendChild(main.node)\n", "M.MessageLoop.sendMessage(main, W.Widget.Msg.AfterAttach)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "greet = new W.Widget()\n", "greet.id = 'greeter'\n", "greet.addClass('content') // again, just because demo\n", "greet.title.label = 'Hello'\n", "greet.title.closable = true\n", "main.layout.addWidget(greet)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interactively adding DOM\n", "Here we're using the native browser API, but one could use `d3`, `react` or any other approach you can make available." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[object HTMLHeadingElement]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h1 = document.createElement('h1')\n", "h1.textContent = `Hello World`\n", "greet.node.appendChild(h1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding (really unsafe) interactivity\n", "Because you're connected to all the `phosphor` machinery, you can seamlessly move DOM between a Jyve iframe and the main JupyterLab application." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[object HTMLButtonElement]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "btn = document.createElement('button')\n", "btn.textContent = '⚠️ Do not press ⚠️'\n", "btn.addEventListener('click', function(){\n", " if(greet.parent === main){\n", " JupyterLab.shell.addToMainArea(greet, {mode: 'split-right'})\n", " } else {\n", " main.layout.addWidget(greet)\n", " }\n", "})\n", "greet.node.appendChild(btn)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "JS (unsafe) — Jyve", "language": "javascript", "name": "jyve-kyrnel-js-unsafe" }, "language_info": { "codemirror_mode": { "name": "javascript" }, "file_extension": ".js", "mimetype": "text/javascript", "name": "javascript", "nbconvert_exporter": "javascript", "pygments_lexer": "javascript", "version": "ES2015" } }, "nbformat": 4, "nbformat_minor": 2 }