UNPKG

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