index.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. IConnectionLost,
  5. ILabShell,
  6. ILabStatus,
  7. ILayoutRestorer,
  8. IRouter,
  9. ConnectionLost,
  10. JupyterFrontEnd,
  11. JupyterFrontEndPlugin,
  12. JupyterLab,
  13. LabShell,
  14. LayoutRestorer,
  15. Router
  16. } from '@jupyterlab/application';
  17. import {
  18. Dialog,
  19. ICommandPalette,
  20. IWindowResolver,
  21. showDialog,
  22. showErrorMessage
  23. } from '@jupyterlab/apputils';
  24. import {
  25. PathExt,
  26. IStateDB,
  27. ISettingRegistry,
  28. URLExt
  29. } from '@jupyterlab/coreutils';
  30. import { each, iter, toArray } from '@phosphor/algorithm';
  31. import { Widget, DockLayout } from '@phosphor/widgets';
  32. import * as React from 'react';
  33. /**
  34. * The command IDs used by the application plugin.
  35. */
  36. namespace CommandIDs {
  37. export const activateNextTab: string = 'application:activate-next-tab';
  38. export const activatePreviousTab: string =
  39. 'application:activate-previous-tab';
  40. export const close = 'application:close';
  41. export const closeOtherTabs = 'application:close-other-tabs';
  42. export const closeRightTabs = 'application:close-right-tabs';
  43. export const closeAll: string = 'application:close-all';
  44. export const setMode: string = 'application:set-mode';
  45. export const toggleMode: string = 'application:toggle-mode';
  46. export const toggleLeftArea: string = 'application:toggle-left-area';
  47. export const toggleRightArea: string = 'application:toggle-right-area';
  48. export const togglePresentationMode: string =
  49. 'application:toggle-presentation-mode';
  50. export const tree: string = 'router:tree';
  51. export const switchSidebar = 'sidebar:switch';
  52. }
  53. /**
  54. * The main extension.
  55. */
  56. const main: JupyterFrontEndPlugin<void> = {
  57. id: '@jupyterlab/application-extension:main',
  58. requires: [ICommandPalette, IRouter, IWindowResolver],
  59. optional: [IConnectionLost],
  60. activate: (
  61. app: JupyterFrontEnd,
  62. palette: ICommandPalette,
  63. router: IRouter,
  64. resolver: IWindowResolver,
  65. connectionLost: IConnectionLost | undefined
  66. ) => {
  67. if (!(app instanceof JupyterLab)) {
  68. throw new Error(`${main.id} must be activated in JupyterLab.`);
  69. }
  70. // Requiring the window resolver guarantees that the application extension
  71. // only loads if there is a viable window name. Otherwise, the application
  72. // will short-circuit and ask the user to navigate away.
  73. const workspace = resolver.name;
  74. console.log(`Starting application in workspace: "${workspace}"`);
  75. // If there were errors registering plugins, tell the user.
  76. if (app.registerPluginErrors.length !== 0) {
  77. const body = (
  78. <pre>{app.registerPluginErrors.map(e => e.message).join('\n')}</pre>
  79. );
  80. void showErrorMessage('Error Registering Plugins', { message: body });
  81. }
  82. addCommands(app, palette);
  83. // If the application shell layout is modified,
  84. // trigger a refresh of the commands.
  85. app.shell.layoutModified.connect(() => {
  86. app.commands.notifyCommandChanged();
  87. });
  88. // If the connection to the server is lost, handle it with the
  89. // connection lost handler.
  90. connectionLost = connectionLost || ConnectionLost;
  91. app.serviceManager.connectionFailure.connect(connectionLost);
  92. const builder = app.serviceManager.builder;
  93. const build = () => {
  94. return builder
  95. .build()
  96. .then(() => {
  97. return showDialog({
  98. title: 'Build Complete',
  99. body: 'Build successfully completed, reload page?',
  100. buttons: [
  101. Dialog.cancelButton(),
  102. Dialog.warnButton({ label: 'RELOAD' })
  103. ]
  104. });
  105. })
  106. .then(result => {
  107. if (result.button.accept) {
  108. router.reload();
  109. }
  110. })
  111. .catch(err => {
  112. void showErrorMessage('Build Failed', {
  113. message: <pre>{err.message}</pre>
  114. });
  115. });
  116. };
  117. if (builder.isAvailable && builder.shouldCheck) {
  118. void builder.getStatus().then(response => {
  119. if (response.status === 'building') {
  120. return build();
  121. }
  122. if (response.status !== 'needed') {
  123. return;
  124. }
  125. const body = (
  126. <div>
  127. JupyterLab build is suggested:
  128. <br />
  129. <pre>{response.message}</pre>
  130. </div>
  131. );
  132. void showDialog({
  133. title: 'Build Recommended',
  134. body,
  135. buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'BUILD' })]
  136. }).then(result => (result.button.accept ? build() : undefined));
  137. });
  138. }
  139. const message =
  140. 'Are you sure you want to exit JupyterLab?\n' +
  141. 'Any unsaved changes will be lost.';
  142. // The spec for the `beforeunload` event is implemented differently by
  143. // the different browser vendors. Consequently, the `event.returnValue`
  144. // attribute needs to set in addition to a return value being returned.
  145. // For more information, see:
  146. // https://developer.mozilla.org/en/docs/Web/Events/beforeunload
  147. window.addEventListener('beforeunload', event => {
  148. if (app.status.isDirty) {
  149. return ((event as any).returnValue = message);
  150. }
  151. });
  152. },
  153. autoStart: true
  154. };
  155. /**
  156. * The default layout restorer provider.
  157. */
  158. const layout: JupyterFrontEndPlugin<ILayoutRestorer> = {
  159. id: '@jupyterlab/application-extension:layout',
  160. requires: [IStateDB, ILabShell],
  161. activate: (app: JupyterFrontEnd, state: IStateDB, labShell: ILabShell) => {
  162. const first = app.started;
  163. const registry = app.commands;
  164. const restorer = new LayoutRestorer({ first, registry, state });
  165. void restorer.fetch().then(saved => {
  166. labShell.restoreLayout(saved);
  167. labShell.layoutModified.connect(() => {
  168. void restorer.save(labShell.saveLayout());
  169. });
  170. });
  171. return restorer;
  172. },
  173. autoStart: true,
  174. provides: ILayoutRestorer
  175. };
  176. /**
  177. * The default URL router provider.
  178. */
  179. const router: JupyterFrontEndPlugin<IRouter> = {
  180. id: '@jupyterlab/application-extension:router',
  181. requires: [JupyterFrontEnd.IPaths],
  182. activate: (app: JupyterFrontEnd, paths: JupyterFrontEnd.IPaths) => {
  183. const { commands } = app;
  184. const base = paths.urls.base;
  185. const router = new Router({ base, commands });
  186. void app.started.then(() => {
  187. // Route the very first request on load.
  188. void router.route();
  189. // Route all pop state events.
  190. window.addEventListener('popstate', () => {
  191. void router.route();
  192. });
  193. });
  194. return router;
  195. },
  196. autoStart: true,
  197. provides: IRouter
  198. };
  199. /**
  200. * The tree route handler provider.
  201. */
  202. const tree: JupyterFrontEndPlugin<void> = {
  203. id: '@jupyterlab/application-extension:tree',
  204. autoStart: true,
  205. requires: [JupyterFrontEnd.IPaths, IRouter, IWindowResolver],
  206. activate: (
  207. app: JupyterFrontEnd,
  208. paths: JupyterFrontEnd.IPaths,
  209. router: IRouter,
  210. resolver: IWindowResolver
  211. ) => {
  212. const { commands } = app;
  213. const treePattern = new RegExp(`^${paths.urls.tree}([^?]+)`);
  214. const workspacePattern = new RegExp(
  215. `^${paths.urls.workspaces}[^?\/]+/tree/([^?]+)`
  216. );
  217. commands.addCommand(CommandIDs.tree, {
  218. execute: async (args: IRouter.ILocation) => {
  219. const treeMatch = args.path.match(treePattern);
  220. const workspaceMatch = args.path.match(workspacePattern);
  221. const match = treeMatch || workspaceMatch;
  222. const path = decodeURI(match[1]);
  223. // const { page, workspaces } = info.urls;
  224. const workspace = PathExt.basename(resolver.name);
  225. const url =
  226. (workspaceMatch
  227. ? URLExt.join(paths.urls.workspaces, workspace)
  228. : paths.urls.app) +
  229. args.search +
  230. args.hash;
  231. const immediate = true;
  232. // Remove the tree portion of the URL leaving the rest intact.
  233. router.navigate(url);
  234. try {
  235. await commands.execute('filebrowser:go-to-path', { path });
  236. await commands.execute('apputils:save-statedb', { immediate });
  237. } catch (error) {
  238. console.warn('Tree routing failed.', error);
  239. }
  240. }
  241. });
  242. router.register({ command: CommandIDs.tree, pattern: treePattern });
  243. router.register({ command: CommandIDs.tree, pattern: workspacePattern });
  244. }
  245. };
  246. /**
  247. * The default URL not found extension.
  248. */
  249. const notfound: JupyterFrontEndPlugin<void> = {
  250. id: '@jupyterlab/application-extension:notfound',
  251. requires: [JupyterFrontEnd.IPaths, IRouter],
  252. activate: (
  253. _: JupyterFrontEnd,
  254. paths: JupyterFrontEnd.IPaths,
  255. router: IRouter
  256. ) => {
  257. const bad = paths.urls.notFound;
  258. if (!bad) {
  259. return;
  260. }
  261. const base = router.base;
  262. const message = `
  263. The path: ${bad} was not found. JupyterLab redirected to: ${base}
  264. `;
  265. // Change the URL back to the base application URL.
  266. router.navigate('');
  267. void showErrorMessage('Path Not Found', { message });
  268. },
  269. autoStart: true
  270. };
  271. /**
  272. * Change the favicon changing based on the busy status;
  273. */
  274. const busy: JupyterFrontEndPlugin<void> = {
  275. id: '@jupyterlab/application-extension:faviconbusy',
  276. requires: [ILabStatus],
  277. activate: async (_: JupyterFrontEnd, status: ILabStatus) => {
  278. status.busySignal.connect((_, isBusy) => {
  279. const favicon = document.querySelector(
  280. `link[rel="icon"]${isBusy ? '.idle.favicon' : '.busy.favicon'}`
  281. ) as HTMLLinkElement;
  282. if (!favicon) {
  283. return;
  284. }
  285. const newFavicon = document.querySelector(
  286. `link${isBusy ? '.busy.favicon' : '.idle.favicon'}`
  287. ) as HTMLLinkElement;
  288. if (!newFavicon) {
  289. return;
  290. }
  291. // If we have the two icons with the special classes, then toggle them.
  292. if (favicon !== newFavicon) {
  293. favicon.rel = '';
  294. newFavicon.rel = 'icon';
  295. // Firefox doesn't seem to recognize just changing rel, so we also
  296. // reinsert the link into the DOM.
  297. newFavicon.parentNode.replaceChild(newFavicon, newFavicon);
  298. }
  299. });
  300. },
  301. autoStart: true
  302. };
  303. const SIDEBAR_ID = '@jupyterlab/application-extension:sidebar';
  304. /**
  305. * Keep user settings for where to show the side panels.
  306. */
  307. const sidebar: JupyterFrontEndPlugin<void> = {
  308. id: SIDEBAR_ID,
  309. activate: (
  310. app: JupyterFrontEnd,
  311. settingRegistry: ISettingRegistry,
  312. labShell: ILabShell
  313. ) => {
  314. type overrideMap = { [id: string]: 'left' | 'right' };
  315. let overrides: overrideMap = {};
  316. const handleLayoutOverrides = () => {
  317. each(labShell.widgets('left'), widget => {
  318. if (overrides[widget.id] && overrides[widget.id] === 'right') {
  319. labShell.add(widget, 'right');
  320. }
  321. });
  322. each(labShell.widgets('right'), widget => {
  323. if (overrides[widget.id] && overrides[widget.id] === 'left') {
  324. labShell.add(widget, 'left');
  325. }
  326. });
  327. };
  328. labShell.layoutModified.connect(handleLayoutOverrides);
  329. // Fetch overrides from the settings system.
  330. void Promise.all([settingRegistry.load(SIDEBAR_ID), app.restored]).then(
  331. ([settings]) => {
  332. overrides = (settings.get('overrides').composite as overrideMap) || {};
  333. settings.changed.connect(settings => {
  334. overrides =
  335. (settings.get('overrides').composite as overrideMap) || {};
  336. handleLayoutOverrides();
  337. });
  338. }
  339. );
  340. // Add a command to switch a side panels's side
  341. app.commands.addCommand(CommandIDs.switchSidebar, {
  342. label: 'Switch Sidebar Side',
  343. execute: () => {
  344. // First, try to find the right panel based on the
  345. // application context menu click,
  346. // If we can't find it there, look for use the active
  347. // left/right widgets.
  348. const contextNode: HTMLElement = app.contextMenuHitTest(
  349. node => !!node.dataset.id
  350. );
  351. let id: string;
  352. let side: 'left' | 'right';
  353. if (contextNode) {
  354. id = contextNode.dataset['id'];
  355. const leftPanel = document.getElementById('jp-left-stack');
  356. const node = document.getElementById(id);
  357. if (leftPanel && node && leftPanel.contains(node)) {
  358. side = 'right';
  359. } else {
  360. side = 'left';
  361. }
  362. } else if (document.body.dataset.leftSidebarWidget) {
  363. id = document.body.dataset.leftSidebarWidget;
  364. side = 'right';
  365. } else if (document.body.dataset.rightSidebarWidget) {
  366. id = document.body.dataset.rightSidebarWidget;
  367. side = 'left';
  368. }
  369. // Move the panel to the other side.
  370. const newOverrides = { ...overrides };
  371. newOverrides[id] = side;
  372. return settingRegistry.set(SIDEBAR_ID, 'overrides', newOverrides);
  373. }
  374. });
  375. // Add a context menu item to sidebar tabs.
  376. app.contextMenu.addItem({
  377. command: CommandIDs.switchSidebar,
  378. selector: '.jp-SideBar .p-TabBar-tab',
  379. rank: 500
  380. });
  381. },
  382. requires: [ISettingRegistry, ILabShell],
  383. autoStart: true
  384. };
  385. /**
  386. * Add the main application commands.
  387. */
  388. function addCommands(app: JupyterLab, palette: ICommandPalette): void {
  389. const { commands, contextMenu, shell } = app;
  390. const category = 'Main Area';
  391. // Returns the widget associated with the most recent contextmenu event.
  392. const contextMenuWidget = (): Widget => {
  393. const test = (node: HTMLElement) => !!node.dataset.id;
  394. const node = app.contextMenuHitTest(test);
  395. if (!node) {
  396. // Fall back to active widget if path cannot be obtained from event.
  397. return shell.currentWidget;
  398. }
  399. const matches = toArray(shell.widgets('main')).filter(
  400. widget => widget.id === node.dataset.id
  401. );
  402. if (matches.length < 1) {
  403. return shell.currentWidget;
  404. }
  405. return matches[0];
  406. };
  407. // Closes an array of widgets.
  408. const closeWidgets = (widgets: Array<Widget>): void => {
  409. widgets.forEach(widget => widget.close());
  410. };
  411. // Find the tab area for a widget within a specific dock area.
  412. const findTab = (
  413. area: DockLayout.AreaConfig,
  414. widget: Widget
  415. ): DockLayout.ITabAreaConfig | null => {
  416. switch (area.type) {
  417. case 'split-area':
  418. const iterator = iter(area.children);
  419. let tab: DockLayout.ITabAreaConfig | null = null;
  420. let value: DockLayout.AreaConfig | null = null;
  421. do {
  422. value = iterator.next();
  423. if (value) {
  424. tab = findTab(value, widget);
  425. }
  426. } while (!tab && value);
  427. return tab;
  428. case 'tab-area':
  429. const { id } = widget;
  430. return area.widgets.some(widget => widget.id === id) ? area : null;
  431. default:
  432. return null;
  433. }
  434. };
  435. // Find the tab area for a widget within the main dock area.
  436. const tabAreaFor = (widget: Widget): DockLayout.ITabAreaConfig | null => {
  437. const { mainArea } = shell.saveLayout();
  438. if (mainArea.mode !== 'multiple-document') {
  439. return null;
  440. }
  441. let area = mainArea.dock.main;
  442. if (!area) {
  443. return null;
  444. }
  445. return findTab(area, widget);
  446. };
  447. // Returns an array of all widgets to the right of a widget in a tab area.
  448. const widgetsRightOf = (widget: Widget): Array<Widget> => {
  449. const { id } = widget;
  450. const tabArea = tabAreaFor(widget);
  451. const widgets = tabArea ? tabArea.widgets || [] : [];
  452. const index = widgets.findIndex(widget => widget.id === id);
  453. if (index < 0) {
  454. return [];
  455. }
  456. return widgets.slice(index + 1);
  457. };
  458. commands.addCommand(CommandIDs.activateNextTab, {
  459. label: 'Activate Next Tab',
  460. execute: () => {
  461. shell.activateNextTab();
  462. }
  463. });
  464. palette.addItem({ command: CommandIDs.activateNextTab, category });
  465. commands.addCommand(CommandIDs.activatePreviousTab, {
  466. label: 'Activate Previous Tab',
  467. execute: () => {
  468. shell.activatePreviousTab();
  469. }
  470. });
  471. palette.addItem({ command: CommandIDs.activatePreviousTab, category });
  472. commands.addCommand(CommandIDs.close, {
  473. label: () => 'Close Tab',
  474. isEnabled: () =>
  475. !!shell.currentWidget && !!shell.currentWidget.title.closable,
  476. execute: () => {
  477. if (shell.currentWidget) {
  478. shell.currentWidget.close();
  479. }
  480. }
  481. });
  482. palette.addItem({ command: CommandIDs.close, category });
  483. contextMenu.addItem({
  484. command: CommandIDs.close,
  485. selector: '.p-TabBar-tab',
  486. rank: 4
  487. });
  488. commands.addCommand(CommandIDs.closeAll, {
  489. label: 'Close All Tabs',
  490. execute: () => {
  491. shell.closeAll();
  492. }
  493. });
  494. palette.addItem({ command: CommandIDs.closeAll, category });
  495. commands.addCommand(CommandIDs.closeOtherTabs, {
  496. label: () => `Close All Other Tabs`,
  497. isEnabled: () => {
  498. // Ensure there are at least two widgets.
  499. const iterator = shell.widgets('main');
  500. return !!iterator.next() && !!iterator.next();
  501. },
  502. execute: () => {
  503. const widget = contextMenuWidget();
  504. if (!widget) {
  505. return;
  506. }
  507. const { id } = widget;
  508. const otherWidgets = toArray(shell.widgets('main')).filter(
  509. widget => widget.id !== id
  510. );
  511. closeWidgets(otherWidgets);
  512. }
  513. });
  514. palette.addItem({ command: CommandIDs.closeOtherTabs, category });
  515. contextMenu.addItem({
  516. command: CommandIDs.closeOtherTabs,
  517. selector: '.p-TabBar-tab',
  518. rank: 4
  519. });
  520. commands.addCommand(CommandIDs.closeRightTabs, {
  521. label: () => `Close Tabs to Right`,
  522. isEnabled: () =>
  523. contextMenuWidget() && widgetsRightOf(contextMenuWidget()).length > 0,
  524. execute: () => {
  525. const widget = contextMenuWidget();
  526. if (!widget) {
  527. return;
  528. }
  529. closeWidgets(widgetsRightOf(widget));
  530. }
  531. });
  532. palette.addItem({ command: CommandIDs.closeRightTabs, category });
  533. contextMenu.addItem({
  534. command: CommandIDs.closeRightTabs,
  535. selector: '.p-TabBar-tab',
  536. rank: 5
  537. });
  538. app.commands.addCommand(CommandIDs.toggleLeftArea, {
  539. label: args => 'Show Left Sidebar',
  540. execute: () => {
  541. if (shell.leftCollapsed) {
  542. shell.expandLeft();
  543. } else {
  544. shell.collapseLeft();
  545. if (shell.currentWidget) {
  546. shell.activateById(shell.currentWidget.id);
  547. }
  548. }
  549. },
  550. isToggled: () => !shell.leftCollapsed,
  551. isVisible: () => !shell.isEmpty('left')
  552. });
  553. palette.addItem({ command: CommandIDs.toggleLeftArea, category });
  554. app.commands.addCommand(CommandIDs.toggleRightArea, {
  555. label: args => 'Show Right Sidebar',
  556. execute: () => {
  557. if (shell.rightCollapsed) {
  558. shell.expandRight();
  559. } else {
  560. shell.collapseRight();
  561. if (shell.currentWidget) {
  562. shell.activateById(shell.currentWidget.id);
  563. }
  564. }
  565. },
  566. isToggled: () => !shell.rightCollapsed,
  567. isVisible: () => !shell.isEmpty('right')
  568. });
  569. palette.addItem({ command: CommandIDs.toggleRightArea, category });
  570. app.commands.addCommand(CommandIDs.togglePresentationMode, {
  571. label: args => 'Presentation Mode',
  572. execute: () => {
  573. shell.presentationMode = !shell.presentationMode;
  574. },
  575. isToggled: () => shell.presentationMode,
  576. isVisible: () => true
  577. });
  578. palette.addItem({ command: CommandIDs.togglePresentationMode, category });
  579. app.commands.addCommand(CommandIDs.setMode, {
  580. isVisible: args => {
  581. const mode = args['mode'] as string;
  582. return mode === 'single-document' || mode === 'multiple-document';
  583. },
  584. execute: args => {
  585. const mode = args['mode'] as string;
  586. if (mode === 'single-document' || mode === 'multiple-document') {
  587. shell.mode = mode;
  588. return;
  589. }
  590. throw new Error(`Unsupported application shell mode: ${mode}`);
  591. }
  592. });
  593. app.commands.addCommand(CommandIDs.toggleMode, {
  594. label: 'Single-Document Mode',
  595. isToggled: () => shell.mode === 'single-document',
  596. execute: () => {
  597. const args =
  598. shell.mode === 'multiple-document'
  599. ? { mode: 'single-document' }
  600. : { mode: 'multiple-document' };
  601. return app.commands.execute(CommandIDs.setMode, args);
  602. }
  603. });
  604. palette.addItem({ command: CommandIDs.toggleMode, category });
  605. }
  606. /**
  607. * The default JupyterLab application shell.
  608. */
  609. const shell: JupyterFrontEndPlugin<ILabShell> = {
  610. id: '@jupyterlab/application-extension:shell',
  611. activate: (app: JupyterFrontEnd) => {
  612. if (!(app.shell instanceof LabShell)) {
  613. throw new Error(`${shell.id} did not find a LabShell instance.`);
  614. }
  615. return app.shell;
  616. },
  617. autoStart: true,
  618. provides: ILabShell
  619. };
  620. /**
  621. * The default JupyterLab application status provider.
  622. */
  623. const status: JupyterFrontEndPlugin<ILabStatus> = {
  624. id: '@jupyterlab/application-extension:status',
  625. activate: (app: JupyterFrontEnd) => {
  626. if (!(app instanceof JupyterLab)) {
  627. throw new Error(`${status.id} must be activated in JupyterLab.`);
  628. }
  629. return app.status;
  630. },
  631. autoStart: true,
  632. provides: ILabStatus
  633. };
  634. /**
  635. * The default JupyterLab application-specific information provider.
  636. *
  637. * #### Notes
  638. * This plugin should only be used by plugins that specifically need to access
  639. * JupyterLab application information, e.g., listing extensions that have been
  640. * loaded or deferred within JupyterLab.
  641. */
  642. const info: JupyterFrontEndPlugin<JupyterLab.IInfo> = {
  643. id: '@jupyterlab/application-extension:info',
  644. activate: (app: JupyterFrontEnd) => {
  645. if (!(app instanceof JupyterLab)) {
  646. throw new Error(`${info.id} must be activated in JupyterLab.`);
  647. }
  648. return app.info;
  649. },
  650. autoStart: true,
  651. provides: JupyterLab.IInfo
  652. };
  653. /**
  654. * The default JupyterLab paths dictionary provider.
  655. */
  656. const paths: JupyterFrontEndPlugin<JupyterFrontEnd.IPaths> = {
  657. id: '@jupyterlab/apputils-extension:paths',
  658. activate: (app: JupyterFrontEnd): JupyterFrontEnd.IPaths => {
  659. if (!(app instanceof JupyterLab)) {
  660. throw new Error(`${paths.id} must be activated in JupyterLab.`);
  661. }
  662. return app.paths;
  663. },
  664. autoStart: true,
  665. provides: JupyterFrontEnd.IPaths
  666. };
  667. /**
  668. * Export the plugins as default.
  669. */
  670. const plugins: JupyterFrontEndPlugin<any>[] = [
  671. main,
  672. layout,
  673. router,
  674. tree,
  675. notfound,
  676. busy,
  677. sidebar,
  678. shell,
  679. status,
  680. info,
  681. paths
  682. ];
  683. export default plugins;