nteract / vdom

🎄 Virtual DOM for Python
https://github.com/nteract/vdom/blob/master/docs/mimetype-spec.md
BSD 3-Clause "New" or "Revised" License
222 stars 35 forks source link

VDOM as a Gateway to Plugins #72

Open rmorshea opened 6 years ago

rmorshea commented 6 years ago

What if you could load React components that were registered as "plugins" using vdom...

{
  "tagName": "MyCoolPlugin",
  "attributes": {
    "data-stuff-my-plugin-uses": "something-super-cool"
  },
}

Assuming you had some sort of global plugin storage mechanism it seems like it would be relatively trivial to identify whether or not the tagName was novel or not before grabbing the appropriate plugin and dynamically mounting it.

The part I don't know anything about is what that "global plugin storage" would look like, or how users would go about registering their plugin with nteract.

rmorshea commented 6 years ago

ping: @mpacer

rgbkrk commented 6 years ago

Yeah I think @gnestor had hoped for a safelist of components, and that it would be those that are require-able (straight from npm packages).

gnestor commented 6 years ago

Hi @rmorshea! 👋

First, vdom doesn't support any tag names that are not native DOM elements. We can change that but that's the first blocker.

Second, this could (and probably should) be a separate Python library (e.g. _vdomreact).

Third, I was able to prototype this in the notebook:

# In[1]
from IPython.display import HTML

def render_component(package, module, props, stylesheets = []):
    # Generate a unique id for the root DOM node (that React mounts to)
    id = str(hash(package + str(props)))
    # Optionally load stylesheets that the React component depends on
    css = '\n'.join([f'<link href="{url}" rel="stylesheet" />' for url in stylesheets])
    html = f'''
        <div id="{id}" style="padding: 10px;""></div>
        <script type="module">
            import React from '//dev.jspm.io/react';
            import ReactDOM from '//dev.jspm.io/react-dom';
            import ImportedPackage from '//dev.jspm.io/{package}';

            // Import a specific or the default module from the package
            const ImportedComponent = '{module}' ? 
                ImportedPackage['{module}'] : 
                (ImportedPackage.default ? ImportedPackage.default : ImportedPackage);

            ReactDOM.render(
                React.createElement(ImportedComponent, {props}),
                document.getElementById('{id}')
            );
        </script>
    '''
    return HTML(html + css)

# In[2]
render_component(
    package='@blueprintjs/core', 
    module='Button', 
    props={
        'intent': 'success',
        'text': 'Click me'
    }
)

# In[3]
render_component(
    package='@blueprintjs/core', 
    module='RangeSlider', 
    props={
        'min': 0,
        'max': 100,
        'stepSize': 5,
        'labelStepSize': 25 
    },
    stylesheets=[
        '//dev.jspm.io/npm:@blueprintjs/core@3.6.1/lib/css/blueprint.css',
        '//dev.jspm.io/npm:@blueprintjs/icons@3.1.0/lib/css/blueprint-icons.css',
        '//dev.jspm.io/npm:normalize.css@8.0.0/normalize.css'
    ]
)

image

A few notes:

from vdom_react import import_component

range_slider = import_component(package='@blueprintjs/core', module='RangeSlider', stylesheets=[
    '//dev.jspm.io/npm:@blueprintjs/core@3.6.1/lib/css/blueprint.css',
    '//dev.jspm.io/npm:@blueprintjs/icons@3.1.0/lib/css/blueprint-icons.css',
    '//dev.jspm.io/npm:normalize.css@8.0.0/normalize.css'
])

range_slider(min=0, max=100, steoSize=5, labelStepSize=25)

Now, combine that with event support in vdom:

def handle_change(event):
    slider_value = event['target']['value']
    my_slider.update(create_slider(value))

def create_slider(value):
    return range_slider(
        min=0, 
        max=100, 
        stepSize=5, 
        labelStepSize=25, 
        onChange=handle_change, 
        value=value
)

my_slider = create_slider(value=0)
rmorshea commented 6 years ago

@gnestor I'm a little too tired to take this in at the moment, but this is definitely interesting!

I haven't followed everything that's gone on with vdom but is the my_slider.update method something new that would be provided by the hypothetical vdom_react library?

gnestor commented 6 years ago

@rmorshea Understood 👌

my_slider is a display handle (the result of IPython.display.display or IPython.display.HTML or any ipython display function). As of ~2 years ago, ipython also provides an update_display function which allows the kernel-side to update a display on the front-end (even if it's in the output of another cell). Display handles also have an update method which is just aconveniencee method for update_display.