<commit_before>const debugModule = require("debug");
const debug = debugModule("lib:debug:interpreter");

const path = require("path");
const util = require("util");
const ora = require("ora");

const DebugUtils = require("truffle-debug-utils");
const selectors = require("truffle-debugger").selectors;
const { solidity, trace, evm, controller } = selectors;

const analytics = require("../services/analytics");
const ReplManager = require("../repl");

const { DebugPrinter } = require("./printer");

function watchExpressionAnalytics(raw) {
  if (raw.includes("!<")) {
    //don't send analytics for watch expressions involving selectors
    return;
  }
  let expression = raw.trim();
  //legal Solidity identifiers (= legal JS identifiers)
  let identifierRegex = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
  let isVariable = expression.match(identifierRegex) !== null;
  analytics.send({
    command: "debug: watch expression",
    args: { isVariable }
  });
}

class DebugInterpreter {
  constructor(config, session, txHash) {
    this.session = session;
    this.network = config.network;
    this.printer = new DebugPrinter(config, session);
    this.txHash = txHash;
    this.lastCommand = "n";

    this.repl = config.repl || new ReplManager(config);
    this.enabledExpressions = new Set();
  }

  async setOrClearBreakpoint(args, setOrClear) {
    //setOrClear: true for set, false for clear
    const currentLocation = this.session.view(controller.current.location);
    const breakpoints = this.session.view(controller.breakpoints);

    const currentNode = currentLocation.node ? currentLocation.node.id : null;
    const currentLine = currentLocation.sourceRange
      ? currentLocation.sourceRange.lines.start.line
      : null;
    const currentSourceId = currentLocation.source
      ? currentLocation.source.id
      : null;

    let breakpoint = {};

    if (args.length === 0) {
      //no arguments, want currrent node
      debug("node case");
      if (currentNode === null) {
        this.printer.print("Cannot determine current location.");
        return;
      }
      breakpoint.node = currentNode;
      breakpoint.line = currentLine;
      breakpoint.sourceId = currentSourceId;
    }

    //the special case of "B all"
    else if (args[0] === "all") {
      if (setOrClear) {
        // only "B all" is legal, not "b all"
        this.printer.print("Cannot add breakpoint everywhere.\n");
        return;
      }
      await this.session.removeAllBreakpoints();
      this.printer.print("Removed all breakpoints.\n");
      return;
    }

    //if the argument starts with a "+" or "-", we have a relative
    //line number
    else if (args[0][0] === "+" || args[0][0] === "-") {
      debug("relative case");
      if (currentLine === null) {
        this.printer.print("Cannot determine current location.");
        return;
      }
      let delta = parseInt(args[0], 10); //want an integer
      debug("delta %d", delta);

      if (isNaN(delta)) {
        this.printer.print("Offset must be an integer.\n");
        return;
      }

      breakpoint.sourceId = currentSourceId;
      breakpoint.line = currentLine + delta;
    }

    //if it contains a colon, it's in the form source:line
    else if (args[0].includes(":")) {
      debug("source case");
      let sourceArgs = args[0].split(":");
      let sourceArg = sourceArgs[0];
      let lineArg = sourceArgs[1];
      debug("sourceArgs %O", sourceArgs);

      //first let's get the line number as usual
      let line = parseInt(lineArg, 10); //want an integer
      if (isNaN(line)) {
        this.printer.print("Line number must be an integer.\n");
        return;
      }

      //search sources for given string
      let sources = this.session.view(solidity.info.sources);

      //we will indeed need the sources here, not just IDs
      let matchingSources = Object.values(sources).filter(source =>
        source.sourcePath.includes(sourceArg)
      );

      if (matchingSources.length === 0) {
        this.printer.print(`No source file found matching ${sourceArg}.\n`);
        return;
      } else if (matchingSources.length > 1) {
        this.printer.print(
          `Multiple source files found matching ${sourceArg}.  Which did you mean?`
        );
        matchingSources.forEach(source =>
          this.printer.print(source.sourcePath)
        );
        this.printer.print("");
        return;
      }

      //otherwise, we found it!
      breakpoint.sourceId = matchingSources[0].id;
      breakpoint.line = line - 1; //adjust for zero-indexing!
    }

    //otherwise, it's a simple line number
    else {
      debug("absolute case");
      if (currentSourceId === null) {
        this.printer.print("Cannot determine current file.");
        return;
      }
      let line = parseInt(args[0], 10); //want an integer
      debug("line %d", line);

      if (isNaN(line)) {
        this.printer.print("Line number must be an integer.\n");
        return;
      }

      breakpoint.sourceId = currentSourceId;
      breakpoint.line = line - 1; //adjust for zero-indexing!
    }

    //OK, we've constructed the breakpoint!  But if we're adding, we'll
    //want to adjust to make sure we don't set it on an empty line or
    //anything like that
    if (setOrClear) {
      let resolver = this.session.view(controller.breakpoints.resolver);
      breakpoint = resolver(breakpoint);
      //of course, this might result in finding that there's nowhere to
      //add it after that point
      if (breakpoint === null) {
        this.printer.print(
          "Nowhere to add breakpoint at or beyond that location.\n"
        );
        return;
      }
    }

    //having constructed and adjusted breakpoint, here's now a
    //user-readable message describing its location
    let sourceNames = Object.assign(
      {},
      ...Object.values(this.session.view(solidity.info.sources)).map(
        ({ id, sourcePath }) => ({
          [id]: path.basename(sourcePath)
        })
      )
    );
    let locationMessage = DebugUtils.formatBreakpointLocation(
      breakpoint,
      true,
      currentSourceId,
      sourceNames
    );

    //one last check -- does this breakpoint already exist?
    let alreadyExists =
      breakpoints.filter(
        existingBreakpoint =>
          existingBreakpoint.sourceId === breakpoint.sourceId &&
          existingBreakpoint.line === breakpoint.line &&
          existingBreakpoint.node === breakpoint.node //may be undefined
      ).length > 0;

    //NOTE: in the "set breakpoint" case, the above check is somewhat
    //redundant, as we're going to check again when we actually make the
    //call to add or remove the breakpoint!  But we need to check here so
    //that we can display the appropriate message.  Hopefully we can find
    //some way to avoid this redundant check in the future.

    //if it already exists and is being set, or doesn't and is being
    //cleared, report back that we can't do that
    if (setOrClear === alreadyExists) {
      if (setOrClear) {
        this.printer.print(
          `Breakpoint at ${locationMessage} already exists.\n`
        );
        return;
      } else {
        this.printer.print(`No breakpoint at ${locationMessage} to remove.\n`);
        return;
      }
    }

    //finally, if we've reached this point, do it!
    //also report back to the user on what happened
    if (setOrClear) {
      await this.session.addBreakpoint(breakpoint);
      this.printer.print(`Breakpoint added at ${locationMessage}.\n`);
    } else {
      await this.session.removeBreakpoint(breakpoint);
      this.printer.print(`Breakpoint removed at ${locationMessage}.\n`);
    }
    return;
  }

  start(terminate) {
    let prompt;

    if (this.session.view(selectors.session.status.loaded)) {
      this.printer.printAddressesAffected();
      this.printer.printHelp();
      debug("Help printed");
      this.printer.printFile();
      debug("File printed");
      this.printer.printState();
      debug("State printed");
      prompt = DebugUtils.formatPrompt(this.network, this.txHash);
    } else {
      if (this.session.view(selectors.session.status.isError)) {
        this.printer.print(this.session.view(selectors.session.status.error));
      }
      this.printer.printHelp();
      prompt = DebugUtils.formatPrompt(this.network);
    }

    this.repl.start({
      prompt,
      interpreter: util.callbackify(this.interpreter.bind(this)),
      ignoreUndefined: true,
      done: terminate
    });
  }

  setPrompt(prompt) {
    this.repl.activate.bind(this.repl)({
      prompt,
      context: {},
      //this argument only *adds* things, so it's safe to set it to {}
      ignoreUndefined: true
      //set to true because it's set to true below :P
    });
  }

