default-toolbar.tsx 7.7 KB

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