// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { Message } from 'phosphor-messaging'; import { Panel } from 'phosphor-panel'; import { ISignal, Signal, clearSignalData } from 'phosphor-signaling'; import { TabPanel } from 'phosphor-tabs'; import { Widget } from 'phosphor-widget'; import { NotebookToolbar, ToolbarButton } from '../notebook/notebook/toolbar'; /** * The class name added to inspector panels. */ const PANEL_CLASS = 'jp-Inspector'; /** * The class name added to inspector child item widgets. */ const ITEM_CLASS = 'jp-InspectorItem'; /** * The class name added to inspector child item widgets' content. */ const CONTENT_CLASS = 'jp-InspectorItem-content'; /** * The history clear button class name. */ const CLEAR_CLASS = 'jp-InspectorItem-clear'; /** * The back button class name. */ const BACK_CLASS = 'jp-InspectorItem-back'; /** * The forward button class name. */ const FORWARD_CLASS = 'jp-InspectorItem-forward'; /** * The orientation toggle bottom button class name. */ const BOTTOM_TOGGLE_CLASS = 'jp-InspectorItem-bottom'; /** * The orientation toggle right button class name. */ const RIGHT_TOGGLE_CLASS = 'jp-InspectorItem-right'; /** * A panel which contains a set of inspectors. */ export class Inspector extends TabPanel { /** * Construct an inspector. */ constructor(options: Inspector.IOptions) { super(); this.addClass(PANEL_CLASS); // Create console inspector widgets and add them to the inspectors panel. (options.items || []).forEach(value => { let widget = value.widget || new InspectorItem(); widget.orientation = this._orientation as Inspector.Orientation; widget.orientationToggled.connect(() => { this.orientation = 'vertical' ? 'horizontal' : 'vertical'; }); widget.rank = value.rank; widget.remember = !!value.remember; widget.title.closable = false; widget.title.text = value.name; if (value.className) { widget.addClass(value.className); } this._items[value.type] = widget; this.addChild(widget); }); } /** * Set the orientation of the inspector panel. */ get orientation(): Inspector.Orientation { return this._orientation; } set orientation(orientation: Inspector.Orientation) { if (this._orientation === orientation) { return; } this._orientation = orientation; Object.keys(this._items).forEach(i => { this._items[i].orientation = orientation; }); } /** * Set the reference to the semantic parent of the inspector panel. */ get reference(): Inspector.IInspectable { return this._reference; } set reference(reference: Inspector.IInspectable) { if (this._reference === reference) { return; } // Disconnect old signal handler. if (this.reference) { this._reference.inspected.disconnect(this.onInspectorUpdate, this); } // Clear the inspector child items (but maintain history). Object.keys(this._items).forEach(i => this._items[i].content = null); this._reference = reference; // Connect new signal handler. if (this.reference) { this._reference.inspected.connect(this.onInspectorUpdate, this); } } /** * Dispose of the resources held by the widget. */ dispose(): void { if (this.isDisposed) { return; } // Dispose the inspector child items. Object.keys(this._items).forEach(i => this._items[i].dispose()); this._items = null; // Disconnect from reference. this.reference = null; super.dispose(); } /** * Handle inspector update signals. */ protected onInspectorUpdate(sender: any, args: Inspector.IInspectorUpdate): void { let widget = this._items[args.type]; if (!widget) { return; } // Update the content of the inspector widget. widget.content = args.content; let items = this._items; // If any inspector with a higher rank has content, do not change focus. if (args.content) { for (let type in items) { let inspector = this._items[type]; if (inspector.rank < widget.rank && inspector.content) { return; } } this.currentWidget = widget; return; } // If the inspector was emptied, show the next best ranked inspector. let lowest = Infinity; widget = null; for (let type in items) { let inspector = this._items[type]; if (inspector.rank < lowest && inspector.content) { lowest = inspector.rank; widget = inspector; } } if (widget) { this.currentWidget = widget; } } private _items: { [type: string]: InspectorItem } = Object.create(null); private _orientation: Inspector.Orientation = 'horizontal'; private _reference: Inspector.IInspectable = null; } /** * A namespace for Inspector statics. */ export namespace Inspector { /** * The orientation options of an inspector panel. */ export type Orientation = 'horizontal' | 'vertical'; /** * The definition of an inspector. */ export interface IInspectable { /** * A signal emitted when an inspector value is generated. */ inspected: ISignal; } /** * An update value for code inspectors. */ export interface IInspectorUpdate { /** * The content being sent to the inspector for display. */ content: Widget; /** * The type of the inspector being updated. */ type: string; } /** * The definition of a child item of an inspector panel. */ export interface IInspectorItem { /** * The optional class name added to the inspector child widget. */ className?: string; /** * The display name of the inspector child. */ name: string; /** * The rank order of display priority for inspector updates. A lower rank * denotes a higher display priority. */ rank: number; /** * A flag that indicates whether the inspector remembers history. * * The default value is `false`. */ remember?: boolean; /** * The type of the inspector. */ type: string; /** * The optional console inspector widget instance. */ widget?: InspectorItem; } /** * The initialization options for a console panel. */ export interface IOptions { /** * The list of available child inspectors items for code introspection. * * #### Notes * The order of items in the inspectors array is the order in which they * will be rendered in the inspectors tab panel. */ items?: IInspectorItem[]; /** * The orientation of the inspector panel. * * The default value is `'horizontal'`. */ orientation?: Orientation; } } /** * A code inspector child widget. */ export class InspectorItem extends Panel { /** * Construct an inspector widget. */ constructor() { super(); this.addClass(ITEM_CLASS); this.update(); } /** * The text of the inspector. */ get content(): Widget { return this._content; } set content(newValue: Widget) { if (newValue === this._content) { return; } if (this._content) { if (this._remember) { this._content.hide(); } else { this._content.dispose(); } } this._content = newValue; if (this._content) { this._content.addClass(CONTENT_CLASS); this.addChild(this._content); if (this.remember) { this._history.push(newValue); this._index++; } } } /** * The display orientation of the inspector widget. */ get orientation(): Inspector.Orientation { return this._orientation; } set orientation(newValue: Inspector.Orientation) { if (newValue === this._orientation) { return; } this._orientation = newValue; this.update(); } /** * A signal emitted when an inspector widget's orientation is toggled. */ get orientationToggled(): ISignal { return Private.orientationToggledSignal.bind(this); } /** * A flag that indicates whether the inspector remembers history. */ get remember(): boolean { return this._remember; } set remember(newValue: boolean) { if (newValue === this._remember) { return; } this._clear(); this._remember = newValue; if (!this.remember) { this._history = null; } this.update(); } /** * The display rank of the inspector. */ get rank(): number { return this._rank; } set rank(newValue: number) { this._rank = newValue; } /** * Dispose of the resources held by the widget. */ dispose(): void { if (this.isDisposed) { return; } clearSignalData(this); if (this._history) { this._history.forEach(widget => widget.dispose()); this._history = null; } if (this._toolbar) { this._toolbar.dispose(); } super.dispose(); } /** * Handle `update_request` messages. */ protected onUpdateRequest(msg: Message): void { if (this._toolbar) { this._toolbar.dispose(); } this._toolbar = this._createToolbar(); this.insertChild(0, this._toolbar); } /** * Navigate back in history. */ private _back(): void { if (this._history.length) { this._navigateTo(Math.max(this._index - 1, 0)); } } /** * Clear history. */ private _clear(): void { if (this._history) { this._history.forEach(widget => widget.dispose()); } this._history = []; this._index = -1; } /** * Navigate forward in history. */ private _forward(): void { if (this._history.length) { this._navigateTo(Math.min(this._index + 1, this._history.length - 1)); } } /** * Create a history toolbar. */ private _createToolbar(): NotebookToolbar { let toolbar = new NotebookToolbar(); let toggle = new ToolbarButton({ className: this.orientation === 'vertical' ? RIGHT_TOGGLE_CLASS : BOTTOM_TOGGLE_CLASS, onClick: () => this.orientationToggled.emit(void 0), tooltip: 'Toggle the inspector orientation.' }); toolbar.add('toggle', toggle); if (!this._remember) { return toolbar; } let clear = new ToolbarButton({ className: CLEAR_CLASS, onClick: () => this._clear(), tooltip: 'Clear history.' }); toolbar.add('clear', clear); let back = new ToolbarButton({ className: BACK_CLASS, onClick: () => this._back(), tooltip: 'Navigate back in history.' }); toolbar.add('back', back); let forward = new ToolbarButton({ className: FORWARD_CLASS, onClick: () => this._forward(), tooltip: 'Navigate forward in history.' }); toolbar.add('forward', forward); return toolbar; } /** * Navigate to a known index in history. */ private _navigateTo(index: number): void { if (this._content) { this._content.hide(); } this._content = this._history[index]; this._index = index; this._content.show(); } private _content: Widget = null; private _history: Widget[] = null; private _index: number = -1; private _orientation: Inspector.Orientation = 'horizontal'; private _rank: number = Infinity; private _remember: boolean = false; private _toolbar: NotebookToolbar = null; } /** * A namespace for inspector private data. */ namespace Private { /** * A signal emitted when an inspector's orientation is toggled. */ export const orientationToggledSignal = new Signal(); }