  async interpreter(cmd) {
    cmd = cmd.trim();
    let cmdArgs, splitArgs;
    debug("cmd %s", cmd);

    if (cmd === ".exit") {
      cmd = "q";
    }

    //split arguments for commands that want that; split on runs of spaces
    splitArgs = cmd
      .trim()
      .split(/ +/)
      .slice(1);
    debug("splitArgs %O", splitArgs);

    //warning: this bit *alters* cmd!
    if (cmd.length > 0) {
      cmdArgs = cmd.slice(1).trim();
      cmd = cmd[0];
    }

    if (cmd === "") {
      cmd = this.lastCommand;
      cmdArgs = "";
      splitArgs = [];
    }

    //quit if that's what we were given
    if (cmd === "q") {
      return await util.promisify(this.repl.stop.bind(this.repl))();
    }

    let alreadyFinished = this.session.view(trace.finishedOrUnloaded);
    let loadFailed = false;

    // If not finished, perform commands that require state changes
    // (other than quitting or resetting)
    if (!alreadyFinished) {
      let stepSpinner = ora("Stepping...").start();
      switch (cmd) {
        case "o":
          await this.session.stepOver();
          break;
        case "i":
          await this.session.stepInto();
          break;
        case "u":
          await this.session.stepOut();
          break;
        case "n":
          await this.session.stepNext();
          break;
        case ";":
          //two cases -- parameterized and unparameterized
          if (cmdArgs !== "") {
            let count = parseInt(cmdArgs, 10);
            debug("cmdArgs=%s", cmdArgs);
            if (isNaN(count)) {
              this.printer.print("Number of steps must be an integer.");
              break;
            }
            await this.session.advance(count);
          } else {
            await this.session.advance();
          }
          break;
        case "c":
          await this.session.continueUntilBreakpoint();
          break;
      }
      stepSpinner.stop();
    } //otherwise, inform the user we can't do that
    else {
      switch (cmd) {
        case "o":
        case "i":
        case "u":
        case "n":
        case "c":
          //are we "finished" because we've reached the end, or because
          //nothing is loaded?
          if (this.session.view(selectors.session.status.loaded)) {
            this.printer.print("Transaction has halted; cannot advance.");
            this.printer.print("");
          } else {
            this.printer.print("No transaction loaded.");
            this.printer.print("");
          }
      }
    }
    if (cmd === "r") {
      //reset if given the reset command
      //(but not if nothing is loaded)
      if (this.session.view(selectors.session.status.loaded)) {
        await this.session.reset();
      } else {
        this.printer.print("No transaction loaded.");
        this.printer.print("");
      }
    }
    if (cmd === "t") {
      if (!this.session.view(selectors.session.status.loaded)) {
        let txSpinner = ora(DebugUtils.formatTransactionStartMessage()).start();
        await this.session.load(cmdArgs);
        //if load succeeded
        if (this.session.view(selectors.session.status.success)) {
          txSpinner.succeed();
          //if successful, change prompt
          this.setPrompt(DebugUtils.formatPrompt(this.network, cmdArgs));
        } else {
          txSpinner.fail();
          loadFailed = true;
        }
      } else {
        loadFailed = true;
        this.printer.print(
          "Please unload the current transaction before loading a new one."
        );
      }
    }
    if (cmd === "T") {
      if (this.session.view(selectors.session.status.loaded)) {
        await this.session.unload();
        this.printer.print("Transaction unloaded.");
        this.setPrompt(DebugUtils.formatPrompt(this.network));
      } else {
        this.printer.print("No transaction to unload.");
        this.printer.print("");
      }
    }

    // Check if execution has (just now) stopped.
    if (this.session.view(trace.finished) && !alreadyFinished) {
      this.printer.print("");
      //check if transaction failed
      if (!this.session.view(evm.transaction.status)) {
        this.printer.print("Transaction halted with a RUNTIME ERROR.");
        this.printer.print("");
        this.printer.print(
          "This is likely due to an intentional halting expression, like assert(), require() or revert(). It can also be due to out-of-gas exceptions. Please inspect your transaction parameters and contract code to determine the meaning of this error."
        );
      } else {
        //case if transaction succeeded
        this.printer.print("Transaction completed successfully.");
      }
    }

    // Perform post printing
    // (we want to see if execution stopped before printing state).
    switch (cmd) {
      case "+":
        if (cmdArgs[0] === ":") {
          watchExpressionAnalytics(cmdArgs.substring(1));
        }
        this.enabledExpressions.add(cmdArgs);
        await this.printer.printWatchExpressionResult(cmdArgs);
        break;
      case "-":
        this.enabledExpressions.delete(cmdArgs);
        break;
      case "!":
        this.printer.printSelector(cmdArgs);
        break;
      case "?":
        this.printer.printWatchExpressions(this.enabledExpressions);
        this.printer.printBreakpoints();
        break;
      case "v":
        await this.printer.printVariables();
        break;
      case ":":
        watchExpressionAnalytics(cmdArgs);
        this.printer.evalAndPrintExpression(cmdArgs);
        break;
      case "b":
        await this.setOrClearBreakpoint(splitArgs, true);
        break;
      case "B":
        await this.setOrClearBreakpoint(splitArgs, false);
        break;
      case ";":
      case "p":
        if (this.session.view(selectors.session.status.loaded)) {
          this.printer.printFile();
          this.printer.printInstruction();
          this.printer.printState();
        }
        await this.printer.printWatchExpressionsResults(
          this.enabledExpressions
        );
        break;
      case "o":
      case "i":
      case "u":
      case "n":
      case "c":
        if (!this.session.view(trace.finishedOrUnloaded)) {
          if (!this.session.view(solidity.current.source).source) {
            this.printer.printInstruction();
          }
          this.printer.printFile();
          this.printer.printState();
        }
        await this.printer.printWatchExpressionsResults(
          this.enabledExpressions
        );
        break;
      case "r":
        if (this.session.view(selectors.session.status.loaded)) {
          this.printer.printAddressesAffected();
          this.printer.printFile();
          this.printer.printState();
        }
        break;
      case "t":
        if (!loadFailed) {
          this.printer.printAddressesAffected();
          this.printer.printFile();
          this.printer.printState();
        } else if (this.session.view(selectors.session.status.isError)) {
          let loadError = this.session.view(selectors.session.status.error);
          this.printer.print(loadError);
        }
        break;
      case "T":
        //nothing to print
        break;
      default:
        this.printer.printHelp();
    }

    if (
      cmd !== "i" &&
      cmd !== "u" &&
      cmd !== "b" &&
      cmd !== "B" &&
      cmd !== "v" &&
      cmd !== "h" &&
      cmd !== "p" &&
      cmd !== "?" &&
      cmd !== "!" &&
      cmd !== ":" &&
      cmd !== "+" &&
      cmd !== "r" &&
      cmd !== "-" &&
      cmd !== "t" &&
      cmd !== "T"
    ) {
      this.lastCommand = cmd;
    }
  }
}

module.exports = {
  DebugInterpreter
};
<commit_msg>Enable CLIDebugger promise interface<commit_after>const debugModule = require("debug");
const debug = debugModule("lib:debug:interpreter");

const path = require("path");
const util = require("util");
const ora = require("ora");

const DebugUtils = require("truffle-debug-utils");
const selectors = require("truffle-debugger").selectors;
const { solidity, trace, evm, controller } = selectors;

const analytics = require("../services/analytics");
const ReplManager = require("../repl");

const { DebugPrinter } = require("./printer");

function watchExpressionAnalytics(raw) {
  if (raw.includes("!<")) {
    //don't send analytics for watch expressions involving selectors
    return;
  }
  let expression = raw.trim();
  //legal Solidity identifiers (= legal JS identifiers)
  let identifierRegex = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
  let isVariable = expression.match(identifierRegex) !== null;
  analytics.send({
    command: "debug: watch expression",
    args: { isVariable }
  });
}

class DebugInterpreter {
  constructor(config, session, txHash) {
    this.session = session;
    this.network = config.network;
    this.printer = new DebugPrinter(config, session);
    this.txHash = txHash;
    this.lastCommand = "n";

    this.repl = config.repl || new ReplManager(config);
    this.enabledExpressions = new Set();
  }

