123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { ArrayExt, each, chain } from '@phosphor/algorithm';
- import { JSONObject, JSONValue, Token } from '@phosphor/coreutils';
- import { ConflatableMessage, Message, MessageLoop } from '@phosphor/messaging';
- import { h, VirtualDOM, VirtualNode } from '@phosphor/virtualdom';
- import { PanelLayout, Widget } from '@phosphor/widgets';
- import { Collapse, Styling } from '@jupyterlab/apputils';
- import { Cell, ICellModel } from '@jupyterlab/cells';
- import {
- CodeEditor,
- CodeEditorWrapper,
- JSONEditor
- } from '@jupyterlab/codeeditor';
- import { nbformat } from '@jupyterlab/coreutils';
- import { IObservableMap, ObservableJSON } from '@jupyterlab/observables';
- import { INotebookTracker } from './';
- import { NotebookPanel } from './panel';
- import { INotebookModel } from './model';
- /* tslint:disable */
- /**
- * The notebook tools token.
- */
- export const INotebookTools = new Token<INotebookTools>(
- '@jupyterlab/notebook:INotebookTools'
- );
- /* tslint:enable */
- /**
- * The interface for notebook metadata tools.
- */
- export interface INotebookTools extends Widget {
- activeNotebookPanel: NotebookPanel | null;
- activeCell: Cell | null;
- selectedCells: Cell[];
- addItem(options: NotebookTools.IAddOptions): void;
- }
- class RankedPanel<T extends Widget = Widget> extends Widget {
- constructor() {
- super();
- this.layout = new PanelLayout();
- this.addClass('jp-RankedPanel');
- }
- addWidget(widget: Widget, rank: number): void {
- const rankItem = { widget, rank };
- const index = ArrayExt.upperBound(this._items, rankItem, Private.itemCmp);
- ArrayExt.insert(this._items, index, rankItem);
- const layout = this.layout as PanelLayout;
- layout.insertWidget(index, widget);
- }
- /**
- * Handle the removal of a child
- *
- */
- protected onChildRemoved(msg: Widget.ChildMessage): void {
- let index = ArrayExt.findFirstIndex(
- this._items,
- item => item.widget === msg.child
- );
- if (index !== -1) {
- ArrayExt.removeAt(this._items, index);
- }
- }
- private _items: Private.IRankItem<T>[] = [];
- }
- /**
- * A widget that provides cell metadata tools.
- */
- export class NotebookTools extends Widget implements INotebookTools {
- /**
- * Construct a new NotebookTools object.
- */
- constructor(options: NotebookTools.IOptions) {
- super();
- this.addClass('jp-NotebookTools');
- this._commonTools = new RankedPanel<NotebookTools.Tool>();
- this._advancedTools = new RankedPanel<NotebookTools.Tool>();
- this._advancedTools.title.label = 'Advanced Tools';
- const layout = (this.layout = new PanelLayout());
- layout.addWidget(this._commonTools);
- layout.addWidget(new Collapse({ widget: this._advancedTools }));
- 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(): Cell | null {
- return this._tracker.activeCell;
- }
- /**
- * The currently selected cells.
- */
- get selectedCells(): Cell[] {
- 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(): NotebookPanel | null {
- return this._tracker.currentWidget;
- }
- /**
- * Add a cell tool item.
- */
- addItem(options: NotebookTools.IAddOptions): void {
- let tool = options.tool;
- let rank = 'rank' in options ? options.rank : 100;
- let section: RankedPanel<NotebookTools.Tool>;
- if (options.section === 'advanced') {
- section = this._advancedTools;
- } else {
- section = this._commonTools;
- }
- 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);
- }
- /**
- * Handle a change to the notebook panel.
- */
- private _onActiveNotebookPanelChanged(): void {
- if (
- this._prevActiveNotebookModel &&
- !this._prevActiveNotebookModel.isDisposed
- ) {
- this._prevActiveNotebookModel.metadata.changed.disconnect(
- this._onActiveNotebookPanelMetadataChanged,
- this
- );
- }
- const activeNBModel =
- this.activeNotebookPanel && this.activeNotebookPanel.content
- ? this.activeNotebookPanel.content.model
- : null;
- this._prevActiveNotebookModel = activeNBModel;
- if (activeNBModel) {
- activeNBModel.metadata.changed.connect(
- this._onActiveNotebookPanelMetadataChanged,
- this
- );
- }
- each(this._toolChildren(), widget => {
- MessageLoop.sendMessage(widget, NotebookTools.ActiveNotebookPanelMessage);
- });
- }
- /**
- * Handle a change to the active cell.
- */
- private _onActiveCellChanged(): void {
- if (this._prevActiveCell && !this._prevActiveCell.isDisposed) {
- this._prevActiveCell.metadata.changed.disconnect(
- this._onActiveCellMetadataChanged,
- this
- );
- }
- const activeCell = this.activeCell ? this.activeCell.model : null;
- this._prevActiveCell = activeCell;
- if (activeCell) {
- activeCell.metadata.changed.connect(
- this._onActiveCellMetadataChanged,
- this
- );
- }
- each(this._toolChildren(), widget => {
- MessageLoop.sendMessage(widget, NotebookTools.ActiveCellMessage);
- });
- }
- /**
- * Handle a change in the selection.
- */
- private _onSelectionChanged(): void {
- each(this._toolChildren(), widget => {
- MessageLoop.sendMessage(widget, NotebookTools.SelectionMessage);
- });
- }
- /**
- * Handle a change in the active cell metadata.
- */
- private _onActiveNotebookPanelMetadataChanged(
- sender: IObservableMap<JSONValue>,
- args: IObservableMap.IChangedArgs<JSONValue>
- ): void {
- let message = new ObservableJSON.ChangeMessage(
- 'activenotebookpanel-metadata-changed',
- args
- );
- each(this._toolChildren(), widget => {
- MessageLoop.sendMessage(widget, message);
- });
- }
- /**
- * Handle a change in the notebook model metadata.
- */
- private _onActiveCellMetadataChanged(
- sender: IObservableMap<JSONValue>,
- args: IObservableMap.IChangedArgs<JSONValue>
- ): void {
- let message = new ObservableJSON.ChangeMessage(
- 'activecell-metadata-changed',
- args
- );
- each(this._toolChildren(), widget => {
- MessageLoop.sendMessage(widget, message);
- });
- }
- private _toolChildren() {
- return chain(this._commonTools.children(), this._advancedTools.children());
- }
- private _commonTools: RankedPanel<NotebookTools.Tool>;
- private _advancedTools: RankedPanel<NotebookTools.Tool>;
- private _tracker: INotebookTracker;
- private _prevActiveCell: ICellModel | null;
- private _prevActiveNotebookModel: INotebookModel | null;
- }
- /**
- * The namespace for NotebookTools class statics.
- */
- export namespace NotebookTools {
- /**
- * The options used to create a NotebookTools object.
- */
- export interface IOptions {
- /**
- * The notebook tracker used by the notebook tools.
- */
- tracker: INotebookTracker;
- }
- /**
- * The options used to add an item to the notebook tools.
- */
- export interface IAddOptions {
- /**
- * The tool to add to the notebook tools area.
- */
- tool: Tool;
- /**
- * The section to which the tool should be added.
- */
- section?: 'common' | 'advanced';
- /**
- * The rank order of the widget among its siblings.
- */
- rank?: number;
- }
- /**
- * A singleton conflatable `'activenotebookpanel-changed'` message.
- */
- export const ActiveNotebookPanelMessage = new ConflatableMessage(
- 'activenotebookpanel-changed'
- );
- /**
- * A singleton conflatable `'activecell-changed'` message.
- */
- export const ActiveCellMessage = new ConflatableMessage('activecell-changed');
- /**
- * A singleton conflatable `'selection-changed'` message.
- */
- export const SelectionMessage = new ConflatableMessage('selection-changed');
- /**
- * The base notebook tool, meant to be subclassed.
- */
- export class Tool extends Widget {
- /**
- * The notebook tools object.
- */
- notebookTools: INotebookTools;
- dispose() {
- super.dispose();
- this.notebookTools = null;
- }
- /**
- * Process a message sent to the widget.
- *
- * @param msg - The message sent to the widget.
- */
- processMessage(msg: Message): void {
- 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 as ObservableJSON.ChangeMessage);
- break;
- case 'activenotebookpanel-metadata-changed':
- this.onActiveNotebookPanelMetadataChanged(
- msg as ObservableJSON.ChangeMessage
- );
- break;
- default:
- break;
- }
- }
- /**
- * Handle a change to the notebook panel.
- *
- * #### Notes
- * The default implementation is a no-op.
- */
- protected onActiveNotebookPanelChanged(msg: Message): void {
- /* no-op */
- }
- /**
- * Handle a change to the active cell.
- *
- * #### Notes
- * The default implementation is a no-op.
- */
- protected onActiveCellChanged(msg: Message): void {
- /* no-op */
- }
- /**
- * Handle a change to the selection.
- *
- * #### Notes
- * The default implementation is a no-op.
- */
- protected onSelectionChanged(msg: Message): void {
- /* no-op */
- }
- /**
- * Handle a change to the metadata of the active cell.
- *
- * #### Notes
- * The default implementation is a no-op.
- */
- protected onActiveCellMetadataChanged(
- msg: ObservableJSON.ChangeMessage
- ): void {
- /* no-op */
- }
- /**
- * Handle a change to the metadata of the active cell.
- *
- * #### Notes
- * The default implementation is a no-op.
- */
- protected onActiveNotebookPanelMetadataChanged(
- msg: ObservableJSON.ChangeMessage
- ): void {
- /* no-op */
- }
- }
- /**
- * A cell tool displaying the active cell contents.
- */
- export class ActiveCellTool extends Tool {
- /**
- * Construct a new active cell tool.
- */
- constructor() {
- super();
- this.addClass('jp-ActiveCellTool');
- this.addClass('jp-InputArea');
- this.layout = new PanelLayout();
- }
- /**
- * Dispose of the resources used by the tool.
- */
- dispose() {
- if (this._model === null) {
- return;
- }
- this._model.dispose();
- this._model = null;
- super.dispose();
- }
- /**
- * Handle a change to the active cell.
- */
- protected onActiveCellChanged(): void {
- let activeCell = this.notebookTools.activeCell;
- let layout = this.layout as PanelLayout;
- let count = layout.widgets.length;
- for (let i = 0; i < count; i++) {
- layout.widgets[0].dispose();
- }
- if (this._cellModel && !this._cellModel.isDisposed) {
- this._cellModel.value.changed.disconnect(this._onValueChanged, this);
- this._cellModel.mimeTypeChanged.disconnect(
- this._onMimeTypeChanged,
- this
- );
- }
- if (!activeCell) {
- let cell = new Widget();
- cell.addClass('jp-InputArea-editor');
- cell.addClass('jp-InputArea-editor');
- layout.addWidget(cell);
- this._cellModel = null;
- return;
- }
- let promptNode = activeCell.promptNode
- ? (activeCell.promptNode.cloneNode(true) as HTMLElement)
- : null;
- let prompt = new Widget({ node: promptNode });
- let factory = activeCell.contentFactory.editorFactory;
- let cellModel = (this._cellModel = activeCell.model);
- cellModel.value.changed.connect(
- this._onValueChanged,
- this
- );
- cellModel.mimeTypeChanged.connect(
- this._onMimeTypeChanged,
- this
- );
- this._model.value.text = cellModel.value.text.split('\n')[0];
- this._model.mimeType = cellModel.mimeType;
- let model = this._model;
- let editorWidget = new CodeEditorWrapper({ model, factory });
- editorWidget.addClass('jp-InputArea-editor');
- editorWidget.addClass('jp-InputArea-editor');
- editorWidget.editor.setOption('readOnly', true);
- layout.addWidget(prompt);
- layout.addWidget(editorWidget);
- }
- /**
- * Handle a change to the current editor value.
- */
- private _onValueChanged(): void {
- this._model.value.text = this._cellModel.value.text.split('\n')[0];
- }
- /**
- * Handle a change to the current editor mimetype.
- */
- private _onMimeTypeChanged(): void {
- this._model.mimeType = this._cellModel.mimeType;
- }
- private _model = new CodeEditor.Model();
- private _cellModel: CodeEditor.IModel;
- }
- /**
- * A raw metadata editor.
- */
- export class MetadataEditorTool extends Tool {
- /**
- * Construct a new raw metadata tool.
- */
- constructor(options: MetadataEditorTool.IOptions) {
- super();
- const { editorFactory } = options;
- this.addClass('jp-MetadataEditorTool');
- let layout = (this.layout = new PanelLayout());
- this.editor = new JSONEditor({
- editorFactory
- });
- this.editor.title.label = options.label || 'Edit Metadata';
- 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.
- */
- readonly editor: JSONEditor;
- }
- /**
- * The namespace for `MetadataEditorTool` static data.
- */
- export namespace MetadataEditorTool {
- /**
- * The options used to initialize a metadata editor tool.
- */
- export interface IOptions {
- /**
- * The editor factory used by the tool.
- */
- editorFactory: CodeEditor.Factory;
- /**
- * The label for the JSON editor
- */
- label?: string;
- /**
- * Initial collapse state, defaults to true.
- */
- collapsed?: boolean;
- }
- }
- /**
- * A notebook metadata editor
- */
- export class NotebookMetadataEditorTool extends MetadataEditorTool {
- constructor(options: MetadataEditorTool.IOptions) {
- options.label = options.label || 'Notebook Metadata';
- super(options);
- }
- /**
- * Handle a change to the notebook.
- */
- protected onActiveNotebookPanelChanged(msg: Message): void {
- this._update();
- }
- /**
- * Handle a change to the notebook metadata.
- */
- protected onActiveNotebookPanelMetadataChanged(msg: Message): void {
- this._update();
- }
- private _update() {
- const nb =
- this.notebookTools.activeNotebookPanel &&
- this.notebookTools.activeNotebookPanel.content;
- this.editor.source = nb ? nb.model.metadata : null;
- }
- }
- /**
- * A cell metadata editor
- */
- export class CellMetadataEditorTool extends MetadataEditorTool {
- constructor(options: MetadataEditorTool.IOptions) {
- options.label = options.label || 'Cell Metadata';
- super(options);
- }
- /**
- * Handle a change to the active cell.
- */
- protected onActiveCellChanged(msg: Message): void {
- this._update();
- }
- /**
- * Handle a change to the active cell metadata.
- */
- protected onActiveCellMetadataChanged(msg: Message): void {
- this._update();
- }
- private _update() {
- let cell = this.notebookTools.activeCell;
- this.editor.source = cell ? cell.model.metadata : null;
- }
- }
- /**
- * A cell tool that provides a selection for a given metadata key.
- */
- export class KeySelector extends Tool {
- /**
- * Construct a new KeySelector.
- */
- constructor(options: KeySelector.IOptions) {
- // TODO: use react
- super({ node: Private.createSelectorNode(options) });
- this.addClass('jp-KeySelector');
- this.key = options.key;
- this._default = options.default;
- this._validCellTypes = options.validCellTypes || [];
- this._getter = options.getter || this._getValue;
- this._setter = options.setter || this._setValue;
- }
- /**
- * The metadata key used by the selector.
- */
- readonly key: string;
- /**
- * The select node for the widget.
- */
- get selectNode(): HTMLSelectElement {
- return this.node.getElementsByTagName('select')[0] as HTMLSelectElement;
- }
- /**
- * Handle the DOM events for the widget.
- *
- * @param event - The DOM event sent to the widget.
- *
- * #### Notes
- * This method implements the DOM `EventListener` interface and is
- * called in response to events on the notebook panel's node. It should
- * not be called directly by user code.
- */
- handleEvent(event: Event): void {
- switch (event.type) {
- case 'change':
- this.onValueChanged();
- break;
- default:
- break;
- }
- }
- /**
- * Handle `after-attach` messages for the widget.
- */
- protected onAfterAttach(msg: Message): void {
- let node = this.selectNode;
- node.addEventListener('change', this);
- }
- /**
- * Handle `before-detach` messages for the widget.
- */
- protected onBeforeDetach(msg: Message): void {
- let node = this.selectNode;
- node.removeEventListener('change', this);
- }
- /**
- * Handle a change to the active cell.
- */
- protected onActiveCellChanged(msg: Message): void {
- let select = this.selectNode;
- let activeCell = this.notebookTools.activeCell;
- if (!activeCell) {
- select.disabled = true;
- select.value = '';
- return;
- }
- let cellType = activeCell.model.type;
- if (
- this._validCellTypes.length &&
- this._validCellTypes.indexOf(cellType) === -1
- ) {
- select.value = undefined;
- select.disabled = true;
- return;
- }
- select.disabled = false;
- this._changeGuard = true;
- let getter = this._getter;
- select.value = JSON.stringify(getter(activeCell));
- this._changeGuard = false;
- }
- /**
- * Handle a change to the metadata of the active cell.
- */
- protected onActiveCellMetadataChanged(msg: ObservableJSON.ChangeMessage) {
- if (this._changeGuard) {
- return;
- }
- let select = this.selectNode;
- let cell = this.notebookTools.activeCell;
- if (msg.args.key === this.key && cell) {
- this._changeGuard = true;
- let getter = this._getter;
- select.value = JSON.stringify(getter(cell));
- this._changeGuard = false;
- }
- }
- /**
- * Handle a change to the value.
- */
- protected onValueChanged(): void {
- let activeCell = this.notebookTools.activeCell;
- if (!activeCell || this._changeGuard) {
- return;
- }
- this._changeGuard = true;
- let select = this.selectNode;
- let setter = this._setter;
- setter(activeCell, JSON.parse(select.value));
- this._changeGuard = false;
- }
- /**
- * Get the value for the data.
- */
- private _getValue = (cell: Cell) => {
- let value = cell.model.metadata.get(this.key);
- if (value === undefined) {
- value = this._default;
- }
- return value;
- };
- /**
- * Set the value for the data.
- */
- private _setValue = (cell: Cell, value: JSONValue) => {
- if (value === this._default) {
- cell.model.metadata.delete(this.key);
- } else {
- cell.model.metadata.set(this.key, value);
- }
- };
- private _changeGuard = false;
- private _validCellTypes: string[];
- private _getter: (cell: Cell) => JSONValue;
- private _setter: (cell: Cell, value: JSONValue) => void;
- private _default: JSONValue;
- }
- /**
- * The namespace for `KeySelector` static data.
- */
- export namespace KeySelector {
- /**
- * The options used to initialize a keyselector.
- */
- export interface IOptions {
- /**
- * The metadata key of interest.
- */
- key: string;
- /**
- * The map of options to values.
- *
- * #### Notes
- * If a value equals the default, choosing it may erase the key from the
- * metadata.
- */
- optionsMap: { [key: string]: JSONValue };
- /**
- * The optional title of the selector - defaults to capitalized `key`.
- */
- title?: string;
- /**
- * The optional valid cell types - defaults to all valid types.
- */
- validCellTypes?: nbformat.CellType[];
- /**
- * An optional value getter for the selector.
- *
- * @param cell - The currently active cell.
- *
- * @returns The appropriate value for the selector.
- */
- getter?: (cell: Cell) => JSONValue;
- /**
- * An optional value setter for the selector.
- *
- * @param cell - The currently active cell.
- *
- * @param value - The value of the selector.
- *
- * #### Notes
- * The setter should set the appropriate metadata value given the value of
- * the selector.
- */
- setter?: (cell: Cell, value: JSONValue) => void;
- /**
- * Default value for default setters and getters if value is not found.
- */
- default?: JSONValue;
- }
- }
- /**
- * Create a slideshow selector.
- */
- export function createSlideShowSelector(): KeySelector {
- let options: KeySelector.IOptions = {
- key: 'slideshow',
- title: 'Slide Type',
- optionsMap: {
- '-': null,
- Slide: 'slide',
- 'Sub-Slide': 'subslide',
- Fragment: 'fragment',
- Skip: 'skip',
- Notes: 'notes'
- },
- getter: cell => {
- let value = cell.model.metadata.get('slideshow');
- return value && (value as JSONObject)['slide_type'];
- },
- setter: (cell, value) => {
- let data = cell.model.metadata.get('slideshow') || Object.create(null);
- if (value === null) {
- // Make a shallow copy so we aren't modifying the original metadata.
- data = { ...data };
- delete data.slide_type;
- } else {
- data = { ...data, slide_type: value };
- }
- if (Object.keys(data).length > 0) {
- cell.model.metadata.set('slideshow', data);
- } else {
- cell.model.metadata.delete('slideshow');
- }
- }
- };
- return new KeySelector(options);
- }
- /**
- * Create an nbconvert selector.
- */
- export function createNBConvertSelector(optionsMap: {
- [key: string]: JSONValue;
- }): KeySelector {
- return new KeySelector({
- key: 'raw_mimetype',
- title: 'Raw NBConvert Format',
- optionsMap: optionsMap,
- validCellTypes: ['raw']
- });
- }
- }
- /**
- * A namespace for private data.
- */
- namespace Private {
- /**
- * An object which holds a widget and its sort rank.
- */
- export interface IRankItem<T extends Widget = Widget> {
- /**
- * The widget for the item.
- */
- widget: T;
- /**
- * The sort rank of the menu.
- */
- rank: number;
- }
- /**
- * A comparator function for widget rank items.
- */
- export function itemCmp(first: IRankItem, second: IRankItem): number {
- return first.rank - second.rank;
- }
- /**
- * Create the node for a KeySelector.
- */
- export function createSelectorNode(
- options: NotebookTools.KeySelector.IOptions
- ): HTMLElement {
- let name = options.key;
- let title = options.title || name[0].toLocaleUpperCase() + name.slice(1);
- let optionNodes: VirtualNode[] = [];
- for (let label in options.optionsMap) {
- let value = JSON.stringify(options.optionsMap[label]);
- optionNodes.push(h.option({ value }, label));
- }
- let node = VirtualDOM.realize(
- h.div({}, h.label(title, h.select({}, optionNodes)))
- );
- Styling.styleNode(node);
- return node;
- }
- }
|