UNPKG

34.4 kBJavaScriptView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3import { nullTranslator } from '@jupyterlab/translation';
4import { Button, circleEmptyIcon, circleIcon, classes, ellipsesIcon, LabIcon, offlineBoltIcon, refreshIcon, stopIcon } from '@jupyterlab/ui-components';
5import { find, map, some } from '@lumino/algorithm';
6import { CommandRegistry } from '@lumino/commands';
7import { MessageLoop } from '@lumino/messaging';
8import { AttachedProperty } from '@lumino/properties';
9import { PanelLayout, Widget } from '@lumino/widgets';
10import * as React from 'react';
11import { sessionContextDialogs } from '../sessioncontext';
12import { translateKernelStatuses } from '../kernelstatuses';
13import { ReactWidget, UseSignal } from '../vdom';
14import { Throttler } from '@lumino/polling';
15/**
16 * The class name added to toolbars.
17 */
18const TOOLBAR_CLASS = 'jp-Toolbar';
19/**
20 * Toolbar pop-up opener button name
21 */
22const TOOLBAR_OPENER_NAME = 'toolbar-popup-opener';
23/**
24 * The class name added to toolbar items.
25 */
26const TOOLBAR_ITEM_CLASS = 'jp-Toolbar-item';
27/**
28 * The class name added to toolbar kernel name text.
29 */
30const TOOLBAR_KERNEL_NAME_CLASS = 'jp-Toolbar-kernelName';
31/**
32 * The class name added to toolbar spacer.
33 */
34const TOOLBAR_SPACER_CLASS = 'jp-Toolbar-spacer';
35/**
36 * The class name added to toolbar kernel status icon.
37 */
38const TOOLBAR_KERNEL_STATUS_CLASS = 'jp-Toolbar-kernelStatus';
39/**
40 * A layout for toolbars.
41 *
42 * #### Notes
43 * This layout automatically collapses its height if there are no visible
44 * toolbar widgets, and expands to the standard toolbar height if there are
45 * visible toolbar widgets.
46 */
47class ToolbarLayout extends PanelLayout {
48 constructor() {
49 super(...arguments);
50 this._dirty = false;
51 }
52 /**
53 * A message handler invoked on a `'fit-request'` message.
54 *
55 * If any child widget is visible, expand the toolbar height to the normal
56 * toolbar height.
57 */
58 onFitRequest(msg) {
59 super.onFitRequest(msg);
60 if (this.parent.isAttached) {
61 // If there are any widgets not explicitly hidden, expand the toolbar to
62 // accommodate them.
63 if (some(this.widgets, w => !w.isHidden)) {
64 this.parent.node.style.minHeight = 'var(--jp-private-toolbar-height)';
65 this.parent.removeClass('jp-Toolbar-micro');
66 }
67 else {
68 this.parent.node.style.minHeight = '';
69 this.parent.addClass('jp-Toolbar-micro');
70 }
71 }
72 // Set the dirty flag to ensure only a single update occurs.
73 this._dirty = true;
74 // Notify the ancestor that it should fit immediately. This may
75 // cause a resize of the parent, fulfilling the required update.
76 if (this.parent.parent) {
77 MessageLoop.sendMessage(this.parent.parent, Widget.Msg.FitRequest);
78 }
79 // If the dirty flag is still set, the parent was not resized.
80 // Trigger the required update on the parent widget immediately.
81 if (this._dirty) {
82 MessageLoop.sendMessage(this.parent, Widget.Msg.UpdateRequest);
83 }
84 }
85 /**
86 * A message handler invoked on an `'update-request'` message.
87 */
88 onUpdateRequest(msg) {
89 super.onUpdateRequest(msg);
90 if (this.parent.isVisible) {
91 this._dirty = false;
92 }
93 }
94 /**
95 * A message handler invoked on a `'child-shown'` message.
96 */
97 onChildShown(msg) {
98 super.onChildShown(msg);
99 // Post a fit request for the parent widget.
100 this.parent.fit();
101 }
102 /**
103 * A message handler invoked on a `'child-hidden'` message.
104 */
105 onChildHidden(msg) {
106 super.onChildHidden(msg);
107 // Post a fit request for the parent widget.
108 this.parent.fit();
109 }
110 /**
111 * A message handler invoked on a `'before-attach'` message.
112 */
113 onBeforeAttach(msg) {
114 super.onBeforeAttach(msg);
115 // Post a fit request for the parent widget.
116 this.parent.fit();
117 }
118 /**
119 * Attach a widget to the parent's DOM node.
120 *
121 * @param index - The current index of the widget in the layout.
122 *
123 * @param widget - The widget to attach to the parent.
124 *
125 * #### Notes
126 * This is a reimplementation of the superclass method.
127 */
128 attachWidget(index, widget) {
129 super.attachWidget(index, widget);
130 // Post a fit request for the parent widget.
131 this.parent.fit();
132 }
133 /**
134 * Detach a widget from the parent's DOM node.
135 *
136 * @param index - The previous index of the widget in the layout.
137 *
138 * @param widget - The widget to detach from the parent.
139 *
140 * #### Notes
141 * This is a reimplementation of the superclass method.
142 */
143 detachWidget(index, widget) {
144 super.detachWidget(index, widget);
145 // Post a fit request for the parent widget.
146 this.parent.fit();
147 }
148}
149/**
150 * A class which provides a toolbar widget.
151 */
152export class Toolbar extends Widget {
153 /**
154 * Construct a new toolbar widget.
155 */
156 constructor(options = {}) {
157 var _a;
158 super();
159 this.addClass(TOOLBAR_CLASS);
160 this.layout = (_a = options.layout) !== null && _a !== void 0 ? _a : new ToolbarLayout();
161 }
162 /**
163 * Get an iterator over the ordered toolbar item names.
164 *
165 * @returns An iterator over the toolbar item names.
166 */
167 names() {
168 const layout = this.layout;
169 return map(layout.widgets, widget => {
170 return Private.nameProperty.get(widget);
171 });
172 }
173 /**
174 * Add an item to the end of the toolbar.
175 *
176 * @param name - The name of the widget to add to the toolbar.
177 *
178 * @param widget - The widget to add to the toolbar.
179 *
180 * @param index - The optional name of the item to insert after.
181 *
182 * @returns Whether the item was added to toolbar. Returns false if
183 * an item of the same name is already in the toolbar.
184 *
185 * #### Notes
186 * The item can be removed from the toolbar by setting its parent to `null`.
187 */
188 addItem(name, widget) {
189 const layout = this.layout;
190 return this.insertItem(layout.widgets.length, name, widget);
191 }
192 /**
193 * Insert an item into the toolbar at the specified index.
194 *
195 * @param index - The index at which to insert the item.
196 *
197 * @param name - The name of the item.
198 *
199 * @param widget - The widget to add.
200 *
201 * @returns Whether the item was added to the toolbar. Returns false if
202 * an item of the same name is already in the toolbar.
203 *
204 * #### Notes
205 * The index will be clamped to the bounds of the items.
206 * The item can be removed from the toolbar by setting its parent to `null`.
207 */
208 insertItem(index, name, widget) {
209 const existing = find(this.names(), value => value === name);
210 if (existing) {
211 return false;
212 }
213 widget.addClass(TOOLBAR_ITEM_CLASS);
214 const layout = this.layout;
215 const j = Math.max(0, Math.min(index, layout.widgets.length));
216 layout.insertWidget(j, widget);
217 Private.nameProperty.set(widget, name);
218 return true;
219 }
220 /**
221 * Insert an item into the toolbar at the after a target item.
222 *
223 * @param at - The target item to insert after.
224 *
225 * @param name - The name of the item.
226 *
227 * @param widget - The widget to add.
228 *
229 * @returns Whether the item was added to the toolbar. Returns false if
230 * an item of the same name is already in the toolbar.
231 *
232 * #### Notes
233 * The index will be clamped to the bounds of the items.
234 * The item can be removed from the toolbar by setting its parent to `null`.
235 */
236 insertAfter(at, name, widget) {
237 return this._insertRelative(at, 1, name, widget);
238 }
239 /**
240 * Insert an item into the toolbar at the before a target item.
241 *
242 * @param at - The target item to insert before.
243 *
244 * @param name - The name of the item.
245 *
246 * @param widget - The widget to add.
247 *
248 * @returns Whether the item was added to the toolbar. Returns false if
249 * an item of the same name is already in the toolbar.
250 *
251 * #### Notes
252 * The index will be clamped to the bounds of the items.
253 * The item can be removed from the toolbar by setting its parent to `null`.
254 */
255 insertBefore(at, name, widget) {
256 return this._insertRelative(at, 0, name, widget);
257 }
258 _insertRelative(at, offset, name, widget) {
259 const nameWithIndex = map(this.names(), (name, i) => {
260 return { name: name, index: i };
261 });
262 const target = find(nameWithIndex, x => x.name === at);
263 if (target) {
264 return this.insertItem(target.index + offset, name, widget);
265 }
266 return false;
267 }
268 /**
269 * Handle the DOM events for the widget.
270 *
271 * @param event - The DOM event sent to the widget.
272 *
273 * #### Notes
274 * This method implements the DOM `EventListener` interface and is
275 * called in response to events on the dock panel's node. It should
276 * not be called directly by user code.
277 */
278 handleEvent(event) {
279 switch (event.type) {
280 case 'click':
281 this.handleClick(event);
282 break;
283 default:
284 break;
285 }
286 }
287 /**
288 * Handle a DOM click event.
289 */
290 handleClick(event) {
291 // Stop propagating the click outside the toolbar
292 event.stopPropagation();
293 // Clicking a label focuses the corresponding control
294 // that is linked with `for` attribute, so let it be.
295 if (event.target instanceof HTMLLabelElement) {
296 const forId = event.target.getAttribute('for');
297 if (forId && this.node.querySelector(`#${forId}`)) {
298 return;
299 }
300 }
301 // If this click already focused a control, let it be.
302 if (this.node.contains(document.activeElement)) {
303 return;
304 }
305 // Otherwise, activate the parent widget, which may take focus if desired.
306 if (this.parent) {
307 this.parent.activate();
308 }
309 }
310 /**
311 * Handle `after-attach` messages for the widget.
312 */
313 onAfterAttach(msg) {
314 this.node.addEventListener('click', this);
315 }
316 /**
317 * Handle `before-detach` messages for the widget.
318 */
319 onBeforeDetach(msg) {
320 this.node.removeEventListener('click', this);
321 }
322}
323/**
324 * A class which provides a toolbar widget.
325 */
326export class ReactiveToolbar extends Toolbar {
327 /**
328 * Construct a new toolbar widget.
329 */
330 constructor() {
331 super();
332 this.popupOpener = new ToolbarPopupOpener();
333 this._widgetWidths = {};
334 this.insertItem(0, TOOLBAR_OPENER_NAME, this.popupOpener);
335 this.popupOpener.hide();
336 this._resizer = new Throttler(this._onResize.bind(this), 500);
337 }
338 /**
339 * Dispose of the widget and its descendant widgets.
340 */
341 dispose() {
342 if (this.isDisposed) {
343 return;
344 }
345 if (this._resizer) {
346 this._resizer.dispose();
347 }
348 super.dispose();
349 }
350 /**
351 * Insert an item into the toolbar at the after a target item.
352 *
353 * @param at - The target item to insert after.
354 *
355 * @param name - The name of the item.
356 *
357 * @param widget - The widget to add.
358 *
359 * @returns Whether the item was added to the toolbar. Returns false if
360 * an item of the same name is already in the toolbar or if the target
361 * is the toolbar pop-up opener.
362 *
363 * #### Notes
364 * The index will be clamped to the bounds of the items.
365 * The item can be removed from the toolbar by setting its parent to `null`.
366 */
367 insertAfter(at, name, widget) {
368 if (at === TOOLBAR_OPENER_NAME) {
369 return false;
370 }
371 return super.insertAfter(at, name, widget);
372 }
373 /**
374 * Insert an item into the toolbar at the specified index.
375 *
376 * @param index - The index at which to insert the item.
377 *
378 * @param name - The name of the item.
379 *
380 * @param widget - The widget to add.
381 *
382 * @returns Whether the item was added to the toolbar. Returns false if
383 * an item of the same name is already in the toolbar.
384 *
385 * #### Notes
386 * The index will be clamped to the bounds of the items.
387 * The item can be removed from the toolbar by setting its parent to `null`.
388 */
389 insertItem(index, name, widget) {
390 if (widget instanceof ToolbarPopupOpener) {
391 return super.insertItem(index, name, widget);
392 }
393 else {
394 const j = Math.max(0, Math.min(index, this.layout.widgets.length - 1));
395 return super.insertItem(j, name, widget);
396 }
397 }
398 /**
399 * A message handler invoked on a `'before-hide'` message.
400 *
401 * It will hide the pop-up panel
402 */
403 onBeforeHide(msg) {
404 this.popupOpener.hidePopup();
405 super.onBeforeHide(msg);
406 }
407 onResize(msg) {
408 super.onResize(msg);
409 if (msg.width > 0 && this._resizer) {
410 void this._resizer.invoke();
411 }
412 }
413 _onResize() {
414 if (this.parent && this.parent.isAttached) {
415 const toolbarWidth = this.node.clientWidth;
416 const opener = this.popupOpener;
417 const openerWidth = 30;
418 const toolbarPadding = 2;
419 const layout = this.layout;
420 let width = opener.isHidden
421 ? toolbarPadding
422 : toolbarPadding + openerWidth;
423 let index = 0;
424 const widgetsToRemove = [];
425 const toIndex = layout.widgets.length - 1;
426 while (index < toIndex) {
427 const widget = layout.widgets[index];
428 this._saveWidgetWidth(widget);
429 width += this._getWidgetWidth(widget);
430 if (widgetsToRemove.length === 0 &&
431 opener.isHidden &&
432 width + openerWidth > toolbarWidth) {
433 width += openerWidth;
434 }
435 if (width > toolbarWidth) {
436 widgetsToRemove.push(widget);
437 }
438 index++;
439 }
440 while (widgetsToRemove.length > 0) {
441 const widget = widgetsToRemove.pop();
442 width -= this._getWidgetWidth(widget);
443 opener.addWidget(widget);
444 }
445 if (opener.widgetCount() > 0) {
446 const widgetsToAdd = [];
447 let index = 0;
448 let widget = opener.widgetAt(index);
449 const widgetCount = opener.widgetCount();
450 width += this._getWidgetWidth(widget);
451 if (widgetCount === 1 && width - openerWidth <= toolbarWidth) {
452 width -= openerWidth;
453 }
454 while (width < toolbarWidth && index < widgetCount) {
455 widgetsToAdd.push(widget);
456 index++;
457 widget = opener.widgetAt(index);
458 if (widget) {
459 width += this._getWidgetWidth(widget);
460 }
461 else {
462 break;
463 }
464 }
465 while (widgetsToAdd.length > 0) {
466 const widget = widgetsToAdd.shift();
467 this.addItem(Private.nameProperty.get(widget), widget);
468 }
469 }
470 if (opener.widgetCount() > 0) {
471 opener.updatePopup();
472 opener.show();
473 }
474 else {
475 opener.hide();
476 }
477 }
478 }
479 _saveWidgetWidth(widget) {
480 const widgetName = Private.nameProperty.get(widget);
481 this._widgetWidths[widgetName] = widget.hasClass(TOOLBAR_SPACER_CLASS)
482 ? 2
483 : widget.node.clientWidth;
484 }
485 _getWidgetWidth(widget) {
486 const widgetName = Private.nameProperty.get(widget);
487 return this._widgetWidths[widgetName];
488 }
489}
490/**
491 * The namespace for Toolbar class statics.
492 */
493(function (Toolbar) {
494 /**
495 * Create an interrupt toolbar item.
496 *
497 * @deprecated since version v3.2
498 * This is dead code now.
499 */
500 function createInterruptButton(sessionContext, translator) {
501 translator = translator || nullTranslator;
502 const trans = translator.load('jupyterlab');
503 return new ToolbarButton({
504 icon: stopIcon,
505 onClick: () => {
506 var _a, _b;
507 void ((_b = (_a = sessionContext.session) === null || _a === void 0 ? void 0 : _a.kernel) === null || _b === void 0 ? void 0 : _b.interrupt());
508 },
509 tooltip: trans.__('Interrupt the kernel')
510 });
511 }
512 Toolbar.createInterruptButton = createInterruptButton;
513 /**
514 * Create a restart toolbar item.
515 *
516 * @deprecated since v3.2
517 * This is dead code now.
518 */
519 function createRestartButton(sessionContext, dialogs, translator) {
520 translator = translator || nullTranslator;
521 const trans = translator.load('jupyterlab');
522 return new ToolbarButton({
523 icon: refreshIcon,
524 onClick: () => {
525 void (dialogs !== null && dialogs !== void 0 ? dialogs : sessionContextDialogs).restart(sessionContext, translator);
526 },
527 tooltip: trans.__('Restart the kernel')
528 });
529 }
530 Toolbar.createRestartButton = createRestartButton;
531 /**
532 * Create a toolbar spacer item.
533 *
534 * #### Notes
535 * It is a flex spacer that separates the left toolbar items
536 * from the right toolbar items.
537 */
538 function createSpacerItem() {
539 return new Private.Spacer();
540 }
541 Toolbar.createSpacerItem = createSpacerItem;
542 /**
543 * Create a kernel name indicator item.
544 *
545 * #### Notes
546 * It will display the `'display_name`' of the session context. It can
547 * handle a change in context or kernel.
548 */
549 function createKernelNameItem(sessionContext, dialogs, translator) {
550 const el = ReactWidget.create(React.createElement(Private.KernelNameComponent, { sessionContext: sessionContext, dialogs: dialogs !== null && dialogs !== void 0 ? dialogs : sessionContextDialogs, translator: translator }));
551 el.addClass('jp-KernelName');
552 return el;
553 }
554 Toolbar.createKernelNameItem = createKernelNameItem;
555 /**
556 * Create a kernel status indicator item.
557 *
558 * #### Notes
559 * It will show a busy status if the kernel status is busy.
560 * It will show the current status in the node title.
561 * It can handle a change to the context or the kernel.
562 */
563 function createKernelStatusItem(sessionContext, translator) {
564 return new Private.KernelStatus(sessionContext, translator);
565 }
566 Toolbar.createKernelStatusItem = createKernelStatusItem;
567})(Toolbar || (Toolbar = {}));
568/**
569 * React component for a toolbar button.
570 *
571 * @param props - The props for ToolbarButtonComponent.
572 */
573export function ToolbarButtonComponent(props) {
574 var _a, _b;
575 // In some browsers, a button click event moves the focus from the main
576 // content to the button (see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus).
577 // We avoid a click event by calling preventDefault in mousedown, and
578 // we bind the button action to `mousedown`.
579 const handleMouseDown = (event) => {
580 var _a;
581 // Fire action only when left button is pressed.
582 if (event.button === 0) {
583 event.preventDefault();
584 (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
585 }
586 };
587 const handleKeyDown = (event) => {
588 var _a;
589 const { key } = event;
590 if (key === 'Enter' || key === ' ') {
591 (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
592 }
593 };
594 const handleClick = (event) => {
595 var _a;
596 if (event.button === 0) {
597 (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
598 }
599 };
600 const getTooltip = () => {
601 if (props.enabled === false && props.disabledTooltip) {
602 return props.disabledTooltip;
603 }
604 else if (props.pressed && props.pressedTooltip) {
605 return props.pressedTooltip;
606 }
607 else {
608 return props.tooltip || props.iconLabel;
609 }
610 };
611 return (React.createElement(Button, Object.assign({ className: props.className
612 ? props.className + ' jp-ToolbarButtonComponent'
613 : 'jp-ToolbarButtonComponent', "aria-pressed": props.pressed, "aria-disabled": props.enabled === false }, props.dataset, { disabled: props.enabled === false, onClick: ((_a = props.actualOnClick) !== null && _a !== void 0 ? _a : false) ? handleClick : undefined, onMouseDown: !((_b = props.actualOnClick) !== null && _b !== void 0 ? _b : false) ? handleMouseDown : undefined, onKeyDown: handleKeyDown, title: getTooltip(), minimal: true }),
614 (props.icon || props.iconClass) && (React.createElement(LabIcon.resolveReact, { icon: props.pressed ? props.pressedIcon : props.icon, iconClass:
615 // add some extra classes for proper support of icons-as-css-background
616 classes(props.iconClass, 'jp-Icon'), className: "jp-ToolbarButtonComponent-icon", tag: "span", stylesheet: "toolbarButton" })),
617 props.label && (React.createElement("span", { className: "jp-ToolbarButtonComponent-label" }, props.label))));
618}
619/**
620 * Adds the toolbar button class to the toolbar widget.
621 * @param w Toolbar button widget.
622 */
623export function addToolbarButtonClass(w) {
624 w.addClass('jp-ToolbarButton');
625 return w;
626}
627/**
628 * Phosphor Widget version of static ToolbarButtonComponent.
629 */
630export class ToolbarButton extends ReactWidget {
631 /**
632 * Creates a toolbar button
633 * @param props props for underlying `ToolbarButton` component
634 */
635 constructor(props = {}) {
636 var _a, _b;
637 super();
638 this.props = props;
639 addToolbarButtonClass(this);
640 this._enabled = (_a = props.enabled) !== null && _a !== void 0 ? _a : true;
641 this._pressed = this._enabled && ((_b = props.pressed) !== null && _b !== void 0 ? _b : false);
642 this._onClick = props.onClick;
643 }
644 /**
645 * Sets the pressed state for the button
646 * @param value true if button is pressed, false otherwise
647 */
648 set pressed(value) {
649 if (this.enabled && value !== this._pressed) {
650 this._pressed = value;
651 this.update();
652 }
653 }
654 /**
655 * Returns true if button is pressed, false otherwise
656 */
657 get pressed() {
658 return this._pressed;
659 }
660 /**
661 * Sets the enabled state for the button
662 * @param value true to enable the button, false otherwise
663 */
664 set enabled(value) {
665 if (value != this._enabled) {
666 this._enabled = value;
667 if (!this._enabled) {
668 this._pressed = false;
669 }
670 this.update();
671 }
672 }
673 /**
674 * Returns true if button is enabled, false otherwise
675 */
676 get enabled() {
677 return this._enabled;
678 }
679 /**
680 * Sets the click handler for the button
681 * @param value click handler
682 */
683 set onClick(value) {
684 if (value !== this._onClick) {
685 this._onClick = value;
686 this.update();
687 }
688 }
689 /**
690 * Returns the click handler for the button
691 */
692 get onClick() {
693 return this._onClick;
694 }
695 render() {
696 return (React.createElement(ToolbarButtonComponent, Object.assign({}, this.props, { pressed: this.pressed, enabled: this.enabled, onClick: this.onClick })));
697 }
698}
699/**
700 * React component for a toolbar button that wraps a command.
701 *
702 * This wraps the ToolbarButtonComponent and watches the command registry
703 * for changes to the command.
704 */
705export function CommandToolbarButtonComponent(props) {
706 return (React.createElement(UseSignal, { signal: props.commands.commandChanged, shouldUpdate: (sender, args) => (args.id === props.id && args.type === 'changed') ||
707 args.type === 'many-changed' }, () => React.createElement(ToolbarButtonComponent, Object.assign({}, Private.propsFromCommand(props)))));
708}
709/*
710 * Adds the command toolbar button class to the command toolbar widget.
711 * @param w Command toolbar button widget.
712 */
713export function addCommandToolbarButtonClass(w) {
714 w.addClass('jp-CommandToolbarButton');
715 return w;
716}
717/**
718 * Phosphor Widget version of CommandToolbarButtonComponent.
719 */
720export class CommandToolbarButton extends ReactWidget {
721 /**
722 * Creates a command toolbar button
723 * @param props props for underlying `CommandToolbarButtonComponent` component
724 */
725 constructor(props) {
726 super();
727 this.props = props;
728 addCommandToolbarButtonClass(this);
729 }
730 render() {
731 return React.createElement(CommandToolbarButtonComponent, Object.assign({}, this.props));
732 }
733}
734/**
735 * A class which provides a toolbar popup
736 * used to store widgets that don't fit
737 * in the toolbar when it is resized
738 */
739class ToolbarPopup extends Widget {
740 /**
741 * Construct a new ToolbarPopup
742 */
743 constructor() {
744 super();
745 this.width = 0;
746 this.addClass('jp-Toolbar-responsive-popup');
747 this.layout = new PanelLayout();
748 Widget.attach(this, document.body);
749 this.hide();
750 }
751 /**
752 * Updates the width of the popup, this
753 * should match with the toolbar width
754 *
755 * @param width - The width to resize to
756 * @protected
757 */
758 updateWidth(width) {
759 if (width > 0) {
760 this.width = width;
761 this.node.style.width = `${width}px`;
762 }
763 }
764 /**
765 * Aligns the popup to left bottom of widget
766 *
767 * @param widget the widget to align to
768 * @private
769 */
770 alignTo(widget) {
771 const { height: widgetHeight, width: widgetWidth, x: widgetX, y: widgetY } = widget.node.getBoundingClientRect();
772 const width = this.width;
773 this.node.style.left = `${widgetX + widgetWidth - width + 1}px`;
774 this.node.style.top = `${widgetY + widgetHeight + 1}px`;
775 }
776 /**
777 * Inserts the widget at specified index
778 * @param index the index
779 * @param widget widget to add
780 */
781 insertWidget(index, widget) {
782 this.layout.insertWidget(0, widget);
783 }
784 /**
785 * Total number of widgets in the popup
786 */
787 widgetCount() {
788 return this.layout.widgets.length;
789 }
790 /**
791 * Returns the widget at index
792 * @param index the index
793 */
794 widgetAt(index) {
795 return this.layout.widgets[index];
796 }
797}
798/**
799 * A class that provides a ToolbarPopupOpener,
800 * which is a button added to toolbar when
801 * the toolbar items overflow toolbar width
802 */
803class ToolbarPopupOpener extends ToolbarButton {
804 /**
805 * Create a new popup opener
806 */
807 constructor() {
808 super({
809 icon: ellipsesIcon,
810 onClick: () => {
811 this.handleClick();
812 }
813 });
814 this.addClass('jp-Toolbar-responsive-opener');
815 this.popup = new ToolbarPopup();
816 }
817 /**
818 * Add widget to the popup, prepends widgets
819 * @param widget the widget to add
820 */
821 addWidget(widget) {
822 this.popup.insertWidget(0, widget);
823 }
824 /**
825 * Dispose of the widget and its descendant widgets.
826 *
827 * #### Notes
828 * It is unsafe to use the widget after it has been disposed.
829 *
830 * All calls made to this method after the first are a no-op.
831 */
832 dispose() {
833 if (this.isDisposed) {
834 return;
835 }
836 this.popup.dispose();
837 super.dispose();
838 }
839 /**
840 * Hides the opener and the popup
841 */
842 hide() {
843 super.hide();
844 this.hidePopup();
845 }
846 /**
847 * Hides the popup
848 */
849 hidePopup() {
850 this.popup.hide();
851 }
852 /**
853 * Updates width and position of the popup
854 * to align with the toolbar
855 */
856 updatePopup() {
857 this.popup.updateWidth(this.parent.node.clientWidth);
858 this.popup.alignTo(this.parent);
859 }
860 /**
861 * Returns widget at index in the popup
862 * @param index
863 */
864 widgetAt(index) {
865 return this.popup.widgetAt(index);
866 }
867 /**
868 * Returns total number of widgets in the popup
869 *
870 * @returns Number of widgets
871 */
872 widgetCount() {
873 return this.popup.widgetCount();
874 }
875 handleClick() {
876 this.updatePopup();
877 this.popup.setHidden(!this.popup.isHidden);
878 }
879}
880/**
881 * A namespace for private data.
882 */
883var Private;
884(function (Private) {
885 function propsFromCommand(options) {
886 var _a, _b;
887 const { commands, id, args } = options;
888 const iconClass = commands.iconClass(id, args);
889 const iconLabel = commands.iconLabel(id, args);
890 // DEPRECATED: remove _icon when lumino 2.0 is adopted
891 // if icon is aliasing iconClass, don't use it
892 const _icon = (_a = options.icon) !== null && _a !== void 0 ? _a : commands.icon(id, args);
893 const icon = _icon === iconClass ? undefined : _icon;
894 const label = commands.label(id, args);
895 let className = commands.className(id, args);
896 // Add the boolean state classes.
897 if (commands.isToggled(id, args)) {
898 className += ' lm-mod-toggled';
899 }
900 if (!commands.isVisible(id, args)) {
901 className += ' lm-mod-hidden';
902 }
903 let tooltip = commands.caption(id, args) || options.label || label || iconLabel;
904 // Shows hot keys in tooltips
905 const binding = commands.keyBindings.find(b => b.command === id);
906 if (binding) {
907 const ks = CommandRegistry.formatKeystroke(binding.keys.join(' '));
908 tooltip = `${tooltip} (${ks})`;
909 }
910 const onClick = () => {
911 void commands.execute(id, args);
912 };
913 const enabled = commands.isEnabled(id, args);
914 return {
915 className,
916 dataset: { 'data-command': options.id },
917 icon,
918 iconClass,
919 tooltip,
920 onClick,
921 enabled,
922 label: (_b = options.label) !== null && _b !== void 0 ? _b : label
923 };
924 }
925 Private.propsFromCommand = propsFromCommand;
926 /**
927 * An attached property for the name of a toolbar item.
928 */
929 Private.nameProperty = new AttachedProperty({
930 name: 'name',
931 create: () => ''
932 });
933 /**
934 * A no-op function.
935 */
936 function noOp() {
937 /* no-op */
938 }
939 Private.noOp = noOp;
940 /**
941 * A spacer widget.
942 */
943 class Spacer extends Widget {
944 /**
945 * Construct a new spacer widget.
946 */
947 constructor() {
948 super();
949 this.addClass(TOOLBAR_SPACER_CLASS);
950 }
951 }
952 Private.Spacer = Spacer;
953 /**
954 * React component for a kernel name button.
955 *
956 * This wraps the ToolbarButtonComponent and watches the kernel
957 * session for changes.
958 */
959 function KernelNameComponent(props) {
960 const translator = props.translator || nullTranslator;
961 const trans = translator.load('jupyterlab');
962 const callback = () => {
963 void props.dialogs.selectKernel(props.sessionContext, translator);
964 };
965 return (React.createElement(UseSignal, { signal: props.sessionContext.kernelChanged, initialSender: props.sessionContext }, sessionContext => (React.createElement(ToolbarButtonComponent, { className: TOOLBAR_KERNEL_NAME_CLASS, onClick: callback, tooltip: trans.__('Switch kernel'), label: sessionContext === null || sessionContext === void 0 ? void 0 : sessionContext.kernelDisplayName }))));
966 }
967 Private.KernelNameComponent = KernelNameComponent;
968 /**
969 * A toolbar item that displays kernel status.
970 */
971 class KernelStatus extends Widget {
972 /**
973 * Construct a new kernel status widget.
974 */
975 constructor(sessionContext, translator) {
976 super();
977 this.translator = translator || nullTranslator;
978 this._trans = this.translator.load('jupyterlab');
979 this.addClass(TOOLBAR_KERNEL_STATUS_CLASS);
980 this._statusNames = translateKernelStatuses(this.translator);
981 this._onStatusChanged(sessionContext);
982 sessionContext.statusChanged.connect(this._onStatusChanged, this);
983 sessionContext.connectionStatusChanged.connect(this._onStatusChanged, this);
984 }
985 /**
986 * Handle a status on a kernel.
987 */
988 _onStatusChanged(sessionContext) {
989 if (this.isDisposed) {
990 return;
991 }
992 const status = sessionContext.kernelDisplayStatus;
993 const circleIconProps = {
994 container: this.node,
995 title: this._trans.__('Kernel %1', this._statusNames[status] || status),
996 stylesheet: 'toolbarButton',
997 alignSelf: 'normal',
998 height: '24px'
999 };
1000 // set the icon
1001 LabIcon.remove(this.node);
1002 if (status === 'busy' ||
1003 status === 'starting' ||
1004 status === 'terminating' ||
1005 status === 'restarting' ||
1006 status === 'initializing') {
1007 circleIcon.element(circleIconProps);
1008 }
1009 else if (status === 'connecting' ||
1010 status === 'disconnected' ||
1011 status === 'unknown') {
1012 offlineBoltIcon.element(circleIconProps);
1013 }
1014 else {
1015 circleEmptyIcon.element(circleIconProps);
1016 }
1017 }
1018 }
1019 Private.KernelStatus = KernelStatus;
1020})(Private || (Private = {}));
1021//# sourceMappingURL=widget.js.map
\No newline at end of file