index.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*-----------------------------------------------------------------------------
  2. | Copyright (c) Jupyter Development Team.
  3. | Distributed under the terms of the Modified BSD License.
  4. |----------------------------------------------------------------------------*/
  5. import {
  6. ILayoutRestorer, IRouter, JupyterLab, JupyterLabPlugin
  7. } from '@jupyterlab/application';
  8. import {
  9. ICommandPalette, IThemeManager, ThemeManager, ISplashScreen
  10. } from '@jupyterlab/apputils';
  11. import {
  12. DataConnector, ISettingRegistry, IStateDB, PageConfig, SettingRegistry,
  13. StateDB, URLExt
  14. } from '@jupyterlab/coreutils';
  15. import {
  16. IMainMenu
  17. } from '@jupyterlab/mainmenu';
  18. import {
  19. ServiceManager
  20. } from '@jupyterlab/services';
  21. import {
  22. each
  23. } from '@phosphor/algorithm';
  24. import {
  25. PromiseDelegate
  26. } from '@phosphor/coreutils';
  27. import {
  28. DisposableDelegate, DisposableSet, IDisposable
  29. } from '@phosphor/disposable';
  30. import {
  31. Menu
  32. } from '@phosphor/widgets';
  33. import {
  34. activatePalette
  35. } from './palette';
  36. import '../style/index.css';
  37. /**
  38. * The command IDs used by the apputils plugin.
  39. */
  40. namespace CommandIDs {
  41. export
  42. const changeTheme = 'apputils:change-theme';
  43. export
  44. const clearStateDB = 'apputils:clear-statedb';
  45. export
  46. const loadState = 'apputils:load-statedb';
  47. }
  48. /**
  49. * A data connector to access plugin settings.
  50. */
  51. class SettingsConnector extends DataConnector<ISettingRegistry.IPlugin, string> {
  52. /**
  53. * Create a new settings connector.
  54. */
  55. constructor(manager: ServiceManager) {
  56. super();
  57. this._manager = manager;
  58. }
  59. /**
  60. * Retrieve a saved bundle from the data connector.
  61. */
  62. fetch(id: string): Promise<ISettingRegistry.IPlugin> {
  63. return this._manager.settings.fetch(id).then(data => {
  64. // Replace the server ID with the original unmodified version.
  65. data.id = id;
  66. return data;
  67. });
  68. }
  69. /**
  70. * Save the user setting data in the data connector.
  71. */
  72. save(id: string, raw: string): Promise<void> {
  73. return this._manager.settings.save(id, raw);
  74. }
  75. private _manager: ServiceManager;
  76. }
  77. /**
  78. * The default commmand palette extension.
  79. */
  80. const palette: JupyterLabPlugin<ICommandPalette> = {
  81. activate: activatePalette,
  82. id: '@jupyterlab/apputils-extension:palette',
  83. provides: ICommandPalette,
  84. requires: [ILayoutRestorer],
  85. autoStart: true
  86. };
  87. /**
  88. * The default setting registry provider.
  89. */
  90. const settings: JupyterLabPlugin<ISettingRegistry> = {
  91. id: '@jupyterlab/apputils-extension:settings',
  92. activate: (app: JupyterLab): ISettingRegistry => {
  93. const connector = new SettingsConnector(app.serviceManager);
  94. return new SettingRegistry({ connector });
  95. },
  96. autoStart: true,
  97. provides: ISettingRegistry
  98. };
  99. /**
  100. * The default theme manager provider.
  101. */
  102. const themes: JupyterLabPlugin<IThemeManager> = {
  103. id: '@jupyterlab/apputils-extension:themes',
  104. requires: [ISettingRegistry, ISplashScreen],
  105. optional: [ICommandPalette, IMainMenu],
  106. activate: (app: JupyterLab, settingRegistry: ISettingRegistry, splash: ISplashScreen, palette: ICommandPalette | null, mainMenu: IMainMenu | null): IThemeManager => {
  107. const host = app.shell;
  108. const when = app.started;
  109. const commands = app.commands;
  110. const manager = new ThemeManager({
  111. key: themes.id,
  112. host, settingRegistry,
  113. url: app.info.urls.themes,
  114. splash,
  115. when
  116. });
  117. commands.addCommand(CommandIDs.changeTheme, {
  118. label: args => {
  119. const theme = args['theme'] as string;
  120. return args['isPalette'] ? `Use ${theme} Theme` : theme;
  121. },
  122. isToggled: args => args['theme'] === manager.theme,
  123. execute: args => {
  124. if (args['theme'] === manager.theme) {
  125. return;
  126. }
  127. manager.setTheme(args['theme'] as string);
  128. }
  129. });
  130. // If we have a main menu, add the theme manager
  131. // to the settings menu.
  132. if (mainMenu) {
  133. const themeMenu = new Menu({ commands });
  134. themeMenu.title.label = 'JupyterLab Theme';
  135. manager.ready.then(() => {
  136. each(manager.themes, theme => {
  137. themeMenu.addItem({
  138. command: CommandIDs.changeTheme,
  139. args: { isPalette: false, theme: theme }
  140. });
  141. });
  142. });
  143. mainMenu.settingsMenu.addGroup([{
  144. type: 'submenu' as Menu.ItemType, submenu: themeMenu
  145. }], 0);
  146. }
  147. // If we have a command palette, add theme
  148. // switching options to it.
  149. if (palette) {
  150. const category = 'Settings';
  151. manager.ready.then(() => {
  152. each(manager.themes, theme => {
  153. palette.addItem({
  154. command: CommandIDs.changeTheme,
  155. args: { isPalette: true, theme: theme },
  156. category
  157. });
  158. });
  159. });
  160. }
  161. return manager;
  162. },
  163. autoStart: true,
  164. provides: IThemeManager
  165. };
  166. /**
  167. * The default splash screen provider.
  168. */
  169. const splash: JupyterLabPlugin<ISplashScreen> = {
  170. id: '@jupyterlab/apputils-extension:splash',
  171. autoStart: true,
  172. provides: ISplashScreen,
  173. activate: () => ({ show: () => Private.showSplash() })
  174. };
  175. /**
  176. * The default state database for storing application state.
  177. */
  178. const state: JupyterLabPlugin<IStateDB> = {
  179. id: '@jupyterlab/apputils-extension:state',
  180. autoStart: true,
  181. provides: IStateDB,
  182. requires: [IRouter],
  183. activate: (app: JupyterLab, router: IRouter) => {
  184. let command: string;
  185. let resolved = false;
  186. const { commands, info } = app;
  187. const transform = new PromiseDelegate<StateDB.DataTransform>();
  188. const state = new StateDB({
  189. namespace: info.namespace,
  190. load: transform.promise
  191. });
  192. const disposables = new DisposableSet();
  193. const pattern = /^\/workspaces\/(.+)/;
  194. const unload = () => {
  195. disposables.dispose();
  196. router.routed.disconnect(unload, state);
  197. // If the request that was routed did not contain a workspace,
  198. // leave the database intact.
  199. if (!resolved) {
  200. console.log('No workspace requested. Leaving state database intact.');
  201. transform.resolve({ type: 'cancel', contents: null });
  202. }
  203. };
  204. command = CommandIDs.clearStateDB;
  205. commands.addCommand(command, {
  206. label: 'Clear Application Restore State',
  207. execute: () => state.clear()
  208. });
  209. command = CommandIDs.loadState;
  210. disposables.add(commands.addCommand(command, {
  211. execute: (args: IRouter.ICommandArgs) => {
  212. const workspace = (args.path || '').match(pattern)[1];
  213. const base = URLExt.join(
  214. PageConfig.getBaseUrl(),
  215. PageConfig.getOption('pageUrl')
  216. );
  217. // Change the URL back to the base application URL.
  218. window.history.replaceState({ }, '', base);
  219. // If there is no workspace, leave the state database intact.
  220. if (!workspace) {
  221. console.log('No workspace found. Leaving state database intact.');
  222. resolved = true;
  223. transform.resolve({ type: 'cancel', contents: null });
  224. return;
  225. }
  226. // Fetch the workspace and overwrite the state database.
  227. console.log('Fetch the workspace:', workspace);
  228. resolved = true;
  229. transform.resolve({ type: 'merge', contents: { } });
  230. }
  231. }));
  232. disposables.add(router.register({ command, pattern }));
  233. // After the first route in the application lifecycle has been routed,
  234. // stop listening to routing events.
  235. router.routed.connect(unload, state);
  236. return state;
  237. }
  238. };
  239. /**
  240. * Export the plugins as default.
  241. */
  242. const plugins: JupyterLabPlugin<any>[] = [
  243. palette, settings, state, splash, themes
  244. ];
  245. export default plugins;
  246. /**
  247. * The namespace for module private data.
  248. */
  249. namespace Private {
  250. /**
  251. * The splash element.
  252. */
  253. let splash: HTMLElement | null;
  254. /**
  255. * The splash screen counter.
  256. */
  257. let splashCount = 0;
  258. /**
  259. * Show the splash element.
  260. */
  261. export
  262. function showSplash(): IDisposable {
  263. if (!splash) {
  264. splash = document.createElement('div');
  265. splash.id = 'jupyterlab-splash';
  266. let galaxy = document.createElement('div');
  267. galaxy.id = 'galaxy';
  268. splash.appendChild(galaxy);
  269. let mainLogo = document.createElement('div');
  270. mainLogo.id = 'main-logo';
  271. let planet = document.createElement('div');
  272. let planet2 = document.createElement('div');
  273. let planet3 = document.createElement('div');
  274. planet.className = 'planet';
  275. planet2.className = 'planet';
  276. planet3.className = 'planet';
  277. let moon1 = document.createElement('div');
  278. moon1.id = 'moon1';
  279. moon1.className = 'moon orbit';
  280. moon1.appendChild(planet);
  281. let moon2 = document.createElement('div');
  282. moon2.id = 'moon2';
  283. moon2.className = 'moon orbit';
  284. moon2.appendChild(planet2);
  285. let moon3 = document.createElement('div');
  286. moon3.id = 'moon3';
  287. moon3.className = 'moon orbit';
  288. moon3.appendChild(planet3);
  289. galaxy.appendChild(mainLogo);
  290. galaxy.appendChild(moon1);
  291. galaxy.appendChild(moon2);
  292. galaxy.appendChild(moon3);
  293. }
  294. splash.classList.remove('splash-fade');
  295. document.body.appendChild(splash);
  296. splashCount++;
  297. return new DisposableDelegate(() => {
  298. splashCount = Math.max(splashCount - 1, 0);
  299. if (splashCount === 0 && splash) {
  300. splash.classList.add('splash-fade');
  301. setTimeout(() => {
  302. document.body.removeChild(splash);
  303. }, 500);
  304. }
  305. });
  306. }
  307. }