UNPKG

jupyterlab-emrys

Version:

A computational environment for Jupyter. Powered by Emrys

856 lines (855 loc) 27.4 kB
// 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 = {}));