Skip to content

Instantly share code, notes, and snippets.

@blink1073

blink1073/index.ts

Last active Nov 2, 2019
Embed
What would you like to do?
property inspector
import {
JupyterFrontEnd, JupyterFrontEndPlugin, ILabShell
} from '@jupyterlab/application';
import {
MainAreaWidget, ReactWidget
} from '@jupyterlab/apputils';
import {
Token
} from '@phosphor/coreutils';
import {
IDisposable
} from '@phosphor/disposable';
import {
Signal, ISignal
} from '@phosphor/signaling';
import {
Widget, FocusTracker, SingletonLayout
} from '@phosphor/widgets';
import * as React from 'react';
/**
* A property inspector interface provided when registering
* to a property inspector provider. Allows an owner widget
* to set the property inspector content for itself.
*/
export
interface IPropertyInspector extends IDisposable {
/*
* Render the property inspector content.
*
* If the owner widget is not the most recently focused,
* The content will not be shown until that widget
* is focused.
*
* @param content - the widget or react element to render.
*/
render(content: Widget | React.ReactElement): void;
/**
* Show the property inspector panel.
*
* If the owner widget is not the most recently focused,
* this is a no-op. It should be triggered by a user
* action.
*/
showPanel(): void;
}
/**
* A provider for property inspectors.
*/
export
interface IPropertyInspectorProvider {
/**
* Register a widget in the property inspector provider.
*
* @param widget The owner widget whose properties will be inspected.
*
* ## Notes
* Only one property inspector can be provided for each widget.
* Registering the same widget twice will result in an error.
* A widget can be unregistered by disposing of its property
* inspector.
*/
register(widget: Widget): IPropertyInspector;
}
/**
* The property inspector provider token.
*/
export const IPropertyInspectorProvider = new Token<IPropertyInspectorProvider>(
'@sagemaker-ui:IPropertyInspectorProvider'
);
/**
* The implementation of the PropertyInspector.
*/
abstract class PropertyInspectorProvider implements IPropertyInspectorProvider {
constructor() {
this._tracker = new FocusTracker();
this._tracker.currentChanged.connect(this._onCurrentChanged, this);
}
/**
* Register a widget in the property inspector provider.
*
* @param widget The owner widget to register.
*/
register(widget: Widget): IPropertyInspector {
if (this._inspectors.has(widget)) {
throw new Error('Widget is already registered');
}
const inspector = new Private.PropertyInspector(widget);
widget.disposed.connect(this._onWidgetDisposed, this);
this._inspectors.set(widget, inspector);
inspector.onAction.connect(this._onInspectorAction, this);
this._tracker.add(widget);
return inspector;
}
/**
* The current widget being tracked by the inspector.
*/
protected get currentWidget(): Widget {
return this._tracker.currentWidget;
}
/**
* Refresh the content for the current widget.
*/
protected refresh(): void {
const current = this._tracker.currentWidget;
const inspector = this._inspectors.get(current);
this.setContent(inspector.content);
}
/**
* Show the provider panel.
*/
protected abstract showPanel(): void;
/**
* Set the content of the provider.
*/
protected abstract setContent(content: Widget | null): void;
/**
* Handle the disposal of a widget.
*/
private _onWidgetDisposed(sender: Widget): void {
const inspector = this._inspectors.get(sender);
if (inspector) {
inspector.dispose();
}
}
/**
* Handle the disposal of a widget.
*/
private _onInspectorAction(sender: Private.PropertyInspector, action: Private.PropertyInspectorAction) {
const owner = sender.owner;
const current = this._tracker.currentWidget;
switch (action) {
case 'content':
if (current === owner) {
this.setContent(sender.content);
}
break;
case 'dispose':
this._tracker.remove(owner);
this._inspectors.delete(owner);
break;
case 'showPanel':
if (current == owner) {
this.showPanel();
}
break;
}
}
/**
* Handle a change to the current widget in the tracker.
*/
private _onCurrentChanged(): void {
const current = this._tracker.currentWidget;
const inspector = this._inspectors.get(current);
const content = inspector.content;
this.setContent(content);
}
private _tracker = new FocusTracker();
private _inspectors = new Map<Widget, Private.PropertyInspector>();
}
/**
* A class that adds a property inspector provider to the
* JupyterLab sidebar.
*/
class SideBarPropertyInspectorProvider extends PropertyInspectorProvider {
constructor(labshell: ILabShell, placeholder?: Widget) {
super();
this._labshell = labshell;
this._providerWidget.id = 'property_inspector';
this._providerWidget.addClass('jp-PropertyInspector');
labshell.add(this._providerWidget, 'right');
this._providerWidget.title.label = 'Props';
const layout = this._providerWidget.layout = new SingletonLayout();
if (placeholder) {
this._placeholder = placeholder;
} else {
this._placeholder = new Widget();
this._placeholder.node.textContent = '<No Properties to Inspect>';
}
this._placeholder.addClass('jp-PropertyInspector-placeholder');
layout.widget = this._placeholder;
labshell.currentChanged.connect(this._onShellCurrentChanged, this);
}
/**
* Set the content of the sidebar panel.
*/
protected setContent(content: Widget | null): void {
const layout = (this._providerWidget.layout as SingletonLayout);
if (layout.widget) {
layout.widget.removeClass('jp-PropertyInspector-content');
layout.removeWidget(layout.widget);
}
if (!content) {
content = this._placeholder;
}
content.addClass('jp-PropertyInspector-content');
layout.widget = content;
}
/**
* Show the sidebar panel.
*/
showPanel(): void {
this._labshell.expandRight();
this._labshell.activateById(this._providerWidget.id);
}
/**
* Handle the case when the current widget is not in our tracker.
*/
private _onShellCurrentChanged(): void {
const current = this.currentWidget;
const currentShell = this._labshell.currentWidget;
if (!currentShell.node.contains(current.node)) {
console.log('clearing the content');
this.setContent(null);
} else {
this.refresh();
}
}
private _labshell: ILabShell;
private _placeholder: Widget;
private _providerWidget = new Widget();
}
/**
* Initialization data for the property_inspector extension.
*/
const inspectorExtension: JupyterFrontEndPlugin<IPropertyInspectorProvider> = {
id: 'property_inspector',
autoStart: true,
requires: [ILabShell],
provides: IPropertyInspectorProvider,
activate: (app: JupyterFrontEnd, labshell: ILabShell) => {
return new SideBarPropertyInspectorProvider(labshell);
}
};
/**
* Initialization data for the property_inspector user.
*/
const userExtension: JupyterFrontEndPlugin<void> = {
id: 'property_inspector_user',
autoStart: true,
requires: [IPropertyInspectorProvider],
activate: (app: JupyterFrontEnd, provider: IPropertyInspectorProvider) => {
const pcontent = new Widget();
const pwidget = new MainAreaWidget({ content: pcontent });
const pinspector = provider.register(pwidget);
pwidget.id = 'test-phosphor';
pwidget.title.label = 'Phosphor';
const pinner = new Widget();
pinner.node.innerHTML = '<h1>Hi from Phosphor</h1>';
const rcontent = new Widget();
const rwidget = new MainAreaWidget({ content: rcontent });
const rinspector = provider.register(rwidget);
rwidget.id = 'test-react';
rwidget.title.label = 'React';
const rinner = React.createElement('h1', null, 'Hi from react');
const ocontent = new Widget();
const owidget = new MainAreaWidget({ content: ocontent });
owidget.id = 'test-other';
owidget.title.label = 'Other';
rcontent.node.onclick = () => {
console.log('click rcontent');
rinspector.render(rinner);
}
pcontent.node.onclick = () => {
console.log('click pcontent');
pinspector.render(pinner);
pinspector.showPanel();
}
app.restored.then(() => {
app.shell.add(rwidget, 'main');
app.shell.add(pwidget, 'main');
app.shell.add(owidget, 'main');
app.shell.activateById(pwidget.id);
console.log('added widgets');
});
}
}
const extensions = [inspectorExtension, userExtension];
export default extensions;
/**
* A namespace for module private data.
*/
namespace Private {
/**
* A type alias for the actions a property inspector can take.
*/
export type PropertyInspectorAction = 'content' | 'dispose' | 'showPanel';
/**
* An implementation of the property inspector used by the
* property inspector provider.
*/
export
class PropertyInspector implements IPropertyInspector {
/**
* Construct a new property inspector.
*/
constructor(owner: Widget) {
this._owner = owner;
}
/**
* The owner widget for the property inspector.
*/
get owner(): Widget | null {
return this._owner;
}
/**
* The current content for the property inspector.
*/
get content(): Widget | null {
return this._content;
}
/**
* Whether the property inspector is disposed.
*/
get isDisposed() {
return this._isDisposed;
}
/**
* A signal used for actions related to the property inspector.
*/
get onAction(): ISignal<PropertyInspector, PropertyInspectorAction> {
return this._onAction;
}
/**
* Show the property inspector panel.
*/
showPanel(): void {
if (this._isDisposed) {
return;
}
this._onAction.emit('showPanel');
}
/**
* Render the property inspector content.
*/
render(widget: Widget | React.ReactElement): void {
if (this._isDisposed) {
return;
}
if (widget instanceof Widget) {
this._content = widget;
} else {
this._content = ReactWidget.create(widget);
}
this._onAction.emit('content');
}
/**
* Dispose of the property inspector.
*/
dispose(): void {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._content = null;
this._owner = null;
Signal.clearData(this);
}
private _isDisposed = false;
private _content: Widget | null = null;
private _owner: Widget | null = null;
private _onAction = new Signal<PropertyInspector, Private.PropertyInspectorAction>(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.