1 |
|
2 |
|
3 | import { Collapse, Styling } from '@jupyterlab/apputils';
|
4 | import { CodeEditor, CodeEditorWrapper, JSONEditor } from '@jupyterlab/codeeditor';
|
5 | import { ObservableJSON } from '@jupyterlab/observables';
|
6 | import { nullTranslator } from '@jupyterlab/translation';
|
7 | import { ArrayExt, chain, each } from '@lumino/algorithm';
|
8 | import { ConflatableMessage, MessageLoop } from '@lumino/messaging';
|
9 | import { h, VirtualDOM } from '@lumino/virtualdom';
|
10 | import { PanelLayout, Widget } from '@lumino/widgets';
|
11 | class RankedPanel extends Widget {
|
12 | constructor() {
|
13 | super();
|
14 | this._items = [];
|
15 | this.layout = new PanelLayout();
|
16 | this.addClass('jp-RankedPanel');
|
17 | }
|
18 | addWidget(widget, rank) {
|
19 | const rankItem = { widget, rank };
|
20 | const index = ArrayExt.upperBound(this._items, rankItem, Private.itemCmp);
|
21 | ArrayExt.insert(this._items, index, rankItem);
|
22 | const layout = this.layout;
|
23 | layout.insertWidget(index, widget);
|
24 | }
|
25 | |
26 |
|
27 |
|
28 |
|
29 | onChildRemoved(msg) {
|
30 | const index = ArrayExt.findFirstIndex(this._items, item => item.widget === msg.child);
|
31 | if (index !== -1) {
|
32 | ArrayExt.removeAt(this._items, index);
|
33 | }
|
34 | }
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 | export class NotebookTools extends Widget {
|
40 | |
41 |
|
42 |
|
43 | constructor(options) {
|
44 | super();
|
45 | this.addClass('jp-NotebookTools');
|
46 | this.translator = options.translator || nullTranslator;
|
47 | this._trans = this.translator.load('jupyterlab');
|
48 | this._commonTools = new RankedPanel();
|
49 | this._advancedTools = new RankedPanel();
|
50 | this._advancedTools.title.label = this._trans.__('Advanced Tools');
|
51 | const layout = (this.layout = new PanelLayout());
|
52 | layout.addWidget(this._commonTools);
|
53 | layout.addWidget(new Collapse({ widget: this._advancedTools }));
|
54 | this._tracker = options.tracker;
|
55 | this._tracker.currentChanged.connect(this._onActiveNotebookPanelChanged, this);
|
56 | this._tracker.activeCellChanged.connect(this._onActiveCellChanged, this);
|
57 | this._tracker.selectionChanged.connect(this._onSelectionChanged, this);
|
58 | this._onActiveNotebookPanelChanged();
|
59 | this._onActiveCellChanged();
|
60 | this._onSelectionChanged();
|
61 | }
|
62 | |
63 |
|
64 |
|
65 | get activeCell() {
|
66 | return this._tracker.activeCell;
|
67 | }
|
68 | |
69 |
|
70 |
|
71 | get selectedCells() {
|
72 | const panel = this._tracker.currentWidget;
|
73 | if (!panel) {
|
74 | return [];
|
75 | }
|
76 | const notebook = panel.content;
|
77 | return notebook.widgets.filter(cell => notebook.isSelectedOrActive(cell));
|
78 | }
|
79 | |
80 |
|
81 |
|
82 | get activeNotebookPanel() {
|
83 | return this._tracker.currentWidget;
|
84 | }
|
85 | |
86 |
|
87 |
|
88 | addItem(options) {
|
89 | var _a;
|
90 | const tool = options.tool;
|
91 | const rank = (_a = options.rank) !== null && _a !== void 0 ? _a : 100;
|
92 | let section;
|
93 | if (options.section === 'advanced') {
|
94 | section = this._advancedTools;
|
95 | }
|
96 | else {
|
97 | section = this._commonTools;
|
98 | }
|
99 | tool.addClass('jp-NotebookTools-tool');
|
100 | section.addWidget(tool, rank);
|
101 |
|
102 |
|
103 | tool.notebookTools = this;
|
104 |
|
105 | MessageLoop.sendMessage(tool, NotebookTools.ActiveNotebookPanelMessage);
|
106 | MessageLoop.sendMessage(tool, NotebookTools.ActiveCellMessage);
|
107 | }
|
108 | |
109 |
|
110 |
|
111 | _onActiveNotebookPanelChanged() {
|
112 | if (this._prevActiveNotebookModel &&
|
113 | !this._prevActiveNotebookModel.isDisposed) {
|
114 | this._prevActiveNotebookModel.metadata.changed.disconnect(this._onActiveNotebookPanelMetadataChanged, this);
|
115 | }
|
116 | const activeNBModel = this.activeNotebookPanel && this.activeNotebookPanel.content
|
117 | ? this.activeNotebookPanel.content.model
|
118 | : null;
|
119 | this._prevActiveNotebookModel = activeNBModel;
|
120 | if (activeNBModel) {
|
121 | activeNBModel.metadata.changed.connect(this._onActiveNotebookPanelMetadataChanged, this);
|
122 | }
|
123 | each(this._toolChildren(), widget => {
|
124 | MessageLoop.sendMessage(widget, NotebookTools.ActiveNotebookPanelMessage);
|
125 | });
|
126 | }
|
127 | |
128 |
|
129 |
|
130 | _onActiveCellChanged() {
|
131 | if (this._prevActiveCell && !this._prevActiveCell.isDisposed) {
|
132 | this._prevActiveCell.metadata.changed.disconnect(this._onActiveCellMetadataChanged, this);
|
133 | }
|
134 | const activeCell = this.activeCell ? this.activeCell.model : null;
|
135 | this._prevActiveCell = activeCell;
|
136 | if (activeCell) {
|
137 | activeCell.metadata.changed.connect(this._onActiveCellMetadataChanged, this);
|
138 | }
|
139 | each(this._toolChildren(), widget => {
|
140 | MessageLoop.sendMessage(widget, NotebookTools.ActiveCellMessage);
|
141 | });
|
142 | }
|
143 | |
144 |
|
145 |
|
146 | _onSelectionChanged() {
|
147 | each(this._toolChildren(), widget => {
|
148 | MessageLoop.sendMessage(widget, NotebookTools.SelectionMessage);
|
149 | });
|
150 | }
|
151 | |
152 |
|
153 |
|
154 | _onActiveNotebookPanelMetadataChanged(sender, args) {
|
155 | const message = new ObservableJSON.ChangeMessage('activenotebookpanel-metadata-changed', args);
|
156 | each(this._toolChildren(), widget => {
|
157 | MessageLoop.sendMessage(widget, message);
|
158 | });
|
159 | }
|
160 | |
161 |
|
162 |
|
163 | _onActiveCellMetadataChanged(sender, args) {
|
164 | const message = new ObservableJSON.ChangeMessage('activecell-metadata-changed', args);
|
165 | each(this._toolChildren(), widget => {
|
166 | MessageLoop.sendMessage(widget, message);
|
167 | });
|
168 | }
|
169 | _toolChildren() {
|
170 | return chain(this._commonTools.children(), this._advancedTools.children());
|
171 | }
|
172 | }
|
173 |
|
174 |
|
175 |
|
176 | (function (NotebookTools) {
|
177 | |
178 |
|
179 |
|
180 | NotebookTools.ActiveNotebookPanelMessage = new ConflatableMessage('activenotebookpanel-changed');
|
181 | |
182 |
|
183 |
|
184 | NotebookTools.ActiveCellMessage = new ConflatableMessage('activecell-changed');
|
185 | |
186 |
|
187 |
|
188 | NotebookTools.SelectionMessage = new ConflatableMessage('selection-changed');
|
189 | |
190 |
|
191 |
|
192 | class Tool extends Widget {
|
193 | dispose() {
|
194 | super.dispose();
|
195 | if (this.notebookTools) {
|
196 | this.notebookTools = null;
|
197 | }
|
198 | }
|
199 | |
200 |
|
201 |
|
202 |
|
203 |
|
204 | processMessage(msg) {
|
205 | super.processMessage(msg);
|
206 | switch (msg.type) {
|
207 | case 'activenotebookpanel-changed':
|
208 | this.onActiveNotebookPanelChanged(msg);
|
209 | break;
|
210 | case 'activecell-changed':
|
211 | this.onActiveCellChanged(msg);
|
212 | break;
|
213 | case 'selection-changed':
|
214 | this.onSelectionChanged(msg);
|
215 | break;
|
216 | case 'activecell-metadata-changed':
|
217 | this.onActiveCellMetadataChanged(msg);
|
218 | break;
|
219 | case 'activenotebookpanel-metadata-changed':
|
220 | this.onActiveNotebookPanelMetadataChanged(msg);
|
221 | break;
|
222 | default:
|
223 | break;
|
224 | }
|
225 | }
|
226 | |
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | onActiveNotebookPanelChanged(msg) {
|
233 |
|
234 | }
|
235 | |
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | onActiveCellChanged(msg) {
|
242 |
|
243 | }
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | onSelectionChanged(msg) {
|
251 |
|
252 | }
|
253 | |
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | onActiveCellMetadataChanged(msg) {
|
260 |
|
261 | }
|
262 | |
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | onActiveNotebookPanelMetadataChanged(msg) {
|
269 |
|
270 | }
|
271 | }
|
272 | NotebookTools.Tool = Tool;
|
273 | |
274 |
|
275 |
|
276 | class ActiveCellTool extends Tool {
|
277 | |
278 |
|
279 |
|
280 | constructor() {
|
281 | super();
|
282 | this._model = new CodeEditor.Model();
|
283 | this.addClass('jp-ActiveCellTool');
|
284 | this.addClass('jp-InputArea');
|
285 | this.layout = new PanelLayout();
|
286 | }
|
287 | |
288 |
|
289 |
|
290 | dispose() {
|
291 | if (this._model === null) {
|
292 | return;
|
293 | }
|
294 | this._model.dispose();
|
295 | this._model = null;
|
296 | super.dispose();
|
297 | }
|
298 | |
299 |
|
300 |
|
301 | onActiveCellChanged() {
|
302 | const activeCell = this.notebookTools.activeCell;
|
303 | const layout = this.layout;
|
304 | const count = layout.widgets.length;
|
305 | for (let i = 0; i < count; i++) {
|
306 | layout.widgets[0].dispose();
|
307 | }
|
308 | if (this._cellModel && !this._cellModel.isDisposed) {
|
309 | this._cellModel.value.changed.disconnect(this._onValueChanged, this);
|
310 | this._cellModel.mimeTypeChanged.disconnect(this._onMimeTypeChanged, this);
|
311 | }
|
312 | if (!activeCell) {
|
313 | const cell = new Widget();
|
314 | cell.addClass('jp-InputArea-editor');
|
315 | cell.addClass('jp-InputArea-editor');
|
316 | layout.addWidget(cell);
|
317 | this._cellModel = null;
|
318 | return;
|
319 | }
|
320 | const promptNode = activeCell.promptNode
|
321 | ? activeCell.promptNode.cloneNode(true)
|
322 | : undefined;
|
323 | const prompt = new Widget({ node: promptNode });
|
324 | const factory = activeCell.contentFactory.editorFactory;
|
325 | const cellModel = (this._cellModel = activeCell.model);
|
326 | cellModel.value.changed.connect(this._onValueChanged, this);
|
327 | cellModel.mimeTypeChanged.connect(this._onMimeTypeChanged, this);
|
328 | this._model.value.text = cellModel.value.text.split('\n')[0];
|
329 | this._model.mimeType = cellModel.mimeType;
|
330 | const model = this._model;
|
331 | const editorWidget = new CodeEditorWrapper({ model, factory });
|
332 | editorWidget.addClass('jp-InputArea-editor');
|
333 | editorWidget.addClass('jp-InputArea-editor');
|
334 | editorWidget.editor.setOption('readOnly', true);
|
335 | layout.addWidget(prompt);
|
336 | layout.addWidget(editorWidget);
|
337 | }
|
338 | |
339 |
|
340 |
|
341 | _onValueChanged() {
|
342 | this._model.value.text = this._cellModel.value.text.split('\n')[0];
|
343 | }
|
344 | |
345 |
|
346 |
|
347 | _onMimeTypeChanged() {
|
348 | this._model.mimeType = this._cellModel.mimeType;
|
349 | }
|
350 | }
|
351 | NotebookTools.ActiveCellTool = ActiveCellTool;
|
352 | |
353 |
|
354 |
|
355 | class MetadataEditorTool extends Tool {
|
356 | |
357 |
|
358 |
|
359 | constructor(options) {
|
360 | super();
|
361 | const { editorFactory } = options;
|
362 | this.addClass('jp-MetadataEditorTool');
|
363 | const layout = (this.layout = new PanelLayout());
|
364 | this.editor = new JSONEditor({
|
365 | editorFactory
|
366 | });
|
367 | this.editor.title.label = options.label || 'Edit Metadata';
|
368 | const titleNode = new Widget({ node: document.createElement('label') });
|
369 | titleNode.node.textContent = options.label || 'Edit Metadata';
|
370 | layout.addWidget(titleNode);
|
371 | layout.addWidget(this.editor);
|
372 | }
|
373 | }
|
374 | NotebookTools.MetadataEditorTool = MetadataEditorTool;
|
375 | |
376 |
|
377 |
|
378 | class NotebookMetadataEditorTool extends MetadataEditorTool {
|
379 | constructor(options) {
|
380 | const translator = options.translator || nullTranslator;
|
381 | const trans = translator.load('jupyterlab');
|
382 | options.label = options.label || trans.__('Notebook Metadata');
|
383 | super(options);
|
384 | }
|
385 | |
386 |
|
387 |
|
388 | onActiveNotebookPanelChanged(msg) {
|
389 | this._update();
|
390 | }
|
391 | |
392 |
|
393 |
|
394 | onActiveNotebookPanelMetadataChanged(msg) {
|
395 | this._update();
|
396 | }
|
397 | _update() {
|
398 | var _a, _b;
|
399 | const nb = this.notebookTools.activeNotebookPanel &&
|
400 | this.notebookTools.activeNotebookPanel.content;
|
401 | this.editor.source = (_b = (_a = nb === null || nb === void 0 ? void 0 : nb.model) === null || _a === void 0 ? void 0 : _a.metadata) !== null && _b !== void 0 ? _b : null;
|
402 | }
|
403 | }
|
404 | NotebookTools.NotebookMetadataEditorTool = NotebookMetadataEditorTool;
|
405 | |
406 |
|
407 |
|
408 | class CellMetadataEditorTool extends MetadataEditorTool {
|
409 | constructor(options) {
|
410 | const translator = options.translator || nullTranslator;
|
411 | const trans = translator.load('jupyterlab');
|
412 | options.label = options.label || trans.__('Cell Metadata');
|
413 | super(options);
|
414 | }
|
415 | |
416 |
|
417 |
|
418 | onActiveCellChanged(msg) {
|
419 | this._update();
|
420 | }
|
421 | |
422 |
|
423 |
|
424 | onActiveCellMetadataChanged(msg) {
|
425 | this._update();
|
426 | }
|
427 | _update() {
|
428 | const cell = this.notebookTools.activeCell;
|
429 | this.editor.source = cell ? cell.model.metadata : null;
|
430 | }
|
431 | }
|
432 | NotebookTools.CellMetadataEditorTool = CellMetadataEditorTool;
|
433 | |
434 |
|
435 |
|
436 | class KeySelector extends Tool {
|
437 | |
438 |
|
439 |
|
440 | constructor(options) {
|
441 |
|
442 | super({ node: Private.createSelectorNode(options) });
|
443 | |
444 |
|
445 |
|
446 | this._getValue = (cell) => {
|
447 | let value = cell.model.metadata.get(this.key);
|
448 | if (value === undefined) {
|
449 | value = this._default;
|
450 | }
|
451 | return value;
|
452 | };
|
453 | |
454 |
|
455 |
|
456 | this._setValue = (cell, value) => {
|
457 | if (value === this._default) {
|
458 | cell.model.metadata.delete(this.key);
|
459 | }
|
460 | else {
|
461 | cell.model.metadata.set(this.key, value);
|
462 | }
|
463 | };
|
464 | this._changeGuard = false;
|
465 | this.addClass('jp-KeySelector');
|
466 | this.key = options.key;
|
467 | this._default = options.default;
|
468 | this._validCellTypes = options.validCellTypes || [];
|
469 | this._getter = options.getter || this._getValue;
|
470 | this._setter = options.setter || this._setValue;
|
471 | }
|
472 | |
473 |
|
474 |
|
475 | get selectNode() {
|
476 | return this.node.getElementsByTagName('select')[0];
|
477 | }
|
478 | |
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 | handleEvent(event) {
|
489 | switch (event.type) {
|
490 | case 'change':
|
491 | this.onValueChanged();
|
492 | break;
|
493 | default:
|
494 | break;
|
495 | }
|
496 | }
|
497 | |
498 |
|
499 |
|
500 | onAfterAttach(msg) {
|
501 | const node = this.selectNode;
|
502 | node.addEventListener('change', this);
|
503 | }
|
504 | |
505 |
|
506 |
|
507 | onBeforeDetach(msg) {
|
508 | const node = this.selectNode;
|
509 | node.removeEventListener('change', this);
|
510 | }
|
511 | |
512 |
|
513 |
|
514 | onActiveCellChanged(msg) {
|
515 | const select = this.selectNode;
|
516 | const activeCell = this.notebookTools.activeCell;
|
517 | if (!activeCell) {
|
518 | select.disabled = true;
|
519 | select.value = '';
|
520 | return;
|
521 | }
|
522 | const cellType = activeCell.model.type;
|
523 | if (this._validCellTypes.length &&
|
524 | this._validCellTypes.indexOf(cellType) === -1) {
|
525 | select.value = '';
|
526 | select.disabled = true;
|
527 | return;
|
528 | }
|
529 | select.disabled = false;
|
530 | this._changeGuard = true;
|
531 | const getter = this._getter;
|
532 | select.value = JSON.stringify(getter(activeCell));
|
533 | this._changeGuard = false;
|
534 | }
|
535 | |
536 |
|
537 |
|
538 | onActiveCellMetadataChanged(msg) {
|
539 | if (this._changeGuard) {
|
540 | return;
|
541 | }
|
542 | const select = this.selectNode;
|
543 | const cell = this.notebookTools.activeCell;
|
544 | if (msg.args.key === this.key && cell) {
|
545 | this._changeGuard = true;
|
546 | const getter = this._getter;
|
547 | select.value = JSON.stringify(getter(cell));
|
548 | this._changeGuard = false;
|
549 | }
|
550 | }
|
551 | |
552 |
|
553 |
|
554 | onValueChanged() {
|
555 | const activeCell = this.notebookTools.activeCell;
|
556 | if (!activeCell || this._changeGuard) {
|
557 | return;
|
558 | }
|
559 | this._changeGuard = true;
|
560 | const select = this.selectNode;
|
561 | const setter = this._setter;
|
562 | setter(activeCell, JSON.parse(select.value));
|
563 | this._changeGuard = false;
|
564 | }
|
565 | }
|
566 | NotebookTools.KeySelector = KeySelector;
|
567 | |
568 |
|
569 |
|
570 | function createSlideShowSelector(translator) {
|
571 | translator = translator || nullTranslator;
|
572 | const trans = translator.load('jupyterlab');
|
573 | trans.__('');
|
574 | const options = {
|
575 | key: 'slideshow',
|
576 | title: trans.__('Slide Type'),
|
577 | optionValueArray: [
|
578 | ['-', null],
|
579 | [trans.__('Slide'), 'slide'],
|
580 | [trans.__('Sub-Slide'), 'subslide'],
|
581 | [trans.__('Fragment'), 'fragment'],
|
582 | [trans.__('Skip'), 'skip'],
|
583 | [trans.__('Notes'), 'notes']
|
584 | ],
|
585 | getter: cell => {
|
586 | const value = cell.model.metadata.get('slideshow');
|
587 | return value && value['slide_type'];
|
588 | },
|
589 | setter: (cell, value) => {
|
590 | let data = cell.model.metadata.get('slideshow') || Object.create(null);
|
591 | if (value === null) {
|
592 |
|
593 | data = Object.assign({}, data);
|
594 | delete data.slide_type;
|
595 | }
|
596 | else {
|
597 | data = Object.assign(Object.assign({}, data), { slide_type: value });
|
598 | }
|
599 | if (Object.keys(data).length > 0) {
|
600 | cell.model.metadata.set('slideshow', data);
|
601 | }
|
602 | else {
|
603 | cell.model.metadata.delete('slideshow');
|
604 | }
|
605 | }
|
606 | };
|
607 | return new KeySelector(options);
|
608 | }
|
609 | NotebookTools.createSlideShowSelector = createSlideShowSelector;
|
610 | |
611 |
|
612 |
|
613 | function createNBConvertSelector(optionValueArray, translator) {
|
614 | translator = translator || nullTranslator;
|
615 | const trans = translator.load('jupyterlab');
|
616 | return new KeySelector({
|
617 | key: 'raw_mimetype',
|
618 | title: trans.__('Raw NBConvert Format'),
|
619 | optionValueArray: optionValueArray,
|
620 | validCellTypes: ['raw']
|
621 | });
|
622 | }
|
623 | NotebookTools.createNBConvertSelector = createNBConvertSelector;
|
624 | })(NotebookTools || (NotebookTools = {}));
|
625 |
|
626 |
|
627 |
|
628 | var Private;
|
629 | (function (Private) {
|
630 | |
631 |
|
632 |
|
633 | function itemCmp(first, second) {
|
634 | return first.rank - second.rank;
|
635 | }
|
636 | Private.itemCmp = itemCmp;
|
637 | |
638 |
|
639 |
|
640 | function createSelectorNode(options) {
|
641 | const name = options.key;
|
642 | const title = options.title || name[0].toLocaleUpperCase() + name.slice(1);
|
643 | const optionNodes = [];
|
644 | let value;
|
645 | let option;
|
646 | each(options.optionValueArray, item => {
|
647 | option = item[0];
|
648 | value = JSON.stringify(item[1]);
|
649 | optionNodes.push(h.option({ value }, option));
|
650 | });
|
651 | const node = VirtualDOM.realize(h.div({}, h.label(title, h.select({}, optionNodes))));
|
652 | Styling.styleNode(node);
|
653 | return node;
|
654 | }
|
655 | Private.createSelectorNode = createSelectorNode;
|
656 | })(Private || (Private = {}));
|
657 |
|
\ | No newline at end of file |