inspector.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Message
  5. } from 'phosphor/lib/core/messaging';
  6. import {
  7. clearSignalData, ISignal
  8. } from 'phosphor/lib/core/signaling';
  9. import {
  10. Token
  11. } from 'phosphor/lib/core/token';
  12. import {
  13. Panel
  14. } from 'phosphor/lib/ui/panel';
  15. import {
  16. TabPanel
  17. } from 'phosphor/lib/ui/tabpanel';
  18. import {
  19. Widget
  20. } from 'phosphor/lib/ui/widget';
  21. import {
  22. Toolbar, ToolbarButton
  23. } from '../toolbar';
  24. /**
  25. * The class name added to inspector panels.
  26. */
  27. const PANEL_CLASS = 'jp-Inspector';
  28. /**
  29. * The class name added to inspector child item widgets.
  30. */
  31. const ITEM_CLASS = 'jp-InspectorItem';
  32. /**
  33. * The class name added to inspector child item widgets' content.
  34. */
  35. const CONTENT_CLASS = 'jp-InspectorItem-content';
  36. /**
  37. * The history clear button class name.
  38. */
  39. const CLEAR_CLASS = 'jp-InspectorItem-clear';
  40. /**
  41. * The back button class name.
  42. */
  43. const BACK_CLASS = 'jp-InspectorItem-back';
  44. /**
  45. * The forward button class name.
  46. */
  47. const FORWARD_CLASS = 'jp-InspectorItem-forward';
  48. /* tslint:disable */
  49. /**
  50. * The inspector panel token.
  51. */
  52. export
  53. const IInspector = new Token<IInspector>('jupyter.services.inspector');
  54. /* tslint:enable */
  55. /**
  56. * An interface for an inspector panel.
  57. */
  58. export
  59. interface IInspector {
  60. /**
  61. * The source of events the inspector panel listens for.
  62. */
  63. source: Inspector.IInspectable;
  64. }
  65. /**
  66. * A panel which contains a set of inspectors.
  67. */
  68. export
  69. class Inspector extends TabPanel implements IInspector {
  70. /**
  71. * Construct an inspector.
  72. */
  73. constructor(options: Inspector.IOptions) {
  74. super();
  75. this.addClass(PANEL_CLASS);
  76. let items = options.items || [];
  77. // Create inspector child items and add them to the inspectors panel.
  78. items.forEach(value => {
  79. let widget = value.widget || new InspectorItem();
  80. widget.rank = value.rank;
  81. widget.remembers = !!value.remembers;
  82. widget.title.closable = false;
  83. widget.title.label = value.name;
  84. if (value.className) {
  85. widget.addClass(value.className);
  86. }
  87. this._items[value.type] = widget;
  88. this.addWidget(widget);
  89. });
  90. if (items.length < 2) {
  91. this.tabBar.hide();
  92. }
  93. }
  94. /**
  95. * The source of events the inspector panel listens for.
  96. */
  97. get source(): Inspector.IInspectable {
  98. return this._source;
  99. }
  100. set source(source: Inspector.IInspectable) {
  101. if (this._source === source) {
  102. return;
  103. }
  104. // Disconnect old signal handler.
  105. if (this._source) {
  106. this._source.inspected.disconnect(this.onInspectorUpdate, this);
  107. this._source.disposed.disconnect(this.onSourceDisposed, this);
  108. }
  109. // Clear the inspector child items (but maintain history) if necessary.
  110. if (this._items) {
  111. Object.keys(this._items).forEach(i => this._items[i].content = null);
  112. }
  113. this._source = source;
  114. // Connect new signal handler.
  115. if (this._source) {
  116. this._source.inspected.connect(this.onInspectorUpdate, this);
  117. this._source.disposed.connect(this.onSourceDisposed, this);
  118. }
  119. }
  120. /**
  121. * Dispose of the resources held by the widget.
  122. */
  123. dispose(): void {
  124. if (this._items == null) {
  125. return;
  126. }
  127. let items = this._items;
  128. this._items = null;
  129. this.source = null;
  130. // Dispose the inspector child items.
  131. Object.keys(items).forEach(i => { items[i].dispose(); });
  132. super.dispose();
  133. }
  134. /**
  135. * Handle `'activate-request'` messages.
  136. */
  137. protected onActivateRequest(msg: Message): void {
  138. this.node.tabIndex = -1;
  139. this.node.focus();
  140. }
  141. /**
  142. * Handle `'close-request'` messages.
  143. */
  144. protected onCloseRequest(msg: Message): void {
  145. super.onCloseRequest(msg);
  146. this.dispose();
  147. }
  148. /**
  149. * Handle inspector update signals.
  150. */
  151. protected onInspectorUpdate(sender: any, args: Inspector.IInspectorUpdate): void {
  152. let widget = this._items[args.type];
  153. if (!widget) {
  154. return;
  155. }
  156. // Update the content of the inspector widget.
  157. widget.content = args.content;
  158. let items = this._items;
  159. // If any inspector with a higher rank has content, do not change focus.
  160. if (args.content) {
  161. for (let type in items) {
  162. let inspector = this._items[type];
  163. if (inspector.rank < widget.rank && inspector.content) {
  164. return;
  165. }
  166. }
  167. this.currentWidget = widget;
  168. return;
  169. }
  170. // If the inspector was emptied, show the next best ranked inspector.
  171. let lowest = Infinity;
  172. widget = null;
  173. for (let type in items) {
  174. let inspector = this._items[type];
  175. if (inspector.rank < lowest && inspector.content) {
  176. lowest = inspector.rank;
  177. widget = inspector;
  178. }
  179. }
  180. if (widget) {
  181. this.currentWidget = widget;
  182. }
  183. }
  184. /**
  185. * Handle source disposed signals.
  186. */
  187. protected onSourceDisposed(sender: any, args: void): void {
  188. this.source = null;
  189. }
  190. private _items: { [type: string]: InspectorItem } = Object.create(null);
  191. private _source: Inspector.IInspectable = null;
  192. }
  193. /**
  194. * A namespace for Inspector statics.
  195. */
  196. export
  197. namespace Inspector {
  198. /**
  199. * The definition of an inspector.
  200. */
  201. export
  202. interface IInspectable {
  203. /**
  204. * A signal emitted when the handler is disposed.
  205. */
  206. disposed: ISignal<any, void>;
  207. /**
  208. * A signal emitted when inspector should clear all items with no history.
  209. */
  210. ephemeralCleared: ISignal<any, void>;
  211. /**
  212. * A signal emitted when an inspector value is generated.
  213. */
  214. inspected: ISignal<any, IInspectorUpdate>;
  215. }
  216. /**
  217. * An update value for code inspectors.
  218. */
  219. export
  220. interface IInspectorUpdate {
  221. /**
  222. * The content being sent to the inspector for display.
  223. */
  224. content: Widget;
  225. /**
  226. * The type of the inspector being updated.
  227. */
  228. type: string;
  229. }
  230. /**
  231. * The definition of a child item of an inspector panel.
  232. */
  233. export
  234. interface IInspectorItem {
  235. /**
  236. * The optional class name added to the inspector child widget.
  237. */
  238. className?: string;
  239. /**
  240. * The display name of the inspector child.
  241. */
  242. name: string;
  243. /**
  244. * The rank order of display priority for inspector updates. A lower rank
  245. * denotes a higher display priority.
  246. */
  247. rank: number;
  248. /**
  249. * A flag that indicates whether the inspector remembers history.
  250. *
  251. * The default value is `false`.
  252. */
  253. remembers?: boolean;
  254. /**
  255. * The type of the inspector.
  256. */
  257. type: string;
  258. /**
  259. * The optional inspector child item instance.
  260. */
  261. widget?: InspectorItem;
  262. }
  263. /**
  264. * The initialization options for an inspector panel.
  265. */
  266. export
  267. interface IOptions {
  268. /**
  269. * The list of available child inspectors items for code introspection.
  270. *
  271. * #### Notes
  272. * The order of items in the inspectors array is the order in which they
  273. * will be rendered in the inspectors tab panel.
  274. */
  275. items?: IInspectorItem[];
  276. }
  277. }
  278. /**
  279. * A code inspector child widget.
  280. */
  281. export
  282. class InspectorItem extends Panel {
  283. /**
  284. * Construct an inspector widget.
  285. */
  286. constructor() {
  287. super();
  288. this.addClass(ITEM_CLASS);
  289. this._toolbar = this._createToolbar();
  290. this.addWidget(this._toolbar);
  291. }
  292. /**
  293. * The text of the inspector.
  294. */
  295. get content(): Widget {
  296. return this._content;
  297. }
  298. set content(newValue: Widget) {
  299. if (newValue === this._content) {
  300. return;
  301. }
  302. if (this._content) {
  303. if (this._remembers) {
  304. this._content.hide();
  305. } else {
  306. this._content.dispose();
  307. }
  308. }
  309. this._content = newValue;
  310. if (this._content) {
  311. this._content.addClass(CONTENT_CLASS);
  312. this.addWidget(this._content);
  313. if (this.remembers) {
  314. this._history.push(newValue);
  315. this._index++;
  316. }
  317. }
  318. }
  319. /**
  320. * A flag that indicates whether the inspector remembers history.
  321. */
  322. get remembers(): boolean {
  323. return this._remembers;
  324. }
  325. set remembers(newValue: boolean) {
  326. if (newValue === this._remembers) {
  327. return;
  328. }
  329. this._clear();
  330. this._remembers = newValue;
  331. if (!this._remembers) {
  332. this._history = null;
  333. }
  334. this.update();
  335. }
  336. /**
  337. * The display rank of the inspector.
  338. */
  339. get rank(): number {
  340. return this._rank;
  341. }
  342. set rank(newValue: number) {
  343. this._rank = newValue;
  344. }
  345. /**
  346. * Dispose of the resources held by the widget.
  347. */
  348. dispose(): void {
  349. if (this._toolbar === null) {
  350. return;
  351. }
  352. let toolbar = this._toolbar;
  353. let history = this._history;
  354. this._toolbar = null;
  355. this._history = null;
  356. history.forEach(widget => widget.dispose());
  357. toolbar.dispose();
  358. super.dispose();
  359. }
  360. /**
  361. * Navigate back in history.
  362. */
  363. private _back(): void {
  364. if (this._history.length) {
  365. this._navigateTo(Math.max(this._index - 1, 0));
  366. }
  367. }
  368. /**
  369. * Clear history.
  370. */
  371. private _clear(): void {
  372. if (this._history) {
  373. this._history.forEach(widget => widget.dispose());
  374. }
  375. this._history = [];
  376. this._index = -1;
  377. }
  378. /**
  379. * Navigate forward in history.
  380. */
  381. private _forward(): void {
  382. if (this._history.length) {
  383. this._navigateTo(Math.min(this._index + 1, this._history.length - 1));
  384. }
  385. }
  386. /**
  387. * Create a history toolbar.
  388. */
  389. private _createToolbar(): Toolbar<Widget> {
  390. let toolbar = new Toolbar();
  391. if (!this._remembers) {
  392. return toolbar;
  393. }
  394. let clear = new ToolbarButton({
  395. className: CLEAR_CLASS,
  396. onClick: () => { this._clear(); },
  397. tooltip: 'Clear history.'
  398. });
  399. toolbar.addItem('clear', clear);
  400. let back = new ToolbarButton({
  401. className: BACK_CLASS,
  402. onClick: () => { this._back(); },
  403. tooltip: 'Navigate back in history.'
  404. });
  405. toolbar.addItem('back', back);
  406. let forward = new ToolbarButton({
  407. className: FORWARD_CLASS,
  408. onClick: () => { this._forward(); },
  409. tooltip: 'Navigate forward in history.'
  410. });
  411. toolbar.addItem('forward', forward);
  412. return toolbar;
  413. }
  414. /**
  415. * Navigate to a known index in history.
  416. */
  417. private _navigateTo(index: number): void {
  418. if (this._content) {
  419. this._content.hide();
  420. }
  421. this._content = this._history[index];
  422. this._index = index;
  423. this._content.show();
  424. }
  425. private _content: Widget = null;
  426. private _history: Widget[] = null;
  427. private _index: number = -1;
  428. private _rank: number = Infinity;
  429. private _remembers: boolean = false;
  430. private _toolbar: Toolbar<Widget> = null;
  431. }