import { ILabShell, JupyterFrontEnd } from '@jupyterlab/application'; import { INotebookTracker, NotebookActions, NotebookPanel } from '@jupyterlab/notebook'; import { CommandRegistry } from '@lumino/commands'; import React from 'react'; import { NOTEBOOK_ID, WELCOME_ID } from './constants'; import { ITourManager } from './tokens'; /** * Add the default welcome tour * * @param manager Tours manager * @param commands Jupyter commands registry */ function addWelcomeTour( manager: ITourManager, commands: CommandRegistry ): void { const __ = manager.translator.__.bind(manager.translator); const welcomeTour = manager.createTour(WELCOME_ID, __('Welcome Tour'), true); welcomeTour.options = { ...welcomeTour.options, hideBackButton: true }; welcomeTour.addStep({ target: '#jp-main-dock-panel', content: __( 'The following tour will point out some of the main UI components within JupyterLab.' ), placement: 'center', title: __('Welcome to JupyterLab!') }); welcomeTour.addStep({ content: ( <> <p> {__('Pause the tour by clicking anywhere outside of the tooltip.')} </p> <p>{__('Resume the tour by clicking on the symbol:')}</p> <div style={{ display: 'inline-block', height: '60px' }}> <span style={{ animation: '1.2s ease-in-out 0s infinite normal none running joyride-beacon-inner', backgroundColor: 'var(--jp-brand-color1)', borderRadius: '50%', display: 'block', height: '30px', opacity: '0.7', position: 'relative', top: '15px', width: '30px' }} /> <span style={{ animation: '1.2s ease-in-out 0s infinite normal none running joyride-beacon-outer', border: '2px solid var(--jp-brand-color1)', borderRadius: '50%', boxSizing: 'border-box', display: 'block', height: '60px', left: '-15px', opacity: '0.9', position: 'relative', top: '-30px', width: '60px' }} /> </div> <p> <small>{__('Tip: Tours can be restarted from the Help menu.')}</small> </p> </> ), target: '#jp-main-dock-panel', placement: 'center', title: __('Some information on the tour, first.') }); welcomeTour.addStep({ content: ( <details> <summary> {__('This is the top menu bar where you can access several menus.')} </summary> <ul> <li> <strong>{__('File')}</strong> {__(': actions related to files and directories')} </li> <li> <strong>{__('Edit')}</strong> {__(': actions related to editing documents and other activities')} </li> <li> <strong>{__('View')}</strong> {__(': actions that alter the appearance of JupyterLab')} </li> <li> <strong>{__('Run')}</strong> {__( ': actions for running code in notebooks and code consoles for example' )} </li> <li> <strong>{__('Kernel')}</strong> {__( ': actions for managing kernels (i.e. separate processes for running code)' )} </li> <li> <strong>{__('Tabs')}</strong> {__(': a list of the open documents and activities')} </li> <li> <strong>{__('Settings')}</strong> {__(': common settings and an advanced settings editor')} </li> <li> <strong>{__('Help')}</strong> {__(': help links')} </li> </ul> </details> ), placement: 'bottom', target: '#jp-MainMenu', title: __('Top Menu Options'), styles: { tooltipContent: { overflowY: 'auto', maxHeight: '200px' } } }); welcomeTour.addStep({ content: ( <> <p> {__( `The main area enables you to arrange documents and activities into panels of tabs that can be resized or subdivided.` )} </p> <p> {__( 'Drag a tab to the center of a tab panel to move the tab to the panel.' )} <br /> {__( 'Subdivide a tab panel by dragging a tab to the left, right, top, or bottom of the panel.' )} </p> <p> {__( 'The tab for the current activity is marked with a colored top border.' )} </p> </> ), placement: 'left-end', target: '#jp-main-dock-panel', title: __('Main Work Area') }); welcomeTour.addStep({ target: '#jp-main-statusbar', content: <p>{__('Various information are reported on the status bar.')}</p>, placement: 'top', title: __('Status Bar') }); welcomeTour.addStep({ content: ( <> <p> {__( 'This sidebar contains a number of tabs: a file browser, a list of tabs, running kernels and terminals,...' )} </p> <p> <small> {__( `Tip: The sidebar can be collapsed or expanded by selecting "Show Left Sidebar" in the View menu or by clicking on the active sidebar tab.` )} </small> </p> </> ), placement: 'right', target: '.jp-SideBar.jp-mod-left', title: __('Left Side Bar') }); welcomeTour.addStep({ content: ( <> <p> {__( `The file browser enable you to work with files and directories on your system. This includes opening, creating, deleting, renaming, downloading, copying, and sharing files and directories.` )} </p> <p> <small> {__('Tip: Actions can be triggered through the context menu.')} </small> </p> </> ), placement: 'right', target: '#filebrowser', title: __('File Browser') }); welcomeTour.addStep({ content: ( <> <p> {__( `All user actions in JupyterLab are processed through a centralized command system, called command palette. It provides a keyboard-driven way to search for and run JupyterLab commands.` )} </p> <p> <small> {__('Tip: To open it, the default shortcut is "Ctrl + Shift + C"')} </small> </p> </> ), placement: 'center', target: '#jp-main-dock-panel', title: __('Command Palette') }); welcomeTour.stepChanged.connect((_, data) => { switch (data.type) { case 'step:after': if (data.step.target === '.jp-SideBar.jp-mod-left') { commands.execute('filebrowser:activate'); } else if (data.step.target === '#filebrowser') { commands.execute('apputils:activate-command-palette'); } break; } }); } /** * Add the default notebook tour * * @param manager Tours manager * @param shell Jupyter shell * @param nbTracker Notebook tracker (optional) */ function addNotebookTour( manager: ITourManager, commands: CommandRegistry, shell: ILabShell, nbTracker?: INotebookTracker ): void { const __ = manager.translator.__.bind(manager.translator); const notebookTour = manager.createTour( NOTEBOOK_ID, __('Notebook Tour'), true ); notebookTour.options = { ...notebookTour.options, hideBackButton: true, disableScrolling: true }; let currentNbPanel: NotebookPanel | null = null; let addedCellIndex: number | null = null; notebookTour.addStep({ target: '.jp-MainAreaWidget.jp-NotebookPanel', content: ( <p> {__( 'Notebooks are documents combining live runnable code with narrative text (i.e. text, images,...).' )} </p> ), placement: 'center', title: __('Working with notebooks!') }); notebookTour.addStep({ target: '.jp-Cell.jp-Notebook-cell', content: ( <p> {__('Notebook consists of one cells list.')} <br /> {__('This is the first cell.')} </p> ), placement: 'bottom' }); notebookTour.addStep({ target: '.jp-NotebookPanel-toolbar .jp-Notebook-toolbarCellType', content: ( <> <p>{__('A cell can have different type')}</p> <ul> <li> <strong>{__('Code')}</strong> {__(': Executable code')} </li> <li> <strong>{__('Markdown')}</strong> {__(': Markdown formatted text')} </li> <li> <strong>{__('Raw')}</strong> {__(': Plain text')} </li> </ul> </> ) }); notebookTour.addStep({ target: '.jp-Notebook-cell:last-child .jp-InputArea.jp-Cell-inputArea', content: ( <p> {__( `A cell has an input and an output area. This is the input area that you can edit with the proper syntax depending on the type.` )} </p> ), placement: 'bottom' }); notebookTour.addStep({ target: '.jp-NotebookPanel-toolbar svg[data-icon="ui-components:run"]', content: ( <p> {__( 'Hitting the Play button (or pressing Shift+Enter) will execute the cell content.' )} </p> ), placement: 'right' }); notebookTour.addStep({ target: '.jp-Notebook-cell:last-child .jp-OutputArea.jp-Cell-outputArea', content: ( <p> {__( 'Once a cell has been executed. Its result is display in the output cell area.' )} </p> ), placement: 'bottom' }); notebookTour.addStep({ target: '.jp-NotebookPanel-toolbar .jp-KernelName', content: ( <p> {__( 'When executing a "Code" cell, its code is sent to a execution kernel.' )} <br /> {__( 'Its name and its status are displayed here and in the status bar.' )} </p> ), placement: 'bottom' }); notebookTour.addStep({ target: '#jp-running-sessions', content: ( <p> {__('The running kernels are listed on this tab.')} <br /> {__( ' It can be used to open the associated document or to shut them down.' )} </p> ), placement: 'right' }); notebookTour.addStep({ target: '#jp-property-inspector', content: ( <p> {__('Metadata (like tags) can be added to cells through this tab.')} </p> ), placement: 'left' }); notebookTour.stepChanged.connect((_, data) => { if (data.type === 'tour:start') { addedCellIndex = null; } else if (data.type === 'step:before') { switch (data.step.target) { case '.jp-NotebookPanel-toolbar svg[data-icon="ui-components:run"]': { if (nbTracker && currentNbPanel) { const { content, context } = currentNbPanel; NotebookActions.run(content, context.sessionContext); } } break; default: break; } } else if (data.type === 'step:after') { switch (data.step.target) { case '.jp-NotebookPanel-toolbar .jp-Notebook-toolbarCellType': { if (nbTracker) { currentNbPanel = nbTracker.currentWidget; if (currentNbPanel && !addedCellIndex) { const notebook = currentNbPanel.content; NotebookActions.insertBelow(notebook); const activeCell = notebook.activeCell; addedCellIndex = notebook.activeCellIndex; if (activeCell) { activeCell.model.value.text = 'a = 2\na'; } } } } break; case '.jp-NotebookPanel-toolbar .jp-KernelName': shell.activateById('jp-running-sessions'); break; case '#jp-running-sessions': shell.activateById('jp-property-inspector'); break; default: break; } } }); // clean notebookTour.finished.connect((_, data) => { if (data.step.target === '#jp-property-inspector') { commands.execute('filebrowser:activate'); if (nbTracker) { if (currentNbPanel && addedCellIndex !== null) { currentNbPanel.content.activeCellIndex = addedCellIndex; NotebookActions.deleteCells(currentNbPanel.content); addedCellIndex = null; } } } }); } /** * Add all default tours * * @param manager Tours manager * @param app Jupyter application * @param nbTracker Notebook tracker (optional) */ export function addTours( manager: ITourManager, app: JupyterFrontEnd, nbTracker?: INotebookTracker ): void { const { commands, shell } = app; addWelcomeTour(manager, commands); addNotebookTour(manager, commands, shell as ILabShell, nbTracker); }