<commit_before>/**
 * Overview of spending / cash flow
 */

import $ from "../../lib/jquery.min";

import {
  COLOR_CATEGORY, MSG_TIME_ERROR
} from "const";

import { arrayAverage, median, months, getYearMonthRow } from "misc/misc";
import { getColorFromScore } from "misc/color";
import { formatCurrency } from "misc/format";
import { validateCurrencyInput } from "misc/edit";

import Page from "page/page";
import { GraphBalance, GraphSpend } from "graph/overview";

export class PageOverview extends Page {
  constructor(api, state) {
    super({ page: "overview" }, api, state);

    this.categories = [
      "funds",
      "bills",
      "food", "general", "holiday", "social",
      "in", "out", "net",
      "predicted", "balance"
    ];

    this.colors = COLOR_CATEGORY;
  }

  hookDataLoadedBeforeRender(callback, res) {
    this.data = res.data;

    this.getYearMonths();
  }

  hookSwitchToAfterLoad() {
    // update data from other pages (which might have changed)
    this.processCategory("funds");
    this.processCategory("in");
    this.processCategory("food");
    this.processCategory("general");
    this.processCategory("holiday");
    this.processCategory("social");
    this.processCategory("bills");

    this.update();
  }

  render() {
    this.$tbl = $("

")
      .addClass("table-insert")
      .addClass("table-overview")
      .addClass("noselect");

    this.$thead = $("

");

    this.$thr = $("

").append("Month	");

    this.categories.forEach((category, key) => this.addCategory(key, category));

    this.$thead.append(this.$thr);
    this.$tbl.append(this.$thead);

    this.$tbody = $("

");

    this.$td = [];
    this.$tr = [];

    this.yearMonths.forEach((yearMonth, key) => this.addTableRow(key, yearMonth));

    this.$tbl.append(this.$tbody);

    this.$page.append(this.$tbl);

    // draw graphs
    this.addGraphs();
  }

  getYearMonths() {
    this.yearMonths = [];

    let y = this.data.startYearMonth[0];
    let m = this.data.startYearMonth[1];

    while (
      y < this.data.endYearMonth[0] ||
      (y === this.data.endYearMonth[0] && m <= this.data.endYearMonth[1])
    ) {
      this.yearMonths.push([y, m]);

      if (++m > 12) {
        m = 1;
        y++;
      }
    }
  }

  calculateScores() {
    this.scores = {};

    for (const category in this.data.cost) {
      const costs = this.data.cost[category];

      const max = Math.max.apply(null, costs);
      const min = Math.min.apply(null, costs);

      const pve = [];
      const nve = [];

      for (let i = 0; i < costs.length; i++) {
        if (costs[i] >= 0) {
          pve.push(costs[i]);
        }
        else {
          nve.push(costs[i]);
        }
      }

      const median1 = median(pve);
      const median2 = median(nve);

      const thisScores = costs.map(thisCost => {
        let medianV = median1;
        let cost = thisCost;
        let M = max;

        if (thisCost < 0) {
          medianV = -median2;
          cost *= -1;
          M = -min;
        }

        if (cost > medianV) {
          return 0.5 * (1 + (cost - medianV) / (M - medianV));
        }

        return 0.5 * cost / medianV;
      });

      this.scores[category] = thisScores;
    }
  }

  calculateFutures() {
    // calculate futures (from past averages)
    const average = {};

    const futureCategories = ["food", "general", "holiday", "social"];

    for (const category of futureCategories) {
      average[category] = arrayAverage(this.data.cost[category], this.data.futureMonths);

      const spliceArgs = [
        this.data.cost[category].length - this.data.futureMonths,
        this.data.futureMonths
      ];

      for (let i = 0; i < this.data.futureMonths; i++) {
        spliceArgs.push(average[category]);
      }

      Array.prototype.splice.apply(this.data.cost[category], spliceArgs);
    }
  }

  calculateColumns() {
    // calculate total spend (including bills) for each month
    this.data.cost.out = this.yearMonths.map((yearMonth, key) => {
      return  this.data.cost.bills[key]   +
              this.data.cost.food[key]    +
              this.data.cost.general[key] +
              this.data.cost.holiday[key] +
              this.data.cost.social[key]
      ;
    });

    // calculate net change in balance for each month
    this.data.cost.net = this.data.cost.out.map((item, key) => {
      return this.data.cost.in[key] - item;
    });

    // calculate the predicted balance for each month
    this.data.cost.predicted = [];

    let lastValue = 0;

    this.data.cost.predicted = this.data.cost.out.map((item, key) => {
      let value = this.data.cost.net[key];

      if (key > 0) {
        const lastBalance = this.data.cost.balance[key - 1];

        if (lastBalance > 0) {
          value += lastBalance;
          this.data.cost.predicted[key] += lastBalance;
        }
        else {
          value += lastValue;
        }
      }

      lastValue = value;

      return value;
    });

    this.data.cost.predicted[0] = this.data.cost.balance[0];
  }

  afterBalanceEdit(callback) {
    const val = validateCurrencyInput(this.state.editing.$input.val());

    if (val === null) {
      return;
    }

    const $tr = this.state.editing.$elem.parent();

    const yearMonth = $tr.data("yearMonth");
    const key = $tr.index();

    if (val !== this.data.cost.balance[key]) {
      this.api.request(
        "update/overview", "POST", {
          year:   yearMonth[0],
          month:  yearMonth[1],
          balance:  val
        },
        () => this.onBalanceEdited(key, val),
        () => this.onBalanceEditError(),
        () => this.onBalanceEditRequestComplete(key, callback)
      );
    }
    else {
      this.state.editing.deactivate(val);

      if (typeof callback === "function") {
        callback();
      }
    }
  }

  onBalanceEdited(key, val) {
    this.data.cost.balance[key] = val;

    this.update();
  }

  onBalanceEditError() {
    this.state.error.newMessage("Error updating value! (Server error)", 2, MSG_TIME_ERROR);
  }

  onBalanceEditRequestComplete(key, callback) {
    this.state.editing.deactivate(this.data.cost.balance[key]);

    if (typeof callback === "function") {
      callback();
    }

  }

  addCategory(key, category) {
    this.$thr.append($("	").text(category));
  }

  addTableCell(key, cKey, category) {
    this.$td[key][category] = $("	")
    .addClass("cost")
    .data("val", 0)
    .append($("").addClass("text"));

    if (category === "balance") {
      this.$td[key][category].editable(
        this.api, this.state,
        callback => this.afterBalanceEdit(callback), "cost"
      );
    }

    this.$tr[key].append(this.$td[key][category]);
  }

  addTableRow(key, yearMonth) {
    this.$tr[key] = $("

")
    .toggleClass(
      "past", (yearMonth[0] === this.data.currentYear &&
        yearMonth[1] < this.data.currentMonth) || yearMonth[0] < this.data.currentYear
    ).toggleClass(
      "active", yearMonth[0] === this.data.currentYear &&
        yearMonth[1] === this.data.currentMonth
    ).toggleClass(
      "future", (yearMonth[0] === this.data.currentYear &&
        yearMonth[1] > this.data.currentMonth) || yearMonth[0] > this.data.currentYear
    ).append($("	")
      .addClass("month")
      .text(months[yearMonth[1] - 1] + "-" + yearMonth[0].toString().substring(2))
    ).data("yearMonth", yearMonth);

    this.$td[key] = {};

    this.categories.forEach((category, cKey) => this.addTableCell(key, cKey, category));

    this.$tbody.append(this.$tr[key]);
  }

  addGraphs() {
    this.graphBalance = new GraphBalance({
      width:  500,
      height: 300,
      $cont:  this.$page,
      title:  "balance",
      dataPast: [],
      dataFuture: [],
      startYear:    this.data.startYearMonth[0],
      startMonth:   this.data.startYearMonth[1],
      currentYear:  this.data.currentYear,
      currentMonth: this.data.currentMonth,
      yearMonths:   this.yearMonths,
      pad: [64, 0, 24, 0],
      range: [0, this.yearMonths.length - 1, 0, 0]
    });

    this.graphSpend = new GraphSpend({
      width:  500,
      height: 300,
      $cont:  this.$page,
      title:  "spend",
      data:   this.data.cost,
      pad: [64, 0, 24, 0],
      range: [0, this.yearMonths.length - 1, 0, 0],
      yearMonths: this.yearMonths
    });
  }

  updateCategories(key, cKey, category) {
    this.$td[key][category]
    .data("val", this.data.cost[category][key])
    .css("background-color", getColorFromScore(
      this.colors[category],
      this.scores[category][key],
      this.data.cost[category][key] < 0)
    ).children(".text").html(formatCurrency(this.data.cost[category][key]));
  }

  updateYearMonths(key) {
    this.categories.forEach((category, cKey) => {
      this.updateCategories(key, cKey, category);
    });
  }

  updateGraphs() {
    this.graphBalance.update(this.data.cost.balance, this.data.cost.predicted);
    this.graphSpend.update(this.data.cost);
  }

  update(data) {
    if (data) {
      this.data = data;
    }

    // calculate future values from the averages
    this.calculateFutures();

    // calculate extra columns (out, spent etc.) from raw data
    this.calculateColumns();

    // colour each column according to values
    this.calculateScores();

    // fill the table with values
    this.yearMonths.forEach((yearMonth, key) => this.updateYearMonths(key));

    // update the graphs
    this.updateGraphs();
  }

  processCategory(category) {
    if (this.state.pages[category]) {
      const doneRows = [];

      for (let i = 0; i < this.state.pages[category].data.length; i++) {
        const year  = this.state.pages[category].data[i].date.year;
        const month = this.state.pages[category].data[i].date.month;

        const row = getYearMonthRow(
          this.data.startYearMonth[0], this.data.startYearMonth[1],
          year, month
        );

        const doneRow = doneRows.indexOf(row) > -1;

        if (!doneRow) {
          this.data.cost[category][row] = 0;

          doneRows.push(row);
        }

        if (row > -1 && row < this.data.cost.in.length) {
          this.data.cost[category][row] += this.state.pages[category].data[i].cost;
        }
      }
    }
  }
}

<commit_msg>Removed funds from overview page<commit_after>/**
 * Overview of spending / cash flow
 */

import $ from "../../lib/jquery.min";

import {
  COLOR_CATEGORY, MSG_TIME_ERROR
} from "const";

import { arrayAverage, median, months, getYearMonthRow } from "misc/misc";
import { getColorFromScore } from "misc/color";
import { formatCurrency } from "misc/format";
import { validateCurrencyInput } from "misc/edit";

import Page from "page/page";
import { GraphBalance, GraphSpend } from "graph/overview";

export class PageOverview extends Page {
  constructor(api, state) {
    super({ page: "overview" }, api, state);

    this.categories = [
      "bills",
      "food", "general", "holiday", "social",
      "in", "out", "net",
      "predicted", "balance"
    ];

    this.colors = COLOR_CATEGORY;
  }

  hookDataLoadedBeforeRender(callback, res) {
    this.data = res.data;

    this.getYearMonths();
  }

  hookSwitchToAfterLoad() {
    // update data from other pages (which might have changed)
    this.processCategory("funds");
    this.processCategory("in");
    this.processCategory("food");
    this.processCategory("general");
    this.processCategory("holiday");
    this.processCategory("social");
    this.processCategory("bills");

    this.update();
  }

  render() {
    this.$tbl = $("

")
      .addClass("table-insert")
      .addClass("table-overview")
      .addClass("noselect");

    this.$thead = $("

");

    this.$thr = $("

").append("Month	");

    this.categories.forEach((category, key) => this.addCategory(key, category));

    this.$thead.append(this.$thr);
    this.$tbl.append(this.$thead);

    this.$tbody = $("

");

    this.$td = [];
    this.$tr = [];

    this.yearMonths.forEach((yearMonth, key) => this.addTableRow(key, yearMonth));

    this.$tbl.append(this.$tbody);

    this.$page.append(this.$tbl);

    // draw graphs
    this.addGraphs();
  }

  getYearMonths() {
    this.yearMonths = [];

    let y = this.data.startYearMonth[0];
    let m = this.data.startYearMonth[1];

    while (
      y < this.data.endYearMonth[0] ||
      (y === this.data.endYearMonth[0] && m <= this.data.endYearMonth[1])
    ) {
      this.yearMonths.push([y, m]);

      if (++m > 12) {
        m = 1;
        y++;
      }
    }
  }

  calculateScores() {
    this.scores = {};

    for (const category in this.data.cost) {
      const costs = this.data.cost[category];

      const max = Math.max.apply(null, costs);
      const min = Math.min.apply(null, costs);

      const pve = [];
      const nve = [];

      for (let i = 0; i < costs.length; i++) {
        if (costs[i] >= 0) {
          pve.push(costs[i]);
        }
        else {
          nve.push(costs[i]);
        }
      }

      const median1 = median(pve);
      const median2 = median(nve);

      const thisScores = costs.map(thisCost => {
        let medianV = median1;
        let cost = thisCost;
        let M = max;

        if (thisCost < 0) {
          medianV = -median2;
          cost *= -1;
          M = -min;
        }

        if (cost > medianV) {
          return 0.5 * (1 + (cost - medianV) / (M - medianV));
        }

        return 0.5 * cost / medianV;
      });

      this.scores[category] = thisScores;
    }
  }

  calculateFutures() {
    // calculate futures (from past averages)
    const average = {};

    const futureCategories = ["food", "general", "holiday", "social"];

    for (const category of futureCategories) {
      average[category] = arrayAverage(this.data.cost[category], this.data.futureMonths);

      const spliceArgs = [
        this.data.cost[category].length - this.data.futureMonths,
        this.data.futureMonths
      ];

      for (let i = 0; i < this.data.futureMonths; i++) {
        spliceArgs.push(average[category]);
      }

      Array.prototype.splice.apply(this.data.cost[category], spliceArgs);
    }
  }

  calculateColumns() {
    // calculate total spend (including bills) for each month
    this.data.cost.out = this.yearMonths.map((yearMonth, key) => {
      return  this.data.cost.bills[key]   +
              this.data.cost.food[key]    +
              this.data.cost.general[key] +
              this.data.cost.holiday[key] +
              this.data.cost.social[key]
      ;
    });

    // calculate net change in balance for each month
    this.data.cost.net = this.data.cost.out.map((item, key) => {
      return this.data.cost.in[key] - item;
    });

    // calculate the predicted balance for each month
    this.data.cost.predicted = [];

    let lastValue = 0;

    this.data.cost.predicted = this.data.cost.out.map((item, key) => {
      let value = this.data.cost.net[key];

      if (key > 0) {
        const lastBalance = this.data.cost.balance[key - 1];

        if (lastBalance > 0) {
          value += lastBalance;
          this.data.cost.predicted[key] += lastBalance;
        }
        else {
          value += lastValue;
        }
      }

      lastValue = value;

      return value;
    });

    this.data.cost.predicted[0] = this.data.cost.balance[0];
  }

  afterBalanceEdit(callback) {
    const val = validateCurrencyInput(this.state.editing.$input.val());

    if (val === null) {
      return;
    }

    const $tr = this.state.editing.$elem.parent();

    const yearMonth = $tr.data("yearMonth");
    const key = $tr.index();

    if (val !== this.data.cost.balance[key]) {
      this.api.request(
        "update/overview", "POST", {
          year:   yearMonth[0],
          month:  yearMonth[1],
          balance:  val
        },
        () => this.onBalanceEdited(key, val),
        () => this.onBalanceEditError(),
        () => this.onBalanceEditRequestComplete(key, callback)
      );
    }
    else {
      this.state.editing.deactivate(val);

      if (typeof callback === "function") {
        callback();
      }
    }
  }

  onBalanceEdited(key, val) {
    this.data.cost.balance[key] = val;

    this.update();
  }

  onBalanceEditError() {
    this.state.error.newMessage("Error updating value! (Server error)", 2, MSG_TIME_ERROR);
  }

  onBalanceEditRequestComplete(key, callback) {
    this.state.editing.deactivate(this.data.cost.balance[key]);

    if (typeof callback === "function") {
      callback();
    }

  }

  addCategory(key, category) {
    this.$thr.append($("	").text(category));
  }

  addTableCell(key, cKey, category) {
    this.$td[key][category] = $("	")
    .addClass("cost")
    .data("val", 0)
    .append($("").addClass("text"));

    if (category === "balance") {
      this.$td[key][category].editable(
        this.api, this.state,
        callback => this.afterBalanceEdit(callback), "cost"
      );
    }

    this.$tr[key].append(this.$td[key][category]);
  }

  addTableRow(key, yearMonth) {
    this.$tr[key] = $("

")
    .toggleClass(
      "past", (yearMonth[0] === this.data.currentYear &&
        yearMonth[1] < this.data.currentMonth) || yearMonth[0] < this.data.currentYear
    ).toggleClass(
      "active", yearMonth[0] === this.data.currentYear &&
        yearMonth[1] === this.data.currentMonth
    ).toggleClass(
      "future", (yearMonth[0] === this.data.currentYear &&
        yearMonth[1] > this.data.currentMonth) || yearMonth[0] > this.data.currentYear
    ).append($("	")
      .addClass("month")
      .text(months[yearMonth[1] - 1] + "-" + yearMonth[0].toString().substring(2))
    ).data("yearMonth", yearMonth);

    this.$td[key] = {};

    this.categories.forEach((category, cKey) => this.addTableCell(key, cKey, category));

    this.$tbody.append(this.$tr[key]);
  }

  addGraphs() {
    this.graphBalance = new GraphBalance({
      width:  500,
      height: 300,
      $cont:  this.$page,
      title:  "balance",
      dataPast: [],
      dataFuture: [],
      startYear:    this.data.startYearMonth[0],
      startMonth:   this.data.startYearMonth[1],
      currentYear:  this.data.currentYear,
      currentMonth: this.data.currentMonth,
      yearMonths:   this.yearMonths,
      pad: [64, 0, 24, 0],
      range: [0, this.yearMonths.length - 1, 0, 0]
    });

    this.graphSpend = new GraphSpend({
      width:  500,
      height: 300,
      $cont:  this.$page,
      title:  "spend",
      data:   this.data.cost,
      pad: [64, 0, 24, 0],
      range: [0, this.yearMonths.length - 1, 0, 0],
      yearMonths: this.yearMonths
    });
  }

  updateCategories(key, cKey, category) {
    this.$td[key][category]
    .data("val", this.data.cost[category][key])
    .css("background-color", getColorFromScore(
      this.colors[category],
      this.scores[category][key],
      this.data.cost[category][key] < 0)
    ).children(".text").html(formatCurrency(this.data.cost[category][key]));
  }

  updateYearMonths(key) {
    this.categories.forEach((category, cKey) => {
      this.updateCategories(key, cKey, category);
    });
  }

  updateGraphs() {
    this.graphBalance.update(this.data.cost.balance, this.data.cost.predicted);
    this.graphSpend.update(this.data.cost);
  }

  update(data) {
    if (data) {
      this.data = data;
    }

    // calculate future values from the averages
    this.calculateFutures();

    // calculate extra columns (out, spent etc.) from raw data
    this.calculateColumns();

    // colour each column according to values
    this.calculateScores();

    // fill the table with values
    this.yearMonths.forEach((yearMonth, key) => this.updateYearMonths(key));

    // update the graphs
    this.updateGraphs();
  }

  processCategory(category) {
    if (this.state.pages[category]) {
      const doneRows = [];

      for (let i = 0; i < this.state.pages[category].data.length; i++) {
        const year  = this.state.pages[category].data[i].date.year;
        const month = this.state.pages[category].data[i].date.month;

        const row = getYearMonthRow(
          this.data.startYearMonth[0], this.data.startYearMonth[1],
          year, month
        );

        const doneRow = doneRows.indexOf(row) > -1;

        if (!doneRow) {
          this.data.cost[category][row] = 0;

          doneRows.push(row);
        }

        if (row > -1 && row < this.data.cost.in.length) {
          this.data.cost[category][row] += this.state.pages[category].data[i].cost;
        }
      }
    }
  }
}

<|endoftext|>


<commit_before><commit_msg>added fixes<commit_after><|endoftext|>


<commit_before>const colors = require('colors');
const util = require('util');
const tty = require('tty');
const charm = require('charm')(process);
const cliSpinners = require('cli-spinners');
const cliCursor = require('cli-cursor');

const PADDING = '  ';

var iterations = 0;
var looper = null;
var running = false;
var items = {};
var defaultPattern = null;
var settings = {
  invert: false,
  interval: 250,
  pattern: null,
  bottom: false
};

var isatty = tty.isatty(1) && tty.isatty(2);
var tty_size = {
  width: isatty
    ? process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[0]
      : tty.getWindowSize()[1]
    : 75,
  height: isatty
    ? process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[1]
      : tty.getWindowSize()[2]
    : 75
};

//
// This is a single item (Or cell or whatever you call it) in the status display
//
var Item = exports.Item = function (options) {
  const defaults = {
    name: null,
    max: null,
    precision: 2
  };
  options = options || {};
  for (var attrname in defaults) {
    this[attrname] = options.hasOwnProperty(attrname) && options[attrname] !== null ? options[attrname] : defaults[attrname];
  }
  if(options.custom) this.custom = options.custom.bind(this);
  this.val = options.count || 0;
};

//
// Item functions for value changes, rendering, etc
//
Item.prototype = {
  inc: function (amount) {
    this.val += (amount !== undefined) ? amount : 1;
  },

  dec: function (amount) {
    this.val -= (amount !== undefined) ? amount : 1;
  },

  render: function (style) {
    switch (style) {
      case 'custom':
        return this.custom ? this.custom() : '';
      case 'percentage':
        if (!this.max) return '';
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        return (100 * this.count / max).toFixed(this.precision) + '%';
        break;
      case 'time':
        return nicetime(this.count);
      case 'bar':
        if (!this.max) return '';
        var bar_len = 10;
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        var done = Math.round(bar_len * this.count / max);
        return '[' + '▒'.repeat(Math.min(bar_len, done)) + '-'.repeat(Math.max(0,bar_len - done)) + ']';
      case 'default':
      case 'count':
      default:
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        return this.count + (max ? '/' + max : '');
    }
  }
};

//
// Getter/setter for count. Auto-rendering, basically.
//
Object.defineProperties(Item.prototype, {
  count: {
    get: function () {
      return this.val;
    },
    set: function (newValue) {
      this.val = newValue;
    }
  }
});

//
// Repeats a string, using it for the status bar instead of loops
//
String.prototype.repeat = function (len) {
  return new Array(len + 1).join(this);
};

//
// Render the status bar row
// If stamp is true, it will console.log it instead of doing an stdout
//
const render = (stamp) => {
  iterations++;
  if (stamp) {
    charm.erase('line').erase('down');
    return console.log(generateBar());
  }
  if (!running) return;

  var color_len = 0;
  for (var i = 0; i < items.length; i++) {
    if (items[i].color) {
      color_len += (items[i].color('')).length;
    }
  }

  var out = ' ' + generateBar();
  var bar = ' '.repeat(tty_size.width);

  if (settings.invert) {
    bar = bar.inverse;
    out = out.inverse;
  }

  var current_height = Math.ceil((out.length - color_len) / tty_size.width );

  charm.position(function (x, y) {
    var current_row = y;

    // If the current cursor row was on the bar, we need to make a gap
    if (settings.bottom && current_row > tty_size.height - current_height) {
      for(var i = 0; i < current_height; i++) {
        // charm.delete('line', 1);
        charm.erase('line');
        write('\n');
      }
      y -= current_height - (tty_size.height - current_row);
    }

    charm
      .move(0, settings.bottom ? tty_size.height : 0)
      .left(tty_size.width)
      .write(bar);

    if(settings.bottom) {
      for(var i = 0; i < Math.max(0, current_height - 1); i++) {
        charm.left(tty_size.width).write(bar).up(1);
      }
    }

    charm
      .left(tty_size.width)
      .write(out)
      .position(x, y);

  });
};

const write = (string) => process.stdout.write(string);

const generateBar = () => {
  var pattern = settings.pattern ? settings.pattern : defaultPattern;
  return pattern.replace(/\{([a-zA-z0-9\s\.]*)\}/g, (match, id) => {
    var tokens = id.split('.');
    var portion = '';
    var color = null;
    var modifier = null;
    if(tokens.length > 1 && colors[tokens[1]]) {
      color = colors[tokens[1]];
      modifier = tokens.length > 2 ? tokens[2] : null;
    } else if (tokens.length > 2 && colors[tokens[2]]) {
      color = colors[tokens[2]];
      modifier = tokens[1];
    } else if(tokens.length > 1) {
      modifier = tokens[1];
    }

    switch (tokens[0]) {
      case 'timestamp':
      case 'uptime':
        portion = nicetime(process.uptime(), true);
        break;
      case 'spinner':
        var spinnerType = modifier || 'dots';
        portion = cliSpinners[spinnerType].frames[iterations % cliSpinners[spinnerType].frames.length];
        break;
      default:
        if(items[tokens[0]]) portion = items[tokens[0]].render(modifier);
        break;
    }
    return color ? color(portion) : portion;
  });
};

//
// Currently just changes the milliseconds to either a number of seconds or number of minutes
//
var nicetime = (ms, use_seconds) => {
  var seconds = (ms / (use_seconds ? 1 : 1000)).toFixed((use_seconds ? 0 : 3));
  var minutes = (seconds / 60).toFixed(3);
  var time = (minutes < 2) ? seconds : minutes;
  return time + (minutes < 2 ?  's' : 'm');
};

process.on('exit', function () {
  if(running) render(true);
});

exports.addItem = (name, options) => {
  if(!name || typeof name !== 'string') return;
  options = options || {};
  options.name = name;
  var item = new Item(options);
  items[name] = item;
  rebuildPattern();
  return items[name];
};

var rebuildPattern = () => {
  defaultPattern = Object.keys(items).reduce((memo, item) => {
    return `${memo}${PADDING}{${item}.label} {${item}}${PADDING}|`;
  }, `Status @ {runtime}${PADDING}|`);
};

exports.removeItem = (item) => delete items[item];
exports.removeAll = () => items = {};
exports.toString = () => generateBar();
exports.clear = () => charm.erase('line').erase('down');

exports.console = function () {
  var methods = {};
  ['log', 'info', 'error', 'warn'].forEach(m => {
    methods[m] = function () {
      if(m !== 'log' || running) exports.clear();
      console[m].apply(this, arguments);
      if(running) render();
    }
  });
  return methods;
};

//
// Turns it on, will start rendering on interval now
//
exports.start = (opts) => {
  settings = Object.assign(settings, opts)
  running = true;
  if(!settings.bottom) cliCursor.hide();
  render();
  looper = setInterval(render, settings.interval);
};

exports.stop = () => {
  running = false;
  clearTimeout(looper);
  cliCursor.show();
  render(true);
  charm.end();
};

//
// Stamps the current status to the console
//
exports.stamp = () => render(true);

//
// Gets the total number of cells in the bar
//
exports.cellCount = () => Object.keys(items).length;
<commit_msg>make addItem return null instead of undefined<commit_after>const colors = require('colors');
const util = require('util');
const tty = require('tty');
const charm = require('charm')(process);
const cliSpinners = require('cli-spinners');
const cliCursor = require('cli-cursor');

const PADDING = '  ';

var iterations = 0;
var looper = null;
var running = false;
var items = {};
var defaultPattern = null;
var settings = {
  invert: false,
  interval: 250,
  pattern: null,
  bottom: false
};

var isatty = tty.isatty(1) && tty.isatty(2);
var tty_size = {
  width: isatty
    ? process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[0]
      : tty.getWindowSize()[1]
    : 75,
  height: isatty
    ? process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[1]
      : tty.getWindowSize()[2]
    : 75
};

//
// This is a single item (Or cell or whatever you call it) in the status display
//
var Item = exports.Item = function (options) {
  const defaults = {
    name: null,
    max: null,
    precision: 2
  };
  options = options || {};
  for (var attrname in defaults) {
    this[attrname] = options.hasOwnProperty(attrname) && options[attrname] !== null ? options[attrname] : defaults[attrname];
  }
  if(options.custom) this.custom = options.custom.bind(this);
  this.val = options.count || 0;
};

//
// Item functions for value changes, rendering, etc
//
Item.prototype = {
  inc: function (amount) {
    this.val += (amount !== undefined) ? amount : 1;
  },

  dec: function (amount) {
    this.val -= (amount !== undefined) ? amount : 1;
  },

  render: function (style) {
    switch (style) {
      case 'custom':
        return this.custom ? this.custom() : '';
      case 'percentage':
        if (!this.max) return '';
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        return (100 * this.count / max).toFixed(this.precision) + '%';
        break;
      case 'time':
        return nicetime(this.count);
      case 'bar':
        if (!this.max) return '';
        var bar_len = 10;
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        var done = Math.round(bar_len * this.count / max);
        return '[' + '▒'.repeat(Math.min(bar_len, done)) + '-'.repeat(Math.max(0,bar_len - done)) + ']';
      case 'default':
      case 'count':
      default:
        var max = typeof this.max == 'function'
                ? this.max()
                : this.max;
        return this.count + (max ? '/' + max : '');
    }
  }
};

//
// Getter/setter for count. Auto-rendering, basically.
//
Object.defineProperties(Item.prototype, {
  count: {
    get: function () {
      return this.val;
    },
    set: function (newValue) {
      this.val = newValue;
    }
  }
});

//
// Repeats a string, using it for the status bar instead of loops
//
String.prototype.repeat = function (len) {
  return new Array(len + 1).join(this);
};

//
// Render the status bar row
// If stamp is true, it will console.log it instead of doing an stdout
//
const render = (stamp) => {
  iterations++;
  if (stamp) {
    charm.erase('line').erase('down');
    return console.log(generateBar());
  }
  if (!running) return;

  var color_len = 0;
  for (var i = 0; i < items.length; i++) {
    if (items[i].color) {
      color_len += (items[i].color('')).length;
    }
  }

  var out = ' ' + generateBar();
  var bar = ' '.repeat(tty_size.width);

  if (settings.invert) {
    bar = bar.inverse;
    out = out.inverse;
  }

  var current_height = Math.ceil((out.length - color_len) / tty_size.width );

  charm.position(function (x, y) {
    var current_row = y;

    // If the current cursor row was on the bar, we need to make a gap
    if (settings.bottom && current_row > tty_size.height - current_height) {
      for(var i = 0; i < current_height; i++) {
        // charm.delete('line', 1);
        charm.erase('line');
        write('\n');
      }
      y -= current_height - (tty_size.height - current_row);
    }

    charm
      .move(0, settings.bottom ? tty_size.height : 0)
      .left(tty_size.width)
      .write(bar);

    if(settings.bottom) {
      for(var i = 0; i < Math.max(0, current_height - 1); i++) {
        charm.left(tty_size.width).write(bar).up(1);
      }
    }

    charm
      .left(tty_size.width)
      .write(out)
      .position(x, y);

  });
};

const write = (string) => process.stdout.write(string);

const generateBar = () => {
  var pattern = settings.pattern ? settings.pattern : defaultPattern;
  return pattern.replace(/\{([a-zA-z0-9\s\.]*)\}/g, (match, id) => {
    var tokens = id.split('.');
    var portion = '';
    var color = null;
    var modifier = null;
    if(tokens.length > 1 && colors[tokens[1]]) {
      color = colors[tokens[1]];
      modifier = tokens.length > 2 ? tokens[2] : null;
    } else if (tokens.length > 2 && colors[tokens[2]]) {
      color = colors[tokens[2]];
      modifier = tokens[1];
    } else if(tokens.length > 1) {
      modifier = tokens[1];
    }

    switch (tokens[0]) {
      case 'timestamp':
      case 'uptime':
        portion = nicetime(process.uptime(), true);
        break;
      case 'spinner':
        var spinnerType = modifier || 'dots';
        portion = cliSpinners[spinnerType].frames[iterations % cliSpinners[spinnerType].frames.length];
        break;
      default:
        if(items[tokens[0]]) portion = items[tokens[0]].render(modifier);
        break;
    }
    return color ? color(portion) : portion;
  });
};

//
// Currently just changes the milliseconds to either a number of seconds or number of minutes
//
var nicetime = (ms, use_seconds) => {
  var seconds = (ms / (use_seconds ? 1 : 1000)).toFixed((use_seconds ? 0 : 3));
  var minutes = (seconds / 60).toFixed(3);
  var time = (minutes < 2) ? seconds : minutes;
  return time + (minutes < 2 ?  's' : 'm');
};

process.on('exit', function () {
  if(running) render(true);
});

exports.addItem = (name, options) => {
  if(!name || typeof name !== 'string') return null;
  options = options || {};
  options.name = name;
  var item = new Item(options);
  items[name] = item;
  rebuildPattern();
  return items[name];
};

var rebuildPattern = () => {
  defaultPattern = Object.keys(items).reduce((memo, item) => {
    return `${memo}${PADDING}{${item}.label} {${item}}${PADDING}|`;
  }, `Status @ {runtime}${PADDING}|`);
};

exports.removeItem = (item) => delete items[item];
exports.removeAll = () => items = {};
exports.toString = () => generateBar();
exports.clear = () => charm.erase('line').erase('down');

exports.console = function () {
  var methods = {};
  ['log', 'info', 'error', 'warn'].forEach(m => {
    methods[m] = function () {
      if(m !== 'log' || running) exports.clear();
      console[m].apply(this, arguments);
      if(running) render();
    }
  });
  return methods;
};

//
// Turns it on, will start rendering on interval now
//
exports.start = (opts) => {
  settings = Object.assign(settings, opts)
  running = true;
  if(!settings.bottom) cliCursor.hide();
  render();
  looper = setInterval(render, settings.interval);
};

exports.stop = () => {
  running = false;
  clearTimeout(looper);
  cliCursor.show();
  render(true);
  charm.end();
};

//
// Stamps the current status to the console
//
exports.stamp = () => render(true);

//
// Gets the total number of cells in the bar
//
exports.cellCount = () => Object.keys(items).length;
<|endoftext|>


<commit_before>import async from 'async';

import ctor from './constructor';
import Talker from './api/talker';
import helper from './helper';

export default class Brex {

  constructor (app) {
    this.pid = app.pluginId;

    this.modules = ctor.object();
    this.talker = new Talker(this.pid);
    this.talker.addListener();

    this.protocol = app.protocol;
    this.host = app.host;
    this.path = app.path;
    this.ptc = app.pathToConfig;
    this.ptm = app.pathToModule;
    this.timeout = app.timeout;
    this.errTimeout = app.errTimeout;

    this.config = this.getConfigurationFromCache(true);
  }

  log () {
    console.log(`Hello ${this.host}`);
  }

  load () {
    console.log('TOE');
    this.log();
    this.loadConfiguration(this.setReady.bind(this));
    this.foo();
  }

  setReady (ready = true) {
    console.log(this);
    this.talker.ready = ready;
    this.talker.cfg = this.config;

    for (propName in this.talker.onReadyCbs) {
      this.talker.onReadyCbs[propName]();
    }
  }

  foo (foo = ['noop', 'console.log(\'noop\');']) {
    this.modules[foo[0]] = ctor.function(foo[1]);
  }

  loadConfiguration (cb) {
    if (this.config.ttl > ctor.number(helper.getCurrentTime())) {
      return this.loadModulesFromCache(cb);
    }
    this.loadConfigurationFromServer(cb);
  }

  loadConfigurationFromServer (cb) {
    this.talker.api.ajax.get({
      url: `${this.protocol}:\/\/${this.host}/${this.ptc}`,
      parse: 'json'
    }, (res) => {
      if (res.err) {
        this.config.ttl = ctor.number(helper.getCurrentTime() + this.errTimeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ctor.string(this.config.ttl));

        return cb(false);
      }

      let newConfiguration = res.value;

      if (!newConfiguration) {
        this.config.ttl = ctor.number(helper.getCurrentTime() + this.errTimeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ctor.string(this.config.ttl));

        return cb();
      }

      let newConfigurationVersion = newConfiguration[0].v;

      if (newConfigurationVersion !== this.config.version) {
        let ttl = ctor.string(helper.getCurrentTime() + this.timeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ttl);
        this.talker.api.localStorage.set(`${this.pid}cfg`, res.value);

        this.config = this.getConfigurationFromCache(false);
        return this.loadModulesFromServer(newConfiguration, cb);
      }

      return this.loadModulesFromCache(cb);
    })
  }

  getConfigurationFromCache (gm) {
    let storedRawCfg = this.talker.api.localStorage.get(`${this.pid}cfg`);
    let defaultCfg = ctor.array(ctor.object({v: 0, k: ''}));
    let storedParsedCfg = helper.parseJson(storedRawCfg);

    if(!storedParsedCfg) {
      storedParsedCfg = defaultCfg;
      this.talker.api.localStorage.set(`${this.pid}cfg`, defaultCfg);
      this.talker.api.localStorage.set(`${this.pid}ttl`, 0);
    }

    let cfg = storedParsedCfg;

    return {
      key: cfg[0].k,
      ttl: ctor.number(this.talker.api.localStorage.get(`${this.pid}ttl`) || 0),
      version: cfg[0].v,
      modules: [],
      raw: cfg
    }
  }

  loadModulesFromServer (cfg, cb) {
    let [key, ...modules]= cfg;
    let urls = [];
    modules.forEach((m) => {
      m.l.forEach((l) => {
        urls.push(l);
      });
    });

    Promise.resolve()
      .then(() => {
        let modulesPromise = urls.map((url) => {
          return this.downloadModule(url);
        });
        return Promise.all(modulesPromise)
          .then((src) => {
            console.log('src', src);
            return cb();
          });
      })
      .catch((err) => {
        console.log('err', err);
        return cb(false);
      })
  }

  downloadModule (url) {
    return new Promise((resolve, reject) => {
      this.talker.api.ajax.get({
        url: this.extractFullUrl(url)
      }, (res)=> {
        if (res.err) {
          return resolve(res.err);
        }
        resolve(res.value);
      });
    })
  }

  loadModulesFromCache (cb) {
    let [key, ...modules]= this.config.raw;

    modules.forEach((moduleCfg) => {
      let moduleList = moduleCfg.l;
      moduleList.forEach((module) => {
        console.log(this.extractFullUrl(module));
      });
    });
    console.log('key', key);
    console.log('modules', modules);
    cb();
  }

  extractFullUrl (url) {
    if (/^https?:\/\//i.test(url)) {
      return url;
    }
    let moduleLocation = (this.ptm ? `${this.ptm}\/${url}` : url);
    return `${this.protocol}:\/\/${this.host}\/${moduleLocation}`;
  }
};
<commit_msg>download modules from server and save it to storage<commit_after>import async from 'async';

import ctor from './constructor';
import Talker from './api/talker';
import helper from './helper';

export default class Brex {

  constructor (app) {
    this.pid = app.pluginId;

    this.modules = ctor.object();
    this.talker = new Talker(this.pid);
    this.talker.addListener();

    this.protocol = app.protocol;
    this.host = app.host;
    this.path = app.path;
    this.ptc = app.pathToConfig;
    this.ptm = app.pathToModule;
    this.timeout = app.timeout;
    this.errTimeout = app.errTimeout;

    this.config = this.getConfigurationFromCache(true);
  }

  log () {
    console.log(`Hello ${this.host}`);
  }

  load () {
    console.log('TOE');
    this.log();
    this.loadConfiguration(this.setReady.bind(this));
    this.foo();
  }

  setReady (ready = true) {
    console.log(this);
    this.talker.ready = ready;
    this.talker.cfg = this.config;

    for (propName in this.talker.onReadyCbs) {
      this.talker.onReadyCbs[propName]();
    }
  }

  foo (foo = ['noop', 'console.log(\'noop\');']) {
    this.modules[foo[0]] = ctor.function(foo[1]);
  }

  loadConfiguration (cb) {
    if (this.config.ttl > ctor.number(helper.getCurrentTime())) {
      return this.loadModulesFromCache(cb);
    }
    this.loadConfigurationFromServer(cb);
  }

  loadConfigurationFromServer (cb) {
    this.talker.api.ajax.get({
      url: `${this.protocol}:\/\/${this.host}/${this.ptc}`,
      parse: 'json'
    }, (res) => {
      if (res.err) {
        this.config.ttl = ctor.number(helper.getCurrentTime() + this.errTimeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ctor.string(this.config.ttl));

        return cb(false);
      }

      let newConfiguration = res.value;

      if (!newConfiguration) {
        this.config.ttl = ctor.number(helper.getCurrentTime() + this.errTimeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ctor.string(this.config.ttl));

        return cb();
      }

      let newConfigurationVersion = newConfiguration[0].v;

      if (newConfigurationVersion !== this.config.version) {
        let ttl = ctor.string(helper.getCurrentTime() + this.timeout);
        this.talker.api.localStorage.set(`${this.pid}ttl`, ttl);
        this.talker.api.localStorage.set(`${this.pid}cfg`, res.value);

        this.config = this.getConfigurationFromCache(false);
        return this.loadModulesFromServer(newConfiguration, cb);
      }

      return this.loadModulesFromCache(cb);
    })
  }

  getConfigurationFromCache (gm) {
    let storedRawCfg = this.talker.api.localStorage.get(`${this.pid}cfg`);
    let defaultCfg = ctor.array(ctor.object({v: 0, k: ''}));
    let storedParsedCfg = helper.parseJson(storedRawCfg);

    if(!storedParsedCfg) {
      storedParsedCfg = defaultCfg;
      this.talker.api.localStorage.set(`${this.pid}cfg`, defaultCfg);
      this.talker.api.localStorage.set(`${this.pid}ttl`, 0);
    }

    let cfg = storedParsedCfg;

    return {
      key: cfg[0].k,
      ttl: ctor.number(this.talker.api.localStorage.get(`${this.pid}ttl`) || 0),
      version: cfg[0].v,
      modules: [],
      raw: cfg
    }
  }

  loadModulesFromServer (cfg, cb) {
    let [key, ...modules]= cfg;
    let urls = [];
    modules.forEach((m) => {
      m.l.forEach((l) => {
        urls.push(l);
      });
    });

    Promise.resolve()
      .then(() => {
        let modulesPromise = urls.map((url) => {
          return this.downloadModule(url);
        });
        return Promise.all(modulesPromise)
          .then((src) => {
            return cb();
          });
      })
      .catch((err) => {
        return cb(false);
      })
  }

  downloadModule (url) {
    return new Promise((resolve, reject) => {
      this.talker.api.ajax.get({
        url: this.extractFullUrl(url)
      }, (res) => {
        if (res.err || (res.value && res.value.status)) {
          return this.saveModuleToStorage(url, ctor.number(helper.getCurrentTime() + this.errTimeout))
            .then((module) => {
              return resolve(module);
            });
        }
        return this.saveModuleToStorage(url, res.value)
          .then((module) => {
            return resolve(module);
          });
      });
    })
  }

  saveModuleToStorage (key, module) {
    if (typeof module === 'string') {
      this.modules[key] = module;
    }

    return Promise.resolve(this.talker.api.localStorage.set(`${this.pid}${key}`, module));
  }

  loadModulesFromCache (cb) {
    let [key, ...modules]= this.config.raw;
    let keys = [];
    modules.forEach((m) => {
      m.l.forEach((l) => {
        keys.push(l);
      });
    });

    keys.forEach((__key) => {
      let module = this.talker.api.localStorage.get(`${this.pid}${__key}`);
      if (Number.isNaN(ctor.number(module)) && typeof module === 'string') {
        this.modules[__key] = module;
      }
    });

    cb();
  }

  extractFullUrl (url) {
    if (/^https?:\/\//i.test(url)) {
      return url;
    }
    let moduleLocation = (this.ptm ? `${this.ptm}\/${url}` : url);
    return `${this.protocol}:\/\/${this.host}\/${moduleLocation}`;
  }
};
<|endoftext|>


<commit_before><commit_msg>parse payload<commit_after><|endoftext|>


<commit_before><commit_msg>fix condition<commit_after><|endoftext|>


<commit_before><commit_msg>preoptimization no caps<commit_after><|endoftext|>


<commit_before>'use strict'

var Transform = require('readable-stream').Transform
var duplexify = require('duplexify')
var WS = require('ws')
var Buffer = require('safe-buffer').Buffer

module.exports = WebSocketStream

function buildProxy (options, socketWrite, socketEnd) {
  var proxy = new Transform({
    objectMode: options.objectMode
  })

  proxy._destroyed = false
  proxy._write = socketWrite
  proxy._flush = socketEnd

  proxy.destroy = function(err) {
    if (this._destroyed) return
      this._destroyed = true

    var self = this
    process.nextTick(function() {
      if (err)
        self.emit('error', err)
      self.emit('close')
    })
  }

  return proxy
}

function WebSocketStream(target, protocols, options) {
  var stream, socket

  var isBrowser = process.title === 'browser'
  var isNative = !!global.WebSocket
  var socketWrite = isBrowser ? socketWriteBrowser : socketWriteNode

  if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) {
    // accept the "options" Object as the 2nd argument
    options = protocols
    protocols = null

    if (typeof options.protocol === 'string' || Array.isArray(options.protocol)) {
      protocols = options.protocol;
    }
  }

  if (!options) options = {}

  if (options.objectMode === undefined) {
    options.objectMode = !(options.binary === true || options.binary === undefined)
  }

  var proxy = buildProxy(options, socketWrite, socketEnd)

  if (!options.objectMode) {
    proxy._writev = writev
  }

  // browser only: sets the maximum socket buffer size before throttling
  var bufferSize = options.browserBufferSize || 1024 * 512

  // browser only: how long to wait when throttling
  var bufferTimeout = options.browserBufferTimeout || 1000

  // use existing WebSocket object that was passed in
  if (typeof target === 'object') {
    socket = target
  // otherwise make a new one
  } else {
    // special constructor treatment for native websockets in browsers, see
    // https://github.com/maxogden/websocket-stream/issues/82
    if (isNative && isBrowser) {
      socket = new WS(target, protocols)
    } else {
      socket = new WS(target, protocols, options)
    }

    socket.binaryType = 'arraybuffer'
  }

  // was already open when passed in
  if (socket.readyState === socket.OPEN) {
    stream = proxy
  } else {
    stream = duplexify.obj()
    socket.onopen = onopen
  }

  stream.socket = socket

  socket.onclose = onclose
  socket.onerror = onerror
  socket.onmessage = onmessage

  proxy.on('close', destroy)

  var coerceToBuffer = !options.objectMode

  function socketWriteNode(chunk, enc, next) {
    // avoid errors, this never happens unless
    // destroy() is called
    if (socket.readyState !== WS.OPEN) {
      next()
      return
    }

    if (coerceToBuffer && typeof chunk === 'string') {
      chunk = new Buffer(chunk, 'utf8')
    }
    socket.send(chunk, next)
  }

  function socketWriteBrowser(chunk, enc, next) {
    if (socket.bufferedAmount > bufferSize) {
      setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next)
      return
    }

    if (coerceToBuffer && typeof chunk === 'string') {
      chunk = new Buffer(chunk, 'utf8')
    }

    try {
      socket.send(chunk)
    } catch(err) {
      return next(err)
    }

    next()
  }

  function socketEnd(done) {
    socket.close()
    done()
  }

  function onopen() {
    stream.setReadable(proxy)
    stream.setWritable(proxy)
    stream.emit('connect')
  }

  function onclose() {
    stream.end()
    stream.destroy()
  }

  function onerror(err) {
    stream.destroy(err)
  }

  function onmessage(event) {
    var data = event.data
    if (data instanceof ArrayBuffer) data = Buffer.from(new Uint8Array(data))
    else data = Buffer.from(data, 'utf8')
    proxy.push(data)
  }

  function destroy() {
    socket.close()
  }

  // this is to be enabled only if objectMode is false
  function writev (chunks, cb) {
    var buffers = new Array(chunks.length)
    for (var i = 0; i < chunks.length; i++) {
      if (typeof chunks[i].chunk === 'string') {
        buffers[i] = Buffer.from(chunks[i], 'utf8')
      } else {
        buffers[i] = chunks[i].chunk
      }
    }

    this._write(Buffer.concat(buffers), 'binary', cb)
  }

  return stream
}
<commit_msg>fixed: WS is null<commit_after>'use strict'

var Transform = require('readable-stream').Transform
var duplexify = require('duplexify')
var WS = require('ws')
var Buffer = require('safe-buffer').Buffer

module.exports = WebSocketStream

function buildProxy (options, socketWrite, socketEnd) {
  var proxy = new Transform({
    objectMode: options.objectMode
  })

  proxy._destroyed = false
  proxy._write = socketWrite
  proxy._flush = socketEnd

  proxy.destroy = function(err) {
    if (this._destroyed) return
      this._destroyed = true

    var self = this
    process.nextTick(function() {
      if (err)
        self.emit('error', err)
      self.emit('close')
    })
  }

  return proxy
}

function WebSocketStream(target, protocols, options) {
  var stream, socket

  var isBrowser = process.title === 'browser'
  var isNative = !!global.WebSocket
  var socketWrite = isBrowser ? socketWriteBrowser : socketWriteNode

  if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) {
    // accept the "options" Object as the 2nd argument
    options = protocols
    protocols = null

    if (typeof options.protocol === 'string' || Array.isArray(options.protocol)) {
      protocols = options.protocol;
    }
  }

  if (!options) options = {}

  if (options.objectMode === undefined) {
    options.objectMode = !(options.binary === true || options.binary === undefined)
  }

  var proxy = buildProxy(options, socketWrite, socketEnd)

  if (!options.objectMode) {
    proxy._writev = writev
  }

  // browser only: sets the maximum socket buffer size before throttling
  var bufferSize = options.browserBufferSize || 1024 * 512

  // browser only: how long to wait when throttling
  var bufferTimeout = options.browserBufferTimeout || 1000

  // use existing WebSocket object that was passed in
  if (typeof target === 'object') {
    socket = target
  // otherwise make a new one
  } else {
    // special constructor treatment for native websockets in browsers, see
    // https://github.com/maxogden/websocket-stream/issues/82
    if (isNative && isBrowser) {
      socket = new WS(target, protocols)
    } else {
      socket = new WS(target, protocols, options)
    }

    socket.binaryType = 'arraybuffer'
  }

  // was already open when passed in
  if (socket.readyState === socket.OPEN) {
    stream = proxy
  } else {
    stream = duplexify.obj()
    socket.onopen = onopen
  }

  stream.socket = socket

  socket.onclose = onclose
  socket.onerror = onerror
  socket.onmessage = onmessage

  proxy.on('close', destroy)

  var coerceToBuffer = !options.objectMode

  function socketWriteNode(chunk, enc, next) {
    // avoid errors, this never happens unless
    // destroy() is called
    if (socket.readyState !== socket.OPEN) {
      next()
      return
    }

    if (coerceToBuffer && typeof chunk === 'string') {
      chunk = new Buffer(chunk, 'utf8')
    }
    socket.send(chunk, next)
  }

  function socketWriteBrowser(chunk, enc, next) {
    if (socket.bufferedAmount > bufferSize) {
      setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next)
      return
    }

    if (coerceToBuffer && typeof chunk === 'string') {
      chunk = new Buffer(chunk, 'utf8')
    }

    try {
      socket.send(chunk)
    } catch(err) {
      return next(err)
    }

    next()
  }

  function socketEnd(done) {
    socket.close()
    done()
  }

  function onopen() {
    stream.setReadable(proxy)
    stream.setWritable(proxy)
    stream.emit('connect')
  }

  function onclose() {
    stream.end()
    stream.destroy()
  }

  function onerror(err) {
    stream.destroy(err)
  }

  function onmessage(event) {
    var data = event.data
    if (data instanceof ArrayBuffer) data = Buffer.from(new Uint8Array(data))
    else data = Buffer.from(data, 'utf8')
    proxy.push(data)
  }

  function destroy() {
    socket.close()
  }

  // this is to be enabled only if objectMode is false
  function writev (chunks, cb) {
    var buffers = new Array(chunks.length)
    for (var i = 0; i < chunks.length; i++) {
      if (typeof chunks[i].chunk === 'string') {
        buffers[i] = Buffer.from(chunks[i], 'utf8')
      } else {
        buffers[i] = chunks[i].chunk
      }
    }

    this._write(Buffer.concat(buffers), 'binary', cb)
  }

  return stream
}
<|endoftext|>


<commit_before>(function(ns) {
  var prevState = ns.prevState || [];
  var currentState = [];
  var domGUID = ns.domGUID = ns.domGUID || 0;
  var nodeTypes = [];

  $('body').contents().each(function processNodes() {
    var selfNode = this;
    var jqThis = $(this);
    var val = "";
    var childs = [];
    var thisUID = -1;

    if(this.domDeltaID) { thisUID = this.domDeltaID; }
    else {
      thisUID = this.domDeltaID = ns.domGUID++;
      val += "ADDED NODE: " + this.domDeltaID + "; ";
    }

    if(nodeTypes.indexOf(this.nodeType) == -1) nodeTypes.push(this.nodeType);

    var tempstyle = window.getComputedStyle(this);
    var style = null;

    // Deep copy the computed style in case the function returns the same object
    if(tempstyle) {
      $.each(tempstyle, function(key, value) {
        style[key] = value;
      });
    }

    if(jqThis.contents().length > 0) {
      // Make sure children are processed first so we can get a UID for each of them
      jqThis.contents().each(processNodes);

      jqThis.contents().each(function() {
        childs.push(this);
      });
    }

    // Record the state of affairs for the node
    currentState[thisUID] = {style: style, children:childs, dom:this};
  });

  console.log("NodeTypes", nodeTypes);
  console.log("Previous State", ns.prevState);
  console.log("Current State", currentState);

  if(prevState.length > 0) {
    var testsRan = 0;
    console.log("Calculating Diff...");
    for(var i=0,end=currentState.length; i!=end; ++i) {
      var dom = currentState[i].dom;
      var style = currentState[i].style;
      var prevStyle = prevState[i].style;

      // Make sure a style was generated for our item in question
      if(style && prevStyle) {
        $.each(style, function(key, value) {
          if(key in prevStyle) {
            if(!(prevStyle[key] === style[key])) {
              console.log("DOM Node:", dom, "Property", key, "PREV: ", prevStyle[key], "NEW: ", style[key]);
            }
          }

          else {
            console.log("DOM Node:", dom, "New Property", key, "NEW: ", style[key]);
          }

          testsRan++;
        });
      }

      $.each(prevStyle, function(key, value) {
        if(!(key in style)) {
          console.log("DOM Node:", dom, "Removed Property", key, "NEW: ", style[key]);
        }
      });
    }
    console.log("Tested " + testsRan + " properties for differences.");
  }

  else {
    console.log("First frame captured, run again after making some changes to see diff");
  }

  ns.prevState = currentState;
  ns.domGUID = domGUID;

})(window.top);
<commit_msg>Added child diffing<commit_after>(function(ns) {
  var prevState = ns.prevState || [];
  var currentState = [];
  var domGUID = ns.domGUID = ns.domGUID || 0;
  var nodeTypes = [];

  $('body').contents().each(function processNodes() {
    var selfNode = this;
    var jqThis = $(this);
    var val = "";
    var childs = [];
    var childIDs = [];
    var thisUID = -1;

    if(this.domDeltaID) { thisUID = this.domDeltaID; }
    else {
      thisUID = this.domDeltaID = ns.domGUID++;
      val += "ADDED NODE: " + this.domDeltaID + "; ";
    }

    if(nodeTypes.indexOf(this.nodeType) == -1) nodeTypes.push(this.nodeType);

    var tempstyle = window.getComputedStyle(this);
    var style = null;

    // Deep copy the computed style in case the function returns the same object
    if(tempstyle) {
      $.each(tempstyle, function(key, value) {
        style[key] = value;
      });
    }

    if(jqThis.contents().length > 0) {
      // Make sure children are processed first so we can get a UID for each of them
      jqThis.contents().each(processNodes);

      jqThis.contents().each(function() {
        childs.push(this);
        childIDs.push(this.domDeltaID);
      });
    }

    // Record the state of affairs for the node
    currentState[thisUID] = {style: style, children:childs, childrenIDs:childIDs, dom:this};
  });

  console.log("NodeTypes", nodeTypes);
  console.log("Previous State", ns.prevState);
  console.log("Current State", currentState);

  if(prevState.length > 0) {
    var testsRan = 0;
    console.log("Calculating Diff...");
    for(var i=0,end=currentState.length; i!=end; ++i) {
      // See if the UID exists in the prev state
      if(!(i in prevState)) {
        console.log("Added element: ", currentState[i]);
        continue;
      }

      var dom = currentState[i].dom;
      var style = currentState[i].style;
      var prevStyle = prevState[i].style;

      // Make sure a style was generated for our item in question
      if(style && prevStyle) {
        $.each(style, function(key, value) {
          if(key in prevStyle) {
            if(!(prevStyle[key] === style[key])) {
              console.log("DOM Node:", dom, "Property", key, "PREV: ", prevStyle[key], "NEW: ", style[key]);
            }
          }

          else {
            console.log("DOM Node:", dom, "New Property", key, "NEW: ", style[key]);
          }

          testsRan++;
        });
      }

      $.each(prevStyle, function(key, value) {
        if(!(key in style)) {
          console.log("DOM Node:", dom, "Removed Property", key, "NEW: ", style[key]);
        }
      });
    }
    console.log("Tested " + testsRan + " properties for differences.");

    // Now diff children additions/removals
    $.each(currentState[i].childrenIDs, function(index, value) {
      if(prevState[i].childrenIDs.indexOf(value) == -1) {
        console.log("Added child to: ", dom, "Child: ", currentState[i].children[value]);
      }
    });

    $.each(prevState[i].childrenIDs, function(index, value) {
      if(currentState[i].childrenIDs.indexOf(value) == -1) {
        console.log("Removed child from: ", dom, "Child: ", prevState[i].children[value]);
      }
    });
  }

  else {
    console.log("First frame captured, run again after making some changes to see diff");
  }

  ns.prevState = currentState;
  ns.domGUID = domGUID;

})(window.top);
<|endoftext|>


<commit_before>'use strict';

var assign = require('object-assign'),
	React = require('react'),
	DaysView = require('./src/DaysView'),
	MonthsView = require('./src/MonthsView'),
	YearsView = require('./src/YearsView'),
	TimeView = require('./src/TimeView'),
	moment = require('moment')
;

var TYPES = React.PropTypes;
var Datetime = React.createClass({
	mixins: [
		require('./src/onClickOutside')
	],
	viewComponents: {
		days: DaysView,
		months: MonthsView,
		years: YearsView,
		time: TimeView
	},
	propTypes: {
		// value: TYPES.object | TYPES.string,
		// defaultValue: TYPES.object | TYPES.string,
		onFocus: TYPES.func,
		onBlur: TYPES.func,
		onChange: TYPES.func,
		locale: TYPES.string,
		utc: TYPES.bool,
		input: TYPES.bool,
		// dateFormat: TYPES.string | TYPES.bool,
		// timeFormat: TYPES.string | TYPES.bool,
		inputProps: TYPES.object,
		timeConstraints: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		isValidDate: TYPES.func,
		open: TYPES.bool,
		strictParsing: TYPES.bool,
		closeOnSelect: TYPES.bool,
		closeOnTab: TYPES.bool
	},

	getDefaultProps: function() {
		var nof = function(){};
		return {
			className: '',
			defaultValue: '',
			inputProps: {},
			input: true,
			onFocus: nof,
			onBlur: nof,
			onChange: nof,
			timeFormat: true,
			timeConstraints: {},
			dateFormat: true,
			strictParsing: true,
			closeOnSelect: false,
			closeOnTab: true,
			utc: false
		};
	},

	getInitialState: function() {
		var state = this.getStateFromProps( this.props );

		if ( state.open === undefined )
			state.open = !this.props.input;

		state.currentView = this.props.dateFormat ? (this.props.viewMode || state.updateOn || 'days') : 'time';

		return state;
	},

	getStateFromProps: function( props ){
		var formats = this.getFormats( props ),
			date = props.value || props.defaultValue,
			selectedDate, viewDate, updateOn, inputValue
		;

		if ( date && typeof date === 'string' )
			selectedDate = this.localMoment( date, formats.datetime );
		else if ( date )
			selectedDate = this.localMoment( date );

		if ( selectedDate && !selectedDate.isValid() )
			selectedDate = null;

		viewDate = selectedDate ?
			selectedDate.clone().startOf('month') :
			this.localMoment().startOf('month')
		;

		updateOn = this.getUpdateOn(formats);

		if ( selectedDate )
			inputValue = selectedDate.format(formats.datetime);
		else if ( date.isValid && !date.isValid() )
			inputValue = '';
		else
			inputValue = date || '';

		return {
			updateOn: updateOn,
			inputFormat: formats.datetime,
			viewDate: viewDate,
			selectedDate: selectedDate,
			inputValue: inputValue,
			open: props.open
		};
	},

	getUpdateOn: function(formats){
		if ( formats.date.match(/[lLD]/) ){
			return 'days';
		}
		else if ( formats.date.indexOf('M') !== -1 ){
			return 'months';
		}
		else if ( formats.date.indexOf('Y') !== -1 ){
			return 'years';
		}

		return 'days';
	},

	getFormats: function( props ){
		var formats = {
				date: props.dateFormat || '',
				time: props.timeFormat || ''
			},
			locale = this.localMoment( props.date ).localeData()
		;

		if ( formats.date === true ){
			formats.date = locale.longDateFormat('L');
		}
		else if ( this.getUpdateOn(formats) !== 'days' ){
			formats.time = '';
		}

		if ( formats.time === true ){
			formats.time = locale.longDateFormat('LT');
		}

		formats.datetime = formats.date && formats.time ?
			formats.date + ' ' + formats.time :
			formats.date || formats.time
		;

		return formats;
	},

	componentWillReceiveProps: function(nextProps) {
		var formats = this.getFormats( nextProps ),
			update = {}
		;

		if ( nextProps.value !== this.props.value ||
            formats.datetime !== this.getFormats( this.props ).datetime ){
            update = this.getStateFromProps( nextProps );
		}

		if ( update.open === undefined ){
			if ( this.props.closeOnSelect && this.state.currentView !== 'time' ){
				update.open = false;
			}
			else {
				update.open = this.state.open;
			}
		}

		this.setState( update );
	},

	onInputChange: function( e ) {
		var value = e.target === null ? e : e.target.value,
			localMoment = this.localMoment( value, this.state.inputFormat ),
			update = { inputValue: value }
		;

		if ( localMoment.isValid() && !this.props.value ) {
			update.selectedDate = localMoment;
			update.viewDate = localMoment.clone().startOf('month');
		}
		else {
			update.selectedDate = null;
		}

		return this.setState( update, function() {
			return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
		});
	},

	onInputKey: function( e ){
		if ( e.which === 9 && this.props.closeOnTab ){
			this.closeCalendar();
		}
	},

	showView: function( view ){
		var me = this;
		return function(){
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ){
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ){
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value'), 10) ).startOf( type ),
				currentView: nextViews[ type ]
			});
		};
	},

	addTime: function( amount, type, toSelected ){
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ){
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ){
		var me = this;

		return function(){
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
	setTime: function( type, value ){
		var index = this.allowedSetTime.indexOf( type ) + 1,
			state = this.state,
			date = (state.selectedDate || state.viewDate).clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		if ( !this.props.value ){
			this.setState({
				selectedDate: date,
				inputValue: date.format( state.inputFormat )
			});
		}
		this.props.onChange( date );
	},

	updateSelectedDate: function( e, close ) {
		var target = e.target,
			modifier = 0,
			viewDate = this.state.viewDate,
			currentDate = this.state.selectedDate || viewDate,
			date
    ;

		if (target.className.indexOf('rdtDay') !== -1){
			if (target.className.indexOf('rdtNew') !== -1)
				modifier = 1;
			else if (target.className.indexOf('rdtOld') !== -1)
				modifier = -1;

			date = viewDate.clone()
				.month( viewDate.month() + modifier )
				.date( parseInt( target.getAttribute('data-value'), 10 ) );
		} else if (target.className.indexOf('rdtMonth') !== -1){
			date = viewDate.clone()
				.month( parseInt( target.getAttribute('data-value'), 10 ) )
				.date( currentDate.date() );
		} else if (target.className.indexOf('rdtYear') !== -1){
			date = viewDate.clone()
				.month( currentDate.month() )
				.date( currentDate.date() )
				.year( parseInt( target.getAttribute('data-value'), 10 ) );
		}

		date.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() );

		if ( !this.props.value ){
			this.setState({
				selectedDate: date,
				viewDate: date.clone().startOf('month'),
				inputValue: date.format( this.state.inputFormat ),
				open: !(this.props.closeOnSelect && close )
			});
		} else {
			if (this.props.closeOnSelect && close) {
				this.closeCalendar();
			}
		}

		this.props.onChange( date );
	},

	openCalendar: function() {
		if (!this.state.open) {
			this.props.onFocus();
			this.setState({ open: true });
		}
	},

	closeCalendar: function() {
		this.setState({ open: false });
		this.props.onBlur( this.state.selectedDate || this.state.inputValue );
	},

	handleClickOutside: function(){
		if ( this.props.input && this.state.open && !this.props.open ){
			this.setState({ open: false });
			this.props.onBlur( this.state.selectedDate || this.state.inputValue );
		}
	},

	localMoment: function( date, format ){
		var momentFn = this.props.utc ? moment.utc : moment;
		var m = momentFn( date, format, this.props.strictParsing );
		if ( this.props.locale )
			m.locale( this.props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear', 'timeConstraints'],
		fromState: ['viewDate', 'selectedDate', 'updateOn'],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment']
	},

	getComponentProps: function(){
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ){
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ){
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ){
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		var Component = this.viewComponents[ this.state.currentView ],
			DOM = React.DOM,
			className = 'rdt' + (this.props.className ?
                  ( Array.isArray( this.props.className ) ?
                  ' ' + this.props.className.join( ' ' ) : ' ' + this.props.className) : ''),
			children = []
		;

		if ( this.props.input ){
			children = [ DOM.input( assign({
				key: 'i',
				type:'text',
				className: 'form-control',
				onFocus: this.openCalendar,
				onChange: this.onInputChange,
				onKeyDown: this.onInputKey,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		} else {
			className += ' rdtStatic';
		}

		if ( this.state.open )
			className += ' rdtOpen';

		return DOM.div({className: className}, children.concat(
			DOM.div(
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( Component, this.getComponentProps())
			)
		));
	}
});

// Make moment accessible through the Datetime class
Datetime.moment = moment;

module.exports = Datetime;
<commit_msg>Fix onFocus() and onBlur() being triggered on inconsistent state<commit_after>'use strict';

var assign = require('object-assign'),
	React = require('react'),
	DaysView = require('./src/DaysView'),
	MonthsView = require('./src/MonthsView'),
	YearsView = require('./src/YearsView'),
	TimeView = require('./src/TimeView'),
	moment = require('moment')
;

var TYPES = React.PropTypes;
var Datetime = React.createClass({
	mixins: [
		require('./src/onClickOutside')
	],
	viewComponents: {
		days: DaysView,
		months: MonthsView,
		years: YearsView,
		time: TimeView
	},
	propTypes: {
		// value: TYPES.object | TYPES.string,
		// defaultValue: TYPES.object | TYPES.string,
		onFocus: TYPES.func,
		onBlur: TYPES.func,
		onChange: TYPES.func,
		locale: TYPES.string,
		utc: TYPES.bool,
		input: TYPES.bool,
		// dateFormat: TYPES.string | TYPES.bool,
		// timeFormat: TYPES.string | TYPES.bool,
		inputProps: TYPES.object,
		timeConstraints: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		isValidDate: TYPES.func,
		open: TYPES.bool,
		strictParsing: TYPES.bool,
		closeOnSelect: TYPES.bool,
		closeOnTab: TYPES.bool
	},

	getDefaultProps: function() {
		var nof = function(){};
		return {
			className: '',
			defaultValue: '',
			inputProps: {},
			input: true,
			onFocus: nof,
			onBlur: nof,
			onChange: nof,
			timeFormat: true,
			timeConstraints: {},
			dateFormat: true,
			strictParsing: true,
			closeOnSelect: false,
			closeOnTab: true,
			utc: false
		};
	},

	getInitialState: function() {
		var state = this.getStateFromProps( this.props );

		if ( state.open === undefined )
			state.open = !this.props.input;

		state.currentView = this.props.dateFormat ? (this.props.viewMode || state.updateOn || 'days') : 'time';

		return state;
	},

	getStateFromProps: function( props ){
		var formats = this.getFormats( props ),
			date = props.value || props.defaultValue,
			selectedDate, viewDate, updateOn, inputValue
		;

		if ( date && typeof date === 'string' )
			selectedDate = this.localMoment( date, formats.datetime );
		else if ( date )
			selectedDate = this.localMoment( date );

		if ( selectedDate && !selectedDate.isValid() )
			selectedDate = null;

		viewDate = selectedDate ?
			selectedDate.clone().startOf('month') :
			this.localMoment().startOf('month')
		;

		updateOn = this.getUpdateOn(formats);

		if ( selectedDate )
			inputValue = selectedDate.format(formats.datetime);
		else if ( date.isValid && !date.isValid() )
			inputValue = '';
		else
			inputValue = date || '';

		return {
			updateOn: updateOn,
			inputFormat: formats.datetime,
			viewDate: viewDate,
			selectedDate: selectedDate,
			inputValue: inputValue,
			open: props.open
		};
	},

	getUpdateOn: function(formats){
		if ( formats.date.match(/[lLD]/) ){
			return 'days';
		}
		else if ( formats.date.indexOf('M') !== -1 ){
			return 'months';
		}
		else if ( formats.date.indexOf('Y') !== -1 ){
			return 'years';
		}

		return 'days';
	},

	getFormats: function( props ){
		var formats = {
				date: props.dateFormat || '',
				time: props.timeFormat || ''
			},
			locale = this.localMoment( props.date ).localeData()
		;

		if ( formats.date === true ){
			formats.date = locale.longDateFormat('L');
		}
		else if ( this.getUpdateOn(formats) !== 'days' ){
			formats.time = '';
		}

		if ( formats.time === true ){
			formats.time = locale.longDateFormat('LT');
		}

		formats.datetime = formats.date && formats.time ?
			formats.date + ' ' + formats.time :
			formats.date || formats.time
		;

		return formats;
	},

	componentWillReceiveProps: function(nextProps) {
		var formats = this.getFormats( nextProps ),
			update = {}
		;

		if ( nextProps.value !== this.props.value ||
            formats.datetime !== this.getFormats( this.props ).datetime ){
            update = this.getStateFromProps( nextProps );
		}

		if ( update.open === undefined ){
			if ( this.props.closeOnSelect && this.state.currentView !== 'time' ){
				update.open = false;
			}
			else {
				update.open = this.state.open;
			}
		}

		this.setState( update );
	},

	onInputChange: function( e ) {
		var value = e.target === null ? e : e.target.value,
			localMoment = this.localMoment( value, this.state.inputFormat ),
			update = { inputValue: value }
		;

		if ( localMoment.isValid() && !this.props.value ) {
			update.selectedDate = localMoment;
			update.viewDate = localMoment.clone().startOf('month');
		}
		else {
			update.selectedDate = null;
		}

		return this.setState( update, function() {
			return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
		});
	},

	onInputKey: function( e ){
		if ( e.which === 9 && this.props.closeOnTab ){
			this.closeCalendar();
		}
	},

	showView: function( view ){
		var me = this;
		return function(){
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ){
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ){
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value'), 10) ).startOf( type ),
				currentView: nextViews[ type ]
			});
		};
	},

	addTime: function( amount, type, toSelected ){
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ){
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ){
		var me = this;

		return function(){
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
	setTime: function( type, value ){
		var index = this.allowedSetTime.indexOf( type ) + 1,
			state = this.state,
			date = (state.selectedDate || state.viewDate).clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		if ( !this.props.value ){
			this.setState({
				selectedDate: date,
				inputValue: date.format( state.inputFormat )
			});
		}
		this.props.onChange( date );
	},

	updateSelectedDate: function( e, close ) {
		var target = e.target,
			modifier = 0,
			viewDate = this.state.viewDate,
			currentDate = this.state.selectedDate || viewDate,
			date
    ;

		if (target.className.indexOf('rdtDay') !== -1){
			if (target.className.indexOf('rdtNew') !== -1)
				modifier = 1;
			else if (target.className.indexOf('rdtOld') !== -1)
				modifier = -1;

			date = viewDate.clone()
				.month( viewDate.month() + modifier )
				.date( parseInt( target.getAttribute('data-value'), 10 ) );
		} else if (target.className.indexOf('rdtMonth') !== -1){
			date = viewDate.clone()
				.month( parseInt( target.getAttribute('data-value'), 10 ) )
				.date( currentDate.date() );
		} else if (target.className.indexOf('rdtYear') !== -1){
			date = viewDate.clone()
				.month( currentDate.month() )
				.date( currentDate.date() )
				.year( parseInt( target.getAttribute('data-value'), 10 ) );
		}

		date.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() );

		if ( !this.props.value ){
			this.setState({
				selectedDate: date,
				viewDate: date.clone().startOf('month'),
				inputValue: date.format( this.state.inputFormat ),
				open: !(this.props.closeOnSelect && close )
			});
		} else {
			if (this.props.closeOnSelect && close) {
				this.closeCalendar();
			}
		}

		this.props.onChange( date );
	},

	openCalendar: function() {
		if (!this.state.open) {
			this.setState({ open: true }, function() {
				this.props.onFocus();
			});
		}
	},

	closeCalendar: function() {
		this.setState({ open: false }, function () {
			this.props.onBlur( this.state.selectedDate || this.state.inputValue );
		});
	},

	handleClickOutside: function(){
		if ( this.props.input && this.state.open && !this.props.open ){
			this.setState({ open: false }, function() {
				this.props.onBlur( this.state.selectedDate || this.state.inputValue );
			});
		}
	},

	localMoment: function( date, format ){
		var momentFn = this.props.utc ? moment.utc : moment;
		var m = momentFn( date, format, this.props.strictParsing );
		if ( this.props.locale )
			m.locale( this.props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear', 'timeConstraints'],
		fromState: ['viewDate', 'selectedDate', 'updateOn'],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment']
	},

	getComponentProps: function(){
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ){
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ){
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ){
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		var Component = this.viewComponents[ this.state.currentView ],
			DOM = React.DOM,
			className = 'rdt' + (this.props.className ?
                  ( Array.isArray( this.props.className ) ?
                  ' ' + this.props.className.join( ' ' ) : ' ' + this.props.className) : ''),
			children = []
		;

		if ( this.props.input ){
			children = [ DOM.input( assign({
				key: 'i',
				type:'text',
				className: 'form-control',
				onFocus: this.openCalendar,
				onChange: this.onInputChange,
				onKeyDown: this.onInputKey,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		} else {
			className += ' rdtStatic';
		}

		if ( this.state.open )
			className += ' rdtOpen';

		return DOM.div({className: className}, children.concat(
			DOM.div(
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( Component, this.getComponentProps())
			)
		));
	}
});

// Make moment accessible through the Datetime class
Datetime.moment = moment;

module.exports = Datetime;
<|endoftext|>


<commit_before>'use strict';

var assign = require('object-assign'),
	React = require('react'),
	DaysView = require('./src/DaysView'),
	MonthsView = require('./src/MonthsView'),
	YearsView = require('./src/YearsView'),
	TimeView = require('./src/TimeView'),
	moment = require('moment')
;

var TYPES = React.PropTypes;
var Datetime = React.createClass({
	mixins: [
		require('react-onclickoutside')
	],
	viewComponents: {
		days: DaysView,
		months: MonthsView,
		years: YearsView,
		time: TimeView
	},
	propTypes: {
		date: TYPES.object,
		onChange: TYPES.func,
		locale: TYPES.string,
		input: TYPES.bool,
		dateFormat: TYPES.string,
		timeFormat: TYPES.string,
		inputProps: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		minDate: TYPES.object,
		maxDate: TYPES.object
	},
	getDefaultProps: function() {

		return {
			date: new Date(),
			viewMode: 'days',
			inputProps: {},
			input: true,
			onChange: function (x) {
				console.log(x);
			}
		};
	},
	getInitialState: function() {
		var formats = this.getFormats( this.props ),
			date = this.props.date
		;
		return {
			currentView: this.props.viewMode,
			open: !this.props.input,
			inputFormat: formats.datetime,
			viewDate: this.localMoment(date).startOf("month"),
			selectedDate: this.localMoment(date),
			inputValue: this.localMoment(date).format( formats.datetime )
		};
	},

	getFormats: function( props ){
		var formats = {
				date: '',
				time: '',
				datetime: ''
			},
			locale = this.localMoment( props.date ).localeData()
		;

		if( props.dateFormat ){
			formats.date = props.dateFormat;
		}
		if( props.timeFormat ){
			formats.time = props.timeFormat;
		}

		if( !formats.date && !formats.time ){
			formats.date = locale.longDateFormat('L');
			formats.time = locale.longDateFormat('LT');
			formats.datetime = formats.date + ' ' + formats.time;
		}
		else {
			if( props.dateFormat ){
				formats.date = props.dateFormat;
				formats.datetime = formats.date;
			}
			if( props.timeFormat ){
				if( formats.date )
					formats.datetime += ' ';
				formats.time = props.timeFormat;
				formats.datetime += formats.time;
			}
		}

		return formats;
	},

	componentWillReceiveProps: function(nextProps) {
		var formats = this.getFormats( nextProps );
		if ( formats.datetime !== this.getFormats(this.props).datetime ) {
			return this.setState({
				inputFormat: nextProps.inputFormat
			});
		}
	},

	onChange: function(event) {
		var value = event.target == null ? event : event.target.value,
			localMoment = this.localMoment( date )
		;

		if (localMoment.isValid()) {
			this.setState({
				selectedDate: localMoment,
				viewDate: localMoment.clone().startOf("month")
			});
		}

		return this.setState({
			inputValue: value
		}, function() {
			return this.props.onChange( localMoment.toDate() );
		});
	},

	showView: function( view ){
		var me = this;
		return function( e ){
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ){
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ){
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.dataset.value) ).startOf( type ),
				currentView: nextViews[ type ]
			});
		};
	},

	addTime: function( amount, type, toSelected ){
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ){
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ){
		var me = this;

		return function(){
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours','minutes','seconds', 'milliseconds'],
	setTime: function( type, value ){
		var index = this.allowedSetTime.indexOf( type ) + 1,
			date = this.state.selectedDate.clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		this.setState({
			selectedDate: date,
			inputValue: date.format( this.state.inputFormat )
		}, this.callOnChange );
	},

	callOnChange: function(){
		this.props.onChange(this.state.selectedDate.format( this.state.inputFormat ));
	},

	updateDate: function( e ) {
		var target = e.target,
			modifier = 0,
			currentDate = this.state.selectedDate,
			date
		;

		if(target.className.indexOf("new") != -1)
			modifier = 1;
		else if(target.className.indexOf("old") != -1)
			modifier = -1;

		date = this.state.viewDate.clone()
			.month( this.state.viewDate.month() + modifier )
			.date( parseInt( target.dataset.value ) )
			.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() )
		;

		this.setState({
			selectedDate: date,
			viewDate: date.clone().startOf('month'),
			inputValue: date.format( this.state.inputFormat )
		});
	},

	openCalendar: function() {
		this.setState({ open: true });
	},

	handleClickOutside: function(){
		if( this.props.input && this.state.open )
			this.setState({ open: false });
	},

	localMoment: function( date ){
		var m = moment( date );
		if( this.props.locale )
			m.locale( this.props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['viewMode', 'minDate', 'maxDate', 'renderDay', 'renderMonth', 'renderYear'],
		fromState: ['viewDate', 'selectedDate' ],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateDate', 'localMoment']
	},

	getComponentProps: function(){
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ){
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ){
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ){
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		var Component = this.viewComponents[ this.state.currentView ],
			DOM = React.DOM,
			className = 'rdt',
			children = []
		;

		if( this.props.input ){
			children = [ DOM.input( assign({
				key: 'i',
				type:'text',
				className:'form-control',
				onFocus: this.openCalendar,
				onChange: this.onChange,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		}
		else {
			className += ' rdtStatic';
		}

		if( this.state.open )
			className += ' rdtOpen';

		return DOM.div({className: className}, children.concat(
			DOM.div(
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( Component, this.getComponentProps())
			)
		));
	}
});

module.exports = Datetime;
<commit_msg>updateDate wasn't calling onChange like setTime was<commit_after>'use strict';

var assign = require('object-assign'),
	React = require('react'),
	DaysView = require('./src/DaysView'),
	MonthsView = require('./src/MonthsView'),
	YearsView = require('./src/YearsView'),
	TimeView = require('./src/TimeView'),
	moment = require('moment')
;

var TYPES = React.PropTypes;
var Datetime = React.createClass({
	mixins: [
		require('react-onclickoutside')
	],
	viewComponents: {
		days: DaysView,
		months: MonthsView,
		years: YearsView,
		time: TimeView
	},
	propTypes: {
		date: TYPES.object,
		onChange: TYPES.func,
		locale: TYPES.string,
		input: TYPES.bool,
		dateFormat: TYPES.string,
		timeFormat: TYPES.string,
		inputProps: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		minDate: TYPES.object,
		maxDate: TYPES.object
	},
	getDefaultProps: function() {

		return {
			date: new Date(),
			viewMode: 'days',
			inputProps: {},
			input: true,
			onChange: function (x) {
				console.log(x);
			}
		};
	},
	getInitialState: function() {
		var formats = this.getFormats( this.props ),
			date = this.props.date
		;
		return {
			currentView: this.props.viewMode,
			open: !this.props.input,
			inputFormat: formats.datetime,
			viewDate: this.localMoment(date).startOf("month"),
			selectedDate: this.localMoment(date),
			inputValue: this.localMoment(date).format( formats.datetime )
		};
	},

	getFormats: function( props ){
		var formats = {
				date: '',
				time: '',
				datetime: ''
			},
			locale = this.localMoment( props.date ).localeData()
		;

		if( props.dateFormat ){
			formats.date = props.dateFormat;
		}
		if( props.timeFormat ){
			formats.time = props.timeFormat;
		}

		if( !formats.date && !formats.time ){
			formats.date = locale.longDateFormat('L');
			formats.time = locale.longDateFormat('LT');
			formats.datetime = formats.date + ' ' + formats.time;
		}
		else {
			if( props.dateFormat ){
				formats.date = props.dateFormat;
				formats.datetime = formats.date;
			}
			if( props.timeFormat ){
				if( formats.date )
					formats.datetime += ' ';
				formats.time = props.timeFormat;
				formats.datetime += formats.time;
			}
		}

		return formats;
	},

	componentWillReceiveProps: function(nextProps) {
		var formats = this.getFormats( nextProps );
		if ( formats.datetime !== this.getFormats(this.props).datetime ) {
			return this.setState({
				inputFormat: nextProps.inputFormat
			});
		}
	},

	onChange: function(event) {
		var value = event.target == null ? event : event.target.value,
			localMoment = this.localMoment( date )
		;

		if (localMoment.isValid()) {
			this.setState({
				selectedDate: localMoment,
				viewDate: localMoment.clone().startOf("month")
			});
		}

		return this.setState({
			inputValue: value
		}, function() {
			return this.props.onChange( localMoment.toDate() );
		});
	},

	showView: function( view ){
		var me = this;
		return function( e ){
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ){
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ){
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.dataset.value) ).startOf( type ),
				currentView: nextViews[ type ]
			});
		};
	},

	addTime: function( amount, type, toSelected ){
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ){
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ){
		var me = this;

		return function(){
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours','minutes','seconds', 'milliseconds'],
	setTime: function( type, value ){
		var index = this.allowedSetTime.indexOf( type ) + 1,
			date = this.state.selectedDate.clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		this.setState({
			selectedDate: date,
			inputValue: date.format( this.state.inputFormat )
		}, this.callOnChange );
	},

	callOnChange: function(){
		this.props.onChange(this.state.selectedDate.format( this.state.inputFormat ));
	},

	updateDate: function( e ) {
		var target = e.target,
			modifier = 0,
			currentDate = this.state.selectedDate,
			date
		;

		if(target.className.indexOf("new") != -1)
			modifier = 1;
		else if(target.className.indexOf("old") != -1)
			modifier = -1;

		date = this.state.viewDate.clone()
			.month( this.state.viewDate.month() + modifier )
			.date( parseInt( target.dataset.value ) )
			.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() )
		;

		this.setState({
			selectedDate: date,
			viewDate: date.clone().startOf('month'),
			inputValue: date.format( this.state.inputFormat )
		}, this.callOnChange );
	},

	openCalendar: function() {
		this.setState({ open: true });
	},

	handleClickOutside: function(){
		if( this.props.input && this.state.open )
			this.setState({ open: false });
	},

	localMoment: function( date ){
		var m = moment( date );
		if( this.props.locale )
			m.locale( this.props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['viewMode', 'minDate', 'maxDate', 'renderDay', 'renderMonth', 'renderYear'],
		fromState: ['viewDate', 'selectedDate' ],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateDate', 'localMoment']
	},

	getComponentProps: function(){
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ){
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ){
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ){
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		var Component = this.viewComponents[ this.state.currentView ],
			DOM = React.DOM,
			className = 'rdt',
			children = []
		;

		if( this.props.input ){
			children = [ DOM.input( assign({
				key: 'i',
				type:'text',
				className:'form-control',
				onFocus: this.openCalendar,
				onChange: this.onChange,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		}
		else {
			className += ' rdtStatic';
		}

		if( this.state.open )
			className += ' rdtOpen';

		return DOM.div({className: className}, children.concat(
			DOM.div(
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( Component, this.getComponentProps())
			)
		));
	}
});

module.exports = Datetime;
<|endoftext|>