toolbar.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Text } from '@jupyterlab/coreutils';
  4. import {
  5. Button,
  6. circleEmptyIcon,
  7. circleIcon,
  8. classes,
  9. LabIcon,
  10. refreshIcon,
  11. stopIcon
  12. } from '@jupyterlab/ui-components';
  13. import { IIterator, find, map, some } from '@lumino/algorithm';
  14. import { CommandRegistry } from '@lumino/commands';
  15. import { ReadonlyJSONObject } from '@lumino/coreutils';
  16. import { Message, MessageLoop } from '@lumino/messaging';
  17. import { AttachedProperty } from '@lumino/properties';
  18. import { PanelLayout, Widget } from '@lumino/widgets';
  19. import * as React from 'react';
  20. import { ISessionContext, sessionContextDialogs } from './sessioncontext';
  21. import { UseSignal, ReactWidget } from './vdom';
  22. /**
  23. * The class name added to toolbars.
  24. */
  25. const TOOLBAR_CLASS = 'jp-Toolbar';
  26. /**
  27. * The class name added to toolbar items.
  28. */
  29. const TOOLBAR_ITEM_CLASS = 'jp-Toolbar-item';
  30. /**
  31. * The class name added to toolbar kernel name text.
  32. */
  33. const TOOLBAR_KERNEL_NAME_CLASS = 'jp-Toolbar-kernelName';
  34. /**
  35. * The class name added to toolbar spacer.
  36. */
  37. const TOOLBAR_SPACER_CLASS = 'jp-Toolbar-spacer';
  38. /**
  39. * The class name added to toolbar kernel status icon.
  40. */
  41. const TOOLBAR_KERNEL_STATUS_CLASS = 'jp-Toolbar-kernelStatus';
  42. /**
  43. * A layout for toolbars.
  44. *
  45. * #### Notes
  46. * This layout automatically collapses its height if there are no visible
  47. * toolbar widgets, and expands to the standard toolbar height if there are
  48. * visible toolbar widgets.
  49. */
  50. class ToolbarLayout extends PanelLayout {
  51. /**
  52. * A message handler invoked on a `'fit-request'` message.
  53. *
  54. * If any child widget is visible, expand the toolbar height to the normal
  55. * toolbar height.
  56. */
  57. protected onFitRequest(msg: Message): void {
  58. super.onFitRequest(msg);
  59. if (this.parent!.isAttached) {
  60. // If there are any widgets not explicitly hidden, expand the toolbar to
  61. // accommodate them.
  62. if (some(this.widgets, w => !w.isHidden)) {
  63. this.parent!.node.style.minHeight = 'var(--jp-private-toolbar-height)';
  64. this.parent!.removeClass('jp-Toolbar-micro');
  65. } else {
  66. this.parent!.node.style.minHeight = '';
  67. this.parent!.addClass('jp-Toolbar-micro');
  68. }
  69. }
  70. // Set the dirty flag to ensure only a single update occurs.
  71. this._dirty = true;
  72. // Notify the ancestor that it should fit immediately. This may
  73. // cause a resize of the parent, fulfilling the required update.
  74. if (this.parent!.parent) {
  75. MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest);
  76. }
  77. // If the dirty flag is still set, the parent was not resized.
  78. // Trigger the required update on the parent widget immediately.
  79. if (this._dirty) {
  80. MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest);
  81. }
  82. }
  83. /**
  84. * A message handler invoked on an `'update-request'` message.
  85. */
  86. protected onUpdateRequest(msg: Message): void {
  87. super.onUpdateRequest(msg);
  88. if (this.parent!.isVisible) {
  89. this._dirty = false;
  90. }
  91. }
  92. /**
  93. * A message handler invoked on a `'child-shown'` message.
  94. */
  95. protected onChildShown(msg: Widget.ChildMessage): void {
  96. super.onChildShown(msg);
  97. // Post a fit request for the parent widget.
  98. this.parent!.fit();
  99. }
  100. /**
  101. * A message handler invoked on a `'child-hidden'` message.
  102. */
  103. protected onChildHidden(msg: Widget.ChildMessage): void {
  104. super.onChildHidden(msg);
  105. // Post a fit request for the parent widget.
  106. this.parent!.fit();
  107. }
  108. /**
  109. * A message handler invoked on a `'before-attach'` message.
  110. */
  111. protected onBeforeAttach(msg: Message): void {
  112. super.onBeforeAttach(msg);
  113. // Post a fit request for the parent widget.
  114. this.parent!.fit();
  115. }
  116. /**
  117. * Attach a widget to the parent's DOM node.
  118. *
  119. * @param index - The current index of the widget in the layout.
  120. *
  121. * @param widget - The widget to attach to the parent.
  122. *
  123. * #### Notes
  124. * This is a reimplementation of the superclass method.
  125. */
  126. protected attachWidget(index: number, widget: Widget): void {
  127. super.attachWidget(index, widget);
  128. // Post a fit request for the parent widget.
  129. this.parent!.fit();
  130. }
  131. /**
  132. * Detach a widget from the parent's DOM node.
  133. *
  134. * @param index - The previous index of the widget in the layout.
  135. *
  136. * @param widget - The widget to detach from the parent.
  137. *
  138. * #### Notes
  139. * This is a reimplementation of the superclass method.
  140. */
  141. protected detachWidget(index: number, widget: Widget): void {
  142. super.detachWidget(index, widget);
  143. // Post a fit request for the parent widget.
  144. this.parent!.fit();
  145. }
  146. private _dirty = false;
  147. }
  148. /**
  149. * A class which provides a toolbar widget.
  150. */
  151. export class Toolbar<T extends Widget = Widget> extends Widget {
  152. /**
  153. * Construct a new toolbar widget.
  154. */
  155. constructor() {
  156. super();
  157. this.addClass(TOOLBAR_CLASS);
  158. this.addClass('jp-scrollbar-tiny');
  159. this.layout = new ToolbarLayout();
  160. }
  161. /**
  162. * Get an iterator over the ordered toolbar item names.
  163. *
  164. * @returns An iterator over the toolbar item names.
  165. */
  166. names(): IIterator<string> {
  167. const layout = this.layout as ToolbarLayout;
  168. return map(layout.widgets, widget => {
  169. return Private.nameProperty.get(widget);
  170. });
  171. }
  172. /**
  173. * Add an item to the end of the toolbar.
  174. *
  175. * @param name - The name of the widget to add to the toolbar.
  176. *
  177. * @param widget - The widget to add to the toolbar.
  178. *
  179. * @param index - The optional name of the item to insert after.
  180. *
  181. * @returns Whether the item was added to toolbar. Returns false if
  182. * an item of the same name is already in the toolbar.
  183. *
  184. * #### Notes
  185. * The item can be removed from the toolbar by setting its parent to `null`.
  186. */
  187. addItem(name: string, widget: T): boolean {
  188. const layout = this.layout as ToolbarLayout;
  189. return this.insertItem(layout.widgets.length, name, widget);
  190. }
  191. /**
  192. * Insert an item into the toolbar at the specified index.
  193. *
  194. * @param index - The index at which to insert the item.
  195. *
  196. * @param name - The name of the item.
  197. *
  198. * @param widget - The widget to add.
  199. *
  200. * @returns Whether the item was added to the toolbar. Returns false if
  201. * an item of the same name is already in the toolbar.
  202. *
  203. * #### Notes
  204. * The index will be clamped to the bounds of the items.
  205. * The item can be removed from the toolbar by setting its parent to `null`.
  206. */
  207. insertItem(index: number, name: string, widget: T): boolean {
  208. const existing = find(this.names(), value => value === name);
  209. if (existing) {
  210. return false;
  211. }
  212. widget.addClass(TOOLBAR_ITEM_CLASS);
  213. const layout = this.layout as ToolbarLayout;
  214. layout.insertWidget(index, widget);
  215. Private.nameProperty.set(widget, name);
  216. return true;
  217. }
  218. /**
  219. * Insert an item into the toolbar at the after a target item.
  220. *
  221. * @param at - The target item to insert after.
  222. *
  223. * @param name - The name of the item.
  224. *
  225. * @param widget - The widget to add.
  226. *
  227. * @returns Whether the item was added to the toolbar. Returns false if
  228. * an item of the same name is already in the toolbar.
  229. *
  230. * #### Notes
  231. * The index will be clamped to the bounds of the items.
  232. * The item can be removed from the toolbar by setting its parent to `null`.
  233. */
  234. insertAfter(at: string, name: string, widget: T): boolean {
  235. return this._insertRelative(at, 1, name, widget);
  236. }
  237. /**
  238. * Insert an item into the toolbar at the before a target item.
  239. *
  240. * @param at - The target item to insert before.
  241. *
  242. * @param name - The name of the item.
  243. *
  244. * @param widget - The widget to add.
  245. *
  246. * @returns Whether the item was added to the toolbar. Returns false if
  247. * an item of the same name is already in the toolbar.
  248. *
  249. * #### Notes
  250. * The index will be clamped to the bounds of the items.
  251. * The item can be removed from the toolbar by setting its parent to `null`.
  252. */
  253. insertBefore(at: string, name: string, widget: T): boolean {
  254. return this._insertRelative(at, 0, name, widget);
  255. }
  256. private _insertRelative(
  257. at: string,
  258. offset: number,
  259. name: string,
  260. widget: T
  261. ): boolean {
  262. const nameWithIndex = map(this.names(), (name, i) => {
  263. return { name: name, index: i };
  264. });
  265. const target = find(nameWithIndex, x => x.name === at);
  266. if (target) {
  267. return this.insertItem(target.index + offset, name, widget);
  268. }
  269. return false;
  270. }
  271. /**
  272. * Handle the DOM events for the widget.
  273. *
  274. * @param event - The DOM event sent to the widget.
  275. *
  276. * #### Notes
  277. * This method implements the DOM `EventListener` interface and is
  278. * called in response to events on the dock panel's node. It should
  279. * not be called directly by user code.
  280. */
  281. handleEvent(event: Event): void {
  282. switch (event.type) {
  283. case 'click':
  284. this.handleClick(event);
  285. break;
  286. default:
  287. break;
  288. }
  289. }
  290. /**
  291. * Handle a DOM click event.
  292. */
  293. protected handleClick(event: Event) {
  294. // Clicking a label focuses the corresponding control
  295. // that is linked with `for` attribute, so let it be.
  296. if (event.target instanceof HTMLLabelElement) {
  297. const forId = event.target.getAttribute('for');
  298. if (forId && this.node.querySelector(`#${forId}`)) {
  299. return;
  300. }
  301. }
  302. // If this click already focused a control, let it be.
  303. if (this.node.contains(document.activeElement)) {
  304. return;
  305. }
  306. // Otherwise, activate the parent widget, which may take focus if desired.
  307. if (this.parent) {
  308. this.parent.activate();
  309. }
  310. }
  311. /**
  312. * Handle `after-attach` messages for the widget.
  313. */
  314. protected onAfterAttach(msg: Message): void {
  315. this.node.addEventListener('click', this);
  316. }
  317. /**
  318. * Handle `before-detach` messages for the widget.
  319. */
  320. protected onBeforeDetach(msg: Message): void {
  321. this.node.removeEventListener('click', this);
  322. }
  323. }
  324. /**
  325. * The namespace for Toolbar class statics.
  326. */
  327. export namespace Toolbar {
  328. /**
  329. * Create an interrupt toolbar item.
  330. */
  331. export function createInterruptButton(
  332. sessionContext: ISessionContext
  333. ): Widget {
  334. return new ToolbarButton({
  335. icon: stopIcon,
  336. onClick: () => {
  337. void sessionContext.session?.kernel?.interrupt();
  338. },
  339. tooltip: 'Interrupt the kernel'
  340. });
  341. }
  342. /**
  343. * Create a restart toolbar item.
  344. */
  345. export function createRestartButton(
  346. sessionContext: ISessionContext,
  347. dialogs?: ISessionContext.IDialogs
  348. ): Widget {
  349. return new ToolbarButton({
  350. icon: refreshIcon,
  351. onClick: () => {
  352. void (dialogs ?? sessionContextDialogs).restart(sessionContext);
  353. },
  354. tooltip: 'Restart the kernel'
  355. });
  356. }
  357. /**
  358. * Create a toolbar spacer item.
  359. *
  360. * #### Notes
  361. * It is a flex spacer that separates the left toolbar items
  362. * from the right toolbar items.
  363. */
  364. export function createSpacerItem(): Widget {
  365. return new Private.Spacer();
  366. }
  367. /**
  368. * Create a kernel name indicator item.
  369. *
  370. * #### Notes
  371. * It will display the `'display_name`' of the session context. It can
  372. * handle a change in context or kernel.
  373. */
  374. export function createKernelNameItem(
  375. sessionContext: ISessionContext,
  376. dialogs?: ISessionContext.IDialogs
  377. ): Widget {
  378. const el = ReactWidget.create(
  379. <Private.KernelNameComponent
  380. sessionContext={sessionContext}
  381. dialogs={dialogs ?? sessionContextDialogs}
  382. />
  383. );
  384. el.addClass('jp-KernelName');
  385. return el;
  386. }
  387. /**
  388. * Create a kernel status indicator item.
  389. *
  390. * #### Notes
  391. * It will show a busy status if the kernel status is busy.
  392. * It will show the current status in the node title.
  393. * It can handle a change to the context or the kernel.
  394. */
  395. export function createKernelStatusItem(
  396. sessionContext: ISessionContext
  397. ): Widget {
  398. return new Private.KernelStatus(sessionContext);
  399. }
  400. }
  401. /**
  402. * Namespace for ToolbarButtonComponent.
  403. */
  404. export namespace ToolbarButtonComponent {
  405. /**
  406. * Interface for ToolbarButttonComponent props.
  407. */
  408. export interface IProps {
  409. className?: string;
  410. label?: string;
  411. icon?: LabIcon.IMaybeResolvable;
  412. iconClass?: string;
  413. iconLabel?: string;
  414. tooltip?: string;
  415. onClick?: () => void;
  416. enabled?: boolean;
  417. /**
  418. * Trigger the button on the actual onClick event rather than onMouseDown.
  419. *
  420. * See note in ToolbarButtonComponent below as to why the default is to
  421. * trigger on onMouseDown.
  422. */
  423. actualOnClick?: boolean;
  424. }
  425. }
  426. /**
  427. * React component for a toolbar button.
  428. *
  429. * @param props - The props for ToolbarButtonComponent.
  430. */
  431. export function ToolbarButtonComponent(props: ToolbarButtonComponent.IProps) {
  432. // In some browsers, a button click event moves the focus from the main
  433. // content to the button (see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus).
  434. // We avoid a click event by calling preventDefault in mousedown, and
  435. // we bind the button action to `mousedown`.
  436. const handleMouseDown = (event: React.MouseEvent) => {
  437. // Fire action only when left button is pressed.
  438. if (event.button === 0) {
  439. event.preventDefault();
  440. props.onClick?.();
  441. }
  442. };
  443. const handleKeyDown = (event: React.KeyboardEvent) => {
  444. const { key } = event;
  445. if (key === 'Enter' || key === ' ') {
  446. props.onClick?.();
  447. }
  448. };
  449. const handleClick = (event: React.MouseEvent) => {
  450. if (event.button === 0) {
  451. props.onClick?.();
  452. }
  453. };
  454. return (
  455. <Button
  456. className={
  457. props.className
  458. ? props.className + ' jp-ToolbarButtonComponent'
  459. : 'jp-ToolbarButtonComponent'
  460. }
  461. disabled={props.enabled === false}
  462. onClick={props.actualOnClick ?? false ? handleClick : undefined}
  463. onMouseDown={
  464. !(props.actualOnClick ?? false) ? handleMouseDown : undefined
  465. }
  466. onKeyDown={handleKeyDown}
  467. title={props.tooltip || props.iconLabel}
  468. minimal
  469. >
  470. {(props.icon || props.iconClass) && (
  471. <LabIcon.resolveReact
  472. icon={props.icon}
  473. iconClass={
  474. // add some extra classes for proper support of icons-as-css-backgorund
  475. classes(props.iconClass, 'jp-Icon')
  476. }
  477. className="jp-ToolbarButtonComponent-icon"
  478. tag="span"
  479. stylesheet="toolbarButton"
  480. />
  481. )}
  482. {props.label && (
  483. <span className="jp-ToolbarButtonComponent-label">{props.label}</span>
  484. )}
  485. </Button>
  486. );
  487. }
  488. /**
  489. * Adds the toolbar button class to the toolbar widget.
  490. * @param w Toolbar button widget.
  491. */
  492. export function addToolbarButtonClass(w: Widget): Widget {
  493. w.addClass('jp-ToolbarButton');
  494. return w;
  495. }
  496. /**
  497. * Phosphor Widget version of static ToolbarButtonComponent.
  498. */
  499. export class ToolbarButton extends ReactWidget {
  500. /**
  501. * Creates a toolbar button
  502. * @param props props for underlying `ToolbarButton` componenent
  503. */
  504. constructor(private props: ToolbarButtonComponent.IProps = {}) {
  505. super();
  506. addToolbarButtonClass(this);
  507. }
  508. render() {
  509. return <ToolbarButtonComponent {...this.props} />;
  510. }
  511. }
  512. /**
  513. * Namespace for CommandToolbarButtonComponent.
  514. */
  515. export namespace CommandToolbarButtonComponent {
  516. /**
  517. * Interface for CommandToolbarButtonComponent props.
  518. */
  519. export interface IProps {
  520. commands: CommandRegistry;
  521. id: string;
  522. args?: ReadonlyJSONObject;
  523. }
  524. }
  525. /**
  526. * React component for a toolbar button that wraps a command.
  527. *
  528. * This wraps the ToolbarButtonComponent and watches the command registry
  529. * for changes to the command.
  530. */
  531. export function CommandToolbarButtonComponent(
  532. props: CommandToolbarButtonComponent.IProps
  533. ) {
  534. return (
  535. <UseSignal
  536. signal={props.commands.commandChanged}
  537. shouldUpdate={(sender, args) =>
  538. (args.id === props.id && args.type === 'changed') ||
  539. args.type === 'many-changed'
  540. }
  541. >
  542. {() => <ToolbarButtonComponent {...Private.propsFromCommand(props)} />}
  543. </UseSignal>
  544. );
  545. }
  546. /*
  547. * Adds the command toolbar button class to the command toolbar widget.
  548. * @param w Command toolbar button widget.
  549. */
  550. export function addCommandToolbarButtonClass(w: Widget): Widget {
  551. w.addClass('jp-CommandToolbarButton');
  552. return w;
  553. }
  554. /**
  555. * Phosphor Widget version of CommandToolbarButtonComponent.
  556. */
  557. export class CommandToolbarButton extends ReactWidget {
  558. /**
  559. * Creates a command toolbar button
  560. * @param props props for underlying `CommandToolbarButtonComponent` componenent
  561. */
  562. constructor(private props: CommandToolbarButtonComponent.IProps) {
  563. super();
  564. addCommandToolbarButtonClass(this);
  565. }
  566. render() {
  567. return <CommandToolbarButtonComponent {...this.props} />;
  568. }
  569. }
  570. /**
  571. * A namespace for private data.
  572. */
  573. namespace Private {
  574. export function propsFromCommand(
  575. options: CommandToolbarButtonComponent.IProps
  576. ): ToolbarButtonComponent.IProps {
  577. const { commands, id, args } = options;
  578. const iconClass = commands.iconClass(id, args);
  579. const iconLabel = commands.iconLabel(id, args);
  580. // DEPRECATED: remove _icon when lumino 2.0 is adopted
  581. // if icon is aliasing iconClass, don't use it
  582. const _icon = commands.icon(id, args);
  583. const icon = _icon === iconClass ? undefined : _icon;
  584. const label = commands.label(id, args);
  585. let className = commands.className(id, args);
  586. // Add the boolean state classes.
  587. if (commands.isToggled(id, args)) {
  588. className += ' lm-mod-toggled';
  589. }
  590. if (!commands.isVisible(id, args)) {
  591. className += ' lm-mod-hidden';
  592. }
  593. const tooltip = commands.caption(id, args) || label || iconLabel;
  594. const onClick = () => {
  595. void commands.execute(id, args);
  596. };
  597. const enabled = commands.isEnabled(id, args);
  598. return { className, icon, iconClass, tooltip, onClick, enabled, label };
  599. }
  600. /**
  601. * An attached property for the name of a toolbar item.
  602. */
  603. export const nameProperty = new AttachedProperty<Widget, string>({
  604. name: 'name',
  605. create: () => ''
  606. });
  607. /**
  608. * A no-op function.
  609. */
  610. export function noOp() {
  611. /* no-op */
  612. }
  613. /**
  614. * A spacer widget.
  615. */
  616. export class Spacer extends Widget {
  617. /**
  618. * Construct a new spacer widget.
  619. */
  620. constructor() {
  621. super();
  622. this.addClass(TOOLBAR_SPACER_CLASS);
  623. }
  624. }
  625. /**
  626. * Namespace for KernelNameComponent.
  627. */
  628. export namespace KernelNameComponent {
  629. /**
  630. * Interface for KernelNameComponent props.
  631. */
  632. export interface IProps {
  633. sessionContext: ISessionContext;
  634. dialogs: ISessionContext.IDialogs;
  635. }
  636. }
  637. /**
  638. * React component for a kernel name button.
  639. *
  640. * This wraps the ToolbarButtonComponent and watches the kernel
  641. * session for changes.
  642. */
  643. export function KernelNameComponent(props: KernelNameComponent.IProps) {
  644. const callback = () => {
  645. void props.dialogs.selectKernel(props.sessionContext);
  646. };
  647. return (
  648. <UseSignal
  649. signal={props.sessionContext.kernelChanged}
  650. initialSender={props.sessionContext}
  651. >
  652. {sessionContext => (
  653. <ToolbarButtonComponent
  654. className={TOOLBAR_KERNEL_NAME_CLASS}
  655. onClick={callback}
  656. tooltip={'Switch kernel'}
  657. label={sessionContext?.kernelDisplayName}
  658. />
  659. )}
  660. </UseSignal>
  661. );
  662. }
  663. /**
  664. * A toolbar item that displays kernel status.
  665. */
  666. export class KernelStatus extends Widget {
  667. /**
  668. * Construct a new kernel status widget.
  669. */
  670. constructor(sessionContext: ISessionContext) {
  671. super();
  672. this.addClass(TOOLBAR_KERNEL_STATUS_CLASS);
  673. this._onStatusChanged(sessionContext);
  674. sessionContext.statusChanged.connect(this._onStatusChanged, this);
  675. }
  676. /**
  677. * Handle a status on a kernel.
  678. */
  679. private _onStatusChanged(sessionContext: ISessionContext) {
  680. if (this.isDisposed) {
  681. return;
  682. }
  683. const status = sessionContext.kernelDisplayStatus;
  684. // set the icon
  685. if (this._isBusy(status)) {
  686. circleIcon.element({
  687. container: this.node,
  688. title: `Kernel ${Text.titleCase(status)}`,
  689. stylesheet: 'toolbarButton',
  690. alignSelf: 'normal',
  691. height: '24px'
  692. });
  693. } else {
  694. circleEmptyIcon.element({
  695. container: this.node,
  696. title: `Kernel ${Text.titleCase(status)}`,
  697. stylesheet: 'toolbarButton',
  698. alignSelf: 'normal',
  699. height: '24px'
  700. });
  701. }
  702. }
  703. /**
  704. * Check if status should be shown as busy.
  705. */
  706. private _isBusy(status: ISessionContext.KernelDisplayStatus): boolean {
  707. return (
  708. status === 'busy' ||
  709. status === 'starting' ||
  710. status === 'terminating' ||
  711. status === 'restarting' ||
  712. status === 'initializing'
  713. );
  714. }
  715. }
  716. }