123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import {
- IIterator, find, map
- } from '@phosphor/algorithm';
- import {
- CommandRegistry
- } from '@phosphor/commands';
- import {
- Message
- } from '@phosphor/messaging';
- import {
- AttachedProperty
- } from '@phosphor/properties';
- import {
- PanelLayout, Widget
- } from '@phosphor/widgets';
- import {
- IClientSession, Styling
- } from '.';
- /**
- * The class name added to toolbars.
- */
- const TOOLBAR_CLASS = 'jp-Toolbar';
- /**
- * The class name added to toolbar items.
- */
- const TOOLBAR_ITEM_CLASS = 'jp-Toolbar-item';
- /**
- * The class name added to toolbar buttons.
- */
- const TOOLBAR_BUTTON_CLASS = 'jp-Toolbar-button';
- /**
- * The class name added to a pressed button.
- */
- const TOOLBAR_PRESSED_CLASS = 'jp-mod-pressed';
- /**
- * The class name added to toolbar interrupt button.
- */
- const TOOLBAR_INTERRUPT_CLASS = 'jp-StopIcon';
- /**
- * The class name added to toolbar restart button.
- */
- const TOOLBAR_RESTART_CLASS = 'jp-RefreshIcon';
- /**
- * The class name added to toolbar kernel name text.
- */
- const TOOLBAR_KERNEL_NAME_CLASS = 'jp-Toolbar-kernelName';
- /**
- * The class name added to toolbar spacer.
- */
- const TOOLBAR_SPACER_CLASS = 'jp-Toolbar-spacer';
- /**
- * The class name added to toolbar kernel status icon.
- */
- const TOOLBAR_KERNEL_STATUS_CLASS = 'jp-Toolbar-kernelStatus';
- /**
- * The class name added to a busy kernel indicator.
- */
- const TOOLBAR_BUSY_CLASS = 'jp-FilledCircleIcon';
- const TOOLBAR_IDLE_CLASS = 'jp-CircleIcon';
- /**
- * A class which provides a toolbar widget.
- */
- export
- class Toolbar<T extends Widget> extends Widget {
- /**
- * Construct a new toolbar widget.
- */
- constructor() {
- super();
- this.addClass(TOOLBAR_CLASS);
- this.layout = new PanelLayout();
- }
- /**
- * Get an iterator over the ordered toolbar item names.
- *
- * @returns An iterator over the toolbar item names.
- */
- names(): IIterator<string> {
- let layout = this.layout as PanelLayout;
- return map(layout.widgets, widget => {
- return Private.nameProperty.get(widget);
- });
- }
- /**
- * Add an item to the end of the toolbar.
- *
- * @param name - The name of the widget to add to the toolbar.
- *
- * @param widget - The widget to add to the toolbar.
- *
- * @param index - The optional name of the item to insert after.
- *
- * @returns Whether the item was added to toolbar. Returns false if
- * an item of the same name is already in the toolbar.
- *
- * #### Notes
- * The item can be removed from the toolbar by setting its parent to `null`.
- */
- addItem(name: string, widget: T): boolean {
- let layout = this.layout as PanelLayout;
- return this.insertItem(layout.widgets.length, name, widget);
- }
- /**
- * Insert an item into the toolbar at the specified index.
- *
- * @param index - The index at which to insert the item.
- *
- * @param name - The name of the item.
- *
- * @param widget - The widget to add.
- *
- * @returns Whether the item was added to the toolbar. Returns false if
- * an item of the same name is already in the toolbar.
- *
- * #### Notes
- * The index will be clamped to the bounds of the items.
- * The item can be removed from the toolbar by setting its parent to `null`.
- */
- insertItem(index: number, name: string, widget: T): boolean {
- let existing = find(this.names(), value => value === name);
- if (existing) {
- return false;
- }
- widget.addClass(TOOLBAR_ITEM_CLASS);
- let layout = this.layout as PanelLayout;
- layout.insertWidget(index, widget);
- Private.nameProperty.set(widget, name);
- return true;
- }
- /**
- * Handle the DOM events for the widget.
- *
- * @param event - The DOM event sent to the widget.
- *
- * #### Notes
- * This method implements the DOM `EventListener` interface and is
- * called in response to events on the dock panel's node. It should
- * not be called directly by user code.
- */
- handleEvent(event: Event): void {
- switch (event.type) {
- case 'click':
- if (!this.node.contains(document.activeElement) && this.parent) {
- this.parent.activate();
- }
- break;
- default:
- break;
- }
- }
- /**
- * Handle `after-attach` messages for the widget.
- */
- protected onAfterAttach(msg: Message): void {
- this.node.addEventListener('click', this);
- }
- /**
- * Handle `before-detach` messages for the widget.
- */
- protected onBeforeDetach(msg: Message): void {
- this.node.removeEventListener('click', this);
- }
- }
- /**
- * The namespace for Toolbar class statics.
- */
- export
- namespace Toolbar {
- /**
- * Create a toolbar item for a command.
- *
- * Notes:
- * If the command has an icon label it will be added to the button.
- * If there is no icon label, and no icon class, the main label will
- * be added.
- */
- export
- function createFromCommand(commands: CommandRegistry, id: string): ToolbarButton {
- let button = new ToolbarButton({
- onClick: () => { commands.execute(id); },
- className: Private.commandClassName(commands, id),
- tooltip: Private.commandTooltip(commands, id),
- });
- let oldClasses = Private.commandClassName(commands, id).split(/\s/);
- (button.node as HTMLButtonElement).disabled = !commands.isEnabled(id);
- Private.setNodeContentFromCommand(button.node, commands, id);
- // Ensure that we pick up relevant changes to the command:
- function onChange(sender: CommandRegistry, args: CommandRegistry.ICommandChangedArgs) {
- if (args.id !== id) {
- return; // Not our command
- }
- if (args.type === 'removed') {
- // Dispose of button
- button.dispose();
- } else if (args.type === 'changed') {
- // Update all fields (onClick is already indirected)
- let newClasses = Private.commandClassName(sender, id).split(/\s/);
- for (let cls of oldClasses) {
- if (cls && newClasses.indexOf(cls) === -1) {
- button.removeClass(cls);
- }
- }
- for (let cls of newClasses) {
- if (cls && oldClasses.indexOf(cls) === -1) {
- button.addClass(cls);
- }
- }
- oldClasses = newClasses;
- button.node.title = Private.commandTooltip(sender, id);
- Private.setNodeContentFromCommand(button.node, sender, id);
- (button.node as HTMLButtonElement).disabled = !sender.isEnabled(id);
- }
- }
- commands.commandChanged.connect(onChange, button);
- return button;
- }
- /**
- * Create an interrupt toolbar item.
- */
- export
- function createInterruptButton(session: IClientSession): ToolbarButton {
- return new ToolbarButton({
- className: TOOLBAR_INTERRUPT_CLASS,
- onClick: () => {
- if (session.kernel) {
- session.kernel.interrupt();
- }
- },
- tooltip: 'Interrupt the kernel'
- });
- }
- /**
- * Create a restart toolbar item.
- */
- export
- function createRestartButton(session: IClientSession): ToolbarButton {
- return new ToolbarButton({
- className: TOOLBAR_RESTART_CLASS,
- onClick: () => {
- session.restart();
- },
- tooltip: 'Restart the kernel'
- });
- }
- /**
- * Create a toolbar spacer item.
- *
- * #### Notes
- * It is a flex spacer that separates the left toolbar items
- * from the right toolbar items.
- */
- export
- function createSpacerItem(): Widget {
- return new Private.Spacer();
- }
- /**
- * Create a kernel name indicator item.
- *
- * #### Notes
- * It will display the `'display_name`' of the current kernel,
- * or `'No Kernel!'` if there is no kernel.
- * It can handle a change in context or kernel.
- */
- export
- function createKernelNameItem(session: IClientSession): ToolbarButton {
- return new Private.KernelName(session);
- }
- /**
- * Create a kernel status indicator item.
- *
- * #### Notes
- * It show display a busy status if the kernel status is
- * not idle.
- * It will show the current status in the node title.
- * It can handle a change to the context or the kernel.
- */
- export
- function createKernelStatusItem(session: IClientSession): Widget {
- return new Private.KernelStatus(session);
- }
- }
- /**
- * A widget which acts as a button in a toolbar.
- */
- export
- class ToolbarButton extends Widget {
- /**
- * Construct a new toolbar button.
- */
- constructor(options: ToolbarButton.IOptions = {}) {
- super({ node: document.createElement('button') });
- Styling.styleNodeByTag(this.node, 'button');
- this.addClass(TOOLBAR_BUTTON_CLASS);
- this._onClick = options.onClick || Private.noOp;
- if (options.className) {
- for (let extra of options.className.split(/\s/)) {
- this.addClass(extra);
- }
- }
- this.node.title = options.tooltip || '';
- }
- /**
- * Handle the DOM events for the widget.
- *
- * @param event - The DOM event sent to the widget.
- *
- * #### Notes
- * This method implements the DOM `EventListener` interface and is
- * called in response to events on the dock panel's node. It should
- * not be called directly by user code.
- */
- handleEvent(event: Event): void {
- switch (event.type) {
- case 'click':
- if ((event as MouseEvent).button === 0) {
- this._onClick();
- }
- break;
- case 'mousedown':
- this.addClass(TOOLBAR_PRESSED_CLASS);
- break;
- case 'mouseup':
- case 'mouseout':
- this.removeClass(TOOLBAR_PRESSED_CLASS);
- break;
- default:
- break;
- }
- }
- /**
- * Handle `after-attach` messages for the widget.
- */
- protected onAfterAttach(msg: Message): void {
- this.node.addEventListener('click', this);
- this.node.addEventListener('mousedown', this);
- this.node.addEventListener('mouseup', this);
- this.node.addEventListener('mouseout', this);
- }
- /**
- * Handle `before-detach` messages for the widget.
- */
- protected onBeforeDetach(msg: Message): void {
- this.node.removeEventListener('click', this);
- this.node.removeEventListener('mousedown', this);
- this.node.removeEventListener('mouseup', this);
- this.node.removeEventListener('mouseout', this);
- }
- private _onClick: () => void;
- }
- /**
- * A namespace for `ToolbarButton` statics.
- */
- export
- namespace ToolbarButton {
- /**
- * The options used to construct a toolbar button.
- */
- export
- interface IOptions {
- /**
- * The callback for a click event.
- */
- onClick?: () => void;
- /**
- * The class name added to the button.
- */
- className?: string;
- /**
- * The tooltip added to the button node.
- */
- tooltip?: string;
- }
- }
- /**
- * A namespace for private data.
- */
- namespace Private {
- /**
- * An attached property for the name of a toolbar item.
- */
- export
- const nameProperty = new AttachedProperty<Widget, string>({
- name: 'name',
- create: () => ''
- });
- /**
- * ToolbarButton tooltip formatter for a command.
- */
- export
- function commandTooltip(commands: CommandRegistry, id: string): string {
- return commands.caption(id);
- }
- /**
- * A no-op function.
- */
- export
- function noOp() { /* no-op */ }
- /**
- * Get the class names for a command based ToolBarButton
- */
- export
- function commandClassName(commands: CommandRegistry, id: string): string {
- let name = commands.className(id);
- // Add the boolean state classes.
- if (commands.isToggled(id)) {
- name += ' p-mod-toggled';
- }
- if (!commands.isVisible(id)) {
- name += ' p-mod-hidden';
- }
- return name;
- }
- /**
- * Fill the node of a command based ToolBarButton.
- */
- export
- function setNodeContentFromCommand(node: HTMLElement, commands: CommandRegistry, id: string): void {
- let iconClass = commands.iconClass(id);
- let iconLabel = commands.iconLabel(id);
- node.innerHTML = '';
- if (iconClass || iconLabel) {
- let icon = document.createElement('div');
- icon.innerText = commands.iconLabel(id);
- icon.className += ` ${iconClass}`;
- node.appendChild(icon);
- } else {
- node.innerText = commands.label(id);
- }
- }
- /**
- * A spacer widget.
- */
- export
- class Spacer extends Widget {
- /**
- * Construct a new spacer widget.
- */
- constructor() {
- super();
- this.addClass(TOOLBAR_SPACER_CLASS);
- }
- }
- /**
- * A kernel name widget.
- */
- export
- class KernelName extends ToolbarButton {
- /**
- * Construct a new kernel name widget.
- */
- constructor(session: IClientSession) {
- super({
- className: TOOLBAR_KERNEL_NAME_CLASS,
- onClick: () => {
- session.selectKernel();
- },
- tooltip: 'Switch kernel'
- });
- this._onKernelChanged(session);
- session.kernelChanged.connect(this._onKernelChanged, this);
- }
- /**
- * Update the text of the kernel name item.
- */
- _onKernelChanged(session: IClientSession): void {
- this.node.textContent = session.kernelDisplayName;
- }
- }
- /**
- * A toolbar item that displays kernel status.
- */
- export
- class KernelStatus extends Widget {
- /**
- * Construct a new kernel status widget.
- */
- constructor(session: IClientSession) {
- super();
- this.addClass(TOOLBAR_KERNEL_STATUS_CLASS);
- this._onStatusChanged(session);
- session.statusChanged.connect(this._onStatusChanged, this);
- }
- /**
- * Handle a status on a kernel.
- */
- private _onStatusChanged(session: IClientSession) {
- if (this.isDisposed) {
- return;
- }
- let status = session.status;
- this.toggleClass(TOOLBAR_IDLE_CLASS, status === 'idle');
- this.toggleClass(TOOLBAR_BUSY_CLASS, status !== 'idle');
- let title = 'Kernel ' + status[0].toUpperCase() + status.slice(1);
- this.node.title = title;
- }
- }
- }
|