outputarea.js
819 lines
| 26.7 KiB
| application/javascript
|
JavascriptLexer
Brian Granger
|
r7177 | //---------------------------------------------------------------------------- | ||
Matthias BUSSONNIER
|
r10776 | // Copyright (C) 2008 The IPython Development Team | ||
Brian Granger
|
r7177 | // | ||
// Distributed under the terms of the BSD License. The full license is in | ||||
// the file COPYING, distributed as part of this software. | ||||
//---------------------------------------------------------------------------- | ||||
//============================================================================ | ||||
// OutputArea | ||||
//============================================================================ | ||||
Matthias BUSSONNIER
|
r10776 | /** | ||
* @module IPython | ||||
* @namespace IPython | ||||
* @submodule OutputArea | ||||
*/ | ||||
Brian Granger
|
r7177 | var IPython = (function (IPython) { | ||
"use strict"; | ||||
var utils = IPython.utils; | ||||
Matthias BUSSONNIER
|
r10776 | /** | ||
* @class OutputArea | ||||
* | ||||
* @constructor | ||||
*/ | ||||
Brian Granger
|
r7177 | var OutputArea = function (selector, prompt_area) { | ||
this.selector = selector; | ||||
MinRK
|
r7362 | this.wrapper = $(selector); | ||
Brian Granger
|
r7177 | this.outputs = []; | ||
this.collapsed = false; | ||||
MinRK
|
r7362 | this.scrolled = false; | ||
Jonathan Frederic
|
r12592 | this.clear_queued = null; | ||
Brian Granger
|
r7177 | if (prompt_area === undefined) { | ||
this.prompt_area = true; | ||||
Brian Granger
|
r7181 | } else { | ||
this.prompt_area = prompt_area; | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
MinRK
|
r7362 | this.create_elements(); | ||
Brian Granger
|
r7177 | this.style(); | ||
MinRK
|
r7362 | this.bind_events(); | ||
}; | ||||
OutputArea.prototype.create_elements = function () { | ||||
this.element = $("<div/>"); | ||||
this.collapse_button = $("<div/>"); | ||||
this.prompt_overlay = $("<div/>"); | ||||
this.wrapper.append(this.prompt_overlay); | ||||
this.wrapper.append(this.element); | ||||
this.wrapper.append(this.collapse_button); | ||||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.style = function () { | ||||
MinRK
|
r7362 | this.collapse_button.hide(); | ||
this.prompt_overlay.hide(); | ||||
this.wrapper.addClass('output_wrapper'); | ||||
Matthias BUSSONNIER
|
r13332 | this.element.addClass('output'); | ||
MinRK
|
r7362 | |||
MinRK
|
r10897 | this.collapse_button.addClass("btn output_collapsed"); | ||
Harry Moreno
|
r9903 | this.collapse_button.attr('title', 'click to expand output'); | ||
MinRK
|
r7362 | this.collapse_button.html('. . .'); | ||
this.prompt_overlay.addClass('out_prompt_overlay prompt'); | ||||
Harry Moreno
|
r9903 | this.prompt_overlay.attr('title', 'click to expand output; double click to hide output'); | ||
MinRK
|
r7362 | |||
Brian Granger
|
r7177 | this.collapse(); | ||
}; | ||||
Matthias BUSSONNIER
|
r10776 | /** | ||
* Should the OutputArea scroll? | ||||
* Returns whether the height (in lines) exceeds a threshold. | ||||
* | ||||
* @private | ||||
* @method _should_scroll | ||||
* @param [lines=100]{Integer} | ||||
* @return {Bool} | ||||
* | ||||
*/ | ||||
MinRK
|
r7362 | OutputArea.prototype._should_scroll = function (lines) { | ||
Matthias BUSSONNIER
|
r10776 | if (lines <=0 ){ return } | ||
MinRK
|
r7362 | if (!lines) { | ||
MinRK
|
r7728 | lines = 100; | ||
MinRK
|
r7362 | } | ||
// line-height from http://stackoverflow.com/questions/1185151 | ||||
var fontSize = this.element.css('font-size'); | ||||
var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5); | ||||
return (this.element.height() > lines * lineHeight); | ||||
}; | ||||
OutputArea.prototype.bind_events = function () { | ||||
var that = this; | ||||
this.prompt_overlay.dblclick(function () { that.toggle_output(); }); | ||||
this.prompt_overlay.click(function () { that.toggle_scroll(); }); | ||||
this.element.resize(function () { | ||||
MinRK
|
r7733 | // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled | ||
Brian E. Granger
|
r9227 | if ( IPython.utils.browser[0] === "Firefox" ) { | ||
MinRK
|
r7733 | return; | ||
} | ||||
MinRK
|
r7362 | // maybe scroll output, | ||
// if it's grown large enough and hasn't already been scrolled. | ||||
Matthias BUSSONNIER
|
r10776 | if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) { | ||
MinRK
|
r7362 | that.scroll_area(); | ||
} | ||||
}); | ||||
this.collapse_button.click(function () { | ||||
that.expand(); | ||||
}); | ||||
}; | ||||
Brian Granger
|
r7177 | OutputArea.prototype.collapse = function () { | ||
if (!this.collapsed) { | ||||
this.element.hide(); | ||||
MinRK
|
r7362 | this.prompt_overlay.hide(); | ||
if (this.element.html()){ | ||||
this.collapse_button.show(); | ||||
} | ||||
Brian Granger
|
r7177 | this.collapsed = true; | ||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.expand = function () { | ||||
if (this.collapsed) { | ||||
MinRK
|
r7362 | this.collapse_button.hide(); | ||
Brian Granger
|
r7177 | this.element.show(); | ||
MinRK
|
r7362 | this.prompt_overlay.show(); | ||
Brian Granger
|
r7177 | this.collapsed = false; | ||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.toggle_output = function () { | ||||
if (this.collapsed) { | ||||
this.expand(); | ||||
} else { | ||||
this.collapse(); | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
MinRK
|
r7362 | OutputArea.prototype.scroll_area = function () { | ||
this.element.addClass('output_scroll'); | ||||
MinRK
|
r7423 | this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide'); | ||
MinRK
|
r7362 | this.scrolled = true; | ||
}; | ||||
OutputArea.prototype.unscroll_area = function () { | ||||
this.element.removeClass('output_scroll'); | ||||
MinRK
|
r7423 | this.prompt_overlay.attr('title', 'click to scroll output; double click to hide'); | ||
MinRK
|
r7362 | this.scrolled = false; | ||
}; | ||||
Matthias BUSSONNIER
|
r10776 | /** | ||
* Threshold to trigger autoscroll when the OutputArea is resized, | ||||
* typically when new outputs are added. | ||||
* | ||||
Matthias BUSSONNIER
|
r10782 | * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold, | ||
Matthias BUSSONNIER
|
r10778 | * unless it is < 0, in which case autoscroll will never be triggered | ||
Matthias BUSSONNIER
|
r10776 | * | ||
* @property auto_scroll_threshold | ||||
* @type Number | ||||
Matthias Bussonnier
|
r10812 | * @default 100 | ||
Matthias BUSSONNIER
|
r10776 | * | ||
**/ | ||||
Matthias Bussonnier
|
r10812 | OutputArea.auto_scroll_threshold = 100; | ||
Matthias BUSSONNIER
|
r10776 | |||
/** | ||||
Matthias BUSSONNIER
|
r10778 | * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas | ||
* shorter than this are never scrolled. | ||||
Matthias BUSSONNIER
|
r10776 | * | ||
Matthias BUSSONNIER
|
r10782 | * @property minimum_scroll_threshold | ||
Matthias BUSSONNIER
|
r10776 | * @type Number | ||
* @default 20 | ||||
* | ||||
**/ | ||||
Matthias BUSSONNIER
|
r10782 | OutputArea.minimum_scroll_threshold = 20; | ||
Matthias BUSSONNIER
|
r10776 | |||
/** | ||||
* | ||||
Matthias BUSSONNIER
|
r10778 | * Scroll OutputArea if height supperior than a threshold (in lines). | ||
Matthias BUSSONNIER
|
r10776 | * | ||
Matthias BUSSONNIER
|
r10778 | * Threshold is a maximum number of lines. If unspecified, defaults to | ||
Matthias BUSSONNIER
|
r10782 | * OutputArea.minimum_scroll_threshold. | ||
Matthias BUSSONNIER
|
r10778 | * | ||
Matthias BUSSONNIER
|
r10793 | * Negative threshold will prevent the OutputArea from ever scrolling. | ||
Matthias BUSSONNIER
|
r10776 | * | ||
* @method scroll_if_long | ||||
Matthias BUSSONNIER
|
r10793 | * | ||
* @param [lines=20]{Number} Default to 20 if not set, | ||||
* behavior undefined for value of `0`. | ||||
Matthias BUSSONNIER
|
r10776 | * | ||
**/ | ||||
MinRK
|
r7362 | OutputArea.prototype.scroll_if_long = function (lines) { | ||
Matthias BUSSONNIER
|
r10782 | var n = lines | OutputArea.minimum_scroll_threshold; | ||
Matthias BUSSONNIER
|
r10776 | if(n <= 0){ | ||
return | ||||
} | ||||
if (this._should_scroll(n)) { | ||||
MinRK
|
r7362 | // only allow scrolling long-enough output | ||
this.scroll_area(); | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
MinRK
|
r7362 | }; | ||
OutputArea.prototype.toggle_scroll = function () { | ||||
if (this.scrolled) { | ||||
this.unscroll_area(); | ||||
} else { | ||||
// only allow scrolling long-enough output | ||||
Matthias BUSSONNIER
|
r10776 | this.scroll_if_long(); | ||
Matthias BUSSONNIER
|
r9555 | } | ||
MinRK
|
r7362 | }; | ||
Brian Granger
|
r7177 | // typeset with MathJax if MathJax is available | ||
OutputArea.prototype.typeset = function () { | ||||
if (window.MathJax){ | ||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub]); | ||||
} | ||||
}; | ||||
MinRK
|
r13207 | OutputArea.prototype.handle_output = function (msg) { | ||
Brian Granger
|
r7177 | var json = {}; | ||
MinRK
|
r13207 | var msg_type = json.output_type = msg.header.msg_type; | ||
var content = msg.content; | ||||
Brian Granger
|
r7177 | if (msg_type === "stream") { | ||
json.text = content.data; | ||||
json.stream = content.name; | ||||
} else if (msg_type === "display_data") { | ||||
Paul Ivanov
|
r14131 | json = content.data; | ||
json.output_type = msg_type; | ||||
json.metadata = content.metadata; | ||||
Brian Granger
|
r7177 | } else if (msg_type === "pyout") { | ||
Paul Ivanov
|
r14131 | json = content.data; | ||
json.output_type = msg_type; | ||||
json.metadata = content.metadata; | ||||
Brian Granger
|
r7177 | json.prompt_number = content.execution_count; | ||
} else if (msg_type === "pyerr") { | ||||
json.ename = content.ename; | ||||
json.evalue = content.evalue; | ||||
json.traceback = content.traceback; | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
Paul Ivanov
|
r14134 | this.append_output(json); | ||
Brian Granger
|
r7177 | }; | ||
MinRK
|
r14115 | OutputArea.mime_map = { | ||
"text/plain" : "text", | ||||
"text/html" : "html", | ||||
"image/svg+xml" : "svg", | ||||
"image/png" : "png", | ||||
"image/jpeg" : "jpeg", | ||||
"text/latex" : "latex", | ||||
"application/json" : "json", | ||||
"application/javascript" : "javascript", | ||||
}; | ||||
Paul Ivanov
|
r14118 | |||
OutputArea.mime_map_r = { | ||||
"text" : "text/plain", | ||||
"html" : "text/html", | ||||
"svg" : "image/svg+xml", | ||||
"png" : "image/png", | ||||
"jpeg" : "image/jpeg", | ||||
"latex" : "text/latex", | ||||
"json" : "application/json", | ||||
"javascript" : "application/javascript", | ||||
}; | ||||
MinRK
|
r14157 | |||
Paul Ivanov
|
r14124 | OutputArea.prototype.rename_keys = function (data, key_map) { | ||
Paul Ivanov
|
r14127 | var remapped = {}; | ||
MinRK
|
r14115 | for (var key in data) { | ||
Paul Ivanov
|
r14124 | var new_key = key_map[key] || key; | ||
MinRK
|
r14157 | remapped[new_key] = data[key]; | ||
Paul Ivanov
|
r14112 | } | ||
Paul Ivanov
|
r14127 | return remapped; | ||
Brian Granger
|
r7177 | }; | ||
MinRK
|
r14158 | |||
OutputArea.output_types = [ | ||||
'application/javascript', | ||||
'text/html', | ||||
'text/latex', | ||||
'image/svg+xml', | ||||
'image/png', | ||||
'image/jpeg', | ||||
'text/plain' | ||||
]; | ||||
OutputArea.prototype.validate_output = function (json) { | ||||
// scrub invalid outputs | ||||
// TODO: right now everything is a string, but JSON really shouldn't be. | ||||
// nbformat 4 will fix that. | ||||
$.map(OutputArea.output_types, function(key){ | ||||
if (json[key] !== undefined && typeof json[key] !== 'string') { | ||||
console.log("Invalid type for " + key, json[key]); | ||||
delete json[key]; | ||||
} | ||||
}); | ||||
return json; | ||||
}; | ||||
Paul Ivanov
|
r14134 | OutputArea.prototype.append_output = function (json) { | ||
Brian Granger
|
r7177 | this.expand(); | ||
Jonathan Frederic
|
r12592 | // Clear the output if clear is queued. | ||
Jonathan Frederic
|
r12817 | var needs_height_reset = false; | ||
Jonathan Frederic
|
r12592 | if (this.clear_queued) { | ||
this.clear_output(false); | ||||
Jonathan Frederic
|
r12817 | needs_height_reset = true; | ||
Jonathan Frederic
|
r12592 | } | ||
MinRK
|
r14158 | |||
// validate output data types | ||||
json = this.validate_output(json); | ||||
Jonathan Frederic
|
r12592 | |||
Brian Granger
|
r7177 | if (json.output_type === 'pyout') { | ||
Paul Ivanov
|
r14134 | this.append_pyout(json); | ||
Brian Granger
|
r7177 | } else if (json.output_type === 'pyerr') { | ||
this.append_pyerr(json); | ||||
} else if (json.output_type === 'display_data') { | ||||
Paul Ivanov
|
r14134 | this.append_display_data(json); | ||
Brian Granger
|
r7177 | } else if (json.output_type === 'stream') { | ||
this.append_stream(json); | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | this.outputs.push(json); | ||
Jonathan Frederic
|
r12817 | |||
// Only reset the height to automatic if the height is currently | ||||
// fixed (done by wait=True flag on clear_output). | ||||
if (needs_height_reset) { | ||||
Jonathan Frederic
|
r12878 | this.element.height(''); | ||
Jonathan Frederic
|
r12817 | } | ||
MinRK
|
r7362 | var that = this; | ||
setTimeout(function(){that.element.trigger('resize');}, 100); | ||||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.create_output_area = function () { | ||||
Matthias BUSSONNIER
|
r10216 | var oa = $("<div/>").addClass("output_area"); | ||
Brian Granger
|
r7177 | if (this.prompt_area) { | ||
oa.append($('<div/>').addClass('prompt')); | ||||
} | ||||
return oa; | ||||
}; | ||||
Pablo de Oliveira
|
r13412 | |||
Paul Ivanov
|
r14116 | function _get_metadata_key(metadata, key, mime) { | ||
var mime_md = metadata[mime]; | ||||
// mime-specific higher priority | ||||
if (mime_md && mime_md[key] !== undefined) { | ||||
return mime_md[key]; | ||||
} | ||||
// fallback on global | ||||
return metadata[key]; | ||||
} | ||||
OutputArea.prototype.create_output_subarea = function(md, classes, mime) { | ||||
Pablo de Oliveira
|
r13412 | var subarea = $('<div/>').addClass('output_subarea').addClass(classes); | ||
Paul Ivanov
|
r14116 | if (_get_metadata_key(md, 'isolated', mime)) { | ||
Pablo de Oliveira
|
r13412 | // Create an iframe to isolate the subarea from the rest of the | ||
// document | ||||
Pablo de Oliveira
|
r13418 | var iframe = $('<iframe/>').addClass('box-flex1'); | ||
Pablo de Oliveira
|
r13419 | iframe.css({'height':1, 'width':'100%', 'display':'block'}); | ||
Pablo de Oliveira
|
r13412 | iframe.attr('frameborder', 0); | ||
iframe.attr('scrolling', 'auto'); | ||||
// Once the iframe is loaded, the subarea is dynamically inserted | ||||
iframe.on('load', function() { | ||||
// Workaround needed by Firefox, to properly render svg inside | ||||
// iframes, see http://stackoverflow.com/questions/10177190/ | ||||
// svg-dynamically-added-to-iframe-does-not-render-correctly | ||||
this.contentDocument.open(); | ||||
// Insert the subarea into the iframe | ||||
// We must directly write the html. When using Jquery's append | ||||
// method, javascript is evaluated in the parent document and | ||||
// not in the iframe document. | ||||
this.contentDocument.write(subarea.html()); | ||||
this.contentDocument.close(); | ||||
var body = this.contentDocument.body; | ||||
Pablo de Oliveira
|
r13418 | // Adjust the iframe height automatically | ||
Pablo de Oliveira
|
r13412 | iframe.height(body.scrollHeight + 'px'); | ||
}); | ||||
// Elements should be appended to the inner subarea and not to the | ||||
// iframe | ||||
iframe.append = function(that) { | ||||
subarea.append(that); | ||||
}; | ||||
return iframe; | ||||
} else { | ||||
return subarea; | ||||
} | ||||
} | ||||
Brian E. Granger
|
r13792 | OutputArea.prototype._append_javascript_error = function (err, element) { | ||
MinRK
|
r12332 | // display a message when a javascript error occurs in display output | ||
var msg = "Javascript error adding output!" | ||||
Brian E. Granger
|
r13792 | if ( element === undefined ) return; | ||
element.append( | ||||
MinRK
|
r12332 | $('<div/>').html(msg + "<br/>" + | ||
err.toString() + | ||||
'<br/>See your browser Javascript console for more details.' | ||||
).addClass('js-error') | ||||
); | ||||
}; | ||||
OutputArea.prototype._safe_append = function (toinsert) { | ||||
// safely append an item to the document | ||||
// this is an object created by user code, | ||||
// and may have errors, which should not be raised | ||||
// under any circumstances. | ||||
try { | ||||
this.element.append(toinsert); | ||||
} catch(err) { | ||||
console.log(err); | ||||
Brian E. Granger
|
r13792 | // Create an actual output_area and output_subarea, which creates | ||
// the prompt area and the proper indentation. | ||||
var toinsert = this.create_output_area(); | ||||
var subarea = $('<div/>').addClass('output_subarea'); | ||||
toinsert.append(subarea); | ||||
this._append_javascript_error(err, subarea); | ||||
this.element.append(toinsert); | ||||
MinRK
|
r12332 | } | ||
}; | ||||
Brian Granger
|
r7177 | |||
Paul Ivanov
|
r14134 | OutputArea.prototype.append_pyout = function (json) { | ||
Brian Granger
|
r7177 | var n = json.prompt_number || ' '; | ||
var toinsert = this.create_output_area(); | ||||
if (this.prompt_area) { | ||||
toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:'); | ||||
} | ||||
Paul Ivanov
|
r14134 | this.append_mime_type(json, toinsert); | ||
MinRK
|
r12332 | this._safe_append(toinsert); | ||
Brian Granger
|
r7177 | // If we just output latex, typeset it. | ||
Paul Ivanov
|
r14148 | if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) { | ||
Brian Granger
|
r7177 | this.typeset(); | ||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.append_pyerr = function (json) { | ||||
var tb = json.traceback; | ||||
if (tb !== undefined && tb.length > 0) { | ||||
var s = ''; | ||||
var len = tb.length; | ||||
for (var i=0; i<len; i++) { | ||||
s = s + tb[i] + '\n'; | ||||
} | ||||
s = s + '\n'; | ||||
var toinsert = this.create_output_area(); | ||||
MinRK
|
r10445 | this.append_text(s, {}, toinsert); | ||
MinRK
|
r12332 | this._safe_append(toinsert); | ||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.append_stream = function (json) { | ||||
// temporary fix: if stream undefined (json file written prior to this patch), | ||||
// default to most likely stdout: | ||||
if (json.stream == undefined){ | ||||
json.stream = 'stdout'; | ||||
} | ||||
Michael Droettboom
|
r7350 | var text = json.text; | ||
Brian Granger
|
r7177 | var subclass = "output_"+json.stream; | ||
if (this.outputs.length > 0){ | ||||
// have at least one output to consider | ||||
var last = this.outputs[this.outputs.length-1]; | ||||
if (last.output_type == 'stream' && json.stream == last.stream){ | ||||
// latest output was in the same stream, | ||||
// so append directly into its pre tag | ||||
// escape ANSI & HTML specials: | ||||
Michael Droettboom
|
r7350 | var pre = this.element.find('div.'+subclass).last().find('pre'); | ||
var html = utils.fixCarriageReturn( | ||||
Michael Droettboom
|
r7339 | pre.html() + utils.fixConsole(text)); | ||
pre.html(html); | ||||
Brian Granger
|
r7177 | return; | ||
} | ||||
} | ||||
Michael Droettboom
|
r7339 | |||
if (!text.replace("\r", "")) { | ||||
// text is nothing (empty string, \r, etc.) | ||||
// so don't append any elements, which might add undesirable space | ||||
return; | ||||
} | ||||
Brian Granger
|
r7177 | // If we got here, attach a new div | ||
var toinsert = this.create_output_area(); | ||||
MinRK
|
r10445 | this.append_text(text, {}, toinsert, "output_stream "+subclass); | ||
MinRK
|
r12332 | this._safe_append(toinsert); | ||
Brian Granger
|
r7177 | }; | ||
Paul Ivanov
|
r14134 | OutputArea.prototype.append_display_data = function (json) { | ||
Brian Granger
|
r7177 | var toinsert = this.create_output_area(); | ||
Paul Ivanov
|
r14134 | if (this.append_mime_type(json, toinsert)) { | ||
Jonathan Frederic
|
r13454 | this._safe_append(toinsert); | ||
// If we just output latex, typeset it. | ||||
Paul Ivanov
|
r14148 | if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) { | ||
Jonathan Frederic
|
r13454 | this.typeset(); | ||
} | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | }; | ||
Paul Ivanov
|
r14118 | OutputArea.display_order = [ | ||
'application/javascript', | ||||
'text/html', | ||||
'text/latex', | ||||
'image/svg+xml', | ||||
'image/png', | ||||
'image/jpeg', | ||||
'text/plain' | ||||
]; | ||||
Brian Granger
|
r7177 | |||
Paul Ivanov
|
r14134 | OutputArea.prototype.append_mime_type = function (json, element) { | ||
Paul Ivanov
|
r14116 | |||
Paul Ivanov
|
r14135 | for (var type_i in OutputArea.display_order) { | ||
Matthias BUSSONNIER
|
r9540 | var type = OutputArea.display_order[type_i]; | ||
Paul Ivanov
|
r14135 | var append = OutputArea.append_map[type]; | ||
if ((json[type] !== undefined) && append) { | ||||
Paul Ivanov
|
r14140 | var md = json.metadata || {}; | ||
Paul Ivanov
|
r14135 | append.apply(this, [json[type], md, element]); | ||
return true; | ||||
Matthias BUSSONNIER
|
r9540 | } | ||
} | ||||
Jonathan Frederic
|
r13454 | return false; | ||
Brian Granger
|
r7177 | }; | ||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_html = function (html, md, element) { | ||
Paul Ivanov
|
r14140 | var type = 'text/html'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_html rendered_html", type); | ||
Brian E. Granger
|
r14035 | IPython.keyboard_manager.register_events(toinsert); | ||
Brian Granger
|
r7177 | toinsert.append(html); | ||
element.append(toinsert); | ||||
}; | ||||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_javascript = function (js, md, container) { | ||
Brian Granger
|
r7177 | // We just eval the JS code, element appears in the local scope. | ||
Paul Ivanov
|
r14140 | var type = 'application/javascript'; | ||
Paul Ivanov
|
r14116 | var element = this.create_output_subarea(md, "output_javascript", type); | ||
Brian E. Granger
|
r14035 | IPython.keyboard_manager.register_events(element); | ||
Brian Granger
|
r7177 | container.append(element); | ||
Matthias BUSSONNIER
|
r8071 | try { | ||
eval(js); | ||||
} catch(err) { | ||||
Brian E. Granger
|
r13792 | console.log(err); | ||
this._append_javascript_error(err, element); | ||||
Matthias BUSSONNIER
|
r8071 | } | ||
Matthias BUSSONNIER
|
r9555 | }; | ||
Brian Granger
|
r7177 | |||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_text = function (data, md, element, extra_class) { | ||
Paul Ivanov
|
r14140 | var type = 'text/plain'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_text", type); | ||
Brian Granger
|
r7177 | // escape ANSI & HTML specials in plaintext: | ||
data = utils.fixConsole(data); | ||||
Michael Droettboom
|
r7339 | data = utils.fixCarriageReturn(data); | ||
Erik M. Bray
|
r8528 | data = utils.autoLinkUrls(data); | ||
Brian Granger
|
r7177 | if (extra_class){ | ||
toinsert.addClass(extra_class); | ||||
} | ||||
toinsert.append($("<pre/>").html(data)); | ||||
element.append(toinsert); | ||||
}; | ||||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_svg = function (svg, md, element) { | ||
Paul Ivanov
|
r14140 | var type = 'image/svg+xml'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_svg", type); | ||
Brian Granger
|
r7355 | toinsert.append(svg); | ||
element.append(toinsert); | ||||
Brian Granger
|
r7177 | }; | ||
MinRK
|
r7601 | OutputArea.prototype._dblclick_to_reset_size = function (img) { | ||
// schedule wrapping image in resizable after a delay, | ||||
// so we don't end up calling resize on a zero-size object | ||||
var that = this; | ||||
MinRK
|
r7599 | setTimeout(function () { | ||
MinRK
|
r7601 | var h0 = img.height(); | ||
var w0 = img.width(); | ||||
if (!(h0 && w0)) { | ||||
// zero size, schedule another timeout | ||||
that._dblclick_to_reset_size(img); | ||||
Matthias BUSSONNIER
|
r9555 | return; | ||
MinRK
|
r7601 | } | ||
MinRK
|
r7599 | img.resizable({ | ||
aspectRatio: true, | ||||
MinRK
|
r7601 | autoHide: true | ||
}); | ||||
img.dblclick(function () { | ||||
// resize wrapper & image together for some reason: | ||||
img.parent().height(h0); | ||||
img.height(h0); | ||||
img.parent().width(w0); | ||||
img.width(w0); | ||||
MinRK
|
r7599 | }); | ||
}, 250); | ||||
Matthias BUSSONNIER
|
r9555 | }; | ||
MinRK
|
r7601 | |||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_png = function (png, md, element) { | ||
Paul Ivanov
|
r14140 | var type = 'image/png'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_png", type); | ||
Paul Ivanov
|
r14119 | var img = $("<img/>"); | ||
Paul Ivanov
|
r14121 | img[0].setAttribute('src','data:image/png;base64,'+png); | ||
MinRK
|
r10445 | if (md['height']) { | ||
Matthias BUSSONNIER
|
r13573 | img[0].setAttribute('height', md['height']); | ||
MinRK
|
r10445 | } | ||
if (md['width']) { | ||||
Matthias BUSSONNIER
|
r13573 | img[0].setAttribute('width', md['width']); | ||
MinRK
|
r10445 | } | ||
MinRK
|
r7601 | this._dblclick_to_reset_size(img); | ||
Brian Granger
|
r7352 | toinsert.append(img); | ||
Brian Granger
|
r7177 | element.append(toinsert); | ||
}; | ||||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_jpeg = function (jpeg, md, element) { | ||
Paul Ivanov
|
r14140 | var type = 'image/jpeg'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_jpeg", type); | ||
Brian Granger
|
r7352 | var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg); | ||
MinRK
|
r10445 | if (md['height']) { | ||
img.attr('height', md['height']); | ||||
} | ||||
if (md['width']) { | ||||
img.attr('width', md['width']); | ||||
} | ||||
MinRK
|
r7601 | this._dblclick_to_reset_size(img); | ||
Brian Granger
|
r7352 | toinsert.append(img); | ||
Brian Granger
|
r7177 | element.append(toinsert); | ||
}; | ||||
Paul Ivanov
|
r14133 | OutputArea.prototype.append_latex = function (latex, md, element) { | ||
Brian Granger
|
r7177 | // This method cannot do the typesetting because the latex first has to | ||
// be on the page. | ||||
Paul Ivanov
|
r14140 | var type = 'text/latex'; | ||
Paul Ivanov
|
r14116 | var toinsert = this.create_output_subarea(md, "output_latex", type); | ||
Brian Granger
|
r7177 | toinsert.append(latex); | ||
element.append(toinsert); | ||||
}; | ||||
Matthias BUSSONNIER
|
r10776 | |||
Paul Ivanov
|
r14132 | OutputArea.append_map = { | ||
"text/plain" : OutputArea.prototype.append_text, | ||||
"text/html" : OutputArea.prototype.append_html, | ||||
"image/svg+xml" : OutputArea.prototype.append_svg, | ||||
"image/png" : OutputArea.prototype.append_png, | ||||
"image/jpeg" : OutputArea.prototype.append_jpeg, | ||||
"text/latex" : OutputArea.prototype.append_latex, | ||||
"application/json" : OutputArea.prototype.append_json, | ||||
"application/javascript" : OutputArea.prototype.append_javascript, | ||||
}; | ||||
MinRK
|
r13207 | OutputArea.prototype.append_raw_input = function (msg) { | ||
MinRK
|
r10370 | var that = this; | ||
MinRK
|
r10368 | this.expand(); | ||
MinRK
|
r13207 | var content = msg.content; | ||
MinRK
|
r10368 | var area = this.create_output_area(); | ||
MinRK
|
r11612 | |||
// disable any other raw_inputs, if they are left around | ||||
$("div.output_subarea.raw_input").remove(); | ||||
MinRK
|
r10368 | area.append( | ||
$("<div/>") | ||||
.addClass("box-flex1 output_subarea raw_input") | ||||
.append( | ||||
MinRK
|
r10370 | $("<span/>") | ||
.addClass("input_prompt") | ||||
.text(content.prompt) | ||||
) | ||||
.append( | ||||
$("<input/>") | ||||
.addClass("raw_input") | ||||
.attr('type', 'text') | ||||
MinRK
|
r10934 | .attr("size", 47) | ||
MinRK
|
r10370 | .keydown(function (event, ui) { | ||
// make sure we submit on enter, | ||||
// and don't re-execute the *cell* on shift-enter | ||||
if (event.which === utils.keycodes.ENTER) { | ||||
that._submit_raw_input(); | ||||
return false; | ||||
} | ||||
}) | ||||
MinRK
|
r10368 | ) | ||
MinRK
|
r10370 | ); | ||
Brian E. Granger
|
r14046 | |||
MinRK
|
r10368 | this.element.append(area); | ||
Brian E. Granger
|
r14046 | var raw_input = area.find('input.raw_input'); | ||
// Register events that enable/disable the keyboard manager while raw | ||||
// input is focused. | ||||
IPython.keyboard_manager.register_events(raw_input); | ||||
// Note, the following line used to read raw_input.focus().focus(). | ||||
// This seemed to be needed otherwise only the cell would be focused. | ||||
// But with the modal UI, this seems to work fine with one call to focus(). | ||||
raw_input.focus(); | ||||
MinRK
|
r10368 | } | ||
Brian E. Granger
|
r14046 | |||
MinRK
|
r10368 | OutputArea.prototype._submit_raw_input = function (evt) { | ||
var container = this.element.find("div.raw_input"); | ||||
var theprompt = container.find("span.input_prompt"); | ||||
var theinput = container.find("input.raw_input"); | ||||
MinRK
|
r10934 | var value = theinput.val(); | ||
MinRK
|
r10368 | var content = { | ||
output_type : 'stream', | ||||
name : 'stdout', | ||||
text : theprompt.text() + value + '\n' | ||||
} | ||||
// remove form container | ||||
container.parent().remove(); | ||||
// replace with plaintext version in stdout | ||||
this.append_output(content, false); | ||||
$([IPython.events]).trigger('send_input_reply.Kernel', value); | ||||
} | ||||
Brian Granger
|
r7177 | |||
MinRK
|
r13207 | OutputArea.prototype.handle_clear_output = function (msg) { | ||
this.clear_output(msg.content.wait); | ||||
Matthias BUSSONNIER
|
r9555 | }; | ||
Brian Granger
|
r7177 | |||
Jonathan Frederic
|
r12592 | OutputArea.prototype.clear_output = function(wait) { | ||
if (wait) { | ||||
Jonathan Frederic
|
r12582 | |||
Jonathan Frederic
|
r12592 | // If a clear is queued, clear before adding another to the queue. | ||
if (this.clear_queued) { | ||||
this.clear_output(false); | ||||
}; | ||||
this.clear_queued = true; | ||||
} else { | ||||
Jonathan Frederic
|
r12606 | // Fix the output div's height if the clear_output is waiting for | ||
// new output (it is being used in an animation). | ||||
if (this.clear_queued) { | ||||
var height = this.element.height(); | ||||
this.element.height(height); | ||||
this.clear_queued = false; | ||||
} | ||||
Jonathan Frederic
|
r12592 | // clear all, no need for logic | ||
this.element.html(""); | ||||
this.outputs = []; | ||||
this.unscroll_area(); | ||||
return; | ||||
}; | ||||
Brian Granger
|
r7177 | }; | ||
// JSON serialization | ||||
OutputArea.prototype.fromJSON = function (outputs) { | ||||
var len = outputs.length; | ||||
Paul Ivanov
|
r14127 | var data; | ||
Paul Ivanov
|
r14134 | |||
// We don't want to display javascript on load, so remove it from the | ||||
// display order for the duration of this function call, but be sure to | ||||
// put it back in there so incoming messages that contain javascript | ||||
// representations get displayed | ||||
var js_index = OutputArea.display_order.indexOf('application/javascript'); | ||||
OutputArea.display_order.splice(js_index, 1); | ||||
Brian Granger
|
r7177 | for (var i=0; i<len; i++) { | ||
Paul Ivanov
|
r14130 | data = outputs[i]; | ||
var msg_type = data.output_type; | ||||
Paul Ivanov
|
r14120 | if (msg_type === "display_data" || msg_type === "pyout") { | ||
Paul Ivanov
|
r14123 | // convert short keys to mime keys | ||
Paul Ivanov
|
r14136 | // TODO: remove mapping of short keys when we update to nbformat 4 | ||
Paul Ivanov
|
r14130 | data = this.rename_keys(data, OutputArea.mime_map_r); | ||
data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r); | ||||
Paul Ivanov
|
r14120 | } | ||
Paul Ivanov
|
r14134 | this.append_output(data); | ||
Matthias BUSSONNIER
|
r9555 | } | ||
Paul Ivanov
|
r14134 | |||
// reinsert javascript into display order, see note above | ||||
OutputArea.display_order.splice(js_index, 0, 'application/javascript'); | ||||
Brian Granger
|
r7177 | }; | ||
OutputArea.prototype.toJSON = function () { | ||||
var outputs = []; | ||||
var len = this.outputs.length; | ||||
Paul Ivanov
|
r14127 | var data; | ||
Brian Granger
|
r7177 | for (var i=0; i<len; i++) { | ||
Paul Ivanov
|
r14130 | data = this.outputs[i]; | ||
var msg_type = data.output_type; | ||||
Paul Ivanov
|
r14123 | if (msg_type === "display_data" || msg_type === "pyout") { | ||
// convert mime keys to short keys | ||||
Paul Ivanov
|
r14130 | data = this.rename_keys(data, OutputArea.mime_map); | ||
Paul Ivanov
|
r14136 | data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map); | ||
Paul Ivanov
|
r14123 | } | ||
outputs[i] = data; | ||||
Matthias BUSSONNIER
|
r9555 | } | ||
Brian Granger
|
r7177 | return outputs; | ||
}; | ||||
IPython.OutputArea = OutputArea; | ||||
return IPython; | ||||
}(IPython)); | ||||