1 | /*
|
2 | * Copyright (c) Jupyter Development Team.
|
3 | * Distributed under the terms of the Modified BSD License.
|
4 | */
|
5 | import { CodeCell } from '@jupyterlab/cells';
|
6 | import { WindowedLayout, WindowedListModel } from '@jupyterlab/ui-components';
|
7 | import { MessageLoop } from '@lumino/messaging';
|
8 | import { Widget } from '@lumino/widgets';
|
9 | import { DROP_SOURCE_CLASS, DROP_TARGET_CLASS } from './constants';
|
10 | /**
|
11 | * Notebook view model for the windowed list.
|
12 | */
|
13 | class 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 | */
|
50 | NotebookViewModel.DEFAULT_CELL_SIZE = 39;
|
51 | /**
|
52 | * Default editor line height
|
53 | */
|
54 | NotebookViewModel.DEFAULT_EDITOR_LINE_HEIGHT = 17;
|
55 | /**
|
56 | * Default cell margin (top + bottom)
|
57 | */
|
58 | NotebookViewModel.DEFAULT_CELL_MARGIN = 22;
|
59 | export { NotebookViewModel };
|
60 | /**
|
61 | * Windowed list layout for the notebook.
|
62 | */
|
63 | export 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 |