  async setOrClearBreakpoint(args, setOrClear) {
    //setOrClear: true for set, false for clear
    const currentLocation = this.session.view(controller.current.location);
    const breakpoints = this.session.view(controller.breakpoints);

    const currentNode = currentLocation.node ? currentLocation.node.id : null;
    const currentLine = currentLocation.sourceRange
      ? currentLocation.sourceRange.lines.start.line
      : null;
    const currentSourceId = currentLocation.source
      ? currentLocation.source.id
      : null;

    let breakpoint = {};

    if (args.length === 0) {
      //no arguments, want currrent node
      debug("node case");
      if (currentNode === null) {
        this.printer.print("Cannot determine current location.");
        return;
      }
      breakpoint.node = currentNode;
      breakpoint.line = currentLine;
      breakpoint.sourceId = currentSourceId;
    }

    //the special case of "B all"
    else if (args[0] === "all") {
      if (setOrClear) {
        // only "B all" is legal, not "b all"
        this.printer.print("Cannot add breakpoint everywhere.\n");
        return;
      }
      await this.session.removeAllBreakpoints();
      this.printer.print("Removed all breakpoints.\n");
      return;
    }

    //if the argument starts with a "+" or "-", we have a relative
    //line number
    else if (args[0][0] === "+" || args[0][0] === "-") {
      debug("relative case");
      if (currentLine === null) {
        this.printer.print("Cannot determine current location.");
        return;
      }
      let delta = parseInt(args[0], 10); //want an integer
      debug("delta %d", delta);

      if (isNaN(delta)) {
        this.printer.print("Offset must be an integer.\n");
        return;
      }

      breakpoint.sourceId = currentSourceId;
      breakpoint.line = currentLine + delta;
    }

    //if it contains a colon, it's in the form source:line
    else if (args[0].includes(":")) {
      debug("source case");
      let sourceArgs = args[0].split(":");
      let sourceArg = sourceArgs[0];
      let lineArg = sourceArgs[1];
      debug("sourceArgs %O", sourceArgs);

      //first let's get the line number as usual
      let line = parseInt(lineArg, 10); //want an integer
      if (isNaN(line)) {
        this.printer.print("Line number must be an integer.\n");
        return;
      }

      //search sources for given string
      let sources = this.session.view(solidity.info.sources);

      //we will indeed need the sources here, not just IDs
      let matchingSources = Object.values(sources).filter(source =>
        source.sourcePath.includes(sourceArg)
      );

      if (matchingSources.length === 0) {
        this.printer.print(`No source file found matching ${sourceArg}.\n`);
        return;
      } else if (matchingSources.length > 1) {
        this.printer.print(
          `Multiple source files found matching ${sourceArg}.  Which did you mean?`
        );
        matchingSources.forEach(source =>
          this.printer.print(source.sourcePath)
        );
        this.printer.print("");
        return;
      }

      //otherwise, we found it!
      breakpoint.sourceId = matchingSources[0].id;
      breakpoint.line = line - 1; //adjust for zero-indexing!
    }

    //otherwise, it's a simple line number
    else {
      debug("absolute case");
      if (currentSourceId === null) {
        this.printer.print("Cannot determine current file.");
        return;
      }
      let line = parseInt(args[0], 10); //want an integer
      debug("line %d", line);

      if (isNaN(line)) {
        this.printer.print("Line number must be an integer.\n");
        return;
      }

      breakpoint.sourceId = currentSourceId;
      breakpoint.line = line - 1; //adjust for zero-indexing!
    }

    //OK, we've constructed the breakpoint!  But if we're adding, we'll
    //want to adjust to make sure we don't set it on an empty line or
    //anything like that
    if (setOrClear) {
      let resolver = this.session.view(controller.breakpoints.resolver);
      breakpoint = resolver(breakpoint);
      //of course, this might result in finding that there's nowhere to
      //add it after that point
      if (breakpoint === null) {
        this.printer.print(
          "Nowhere to add breakpoint at or beyond that location.\n"
        );
        return;
      }
    }

    //having constructed and adjusted breakpoint, here's now a
    //user-readable message describing its location
    let sourceNames = Object.assign(
      {},
      ...Object.values(this.session.view(solidity.info.sources)).map(
        ({ id, sourcePath }) => ({
          [id]: path.basename(sourcePath)
        })
      )
    );
    let locationMessage = DebugUtils.formatBreakpointLocation(
      breakpoint,
      true,
      currentSourceId,
      sourceNames
    );

    //one last check -- does this breakpoint already exist?
    let alreadyExists =
      breakpoints.filter(
        existingBreakpoint =>
          existingBreakpoint.sourceId === breakpoint.sourceId &&
          existingBreakpoint.line === breakpoint.line &&
          existingBreakpoint.node === breakpoint.node //may be undefined
      ).length > 0;

    //NOTE: in the "set breakpoint" case, the above check is somewhat
    //redundant, as we're going to check again when we actually make the
    //call to add or remove the breakpoint!  But we need to check here so
    //that we can display the appropriate message.  Hopefully we can find
    //some way to avoid this redundant check in the future.

    //if it already exists and is being set, or doesn't and is being
    //cleared, report back that we can't do that
    if (setOrClear === alreadyExists) {
      if (setOrClear) {
        this.printer.print(
          `Breakpoint at ${locationMessage} already exists.\n`
        );
        return;
      } else {
        this.printer.print(`No breakpoint at ${locationMessage} to remove.\n`);
        return;
      }
    }

    //finally, if we've reached this point, do it!
    //also report back to the user on what happened
    if (setOrClear) {
      await this.session.addBreakpoint(breakpoint);
      this.printer.print(`Breakpoint added at ${locationMessage}.\n`);
    } else {
      await this.session.removeBreakpoint(breakpoint);
      this.printer.print(`Breakpoint removed at ${locationMessage}.\n`);
    }
    return;
  }

  start(terminate) {
    if (terminate === undefined) {
      return util.promisify(this.start.bind(this))();
    }

    let prompt;

    if (this.session.view(selectors.session.status.loaded)) {
      this.printer.printAddressesAffected();
      this.printer.printHelp();
      debug("Help printed");
      this.printer.printFile();
      debug("File printed");
      this.printer.printState();
      debug("State printed");
      prompt = DebugUtils.formatPrompt(this.network, this.txHash);
    } else {
      if (this.session.view(selectors.session.status.isError)) {
        this.printer.print(this.session.view(selectors.session.status.error));
      }
      this.printer.printHelp();
      prompt = DebugUtils.formatPrompt(this.network);
    }

    this.repl.start({
      prompt,
      interpreter: util.callbackify(this.interpreter.bind(this)),
      ignoreUndefined: true,
      done: terminate
    });
  }

  setPrompt(prompt) {
    this.repl.activate.bind(this.repl)({
      prompt,
      context: {},
      //this argument only *adds* things, so it's safe to set it to {}
      ignoreUndefined: true
      //set to true because it's set to true below :P
    });
  }

  async interpreter(cmd) {
    cmd = cmd.trim();
    let cmdArgs, splitArgs;
    debug("cmd %s", cmd);

    if (cmd === ".exit") {
      cmd = "q";
    }

    //split arguments for commands that want that; split on runs of spaces
    splitArgs = cmd
      .trim()
      .split(/ +/)
      .slice(1);
    debug("splitArgs %O", splitArgs);

    //warning: this bit *alters* cmd!
    if (cmd.length > 0) {
      cmdArgs = cmd.slice(1).trim();
      cmd = cmd[0];
    }

    if (cmd === "") {
      cmd = this.lastCommand;
      cmdArgs = "";
      splitArgs = [];
    }

    //quit if that's what we were given
    if (cmd === "q") {
      return await util.promisify(this.repl.stop.bind(this.repl))();
    }

    let alreadyFinished = this.session.view(trace.finishedOrUnloaded);
    let loadFailed = false;

    // If not finished, perform commands that require state changes
    // (other than quitting or resetting)
    if (!alreadyFinished) {
      let stepSpinner = ora("Stepping...").start();
      switch (cmd) {
        case "o":
          await this.session.stepOver();
          break;
        case "i":
          await this.session.stepInto();
          break;
        case "u":
          await this.session.stepOut();
          break;
        case "n":
          await this.session.stepNext();
          break;
        case ";":
          //two cases -- parameterized and unparameterized
          if (cmdArgs !== "") {
            let count = parseInt(cmdArgs, 10);
            debug("cmdArgs=%s", cmdArgs);
            if (isNaN(count)) {
              this.printer.print("Number of steps must be an integer.");
              break;
            }
            await this.session.advance(count);
          } else {
            await this.session.advance();
          }
          break;
        case "c":
          await this.session.continueUntilBreakpoint();
          break;
      }
      stepSpinner.stop();
    } //otherwise, inform the user we can't do that
    else {
      switch (cmd) {
        case "o":
        case "i":
        case "u":
        case "n":
        case "c":
          //are we "finished" because we've reached the end, or because
          //nothing is loaded?
          if (this.session.view(selectors.session.status.loaded)) {
            this.printer.print("Transaction has halted; cannot advance.");
            this.printer.print("");
          } else {
            this.printer.print("No transaction loaded.");
            this.printer.print("");
          }
      }
    }
    if (cmd === "r") {
      //reset if given the reset command
      //(but not if nothing is loaded)
      if (this.session.view(selectors.session.status.loaded)) {
        await this.session.reset();
      } else {
        this.printer.print("No transaction loaded.");
        this.printer.print("");
      }
    }
    if (cmd === "t") {
      if (!this.session.view(selectors.session.status.loaded)) {
        let txSpinner = ora(DebugUtils.formatTransactionStartMessage()).start();
        await this.session.load(cmdArgs);
        //if load succeeded
        if (this.session.view(selectors.session.status.success)) {
          txSpinner.succeed();
          //if successful, change prompt
          this.setPrompt(DebugUtils.formatPrompt(this.network, cmdArgs));
        } else {
          txSpinner.fail();
          loadFailed = true;
        }
      } else {
        loadFailed = true;
        this.printer.print(
          "Please unload the current transaction before loading a new one."
        );
      }
    }
    if (cmd === "T") {
      if (this.session.view(selectors.session.status.loaded)) {
        await this.session.unload();
        this.printer.print("Transaction unloaded.");
        this.setPrompt(DebugUtils.formatPrompt(this.network));
      } else {
        this.printer.print("No transaction to unload.");
        this.printer.print("");
      }
    }

    // Check if execution has (just now) stopped.
    if (this.session.view(trace.finished) && !alreadyFinished) {
      this.printer.print("");
      //check if transaction failed
      if (!this.session.view(evm.transaction.status)) {
        this.printer.print("Transaction halted with a RUNTIME ERROR.");
        this.printer.print("");
        this.printer.print(
          "This is likely due to an intentional halting expression, like assert(), require() or revert(). It can also be due to out-of-gas exceptions. Please inspect your transaction parameters and contract code to determine the meaning of this error."
        );
      } else {
        //case if transaction succeeded
        this.printer.print("Transaction completed successfully.");
      }
    }

    // Perform post printing
    // (we want to see if execution stopped before printing state).
    switch (cmd) {
      case "+":
        if (cmdArgs[0] === ":") {
          watchExpressionAnalytics(cmdArgs.substring(1));
        }
        this.enabledExpressions.add(cmdArgs);
        await this.printer.printWatchExpressionResult(cmdArgs);
        break;
      case "-":
        this.enabledExpressions.delete(cmdArgs);
        break;
      case "!":
        this.printer.printSelector(cmdArgs);
        break;
      case "?":
        this.printer.printWatchExpressions(this.enabledExpressions);
        this.printer.printBreakpoints();
        break;
      case "v":
        await this.printer.printVariables();
        break;
      case ":":
        watchExpressionAnalytics(cmdArgs);
        this.printer.evalAndPrintExpression(cmdArgs);
        break;
      case "b":
        await this.setOrClearBreakpoint(splitArgs, true);
        break;
      case "B":
        await this.setOrClearBreakpoint(splitArgs, false);
        break;
      case ";":
      case "p":
        if (this.session.view(selectors.session.status.loaded)) {
          this.printer.printFile();
          this.printer.printInstruction();
          this.printer.printState();
        }
        await this.printer.printWatchExpressionsResults(
          this.enabledExpressions
        );
        break;
      case "o":
      case "i":
      case "u":
      case "n":
      case "c":
        if (!this.session.view(trace.finishedOrUnloaded)) {
          if (!this.session.view(solidity.current.source).source) {
            this.printer.printInstruction();
          }
          this.printer.printFile();
          this.printer.printState();
        }
        await this.printer.printWatchExpressionsResults(
          this.enabledExpressions
        );
        break;
      case "r":
        if (this.session.view(selectors.session.status.loaded)) {
          this.printer.printAddressesAffected();
          this.printer.printFile();
          this.printer.printState();
        }
        break;
      case "t":
        if (!loadFailed) {
          this.printer.printAddressesAffected();
          this.printer.printFile();
          this.printer.printState();
        } else if (this.session.view(selectors.session.status.isError)) {
          let loadError = this.session.view(selectors.session.status.error);
          this.printer.print(loadError);
        }
        break;
      case "T":
        //nothing to print
        break;
      default:
        this.printer.printHelp();
    }

    if (
      cmd !== "i" &&
      cmd !== "u" &&
      cmd !== "b" &&
      cmd !== "B" &&
      cmd !== "v" &&
      cmd !== "h" &&
      cmd !== "p" &&
      cmd !== "?" &&
      cmd !== "!" &&
      cmd !== ":" &&
      cmd !== "+" &&
      cmd !== "r" &&
      cmd !== "-" &&
      cmd !== "t" &&
      cmd !== "T"
    ) {
      this.lastCommand = cmd;
    }
  }
}

