index.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { ILabShell } from '@jupyterlab/application';
  4. import { ReactWidget } from '@jupyterlab/apputils';
  5. import { Signal, ISignal } from '@lumino/signaling';
  6. import { Widget, FocusTracker, SingletonLayout } from '@lumino/widgets';
  7. import * as React from 'react';
  8. import { IPropertyInspector, IPropertyInspectorProvider } from './token';
  9. export { IPropertyInspector, IPropertyInspectorProvider };
  10. /**
  11. * The implementation of the PropertyInspector.
  12. */
  13. abstract class PropertyInspectorProvider extends Widget
  14. implements IPropertyInspectorProvider {
  15. /**
  16. * Construct a new Property Inspector.
  17. */
  18. constructor() {
  19. super();
  20. this.addClass('jp-PropertyInspector');
  21. this._tracker = new FocusTracker();
  22. this._tracker.currentChanged.connect(this._onCurrentChanged, this);
  23. }
  24. /**
  25. * Register a widget in the property inspector provider.
  26. *
  27. * @param widget The owner widget to register.
  28. */
  29. register(widget: Widget): IPropertyInspector {
  30. if (this._inspectors.has(widget)) {
  31. throw new Error('Widget is already registered');
  32. }
  33. const inspector = new Private.PropertyInspector(widget);
  34. widget.disposed.connect(this._onWidgetDisposed, this);
  35. this._inspectors.set(widget, inspector);
  36. inspector.onAction.connect(this._onInspectorAction, this);
  37. this._tracker.add(widget);
  38. return inspector;
  39. }
  40. /**
  41. * The current widget being tracked by the inspector.
  42. */
  43. protected get currentWidget(): Widget | null {
  44. return this._tracker.currentWidget;
  45. }
  46. /**
  47. * Refresh the content for the current widget.
  48. */
  49. protected refresh(): void {
  50. const current = this._tracker.currentWidget;
  51. if (!current) {
  52. this.setContent(null);
  53. return;
  54. }
  55. const inspector = this._inspectors.get(current);
  56. if (inspector) {
  57. this.setContent(inspector.content);
  58. }
  59. }
  60. /**
  61. * Show the provider panel.
  62. */
  63. protected abstract showPanel(): void;
  64. /**
  65. * Set the content of the provider.
  66. */
  67. protected abstract setContent(content: Widget | null): void;
  68. /**
  69. * Handle the disposal of a widget.
  70. */
  71. private _onWidgetDisposed(sender: Widget): void {
  72. const inspector = this._inspectors.get(sender);
  73. if (inspector) {
  74. inspector.dispose();
  75. this._inspectors.delete(sender);
  76. }
  77. }
  78. /**
  79. * Handle inspector actions.
  80. */
  81. private _onInspectorAction(
  82. sender: Private.PropertyInspector,
  83. action: Private.PropertyInspectorAction
  84. ) {
  85. const owner = sender.owner;
  86. const current = this._tracker.currentWidget;
  87. switch (action) {
  88. case 'content':
  89. if (current === owner) {
  90. this.setContent(sender.content);
  91. }
  92. break;
  93. case 'dispose':
  94. if (owner) {
  95. this._tracker.remove(owner);
  96. this._inspectors.delete(owner);
  97. }
  98. break;
  99. case 'show-panel':
  100. if (current === owner) {
  101. this.showPanel();
  102. }
  103. break;
  104. default:
  105. throw new Error('Unsupported inspector action');
  106. }
  107. }
  108. /**
  109. * Handle a change to the current widget in the tracker.
  110. */
  111. private _onCurrentChanged(): void {
  112. const current = this._tracker.currentWidget;
  113. if (current) {
  114. const inspector = this._inspectors.get(current);
  115. const content = inspector!.content;
  116. this.setContent(content);
  117. } else {
  118. this.setContent(null);
  119. }
  120. }
  121. private _tracker = new FocusTracker();
  122. private _inspectors = new Map<Widget, Private.PropertyInspector>();
  123. }
  124. /**
  125. * A class that adds a property inspector provider to the
  126. * JupyterLab sidebar.
  127. */
  128. export class SideBarPropertyInspectorProvider extends PropertyInspectorProvider {
  129. /**
  130. * Construct a new Side Bar Property Inspector.
  131. */
  132. constructor(labshell: ILabShell, placeholder?: Widget) {
  133. super();
  134. this._labshell = labshell;
  135. const layout = (this.layout = new SingletonLayout());
  136. if (placeholder) {
  137. this._placeholder = placeholder;
  138. } else {
  139. this._placeholder = new Widget();
  140. this._placeholder.node.textContent = 'No properties to inspect.';
  141. this._placeholder.addClass('jp-PropertyInspector-placeholder');
  142. }
  143. layout.widget = this._placeholder;
  144. labshell.currentChanged.connect(this._onShellCurrentChanged, this);
  145. }
  146. /**
  147. * Set the content of the sidebar panel.
  148. */
  149. protected setContent(content: Widget | null): void {
  150. const layout = this.layout as SingletonLayout;
  151. if (layout.widget) {
  152. layout.widget.removeClass('jp-PropertyInspector-content');
  153. layout.removeWidget(layout.widget);
  154. }
  155. if (!content) {
  156. content = this._placeholder;
  157. }
  158. content.addClass('jp-PropertyInspector-content');
  159. layout.widget = content;
  160. }
  161. /**
  162. * Show the sidebar panel.
  163. */
  164. showPanel(): void {
  165. this._labshell.activateById(this.id);
  166. }
  167. /**
  168. * Handle the case when the current widget is not in our tracker.
  169. */
  170. private _onShellCurrentChanged(): void {
  171. const current = this.currentWidget;
  172. if (!current) {
  173. this.setContent(null);
  174. return;
  175. }
  176. const currentShell = this._labshell.currentWidget;
  177. if (currentShell?.node.contains(current.node)) {
  178. this.refresh();
  179. } else {
  180. this.setContent(null);
  181. }
  182. }
  183. private _labshell: ILabShell;
  184. private _placeholder: Widget;
  185. }
  186. /**
  187. * A namespace for module private data.
  188. */
  189. namespace Private {
  190. /**
  191. * A type alias for the actions a property inspector can take.
  192. */
  193. export type PropertyInspectorAction = 'content' | 'dispose' | 'show-panel';
  194. /**
  195. * An implementation of the property inspector used by the
  196. * property inspector provider.
  197. */
  198. export class PropertyInspector implements IPropertyInspector {
  199. /**
  200. * Construct a new property inspector.
  201. */
  202. constructor(owner: Widget) {
  203. this._owner = owner;
  204. }
  205. /**
  206. * The owner widget for the property inspector.
  207. */
  208. get owner(): Widget | null {
  209. return this._owner;
  210. }
  211. /**
  212. * The current content for the property inspector.
  213. */
  214. get content(): Widget | null {
  215. return this._content;
  216. }
  217. /**
  218. * Whether the property inspector is disposed.
  219. */
  220. get isDisposed() {
  221. return this._isDisposed;
  222. }
  223. /**
  224. * A signal used for actions related to the property inspector.
  225. */
  226. get onAction(): ISignal<PropertyInspector, PropertyInspectorAction> {
  227. return this._onAction;
  228. }
  229. /**
  230. * Show the property inspector panel.
  231. */
  232. showPanel(): void {
  233. if (this._isDisposed) {
  234. return;
  235. }
  236. this._onAction.emit('show-panel');
  237. }
  238. /**
  239. * Render the property inspector content.
  240. */
  241. render(widget: Widget | React.ReactElement): void {
  242. if (this._isDisposed) {
  243. return;
  244. }
  245. if (widget instanceof Widget) {
  246. this._content = widget;
  247. } else {
  248. this._content = ReactWidget.create(widget);
  249. }
  250. this._onAction.emit('content');
  251. }
  252. /**
  253. * Dispose of the property inspector.
  254. */
  255. dispose(): void {
  256. if (this._isDisposed) {
  257. return;
  258. }
  259. this._isDisposed = true;
  260. this._content = null;
  261. this._owner = null;
  262. Signal.clearData(this);
  263. }
  264. private _isDisposed = false;
  265. private _content: Widget | null = null;
  266. private _owner: Widget | null = null;
  267. private _onAction = new Signal<
  268. PropertyInspector,
  269. Private.PropertyInspectorAction
  270. >(this);
  271. }
  272. }