rohanchandra/react-terminal-component

How can I handle pending state?

Opened this issue · 1 comments

It seems that inputAccept doesn't not supported get command result in async way.
For example: I send a request then wait for response and render result. This is a async action, component state should be set to pending before response and rerender output result after response.But inputAccept response directly in a sync way.

Here's how I do:

File App.js

import React, { Component } from "react";
import { ReactTerminalStateless } from "react-terminal-component";
import { EmulatorState, CommandMapping, Emulator } from "javascript-terminal";
import all from "./commands/all";
import TerminalContext, {
  defaultTerminalContext
} from "./contexts/TerminalContext";

class App extends Component {
  constructor() {
    super();

    const getTerminalState = defaultTerminalContext.getTerminalState.bind(this);
    const setTerminalState = defaultTerminalContext.setTerminalState.bind(this);
    const setAcceptInput = defaultTerminalContext.setAcceptInput.bind(this);
    const terminalState = EmulatorState.createEmpty().setCommandMapping(
      CommandMapping.create(
        all(getTerminalState, setTerminalState, setAcceptInput)
      )
    );

    this.state = {
      ...defaultTerminalContext,
      getTerminalState,
      setTerminalState,
      setAcceptInput,
      terminalState
    };
  }

  triggerAction() {
    const emulator = new Emulator();

    const newTerminalState = emulator.execute(
      this.state.terminalState, "print i clicked the btn", []
    );

    this.setState((prevState) => {
      this.setState({
        ...prevState,
        terminalState: newTerminalState,
      });
    })
  }

  render() {
    return (
      <TerminalContext.Provider value={this.state}>
        <ReactTerminalStateless
          acceptInput={this.state.acceptInput}
          emulatorState={this.state.terminalState}
          inputStr={this.state.inputStr}
          onInputChange={inputStr => this.setState({ inputStr })}
          onStateChange={terminalState =>
            this.setState({ terminalState, inputStr: "" })
          }
          clickToFocus={true}
          theme={{
            background: "#141313",
            promptSymbolColor: "#6effe6",
            commandColor: "#fcfcfc",
            outputColor: "#fcfcfc",
            errorOutputColor: "#ff89bd",
            fontSize: "1.1rem",
            spacing: "1%",
            fontFamily: "monospace",
            width: "100%",
            height: "50vh"
          }}
        />
        <span onClick={this.triggerAction.bind(this)}>test action on click</span>
      </TerminalContext.Provider>
    );
  }
}

export default App;

File src\contexts\TerminalContext.tsx

import React from "react";
import App from "../App";

function isAppComponent(el: any): el is App {
  if (typeof el === "undefined" || typeof el.setState !== "function") {
    return false;
  }
  return true;
}

const defaultTerminalContext = {
  inputStr: "",
  terminalState: null,
  acceptInput: true,
  setAcceptInput: function(acceptInput: boolean): void {
    if (!isAppComponent(this)) {
      throw new Error(
        "You must bind this function to the App component to use it."
      );
    }
    this.setState((prevState: any) => ({ ...prevState, acceptInput }));
  },
  setTerminalState: function(newState: any): void {
    if (!isAppComponent(this)) {
      throw new Error(
        "You must bind this function to the App component to use it."
      );
    }
    this.setState((prevState: any) => ({ ...prevState, terminalState: newState }));
  },
  getTerminalState: function(): any {
    if (!isAppComponent(this)) {
      throw new Error(
        "You must bind this function to the App component to use it."
      );
    }
    return this?.state?.terminalState;
  }
};

const TerminalContext = React.createContext(defaultTerminalContext);
TerminalContext.displayName = "TerminalContext";

export { defaultTerminalContext };
export default TerminalContext;

File src\commands\print.js

import { OutputFactory, Outputs } from 'javascript-terminal';

const print = (getTerminalStateFn, setTerminalStateFn, setAcceptInput) => {
    return {
        'print': {
            'function': (state, opts) => {
                setAcceptInput(false);
                const input = opts.join(' ');
                
                setTimeout(()=>{
                    const oldState = getTerminalStateFn();
                    const oldOutputs = oldState.getOutputs();

                    const newOutputs = Outputs.addRecord(
                        oldOutputs, OutputFactory.makeTextOutput(input)
                    );
                    const newState = oldState.setOutputs(newOutputs);
                    setAcceptInput(true);
                    setTerminalStateFn(newState);
                }, 1000);
    
                return {
                    output: OutputFactory.makeTextOutput("waiting ..."),
                };
            },
            'optDef': {}
        }
    }
};

export default print;

File src\commands\all.js

import print from './print';

const createMapping = (getTerminalStateFn, setTerminalStateFn, setAcceptInput) => {
    return {
        ...print(getTerminalStateFn, setTerminalStateFn, setAcceptInput),
    }
}

export default createMapping;