module.exports = {
  DebugInterpreter
};
<|endoftext|>


<commit_before>(function() {
  // Technique from Juriy Zaytsev
  // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
  function isEventSupported(eventName) {
    var el = document.createElement('div');
    eventName = 'on' + eventName;
    var isSupported = (eventName in el);
    if (!isSupported) {
      el.setAttribute(eventName, 'return;');
      isSupported = typeof el[eventName] == 'function';
    }
    el = null;
    return isSupported;
  }

  function isForm(element) {
    return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
  }

  function isInput(element) {
    if (Object.isElement(element)) {
      var name = element.nodeName.toUpperCase()
      return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
    }
    else return false
  }

  var submitBubbles = isEventSupported('submit'),
      changeBubbles = isEventSupported('change')

  if (!submitBubbles || !changeBubbles) {
    // augment the Event.Handler class to observe custom events when needed
    Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
      function(init, element, eventName, selector, callback) {
        init(element, eventName, selector, callback)
        // is the handler being attached to an element that doesn't support this event?
        if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
             (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
          // "submit" => "emulated:submit"
          this.eventName = 'emulated:' + this.eventName
        }
      }
    )
  }

  if (!submitBubbles) {
    // discover forms on the page by observing focus events which always bubble
    document.on('focusin', 'form', function(focusEvent, form) {
      // special handler for the real "submit" event (one-time operation)
      if (!form.retrieve('emulated:submit')) {
        form.on('submit', function(submitEvent) {
          var emulated = form.fire('emulated:submit', submitEvent, true)
          // if custom event received preventDefault, cancel the real one too
          if (emulated.returnValue === false) submitEvent.preventDefault()
        })
        form.store('emulated:submit', true)
      }
    })
  }

  if (!changeBubbles) {
    // discover form inputs on the page
    document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
      // special handler for real "change" events
      if (!input.retrieve('emulated:change')) {
        input.on('change', function(changeEvent) {
          input.fire('emulated:change', changeEvent, true)
        })
        input.store('emulated:change', true)
      }
    })
  }

  function handleRemote(element) {
    var method, url, params;

    var event = element.fire("ajax:before");
    if (event.stopped) return false;

    if (element.tagName.toLowerCase() === 'form') {
      method = element.readAttribute('method') || 'post';
      url    = element.readAttribute('action');
      params = element.serialize();
    } else {
      method = element.readAttribute('data-method') || 'get';
      url    = element.readAttribute('href');
      params = {};
    }

    new Ajax.Request(url, {
      method: method,
      parameters: params,
      evalScripts: true,

      onComplete:    function(request) { element.fire("ajax:complete", request); },
      onSuccess:     function(request) { element.fire("ajax:success",  request); },
      onFailure:     function(request) { element.fire("ajax:failure",  request); }
    });

    element.fire("ajax:after");
  }

  function handleMethod(element) {
    var method = element.readAttribute('data-method'),
        url = element.readAttribute('href'),
        csrf_param = $$('meta[name=csrf-param]')[0],
        csrf_token = $$('meta[name=csrf-token]')[0];

    var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
    element.parentNode.insert(form);

    if (method !== 'post') {
      var field = new Element('input', { type: 'hidden', name: '_method', value: method });
      form.insert(field);
    }

    if (csrf_param) {
      var param = csrf_param.readAttribute('content'),
          token = csrf_token.readAttribute('content'),
          field = new Element('input', { type: 'hidden', name: param, value: token });
      form.insert(field);
    }

    form.submit();
  }

  document.on("click", "*[data-confirm]", function(event, element) {
    var message = element.readAttribute('data-confirm');
    if (!confirm(message)) event.stop();
  });

  document.on("click", "a[data-remote]", function(event, element) {
    if (event.stopped) return;
    handleRemote(element);
    event.stop();
  });

  document.on("click", "a[data-method]", function(event, element) {
    if (event.stopped) return;
    handleMethod(element);
    event.stop();
  });

  document.on("submit", function(event) {
    var element = event.findElement(),
        message = element.readAttribute('data-confirm');
    if (message && !confirm(message)) {
      event.stop();
      return false;
    }

    var inputs = element.select("input[type=submit][data-disable-with]");
    inputs.each(function(input) {
      input.disabled = true;
      input.writeAttribute('data-original-value', input.value);
      input.value = input.readAttribute('data-disable-with');
    });

    var element = event.findElement("form[data-remote]");
    if (element) {
      handleRemote(element);
      event.stop();
    }
  });

  document.on("ajax:after", "form", function(event, element) {
    var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
    inputs.each(function(input) {
      input.value = input.readAttribute('data-original-value');
      input.removeAttribute('data-original-value');
      input.disabled = false;
    });
  });
})();
<commit_msg>change rails.js from prototype to jquery<commit_after>/*
 * jquery-ujs
 *
 * http://github.com/rails/jquery-ujs/blob/master/src/rails.js
 *
 * This rails.js file supports jQuery 1.4.3 and 1.4.4 .
 *
 */

jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls. Provides following callbacks:
         *
         * - ajax:beforeSend  - is executed before firing ajax call
         * - ajax:success  - is executed when status is success
         * - ajax:complete - is executed when the request finishes, whether in failure or success.
         * - ajax:error    - is execute in case of error
         */
        callRemote: function () {
            var el      = this,
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href'),
                dataType  = el.attr('data-type')  || ($.ajaxSettings && $.ajaxSettings.dataType);

            if (url === undefined) {
                throw "No URL specified for remote call (action or href must be present).";
            } else {
                    var $this = $(this), data = el.is('form') ? el.serializeArray() : [];

                    $.ajax({
                        url: url,
                        data: data,
                        dataType: dataType,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("Accept", "text/javascript");
                            if ($this.triggerHandler('ajax:beforeSend') === false) {
                              return false;
                            }
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:error', [xhr, status, error]);
                        }
                    });
            }
        }
    });

    /**
     *  confirmation handler
     */

    $('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click.rails', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit.rails', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote],input[data-remote]').live('click.rails', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    /**
     * <%= link_to "Delete", user_path(@user), :method => :delete, :confirm => "Are you sure?" %>
     *
     * Delete
     */
    $('a[data-method]:not([data-remote])').live('click.rails', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param !== undefined && csrf_token !== undefined) {
            metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector           = 'input[data-disable-with]',
        disable_with_form_remote_selector     = 'form[data-remote]:has('       + disable_with_input_selector + ')',
        disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';

    var disable_with_input_function = function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                .attr('value', input.attr('data-disable-with'))
                .attr('disabled', 'disabled');
        });
    };

    $(disable_with_form_remote_selector).live('ajax:before.rails', disable_with_input_function);
    $(disable_with_form_not_remote_selector).live('submit.rails', disable_with_input_function);

    $(disable_with_form_remote_selector).live('ajax:complete.rails', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });

    var jqueryVersion = $().jquery;

	if (!( (jqueryVersion === '1.4.3') || (jqueryVersion === '1.4.4'))){
		alert('This rails.js does not support the jQuery version you are using. Please read documentation.');
	}

});
<|endoftext|>


