celldragutils.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /* -----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. /**
  6. * This module contains some utility functions to operate on cells. This
  7. * could be shared by widgets that contain cells, like the CodeConsole or
  8. * Notebook widgets.
  9. */
  10. import { each, IterableOrArrayLike } from '@lumino/algorithm';
  11. import { ICodeCellModel } from './model';
  12. import { Cell } from './widget';
  13. import { h, VirtualDOM } from '@lumino/virtualdom';
  14. import * as nbformat from '@jupyterlab/nbformat';
  15. /**
  16. * Constants for drag
  17. */
  18. /**
  19. * The threshold in pixels to start a drag event.
  20. */
  21. const DRAG_THRESHOLD = 5;
  22. /**
  23. * The class name added to drag images.
  24. */
  25. const DRAG_IMAGE_CLASS = 'jp-dragImage';
  26. /**
  27. * The class name added to singular drag images
  28. */
  29. const SINGLE_DRAG_IMAGE_CLASS = 'jp-dragImage-singlePrompt';
  30. /**
  31. * The class name added to the drag image cell content.
  32. */
  33. const CELL_DRAG_CONTENT_CLASS = 'jp-dragImage-content';
  34. /**
  35. * The class name added to the drag image cell content.
  36. */
  37. const CELL_DRAG_PROMPT_CLASS = 'jp-dragImage-prompt';
  38. /**
  39. * The class name added to the drag image cell content.
  40. */
  41. const CELL_DRAG_MULTIPLE_BACK = 'jp-dragImage-multipleBack';
  42. export namespace CellDragUtils {
  43. export type ICellTargetArea = 'input' | 'prompt' | 'cell' | 'unknown';
  44. /**
  45. * Find the cell index containing the target html element.
  46. * This function traces up the DOM hierarchy to find the root cell
  47. * node. Then find the corresponding child and select it.
  48. *
  49. * @param node - the cell node or a child of the cell node.
  50. * @param cells - an iterable of Cells
  51. * @param isCellNode - a function that takes in a node and checks if
  52. * it is a cell node.
  53. *
  54. * @returns index of the cell we're looking for. Returns -1 if
  55. * the cell is not founds
  56. */
  57. export function findCell(
  58. node: HTMLElement,
  59. cells: IterableOrArrayLike<Cell>,
  60. isCellNode: (node: HTMLElement) => boolean
  61. ): number {
  62. let cellIndex: number = -1;
  63. while (node && node.parentElement) {
  64. if (isCellNode(node)) {
  65. each(cells, (cell, index) => {
  66. if (cell.node === node) {
  67. cellIndex = index;
  68. return false;
  69. }
  70. });
  71. break;
  72. }
  73. node = node.parentElement;
  74. }
  75. return cellIndex;
  76. }
  77. /**
  78. * Detect which part of the cell triggered the MouseEvent
  79. *
  80. * @param cell - The cell which contains the MouseEvent's target
  81. * @param target - The DOM node which triggered the MouseEvent
  82. */
  83. export function detectTargetArea(
  84. cell: Cell,
  85. target: HTMLElement
  86. ): ICellTargetArea {
  87. let targetArea: ICellTargetArea;
  88. if (cell) {
  89. if (cell.editorWidget.node.contains(target)) {
  90. targetArea = 'input';
  91. } else if (cell.promptNode.contains(target)) {
  92. targetArea = 'prompt';
  93. } else {
  94. targetArea = 'cell';
  95. }
  96. } else {
  97. targetArea = 'unknown';
  98. }
  99. return targetArea;
  100. }
  101. /**
  102. * Detect if a drag event should be started. This is down if the
  103. * mouse is moved beyond a certain distance (DRAG_THRESHOLD).
  104. *
  105. * @param prevX - X Coordinate of the mouse pointer during the mousedown event
  106. * @param prevY - Y Coordinate of the mouse pointer during the mousedown event
  107. * @param nextX - Current X Coordinate of the mouse pointer
  108. * @param nextY - Current Y Coordinate of the mouse pointer
  109. */
  110. export function shouldStartDrag(
  111. prevX: number,
  112. prevY: number,
  113. nextX: number,
  114. nextY: number
  115. ): boolean {
  116. const dx = Math.abs(nextX - prevX);
  117. const dy = Math.abs(nextY - prevY);
  118. return dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD;
  119. }
  120. /**
  121. * Create an image for the cell(s) to be dragged
  122. *
  123. * @param activeCell - The cell from where the drag event is triggered
  124. * @param selectedCells - The cells to be dragged
  125. */
  126. export function createCellDragImage(
  127. activeCell: Cell,
  128. selectedCells: nbformat.ICell[]
  129. ): HTMLElement {
  130. const count = selectedCells.length;
  131. let promptNumber: string;
  132. if (activeCell.model.type === 'code') {
  133. const executionCount = (activeCell.model as ICodeCellModel)
  134. .executionCount;
  135. promptNumber = ' ';
  136. if (executionCount) {
  137. promptNumber = executionCount.toString();
  138. }
  139. } else {
  140. promptNumber = '';
  141. }
  142. const cellContent = activeCell.model.value.text.split('\n')[0].slice(0, 26);
  143. if (count > 1) {
  144. if (promptNumber !== '') {
  145. return VirtualDOM.realize(
  146. h.div(
  147. h.div(
  148. { className: DRAG_IMAGE_CLASS },
  149. h.span(
  150. { className: CELL_DRAG_PROMPT_CLASS },
  151. '[' + promptNumber + ']:'
  152. ),
  153. h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)
  154. ),
  155. h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')
  156. )
  157. );
  158. } else {
  159. return VirtualDOM.realize(
  160. h.div(
  161. h.div(
  162. { className: DRAG_IMAGE_CLASS },
  163. h.span({ className: CELL_DRAG_PROMPT_CLASS }),
  164. h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)
  165. ),
  166. h.div({ className: CELL_DRAG_MULTIPLE_BACK }, '')
  167. )
  168. );
  169. }
  170. } else {
  171. if (promptNumber !== '') {
  172. return VirtualDOM.realize(
  173. h.div(
  174. h.div(
  175. { className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` },
  176. h.span(
  177. { className: CELL_DRAG_PROMPT_CLASS },
  178. '[' + promptNumber + ']:'
  179. ),
  180. h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)
  181. )
  182. )
  183. );
  184. } else {
  185. return VirtualDOM.realize(
  186. h.div(
  187. h.div(
  188. { className: `${DRAG_IMAGE_CLASS} ${SINGLE_DRAG_IMAGE_CLASS}` },
  189. h.span({ className: CELL_DRAG_PROMPT_CLASS }),
  190. h.span({ className: CELL_DRAG_CONTENT_CLASS }, cellContent)
  191. )
  192. )
  193. );
  194. }
  195. }
  196. }
  197. }