text: /*
   *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
   *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
   */
  
  // A single terminal frame.
  
  import { Map } from "immutable";
  import { Terminal } from "./connected-terminal";
  import { throttle } from "underscore";
  import { background_color } from "./themes";
  import { is_different } from "smc-util/misc2";
  import { React, Component, Rendered, ReactDOM } from "../../app-framework";
  
  interface Props {
    actions: any;
    id: string;
    path: string;
    project_id: string;
    font_size: number;
    editor_state: any;
    is_current: boolean;
    terminal: Map<string, any>;
    desc: Map<string, any>;
    resize: number;
  }
  
  export class TerminalFrame extends Component<Props, {}> {
    static displayName = "TerminalFrame";
  
    private terminal?: Terminal;
    private is_mounted: boolean = false;
  
    shouldComponentUpdate(next): boolean {
      return is_different(this.props, next, [
        "id",
        "project_id",
        "path",
        "font_size",
        "terminal",
        "desc",
        "resize",
      ]);
    }
  
    UNSAFE_componentWillReceiveProps(next: Props): void {
      if (this.props.id != next.id || this.terminal == null) {
        /* yes, this can change!! -- see https://github.com/sagemathinc/cocalc/issues/3819 */
        this.delete_terminal();
        this.init_terminal();
      }
      if (this.props.font_size !== next.font_size && this.terminal != null) {
        this.set_font_size(next.font_size);
      }
      if (!this.props.is_current && next.is_current && this.terminal != null) {
        this.terminal.focus();
      }
      if (this.props.resize != next.resize) {
        this.measure_size();
      }
    }
  
    componentDidMount(): void {
      this.is_mounted = true;
      this.set_font_size = throttle(this.set_font_size, 500);
      this.init_terminal();
      if (this.terminal == null) return;
      this.terminal.is_mounted = true;
      this.measure_size = this.measure_size.bind(this);
    }
  
    componentWillUnmount(): void {
      this.is_mounted = false;
      this.delete_terminal();
    }
  
    delete_terminal(): void {
      if (this.terminal == null) return;
      this.terminal.element?.remove();
      this.terminal.is_mounted = false;
      // Ignore size for this terminal.
      this.terminal.conn_write({ cmd: "size", rows: 0, cols: 0 });
      delete this.terminal;
    }
  
    init_terminal(): void {
      const node: any = ReactDOM.findDOMNode(this.refs.terminal);
      if (node == null) {
        throw Error("refs.terminal MUST be defined");
      }
      try {
        this.terminal = this.props.actions._get_terminal(this.props.id, node);
      } catch (err) {
        console.log("init_terminal warning -- ", err);
        return; // not yet ready -- might be ok; will try again.
      }
      if (this.terminal == null) return; // should be impossible.
      this.set_font_size(this.props.font_size);
      this.measure_size();
      if (this.props.is_current) {
        this.terminal.focus();
      }
      // Get rid of browser context menu, which makes no sense on a canvas.
      // See https://stackoverflow.com/questions/10864249/disabling-right-click-context-menu-on-a-html-canvas
      // NOTE: this would probably make sense in DOM mode instead of canvas mode;
      // if we switch, disable this...
      // Well, this context menu is still silly. Always disable it.
      $(node).bind("contextmenu", function () {
        return false;
      });
  
      // TODO: Obviously restoring the exact scroll position would be better...
      this.terminal.scroll_to_bottom();
    }
  
    async set_font_size(font_size: number): Promise<void> {
      if (this.terminal == null || !this.is_mounted) {
        return;
      }
      if (this.terminal.getOption("fontSize") !== font_size) {
        this.terminal.set_font_size(font_size);
        this.measure_size();
      }
    }
  
    private measure_size(): void {
      if (this.terminal == null || !this.is_mounted) {
        return;
      }
      this.terminal.measure_size();
    }
  
    render_command(): Rendered {
      const command = this.props.desc.get("command");
      if (!command) return;
      const args: string[] | undefined = this.props.desc.get("args"); // TODO: need to quote if args have spaces...
      return (
        <div
          style={{
            borderBottom: "1px solid grey",
            paddingLeft: "5px",
            background: "rgb(248, 248, 248)",
            height: "20px",
            overflow: "hidden",
          }}
        >
          {command} {args != null ? args.join(" ") : ""}
        </div>
      );
    }
  
    render(): Rendered {
      const color = background_color(this.props.terminal.get("color_scheme"));
      /* 4px padding is consistent with CodeMirror */
      return (
        <div className={"smc-vfill"}>
          {this.render_command()}
          <div
            className={"smc-vfill"}
            style={{ backgroundColor: color, padding: "0 0 0 4px" }}
            onClick={() => {
              /* otherwise, clicking right outside term defocuses,
               which is confusing */
              if (this.terminal != null) this.terminal.focus();
            }}
          >
            <div className={"smc-vfill cocalc-xtermjs"} ref={"terminal"} />
          </div>
        </div>
      );
    }
  }
  