jupyterlab-emrys
Version:
A computational environment for Jupyter. Powered by Emrys
856 lines (855 loc) • 27.4 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var phosphor_dragdrop_1 = require('phosphor-dragdrop');
var phosphor_panel_1 = require('phosphor-panel');
var phosphor_signaling_1 = require('phosphor-signaling');
var phosphor_widget_1 = require('phosphor-widget');
var observablelist_1 = require('../../common/observablelist');
/**
* The threshold in pixels to start a drag event.
*/
var DRAG_THRESHOLD = 5;
/**
* The factory MIME type supported by phosphor dock panels.
*/
var FACTORY_MIME = 'application/x-phosphor-widget-factory';
/**
* The class name added to an output area widget.
*/
var OUTPUT_AREA_CLASS = 'jp-OutputArea';
/**
* The class name added to a "mirrored" output area widget created by a drag.
*/
var MIRRORED_OUTPUT_AREA_CLASS = 'jp-MirroredOutputArea';
/**
* The class name added to an output widget.
*/
var OUTPUT_CLASS = 'jp-Output';
/**
* The class name added to an execute result.
*/
var EXECUTE_CLASS = 'jp-Output-executeResult';
/**
* The class name added to display data.
*/
var DISPLAY_CLASS = 'jp-Output-displayData';
/**
* The class name added to stdout data.
*/
var STDOUT_CLASS = 'jp-Output-stdout';
/**
* The class name added to stderr data.
*/
var STDERR_CLASS = 'jp-Output-stderr';
/**
* The class name added to error data.
*/
var ERROR_CLASS = 'jp-Output-error';
/**
* The class name added to stdin data.
*/
var STDIN_CLASS = 'jp-Output-stdin';
/**
* The class name added to stdin data prompt nodes.
*/
var STDIN_PROMPT_CLASS = 'jp-Output-stdinPrompt';
/**
* The class name added to stdin data input nodes.
*/
var STDIN_INPUT_CLASS = 'jp-Output-stdinInput';
/**
* The class name added to stdin rendered text nodes.
*/
var STDIN_RENDERED_CLASS = 'jp-Output-stdinRendered';
/**
* The class name added to fixed height output areas.
*/
var FIXED_HEIGHT_CLASS = 'jp-mod-fixedHeight';
/**
* The class name added to collaped output areas.
*/
var COLLAPSED_CLASS = 'jp-mod-collapsed';
/**
* The class name added to output area prompts.
*/
var PROMPT_CLASS = 'jp-Output-prompt';
/**
* The class name added to output area results.
*/
var RESULT_CLASS = 'jp-Output-result';
/**
* An output area widget.
*
* #### Notes
* The widget model must be set separately and can be changed
* at any time. Consumers of the widget must account for a
* `null` model, and may want to listen to the `modelChanged`
* signal.
*/
var OutputAreaWidget = (function (_super) {
__extends(OutputAreaWidget, _super);
/**
* Construct an output area widget.
*/
function OutputAreaWidget(options) {
_super.call(this);
this._trusted = false;
this._fixedHeight = false;
this._collapsed = false;
this._model = null;
this._rendermime = null;
this._renderer = null;
this.addClass(OUTPUT_AREA_CLASS);
this._rendermime = options.rendermime;
this._renderer = options.renderer || OutputAreaWidget.defaultRenderer;
this.layout = new phosphor_panel_1.PanelLayout();
}
/**
* Create a mirrored output widget.
*/
OutputAreaWidget.prototype.mirror = function () {
var rendermime = this._rendermime;
var renderer = this._renderer;
var widget = new OutputAreaWidget({ rendermime: rendermime, renderer: renderer });
widget.model = this._model;
widget.trusted = this._trusted;
widget.title.text = 'Mirrored Output';
widget.title.closable = true;
widget.addClass(MIRRORED_OUTPUT_AREA_CLASS);
return widget;
};
Object.defineProperty(OutputAreaWidget.prototype, "modelChanged", {
/**
* A signal emitted when the widget's model changes.
*/
get: function () {
return Private.modelChangedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "modelDisposed", {
/**
* A signal emitted when the widget's model is disposed.
*/
get: function () {
return Private.modelDisposedSignal.bind(this);
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "model", {
/**
* The model for the widget.
*/
get: function () {
return this._model;
},
set: function (newValue) {
if (!newValue && !this._model || newValue === this._model) {
return;
}
var oldValue = this._model;
this._model = newValue;
// Trigger private, protected, and public updates.
this._onModelChanged(oldValue, newValue);
this.onModelChanged(oldValue, newValue);
this.modelChanged.emit(void 0);
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "rendermime", {
/**
* Get the rendermime instance used by the widget.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._rendermime;
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "renderer", {
/**
* Get the renderer used by the widget.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
return this._renderer;
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "trusted", {
/**
* The trusted state of the widget.
*/
get: function () {
return this._trusted;
},
set: function (value) {
if (this._trusted === value) {
return;
}
this._trusted = value;
// Trigger a update of the child widgets.
var layout = this.layout;
for (var i = 0; i < layout.childCount(); i++) {
this._updateChild(i);
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "collapsed", {
/**
* The collapsed state of the widget.
*/
get: function () {
return this._collapsed;
},
set: function (value) {
if (this._collapsed === value) {
return;
}
this._collapsed = value;
this.update();
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputAreaWidget.prototype, "fixedHeight", {
/**
* The fixed height state of the widget.
*/
get: function () {
return this._fixedHeight;
},
set: function (value) {
if (this._fixedHeight === value) {
return;
}
this._fixedHeight = value;
this.update();
},
enumerable: true,
configurable: true
});
/**
* Dispose of the resources held by the widget.
*/
OutputAreaWidget.prototype.dispose = function () {
// Do nothing if already disposed.
if (this.isDisposed) {
return;
}
this._model = null;
this._rendermime = null;
this._renderer = null;
_super.prototype.dispose.call(this);
};
/**
* Get the child widget at the specified index.
*/
OutputAreaWidget.prototype.childAt = function (index) {
var layout = this.layout;
return layout.childAt(index);
};
/**
* Get the number of child widgets.
*/
OutputAreaWidget.prototype.childCount = function () {
var layout = this.layout;
return layout.childCount();
};
/**
* Handle `update_request` messages.
*/
OutputAreaWidget.prototype.onUpdateRequest = function (msg) {
if (this.collapsed) {
this.addClass(COLLAPSED_CLASS);
}
else {
this.removeClass(COLLAPSED_CLASS);
}
if (this.fixedHeight) {
this.addClass(FIXED_HEIGHT_CLASS);
}
else {
this.removeClass(FIXED_HEIGHT_CLASS);
}
};
/**
* Handle a new model.
*
* #### Notes
* This method is called after the model change has been handled
* internally and before the `modelChanged` signal is emitted.
* The default implementation is a no-op.
*/
OutputAreaWidget.prototype.onModelChanged = function (oldValue, newValue) { };
/**
* Handle a change to the model.
*/
OutputAreaWidget.prototype._onModelChanged = function (oldValue, newValue) {
var layout = this.layout;
if (oldValue) {
oldValue.changed.disconnect(this._onModelStateChanged, this);
oldValue.disposed.disconnect(this._onModelDisposed, this);
}
var start = newValue ? newValue.length : 0;
// Clear unnecessary child widgets.
for (var i = start; i < layout.childCount(); i++) {
this._removeChild(i);
}
if (!newValue) {
return;
}
newValue.changed.connect(this._onModelStateChanged, this);
newValue.disposed.connect(this._onModelDisposed, this);
// Reuse existing child widgets.
for (var i = 0; i < layout.childCount(); i++) {
this._updateChild(i);
}
// Add new widgets as necessary.
for (var i = layout.childCount(); i < newValue.length; i++) {
this._addChild();
}
};
/**
* Handle a model disposal.
*/
OutputAreaWidget.prototype.onModelDisposed = function (oldValue, newValue) { };
OutputAreaWidget.prototype._onModelDisposed = function () {
this.modelDisposed.emit(void 0);
this.dispose();
};
/**
* Add a child to the layout.
*/
OutputAreaWidget.prototype._addChild = function () {
var widget = this._renderer.createOutput({ rendermime: this.rendermime });
var layout = this.layout;
layout.addChild(widget);
this._updateChild(layout.childCount() - 1);
};
/**
* Remove a child from the layout.
*/
OutputAreaWidget.prototype._removeChild = function (index) {
var layout = this.layout;
layout.childAt(index).dispose();
};
/**
* Update a child in the layout.
*/
OutputAreaWidget.prototype._updateChild = function (index) {
var layout = this.layout;
var widget = layout.childAt(index);
var output = this._model.get(index);
widget.render(output, this._trusted);
};
/**
* Follow changes on the model state.
*/
OutputAreaWidget.prototype._onModelStateChanged = function (sender, args) {
var _this = this;
switch (args.type) {
case observablelist_1.ListChangeType.Add:
// Children are always added at the end.
this._addChild();
break;
case observablelist_1.ListChangeType.Replace:
// Only "clear" is supported by the model.
// When an output area is cleared and then quickly replaced with new
// content (as happens with @interact in widgets, for example), the
// quickly changing height can make the page jitter.
// We introduce a small delay in the minimum height
// to prevent this jitter.
var rect = this.node.getBoundingClientRect();
var oldHeight_1 = this.node.style.minHeight;
this.node.style.minHeight = rect.height + "px";
setTimeout(function () { _this.node.style.minHeight = oldHeight_1; }, 50);
var oldValues = args.oldValue;
for (var i = args.oldIndex; i < oldValues.length; i++) {
this._removeChild(args.oldIndex);
}
break;
case observablelist_1.ListChangeType.Set:
this._updateChild(args.newIndex);
break;
default:
break;
}
this.update();
};
return OutputAreaWidget;
}(phosphor_widget_1.Widget));
exports.OutputAreaWidget = OutputAreaWidget;
/**
* A namespace for OutputAreaWidget statics.
*/
var OutputAreaWidget;
(function (OutputAreaWidget) {
/**
* The default implementation of `IRenderer`.
*/
var Renderer = (function () {
function Renderer() {
}
/**
* Create an output widget.
*
*
* @returns A new widget for an output.
*/
Renderer.prototype.createOutput = function (options) {
return new OutputWidget(options);
};
return Renderer;
}());
OutputAreaWidget.Renderer = Renderer;
/**
* The default `Renderer` instance.
*/
OutputAreaWidget.defaultRenderer = new Renderer();
})(OutputAreaWidget = exports.OutputAreaWidget || (exports.OutputAreaWidget = {}));
/**
* The gutter on the left side of the OutputWidget
*/
var OutputGutter = (function (_super) {
__extends(OutputGutter, _super);
function OutputGutter() {
_super.apply(this, arguments);
this._drag = null;
this._dragData = null;
}
/**
* Handle the DOM events for the output gutter widget.
*
* @param event - The DOM event sent to the widget.
*
* #### Notes
* This method implements the DOM `EventListener` interface and is
* called in response to events on the panel's DOM node. It should
* not be called directly by user code.
*/
OutputGutter.prototype.handleEvent = function (event) {
switch (event.type) {
case 'mousedown':
this._evtMousedown(event);
break;
case 'mouseup':
this._evtMouseup(event);
break;
case 'mousemove':
this._evtMousemove(event);
break;
}
};
/**
* A message handler invoked on an `'after-attach'` message.
*/
OutputGutter.prototype.onAfterAttach = function (msg) {
_super.prototype.onAfterAttach.call(this, msg);
this.node.addEventListener('mousedown', this);
};
/**
* A message handler invoked on a `'before-detach'` message.
*/
OutputGutter.prototype.onBeforeDetach = function (msg) {
_super.prototype.onBeforeDetach.call(this, msg);
var node = this.node;
node.removeEventListener('mousedown', this);
};
/**
* Handle the `'mousedown'` event for the widget.
*/
OutputGutter.prototype._evtMousedown = function (event) {
// Left mouse press for drag start.
if (event.button === 0) {
this._dragData = { pressX: event.clientX, pressY: event.clientY };
document.addEventListener('mouseup', this, true);
document.addEventListener('mousemove', this, true);
}
};
/**
* Handle the `'mouseup'` event for the widget.
*/
OutputGutter.prototype._evtMouseup = function (event) {
if (event.button !== 0 || !this._drag) {
document.removeEventListener('mousemove', this, true);
document.removeEventListener('mouseup', this, true);
return;
}
event.preventDefault();
event.stopPropagation();
};
/**
* Handle the `'mousemove'` event for the widget.
*/
OutputGutter.prototype._evtMousemove = function (event) {
event.preventDefault();
event.stopPropagation();
// Bail if we are the one dragging.
if (this._drag) {
return;
}
// Check for a drag initialization.
var data = this._dragData;
var dx = Math.abs(event.clientX - data.pressX);
var dy = Math.abs(event.clientY - data.pressY);
if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) {
return;
}
this._startDrag(event.clientX, event.clientY);
};
/**
* Start a drag event.
*/
OutputGutter.prototype._startDrag = function (clientX, clientY) {
var _this = this;
// Set up the drag event.
this._drag = new phosphor_dragdrop_1.Drag({
mimeData: new phosphor_dragdrop_1.MimeData(),
supportedActions: phosphor_dragdrop_1.DropActions.Copy,
proposedAction: phosphor_dragdrop_1.DropAction.Copy
});
this._drag.mimeData.setData(FACTORY_MIME, function () {
var outputArea = _this.parent.parent;
return outputArea.mirror();
});
// Remove mousemove and mouseup listeners and start the drag.
document.removeEventListener('mousemove', this, true);
document.removeEventListener('mouseup', this, true);
this._drag.start(clientX, clientY).then(function (action) {
_this._drag = null;
});
};
/**
* Dispose of the resources held by the widget.
*/
OutputGutter.prototype.dispose = function () {
// Do nothing if already disposed.
if (this.isDisposed) {
return;
}
this._dragData = null;
this._drag = null;
_super.prototype.dispose.call(this);
};
return OutputGutter;
}(phosphor_widget_1.Widget));
exports.OutputGutter = OutputGutter;
/**
* An output widget.
*/
var OutputWidget = (function (_super) {
__extends(OutputWidget, _super);
/**
* Construct a new output widget.
*/
function OutputWidget(options) {
_super.call(this);
this._rendermime = null;
this._placeholder = null;
var layout = new phosphor_panel_1.PanelLayout();
this.layout = layout;
var prompt = new OutputGutter();
this._placeholder = new phosphor_widget_1.Widget();
this.addClass(OUTPUT_CLASS);
prompt.addClass(PROMPT_CLASS);
this._placeholder.addClass(RESULT_CLASS);
layout.addChild(prompt);
layout.addChild(this._placeholder);
this._rendermime = options.rendermime;
}
Object.defineProperty(OutputWidget.prototype, "prompt", {
/**
* The prompt widget used by the output widget.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
var layout = this.layout;
return layout.childAt(0);
},
enumerable: true,
configurable: true
});
Object.defineProperty(OutputWidget.prototype, "output", {
/**
* The rendered output used by the output widget.
*
* #### Notes
* This is a read-only property.
*/
get: function () {
var layout = this.layout;
return layout.childAt(1);
},
enumerable: true,
configurable: true
});
/**
* Dispose of the resources held by the widget.
*/
OutputWidget.prototype.dispose = function () {
this._rendermime = null;
this._placeholder = null;
_super.prototype.dispose.call(this);
};
/**
* Clear the widget contents.
*/
OutputWidget.prototype.clear = function () {
this.setOutput(this._placeholder);
this.prompt.node.textContent = '';
};
/**
* Render an output.
*
* @param output - The kernel output message payload.
*
* @param trusted - Whether the output is trusted.
*/
OutputWidget.prototype.render = function (output, trusted) {
if (trusted === void 0) { trusted = false; }
// Handle an input request.
if (output.output_type === 'input_request') {
var child_1 = new InputWidget(output);
this.setOutput(child_1);
return;
}
// Extract the data from the output and sanitize if necessary.
var rendermime = this._rendermime;
var bundle = this.getBundle(output);
var data = this.convertBundle(bundle);
// Clear the content.
this.clear();
// Bail if no data to display.
var msg = 'Did not find renderer for output mimebundle.';
if (!data) {
console.log(msg);
return;
}
// Create the output result area.
var child = rendermime.render(data, trusted);
if (!child) {
console.log(msg);
console.log(data);
return;
}
this.setOutput(child);
// Add classes and output prompt as necessary.
switch (output.output_type) {
case 'execute_result':
child.addClass(EXECUTE_CLASS);
var count = output.execution_count;
this.prompt.node.textContent = "Out[" + (count === null ? ' ' : count) + "]:";
break;
case 'display_data':
child.addClass(DISPLAY_CLASS);
break;
case 'stream':
if (output.name === 'stdout') {
child.addClass(STDOUT_CLASS);
}
else {
child.addClass(STDERR_CLASS);
}
break;
case 'error':
child.addClass(ERROR_CLASS);
break;
default:
console.error("Unrecognized output type: " + output.output_type);
data = {};
}
};
/**
* Set the widget output.
*/
OutputWidget.prototype.setOutput = function (value) {
var layout = this.layout;
var old = this.output;
value = value || null;
if (old === value) {
return;
}
if (old) {
if (old !== this._placeholder) {
old.dispose();
}
else {
old.parent = null;
}
}
if (value) {
layout.addChild(value);
value.addClass(RESULT_CLASS);
}
else {
layout.addChild(this._placeholder);
}
};
/**
* Get the mime bundle for an output.
*
* @params output - A kernel output message payload.
*
* @returns - A mime bundle for the payload.
*/
OutputWidget.prototype.getBundle = function (output) {
var bundle;
switch (output.output_type) {
case 'execute_result':
bundle = output.data;
break;
case 'display_data':
bundle = output.data;
break;
case 'stream':
bundle = {
'application/vnd.jupyter.console-text': output.text
};
break;
case 'error':
var out = output;
var traceback = out.traceback.join('\n');
bundle = {
'application/vnd.jupyter.console-text': traceback ||
out.ename + ": " + out.evalue
};
break;
default:
console.error("Unrecognized output type: " + output.output_type);
bundle = {};
}
return bundle;
};
/**
* Convert a mime bundle to a mime map.
*/
OutputWidget.prototype.convertBundle = function (bundle) {
var map = Object.create(null);
for (var mimeType in bundle) {
var value = bundle[mimeType];
if (Array.isArray(value)) {
map[mimeType] = value.join('\n');
}
else {
map[mimeType] = value;
}
}
return map;
};
return OutputWidget;
}(phosphor_widget_1.Widget));
exports.OutputWidget = OutputWidget;
/**
* A widget that handles stdin requests from the kernel.
*/
var InputWidget = (function (_super) {
__extends(InputWidget, _super);
/**
* Construct a new input widget.
*/
function InputWidget(request) {
_super.call(this);
this._kernel = null;
this._input = null;
this.addClass(STDIN_CLASS);
var text = this.node.firstChild;
text.textContent = request.prompt;
this._input = this.node.lastChild;
if (request.password) {
this._input.type = 'password';
}
this._kernel = request.kernel;
}
/**
* Create the node for an InputWidget.
*/
InputWidget.createNode = function () {
var node = document.createElement('div');
var prompt = document.createElement('span');
prompt.className = STDIN_PROMPT_CLASS;
var input = document.createElement('input');
input.className = STDIN_INPUT_CLASS;
node.appendChild(prompt);
node.appendChild(input);
return node;
};
/**
* Handle the DOM events for the widget.
*
* @param event - The DOM event sent to the widget.
*
* #### Notes
* This method implements the DOM `EventListener` interface and is
* called in response to events on the dock panel's node. It should
* not be called directly by user code.
*/
InputWidget.prototype.handleEvent = function (event) {
var input = this._input;
if (event.type === 'keydown') {
if (event.keyCode === 13) {
this._kernel.sendInputReply({
value: input.value
});
var rendered = document.createElement('span');
rendered.className = STDIN_RENDERED_CLASS;
if (input.type === 'password') {
rendered.textContent = Array(input.value.length + 1).join('·');
}
else {
rendered.textContent = input.value;
}
this.node.replaceChild(rendered, input);
}
// Suppress keydown events from leaving the input.
event.stopPropagation();
}
};
/**
* Handle `after-attach` messages sent to the widget.
*/
InputWidget.prototype.onAfterAttach = function (msg) {
this._input.focus();
this._input.addEventListener('keydown', this);
};
/**
* Handle `before-detach` messages sent to the widget.
*/
InputWidget.prototype.onBeforeDetach = function (msg) {
this._input.removeEventListener('keydown', this);
};
return InputWidget;
}(phosphor_widget_1.Widget));
/**
* A namespace for private data.
*/
var Private;
(function (Private) {
/**
* A signal emitted when the widget's model changes.
*/
Private.modelChangedSignal = new phosphor_signaling_1.Signal();
/**
* A signal emitted when the widget's model is disposed.
*/
Private.modelDisposedSignal = new phosphor_signaling_1.Signal();
})(Private || (Private = {}));