index.ts 8.0 KB

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