widget.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  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. * Add a child to the layout.
  273. */
  274. protected addChild(): void {
  275. let widget = this._renderer.createOutput({ rendermime: this.rendermime });
  276. let layout = this.layout as PanelLayout;
  277. layout.addWidget(widget);
  278. this.updateChild(layout.widgets.length - 1);
  279. }
  280. /**
  281. * Remove a child from the layout.
  282. */
  283. protected removeChild(index: number): void {
  284. let layout = this.layout as PanelLayout;
  285. layout.widgets.at(index).dispose();
  286. }
  287. /**
  288. * Update a child in the layout.
  289. */
  290. protected updateChild(index: number): void {
  291. let layout = this.layout as PanelLayout;
  292. let widget = layout.widgets.at(index) as OutputWidget;
  293. let output = this._model.get(index);
  294. widget.render(output, this._trusted);
  295. }
  296. /**
  297. * Follow changes on the model state.
  298. */
  299. protected onModelStateChanged(sender: OutputAreaModel, args: IListChangedArgs<nbformat.IOutput>) {
  300. switch (args.type) {
  301. case 'add':
  302. // Children are always added at the end.
  303. this.addChild();
  304. break;
  305. case 'replace':
  306. // Only "clear" is supported by the model.
  307. // When an output area is cleared and then quickly replaced with new
  308. // content (as happens with @interact in widgets, for example), the
  309. // quickly changing height can make the page jitter.
  310. // We introduce a small delay in the minimum height
  311. // to prevent this jitter.
  312. let rect = this.node.getBoundingClientRect();
  313. this.node.style.minHeight = `${rect.height}px`;
  314. if (this._minHeightTimeout) {
  315. clearTimeout(this._minHeightTimeout);
  316. }
  317. this._minHeightTimeout = setTimeout(() => {
  318. if (this.isDisposed) {
  319. return;
  320. }
  321. this.node.style.minHeight = '';
  322. }, 50);
  323. let oldValues = args.oldValue as nbformat.IOutput[];
  324. for (let i = args.oldIndex; i < oldValues.length; i++) {
  325. this.removeChild(args.oldIndex);
  326. }
  327. break;
  328. case 'set':
  329. this.updateChild(args.newIndex);
  330. break;
  331. default:
  332. break;
  333. }
  334. this.update();
  335. }
  336. /**
  337. * Handle a new model.
  338. *
  339. * #### Notes
  340. * This method is called after the model change has been handled
  341. * internally and before the `modelChanged` signal is emitted.
  342. * The default implementation is a no-op.
  343. */
  344. protected onModelChanged(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  345. // no-op
  346. }
  347. /**
  348. * Handle a change to the model.
  349. */
  350. private _onModelChanged(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  351. let layout = this.layout as PanelLayout;
  352. if (oldValue) {
  353. oldValue.changed.disconnect(this.onModelStateChanged, this);
  354. oldValue.disposed.disconnect(this._onModelDisposed, this);
  355. }
  356. let start = newValue ? newValue.length : 0;
  357. // Clear unnecessary child widgets.
  358. for (let i = start; i < layout.widgets.length; i++) {
  359. this.removeChild(i);
  360. }
  361. if (!newValue) {
  362. return;
  363. }
  364. newValue.changed.connect(this.onModelStateChanged, this);
  365. newValue.disposed.connect(this._onModelDisposed, this);
  366. // Reuse existing child widgets.
  367. for (let i = 0; i < layout.widgets.length; i++) {
  368. this.updateChild(i);
  369. }
  370. // Add new widgets as necessary.
  371. for (let i = layout.widgets.length; i < newValue.length; i++) {
  372. this.addChild();
  373. }
  374. }
  375. /**
  376. * Handle a model disposal.
  377. */
  378. protected onModelDisposed(oldValue: OutputAreaModel, newValue: OutputAreaModel): void {
  379. // no-op
  380. }
  381. private _onModelDisposed(): void {
  382. this.modelDisposed.emit(void 0);
  383. this.dispose();
  384. }
  385. private _trusted = false;
  386. private _fixedHeight = false;
  387. private _collapsed = false;
  388. private _minHeightTimeout: number = null;
  389. private _model: OutputAreaModel = null;
  390. private _rendermime: RenderMime = null;
  391. private _renderer: OutputAreaWidget.IRenderer = null;
  392. }
  393. /**
  394. * A namespace for OutputAreaWidget statics.
  395. */
  396. export
  397. namespace OutputAreaWidget {
  398. /**
  399. * The options to pass to an `OutputAreaWidget`.
  400. */
  401. export
  402. interface IOptions {
  403. /**
  404. * The rendermime instance used by the widget.
  405. */
  406. rendermime: RenderMime;
  407. /**
  408. * The output widget renderer.
  409. *
  410. * Defaults to a shared `IRenderer` instance.
  411. */
  412. renderer?: IRenderer;
  413. }
  414. /**
  415. * An output widget renderer.
  416. */
  417. export
  418. interface IRenderer {
  419. /**
  420. * Create an output widget.
  421. *
  422. *
  423. * @returns A new widget for an output.
  424. */
  425. createOutput(options: OutputWidget.IOptions): Widget;
  426. }
  427. /**
  428. * The default implementation of `IRenderer`.
  429. */
  430. export
  431. class Renderer implements IRenderer {
  432. /**
  433. * Create an output widget.
  434. *
  435. *
  436. * @returns A new widget for an output.
  437. */
  438. createOutput(options: OutputWidget.IOptions): OutputWidget {
  439. return new OutputWidget(options);
  440. }
  441. }
  442. /**
  443. * The default `Renderer` instance.
  444. */
  445. export
  446. const defaultRenderer = new Renderer();
  447. }
  448. /**
  449. * The gutter on the left side of the OutputWidget
  450. */
  451. export
  452. class OutputGutter extends Widget {
  453. /**
  454. * Handle the DOM events for the output gutter widget.
  455. *
  456. * @param event - The DOM event sent to the widget.
  457. *
  458. * #### Notes
  459. * This method implements the DOM `EventListener` interface and is
  460. * called in response to events on the panel's DOM node. It should
  461. * not be called directly by user code.
  462. */
  463. handleEvent(event: Event): void {
  464. switch (event.type) {
  465. case 'mousedown':
  466. this._evtMousedown(event as MouseEvent);
  467. break;
  468. case 'mouseup':
  469. this._evtMouseup(event as MouseEvent);
  470. break;
  471. case 'mousemove':
  472. this._evtMousemove(event as MouseEvent);
  473. break;
  474. default:
  475. break;
  476. }
  477. }
  478. /**
  479. * A message handler invoked on an `'after-attach'` message.
  480. */
  481. protected onAfterAttach(msg: Message): void {
  482. super.onAfterAttach(msg);
  483. this.node.addEventListener('mousedown', this);
  484. }
  485. /**
  486. * A message handler invoked on a `'before-detach'` message.
  487. */
  488. protected onBeforeDetach(msg: Message): void {
  489. super.onBeforeDetach(msg);
  490. let node = this.node;
  491. node.removeEventListener('mousedown', this);
  492. }
  493. /**
  494. * Handle the `'mousedown'` event for the widget.
  495. */
  496. private _evtMousedown(event: MouseEvent): void {
  497. // Left mouse press for drag start.
  498. if (event.button === 0) {
  499. this._dragData = { pressX: event.clientX, pressY: event.clientY };
  500. document.addEventListener('mouseup', this, true);
  501. document.addEventListener('mousemove', this, true);
  502. }
  503. }
  504. /**
  505. * Handle the `'mouseup'` event for the widget.
  506. */
  507. private _evtMouseup(event: MouseEvent): void {
  508. if (event.button !== 0 || !this._drag) {
  509. document.removeEventListener('mousemove', this, true);
  510. document.removeEventListener('mouseup', this, true);
  511. return;
  512. }
  513. event.preventDefault();
  514. event.stopPropagation();
  515. }
  516. /**
  517. * Handle the `'mousemove'` event for the widget.
  518. */
  519. private _evtMousemove(event: MouseEvent): void {
  520. event.preventDefault();
  521. event.stopPropagation();
  522. // Bail if we are the one dragging.
  523. if (this._drag) {
  524. return;
  525. }
  526. // Check for a drag initialization.
  527. let data = this._dragData;
  528. let dx = Math.abs(event.clientX - data.pressX);
  529. let dy = Math.abs(event.clientY - data.pressY);
  530. if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) {
  531. return;
  532. }
  533. this._startDrag(event.clientX, event.clientY);
  534. }
  535. /**
  536. * Start a drag event.
  537. */
  538. private _startDrag(clientX: number, clientY: number): void {
  539. // Set up the drag event.
  540. this._drag = new Drag({
  541. mimeData: new MimeData(),
  542. supportedActions: 'copy',
  543. proposedAction: 'copy'
  544. });
  545. this._drag.mimeData.setData(FACTORY_MIME, () => {
  546. let outputArea = this.parent.parent as OutputAreaWidget;
  547. return outputArea.mirror();
  548. });
  549. // Remove mousemove and mouseup listeners and start the drag.
  550. document.removeEventListener('mousemove', this, true);
  551. document.removeEventListener('mouseup', this, true);
  552. this._drag.start(clientX, clientY).then(action => {
  553. this._drag = null;
  554. });
  555. }
  556. /**
  557. * Dispose of the resources held by the widget.
  558. */
  559. dispose() {
  560. // Do nothing if already disposed.
  561. if (this.isDisposed) {
  562. return;
  563. }
  564. this._dragData = null;
  565. this._drag = null;
  566. super.dispose();
  567. }
  568. private _drag: Drag = null;
  569. private _dragData: { pressX: number, pressY: number } = null;
  570. }
  571. /**
  572. * An output widget.
  573. */
  574. export
  575. class OutputWidget extends Widget {
  576. /**
  577. * Construct a new output widget.
  578. */
  579. constructor(options: OutputWidget.IOptions) {
  580. super();
  581. let layout = new PanelLayout();
  582. this.layout = layout;
  583. let prompt = new OutputGutter();
  584. this._placeholder = new Widget();
  585. this.addClass(OUTPUT_CLASS);
  586. prompt.addClass(PROMPT_CLASS);
  587. this._placeholder.addClass(RESULT_CLASS);
  588. layout.addWidget(prompt);
  589. layout.addWidget(this._placeholder);
  590. this._rendermime = options.rendermime;
  591. }
  592. /**
  593. * The prompt widget used by the output widget.
  594. *
  595. * #### Notes
  596. * This is a read-only property.
  597. */
  598. get prompt(): Widget {
  599. let layout = this.layout as PanelLayout;
  600. return layout.widgets.at(0);
  601. }
  602. /**
  603. * The rendered output used by the output widget.
  604. *
  605. * #### Notes
  606. * This is a read-only property.
  607. */
  608. get output(): Widget {
  609. let layout = this.layout as PanelLayout;
  610. return layout.widgets.at(1);
  611. }
  612. /**
  613. * Dispose of the resources held by the widget.
  614. */
  615. dispose(): void {
  616. this._rendermime = null;
  617. this._placeholder = null;
  618. super.dispose();
  619. }
  620. /**
  621. * Clear the widget contents.
  622. */
  623. clear(): void {
  624. this.setOutput(this._placeholder);
  625. this.prompt.node.textContent = '';
  626. }
  627. /**
  628. * Render an output.
  629. *
  630. * @param output - The kernel output message payload.
  631. *
  632. * @param trusted - Whether the output is trusted.
  633. */
  634. render(output: OutputAreaModel.Output, trusted=false): void {
  635. // Handle an input request.
  636. if (output.output_type === 'input_request') {
  637. let child = new InputWidget(output as OutputAreaModel.IInputRequest);
  638. this.setOutput(child);
  639. return;
  640. }
  641. // Extract the data from the output and sanitize if necessary.
  642. let rendermime = this._rendermime;
  643. let bundle = this.getBundle(output as nbformat.IOutput);
  644. let data = this.convertBundle(bundle);
  645. // Clear the content.
  646. this.clear();
  647. // Bail if no data to display.
  648. let msg = 'Did not find renderer for output mimebundle.';
  649. if (!data) {
  650. console.log(msg);
  651. return;
  652. }
  653. // Create the output result area.
  654. let child = rendermime.render(data, trusted);
  655. if (!child) {
  656. console.log(msg);
  657. console.log(data);
  658. return;
  659. }
  660. this.setOutput(child);
  661. // Add classes and output prompt as necessary.
  662. switch (output.output_type) {
  663. case 'execute_result':
  664. child.addClass(EXECUTE_CLASS);
  665. let count = (output as nbformat.IExecuteResult).execution_count;
  666. this.prompt.node.textContent = `Out[${count === null ? ' ' : count}]:`;
  667. break;
  668. case 'display_data':
  669. child.addClass(DISPLAY_CLASS);
  670. break;
  671. case 'stream':
  672. if ((output as nbformat.IStream).name === 'stdout') {
  673. child.addClass(STDOUT_CLASS);
  674. } else {
  675. child.addClass(STDERR_CLASS);
  676. }
  677. break;
  678. case 'error':
  679. child.addClass(ERROR_CLASS);
  680. break;
  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. }
  737. return bundle || {};
  738. }
  739. /**
  740. * Convert a mime bundle to a mime map.
  741. */
  742. protected convertBundle(bundle: nbformat.MimeBundle): RenderMime.MimeMap<string> {
  743. let map: RenderMime.MimeMap<string> = Object.create(null);
  744. for (let mimeType in bundle) {
  745. let value = bundle[mimeType];
  746. if (Array.isArray(value)) {
  747. map[mimeType] = (value as string[]).join('\n');
  748. } else {
  749. map[mimeType] = value as string;
  750. }
  751. }
  752. return map;
  753. }
  754. private _rendermime: RenderMime = null;
  755. private _placeholder: Widget = null;
  756. }
  757. /**
  758. * A widget that handles stdin requests from the kernel.
  759. */
  760. class InputWidget extends Widget {
  761. /**
  762. * Construct a new input widget.
  763. */
  764. constructor(request: OutputAreaModel.IInputRequest) {
  765. super({ node: Private.createInputWidgetNode() });
  766. this.addClass(STDIN_CLASS);
  767. let text = this.node.firstChild as HTMLElement;
  768. text.textContent = request.prompt;
  769. this._input = this.node.lastChild as HTMLInputElement;
  770. if (request.password) {
  771. this._input.type = 'password';
  772. }
  773. this._kernel = request.kernel;
  774. }
  775. /**
  776. * Handle the DOM events for the widget.
  777. *
  778. * @param event - The DOM event sent to the widget.
  779. *
  780. * #### Notes
  781. * This method implements the DOM `EventListener` interface and is
  782. * called in response to events on the dock panel's node. It should
  783. * not be called directly by user code.
  784. */
  785. handleEvent(event: Event): void {
  786. let input = this._input;
  787. if (event.type === 'keydown') {
  788. if ((event as KeyboardEvent).keyCode === 13) { // Enter
  789. this._kernel.sendInputReply({
  790. value: input.value
  791. });
  792. let rendered = document.createElement('span');
  793. rendered.className = STDIN_RENDERED_CLASS;
  794. if (input.type === 'password') {
  795. rendered.textContent = Array(input.value.length + 1).join('·');
  796. } else {
  797. rendered.textContent = input.value;
  798. }
  799. this.node.replaceChild(rendered, input);
  800. }
  801. // Suppress keydown events from leaving the input.
  802. event.stopPropagation();
  803. }
  804. }
  805. /**
  806. * Handle `after-attach` messages sent to the widget.
  807. */
  808. protected onAfterAttach(msg: Message): void {
  809. this._input.addEventListener('keydown', this);
  810. this.update();
  811. }
  812. /**
  813. * Handle `update-request` messages sent to the widget.
  814. */
  815. protected onUpdateRequest(msg: Message): void {
  816. this._input.focus();
  817. }
  818. /**
  819. * Handle `before-detach` messages sent to the widget.
  820. */
  821. protected onBeforeDetach(msg: Message): void {
  822. this._input.removeEventListener('keydown', this);
  823. }
  824. private _kernel: IKernel = null;
  825. private _input: HTMLInputElement = null;
  826. }
  827. /**
  828. * A namespace for OutputArea statics.
  829. */
  830. export
  831. namespace OutputWidget {
  832. /**
  833. * The options to pass to an `OutputWidget`.
  834. */
  835. export
  836. interface IOptions {
  837. /**
  838. * The rendermime instance used by the widget.
  839. */
  840. rendermime: RenderMime;
  841. }
  842. }
  843. // Define the signals for the `OutputAreaWidget` class.
  844. defineSignal(OutputAreaWidget.prototype, 'modelChanged');
  845. defineSignal(OutputAreaWidget.prototype, 'modelDisposed');
  846. /**
  847. * A namespace for private data.
  848. */
  849. namespace Private {
  850. /**
  851. * Create the node for an InputWidget.
  852. */
  853. export
  854. function createInputWidgetNode(): HTMLElement {
  855. let node = document.createElement('div');
  856. let prompt = document.createElement('span');
  857. prompt.className = STDIN_PROMPT_CLASS;
  858. let input = document.createElement('input');
  859. input.className = STDIN_INPUT_CLASS;
  860. node.appendChild(prompt);
  861. node.appendChild(input);
  862. return node;
  863. }
  864. }