UNPKG

@jupyterlab/notebook

Version:
366 lines 12.7 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { JSONEditor } from '@jupyterlab/codeeditor'; import { ObservableJSON } from '@jupyterlab/observables'; import { nullTranslator } from '@jupyterlab/translation'; import { Collapser } from '@jupyterlab/ui-components'; import { ArrayExt } from '@lumino/algorithm'; import { ConflatableMessage, MessageLoop } from '@lumino/messaging'; import { PanelLayout, Widget } from '@lumino/widgets'; class RankedPanel extends Widget { constructor() { super(); this._items = []; this.layout = new PanelLayout(); this.addClass('jp-RankedPanel'); } addWidget(widget, rank) { const rankItem = { widget, rank }; const index = ArrayExt.upperBound(this._items, rankItem, Private.itemCmp); ArrayExt.insert(this._items, index, rankItem); const layout = this.layout; layout.insertWidget(index, widget); } /** * Handle the removal of a child * */ onChildRemoved(msg) { const index = ArrayExt.findFirstIndex(this._items, item => item.widget === msg.child); if (index !== -1) { ArrayExt.removeAt(this._items, index); } } } /** * A widget that provides metadata tools. */ export class NotebookTools extends Widget { /** * Construct a new NotebookTools object. */ constructor(options) { super(); this.addClass('jp-NotebookTools'); this.translator = options.translator || nullTranslator; this._tools = []; this.layout = new PanelLayout(); this._tracker = options.tracker; this._tracker.currentChanged.connect(this._onActiveNotebookPanelChanged, this); this._tracker.activeCellChanged.connect(this._onActiveCellChanged, this); this._tracker.selectionChanged.connect(this._onSelectionChanged, this); this._onActiveNotebookPanelChanged(); this._onActiveCellChanged(); this._onSelectionChanged(); } /** * The active cell widget. */ get activeCell() { return this._tracker.activeCell; } /** * The currently selected cells. */ get selectedCells() { const panel = this._tracker.currentWidget; if (!panel) { return []; } const notebook = panel.content; return notebook.widgets.filter(cell => notebook.isSelectedOrActive(cell)); } /** * The current notebook. */ get activeNotebookPanel() { return this._tracker.currentWidget; } /** * Add a cell tool item. */ addItem(options) { var _a; const tool = options.tool; const rank = (_a = options.rank) !== null && _a !== void 0 ? _a : 100; let section; const extendedTool = this._tools.find(extendedTool => extendedTool.section === options.section); if (extendedTool) section = extendedTool.panel; else { throw new Error(`The section ${options.section} does not exist`); } tool.addClass('jp-NotebookTools-tool'); section.addWidget(tool, rank); // TODO: perhaps the necessary notebookTools functionality should be // consolidated into a single object, rather than a broad reference to this. tool.notebookTools = this; // Trigger the tool to update its active notebook and cell. MessageLoop.sendMessage(tool, NotebookTools.ActiveNotebookPanelMessage); MessageLoop.sendMessage(tool, NotebookTools.ActiveCellMessage); } /* * Add a section to the notebook tool with its widget */ addSection(options) { var _a; const sectionName = options.sectionName; const label = options.label || options.sectionName; const widget = options.tool; let rank = (_a = options.rank) !== null && _a !== void 0 ? _a : null; const newSection = new RankedPanel(); newSection.title.label = label; if (widget) newSection.addWidget(widget, 0); this._tools.push({ section: sectionName, panel: newSection, rank: rank }); if (rank != null) this.layout.insertWidget(rank, new Collapser({ widget: newSection })); else { // If no rank is provided, try to add the new section before the AdvancedTools. let advancedToolsRank = null; const layout = this.layout; for (let i = 0; i < layout.widgets.length; i++) { let w = layout.widgets[i]; if (w instanceof Collapser) { if (w.widget.id === 'advancedToolsSection') { advancedToolsRank = i; break; } } } if (advancedToolsRank !== null) this.layout.insertWidget(advancedToolsRank, new Collapser({ widget: newSection })); else this.layout.addWidget(new Collapser({ widget: newSection })); } } /** * Handle a change to the notebook panel. */ _onActiveNotebookPanelChanged() { if (this._prevActiveNotebookModel && !this._prevActiveNotebookModel.isDisposed) { this._prevActiveNotebookModel.metadataChanged.disconnect(this._onActiveNotebookPanelMetadataChanged, this); } const activeNBModel = this.activeNotebookPanel && this.activeNotebookPanel.content ? this.activeNotebookPanel.content.model : null; this._prevActiveNotebookModel = activeNBModel; if (activeNBModel) { activeNBModel.metadataChanged.connect(this._onActiveNotebookPanelMetadataChanged, this); } for (const widget of this._toolChildren()) { MessageLoop.sendMessage(widget, NotebookTools.ActiveNotebookPanelMessage); } } /** * Handle a change to the active cell. */ _onActiveCellChanged() { if (this._prevActiveCell && !this._prevActiveCell.isDisposed) { this._prevActiveCell.metadataChanged.disconnect(this._onActiveCellMetadataChanged, this); } const activeCell = this.activeCell ? this.activeCell.model : null; this._prevActiveCell = activeCell; if (activeCell) { activeCell.metadataChanged.connect(this._onActiveCellMetadataChanged, this); } for (const widget of this._toolChildren()) { MessageLoop.sendMessage(widget, NotebookTools.ActiveCellMessage); } } /** * Handle a change in the selection. */ _onSelectionChanged() { for (const widget of this._toolChildren()) { MessageLoop.sendMessage(widget, NotebookTools.SelectionMessage); } } /** * Handle a change in the active cell metadata. */ _onActiveNotebookPanelMetadataChanged(sender, args) { const message = new ObservableJSON.ChangeMessage('activenotebookpanel-metadata-changed', { oldValue: undefined, newValue: undefined, ...args }); for (const widget of this._toolChildren()) { MessageLoop.sendMessage(widget, message); } } /** * Handle a change in the notebook model metadata. */ _onActiveCellMetadataChanged(sender, args) { const message = new ObservableJSON.ChangeMessage('activecell-metadata-changed', { newValue: undefined, oldValue: undefined, ...args }); for (const widget of this._toolChildren()) { MessageLoop.sendMessage(widget, message); } } *_toolChildren() { for (let tool of this._tools) { yield* tool.panel.children(); } } } /** * The namespace for NotebookTools class statics. */ (function (NotebookTools) { /** * A singleton conflatable `'activenotebookpanel-changed'` message. */ NotebookTools.ActiveNotebookPanelMessage = new ConflatableMessage('activenotebookpanel-changed'); /** * A singleton conflatable `'activecell-changed'` message. */ NotebookTools.ActiveCellMessage = new ConflatableMessage('activecell-changed'); /** * A singleton conflatable `'selection-changed'` message. */ NotebookTools.SelectionMessage = new ConflatableMessage('selection-changed'); /** * The base notebook tool, meant to be subclassed. */ class Tool extends Widget { dispose() { super.dispose(); if (this.notebookTools) { this.notebookTools = null; } } /** * Process a message sent to the widget. * * @param msg - The message sent to the widget. */ processMessage(msg) { super.processMessage(msg); switch (msg.type) { case 'activenotebookpanel-changed': this.onActiveNotebookPanelChanged(msg); break; case 'activecell-changed': this.onActiveCellChanged(msg); break; case 'selection-changed': this.onSelectionChanged(msg); break; case 'activecell-metadata-changed': this.onActiveCellMetadataChanged(msg); break; case 'activenotebookpanel-metadata-changed': this.onActiveNotebookPanelMetadataChanged(msg); break; default: break; } } /** * Handle a change to the notebook panel. * * #### Notes * The default implementation is a no-op. */ onActiveNotebookPanelChanged(msg) { /* no-op */ } /** * Handle a change to the active cell. * * #### Notes * The default implementation is a no-op. */ onActiveCellChanged(msg) { /* no-op */ } /** * Handle a change to the selection. * * #### Notes * The default implementation is a no-op. */ onSelectionChanged(msg) { /* no-op */ } /** * Handle a change to the metadata of the active cell. * * #### Notes * The default implementation is a no-op. */ onActiveCellMetadataChanged(msg) { /* no-op */ } /** * Handle a change to the metadata of the active cell. * * #### Notes * The default implementation is a no-op. */ onActiveNotebookPanelMetadataChanged(msg) { /* no-op */ } } NotebookTools.Tool = Tool; /** * A raw metadata editor. */ class MetadataEditorTool extends Tool { /** * Construct a new raw metadata tool. */ constructor(options) { super(); const { editorFactory } = options; this.addClass('jp-MetadataEditorTool'); const layout = (this.layout = new PanelLayout()); this._editorFactory = editorFactory; this._editorLabel = options.label || 'Edit Metadata'; this.createEditor(); const titleNode = new Widget({ node: document.createElement('label') }); titleNode.node.textContent = options.label || 'Edit Metadata'; layout.addWidget(titleNode); layout.addWidget(this.editor); } /** * The editor used by the tool. */ get editor() { return this._editor; } /** * Handle a change to the notebook. */ onActiveNotebookPanelChanged(msg) { this.editor.dispose(); if (this.notebookTools.activeNotebookPanel) { this.createEditor(); } } createEditor() { this._editor = new JSONEditor({ editorFactory: this._editorFactory }); this.editor.title.label = this._editorLabel; this.layout.addWidget(this.editor); } } NotebookTools.MetadataEditorTool = MetadataEditorTool; })(NotebookTools || (NotebookTools = {})); /** * A namespace for private data. */ var Private; (function (Private) { /** * A comparator function for widget rank items. */ function itemCmp(first, second) { return first.rank - second.rank; } Private.itemCmp = itemCmp; })(Private || (Private = {})); //# sourceMappingURL=notebooktools.js.map