<commit_before>var Core = new (function () {
    var self = this;

    var $ContentWindow = $("#content-window");

    //************************************************
    // Active Environment
    //************************************************
    self.activeEnvironment = null;
    self.loadActiveEnvironment = function() {

        var environment = _.find(AppSettings.environments, function (env) {
            var inHostList = _.find(env.hostnames, function (hostName) {
                return hostName.toLowerCase() == location.hostname.toLowerCase();
            });
            return inHostList != null;
        });

        var env = self.readQueryString('env');
        if (env) {
            AppSettings.defaultEnvironment = env;
        }

        if (environment == null || env) {
            environment = _.findWhere(environments, { name: AppSettings.defaultEnvironment });
        }
        self.activeEnvironment = environment;

        var includes = '';
        for (var i = 0; i < AppSettings.appRoutes.length; i++) {
            var route = AppSettings.appRoutes[i];
            includes += self.composeViewModelScriptInclude(route.path);
            includes += self.composeStyleSheetInclude(route.styles);
            includes += self.composeScriptsInclude(route.scripts);
        }
        document.write(includes);
    };

    //************************************************
    // Current User Management
    //************************************************

    self.currentUser = null;
    var authCookieName = "userinfo";

    $.cookie.json = true;
    self.loginUser = function (userDetails, rememberUser) {
        self.currentUser = userDetails;

        //Set Current User cookie
        var expires = rememberUser ? 365 : 1;

        $.cookie(authCookieName, self.currentUser, { expires: expires });

        //Check to see if there was a redirect
        var target = AppSettings.defaultRoute;
        if (self.currentPageState && self.currentPageState.returnPath) {
            target = self.currentPageState.returnPath;
        }

        self.clearPageState();

        self.loadPage(target);
    };

    self.logoutUser = function () {
        self.currentUser = null;
        $.removeCookie(authCookieName);

        var target = window.location.href.split('#')[0];
        window.location.href = target;
    };

    self.getCurrentUser = function () {
        self.currentUser = self.currentUser ? self.currentUser : $.cookie(authCookieName);
        return self.currentUser;
    };

    //************************************************
    // Hash Processing
    //************************************************

    self.currentRoute = {
        path: '',
        state : {}
    };

    self.currentPath = null;
    self.currentPageState = null;
    self.documentReady = null;

    //Loads Specified path's content into Content Window protects paths if no user is logged in
    self.loadPage = function (path, state) {
        self.currentRoute = {
            path: path,
            state: state
        };

        self.writeCurrentRoute();
    };

    self.setPageState = function (pageState) {
        if (pageState) {
            self.currentRoute.state = pageState;
            self.currentPageState = pageState;
            self.writeCurrentRoute();
        }
    };

    self.getPageState = function () {
        return self.currentRoute.state;
    };

    self.clearPageState = function () {
        self.currentRoute.state = null;
        self.currentPageState = null;
        self.writeCurrentRoute();
    };

    self.writeCurrentRoute = function () {
        if (self.currentRoute.state != null) {
            window.location.hash = encodeURI(JSON.stringify(self.currentRoute));
        } else {
            window.location.hash = encodeURI(JSON.stringify({ path: self.currentRoute.path }));
        }
    };

    self.loadPageState = function () {
        if (window.location.hash != '') {
            self.currentRoute = JSON.parse(decodeURI(window.location.hash).substring(1));
        }
    };

    //************************************************
    // Resource Include Composition
    //************************************************
    function endsWith(str, suffix) {
        return str.toLowerCase().indexOf(suffix.toLowerCase(), str.length - suffix.length) !== -1;
    }

    var includeScriptHtml = function (path) {
        if (endsWith(path, '.js') != true) {
            path = path + '.js';
        }

        return '';
    };

    var includeStyleSheetHtml = function (path) {
        if (endsWith(path, '.css') != true) {
            path = path + '.css';
        }
        return '<link href="' + path + '" rel="stylesheet" type="text/css" />';
    };

    self.composeViewModelScriptInclude = function (path) {
        return includeScriptHtml("app/view-models/" + path + "-viewmodel.js");
    };

    self.composeStyleSheetInclude = function (styles) {
        var cssInclude = '';
        if (styles) {
            for (var i = 0; i < styles.length; i++) {
                var style = styles[i];
                cssInclude += includeStyleSheetHtml(style);
            }
        }
        return cssInclude;
    };

    self.composeScriptsInclude = function (scripts) {
        var jsInclude = '';
        if (scripts) {
            for (var i = 0; i < scripts.length; i++) {
                var script = scripts[i];
                jsInclude += includeScriptHtml(script);
            }
        }
        return jsInclude;
    };

    //************************************************
    // Global View Model
    //************************************************
    self.globalViewModel = {};

    var partialBindingProvider = function () {
        var result = new ko.bindingProvider(),
            originalHasBindings = result.nodeHasBindings;

        result.exclusionSelector = '#content-window';

        result.nodeHasBindings = function (node) {
            return !$(node).is(result.exclusionSelector) && originalHasBindings.call(this, node);
        };

        return result;
    };

    function instantiateGlobalViewModel() {
        try {
            var gblVm = eval('new ' + AppSettings.globalViewModel.className + '();');
        } catch(e) {}

        if (gblVm) {
            ko.bindingProvider.instance = new partialBindingProvider();

            self.globalViewModel = gblVm;
            ko.applyBindings(self.globalViewModel);

            ko.bindingProvider.instance = new ko.bindingProvider();
        } else {
            setTimeout(function() {
                instantiateGlobalViewModel();
            }, 100);
        }
    }

    self.loadGlobalViewModel = function () {
        if (AppSettings.globalViewModel) {
            var gblVmInclude = includeScriptHtml(AppSettings.globalViewModel.path);
            document.write(gblVmInclude);

            instantiateGlobalViewModel();
        }
    };

    //************************************************
    // Content Loading
    //************************************************
    $(window).on('hashchange', function () {
        self.loadPageFromCurrentUrl();
    });

    //Load the current url
    self.loadPageFromCurrentUrl = function () {
        self.loadPageState();
        self.getCurrentUser();

        if (!self.currentRoute.path) {
            self.loadPage(AppSettings.defaultRoute, self.currentPageState);
        } else if (self.currentPath != self.currentRoute.path) {

            var navigationEntry = _.findWhere(AppSettings.appRoutes, { path: self.currentRoute.path });

            if (navigationEntry.secure && self.currentUser == null) {
                self.loadPage(AppSettings.securedRedirect, { returnPath: self.currentRoute.path });
            } else {
                self.loadTemplates(navigationEntry.templates).then(function() {
                    self.loadPageHtml(navigationEntry);
                });
            }
        }
    };

    self.loadPageHtml = function (navigationEntry) {
        //Load the page
        var htmlPath = 'app/views/' + navigationEntry.path + '.html?z=' + new Date().getTime();

        $.get(htmlPath, function (data) {
            self.currentPath = navigationEntry.path;
            $ContentWindow.html(data);

            var vModel;
            eval('vModel = ' + navigationEntry.path + 'ViewModel;');
            if (vModel) {
                self.bindViewModel(vModel);
            }

            document.title = AppSettings.applicationTitle + " - " + navigationEntry.title;
            $("#panel-title").html(navigationEntry.title);

            if (self.documentReady) {
                self.documentReady();
                self.documentReady = null;
            }

        });
    };

    //************************************************
    // Template Loading
    //************************************************

    var templateContainer;
    self.loadTemplates = function (templates) {
        var deferred = Q.defer();

        var containerId = "template-container";
        templateContainer = $('#' + containerId);
        if (templateContainer.length == 0) {
            $('body').append('

');
            templateContainer = $('#' + containerId);
        }

        templateContainer.html("");
        if (templates) {
            var promises = [];
            _.each(templates, function (template) {
                promises.push(self.getTemplate(template));
            });
            Q.all(promises).spread(function (results) {
                deferred.resolve();
            });
        } else {
            deferred.resolve();
        }
        return deferred.promise;
    };

    self.getTemplate = function (template) {
        var d = Q.defer();

        var templatePath = template + '.html?z=' + new Date().getTime();

        $.get(templatePath, function (data) {
            templateContainer.append(data);
            d.resolve(template);
        });
        return d.promise;
    };

    self.bindViewModel = function (vModel) {
        ko.cleanNode($ContentWindow[0]);
        window.currentViewModel = new vModel();
        ko.applyBindings(window.currentViewModel, $ContentWindow[0]);

        //Fills viewmodels with any fields auto filled by browser
        setTimeout(function () {
            $('input').trigger('change');
        }, 250);
    };

    //************************************************
    // Loader Methods
    //************************************************

    self.showLoader = function () {
        $("#loader").fadeIn();
    };

    self.hideLoader = function () {
        $("#loader").fadeOut();
    };

    //************************************************
    // Query String Methods
    //************************************************
    self.readQueryString = function(name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    };

    //************************************************
    // API Methods
    //************************************************

    self.apiGet = function (endpoint, params, onSuccess, onFail) {
        return self.apiAjaxRequest("GET", endpoint, params, onSuccess, onFail);
    };

    self.apiPost = function (endpoint, params, onSuccess, onFail) {
        return self.apiAjaxRequest("POST", endpoint, params, onSuccess, onFail);
    };

    self.apiPostAsJson = function (endpoint, payloadObject, onSuccess, onFail) {
        var payloadJson = JSON.stringify(payloadObject);
        return self.apiAjaxRequest("POST", endpoint, { payload: payloadJson }, onSuccess, onFail);
    };

    self.apiAjaxRequest = function (method, endpoint, params, onSuccess, onFail) {
        self.showLoader();

        self.currentUser = self.getCurrentUser();
        if (self.currentUser) {
            params = $.extend({ token: self.currentUser.token }, params);
        }

        var requestPath = endpoint;
        if (requestPath.indexOf('http://') == -1 && self.activeEnvironment.apiUrl) {

            var apiUrlEndsWithSlash = self.activeEnvironment.apiUrl.lastIndexOf('/') == self.activeEnvironment.apiUrl.length - 1,
                endPointStartsWithSlash = endpoint.indexOf('/') == 0;

            if (apiUrlEndsWithSlash && endPointStartsWithSlash) {
                endpoint = endpoint.substring(1, endpoint.length);
            }

            if (!apiUrlEndsWithSlash && !endPointStartsWithSlash) {
                endpoint = '/' + endpoint;
            }

            requestPath = self.activeEnvironment.apiUrl + endpoint;
        }

        requestPath += (requestPath.indexOf('?') == -1) ? '?' : '&';
        requestPath += 'z=' + new Date().getTime();

        $.ajax({
            url: requestPath,
            type: method,
            data: params,
            success: function (data) {
                if (data.success == null) {
                    onSuccess(data);
                } else {
                    if (data.success == true) {
                        if (onSuccess) {
                            onSuccess(data.data);
                        }
                    } else {
                        if (onFail) {
                            onFail(data.errorMessage);
                        } else {
                            alert("Error : " + data.errorMessage);
                        }
                    }
                }
                self.hideLoader();
            },
            error: function (jqXHR, textStatus, errorThrown) {
                if (jqXHR.status == 403) {
                    alert('You do not have permission to complete the requested opertation.  Redirecting to login page');
                    self.loadPage(AppSettings.securedRedirect);
                } else {
                    if (onFail) {
                        onFail(errorThrown);
                    } else {
                        alert("Error : " + errorThrown);
                    }
                }
                self.hideLoader();
            }
        });
    };

    //************************************************
    // Init
    //************************************************
    self.init = function () {
        if (AppSettings.libs) {
            var jsInclude = '';
            for (var i = 0; i < AppSettings.libs.length; i++) {
                var script = AppSettings.libs[i];
                jsInclude += '';
            }
            document.write(jsInclude);
        }
        self.loadActiveEnvironment();
        self.loadGlobalViewModel();
    };
    self.init();

    return self;
})();
$(function () {
    Core.loadPageFromCurrentUrl();
});<commit_msg>improved security<commit_after>var Core = new (function () {
    var self = this;

    var $ContentWindow = $("#content-window");

    //************************************************
    // Active Environment
    //************************************************
    self.activeEnvironment = null;
    self.loadActiveEnvironment = function() {

        var environment = _.find(AppSettings.environments, function (env) {
            var inHostList = _.find(env.hostnames, function (hostName) {
                return hostName.toLowerCase() == location.hostname.toLowerCase();
            });
            return inHostList != null;
        });

        var env = self.readQueryString('env');
        if (env) {
            AppSettings.defaultEnvironment = env;
        }

        if (environment == null || env) {
            environment = _.findWhere(environments, { name: AppSettings.defaultEnvironment });
        }
        self.activeEnvironment = environment;

        var includes = '';
        for (var i = 0; i < AppSettings.appRoutes.length; i++) {
            var route = AppSettings.appRoutes[i];
            includes += self.composeViewModelScriptInclude(route.path);
            includes += self.composeStyleSheetInclude(route.styles);
            includes += self.composeScriptsInclude(route.scripts);
        }
        document.write(includes);
    };

    //************************************************
    // Current User Management
    //************************************************

    self.currentUser = null;
    var authCookieName = (window.location.pathname != '/' ? window.location.pathname : window.location.hostname).replace(/\//g, "!") + '_cookie';

    $.cookie.json = true;
    self.loginUser = function (userDetails, rememberUser) {
        self.currentUser = userDetails;

        //Set Current User cookie
        var expires = rememberUser ? 365 : 1;

        $.cookie(authCookieName, self.currentUser, { expires: expires, path: window.location.pathname });

        //Check to see if there was a redirect
        var target = AppSettings.defaultRoute;
        if (self.currentPageState && self.currentPageState.returnPath) {
            target = self.currentPageState.returnPath;
        }

        self.clearPageState();

        self.loadPage(target);
    };

    self.logoutUser = function () {
        self.currentUser = null;
        self.currentPath = null;

        $.removeCookie(authCookieName, { path: window.location.pathname });

        self.loadPage(AppSettings.defaultRoute, null);
    };

    self.getCurrentUser = function () {
        self.currentUser = self.currentUser ? self.currentUser : $.cookie(authCookieName);
        return self.currentUser;
    };

    //************************************************
    // Hash Processing
    //************************************************

    self.currentRoute = {
        path: '',
        state : {}
    };

    self.currentPath = null;
    self.currentPageState = null;
    self.documentReady = null;

    self.setPageState = function (pageState) {
        if (pageState) {
            self.currentRoute.state = pageState;
            self.currentPageState = pageState;
            self.writeCurrentRoute();
        }
    };

    self.getPageState = function () {
        return self.currentRoute.state;
    };

    self.clearPageState = function () {
        self.currentRoute.state = null;
        self.currentPageState = null;
        self.writeCurrentRoute();
    };

    self.writeCurrentRoute = function () {
        if (self.currentRoute.state != null) {
            window.location.hash = encodeURI(JSON.stringify(self.currentRoute));
        } else {
            window.location.hash = encodeURI(JSON.stringify({ path: self.currentRoute.path }));
        }
    };

    self.loadPageState = function () {
        if (window.location.hash != '') {
            self.currentRoute = JSON.parse(decodeURI(window.location.hash).substring(1));
        }
    };

    //************************************************
    // Resource Include Composition
    //************************************************
    function endsWith(str, suffix) {
        return str.toLowerCase().indexOf(suffix.toLowerCase(), str.length - suffix.length) !== -1;
    }

    var includeScriptHtml = function (path) {
        if (endsWith(path, '.js') != true) {
            path = path + '.js';
        }

        return '';
    };

    var includeStyleSheetHtml = function (path) {
        if (endsWith(path, '.css') != true) {
            path = path + '.css';
        }
        return '<link href="' + path + '" rel="stylesheet" type="text/css" />';
    };

    self.composeViewModelScriptInclude = function (path) {
        return includeScriptHtml("app/view-models/" + path + "-viewmodel.js");
    };

    self.composeStyleSheetInclude = function (styles) {
        var cssInclude = '';
        if (styles) {
            for (var i = 0; i < styles.length; i++) {
                var style = styles[i];
                cssInclude += includeStyleSheetHtml(style);
            }
        }
        return cssInclude;
    };

    self.composeScriptsInclude = function (scripts) {
        var jsInclude = '';
        if (scripts) {
            for (var i = 0; i < scripts.length; i++) {
                var script = scripts[i];
                jsInclude += includeScriptHtml(script);
            }
        }
        return jsInclude;
    };

    //************************************************
    // Global View Model
    //************************************************
    self.globalViewModel = {};

    var partialBindingProvider = function () {
        var result = new ko.bindingProvider(),
            originalHasBindings = result.nodeHasBindings;

        result.exclusionSelector = '#content-window';

        result.nodeHasBindings = function (node) {
            return !$(node).is(result.exclusionSelector) && originalHasBindings.call(this, node);
        };

        return result;
    };

    function instantiateGlobalViewModel() {
        try {
            var gblVm = eval('new ' + AppSettings.globalViewModel.className + '();');
        } catch(e) {}

        if (gblVm) {
            ko.bindingProvider.instance = new partialBindingProvider();

            self.globalViewModel = gblVm;
            ko.applyBindings(self.globalViewModel);

            ko.bindingProvider.instance = new ko.bindingProvider();
        } else {
            setTimeout(function() {
                instantiateGlobalViewModel();
            }, 100);
        }
    }

    self.loadGlobalViewModel = function () {
        if (AppSettings.globalViewModel) {
            var gblVmInclude = includeScriptHtml(AppSettings.globalViewModel.path);
            document.write(gblVmInclude);

            instantiateGlobalViewModel();
        }
    };

    //************************************************
    // Content Loading
    //************************************************
    $(window).on('hashchange', function () {
        self.loadPageFromCurrentUrl();
    });

    self.loadPage = function (path, state) {
        var navigationEntry = _.findWhere(AppSettings.appRoutes, { path: path });
        if (navigationEntry == null) {
            self.currentRoute = {
                path: AppSettings.defaultRoute,
                state: null
            };
        } else {
            if (navigationEntry.secure && self.currentUser == null) {
                self.currentRoute = {
                    path: AppSettings.securedRedirect,
                    state: { returnPath: path }
                };
            } else {
                self.currentRoute = {
                    path: path,
                    state: state
                };
            }
        }

        self.writeCurrentRoute();
    };

    //Load the current url
    self.loadPageFromCurrentUrl = function () {
        self.loadPageState();
        self.getCurrentUser();

        if (!self.currentRoute.path) {
            self.loadPage(AppSettings.defaultRoute, self.currentPageState);
        } else if (self.currentPath != self.currentRoute.path) {

            var navigationEntry = _.findWhere(AppSettings.appRoutes, { path: self.currentRoute.path });

            if (navigationEntry.secure && self.currentUser == null) {
                self.loadPage(AppSettings.securedRedirect, { returnPath: self.currentRoute.path });
            } else {
                self.loadTemplates(navigationEntry.templates).then(function() {
                    self.loadPageHtml(navigationEntry);
                });
            }
        }
    };

    self.loadPageHtml = function (navigationEntry) {
        //Load the page
        var htmlPath = 'app/views/' + navigationEntry.path + '.html?z=' + new Date().getTime();

        $.get(htmlPath, function (data) {
            self.currentPath = navigationEntry.path;
            $ContentWindow.html(data);

            var vModel;
            eval('vModel = ' + navigationEntry.path + 'ViewModel;');
            if (vModel) {
                self.bindViewModel(vModel);
            }

            document.title = AppSettings.applicationTitle + " - " + navigationEntry.title;
            $("#panel-title").html(navigationEntry.title);

            if (self.documentReady) {
                self.documentReady();
                self.documentReady = null;
            }

        });
    };

    //************************************************
    // Template Loading
    //************************************************

    var templateContainer;
    self.loadTemplates = function (templates) {
        var deferred = Q.defer();

        var containerId = "template-container";
        templateContainer = $('#' + containerId);
        if (templateContainer.length == 0) {
            $('body').append('

');
            templateContainer = $('#' + containerId);
        }

        templateContainer.html("");
        if (templates) {
            var promises = [];
            _.each(templates, function (template) {
                promises.push(self.getTemplate(template));
            });
            Q.all(promises).spread(function (results) {
                deferred.resolve();
            });
        } else {
            deferred.resolve();
        }
        return deferred.promise;
    };

    self.getTemplate = function (template) {
        var d = Q.defer();

        var templatePath = template + '.html?z=' + new Date().getTime();

        $.get(templatePath, function (data) {
            templateContainer.append(data);
            d.resolve(template);
        });
        return d.promise;
    };

    self.bindViewModel = function (vModel) {
        ko.cleanNode($ContentWindow[0]);
        window.currentViewModel = new vModel();
        ko.applyBindings(window.currentViewModel, $ContentWindow[0]);

        //Fills viewmodels with any fields auto filled by browser
        setTimeout(function () {
            $('input').trigger('change');
        }, 250);
    };

    //************************************************
    // Loader Methods
    //************************************************

    self.showLoader = function () {
        $("#loader").fadeIn();
    };

    self.hideLoader = function () {
        $("#loader").fadeOut();
    };

    //************************************************
    // Query String Methods
    //************************************************
    self.readQueryString = function(name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    };

    //************************************************
    // API Methods
    //************************************************

    self.apiGet = function (endpoint, params, onSuccess, onFail) {
        return self.apiAjaxRequest("GET", endpoint, params, onSuccess, onFail);
    };

    self.apiPost = function (endpoint, params, onSuccess, onFail) {
        return self.apiAjaxRequest("POST", endpoint, params, onSuccess, onFail);
    };

    self.apiPostAsJson = function (endpoint, payloadObject, onSuccess, onFail) {
        var payloadJson = JSON.stringify(payloadObject);
        return self.apiAjaxRequest("POST", endpoint, { payload: payloadJson }, onSuccess, onFail);
    };

    self.apiAjaxRequest = function (method, endpoint, params, onSuccess, onFail) {
        self.showLoader();

        self.currentUser = self.getCurrentUser();
        if (self.currentUser) {
            params = $.extend({ token: self.currentUser.token }, params);
        }

        var requestPath = endpoint;
        if (requestPath.indexOf('http://') == -1 && self.activeEnvironment.apiUrl) {

            var apiUrlEndsWithSlash = self.activeEnvironment.apiUrl.lastIndexOf('/') == self.activeEnvironment.apiUrl.length - 1,
                endPointStartsWithSlash = endpoint.indexOf('/') == 0;

            if (apiUrlEndsWithSlash && endPointStartsWithSlash) {
                endpoint = endpoint.substring(1, endpoint.length);
            }

            if (!apiUrlEndsWithSlash && !endPointStartsWithSlash) {
                endpoint = '/' + endpoint;
            }

            requestPath = self.activeEnvironment.apiUrl + endpoint;
        }

        requestPath += (requestPath.indexOf('?') == -1) ? '?' : '&';
        requestPath += 'z=' + new Date().getTime();

        $.ajax({
            url: requestPath,
            type: method,
            data: params,
            success: function (data) {
                if (data.success == null) {
                    onSuccess(data);
                } else {
                    if (data.success == true) {
                        if (onSuccess) {
                            onSuccess(data.data);
                        }
                    } else {
                        if (onFail) {
                            onFail(data.errorMessage);
                        } else {
                            alert("Error : " + data.errorMessage);
                        }
                    }
                }
                self.hideLoader();
            },
            error: function (jqXHR, textStatus, errorThrown) {
                if (jqXHR.status == 403) {
                    alert('You do not have permission to complete the requested opertation.  Redirecting to login page');
                    self.loadPage(AppSettings.securedRedirect);
                } else {
                    if (onFail) {
                        onFail(errorThrown);
                    } else {
                        alert("Error : " + errorThrown);
                    }
                }
                self.hideLoader();
            }
        });
    };

    //************************************************
    // Init
    //************************************************
    self.init = function () {
        if (AppSettings.libs) {
            var jsInclude = '';
            for (var i = 0; i < AppSettings.libs.length; i++) {
                var script = AppSettings.libs[i];
                jsInclude += '';
            }
            document.write(jsInclude);
        }
        self.loadActiveEnvironment();
        self.loadGlobalViewModel();
    };
    self.init();

    return self;
})();
$(function () {
    Core.loadPageFromCurrentUrl();
});<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
} from '@appbaseio/reactivecore/lib/actions';
import {
	isEqual,
	checkValueChange,
	checkPropChange,
	getClassName,
} from '@appbaseio/reactivecore/lib/utils/helper';
import dateFormats from '@appbaseio/reactivecore/lib/utils/dateFormats';
import types from '@appbaseio/reactivecore/lib/utils/types';
import XDate from 'xdate';
import DayPickerInput from 'react-day-picker/DayPickerInput';

import DateContainer from '../../styles/DateContainer';
import Title from '../../styles/Title';
import Flex from '../../styles/Flex';
import CancelSvg from '../shared/CancelSvg';

class DatePicker extends Component {
	constructor(props) {
		super(props);
		this.state = {
			currentDate: '',
		};
		this.locked = false;
	}

	componentWillMount() {
		this.props.addComponent(this.props.componentId);
		this.setReact(this.props);

		if (this.props.selectedValue) {
			this.handleDateChange(this.props.selectedValue, true);
		} else if (this.props.defaultSelected) {
			this.handleDateChange(this.props.defaultSelected, true);
		}
	}

	componentWillReceiveProps(nextProps) {
		checkPropChange(
			this.props.react,
			nextProps.react,
			() => this.setReact(nextProps),
		);
		checkPropChange(
			this.props.dataField,
			nextProps.dataField,
			() => this.updateQuery(
				this.state.currentDate ? this.formatInputDate(this.state.currentDate) : null,
				nextProps,
			),
		);
		if (!isEqual(this.props.defaultSelected, nextProps.defaultSelected)) {
			this.handleDateChange(nextProps.defaultSelected, true, nextProps);
		} else if (
			!isEqual(this.formatInputDate(this.state.currentDate), nextProps.selectedValue)
			&& !isEqual(this.props.selectedValue, nextProps.selectedValue)
		) {
			this.handleDateChange(nextProps.selectedValue || '', true, nextProps);
		}
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	formatDate = (date, props = this.props) => {
		switch (props.queryFormat) {
			case 'epoch_millis':
				return date.getTime();
			case 'epoch_seconds':
				return Math.floor(date.getTime() / 1000);
			default: {
				if (dateFormats[props.queryFormat]) {
					return date.toString(dateFormats[props.queryFormat]);
				}
				return date;
			}
		}
	};

	formatInputDate = date => new XDate(date).toString('yyyy-MM-dd');

	defaultQuery = (value, props) => {
		let query = null;
		if (value && props.queryFormat) {
			query = {
				range: {
					[props.dataField]: {
						gte: this.formatDate(new XDate(value).addHours(-24), props),
						lte: this.formatDate(new XDate(value), props),
					},
				},
			};
		}
		return query;
	};

	clearDayPicker = () => {
		if (this.state.currentDate !== '') {
			this.handleDateChange('');	// resets the day picker component
		}
	}

	handleDayPicker = (date) => {
		this.handleDateChange(date || '');
	}

	handleDateChange = (
		currentDate,
		isDefaultValue = false,
		props = this.props,
	) => {
		// currentDate should be valid or empty string for resetting the query
		if (isDefaultValue && !new XDate(currentDate).valid() && currentDate.length) {
			console.error(`DatePicker: ${props.componentId} invalid value passed for date`);
		} else {
			// ignore state updates when component is locked
			if (props.beforeValueChange && this.locked) {
				return;
			}

			this.locked = true;
			let value = null;
			if (currentDate) {
				value = isDefaultValue ? currentDate : this.formatInputDate(currentDate);
			}

			const performUpdate = () => {
				this.setState({
					currentDate,
				}, () => {
					this.updateQuery(value, props);
					this.locked = false;
				});
			};
			checkValueChange(
				props.componentId,
				value,
				props.beforeValueChange,
				props.onValueChange,
				performUpdate,
			);
		}
	};

	updateQuery = (value, props) => {
		const query = props.customQuery || this.defaultQuery;
		let onQueryChange = null;
		if (props.onQueryChange) {
			onQueryChange = props.onQueryChange;
		}
		props.updateQuery({
			componentId: props.componentId,
			query: query(value, props),
			value,
			showFilter: props.showFilter,
			label: props.filterLabel,
			onQueryChange,
			URLParams: props.URLParams,
		});
	};

	render() {
		return (
			<DateContainer
				showBorder={!this.props.showClear}
				style={this.props.style}
				className={this.props.className}
			>
				{this.props.title && (
					<Title
						className={getClassName(this.props.innerClass, 'title') || null}
					>
						{this.props.title}
					</Title>
				)}
				<Flex showBorder={this.props.showClear} iconPosition="right">
					<DayPickerInput
						showOverlay={this.props.focused}
						formatDate={this.formatInputDate}
						value={this.state.currentDate}
						placeholder={this.props.placeholder}
						dayPickerProps={{
							numberOfMonths: this.props.numberOfMonths,
							initialMonth: this.props.initialMonth,
						}}
						clickUnselectsDay={this.props.clickUnselectsDay}
						onDayChange={this.handleDayPicker}
						inputProps={{
							readOnly: true,
						}}
						classNames={{
							container:
								getClassName(this.props.innerClass, 'daypicker-container')
								|| 'DayPickerInput',
							overlayWrapper:
								getClassName(
									this.props.innerClass,
									'daypicker-overlay-wrapper',
								) || 'DayPickerInput-OverlayWrapper',
							overlay:
								getClassName(this.props.innerClass, 'daypicker-overlay')
								|| 'DayPickerInput-Overlay',
						}}
						{...this.props.dayPickerInputProps}
					/>
					{
						this.props.showClear && this.state.currentDate
						&& <CancelSvg onClick={this.clearDayPicker} />
					}
				</Flex>
			</DateContainer>
		);
	}
}

DatePicker.propTypes = {
	addComponent: types.funcRequired,
	componentId: types.stringRequired,
	defaultSelected: types.date,
	react: types.react,
	removeComponent: types.funcRequired,
	queryFormat: types.queryFormatDate,
	selectedValue: types.selectedValue,
	placeholder: types.string,
	focused: types.bool,
	innerClass: types.style,
	title: types.string,
	style: types.style,
	className: types.string,
	numberOfMonths: types.number,
	initialMonth: types.dateObject,
	dayPickerInputProps: types.props,
	showFilter: types.bool,
	filterLabel: types.string,
	showClear: types.bool,
	clickUnselectsDay: types.bool,
	dataField: types.stringRequired,
	onQueryChange: types.func,
};

DatePicker.defaultProps = {
	placeholder: 'Select Date',
	numberOfMonths: 1,
	showFilter: true,
	showClear: true,
	clickUnselectsDay: true,
};

const mapStateToProps = (state, props) => ({
	selectedValue: state.selectedValues[props.componentId]
		? state.selectedValues[props.componentId].value
		: null,
});

const mapDispatchtoProps = dispatch => ({
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	watchComponent: (component, react) =>
		dispatch(watchComponent(component, react)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
});

export default connect(mapStateToProps, mapDispatchtoProps)(DatePicker);
<commit_msg>web: DatePicker - onQueryChange prop destructing<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
} from '@appbaseio/reactivecore/lib/actions';
import {
	isEqual,
	checkValueChange,
	checkPropChange,
	getClassName,
} from '@appbaseio/reactivecore/lib/utils/helper';
import dateFormats from '@appbaseio/reactivecore/lib/utils/dateFormats';
import types from '@appbaseio/reactivecore/lib/utils/types';
import XDate from 'xdate';
import DayPickerInput from 'react-day-picker/DayPickerInput';

import DateContainer from '../../styles/DateContainer';
import Title from '../../styles/Title';
import Flex from '../../styles/Flex';
import CancelSvg from '../shared/CancelSvg';

class DatePicker extends Component {
	constructor(props) {
		super(props);
		this.state = {
			currentDate: '',
		};
		this.locked = false;
	}

	componentWillMount() {
		this.props.addComponent(this.props.componentId);
		this.setReact(this.props);

		if (this.props.selectedValue) {
			this.handleDateChange(this.props.selectedValue, true);
		} else if (this.props.defaultSelected) {
			this.handleDateChange(this.props.defaultSelected, true);
		}
	}

	componentWillReceiveProps(nextProps) {
		checkPropChange(
			this.props.react,
			nextProps.react,
			() => this.setReact(nextProps),
		);
		checkPropChange(
			this.props.dataField,
			nextProps.dataField,
			() => this.updateQuery(
				this.state.currentDate ? this.formatInputDate(this.state.currentDate) : null,
				nextProps,
			),
		);
		if (!isEqual(this.props.defaultSelected, nextProps.defaultSelected)) {
			this.handleDateChange(nextProps.defaultSelected, true, nextProps);
		} else if (
			!isEqual(this.formatInputDate(this.state.currentDate), nextProps.selectedValue)
			&& !isEqual(this.props.selectedValue, nextProps.selectedValue)
		) {
			this.handleDateChange(nextProps.selectedValue || '', true, nextProps);
		}
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);
	}

	setReact(props) {
		if (props.react) {
			props.watchComponent(props.componentId, props.react);
		}
	}

	formatDate = (date, props = this.props) => {
		switch (props.queryFormat) {
			case 'epoch_millis':
				return date.getTime();
			case 'epoch_seconds':
				return Math.floor(date.getTime() / 1000);
			default: {
				if (dateFormats[props.queryFormat]) {
					return date.toString(dateFormats[props.queryFormat]);
				}
				return date;
			}
		}
	};

	formatInputDate = date => new XDate(date).toString('yyyy-MM-dd');

	defaultQuery = (value, props) => {
		let query = null;
		if (value && props.queryFormat) {
			query = {
				range: {
					[props.dataField]: {
						gte: this.formatDate(new XDate(value).addHours(-24), props),
						lte: this.formatDate(new XDate(value), props),
					},
				},
			};
		}
		return query;
	};

	clearDayPicker = () => {
		if (this.state.currentDate !== '') {
			this.handleDateChange('');	// resets the day picker component
		}
	}

	handleDayPicker = (date) => {
		this.handleDateChange(date || '');
	}

	handleDateChange = (
		currentDate,
		isDefaultValue = false,
		props = this.props,
	) => {
		// currentDate should be valid or empty string for resetting the query
		if (isDefaultValue && !new XDate(currentDate).valid() && currentDate.length) {
			console.error(`DatePicker: ${props.componentId} invalid value passed for date`);
		} else {
			// ignore state updates when component is locked
			if (props.beforeValueChange && this.locked) {
				return;
			}

			this.locked = true;
			let value = null;
			if (currentDate) {
				value = isDefaultValue ? currentDate : this.formatInputDate(currentDate);
			}

			const performUpdate = () => {
				this.setState({
					currentDate,
				}, () => {
					this.updateQuery(value, props);
					this.locked = false;
				});
			};
			checkValueChange(
				props.componentId,
				value,
				props.beforeValueChange,
				props.onValueChange,
				performUpdate,
			);
		}
	};

	updateQuery = (value, props) => {
		const query = props.customQuery || this.defaultQuery;

		const { onQueryChange = null } = props;

		props.updateQuery({
			componentId: props.componentId,
			query: query(value, props),
			value,
			showFilter: props.showFilter,
			label: props.filterLabel,
			onQueryChange,
			URLParams: props.URLParams,
		});
	};

	render() {
		return (
			<DateContainer
				showBorder={!this.props.showClear}
				style={this.props.style}
				className={this.props.className}
			>
				{this.props.title && (
					<Title
						className={getClassName(this.props.innerClass, 'title') || null}
					>
						{this.props.title}
					</Title>
				)}
				<Flex showBorder={this.props.showClear} iconPosition="right">
					<DayPickerInput
						showOverlay={this.props.focused}
						formatDate={this.formatInputDate}
						value={this.state.currentDate}
						placeholder={this.props.placeholder}
						dayPickerProps={{
							numberOfMonths: this.props.numberOfMonths,
							initialMonth: this.props.initialMonth,
						}}
						clickUnselectsDay={this.props.clickUnselectsDay}
						onDayChange={this.handleDayPicker}
						inputProps={{
							readOnly: true,
						}}
						classNames={{
							container:
								getClassName(this.props.innerClass, 'daypicker-container')
								|| 'DayPickerInput',
							overlayWrapper:
								getClassName(
									this.props.innerClass,
									'daypicker-overlay-wrapper',
								) || 'DayPickerInput-OverlayWrapper',
							overlay:
								getClassName(this.props.innerClass, 'daypicker-overlay')
								|| 'DayPickerInput-Overlay',
						}}
						{...this.props.dayPickerInputProps}
					/>
					{
						this.props.showClear && this.state.currentDate
						&& <CancelSvg onClick={this.clearDayPicker} />
					}
				</Flex>
			</DateContainer>
		);
	}
}

DatePicker.propTypes = {
	addComponent: types.funcRequired,
	componentId: types.stringRequired,
	defaultSelected: types.date,
	react: types.react,
	removeComponent: types.funcRequired,
	queryFormat: types.queryFormatDate,
	selectedValue: types.selectedValue,
	placeholder: types.string,
	focused: types.bool,
	innerClass: types.style,
	title: types.string,
	style: types.style,
	className: types.string,
	numberOfMonths: types.number,
	initialMonth: types.dateObject,
	dayPickerInputProps: types.props,
	showFilter: types.bool,
	filterLabel: types.string,
	showClear: types.bool,
	clickUnselectsDay: types.bool,
	dataField: types.stringRequired,
	onQueryChange: types.func,
};

DatePicker.defaultProps = {
	placeholder: 'Select Date',
	numberOfMonths: 1,
	showFilter: true,
	showClear: true,
	clickUnselectsDay: true,
};

const mapStateToProps = (state, props) => ({
	selectedValue: state.selectedValues[props.componentId]
		? state.selectedValues[props.componentId].value
		: null,
});

const mapDispatchtoProps = dispatch => ({
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	watchComponent: (component, react) =>
		dispatch(watchComponent(component, react)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
});

export default connect(mapStateToProps, mapDispatchtoProps)(DatePicker);
<|endoftext|>