Our `docs` directory doesn't use a `source/` subdirectory, so take that out of
the Webpack config. Add the generated files to .gitignore. Seeing as this file
wasn't landing in the right place and I don't see it referenced anywhere else
in the repo, I suspect that it's not actually doing anything.
I believe that we need to do this to be compatible with the JupyterLab 1.0.x
series. I don't fully understand what's going on, but changing this fixed a
failure to load for me, with an error along the lines of "No provider for
jupyterWidgetRegistry".
This limits the files included in the NPM package to just our JavaScript
files. To the best of my understanding, these are the only ones we want. This
reduces the size of the resulting tarball from 14M to 6k.
When we're serving up files in JupyterHub, we need to include the notebook
server's base_url in the URL we generate. This is obnoxious to determine from
inside a kernel, but I found some code that shows how to do it.
Also added some module-level documentation outlining what's going on and why.
Now by showing `my_image_layer.controls` you get widgets that let you adjust
the opacity, stretch function, and data range. The best UI I could come up
with for the data range has "coarse" controls that are typed in manually and
"fine" controls that use a range slider widget.
I note that in my testing, the adjustments don't apply until focus changes to
another widget, which makes for a kind of weak UX. I checked the ipywidgets
bug tracker but don't see any complaints about this; I'm not sure what's going
on, since this seems like a pretty basic piece of functionality.
The way that the ImageLayer constructor was structured, any user specification
of the vmax and vmin values would be overridden. Restructure to prefer them,
and also make sure that the settings are applied but not in a redundant way.
In the Jupyter widget system, it is possible to create multiple views of a
single widget model. Views can be "detached", in which case their underlying
DOM elements are removed from the document. This is what happens if you create
a WWT view in a JupyterLab notebook, use the "Create a new view for this
output" context menu item to move the output to a new tab, and then hide the
original output. There is one underlying widget model and two views, only one
of which has active DOM elements.
The wrinkle for pywwt is that all of our widget state is stored inside the
<iframe> that does the rendering, so we're not really a real model-view setup.
The way things currently work, operations that affect the widget state are
passed as message from Python to JS, which are then distributed to all of the
views. So long as all views receive the same messages, things should stay in
sync. (Although if you create a widget, do some things to it, and then create
a new view of it, that view will have missed historical messages and so will
be out of sync.)
The problem I discovered was that if you send messages to a "detached" widget
view, you sometimes get JS exceptions. And apparently the code that
distributes the messages just terminates, preventing the control messages from
being sent to other widget views. This means that those views basically stop
responding to any Python controls.
This is bad news for the workflow of creating a WWT view then moving it to a
separate tab, which is something we very much want to support for UX reasons.
The real solution is to Do The Right Thing when a widget is detached, so that
the JS exceptions don't happen in the first place, but that's going to take
some time to get right. In the meantime, let's just log and swallow those JS
exceptions, so that the message passing can keep on working to the views that
aren't broken. This will keep things limping along.