output.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import * as outputBase from '@jupyter-widgets/output';
  4. import { Panel } from '@lumino/widgets';
  5. import { OutputAreaModel, OutputArea } from '@jupyterlab/outputarea';
  6. import $ from 'jquery';
  7. export const OUTPUT_WIDGET_VERSION = outputBase.OUTPUT_WIDGET_VERSION;
  8. export class OutputModel extends outputBase.OutputModel {
  9. defaults() {
  10. return Object.assign(Object.assign({}, super.defaults()), { msg_id: '', outputs: [] });
  11. }
  12. initialize(attributes, options) {
  13. super.initialize(attributes, options);
  14. // The output area model is trusted since widgets are only rendered in trusted contexts.
  15. this._outputs = new OutputAreaModel({ trusted: true });
  16. this._msgHook = (msg) => {
  17. this.add(msg);
  18. return false;
  19. };
  20. this.widget_manager.context.sessionContext.kernelChanged.connect((sender, args) => {
  21. this._handleKernelChanged(args);
  22. });
  23. this.listenTo(this, 'change:msg_id', this.reset_msg_id);
  24. this.listenTo(this, 'change:outputs', this.setOutputs);
  25. this.setOutputs();
  26. }
  27. /**
  28. * Register a new kernel
  29. */
  30. _handleKernelChanged({ oldValue }) {
  31. const msgId = this.get('msg_id');
  32. if (msgId && oldValue) {
  33. oldValue.removeMessageHook(msgId, this._msgHook);
  34. this.set('msg_id', null);
  35. }
  36. }
  37. /**
  38. * Reset the message id.
  39. */
  40. reset_msg_id() {
  41. const kernel = this.widget_manager.context.sessionContext.session.kernel;
  42. const msgId = this.get('msg_id');
  43. const oldMsgId = this.previous('msg_id');
  44. // Clear any old handler.
  45. if (oldMsgId && kernel) {
  46. kernel.removeMessageHook(oldMsgId, this._msgHook);
  47. }
  48. // Register any new handler.
  49. if (msgId && kernel) {
  50. kernel.registerMessageHook(msgId, this._msgHook);
  51. }
  52. }
  53. add(msg) {
  54. let msgType = msg.header.msg_type;
  55. switch (msgType) {
  56. case 'execute_result':
  57. case 'display_data':
  58. case 'stream':
  59. case 'error':
  60. let model = msg.content;
  61. model.output_type = msgType;
  62. this._outputs.add(model);
  63. break;
  64. case 'clear_output':
  65. this.clear_output(msg.content.wait);
  66. break;
  67. default:
  68. break;
  69. }
  70. this.set('outputs', this._outputs.toJSON(), { newMessage: true });
  71. this.save_changes();
  72. }
  73. clear_output(wait = false) {
  74. this._outputs.clear(wait);
  75. }
  76. get outputs() {
  77. return this._outputs;
  78. }
  79. setOutputs(model, value, options) {
  80. if (!(options && options.newMessage)) {
  81. // fromJSON does not clear the existing output
  82. this.clear_output();
  83. // fromJSON does not copy the message, so we make a deep copy
  84. this._outputs.fromJSON(JSON.parse(JSON.stringify(this.get('outputs'))));
  85. }
  86. }
  87. }
  88. export class JupyterPhosphorPanelWidget extends Panel {
  89. constructor(options) {
  90. let view = options.view;
  91. delete options.view;
  92. super(options);
  93. this._view = view;
  94. }
  95. /**
  96. * Process the phosphor message.
  97. *
  98. * Any custom phosphor widget used inside a Jupyter widget should override
  99. * the processMessage function like this.
  100. */
  101. processMessage(msg) {
  102. super.processMessage(msg);
  103. this._view.processPhosphorMessage(msg);
  104. }
  105. /**
  106. * Dispose the widget.
  107. *
  108. * This causes the view to be destroyed as well with 'remove'
  109. */
  110. dispose() {
  111. if (this.isDisposed) {
  112. return;
  113. }
  114. super.dispose();
  115. if (this._view) {
  116. this._view.remove();
  117. }
  118. this._view = null;
  119. }
  120. }
  121. export class OutputView extends outputBase.OutputView {
  122. _createElement(tagName) {
  123. this.pWidget = new JupyterPhosphorPanelWidget({ view: this });
  124. return this.pWidget.node;
  125. }
  126. _setElement(el) {
  127. if (this.el || el !== this.pWidget.node) {
  128. // Boxes don't allow setting the element beyond the initial creation.
  129. throw new Error('Cannot reset the DOM element.');
  130. }
  131. this.el = this.pWidget.node;
  132. this.$el = $(this.pWidget.node);
  133. }
  134. /**
  135. * Called when view is rendered.
  136. */
  137. render() {
  138. super.render();
  139. this._outputView = new OutputArea({
  140. rendermime: this.model.widget_manager.rendermime,
  141. contentFactory: OutputArea.defaultContentFactory,
  142. model: this.model.outputs
  143. });
  144. // TODO: why is this a readonly property now?
  145. // this._outputView.model = this.model.outputs;
  146. // TODO: why is this on the model now?
  147. // this._outputView.trusted = true;
  148. this.pWidget.insertWidget(0, this._outputView);
  149. this.pWidget.addClass('jupyter-widgets');
  150. this.pWidget.addClass('widget-output');
  151. this.update(); // Set defaults.
  152. }
  153. remove() {
  154. this._outputView.dispose();
  155. return super.remove();
  156. }
  157. }