default-toolbar.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Widget } from '@lumino/widgets';
  4. import * as React from 'react';
  5. import {
  6. showDialog,
  7. Dialog,
  8. Toolbar,
  9. ToolbarButtonComponent,
  10. UseSignal,
  11. addToolbarButtonClass,
  12. ReactWidget,
  13. ToolbarButton,
  14. ISessionContextDialogs,
  15. ISessionContext,
  16. sessionContextDialogs
  17. } from '@jupyterlab/apputils';
  18. import { DocumentRegistry } from '@jupyterlab/docregistry';
  19. import * as nbformat from '@jupyterlab/nbformat';
  20. import {
  21. addIcon,
  22. copyIcon,
  23. cutIcon,
  24. fastForwardIcon,
  25. HTMLSelect,
  26. pasteIcon,
  27. runIcon,
  28. saveIcon
  29. } from '@jupyterlab/ui-components';
  30. import { NotebookActions } from './actions';
  31. import { NotebookPanel } from './panel';
  32. import { Notebook } from './widget';
  33. /**
  34. * The class name added to toolbar cell type dropdown wrapper.
  35. */
  36. const TOOLBAR_CELLTYPE_CLASS = 'jp-Notebook-toolbarCellType';
  37. /**
  38. * The class name added to toolbar cell type dropdown.
  39. */
  40. const TOOLBAR_CELLTYPE_DROPDOWN_CLASS = 'jp-Notebook-toolbarCellTypeDropdown';
  41. /**
  42. * A namespace for the default toolbar items.
  43. */
  44. export namespace ToolbarItems {
  45. /**
  46. * Create save button toolbar item.
  47. */
  48. export function createSaveButton(panel: NotebookPanel): Widget {
  49. function onClick() {
  50. if (panel.context.model.readOnly) {
  51. return showDialog({
  52. title: 'Cannot Save',
  53. body: 'Document is read-only',
  54. buttons: [Dialog.okButton()]
  55. });
  56. }
  57. void panel.context.save().then(() => {
  58. if (!panel.isDisposed) {
  59. return panel.context.createCheckpoint();
  60. }
  61. });
  62. }
  63. return addToolbarButtonClass(
  64. ReactWidget.create(
  65. <UseSignal signal={panel.context.fileChanged}>
  66. {() => (
  67. <ToolbarButtonComponent
  68. icon={saveIcon}
  69. onClick={onClick}
  70. tooltip="Save the notebook contents and create checkpoint"
  71. enabled={
  72. !!(
  73. panel &&
  74. panel.context &&
  75. panel.context.contentsModel &&
  76. panel.context.contentsModel.writable
  77. )
  78. }
  79. />
  80. )}
  81. </UseSignal>
  82. )
  83. );
  84. }
  85. /**
  86. * Create an insert toolbar item.
  87. */
  88. export function createInsertButton(panel: NotebookPanel): Widget {
  89. return new ToolbarButton({
  90. icon: addIcon,
  91. onClick: () => {
  92. NotebookActions.insertBelow(panel.content);
  93. },
  94. tooltip: 'Insert a cell below'
  95. });
  96. }
  97. /**
  98. * Create a cut toolbar item.
  99. */
  100. export function createCutButton(panel: NotebookPanel): Widget {
  101. return new ToolbarButton({
  102. icon: cutIcon,
  103. onClick: () => {
  104. NotebookActions.cut(panel.content);
  105. },
  106. tooltip: 'Cut the selected cells'
  107. });
  108. }
  109. /**
  110. * Create a copy toolbar item.
  111. */
  112. export function createCopyButton(panel: NotebookPanel): Widget {
  113. return new ToolbarButton({
  114. icon: copyIcon,
  115. onClick: () => {
  116. NotebookActions.copy(panel.content);
  117. },
  118. tooltip: 'Copy the selected cells'
  119. });
  120. }
  121. /**
  122. * Create a paste toolbar item.
  123. */
  124. export function createPasteButton(panel: NotebookPanel): Widget {
  125. return new ToolbarButton({
  126. icon: pasteIcon,
  127. onClick: () => {
  128. NotebookActions.paste(panel.content);
  129. },
  130. tooltip: 'Paste cells from the clipboard'
  131. });
  132. }
  133. /**
  134. * Create a run toolbar item.
  135. */
  136. export function createRunButton(panel: NotebookPanel): Widget {
  137. return new ToolbarButton({
  138. icon: runIcon,
  139. onClick: () => {
  140. void NotebookActions.runAndAdvance(panel.content, panel.sessionContext);
  141. },
  142. tooltip: 'Run the selected cells and advance'
  143. });
  144. }
  145. /**
  146. * Create a restart run all toolbar item
  147. */
  148. export function createRestartRunAllButton(
  149. panel: NotebookPanel,
  150. dialogs?: ISessionContext.IDialogs
  151. ): Widget {
  152. return new ToolbarButton({
  153. icon: fastForwardIcon,
  154. onClick: () => {
  155. void (dialogs ?? sessionContextDialogs)
  156. .restart(panel.sessionContext)
  157. .then(restarted => {
  158. if (restarted) {
  159. void NotebookActions.runAll(panel.content, panel.sessionContext);
  160. }
  161. return restarted;
  162. });
  163. },
  164. tooltip: 'Restart the kernel, then re-run the whole notebook'
  165. });
  166. }
  167. /**
  168. * Create a cell type switcher item.
  169. *
  170. * #### Notes
  171. * It will display the type of the current active cell.
  172. * If more than one cell is selected but are of different types,
  173. * it will display `'-'`.
  174. * When the user changes the cell type, it will change the
  175. * cell types of the selected cells.
  176. * It can handle a change to the context.
  177. */
  178. export function createCellTypeItem(panel: NotebookPanel): Widget {
  179. return new CellTypeSwitcher(panel.content);
  180. }
  181. /**
  182. * Get the default toolbar items for panel
  183. */
  184. export function getDefaultItems(
  185. panel: NotebookPanel,
  186. sessionDialogs?: ISessionContextDialogs
  187. ): DocumentRegistry.IToolbarItem[] {
  188. return [
  189. { name: 'save', widget: createSaveButton(panel) },
  190. { name: 'insert', widget: createInsertButton(panel) },
  191. { name: 'cut', widget: createCutButton(panel) },
  192. { name: 'copy', widget: createCopyButton(panel) },
  193. { name: 'paste', widget: createPasteButton(panel) },
  194. { name: 'run', widget: createRunButton(panel) },
  195. {
  196. name: 'interrupt',
  197. widget: Toolbar.createInterruptButton(panel.sessionContext)
  198. },
  199. {
  200. name: 'restart',
  201. widget: Toolbar.createRestartButton(
  202. panel.sessionContext,
  203. sessionDialogs
  204. )
  205. },
  206. {
  207. name: 'restart-and-run',
  208. widget: createRestartRunAllButton(panel, sessionDialogs)
  209. },
  210. { name: 'cellType', widget: createCellTypeItem(panel) },
  211. { name: 'spacer', widget: Toolbar.createSpacerItem() },
  212. {
  213. name: 'kernelName',
  214. widget: Toolbar.createKernelNameItem(
  215. panel.sessionContext,
  216. sessionDialogs
  217. )
  218. },
  219. {
  220. name: 'kernelStatus',
  221. widget: Toolbar.createKernelStatusItem(panel.sessionContext)
  222. }
  223. ];
  224. }
  225. }
  226. /**
  227. * A toolbar widget that switches cell types.
  228. */
  229. export class CellTypeSwitcher extends ReactWidget {
  230. /**
  231. * Construct a new cell type switcher.
  232. */
  233. constructor(widget: Notebook) {
  234. super();
  235. this.addClass(TOOLBAR_CELLTYPE_CLASS);
  236. this._notebook = widget;
  237. if (widget.model) {
  238. this.update();
  239. }
  240. widget.activeCellChanged.connect(this.update, this);
  241. // Follow a change in the selection.
  242. widget.selectionChanged.connect(this.update, this);
  243. }
  244. /**
  245. * Handle `change` events for the HTMLSelect component.
  246. */
  247. handleChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
  248. if (event.target.value !== '-') {
  249. NotebookActions.changeCellType(
  250. this._notebook,
  251. event.target.value as nbformat.CellType
  252. );
  253. this._notebook.activate();
  254. }
  255. };
  256. /**
  257. * Handle `keydown` events for the HTMLSelect component.
  258. */
  259. handleKeyDown = (event: React.KeyboardEvent): void => {
  260. if (event.keyCode === 13) {
  261. this._notebook.activate();
  262. }
  263. };
  264. render() {
  265. let value = '-';
  266. if (this._notebook.activeCell) {
  267. value = this._notebook.activeCell.model.type;
  268. }
  269. for (let widget of this._notebook.widgets) {
  270. if (this._notebook.isSelectedOrActive(widget)) {
  271. if (widget.model.type !== value) {
  272. value = '-';
  273. break;
  274. }
  275. }
  276. }
  277. return (
  278. <HTMLSelect
  279. className={TOOLBAR_CELLTYPE_DROPDOWN_CLASS}
  280. onChange={this.handleChange}
  281. onKeyDown={this.handleKeyDown}
  282. value={value}
  283. aria-label="Cell type"
  284. >
  285. <option value="-">-</option>
  286. <option value="code">Code</option>
  287. <option value="markdown">Markdown</option>
  288. <option value="raw">Raw</option>
  289. </HTMLSelect>
  290. );
  291. }
  292. private _notebook: Notebook;
  293. }