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 |
|
342 |
|
343 |
|
344 | onActiveNotebookPanelChanged(msg) {
|
345 | if (!this.notebookTools.activeNotebookPanel) {
|
346 |
|
347 | void this.onActiveCellChanged();
|
348 | }
|
349 | }
|
350 | |
351 |
|
352 |
|
353 | _onValueChanged() {
|
354 | this._model.value.text = this._cellModel.value.text.split('\n')[0];
|
355 | }
|
356 | |
357 |
|
358 |
|
359 | _onMimeTypeChanged() {
|
360 | this._model.mimeType = this._cellModel.mimeType;
|
361 | }
|
362 | }
|
363 | NotebookTools.ActiveCellTool = ActiveCellTool;
|
364 | |
365 |
|
366 |
|
367 | class MetadataEditorTool extends Tool {
|
368 | |
369 |
|
370 |
|
371 | constructor(options) {
|
372 | super();
|
373 | const { editorFactory } = options;
|
374 | this.addClass('jp-MetadataEditorTool');
|
375 | const layout = (this.layout = new PanelLayout());
|
376 | this._editorFactory = editorFactory;
|
377 | this._editorLabel = options.label || 'Edit Metadata';
|
378 | this.createEditor();
|
379 | const titleNode = new Widget({ node: document.createElement('label') });
|
380 | titleNode.node.textContent = options.label || 'Edit Metadata';
|
381 | layout.addWidget(titleNode);
|
382 | layout.addWidget(this.editor);
|
383 | }
|
384 | |
385 |
|
386 |
|
387 | get editor() {
|
388 | return this._editor;
|
389 | }
|
390 | |
391 |
|
392 |
|
393 | onActiveNotebookPanelChanged(msg) {
|
394 | this.editor.dispose();
|
395 | if (this.notebookTools.activeNotebookPanel) {
|
396 | this.createEditor();
|
397 | }
|
398 | }
|
399 | createEditor() {
|
400 | this._editor = new JSONEditor({
|
401 | editorFactory: this._editorFactory
|
402 | });
|
403 | this.editor.title.label = this._editorLabel;
|
404 | this.layout.addWidget(this.editor);
|
405 | }
|
406 | }
|
407 | NotebookTools.MetadataEditorTool = MetadataEditorTool;
|
408 | |
409 |
|
410 |
|
411 | class NotebookMetadataEditorTool extends MetadataEditorTool {
|
412 | constructor(options) {
|
413 | const translator = options.translator || nullTranslator;
|
414 | const trans = translator.load('jupyterlab');
|
415 | options.label = options.label || trans.__('Notebook Metadata');
|
416 | super(options);
|
417 | }
|
418 | |
419 |
|
420 |
|
421 | onActiveNotebookPanelChanged(msg) {
|
422 | super.onActiveNotebookPanelChanged(msg);
|
423 | this._update();
|
424 | }
|
425 | |
426 |
|
427 |
|
428 | onActiveNotebookPanelMetadataChanged(msg) {
|
429 | this._update();
|
430 | }
|
431 | _update() {
|
432 | var _a, _b;
|
433 | const nb = this.notebookTools.activeNotebookPanel &&
|
434 | this.notebookTools.activeNotebookPanel.content;
|
435 | 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;
|
436 | }
|
437 | }
|
438 | NotebookTools.NotebookMetadataEditorTool = NotebookMetadataEditorTool;
|
439 | |
440 |
|
441 |
|
442 | class CellMetadataEditorTool extends MetadataEditorTool {
|
443 | constructor(options) {
|
444 | const translator = options.translator || nullTranslator;
|
445 | const trans = translator.load('jupyterlab');
|
446 | options.label = options.label || trans.__('Cell Metadata');
|
447 | super(options);
|
448 | }
|
449 | |
450 |
|
451 |
|
452 | onActiveCellChanged(msg) {
|
453 | this.editor.dispose();
|
454 | if (this.notebookTools.activeCell) {
|
455 | this.createEditor();
|
456 | }
|
457 | this._update();
|
458 | }
|
459 | |
460 |
|
461 |
|
462 | onActiveCellMetadataChanged(msg) {
|
463 | this._update();
|
464 | }
|
465 | _update() {
|
466 | const cell = this.notebookTools.activeCell;
|
467 | this.editor.source = cell ? cell.model.metadata : null;
|
468 | }
|
469 | }
|
470 | NotebookTools.CellMetadataEditorTool = CellMetadataEditorTool;
|
471 | |
472 |
|
473 |
|
474 | class KeySelector extends Tool {
|
475 | |
476 |
|
477 |
|
478 | constructor(options) {
|
479 |
|
480 | super({ node: Private.createSelectorNode(options) });
|
481 | |
482 |
|
483 |
|
484 | this._getValue = (cell) => {
|
485 | let value = cell.model.metadata.get(this.key);
|
486 | if (value === undefined) {
|
487 | value = this._default;
|
488 | }
|
489 | return value;
|
490 | };
|
491 | |
492 |
|
493 |
|
494 | this._setValue = (cell, value) => {
|
495 | if (value === this._default) {
|
496 | cell.model.metadata.delete(this.key);
|
497 | }
|
498 | else {
|
499 | cell.model.metadata.set(this.key, value);
|
500 | }
|
501 | };
|
502 | this._changeGuard = false;
|
503 | this.addClass('jp-KeySelector');
|
504 | this.key = options.key;
|
505 | this._default = options.default;
|
506 | this._validCellTypes = options.validCellTypes || [];
|
507 | this._getter = options.getter || this._getValue;
|
508 | this._setter = options.setter || this._setValue;
|
509 | }
|
510 | |
511 |
|
512 |
|
513 | get selectNode() {
|
514 | return this.node.getElementsByTagName('select')[0];
|
515 | }
|
516 | |
517 |
|
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 | handleEvent(event) {
|
527 | switch (event.type) {
|
528 | case 'change':
|
529 | this.onValueChanged();
|
530 | break;
|
531 | default:
|
532 | break;
|
533 | }
|
534 | }
|
535 | |
536 |
|
537 |
|
538 | onAfterAttach(msg) {
|
539 | const node = this.selectNode;
|
540 | node.addEventListener('change', this);
|
541 | }
|
542 | |
543 |
|
544 |
|
545 | onBeforeDetach(msg) {
|
546 | const node = this.selectNode;
|
547 | node.removeEventListener('change', this);
|
548 | }
|
549 | |
550 |
|
551 |
|
552 | onActiveCellChanged(msg) {
|
553 | const select = this.selectNode;
|
554 | const activeCell = this.notebookTools.activeCell;
|
555 | if (!activeCell) {
|
556 | select.disabled = true;
|
557 | select.value = '';
|
558 | return;
|
559 | }
|
560 | const cellType = activeCell.model.type;
|
561 | if (this._validCellTypes.length &&
|
562 | this._validCellTypes.indexOf(cellType) === -1) {
|
563 | select.value = '';
|
564 | select.disabled = true;
|
565 | return;
|
566 | }
|
567 | select.disabled = false;
|
568 | this._changeGuard = true;
|
569 | const getter = this._getter;
|
570 | select.value = JSON.stringify(getter(activeCell));
|
571 | this._changeGuard = false;
|
572 | }
|
573 | |
574 |
|
575 |
|
576 | onActiveCellMetadataChanged(msg) {
|
577 | if (this._changeGuard) {
|
578 | return;
|
579 | }
|
580 | const select = this.selectNode;
|
581 | const cell = this.notebookTools.activeCell;
|
582 | if (msg.args.key === this.key && cell) {
|
583 | this._changeGuard = true;
|
584 | const getter = this._getter;
|
585 | select.value = JSON.stringify(getter(cell));
|
586 | this._changeGuard = false;
|
587 | }
|
588 | }
|
589 | |
590 |
|
591 |
|
592 | onValueChanged() {
|
593 | const activeCell = this.notebookTools.activeCell;
|
594 | if (!activeCell || this._changeGuard) {
|
595 | return;
|
596 | }
|
597 | this._changeGuard = true;
|
598 | const select = this.selectNode;
|
599 | const setter = this._setter;
|
600 | setter(activeCell, JSON.parse(select.value));
|
601 | this._changeGuard = false;
|
602 | }
|
603 | }
|
604 | NotebookTools.KeySelector = KeySelector;
|
605 | |
606 |
|
607 |
|
608 | function createSlideShowSelector(translator) {
|
609 | translator = translator || nullTranslator;
|
610 | const trans = translator.load('jupyterlab');
|
611 | trans.__('');
|
612 | const options = {
|
613 | key: 'slideshow',
|
614 | title: trans.__('Slide Type'),
|
615 | optionValueArray: [
|
616 | ['-', null],
|
617 | [trans.__('Slide'), 'slide'],
|
618 | [trans.__('Sub-Slide'), 'subslide'],
|
619 | [trans.__('Fragment'), 'fragment'],
|
620 | [trans.__('Skip'), 'skip'],
|
621 | [trans.__('Notes'), 'notes']
|
622 | ],
|
623 | getter: cell => {
|
624 | const value = cell.model.metadata.get('slideshow');
|
625 | return value && value['slide_type'];
|
626 | },
|
627 | setter: (cell, value) => {
|
628 | let data = cell.model.metadata.get('slideshow') || Object.create(null);
|
629 | if (value === null) {
|
630 |
|
631 | data = Object.assign({}, data);
|
632 | delete data.slide_type;
|
633 | }
|
634 | else {
|
635 | data = Object.assign(Object.assign({}, data), { slide_type: value });
|
636 | }
|
637 | if (Object.keys(data).length > 0) {
|
638 | cell.model.metadata.set('slideshow', data);
|
639 | }
|
640 | else {
|
641 | cell.model.metadata.delete('slideshow');
|
642 | }
|
643 | }
|
644 | };
|
645 | return new KeySelector(options);
|
646 | }
|
647 | NotebookTools.createSlideShowSelector = createSlideShowSelector;
|
648 | |
649 |
|
650 |
|
651 | function createNBConvertSelector(optionValueArray, translator) {
|
652 | translator = translator || nullTranslator;
|
653 | const trans = translator.load('jupyterlab');
|
654 | return new KeySelector({
|
655 | key: 'raw_mimetype',
|
656 | title: trans.__('Raw NBConvert Format'),
|
657 | optionValueArray: optionValueArray,
|
658 | validCellTypes: ['raw']
|
659 | });
|
660 | }
|
661 | NotebookTools.createNBConvertSelector = createNBConvertSelector;
|
662 | })(NotebookTools || (NotebookTools = {}));
|
663 |
|
664 |
|
665 |
|
666 | var Private;
|
667 | (function (Private) {
|
668 | |
669 |
|
670 |
|
671 | function itemCmp(first, second) {
|
672 | return first.rank - second.rank;
|
673 | }
|
674 | Private.itemCmp = itemCmp;
|
675 | |
676 |
|
677 |
|
678 | function createSelectorNode(options) {
|
679 | const name = options.key;
|
680 | const title = options.title || name[0].toLocaleUpperCase() + name.slice(1);
|
681 | const optionNodes = [];
|
682 | let value;
|
683 | let option;
|
684 | each(options.optionValueArray, item => {
|
685 | option = item[0];
|
686 | value = JSON.stringify(item[1]);
|
687 | optionNodes.push(h.option({ value }, option));
|
688 | });
|
689 | const node = VirtualDOM.realize(h.div({}, h.label(title, h.select({}, optionNodes))));
|
690 | Styling.styleNode(node);
|
691 | return node;
|
692 | }
|
693 | Private.createSelectorNode = createSelectorNode;
|
694 | })(Private || (Private = {}));
|
695 |
|
\ | No newline at end of file |