UNPKG

12.4 kBJavaScriptView Raw
1/*
2 * Copyright (c) Jupyter Development Team.
3 * Distributed under the terms of the Modified BSD License.
4 */
5import { CodeCell } from '@jupyterlab/cells';
6import { WindowedLayout, WindowedListModel } from '@jupyterlab/ui-components';
7import { MessageLoop } from '@lumino/messaging';
8import { Widget } from '@lumino/widgets';
9import { DROP_SOURCE_CLASS, DROP_TARGET_CLASS } from './constants';
10/**
11 * Notebook view model for the windowed list.
12 */
13class NotebookViewModel extends WindowedListModel {
14 /**
15 * Construct a notebook windowed list model.
16 */
17 constructor(cells, options) {
18 super(options);
19 this.cells = cells;
20 /**
21 * Cell size estimator
22 *
23 * @param index Cell index
24 * @returns Cell height in pixels
25 */
26 this.estimateWidgetSize = (index) => {
27 // TODO could be improved, takes only into account the editor height
28 const nLines = this.cells[index].model.sharedModel
29 .getSource()
30 .split('\n').length;
31 return (NotebookViewModel.DEFAULT_EDITOR_LINE_HEIGHT * nLines +
32 NotebookViewModel.DEFAULT_CELL_MARGIN);
33 };
34 /**
35 * Render the cell at index.
36 *
37 * @param index Cell index
38 * @returns Cell widget
39 */
40 this.widgetRenderer = (index) => {
41 return this.cells[index];
42 };
43 // Set default cell size
44 this._estimatedWidgetSize = NotebookViewModel.DEFAULT_CELL_SIZE;
45 }
46}
47/**
48 * Default cell height
49 */
50NotebookViewModel.DEFAULT_CELL_SIZE = 39;
51/**
52 * Default editor line height
53 */
54NotebookViewModel.DEFAULT_EDITOR_LINE_HEIGHT = 17;
55/**
56 * Default cell margin (top + bottom)
57 */
58NotebookViewModel.DEFAULT_CELL_MARGIN = 22;
59export { NotebookViewModel };
60/**
61 * Windowed list layout for the notebook.
62 */
63export class NotebookWindowedLayout extends WindowedLayout {
64 constructor() {
65 super(...arguments);
66 this._header = null;
67 this._footer = null;
68 this._willBeRemoved = null;
69 this._topHiddenCodeCells = -1;
70 }
71 /**
72 * Notebook's header
73 */
74 get header() {
75 return this._header;
76 }
77 set header(header) {
78 var _a;
79 if (this._header && this._header.isAttached) {
80 Widget.detach(this._header);
81 }
82 this._header = header;
83 if (this._header && ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isAttached)) {
84 Widget.attach(this._header, this.parent.node);
85 }
86 }
87 /**
88 * Notebook widget's footer
89 */
90 get footer() {
91 return this._footer;
92 }
93 set footer(footer) {
94 var _a;
95 if (this._footer && this._footer.isAttached) {
96 Widget.detach(this._footer);
97 }
98 this._footer = footer;
99 if (this._footer && ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isAttached)) {
100 Widget.attach(this._footer, this.parent.node);
101 }
102 }
103 /**
104 * Dispose the layout
105 * */
106 dispose() {
107 var _a, _b;
108 if (this.isDisposed) {
109 return;
110 }
111 (_a = this._header) === null || _a === void 0 ? void 0 : _a.dispose();
112 (_b = this._footer) === null || _b === void 0 ? void 0 : _b.dispose();
113 super.dispose();
114 }
115 /**
116 * * A message handler invoked on a `'child-removed'` message.
117 * *
118 * @param widget - The widget to remove from the layout.
119 *
120 * #### Notes
121 * A widget is automatically removed from the layout when its `parent`
122 * is set to `null`. This method should only be invoked directly when
123 * removing a widget from a layout which has yet to be installed on a
124 * parent widget.
125 *
126 * This method does *not* modify the widget's `parent`.
127 */
128 removeWidget(widget) {
129 const index = this.widgets.indexOf(widget);
130 // We need to deal with code cell widget not in viewport (aka not in this.widgets) but still
131 // partly attached
132 if (index >= 0) {
133 this.removeWidgetAt(index);
134 } // If the layout is parented, detach the widget from the DOM.
135 else if (widget === this._willBeRemoved && this.parent) {
136 this.detachWidget(index, widget);
137 }
138 }
139 /**
140 * Attach a widget to the parent's DOM node.
141 *
142 * @param index - The current index of the widget in the layout.
143 *
144 * @param widget - The widget to attach to the parent.
145 *
146 * #### Notes
147 * This method is called automatically by the panel layout at the
148 * appropriate time. It should not be called directly by user code.
149 *
150 * The default implementation adds the widgets's node to the parent's
151 * node at the proper location, and sends the appropriate attach
152 * messages to the widget if the parent is attached to the DOM.
153 *
154 * Subclasses may reimplement this method to control how the widget's
155 * node is added to the parent's node.
156 */
157 attachWidget(index, widget) {
158 // Status may change in onBeforeAttach
159 const wasPlaceholder = widget.isPlaceholder();
160 // Initialized sub-widgets or attached them for CodeCell
161 if (this.parent.isAttached) {
162 MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
163 }
164 if (!wasPlaceholder &&
165 widget instanceof CodeCell &&
166 widget.node.parentElement) {
167 // We don't remove code cells to preserve outputs internal state
168 widget.node.style.display = '';
169 // Reset cache
170 this._topHiddenCodeCells = -1;
171 }
172 else {
173 // Look up the next sibling reference node.
174 const siblingIndex = this._findNearestChildBinarySearch(this.parent.viewportNode.childElementCount - 1, 0, parseInt(widget.dataset.windowedListIndex, 10) + 1);
175 let ref = this.parent.viewportNode.children[siblingIndex];
176 // Insert the widget's node before the sibling.
177 this.parent.viewportNode.insertBefore(widget.node, ref);
178 // Send an `'after-attach'` message if the parent is attached.
179 // Event listeners will be added here
180 // Some widgets are updating/resetting when attached, so
181 // we should not recall this each time a cell move into the
182 // viewport.
183 if (this.parent.isAttached) {
184 MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
185 }
186 }
187 widget.inViewport = true;
188 }
189 /**
190 * Detach a widget from the parent's DOM node.
191 *
192 * @param index - The previous index of the widget in the layout.
193 *
194 * @param widget - The widget to detach from the parent.
195 *
196 * #### Notes
197 * This method is called automatically by the panel layout at the
198 * appropriate time. It should not be called directly by user code.
199 *
200 * The default implementation removes the widget's node from the
201 * parent's node, and sends the appropriate detach messages to the
202 * widget if the parent is attached to the DOM.
203 *
204 * Subclasses may reimplement this method to control how the widget's
205 * node is removed from the parent's node.
206 */
207 detachWidget(index, widget) {
208 widget.inViewport = false;
209 // TODO we could improve this further by discarding also the code cell without outputs
210 if (widget instanceof CodeCell &&
211 // We detach the code cell currently dragged otherwise it won't be attached at the correct position
212 !widget.node.classList.contains(DROP_SOURCE_CLASS) &&
213 widget !== this._willBeRemoved) {
214 // We don't remove code cells to preserve outputs internal state
215 // Transform does not work because the widget height is kept (at lease in FF)
216 widget.node.style.display = 'none';
217 // Reset cache
218 this._topHiddenCodeCells = -1;
219 }
220 else {
221 // Send a `'before-detach'` message if the parent is attached.
222 // This should not be called every time a cell leaves the viewport
223 // as it will remove listeners that won't be added back as afterAttach
224 // is shunted to avoid unwanted update/reset.
225 if (this.parent.isAttached) {
226 // Event listeners will be removed here
227 MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach);
228 }
229 // Remove the widget's node from the parent.
230 this.parent.viewportNode.removeChild(widget.node);
231 // Ensure to clean up drop target class if the widget move out of the viewport
232 widget.node.classList.remove(DROP_TARGET_CLASS);
233 }
234 if (this.parent.isAttached) {
235 // Detach sub widget of CodeCell except the OutputAreaWrapper
236 MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach);
237 }
238 }
239 /**
240 * Move a widget in the parent's DOM node.
241 *
242 * @param fromIndex - The previous index of the widget in the layout.
243 *
244 * @param toIndex - The current index of the widget in the layout.
245 *
246 * @param widget - The widget to move in the parent.
247 *
248 * #### Notes
249 * This method is called automatically by the panel layout at the
250 * appropriate time. It should not be called directly by user code.
251 *
252 * The default implementation moves the widget's node to the proper
253 * location in the parent's node and sends the appropriate attach and
254 * detach messages to the widget if the parent is attached to the DOM.
255 *
256 * Subclasses may reimplement this method to control how the widget's
257 * node is moved in the parent's node.
258 */
259 moveWidget(fromIndex, toIndex, widget) {
260 // Optimize move without de-/attaching as motion appends with parent attached
261 // Case fromIndex === toIndex, already checked in PanelLayout.insertWidget
262 if (this._topHiddenCodeCells < 0) {
263 this._topHiddenCodeCells = 0;
264 for (let idx = 0; idx < this.parent.viewportNode.children.length; idx++) {
265 const n = this.parent.viewportNode.children[idx];
266 if (n.style.display == 'none') {
267 this._topHiddenCodeCells++;
268 }
269 else {
270 break;
271 }
272 }
273 }
274 const ref = this.parent.viewportNode.children[toIndex + this._topHiddenCodeCells];
275 if (fromIndex < toIndex) {
276 ref.insertAdjacentElement('afterend', widget.node);
277 }
278 else {
279 ref.insertAdjacentElement('beforebegin', widget.node);
280 }
281 }
282 onAfterAttach(msg) {
283 super.onAfterAttach(msg);
284 if (this._header && !this._header.isAttached) {
285 Widget.attach(this._header, this.parent.node, this.parent.node.firstElementChild);
286 }
287 if (this._footer && !this._footer.isAttached) {
288 Widget.attach(this._footer, this.parent.node);
289 }
290 }
291 onBeforeDetach(msg) {
292 var _a, _b;
293 if ((_a = this._header) === null || _a === void 0 ? void 0 : _a.isAttached) {
294 Widget.detach(this._header);
295 }
296 if ((_b = this._footer) === null || _b === void 0 ? void 0 : _b.isAttached) {
297 Widget.detach(this._footer);
298 }
299 super.onBeforeDetach(msg);
300 }
301 /**
302 * A message handler invoked on a `'child-removed'` message.
303 *
304 * @param msg Message
305 */
306 onChildRemoved(msg) {
307 this._willBeRemoved = msg.child;
308 super.onChildRemoved(msg);
309 this._willBeRemoved = null;
310 }
311 _findNearestChildBinarySearch(high, low, index) {
312 while (low <= high) {
313 const middle = low + Math.floor((high - low) / 2);
314 const currentIndex = parseInt(this.parent.viewportNode.children[middle].dataset
315 .windowedListIndex, 10);
316 if (currentIndex === index) {
317 return middle;
318 }
319 else if (currentIndex < index) {
320 low = middle + 1;
321 }
322 else if (currentIndex > index) {
323 high = middle - 1;
324 }
325 }
326 if (low > 0) {
327 return low;
328 }
329 else {
330 return 0;
331 }
332 }
333}
334//# sourceMappingURL=windowing.js.map
\No newline at end of file