widget.ts 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ArrayExt, each
  5. } from '@phosphor/algorithm';
  6. import {
  7. JSONValue
  8. } from '@phosphor/coreutils';
  9. import {
  10. Message
  11. } from '@phosphor/messaging';
  12. import {
  13. MimeData
  14. } from '@phosphor/coreutils';
  15. import {
  16. AttachedProperty
  17. } from '@phosphor/properties';
  18. import {
  19. ISignal, Signal
  20. } from '@phosphor/signaling';
  21. import {
  22. Drag, IDragEvent
  23. } from '@phosphor/dragdrop';
  24. import {
  25. PanelLayout
  26. } from '@phosphor/widgets';
  27. import {
  28. Widget
  29. } from '@phosphor/widgets';
  30. import {
  31. ICellModel, Cell, IMarkdownCellModel,
  32. CodeCell, MarkdownCell,
  33. ICodeCellModel, RawCell, IRawCellModel,
  34. } from '@jupyterlab/cells';
  35. import {
  36. IEditorMimeTypeService, CodeEditor
  37. } from '@jupyterlab/codeeditor';
  38. import {
  39. IChangedArgs, IObservableMap, ObservableMap, IObservableVector,
  40. ObservableVector, nbformat
  41. } from '@jupyterlab/coreutils';
  42. import {
  43. RenderMime
  44. } from '@jupyterlab/rendermime';
  45. import {
  46. INotebookModel
  47. } from './model';
  48. /**
  49. * The class name added to notebook widgets.
  50. */
  51. const NB_CLASS = 'jp-Notebook';
  52. /**
  53. * The class name added to notebook widget cells.
  54. */
  55. const NB_CELL_CLASS = 'jp-Notebook-cell';
  56. /**
  57. * The class name added to a notebook in edit mode.
  58. */
  59. const EDIT_CLASS = 'jp-mod-editMode';
  60. /**
  61. * The class name added to a notebook in command mode.
  62. */
  63. const COMMAND_CLASS = 'jp-mod-commandMode';
  64. /**
  65. * The class name added to the active cell.
  66. */
  67. const ACTIVE_CLASS = 'jp-mod-active';
  68. /**
  69. * The class name added to selected cells.
  70. */
  71. const SELECTED_CLASS = 'jp-mod-selected';
  72. /**
  73. * The class name added to an active cell when there are other selected cells.
  74. */
  75. const OTHER_SELECTED_CLASS = 'jp-mod-multiSelected';
  76. /**
  77. * The class name added to unconfined images.
  78. */
  79. const UNCONFINED_CLASS = 'jp-mod-unconfined';
  80. /**
  81. * The class name added to a drop target.
  82. */
  83. const DROP_TARGET_CLASS = 'jp-mod-dropTarget';
  84. /**
  85. * The class name added to a drop source.
  86. */
  87. const DROP_SOURCE_CLASS = 'jp-mod-dropSource';
  88. /**
  89. * The class name added to drag images.
  90. */
  91. const DRAG_IMAGE_CLASS = 'jp-dragImage';
  92. /**
  93. * The class name added to a filled circle.
  94. */
  95. const FILLED_CIRCLE_CLASS = 'jp-filledCircle';
  96. /**
  97. * The mimetype used for Jupyter cell data.
  98. */
  99. export
  100. const JUPYTER_CELL_MIME: string = 'application/vnd.jupyter.cells';
  101. /**
  102. * The threshold in pixels to start a drag event.
  103. */
  104. const DRAG_THRESHOLD = 5;
  105. /**
  106. * The interactivity modes for the notebook.
  107. */
  108. export
  109. type NotebookMode = 'command' | 'edit';
  110. /**
  111. * A widget which renders static non-interactive notebooks.
  112. *
  113. * #### Notes
  114. * The widget model must be set separately and can be changed
  115. * at any time. Consumers of the widget must account for a
  116. * `null` model, and may want to listen to the `modelChanged`
  117. * signal.
  118. */
  119. export
  120. class StaticNotebook extends Widget {
  121. /**
  122. * Construct a notebook widget.
  123. */
  124. constructor(options: StaticNotebook.IOptions) {
  125. super();
  126. this.addClass(NB_CLASS);
  127. this.rendermime = options.rendermime;
  128. this.layout = new Private.NotebookPanelLayout();
  129. this.contentFactory = (
  130. options.contentFactory || StaticNotebook.defaultContentFactory
  131. );
  132. this._mimetypeService = options.mimeTypeService;
  133. }
  134. /**
  135. * A signal emitted when the model of the notebook changes.
  136. */
  137. get modelChanged(): ISignal<this, void> {
  138. return this._modelChanged;
  139. }
  140. /**
  141. * A signal emitted when the model content changes.
  142. *
  143. * #### Notes
  144. * This is a convenience signal that follows the current model.
  145. */
  146. get modelContentChanged(): ISignal<this, void> {
  147. return this._modelContentChanged;
  148. }
  149. /**
  150. * The cell factory used by the widget.
  151. */
  152. readonly contentFactory: StaticNotebook.IContentFactory;
  153. /**
  154. * The Rendermime instance used by the widget.
  155. */
  156. readonly rendermime: RenderMime;
  157. /**
  158. * The model for the widget.
  159. */
  160. get model(): INotebookModel {
  161. return this._model;
  162. }
  163. set model(newValue: INotebookModel) {
  164. newValue = newValue || null;
  165. if (this._model === newValue) {
  166. return;
  167. }
  168. let oldValue = this._model;
  169. this._model = newValue;
  170. // Trigger private, protected, and public changes.
  171. this._onModelChanged(oldValue, newValue);
  172. this.onModelChanged(oldValue, newValue);
  173. this._modelChanged.emit(void 0);
  174. }
  175. /**
  176. * Get the mimetype for code cells.
  177. */
  178. get codeMimetype(): string {
  179. return this._mimetype;
  180. }
  181. /**
  182. * A read-only sequence of the widgets in the notebook.
  183. */
  184. get widgets(): ReadonlyArray<Cell> {
  185. return (this.layout as PanelLayout).widgets as ReadonlyArray<Cell>;
  186. }
  187. /**
  188. * Dispose of the resources held by the widget.
  189. */
  190. dispose() {
  191. // Do nothing if already disposed.
  192. if (this.isDisposed) {
  193. return;
  194. }
  195. this._model = null;
  196. super.dispose();
  197. }
  198. /**
  199. * Handle a new model.
  200. *
  201. * #### Notes
  202. * This method is called after the model change has been handled
  203. * internally and before the `modelChanged` signal is emitted.
  204. * The default implementation is a no-op.
  205. */
  206. protected onModelChanged(oldValue: INotebookModel, newValue: INotebookModel): void {
  207. // No-op.
  208. }
  209. /**
  210. * Handle changes to the notebook model content.
  211. *
  212. * #### Notes
  213. * The default implementation emits the `modelContentChanged` signal.
  214. */
  215. protected onModelContentChanged(model: INotebookModel, args: void): void {
  216. this._modelContentChanged.emit(void 0);
  217. }
  218. /**
  219. * Handle changes to the notebook model metadata.
  220. *
  221. * #### Notes
  222. * The default implementation updates the mimetypes of the code cells
  223. * when the `language_info` metadata changes.
  224. */
  225. protected onMetadataChanged(sender: IObservableMap<JSONValue>, args: ObservableMap.IChangedArgs<JSONValue>): void {
  226. switch (args.key) {
  227. case 'language_info':
  228. this._updateMimetype();
  229. break;
  230. default:
  231. break;
  232. }
  233. }
  234. /**
  235. * Handle a cell being inserted.
  236. *
  237. * The default implementation is a no-op
  238. */
  239. protected onCellInserted(index: number, cell: Cell): void {
  240. // This is a no-op.
  241. }
  242. /**
  243. * Handle a cell being moved.
  244. *
  245. * The default implementation is a no-op
  246. */
  247. protected onCellMoved(fromIndex: number, toIndex: number): void {
  248. // This is a no-op.
  249. }
  250. /**
  251. * Handle a cell being removed.
  252. *
  253. * The default implementation is a no-op
  254. */
  255. protected onCellRemoved(index: number, cell: Cell): void {
  256. // This is a no-op.
  257. }
  258. /**
  259. * Handle a new model on the widget.
  260. */
  261. private _onModelChanged(oldValue: INotebookModel, newValue: INotebookModel): void {
  262. let layout = this.layout as PanelLayout;
  263. if (oldValue) {
  264. oldValue.cells.changed.disconnect(this._onCellsChanged, this);
  265. oldValue.metadata.changed.disconnect(this.onMetadataChanged, this);
  266. oldValue.contentChanged.disconnect(this.onModelContentChanged, this);
  267. // TODO: reuse existing cell widgets if possible.
  268. while (layout.widgets.length) {
  269. this._removeCell(0);
  270. }
  271. }
  272. if (!newValue) {
  273. this._mimetype = 'text/plain';
  274. return;
  275. }
  276. this._updateMimetype();
  277. let cells = newValue.cells;
  278. each(cells, (cell: ICellModel, i: number) => {
  279. this._insertCell(i, cell);
  280. });
  281. cells.changed.connect(this._onCellsChanged, this);
  282. newValue.contentChanged.connect(this.onModelContentChanged, this);
  283. newValue.metadata.changed.connect(this.onMetadataChanged, this);
  284. }
  285. /**
  286. * Handle a change cells event.
  287. */
  288. private _onCellsChanged(sender: IObservableVector<ICellModel>, args: ObservableVector.IChangedArgs<ICellModel>) {
  289. let index = 0;
  290. switch (args.type) {
  291. case 'add':
  292. index = args.newIndex;
  293. each(args.newValues, value => {
  294. this._insertCell(index++, value);
  295. });
  296. break;
  297. case 'move':
  298. this._moveCell(args.oldIndex, args.newIndex);
  299. break;
  300. case 'remove':
  301. each(args.oldValues, value => {
  302. this._removeCell(args.oldIndex);
  303. });
  304. break;
  305. case 'set':
  306. // TODO: reuse existing widgets if possible.
  307. index = args.newIndex;
  308. each(args.newValues, value => {
  309. // Note: this ordering (insert then remove)
  310. // is important for getting the active cell
  311. // index for the editable notebook correct.
  312. this._insertCell(index, value);
  313. this._removeCell(index+1);
  314. index++;
  315. });
  316. break;
  317. default:
  318. return;
  319. }
  320. }
  321. /**
  322. * Create a cell widget and insert into the notebook.
  323. */
  324. private _insertCell(index: number, cell: ICellModel): void {
  325. let widget: Cell;
  326. switch (cell.type) {
  327. case 'code':
  328. widget = this._createCodeCell(cell as ICodeCellModel);
  329. widget.model.mimeType = this._mimetype;
  330. break;
  331. case 'markdown':
  332. widget = this._createMarkdownCell(cell as IMarkdownCellModel);
  333. break;
  334. default:
  335. widget = this._createRawCell(cell as IRawCellModel);
  336. }
  337. widget.addClass(NB_CELL_CLASS);
  338. let layout = this.layout as PanelLayout;
  339. layout.insertWidget(index, widget);
  340. this.onCellInserted(index, widget);
  341. }
  342. /**
  343. * Create a code cell widget from a code cell model.
  344. */
  345. private _createCodeCell(model: ICodeCellModel): CodeCell {
  346. let rendermime = this.rendermime;
  347. let contentFactory = this.contentFactory;
  348. let options = { model, rendermime, contentFactory };
  349. return this.contentFactory.createCodeCell(options, this);
  350. }
  351. /**
  352. * Create a markdown cell widget from a markdown cell model.
  353. */
  354. private _createMarkdownCell(model: IMarkdownCellModel): MarkdownCell {
  355. let rendermime = this.rendermime;
  356. let contentFactory = this.contentFactory;
  357. let options = { model, rendermime, contentFactory };
  358. return this.contentFactory.createMarkdownCell(options, this);
  359. }
  360. /**
  361. * Create a raw cell widget from a raw cell model.
  362. */
  363. private _createRawCell(model: IRawCellModel): RawCell {
  364. let contentFactory = this.contentFactory;
  365. let options = { model, contentFactory };
  366. return this.contentFactory.createRawCell(options, this);
  367. }
  368. /**
  369. * Move a cell widget.
  370. */
  371. private _moveCell(fromIndex: number, toIndex: number): void {
  372. let layout = this.layout as PanelLayout;
  373. layout.insertWidget(toIndex, layout.widgets[fromIndex]);
  374. this.onCellMoved(fromIndex, toIndex);
  375. }
  376. /**
  377. * Remove a cell widget.
  378. */
  379. private _removeCell(index: number): void {
  380. let layout = this.layout as PanelLayout;
  381. let widget = layout.widgets[index] as Cell;
  382. widget.parent = null;
  383. this.onCellRemoved(index, widget);
  384. widget.dispose();
  385. }
  386. /**
  387. * Update the mimetype of the notebook.
  388. */
  389. private _updateMimetype(): void {
  390. let info = this._model.metadata.get('language_info') as nbformat.ILanguageInfoMetadata;
  391. if (!info) {
  392. return;
  393. }
  394. this._mimetype = this._mimetypeService.getMimeTypeByLanguage(info);
  395. each(this.widgets, widget => {
  396. if (widget.model.type === 'code') {
  397. widget.model.mimeType = this._mimetype;
  398. }
  399. });
  400. }
  401. private _mimetype = 'text/plain';
  402. private _model: INotebookModel = null;
  403. private _mimetypeService: IEditorMimeTypeService;
  404. private _modelChanged = new Signal<this, void>(this);
  405. private _modelContentChanged = new Signal<this, void>(this);
  406. }
  407. /**
  408. * The namespace for the `StaticNotebook` class statics.
  409. */
  410. export
  411. namespace StaticNotebook {
  412. /**
  413. * An options object for initializing a static notebook.
  414. */
  415. export
  416. interface IOptions {
  417. /**
  418. * The rendermime instance used by the widget.
  419. */
  420. rendermime: RenderMime;
  421. /**
  422. * The language preference for the model.
  423. */
  424. languagePreference?: string;
  425. /**
  426. * A factory for creating content.
  427. */
  428. contentFactory?: IContentFactory;
  429. /**
  430. * The service used to look up mime types.
  431. */
  432. mimeTypeService: IEditorMimeTypeService;
  433. }
  434. /**
  435. * A factory for creating notebook content.
  436. *
  437. * #### Notes
  438. * This extends the content factory of the cell itself, which extends the content
  439. * factory of the output area and input area. The result is that there is a single
  440. * factory for creating all child content of a notebook.
  441. */
  442. export
  443. interface IContentFactory extends Cell.IContentFactory {
  444. /**
  445. * Create a new code cell widget.
  446. */
  447. createCodeCell(options: CodeCell.IOptions, parent: StaticNotebook): CodeCell;
  448. /**
  449. * Create a new markdown cell widget.
  450. */
  451. createMarkdownCell(options: MarkdownCell.IOptions, parent: StaticNotebook): MarkdownCell;
  452. /**
  453. * Create a new raw cell widget.
  454. */
  455. createRawCell(options: RawCell.IOptions, parent: StaticNotebook): RawCell;
  456. }
  457. /**
  458. * The default implementation of an `IContentFactory`.
  459. */
  460. export
  461. class ContentFactory extends Cell.ContentFactory implements IContentFactory {
  462. /**
  463. * Create a new code cell widget.
  464. *
  465. * #### Notes
  466. * If no cell content factory is passed in with the options, the one on the
  467. * notebook content factory is used.
  468. */
  469. createCodeCell(options: CodeCell.IOptions, parent: StaticNotebook): CodeCell {
  470. if (!options.contentFactory) {
  471. options.contentFactory = this;
  472. }
  473. return new CodeCell(options);
  474. }
  475. /**
  476. * Create a new markdown cell widget.
  477. *
  478. * #### Notes
  479. * If no cell content factory is passed in with the options, the one on the
  480. * notebook content factory is used.
  481. */
  482. createMarkdownCell(options: MarkdownCell.IOptions, parent: StaticNotebook): MarkdownCell {
  483. if (!options.contentFactory) {
  484. options.contentFactory = this;
  485. }
  486. return new MarkdownCell(options);
  487. }
  488. /**
  489. * Create a new raw cell widget.
  490. *
  491. * #### Notes
  492. * If no cell content factory is passed in with the options, the one on the
  493. * notebook content factory is used.
  494. */
  495. createRawCell(options: RawCell.IOptions, parent: StaticNotebook): RawCell {
  496. if (!options.contentFactory) {
  497. options.contentFactory = this;
  498. }
  499. return new RawCell(options);
  500. }
  501. }
  502. /**
  503. * Options for the content factory.
  504. */
  505. export
  506. interface IContentFactoryOptions extends Cell.IContentFactoryOptions { }
  507. /**
  508. * Default content factory for the static notebook widget.
  509. */
  510. export
  511. const defaultContentFactory: IContentFactory = new ContentFactory();
  512. }
  513. /**
  514. * A notebook widget that supports interactivity.
  515. */
  516. export
  517. class Notebook extends StaticNotebook {
  518. /**
  519. * Construct a notebook widget.
  520. */
  521. constructor(options: Notebook.IOptions) {
  522. super( Private.processNotebookOptions(options) );
  523. this.node.tabIndex = -1; // Allow the widget to take focus.
  524. // Allow the node to scroll while dragging items.
  525. this.node.setAttribute('data-p-dragscroll', 'true');
  526. }
  527. /**
  528. * A signal emitted when the active cell changes.
  529. *
  530. * #### Notes
  531. * This can be due to the active index changing or the
  532. * cell at the active index changing.
  533. */
  534. get activeCellChanged(): ISignal<this, Cell> {
  535. return this._activeCellChanged;
  536. }
  537. /**
  538. * A signal emitted when the state of the notebook changes.
  539. */
  540. get stateChanged(): ISignal<this, IChangedArgs<any>> {
  541. return this._stateChanged;
  542. }
  543. /**
  544. * A signal emitted when the selection state of the notebook changes.
  545. */
  546. get selectionChanged(): ISignal<this, void> {
  547. return this._selectionChanged;
  548. }
  549. /**
  550. * The interactivity mode of the notebook.
  551. */
  552. get mode(): NotebookMode {
  553. return this._mode;
  554. }
  555. set mode(newValue: NotebookMode) {
  556. let activeCell = this.activeCell;
  557. if (!activeCell) {
  558. newValue = 'command';
  559. }
  560. if (newValue === this._mode) {
  561. this._ensureFocus();
  562. return;
  563. }
  564. // Post an update request.
  565. this.update();
  566. let oldValue = this._mode;
  567. this._mode = newValue;
  568. if (newValue === 'edit') {
  569. let node = this.activeCell.editorWidget.node;
  570. this.scrollToPosition(node.getBoundingClientRect().top);
  571. // Edit mode deselects all cells.
  572. each(this.widgets, widget => { this.deselect(widget); });
  573. // Edit mode unrenders an active markdown widget.
  574. if (activeCell instanceof MarkdownCell) {
  575. activeCell.rendered = false;
  576. }
  577. }
  578. this._stateChanged.emit({ name: 'mode', oldValue, newValue });
  579. this._ensureFocus();
  580. }
  581. /**
  582. * The active cell index of the notebook.
  583. *
  584. * #### Notes
  585. * The index will be clamped to the bounds of the notebook cells.
  586. */
  587. get activeCellIndex(): number {
  588. if (!this.model) {
  589. return -1;
  590. }
  591. return this.model.cells.length ? this._activeCellIndex : -1;
  592. }
  593. set activeCellIndex(newValue: number) {
  594. let oldValue = this._activeCellIndex;
  595. if (!this.model || !this.model.cells.length) {
  596. newValue = -1;
  597. } else {
  598. newValue = Math.max(newValue, 0);
  599. newValue = Math.min(newValue, this.model.cells.length - 1);
  600. }
  601. this._activeCellIndex = newValue;
  602. let cell = this.widgets[newValue];
  603. if (cell !== this._activeCell) {
  604. // Post an update request.
  605. this.update();
  606. this._activeCell = cell;
  607. this._activeCellChanged.emit(cell);
  608. }
  609. if (this.mode === 'edit' && cell instanceof MarkdownCell) {
  610. cell.rendered = false;
  611. }
  612. this._ensureFocus();
  613. if (newValue === oldValue) {
  614. return;
  615. }
  616. this._stateChanged.emit({ name: 'activeCellIndex', oldValue, newValue });
  617. }
  618. /**
  619. * Get the active cell widget.
  620. */
  621. get activeCell(): Cell {
  622. return this._activeCell;
  623. }
  624. /**
  625. * Dispose of the resources held by the widget.
  626. */
  627. dispose(): void {
  628. if (this._activeCell === null) {
  629. return;
  630. }
  631. this._activeCell = null;
  632. super.dispose();
  633. }
  634. /**
  635. * Select a cell widget.
  636. *
  637. * #### Notes
  638. * It is a no-op if the value does not change.
  639. * It will emit the `selectionChanged` signal.
  640. */
  641. select(widget: Cell): void {
  642. if (Private.selectedProperty.get(widget)) {
  643. return;
  644. }
  645. Private.selectedProperty.set(widget, true);
  646. this._selectionChanged.emit(void 0);
  647. this.update();
  648. }
  649. /**
  650. * Deselect a cell widget.
  651. *
  652. * #### Notes
  653. * It is a no-op if the value does not change.
  654. * It will emit the `selectionChanged` signal.
  655. */
  656. deselect(widget: Cell): void {
  657. if (!Private.selectedProperty.get(widget)) {
  658. return;
  659. }
  660. Private.selectedProperty.set(widget, false);
  661. this._selectionChanged.emit(void 0);
  662. this.update();
  663. }
  664. /**
  665. * Whether a cell is selected or is the active cell.
  666. */
  667. isSelected(widget: Cell): boolean {
  668. if (widget === this._activeCell) {
  669. return true;
  670. }
  671. return Private.selectedProperty.get(widget);
  672. }
  673. /**
  674. * Deselect all of the cells.
  675. */
  676. deselectAll(): void {
  677. let changed = false;
  678. each(this.widgets, widget => {
  679. if (Private.selectedProperty.get(widget)) {
  680. changed = true;
  681. }
  682. Private.selectedProperty.set(widget, false);
  683. });
  684. if (changed) {
  685. this._selectionChanged.emit(void 0);
  686. }
  687. // Make sure we have a valid active cell.
  688. this.activeCellIndex = this.activeCellIndex;
  689. }
  690. /**
  691. * Scroll so that the given position is visible.
  692. *
  693. * @param position - The vertical position in the notebook widget.
  694. *
  695. * @param threshold - An optional threshold for the scroll. Defaults to 25
  696. * percent of the widget height.
  697. */
  698. scrollToPosition(position: number, threshold=25): void {
  699. let node = this.node;
  700. let ar = node.getBoundingClientRect();
  701. let delta = position - ar.top - ar.height / 2;
  702. if (Math.abs(delta) > ar.height * threshold / 100) {
  703. node.scrollTop += delta;
  704. }
  705. }
  706. /**
  707. * Handle the DOM events for the widget.
  708. *
  709. * @param event - The DOM event sent to the widget.
  710. *
  711. * #### Notes
  712. * This method implements the DOM `EventListener` interface and is
  713. * called in response to events on the notebook panel's node. It should
  714. * not be called directly by user code.
  715. */
  716. handleEvent(event: Event): void {
  717. if (!this.model || this.model.readOnly) {
  718. return;
  719. }
  720. switch (event.type) {
  721. case 'mousedown':
  722. this._evtMouseDown(event as MouseEvent);
  723. break;
  724. case 'mouseup':
  725. this._evtMouseup(event as MouseEvent);
  726. break;
  727. case 'mousemove':
  728. this._evtMousemove(event as MouseEvent);
  729. break;
  730. case 'keydown':
  731. this._ensureFocus(true);
  732. break;
  733. case 'dblclick':
  734. this._evtDblClick(event as MouseEvent);
  735. break;
  736. case 'focus':
  737. this._evtFocus(event as MouseEvent);
  738. break;
  739. case 'blur':
  740. this._evtBlur(event as MouseEvent);
  741. break;
  742. case 'p-dragenter':
  743. this._evtDragEnter(event as IDragEvent);
  744. break;
  745. case 'p-dragleave':
  746. this._evtDragLeave(event as IDragEvent);
  747. break;
  748. case 'p-dragover':
  749. this._evtDragOver(event as IDragEvent);
  750. break;
  751. case 'p-drop':
  752. this._evtDrop(event as IDragEvent);
  753. break;
  754. default:
  755. break;
  756. }
  757. }
  758. /**
  759. * Handle `after-attach` messages for the widget.
  760. */
  761. protected onAfterAttach(msg: Message): void {
  762. super.onAfterAttach(msg);
  763. let node = this.node;
  764. node.addEventListener('mousedown', this);
  765. node.addEventListener('keydown', this);
  766. node.addEventListener('dblclick', this);
  767. node.addEventListener('focus', this, true);
  768. node.addEventListener('blur', this, true);
  769. node.addEventListener('p-dragenter', this);
  770. node.addEventListener('p-dragleave', this);
  771. node.addEventListener('p-dragover', this);
  772. node.addEventListener('p-drop', this);
  773. }
  774. /**
  775. * Handle `before-detach` messages for the widget.
  776. */
  777. protected onBeforeDetach(msg: Message): void {
  778. let node = this.node;
  779. node.removeEventListener('mousedown', this);
  780. node.removeEventListener('keydown', this);
  781. node.removeEventListener('dblclick', this);
  782. node.removeEventListener('focus', this, true);
  783. node.removeEventListener('blur', this, true);
  784. node.removeEventListener('p-dragenter', this);
  785. node.removeEventListener('p-dragleave', this);
  786. node.removeEventListener('p-dragover', this);
  787. node.removeEventListener('p-drop', this);
  788. document.removeEventListener('mousemove', this, true);
  789. document.removeEventListener('mouseup', this, true);
  790. }
  791. /**
  792. * Handle `'activate-request'` messages.
  793. */
  794. protected onActivateRequest(msg: Message): void {
  795. this._ensureFocus(true);
  796. }
  797. /**
  798. * Handle `update-request` messages sent to the widget.
  799. */
  800. protected onUpdateRequest(msg: Message): void {
  801. let activeCell = this.activeCell;
  802. // Set the appropriate classes on the cells.
  803. if (this.mode === 'edit') {
  804. this.addClass(EDIT_CLASS);
  805. this.removeClass(COMMAND_CLASS);
  806. } else {
  807. this.addClass(COMMAND_CLASS);
  808. this.removeClass(EDIT_CLASS);
  809. }
  810. if (activeCell) {
  811. activeCell.addClass(ACTIVE_CLASS);
  812. }
  813. let count = 0;
  814. each(this.widgets, widget => {
  815. if (widget !== activeCell) {
  816. widget.removeClass(ACTIVE_CLASS);
  817. }
  818. widget.removeClass(OTHER_SELECTED_CLASS);
  819. if (this.isSelected(widget)) {
  820. widget.addClass(SELECTED_CLASS);
  821. count++;
  822. } else {
  823. widget.removeClass(SELECTED_CLASS);
  824. }
  825. });
  826. if (count > 1) {
  827. activeCell.addClass(OTHER_SELECTED_CLASS);
  828. }
  829. }
  830. /**
  831. * Handle a cell being inserted.
  832. */
  833. protected onCellInserted(index: number, cell: Cell): void {
  834. cell.editor.edgeRequested.connect(this._onEdgeRequest, this);
  835. // If the insertion happened above, increment the active cell
  836. // index, otherwise it stays the same.
  837. this.activeCellIndex = index <= this.activeCellIndex ?
  838. this.activeCellIndex + 1 : this.activeCellIndex ;
  839. }
  840. /**
  841. * Handle a cell being moved.
  842. */
  843. protected onCellMoved(fromIndex: number, toIndex: number): void {
  844. if (fromIndex === this.activeCellIndex) {
  845. this.activeCellIndex = toIndex;
  846. }
  847. }
  848. /**
  849. * Handle a cell being removed.
  850. */
  851. protected onCellRemoved(index: number, cell: Cell): void {
  852. // If the removal happened above, decrement the active
  853. // cell index, otherwise it stays the same.
  854. this.activeCellIndex = index <= this.activeCellIndex ?
  855. this.activeCellIndex - 1 : this.activeCellIndex ;
  856. if (this.isSelected(cell)) {
  857. this._selectionChanged.emit(void 0);
  858. }
  859. }
  860. /**
  861. * Handle a new model.
  862. */
  863. protected onModelChanged(oldValue: INotebookModel, newValue: INotebookModel): void {
  864. // Try to set the active cell index to 0.
  865. // It will be set to `-1` if there is no new model or the model is empty.
  866. this.activeCellIndex = 0;
  867. }
  868. /**
  869. * Handle edge request signals from cells.
  870. */
  871. private _onEdgeRequest(editor: CodeEditor.IEditor, location: CodeEditor.EdgeLocation): void {
  872. let prev = this.activeCellIndex;
  873. if (location === 'top') {
  874. this.activeCellIndex--;
  875. // Move the cursor to the first position on the last line.
  876. if (this.activeCellIndex < prev) {
  877. let editor = this.activeCell.editor;
  878. let lastLine = editor.lineCount - 1;
  879. editor.setCursorPosition({ line: lastLine, column: 0 });
  880. }
  881. } else {
  882. this.activeCellIndex++;
  883. // Move the cursor to the first character.
  884. if (this.activeCellIndex > prev) {
  885. let editor = this.activeCell.editor;
  886. editor.setCursorPosition({ line: 0, column: 0 });
  887. }
  888. }
  889. }
  890. /**
  891. * Ensure that the notebook has proper focus.
  892. */
  893. private _ensureFocus(force=false): void {
  894. let activeCell = this.activeCell;
  895. if (this.mode === 'edit' && activeCell) {
  896. activeCell.editor.focus();
  897. } else if (activeCell) {
  898. activeCell.editor.blur();
  899. }
  900. if (force && !this.node.contains(document.activeElement)) {
  901. this.node.focus();
  902. }
  903. }
  904. /**
  905. * Find the cell index containing the target html element.
  906. *
  907. * #### Notes
  908. * Returns -1 if the cell is not found.
  909. */
  910. private _findCell(node: HTMLElement): number {
  911. // Trace up the DOM hierarchy to find the root cell node.
  912. // Then find the corresponding child and select it.
  913. while (node && node !== this.node) {
  914. if (node.classList.contains(NB_CELL_CLASS)) {
  915. let i = ArrayExt.findFirstIndex(this.widgets, widget => widget.node === node);
  916. if (i !== -1) {
  917. return i;
  918. }
  919. break;
  920. }
  921. node = node.parentElement;
  922. }
  923. return -1;
  924. }
  925. /**
  926. * Handle `mousedown` events for the widget.
  927. */
  928. private _evtMouseDown(event: MouseEvent): void {
  929. let target = event.target as HTMLElement;
  930. let i = this._findCell(target);
  931. let shouldDrag = false;
  932. if (i !== -1) {
  933. let widget = this.widgets[i];
  934. // Event is on a cell but not in its editor, switch to command mode.
  935. if (!widget.editorWidget.node.contains(target)) {
  936. this.mode = 'command';
  937. shouldDrag = widget.promptNode.contains(target);
  938. }
  939. if (event.shiftKey) {
  940. shouldDrag = false;
  941. this._extendSelectionTo(i);
  942. // Prevent text select behavior.
  943. event.preventDefault();
  944. event.stopPropagation();
  945. } else {
  946. if (!this.isSelected(widget)) {
  947. this.deselectAll();
  948. }
  949. }
  950. // Set the cell as the active one.
  951. // This must be done *after* setting the mode above.
  952. this.activeCellIndex = i;
  953. }
  954. this._ensureFocus(true);
  955. // Left mouse press for drag start.
  956. if (event.button === 0 && shouldDrag) {
  957. this._dragData = { pressX: event.clientX, pressY: event.clientY, index: i};
  958. document.addEventListener('mouseup', this, true);
  959. document.addEventListener('mousemove', this, true);
  960. event.preventDefault();
  961. }
  962. }
  963. /**
  964. * Handle the `'mouseup'` event for the widget.
  965. */
  966. private _evtMouseup(event: MouseEvent): void {
  967. if (event.button !== 0 || !this._drag) {
  968. document.removeEventListener('mousemove', this, true);
  969. document.removeEventListener('mouseup', this, true);
  970. return;
  971. }
  972. event.preventDefault();
  973. event.stopPropagation();
  974. }
  975. /**
  976. * Handle the `'mousemove'` event for the widget.
  977. */
  978. private _evtMousemove(event: MouseEvent): void {
  979. event.preventDefault();
  980. event.stopPropagation();
  981. // Bail if we are the one dragging.
  982. if (this._drag) {
  983. return;
  984. }
  985. // Check for a drag initialization.
  986. let data = this._dragData;
  987. let dx = Math.abs(event.clientX - data.pressX);
  988. let dy = Math.abs(event.clientY - data.pressY);
  989. if (dx < DRAG_THRESHOLD && dy < DRAG_THRESHOLD) {
  990. return;
  991. }
  992. this._startDrag(data.index, event.clientX, event.clientY);
  993. }
  994. /**
  995. * Handle the `'p-dragenter'` event for the widget.
  996. */
  997. private _evtDragEnter(event: IDragEvent): void {
  998. if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
  999. return;
  1000. }
  1001. event.preventDefault();
  1002. event.stopPropagation();
  1003. let target = event.target as HTMLElement;
  1004. let index = this._findCell(target);
  1005. if (index === -1) {
  1006. return;
  1007. }
  1008. let widget = (this.layout as PanelLayout).widgets[index];
  1009. widget.node.classList.add(DROP_TARGET_CLASS);
  1010. }
  1011. /**
  1012. * Handle the `'p-dragleave'` event for the widget.
  1013. */
  1014. private _evtDragLeave(event: IDragEvent): void {
  1015. if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
  1016. return;
  1017. }
  1018. event.preventDefault();
  1019. event.stopPropagation();
  1020. let elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
  1021. if (elements.length) {
  1022. (elements[0] as HTMLElement).classList.remove(DROP_TARGET_CLASS);
  1023. }
  1024. }
  1025. /**
  1026. * Handle the `'p-dragover'` event for the widget.
  1027. */
  1028. private _evtDragOver(event: IDragEvent): void {
  1029. if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
  1030. return;
  1031. }
  1032. event.preventDefault();
  1033. event.stopPropagation();
  1034. event.dropAction = event.proposedAction;
  1035. let elements = this.node.getElementsByClassName(DROP_TARGET_CLASS);
  1036. if (elements.length) {
  1037. (elements[0] as HTMLElement).classList.remove(DROP_TARGET_CLASS);
  1038. }
  1039. let target = event.target as HTMLElement;
  1040. let index = this._findCell(target);
  1041. if (index === -1) {
  1042. return;
  1043. }
  1044. let widget = (this.layout as PanelLayout).widgets[index];
  1045. widget.node.classList.add(DROP_TARGET_CLASS);
  1046. }
  1047. /**
  1048. * Handle the `'p-drop'` event for the widget.
  1049. */
  1050. private _evtDrop(event: IDragEvent): void {
  1051. if (!event.mimeData.hasData(JUPYTER_CELL_MIME)) {
  1052. return;
  1053. }
  1054. event.preventDefault();
  1055. event.stopPropagation();
  1056. if (event.proposedAction === 'none') {
  1057. event.dropAction = 'none';
  1058. return;
  1059. }
  1060. let target = event.target as HTMLElement;
  1061. while (target && target.parentElement) {
  1062. if (target.classList.contains(DROP_TARGET_CLASS)) {
  1063. target.classList.remove(DROP_TARGET_CLASS);
  1064. break;
  1065. }
  1066. target = target.parentElement;
  1067. }
  1068. let source: Notebook = event.source;
  1069. if (source === this) {
  1070. // Handle the case where we are moving cells within
  1071. // the same notebook.
  1072. event.dropAction = 'move';
  1073. let toMove: Cell[] = event.mimeData.getData('internal:cells');
  1074. //Compute the to/from indices for the move.
  1075. let fromIndex = ArrayExt.firstIndexOf(this.widgets, toMove[0]);
  1076. let toIndex = this._findCell(target);
  1077. // This check is needed for consistency with the view.
  1078. if (toIndex !== -1 && toIndex > fromIndex) {
  1079. toIndex -= 1;
  1080. } else if (toIndex === -1) {
  1081. // If the drop is within the notebook but not on any cell,
  1082. // most often this means it is past the cell areas, so
  1083. // set it to move the cells to the end of the notebook.
  1084. toIndex = this.widgets.length - 1;
  1085. }
  1086. //Don't move if we are within the block of selected cells.
  1087. if (toIndex >= fromIndex && toIndex < fromIndex + toMove.length) {
  1088. return;
  1089. }
  1090. // Move the cells one by one
  1091. this.model.cells.beginCompoundOperation();
  1092. if (fromIndex < toIndex) {
  1093. each(toMove, (cellWidget)=> {
  1094. this.model.cells.move(fromIndex, toIndex);
  1095. });
  1096. } else if (fromIndex > toIndex) {
  1097. each(toMove, (cellWidget)=> {
  1098. this.model.cells.move(fromIndex++, toIndex++);
  1099. });
  1100. }
  1101. this.model.cells.endCompoundOperation();
  1102. } else {
  1103. // Handle the case where we are copying cells between
  1104. // notebooks.
  1105. event.dropAction = 'copy';
  1106. // Find the target cell and insert the copied cells.
  1107. let index = this._findCell(target);
  1108. if (index === -1) {
  1109. index = this.widgets.length;
  1110. }
  1111. let model = this.model;
  1112. let values = event.mimeData.getData(JUPYTER_CELL_MIME);
  1113. let factory = model.contentFactory;
  1114. // Insert the copies of the original cells.
  1115. model.cells.beginCompoundOperation();
  1116. each(values, (cell: nbformat.ICell) => {
  1117. let value: ICellModel;
  1118. switch (cell.cell_type) {
  1119. case 'code':
  1120. value = factory.createCodeCell({ cell });
  1121. break;
  1122. case 'markdown':
  1123. value = factory.createMarkdownCell({ cell });
  1124. break;
  1125. default:
  1126. value = factory.createRawCell({ cell });
  1127. break;
  1128. }
  1129. model.cells.insert(index++, value);
  1130. });
  1131. model.cells.endCompoundOperation();
  1132. // Activate the last cell.
  1133. this.activeCellIndex = index - 1;
  1134. }
  1135. }
  1136. /**
  1137. * Start a drag event.
  1138. */
  1139. private _startDrag(index: number, clientX: number, clientY: number): void {
  1140. let cells = this.model.cells;
  1141. let selected: nbformat.ICell[] = [];
  1142. let toMove: Cell[] = [];
  1143. each(this.widgets, (widget, i) => {
  1144. let cell = cells.at(i);
  1145. if (this.isSelected(widget)) {
  1146. widget.addClass(DROP_SOURCE_CLASS);
  1147. selected.push(cell.toJSON());
  1148. toMove.push(widget);
  1149. }
  1150. });
  1151. // Create the drag image.
  1152. let dragImage = Private.createDragImage(selected.length);
  1153. // Set up the drag event.
  1154. this._drag = new Drag({
  1155. mimeData: new MimeData(),
  1156. dragImage,
  1157. supportedActions: 'copy-move',
  1158. proposedAction: 'copy',
  1159. source: this
  1160. });
  1161. this._drag.mimeData.setData(JUPYTER_CELL_MIME, selected);
  1162. // Add mimeData for the fully reified cell widgets, for the
  1163. // case where the target is in the same notebook and we
  1164. // can just move the cells.
  1165. this._drag.mimeData.setData('internal:cells', toMove);
  1166. // Remove mousemove and mouseup listeners and start the drag.
  1167. document.removeEventListener('mousemove', this, true);
  1168. document.removeEventListener('mouseup', this, true);
  1169. this._drag.start(clientX, clientY).then(action => {
  1170. if (this.isDisposed) {
  1171. return;
  1172. }
  1173. this._drag = null;
  1174. each(toMove, widget => { widget.removeClass(DROP_SOURCE_CLASS); });
  1175. });
  1176. }
  1177. /**
  1178. * Handle `focus` events for the widget.
  1179. */
  1180. private _evtFocus(event: MouseEvent): void {
  1181. let target = event.target as HTMLElement;
  1182. let i = this._findCell(target);
  1183. if (i !== -1) {
  1184. let widget = this.widgets[i];
  1185. // If the editor itself does not have focus, ensure command mode.
  1186. if (!widget.editorWidget.node.contains(target)) {
  1187. this.mode = 'command';
  1188. }
  1189. this.activeCellIndex = i;
  1190. // If the editor has focus, ensure edit mode.
  1191. let node = widget.editorWidget.node;
  1192. if (node.contains(target)) {
  1193. this.mode = 'edit';
  1194. this.scrollToPosition(node.getBoundingClientRect().top);
  1195. }
  1196. } else {
  1197. // No cell has focus, ensure command mode.
  1198. this.mode = 'command';
  1199. }
  1200. }
  1201. /**
  1202. * Handle `blur` events for the notebook.
  1203. */
  1204. private _evtBlur(event: MouseEvent): void {
  1205. let relatedTarget = event.relatedTarget as HTMLElement;
  1206. // Bail if focus is leaving the notebook.
  1207. if (!this.node.contains(relatedTarget)) {
  1208. return;
  1209. }
  1210. this.mode = 'command';
  1211. }
  1212. /**
  1213. * Handle `dblclick` events for the widget.
  1214. */
  1215. private _evtDblClick(event: MouseEvent): void {
  1216. let model = this.model;
  1217. if (!model || model.readOnly) {
  1218. return;
  1219. }
  1220. let target = event.target as HTMLElement;
  1221. let i = this._findCell(target);
  1222. if (i === -1) {
  1223. return;
  1224. }
  1225. this.activeCellIndex = i;
  1226. if (model.cells.at(i).type === 'markdown') {
  1227. let widget = this.widgets[i] as MarkdownCell;
  1228. widget.rendered = false;
  1229. } else if (target.localName === 'img') {
  1230. target.classList.toggle(UNCONFINED_CLASS);
  1231. }
  1232. }
  1233. /**
  1234. * Extend the selection to a given index.
  1235. */
  1236. private _extendSelectionTo(index: number): void {
  1237. let activeIndex = this.activeCellIndex;
  1238. let j = index;
  1239. // extend the existing selection.
  1240. if (j > activeIndex) {
  1241. while (j > activeIndex) {
  1242. Private.selectedProperty.set(this.widgets[j], true);
  1243. j--;
  1244. }
  1245. } else if (j < activeIndex) {
  1246. while (j < activeIndex) {
  1247. Private.selectedProperty.set(this.widgets[j], true);
  1248. j++;
  1249. }
  1250. }
  1251. Private.selectedProperty.set(this.widgets[activeIndex], true);
  1252. this._selectionChanged.emit(void 0);
  1253. }
  1254. private _activeCellIndex = -1;
  1255. private _activeCell: Cell = null;
  1256. private _mode: NotebookMode = 'command';
  1257. private _drag: Drag = null;
  1258. private _dragData: { pressX: number, pressY: number, index: number } = null;
  1259. private _activeCellChanged = new Signal<this, Cell>(this);
  1260. private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
  1261. private _selectionChanged = new Signal<this, void>(this);
  1262. }
  1263. /**
  1264. * The namespace for the `Notebook` class statics.
  1265. */
  1266. export
  1267. namespace Notebook {
  1268. /**
  1269. * An options object for initializing a notebook widget.
  1270. */
  1271. export
  1272. interface IOptions extends StaticNotebook.IOptions { }
  1273. /**
  1274. * The content factory for the notebook widget.
  1275. */
  1276. export
  1277. interface IContentFactory extends StaticNotebook.IContentFactory { }
  1278. /**
  1279. * The default implementation of a notebook content factory..
  1280. *
  1281. * #### Notes
  1282. * Override methods on this class to customize the default notebook factory
  1283. * methods that create notebook content.
  1284. */
  1285. export
  1286. class ContentFactory extends StaticNotebook.ContentFactory { }
  1287. /**
  1288. * An options object for initializing a notebook content factory.
  1289. */
  1290. export
  1291. interface IContentFactoryOptions extends StaticNotebook.IContentFactoryOptions { }
  1292. export
  1293. const defaultContentFactory: IContentFactory = new ContentFactory();
  1294. }
  1295. /**
  1296. * A namespace for private data.
  1297. */
  1298. namespace Private {
  1299. /**
  1300. * An attached property for the selected state of a cell.
  1301. */
  1302. export
  1303. const selectedProperty = new AttachedProperty<Cell, boolean>({
  1304. name: 'selected',
  1305. create: () => false
  1306. });
  1307. /**
  1308. * A custom panel layout for the notebook.
  1309. */
  1310. export
  1311. class NotebookPanelLayout extends PanelLayout {
  1312. /**
  1313. * A message handler invoked on an `'update-request'` message.
  1314. *
  1315. * #### Notes
  1316. * This is a reimplementation of the base class method,
  1317. * and is a no-op.
  1318. */
  1319. protected onUpdateRequest(msg: Message): void {
  1320. // This is a no-op.
  1321. }
  1322. }
  1323. /**
  1324. * Create a cell drag image.
  1325. */
  1326. export
  1327. function createDragImage(count: number): HTMLElement {
  1328. let node = document.createElement('div');
  1329. let span = document.createElement('span');
  1330. span.textContent = `${count}`;
  1331. span.className = FILLED_CIRCLE_CLASS;
  1332. node.appendChild(span);
  1333. node.className = DRAG_IMAGE_CLASS;
  1334. return node;
  1335. }
  1336. /**
  1337. * Process the `IOptions` passed to the notebook widget.
  1338. *
  1339. * #### Notes
  1340. * This defaults the content factory to that in the `Notebook` namespace.
  1341. */
  1342. export
  1343. function processNotebookOptions(options: Notebook.IOptions) {
  1344. if (options.contentFactory) {
  1345. return options;
  1346. } else {
  1347. return {
  1348. rendermime: options.rendermime,
  1349. languagePreference: options.languagePreference,
  1350. contentFactory: Notebook.defaultContentFactory,
  1351. mimeTypeService: options.mimeTypeService
  1352. }
  1353. }
  1354. }
  1355. }