widget.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. IKernel
  5. } from 'jupyter-js-services';
  6. import {
  7. Message
  8. } from 'phosphor/lib/core/messaging';
  9. import {
  10. MimeData
  11. } from 'phosphor/lib/core/mimedata';
  12. import {
  13. defineSignal, ISignal
  14. } from 'phosphor/lib/core/signaling';
  15. import {
  16. Drag
  17. } from 'phosphor/lib/dom/dragdrop';
  18. import {
  19. PanelLayout
  20. } from 'phosphor/lib/ui/panel';
  21. import {
  22. Widget
  23. } from 'phosphor/lib/ui/widget';
  24. import {
  25. IListChangedArgs
  26. } from '../../common/observablelist';
  27. import {
  28. RenderMime
  29. } from '../../rendermime';
  30. import {
  31. nbformat
  32. } from '../notebook/nbformat';
  33. import {
  34. OutputAreaModel
  35. } from './model';
  36. /**
  37. * The threshold in pixels to start a drag event.
  38. */
  39. const DRAG_THRESHOLD = 5;
  40. /**
  41. * The factory MIME type supported by phosphor dock panels.
  42. */
  43. const FACTORY_MIME = 'application/vnd.phosphor.widget-factory';
  44. /**
  45. * The class name added to an output area widget.
  46. */
  47. const OUTPUT_AREA_CLASS = 'jp-OutputArea';
  48. /**
  49. * The class name added to a "mirrored" output area widget created by a drag.
  50. */
  51. const MIRRORED_OUTPUT_AREA_CLASS = 'jp-MirroredOutputArea';
  52. /**
  53. * The class name added to an output widget.
  54. */
  55. const OUTPUT_CLASS = 'jp-Output';
  56. /**
  57. * The class name added to an execute result.
  58. */
  59. const EXECUTE_CLASS = 'jp-Output-executeResult';
  60. /**
  61. * The class name added to display data.
  62. */
  63. const DISPLAY_CLASS = 'jp-Output-displayData';
  64. /**
  65. * The class name added to stdout data.
  66. */
  67. const STDOUT_CLASS = 'jp-Output-stdout';
  68. /**
  69. * The class name added to stderr data.
  70. */
  71. const STDERR_CLASS = 'jp-Output-stderr';
  72. /**
  73. * The class name added to error data.
  74. */
  75. const ERROR_CLASS = 'jp-Output-error';
  76. /**
  77. * The class name added to stdin data.
  78. */
  79. const STDIN_CLASS = 'jp-Output-stdin';
  80. /**
  81. * The class name added to stdin data prompt nodes.
  82. */
  83. const STDIN_PROMPT_CLASS = 'jp-Output-stdinPrompt';
  84. /**
  85. * The class name added to stdin data input nodes.
  86. */
  87. const STDIN_INPUT_CLASS = 'jp-Output-stdinInput';
  88. /**
  89. * The class name added to stdin rendered text nodes.
  90. */
  91. const STDIN_RENDERED_CLASS = 'jp-Output-stdinRendered';
  92. /**
  93. * The class name added to fixed height output areas.
  94. */
  95. const FIXED_HEIGHT_CLASS = 'jp-mod-fixedHeight';
  96. /**
  97. * The class name added to collaped output areas.
  98. */
  99. const COLLAPSED_CLASS = 'jp-mod-collapsed';
  100. /**
  101. * The class name added to output area prompts.
  102. */
  103. const PROMPT_CLASS = 'jp-Output-prompt';
  104. /**
  105. * The class name added to output area results.
  106. */
  107. const RESULT_CLASS = 'jp-Output-result';
  108. /**
  109. * An output area widget.
  110. *
  111. * #### Notes
  112. * The widget model must be set separately and can be changed
  113. * at any time. Consumers of the widget must account for a
  114. * `null` model, and may want to listen to the `modelChanged`
  115. * signal.
  116. */
  117. export
  118. class OutputAreaWidget extends Widget {
  119. /**
  120. * Construct an output area widget.
  121. */
  122. constructor(options: OutputAreaWidget.IOptions) {
  123. super();
  124. this.addClass(OUTPUT_AREA_CLASS);
  125. this._rendermime = options.rendermime;
  126. this._renderer = options.renderer || OutputAreaWidget.defaultRenderer;
  127. this.layout = new PanelLayout();
  128. }
  129. /**
  130. * Create a mirrored output widget.
  131. */
  132. mirror(): OutputAreaWidget {
  133. let rendermime = this._rendermime;
  134. let renderer = this._renderer;
  135. let widget = new OutputAreaWidget({ rendermime, renderer });
  136. widget.model = this._model;
  137. widget.trusted = this._trusted;
  138. widget.title.label = 'Mirrored Output';
  139. widget.title.closable = true;
  140. widget.addClass(MIRRORED_OUTPUT_AREA_CLASS);
  141. return widget;
  142. }
  143. /**
  144. * A signal emitted when the widget's model changes.
  145. */
  146. modelChanged: ISignal<OutputAreaWidget, void>;
  147. /**
  148. * A signal emitted when the widget's model is disposed.
  149. */
  150. modelDisposed: ISignal<OutputAreaWidget, void>;
  151. /**
  152. * The model for the widget.
  153. */
  154. get model(): OutputAreaModel {
  155. return this._model;
  156. }
  157. set model(newValue: OutputAreaModel) {
  158. if (!newValue && !this._model || newValue === this._model) {
  159. return;
  160. }
  161. let oldValue = this._model;
  162. this._model = newValue;
  163. // Trigger private, protected, and public updates.
  164. this._onModelChanged(oldValue, newValue);
  165. this.onModelChanged(oldValue, newValue);
  166. this.modelChanged.emit(void 0);
  167. }
  168. /**
  169. * Get the rendermime instance used by the widget.
  170. *
  171. * #### Notes
  172. * This is a read-only property.
  173. */
  174. get rendermime(): RenderMime {
  175. return this._rendermime;
  176. }
  177. /**
  178. * Get the renderer used by the widget.
  179. *
  180. * #### Notes
  181. * This is a read-only property.
  182. */
  183. get renderer(): OutputAreaWidget.IRenderer {
  184. return this._renderer;
  185. }
  186. /**
  187. * The trusted state of the widget.
  188. */
  189. get trusted(): boolean {
  190. return this._trusted;
  191. }
  192. set trusted(value: boolean) {
  193. if (this._trusted === value) {
  194. return;
  195. }
  196. this._trusted = value;
  197. // Trigger a update of the child widgets.
  198. let layout = this.layout as PanelLayout;
  199. for (let i = 0; i < layout.widgets.length; i++) {
  200. this._updateChild(i);
  201. }
  202. }
  203. /**
  204. * The collapsed state of the widget.
  205. */
  206. get collapsed(): boolean {
  207. return this._collapsed;
  208. }
  209. set collapsed(value: boolean) {
  210. if (this._collapsed === value) {
  211. return;
  212. }
  213. this._collapsed = value;
  214. this.update();
  215. }
  216. /**
  217. * The fixed height state of the widget.
  218. */
  219. get fixedHeight(): boolean {
  220. return this._fixedHeight;
  221. }
  222. set fixedHeight(value: boolean) {
  223. if (this._fixedHeight === value) {
  224. return;
  225. }
  226. this._fixedHeight = value;
  227. this.update();
  228. }
  229. /**
  230. * Dispose of the resources held by the widget.
  231. */
  232. dispose() {
  233. // Do nothing if already disposed.
  234. if (this.isDisposed) {
  235. return;
  236. }
  237. this._model = null;
  238. this._rendermime = null;
  239. this._renderer = null;
  240. super.dispose();
  241. }
  242. /**
  243. * Get the child widget at the specified index.
  244. */
  245. childAt(index: number): OutputWidget {
  246. let layout = this.layout as PanelLayout;
  247. return layout.widgets.at(index) as OutputWidget;
  248. }
  249. /**
  250. * Get the number of child widgets.
  251. */
  252. childCount(): number {
  253. let layout = this.layout as PanelLayout;
  254. return layout.widgets.length;
  255. }
  256. /**
  257. * Handle `update_request` messages.
  258. */
  259. protected onUpdateRequest(msg: Message): void {
  260. if (this.collapsed) {
  261. this.addClass(COLLAPSED_CLASS);
  262. } else {
  263. this.removeClass(COLLAPSED_CLASS);
  264. }
  265. if (this.fixedHeight) {
  266. this.addClass(FIXED_HEIGHT_CLASS);
  267. } else {
  268. this.removeClass(FIXED_HEIGHT_CLASS);
  269. }
  270. }
  271. /**
  272. * Handle a new model.
  273. *
  274. * #### Notes
  275. * This method is called after the model change has been handled
  276. * internally and before the `modelChanged` signal is emitted.
  277. * The default implementation is a no-op.
  278. */
  279. protected onModelChanged(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  280. // no-op
  281. }
  282. /**
  283. * Handle a change to the model.
  284. */
  285. private _onModelChanged(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  286. let layout = this.layout as PanelLayout;
  287. if (oldValue) {
  288. oldValue.changed.disconnect(this._onModelStateChanged, this);
  289. oldValue.disposed.disconnect(this._onModelDisposed, this);
  290. }
  291. let start = newValue ? newValue.length : 0;
  292. // Clear unnecessary child widgets.
  293. for (let i = start; i < layout.widgets.length; i++) {
  294. this._removeChild(i);
  295. }
  296. if (!newValue) {
  297. return;
  298. }
  299. newValue.changed.connect(this._onModelStateChanged, this);
  300. newValue.disposed.connect(this._onModelDisposed, this);
  301. // Reuse existing child widgets.
  302. for (let i = 0; i < layout.widgets.length; i++) {
  303. this._updateChild(i);
  304. }
  305. // Add new widgets as necessary.
  306. for (let i = layout.widgets.length; i < newValue.length; i++) {
  307. this._addChild();
  308. }
  309. }
  310. /**
  311. * Handle a model disposal.
  312. */
  313. protected onModelDisposed(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  314. // no-op
  315. }
  316. private _onModelDisposed(): void {
  317. this.modelDisposed.emit(void 0);
  318. this.dispose();
  319. }
  320. /**
  321. * Add a child to the layout.
  322. */
  323. private _addChild(): void {
  324. let widget = this._renderer.createOutput({ rendermime: this.rendermime });
  325. let layout = this.layout as PanelLayout;
  326. layout.addWidget(widget);
  327. this._updateChild(layout.widgets.length - 1);
  328. }
  329. /**
  330. * Remove a child from the layout.
  331. */
  332. private _removeChild(index: number): void {
  333. let layout = this.layout as PanelLayout;
  334. layout.widgets.at(index).dispose();
  335. }
  336. /**
  337. * Update a child in the layout.
  338. */
  339. private _updateChild(index: number): void {
  340. let layout = this.layout as PanelLayout;
  341. let widget = layout.widgets.at(index) as OutputWidget;
  342. let output = this._model.get(index);
  343. widget.render(output, this._trusted);
  344. }
  345. /**
  346. * Follow changes on the model state.
  347. */
  348. private _onModelStateChanged(sender: OutputAreaModel, args: IListChangedArgs<nbformat.IOutput>) {
  349. switch (args.type) {
  350. case 'add':
  351. // Children are always added at the end.
  352. this._addChild();
  353. break;
  354. case 'replace':
  355. // Only "clear" is supported by the model.
  356. // When an output area is cleared and then quickly replaced with new
  357. // content (as happens with @interact in widgets, for example), the
  358. // quickly changing height can make the page jitter.
  359. // We introduce a small delay in the minimum height
  360. // to prevent this jitter.
  361. let rect = this.node.getBoundingClientRect();
  362. let oldHeight = this.node.style.minHeight;
  363. this.node.style.minHeight = `${rect.height}px`;
  364. setTimeout(() => {
  365. if (this.isDisposed) {
  366. return;
  367. }
  368. this.node.style.minHeight = oldHeight;
  369. }, 50);
  370. let oldValues = args.oldValue as nbformat.IOutput[];
  371. for (let i = args.oldIndex; i < oldValues.length; i++) {
  372. this._removeChild(args.oldIndex);
  373. }
  374. break;
  375. case 'set':
  376. this._updateChild(args.newIndex);
  377. break;
  378. default:
  379. break;
  380. }
  381. this.update();
  382. }
  383. private _trusted = false;
  384. private _fixedHeight = false;
  385. private _collapsed = false;
  386. private _model: OutputAreaModel = null;
  387. private _rendermime: RenderMime = null;
  388. private _renderer: OutputAreaWidget.IRenderer = null;
  389. }
  390. /**
  391. * A namespace for OutputAreaWidget statics.
  392. */
  393. export
  394. namespace OutputAreaWidget {
  395. /**
  396. * The options to pass to an `OutputAreaWidget`.
  397. */
  398. export
  399. interface IOptions {
  400. /**
  401. * The rendermime instance used by the widget.
  402. */
  403. rendermime: RenderMime;
  404. /**
  405. * The output widget renderer.
  406. *
  407. * Defaults to a shared `IRenderer` instance.
  408. */
  409. renderer?: IRenderer;
  410. }
  411. /**
  412. * An output widget renderer.
  413. */
  414. export
  415. interface IRenderer {
  416. /**
  417. * Create an output widget.
  418. *
  419. *
  420. * @returns A new widget for an output.
  421. */
  422. createOutput(options: OutputWidget.IOptions): Widget;
  423. }
  424. /**
  425. * The default implementation of `IRenderer`.
  426. */
  427. export
  428. class Renderer implements IRenderer {
  429. /**
  430. * Create an output widget.
  431. *
  432. *
  433. * @returns A new widget for an output.
  434. */
  435. createOutput(options: OutputWidget.IOptions): OutputWidget {
  436. return new OutputWidget(options);
  437. }
  438. }
  439. /**
  440. * The default `Renderer` instance.
  441. */
  442. export
  443. const defaultRenderer = new Renderer();
  444. }
  445. /**
  446. * The gutter on the left side of the OutputWidget
  447. */
  448. export
  449. class OutputGutter extends Widget {
  450. /**
  451. * Handle the DOM events for the output gutter widget.
  452. *
  453. * @param event - The DOM event sent to the widget.
  454. *
  455. * #### Notes
  456. * This method implements the DOM `EventListener` interface and is
  457. * called in response to events on the panel's DOM node. It should
  458. * not be called directly by user code.
  459. */
  460. handleEvent(event: Event): void {
  461. switch (event.type) {
  462. case 'mousedown':
  463. this._evtMousedown(event as MouseEvent);
  464. break;
  465. case 'mouseup':
  466. this._evtMouseup(event as MouseEvent);
  467. break;
  468. case 'mousemove':
  469. this._evtMousemove(event as MouseEvent);
  470. break;
  471. default:
  472. break;
  473. }
  474. }
  475. /**
  476. * A message handler invoked on an `'after-attach'` message.
  477. */
  478. protected onAfterAttach(msg: Message): void {
  479. super.onAfterAttach(msg);
  480. this.node.addEventListener('mousedown', this);
  481. }
  482. /**
  483. * A message handler invoked on a `'before-detach'` message.
  484. */
  485. protected onBeforeDetach(msg: Message): void {
  486. super.onBeforeDetach(msg);
  487. let node = this.node;
  488. node.removeEventListener('mousedown', this);
  489. }
  490. /**
  491. * Handle the `'mousedown'` event for the widget.
  492. */
  493. private _evtMousedown(event: MouseEvent): void {
  494. // Left mouse press for drag start.
  495. if (event.button === 0) {
  496. this._dragData = { pressX: event.clientX, pressY: event.clientY };
  497. document.addEventListener('mouseup', this, true);
  498. document.addEventListener('mousemove', this, true);
  499. }
  500. }
  501. /**
  502. * Handle the `'mouseup'` event for the widget.
  503. */
  504. private _evtMouseup(event: MouseEvent): void {
  505. if (event.button !== 0 || !this._drag) {
  506. document.removeEventListener('mousemove', this, true);
  507. document.removeEventListener('mouseup', this, true);
  508. return;
  509. }
  510. event.preventDefault();
  511. event.stopPropagation();
  512. }
  513. /**
  514. * Handle the `'mousemove'` event for the widget.
  515. */
  516. private _evtMousemove(event: MouseEvent): void {
  517. event.preventDefault();
  518. event.stopPropagation();
  519. // Bail if we are the one dragging.
  520. if (this._drag) {
  521. return;
  522. }
  523. // Check for a drag initialization.
  524. let data = this._dragData;
  525. let dx = Math.abs(event.clientX - data.pressX);
  526. let dy = Math.abs(event.clientY - data.pressY);
  527. if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) {
  528. return;
  529. }
  530. this._startDrag(event.clientX, event.clientY);
  531. }
  532. /**
  533. * Start a drag event.
  534. */
  535. private _startDrag(clientX: number, clientY: number): void {
  536. // Set up the drag event.
  537. this._drag = new Drag({
  538. mimeData: new MimeData(),
  539. supportedActions: 'copy',
  540. proposedAction: 'copy'
  541. });
  542. this._drag.mimeData.setData(FACTORY_MIME, () => {
  543. let outputArea = this.parent.parent as OutputAreaWidget;
  544. return outputArea.mirror();
  545. });
  546. // Remove mousemove and mouseup listeners and start the drag.
  547. document.removeEventListener('mousemove', this, true);
  548. document.removeEventListener('mouseup', this, true);
  549. this._drag.start(clientX, clientY).then(action => {
  550. this._drag = null;
  551. });
  552. }
  553. /**
  554. * Dispose of the resources held by the widget.
  555. */
  556. dispose() {
  557. // Do nothing if already disposed.
  558. if (this.isDisposed) {
  559. return;
  560. }
  561. this._dragData = null;
  562. this._drag = null;
  563. super.dispose();
  564. }
  565. private _drag: Drag = null;
  566. private _dragData: { pressX: number, pressY: number } = null;
  567. }
  568. /**
  569. * An output widget.
  570. */
  571. export
  572. class OutputWidget extends Widget {
  573. /**
  574. * Construct a new output widget.
  575. */
  576. constructor(options: OutputWidget.IOptions) {
  577. super();
  578. let layout = new PanelLayout();
  579. this.layout = layout;
  580. let prompt = new OutputGutter();
  581. this._placeholder = new Widget();
  582. this.addClass(OUTPUT_CLASS);
  583. prompt.addClass(PROMPT_CLASS);
  584. this._placeholder.addClass(RESULT_CLASS);
  585. layout.addWidget(prompt);
  586. layout.addWidget(this._placeholder);
  587. this._rendermime = options.rendermime;
  588. }
  589. /**
  590. * The prompt widget used by the output widget.
  591. *
  592. * #### Notes
  593. * This is a read-only property.
  594. */
  595. get prompt(): Widget {
  596. let layout = this.layout as PanelLayout;
  597. return layout.widgets.at(0);
  598. }
  599. /**
  600. * The rendered output used by the output widget.
  601. *
  602. * #### Notes
  603. * This is a read-only property.
  604. */
  605. get output(): Widget {
  606. let layout = this.layout as PanelLayout;
  607. return layout.widgets.at(1);
  608. }
  609. /**
  610. * Dispose of the resources held by the widget.
  611. */
  612. dispose(): void {
  613. this._rendermime = null;
  614. this._placeholder = null;
  615. super.dispose();
  616. }
  617. /**
  618. * Clear the widget contents.
  619. */
  620. clear(): void {
  621. this.setOutput(this._placeholder);
  622. this.prompt.node.textContent = '';
  623. }
  624. /**
  625. * Render an output.
  626. *
  627. * @param output - The kernel output message payload.
  628. *
  629. * @param trusted - Whether the output is trusted.
  630. */
  631. render(output: OutputAreaModel.Output, trusted=false): void {
  632. // Handle an input request.
  633. if (output.output_type === 'input_request') {
  634. let child = new InputWidget(output as OutputAreaModel.IInputRequest);
  635. this.setOutput(child);
  636. return;
  637. }
  638. // Extract the data from the output and sanitize if necessary.
  639. let rendermime = this._rendermime;
  640. let bundle = this.getBundle(output as nbformat.IOutput);
  641. let data = this.convertBundle(bundle);
  642. // Clear the content.
  643. this.clear();
  644. // Bail if no data to display.
  645. let msg = 'Did not find renderer for output mimebundle.';
  646. if (!data) {
  647. console.log(msg);
  648. return;
  649. }
  650. // Create the output result area.
  651. let child = rendermime.render(data, trusted);
  652. if (!child) {
  653. console.log(msg);
  654. console.log(data);
  655. return;
  656. }
  657. this.setOutput(child);
  658. // Add classes and output prompt as necessary.
  659. switch (output.output_type) {
  660. case 'execute_result':
  661. child.addClass(EXECUTE_CLASS);
  662. let count = (output as nbformat.IExecuteResult).execution_count;
  663. this.prompt.node.textContent = `Out[${count === null ? ' ' : count}]:`;
  664. break;
  665. case 'display_data':
  666. child.addClass(DISPLAY_CLASS);
  667. break;
  668. case 'stream':
  669. if ((output as nbformat.IStream).name === 'stdout') {
  670. child.addClass(STDOUT_CLASS);
  671. } else {
  672. child.addClass(STDERR_CLASS);
  673. }
  674. break;
  675. case 'error':
  676. child.addClass(ERROR_CLASS);
  677. break;
  678. default:
  679. console.error(`Unrecognized output type: ${output.output_type}`);
  680. data = {};
  681. }
  682. }
  683. /**
  684. * Set the widget output.
  685. */
  686. protected setOutput(value: Widget): void {
  687. let layout = this.layout as PanelLayout;
  688. let old = this.output;
  689. value = value || null;
  690. if (old === value) {
  691. return;
  692. }
  693. if (old) {
  694. if (old !== this._placeholder) {
  695. old.dispose();
  696. } else {
  697. old.parent = null;
  698. }
  699. }
  700. if (value) {
  701. layout.addWidget(value);
  702. value.addClass(RESULT_CLASS);
  703. } else {
  704. layout.addWidget(this._placeholder);
  705. }
  706. }
  707. /**
  708. * Get the mime bundle for an output.
  709. *
  710. * @params output - A kernel output message payload.
  711. *
  712. * @returns - A mime bundle for the payload.
  713. */
  714. protected getBundle(output: nbformat.IOutput): nbformat.MimeBundle {
  715. let bundle: nbformat.MimeBundle;
  716. switch (output.output_type) {
  717. case 'execute_result':
  718. bundle = (output as nbformat.IExecuteResult).data;
  719. break;
  720. case 'display_data':
  721. bundle = (output as nbformat.IDisplayData).data;
  722. break;
  723. case 'stream':
  724. bundle = {
  725. 'application/vnd.jupyter.console-text': (output as nbformat.IStream).text
  726. };
  727. break;
  728. case 'error':
  729. let out: nbformat.IError = output as nbformat.IError;
  730. let traceback = out.traceback.join('\n');
  731. bundle = {
  732. 'application/vnd.jupyter.console-text': traceback ||
  733. `${out.ename}: ${out.evalue}`
  734. };
  735. break;
  736. default:
  737. console.error(`Unrecognized output type: ${output.output_type}`);
  738. bundle = {};
  739. }
  740. return bundle;
  741. }
  742. /**
  743. * Convert a mime bundle to a mime map.
  744. */
  745. protected convertBundle(bundle: nbformat.MimeBundle): RenderMime.MimeMap<string> {
  746. let map: RenderMime.MimeMap<string> = Object.create(null);
  747. for (let mimeType in bundle) {
  748. let value = bundle[mimeType];
  749. if (Array.isArray(value)) {
  750. map[mimeType] = (value as string[]).join('\n');
  751. } else {
  752. map[mimeType] = value as string;
  753. }
  754. }
  755. return map;
  756. }
  757. private _rendermime: RenderMime = null;
  758. private _placeholder: Widget = null;
  759. }
  760. /**
  761. * A widget that handles stdin requests from the kernel.
  762. */
  763. class InputWidget extends Widget {
  764. /**
  765. * Construct a new input widget.
  766. */
  767. constructor(request: OutputAreaModel.IInputRequest) {
  768. super({ node: Private.createInputWidgetNode() });
  769. this.addClass(STDIN_CLASS);
  770. let text = this.node.firstChild as HTMLElement;
  771. text.textContent = request.prompt;
  772. this._input = this.node.lastChild as HTMLInputElement;
  773. if (request.password) {
  774. this._input.type = 'password';
  775. }
  776. this._kernel = request.kernel;
  777. }
  778. /**
  779. * Handle the DOM events for the widget.
  780. *
  781. * @param event - The DOM event sent to the widget.
  782. *
  783. * #### Notes
  784. * This method implements the DOM `EventListener` interface and is
  785. * called in response to events on the dock panel's node. It should
  786. * not be called directly by user code.
  787. */
  788. handleEvent(event: Event): void {
  789. let input = this._input;
  790. if (event.type === 'keydown') {
  791. if ((event as KeyboardEvent).keyCode === 13) { // Enter
  792. this._kernel.sendInputReply({
  793. value: input.value
  794. });
  795. let rendered = document.createElement('span');
  796. rendered.className = STDIN_RENDERED_CLASS;
  797. if (input.type === 'password') {
  798. rendered.textContent = Array(input.value.length + 1).join('·');
  799. } else {
  800. rendered.textContent = input.value;
  801. }
  802. this.node.replaceChild(rendered, input);
  803. }
  804. // Suppress keydown events from leaving the input.
  805. event.stopPropagation();
  806. }
  807. }
  808. /**
  809. * Handle `after-attach` messages sent to the widget.
  810. */
  811. protected onAfterAttach(msg: Message): void {
  812. this._input.focus();
  813. this._input.addEventListener('keydown', this);
  814. }
  815. /**
  816. * Handle `before-detach` messages sent to the widget.
  817. */
  818. protected onBeforeDetach(msg: Message): void {
  819. this._input.removeEventListener('keydown', this);
  820. }
  821. private _kernel: IKernel = null;
  822. private _input: HTMLInputElement = null;
  823. }
  824. /**
  825. * A namespace for OutputArea statics.
  826. */
  827. export
  828. namespace OutputWidget {
  829. /**
  830. * The options to pass to an `OutputWidget`.
  831. */
  832. export
  833. interface IOptions {
  834. /**
  835. * The rendermime instance used by the widget.
  836. */
  837. rendermime: RenderMime;
  838. }
  839. }
  840. // Define the signals for the `OutputAreaWidget` class.
  841. defineSignal(OutputAreaWidget.prototype, 'modelChanged');
  842. defineSignal(OutputAreaWidget.prototype, 'modelDisposed');
  843. /**
  844. * A namespace for private data.
  845. */
  846. namespace Private {
  847. /**
  848. * Create the node for an InputWidget.
  849. */
  850. export
  851. function createInputWidgetNode(): HTMLElement {
  852. let node = document.createElement('div');
  853. let prompt = document.createElement('span');
  854. prompt.className = STDIN_PROMPT_CLASS;
  855. let input = document.createElement('input');
  856. input.className = STDIN_INPUT_CLASS;
  857. node.appendChild(prompt);
  858. node.appendChild(input);
  859. return node;
  860. }
  861. }