All files / src/tools remote.tsx

98.07% Statements 51/52
84.61% Branches 11/13
93.75% Functions 15/16
100% Lines 51/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157                                      78x 78x 78x       56x 56x 56x       592x 592x   592x   592x 2368x 2368x     442x         592x     28x       592x                             592x 592x 488x   104x 104x 132x 132x 132x   4x             80x                 2960x                       40x 40x   78x 78x     78x 78x 78x       56x 56x       4176x       592x       1634x 1634x 1634x 534x 534x   1634x 1634x 112x 112x   1634x 542x       78x 542x              
import { VDomRenderer, VDomModel } from '@jupyterlab/apputils';
import { PathExt } from '@jupyterlab/coreutils';
import { LabIcon } from '@jupyterlab/ui-components';
import { JSONExt } from '@lumino/coreutils';
import type { Widget } from '@lumino/widgets';
import React from 'react';
 
import { ICONS } from '../icons';
import {
  CSS,
  IDeckManager,
  DIRECTION,
  DIRECTION_LABEL,
  TCanGoDirection,
  IPresenter,
} from '../tokens';
 
export class DeckRemote extends VDomRenderer<DeckRemote.Model> {
  constructor(options: DeckRemote.IOptions) {
    super(new DeckRemote.Model(options));
    this.addClass(CSS.remote);
    document.body.appendChild(this.node);
  }
 
  dispose() {
    this.model.dispose();
    super.dispose();
    document.body.removeChild(this.node);
  }
 
  protected render(): JSX.Element {
    const { manager, canGo } = this.model;
    const { __ } = manager;
 
    const directions: Record<string, JSX.Element> = {};
 
    for (const direction of Object.values(DIRECTION)) {
      const enabled = !!canGo[direction];
      directions[direction] = this.makeButton(
        enabled ? ICONS.goEnabled : ICONS.goDisabled,
        DIRECTION_LABEL[direction],
        enabled ? () => manager.go(direction) : () => null,
        `${CSS.direction}-${direction} ${enabled ? '' : CSS.disabled}`,
      );
    }
 
    const exit = this.makeButton(
      ICONS.deckStop,
      __('Exit Deck'),
      () => void this.model.manager.stop(),
      CSS.stop,
    );
 
    return (
      <div className={CSS.directions}>
        {this.makeStack()}
        {directions.up}
        <div>
          {directions.back}
          {exit}
          {directions.forward}
        </div>
        {directions.down}
      </div>
    );
  }
 
  makeStack(): JSX.Element {
    let { manager } = this.model;
    if (!manager.activeWidgetStack.length) {
      return <></>;
    }
    let stack: JSX.Element[] = [];
    for (const widget of manager.activeWidgetStack) {
      let icon = widget.title.icon as LabIcon;
      let label = PathExt.basename(widget.title.label);
      stack.push(
        <li key={widget.id}>
          <button onClick={() => this.model.manager.activateWidget(widget)}>
            <label>{label}</label>
            <icon.react width={24}></icon.react>
          </button>
        </li>,
      );
    }
    return <ul className={CSS.widgetStack}>{stack}</ul>;
  }
 
  makeButton(
    icon: LabIcon,
    title: string,
    onClick: () => void,
    className: string = '',
  ) {
    return (
      <button
        className={className}
        onClick={onClick}
        title={this.model.manager.__(title)}
      >
        <icon.react width={32} />
      </button>
    );
  }
}
 
export namespace DeckRemote {
  export class Model extends VDomModel {
    private _manager: IDeckManager;
    private _canGo: Partial<TCanGoDirection> = {};
    private _activePresenter: IPresenter<Widget> | null = null;
 
    constructor(options: IOptions) {
      super();
      this._manager = options.manager;
      this._manager.activeChanged.connect(this._onActiveChanged, this);
    }
 
    dispose() {
      this._manager.activeChanged.disconnect(this._onActiveChanged, this);
      super.dispose();
    }
 
    get manager(): IDeckManager {
      return this._manager;
    }
 
    get canGo(): Partial<TCanGoDirection> {
      return this._canGo;
    }
 
    private async _onActiveChanged() {
      const canGo = await this._manager.canGo();
      let emit = false;
      if (!JSONExt.deepEqual(canGo, this._canGo)) {
        this._canGo = canGo;
        emit = true;
      }
      let { activePresenter } = this._manager;
      if (activePresenter !== this._activePresenter) {
        this._activePresenter = activePresenter;
        emit = true;
      }
      if (emit) {
        this.emit();
      }
    }
 
    private emit = () => {
      this.stateChanged.emit(void 0);
    };
  }
  export interface IOptions {
    manager: IDeckManager;
  }
}