<commit_before>/**
 * Display fund data with historical prices etc.
 */

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

import { GRAPH_FUND_HISTORY_WIDTH } from "const";

import { todayDate } from "misc/date";
import { formatCurrency, formatAge, TransactionsList } from "misc/format";
import { arraySum } from "misc/misc";

import { PageList } from "page/list";

import { WorldMap } from "graph/world_map";
import { GraphFundItem, GraphFundHistory } from "graph/fund";
import { StocksList } from "page/funds_stocks";

export class PageFunds extends PageList {
  constructor(options, api, state) {
    super(options, api, state);

    this.query = { history: true }; // tell api to get history data

    this.minDown = 0;
    this.maxUp = 0;

    this.downColor = [255, 44, 44];
    this.upColor = [0, 230, 18];

    this.drawGraphs();

    // world map graph
    this.worldMap = new WorldMap();
    this.$graphs.append(this.worldMap.$elem);
  }

  calculateGain(unitsTxt, priceVal, cost) {
    const units = parseFloat(unitsTxt, 10);
    const price = parseFloat(priceVal[0], 10);
    const lastChange = parseFloat(priceVal[1], 10);
    let pct = 0;
    let gainAbs = 0;
    let value = cost;

    if (!isNaN(units) && !isNaN(price) && cost > 0) {
      value = units * price;
      pct = 100 * (value - cost) / cost;
      gainAbs = value - cost;
    }

    const pctParam = {
      noSymbol: true, suffix: "%", brackets: true, noDivide: true
    };

    const pctFormat = formatCurrency(pct, pctParam);
    const dayGain = formatCurrency(100 * lastChange, pctParam);
    const dayGainClass = lastChange !== 0 ?
      "high " + (lastChange > 0 ? "profit" : "loss") : "";

    const format = {
      raw: false,
      noZeroes: false,
      abbreviate: true,
      brackets: true
    };

    const formatValue = formatCurrency(value, format);
    const formatPrice = price.toFixed(2);
    const formatGain = formatCurrency(gainAbs, format);

    const txt = `
      ${formatValue}

      ${formatPrice}

    ${formatGain}

      ${pctFormat}

      ${dayGain}
    `
    ;

    return { pct, txt };
  }
  addGainText(gain, $span) {
    const color = gain.pct >= 0 ? this.upColor : this.downColor;
    const range = gain.pct >= 0 ? this.maxUp : this.minDown;
    const thisColor = Math.abs(range) > 0
      ? color.map(channel => Math.round(255 + (gain.pct / range) * (channel - 255)))
      : [255, 255, 255];

    return $span
      .toggleClass("profit", gain.pct > 0)
      .toggleClass("loss", gain.pct < 0)
      .toggleClass("high", Math.abs(gain.pct) > 5)
      .css("background-color", "rgb(" + thisColor.join(",") + ")")
      .html(gain.txt);
  }

  setGainRanges(pct) {
    this.minDown = Math.min.apply(null, pct.filter(item => item < 0));
    this.maxUp = Math.max.apply(null, pct.filter(item => item > 0));
  }
  hookCalculate() {
    super.hookCalculate();
    const $list = this.$lbody.children("li:not(.li-add)");
    const gain = [];
    $list.each((i, li) => {
      const id = $(li).data("id");

      // update gain info TODO
      const units = this.$li[id].transactions.data("val").getTotalUnits();
      const price = this.$li[id].transactions.data("price");

      gain.push(this.calculateGain(units, price, this.data[i].cost));
    });

    const pct = gain.map(item => item.pct);
    this.setGainRanges(pct);

    $list.each((i, li) => {
      const id = $(li).data("id");

      const $span = this.addGainText(
        gain[i],
        this.$li[id].gain.children(".text")
      );

      this.$li[id].gain.children(".text").replaceWith($span);
    });
  }
  hookCustomColumns(newItem, newData) {
    const id = newItem.id;
    let lastChange = 0;

    // add a graph column
    const $graph = $("

").addClass("fund-graph-cont");

    const fundIndex = this.history.funds.items.indexOf(newData.i);

    if (fundIndex > -1) {
      const historyWithFund = this.history.history.filter(
        item => item[1].length > fundIndex && item[1][fundIndex] > 0
      );
      const start = historyWithFund[0][1];
      const data = historyWithFund.map(item => {
        return [
          item[0],
          100 * (item[1][fundIndex] - start[fundIndex]) / start[fundIndex]
        ];
      });

      const lastValue = historyWithFund[historyWithFund.length - 1][1][fundIndex];
      const changedValues = historyWithFund.map(item => item[1][fundIndex])
      .filter(value => value !== lastValue).reverse().slice(1);
      if (changedValues.length > 0) {
        lastChange = (lastValue - changedValues[0]) / changedValues[0];
      }

      const fundGraph = new GraphFundItem({
        $cont: $graph,
        width: 100,
        height: 48,
        title: newData.i.toLowerCase().replace(/\W+/g, "-"),
        data
      }, this.api);

      fundGraph.draw();

      this.$li[id].graph = $("").addClass("fund-graph").append($graph);
      this.$lis[id].append(this.$li[id].graph);
    }

    // add a "gain/loss" column
    const units = newData.t.getTotalUnits();
    const price = [newData.P, lastChange];

    this.$li[id].transactions.data("price", price);

    const $gainSpan = this.addGainText(
      this.calculateGain(units, price, newData.c),
      $("").addClass("text")
    );

    this.$li[id].gain = $("").addClass("gain").append($gainSpan);
    this.$lis[id].append(this.$li[id].gain);

    return newItem;
  }
  render() {
    super.render();

    const $gain = $("").addClass("gain");
    this.$gainInfo = $("").addClass("gain-info");
    this.$gainText = $("").addClass("text");

    $gain.append(this.$gainInfo);
    $gain.append(this.$gainText);
    this.$lhead.append($gain);
  }

  hookSwitchToCallback(pageExists) {
    super.hookSwitchToCallback();

    if (pageExists) {
      this.stocksList && this.stocksList.loadStocksList();
    }
  }
  hookDataLoadedBeforeRender(callback, res) {
    super.hookDataLoadedBeforeRender(callback, res);

    const gainPct = res.data.data.map(item => {
      const transactions = new TransactionsList(JSON.parse(item.t));
      const units = transactions.getTotalUnits();
      const cost = transactions.getTotalCost();

      return cost ? 100 * (units * item.P - cost) / cost : 0;
    });
    this.setGainRanges(gainPct);

    this.history = res.data.history;
  }
  hookDataLoadedAfterRender(callback, res) {
    super.hookDataLoadedAfterRender(callback, res);

    if (this.history.history.length > 0) {
      // calculate latest value
      const historyFunds = this.history.funds.items.map((item, key) => {
        return { item, transactions: new TransactionsList(this.history.funds.transactions[key]) };
      });
      const units = historyFunds.map(fund => {
        return fund.transactions.getTotalUnits();
      });
      const lastValue = arraySum(this.history.history[this.history.history.length - 1][1]
      .map((price, fundKey) => {
        return units[fundKey] * price;
      }));

      const profit = lastValue - this.costTotal;
      const profitPct = 100 * profit / this.costTotal;

      const profitLabel = formatCurrency(profit, { brackets: true }) +
      " " + formatCurrency(profitPct, {
        brackets: true, noSymbol: true, suffix: "%", noDivide: true
      });

      const valueTime = this.history.startTime +
        this.history.history[this.history.history.length - 1][0];

      const cacheAge = formatAge((todayDate.getTime() - valueTime * 1000) / 1000);

      this.$gainInfo.html(
        "Current value (" + cacheAge + "): " +
          formatCurrency(lastValue)
      );

      this.$gainText.html(profitLabel)
      .toggleClass("profit", profit > 0)
      .toggleClass("loss", profit < 0);

      // intiate the main fund history graph
      this.graphFundHistory = new GraphFundHistory({
        width:  GRAPH_FUND_HISTORY_WIDTH,
        height: this.pieHeight,
        $cont:  this.$graphs,
        page:   this.page,
        title:  "fund-history",
        data:   this.history.history,
        funds:  historyFunds,
        pad:    [24, 0, 0, 0],
        startTime: this.history.startTime,
        range: [0, 0, 0, 0]
      }, this.api, this.state);

      this.stocksList = new StocksList({ $list: this.$cont, worldMap: this.worldMap }, this.api, this.state);
    }
  }
}

<commit_msg>Recalculate overall fund profits when editing items<commit_after>/**
 * Display fund data with historical prices etc.
 */

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

import { GRAPH_FUND_HISTORY_WIDTH } from "const";

import { todayDate } from "misc/date";
import { formatCurrency, formatAge, TransactionsList } from "misc/format";
import { arraySum } from "misc/misc";

import { PageList } from "page/list";

import { WorldMap } from "graph/world_map";
import { GraphFundItem, GraphFundHistory } from "graph/fund";
import { StocksList } from "page/funds_stocks";

export class PageFunds extends PageList {
  constructor(options, api, state) {
    super(options, api, state);

    this.query = { history: true }; // tell api to get history data

    this.minDown = 0;
    this.maxUp = 0;

    this.downColor = [255, 44, 44];
    this.upColor = [0, 230, 18];

    this.drawGraphs();

    // world map graph
    this.worldMap = new WorldMap();
    this.$graphs.append(this.worldMap.$elem);
  }

  calculateGain(unitsTxt, priceVal, cost) {
    const units = parseFloat(unitsTxt, 10);
    const price = parseFloat(priceVal[0], 10);
    const lastChange = parseFloat(priceVal[1], 10);
    let pct = 0;
    let gainAbs = 0;
    let value = cost;

    if (!isNaN(units) && !isNaN(price) && cost > 0) {
      value = units * price;
      pct = 100 * (value - cost) / cost;
      gainAbs = value - cost;
    }

    const pctParam = {
      noSymbol: true, suffix: "%", brackets: true, noDivide: true
    };

    const pctFormat = formatCurrency(pct, pctParam);
    const dayGain = formatCurrency(100 * lastChange, pctParam);
    const dayGainClass = lastChange !== 0 ?
      "high " + (lastChange > 0 ? "profit" : "loss") : "";

    const format = {
      raw: false,
      noZeroes: false,
      abbreviate: true,
      brackets: true
    };

    const formatValue = formatCurrency(value, format);
    const formatPrice = price.toFixed(2);
    const formatGain = formatCurrency(gainAbs, format);

    const txt = `
      ${formatValue}

      ${formatPrice}

    ${formatGain}

      ${pctFormat}

      ${dayGain}
    `
    ;

    return { pct, txt };
  }
  addGainText(gain, $span) {
    const color = gain.pct >= 0 ? this.upColor : this.downColor;
    const range = gain.pct >= 0 ? this.maxUp : this.minDown;
    const thisColor = Math.abs(range) > 0
      ? color.map(channel => Math.round(255 + (gain.pct / range) * (channel - 255)))
      : [255, 255, 255];

    return $span
      .toggleClass("profit", gain.pct > 0)
      .toggleClass("loss", gain.pct < 0)
      .toggleClass("high", Math.abs(gain.pct) > 5)
      .css("background-color", "rgb(" + thisColor.join(",") + ")")
      .html(gain.txt);
  }

  setGainRanges(pct) {
    this.minDown = Math.min.apply(null, pct.filter(item => item < 0));
    this.maxUp = Math.max.apply(null, pct.filter(item => item > 0));
  }
  calculateOverallGain() {
    const historyFunds = this.history.funds.items.map((item, key) => {
      return { item, transactions: new TransactionsList(this.history.funds.transactions[key]) };
    });
    const units = historyFunds.map(fund => {
      return fund.transactions.getTotalUnits();
    });
    const lastValue = arraySum(this.history.history[this.history.history.length - 1][1]
    .map((price, fundKey) => {
      return units[fundKey] * price;
    }));

    const profit = lastValue - this.costTotal;
    const profitPct = 100 * profit / this.costTotal;

    const profitLabel = formatCurrency(profit, { brackets: true }) +
    " " + formatCurrency(profitPct, {
      brackets: true, noSymbol: true, suffix: "%", noDivide: true
    });

    const valueTime = this.history.startTime +
      this.history.history[this.history.history.length - 1][0];
    const cacheAge = formatAge((todayDate.getTime() - valueTime * 1000) / 1000);

    this.$gainInfo.html(
      "Current value (" + cacheAge + "): " +
        formatCurrency(lastValue)
    );
    this.$gainText.html(profitLabel)
    .toggleClass("profit", profit > 0)
    .toggleClass("loss", profit < 0);

    return historyFunds;
  }
  hookCalculate() {
    super.hookCalculate();
    const $list = this.$lbody.children("li:not(.li-add)");
    const gain = [];
    $list.each((i, li) => {
      const id = $(li).data("id");

      // update gain info TODO
      const units = this.$li[id].transactions.data("val").getTotalUnits();
      const price = this.$li[id].transactions.data("price");

      gain.push(this.calculateGain(units, price, this.data[i].cost));
    });

    const pct = gain.map(item => item.pct);
    this.setGainRanges(pct);

    $list.each((i, li) => {
      const id = $(li).data("id");
      const $span = this.addGainText(
        gain[i],
        this.$li[id].gain.children(".text")
      );

      this.$li[id].gain.children(".text").replaceWith($span);
    });

    this.calculateOverallGain();
  }
  hookCustomColumns(newItem, newData) {
    const id = newItem.id;
    let lastChange = 0;

    // add a graph column
    const $graph = $("

").addClass("fund-graph-cont");

    const fundIndex = this.history.funds.items.indexOf(newData.i);

    if (fundIndex > -1) {
      const historyWithFund = this.history.history.filter(
        item => item[1].length > fundIndex && item[1][fundIndex] > 0
      );
      const start = historyWithFund[0][1];
      const data = historyWithFund.map(item => {
        return [
          item[0],
          100 * (item[1][fundIndex] - start[fundIndex]) / start[fundIndex]
        ];
      });

      const lastValue = historyWithFund[historyWithFund.length - 1][1][fundIndex];
      const changedValues = historyWithFund.map(item => item[1][fundIndex])
      .filter(value => value !== lastValue).reverse().slice(1);
      if (changedValues.length > 0) {
        lastChange = (lastValue - changedValues[0]) / changedValues[0];
      }

      const fundGraph = new GraphFundItem({
        $cont: $graph,
        width: 100,
        height: 48,
        title: newData.i.toLowerCase().replace(/\W+/g, "-"),
        data
      }, this.api);

      fundGraph.draw();

      this.$li[id].graph = $("").addClass("fund-graph").append($graph);
      this.$lis[id].append(this.$li[id].graph);
    }

    // add a "gain/loss" column
    const units = newData.t.getTotalUnits();
    const price = [newData.P, lastChange];

    this.$li[id].transactions.data("price", price);

    const $gainSpan = this.addGainText(
      this.calculateGain(units, price, newData.c),
      $("").addClass("text")
    );

    this.$li[id].gain = $("").addClass("gain").append($gainSpan);
    this.$lis[id].append(this.$li[id].gain);

    return newItem;
  }
  render() {
    super.render();

    const $gain = $("").addClass("gain");
    this.$gainInfo = $("").addClass("gain-info");
    this.$gainText = $("").addClass("text");

    $gain.append(this.$gainInfo);
    $gain.append(this.$gainText);
    this.$lhead.append($gain);
  }

  hookSwitchToCallback(pageExists) {
    super.hookSwitchToCallback();

    if (pageExists) {
      this.stocksList && this.stocksList.loadStocksList();
    }
  }
  hookDataLoadedBeforeRender(callback, res) {
    super.hookDataLoadedBeforeRender(callback, res);

    const gainPct = res.data.data.map(item => {
      const transactions = new TransactionsList(JSON.parse(item.t));
      const units = transactions.getTotalUnits();
      const cost = transactions.getTotalCost();

      return cost ? 100 * (units * item.P - cost) / cost : 0;
    });
    this.setGainRanges(gainPct);

    this.history = res.data.history;
  }
  hookDataLoadedAfterRender(callback, res) {
    super.hookDataLoadedAfterRender(callback, res);

    if (this.history.history.length > 0) {
      // calculate latest value
      const historyFunds = this.calculateOverallGain();

      // intiate the main fund history graph
      this.graphFundHistory = new GraphFundHistory({
        width:  GRAPH_FUND_HISTORY_WIDTH,
        height: this.pieHeight,
        $cont:  this.$graphs,
        page:   this.page,
        title:  "fund-history",
        data:   this.history.history,
        funds:  historyFunds,
        pad:    [24, 0, 0, 0],
        startTime: this.history.startTime,
        range: [0, 0, 0, 0]
      }, this.api, this.state);

      this.stocksList = new StocksList({ $list: this.$cont, worldMap: this.worldMap }, this.api, this.state);
    }
  }
}

<|endoftext|>


<commit_before>if (!Canvas.resize) {
  Canvas.resize = function(relWidth, relHeight) {
    if (!Canvas.configured) {
      console.warn("KAPhy warning - Attempted to resize when not configured");
      return;
    }

    Canvas.relWidth = relWidth;
    Canvas.relHeight = relHeight;

    if (window.innerWidth / window.innerHeight < relWidth / relHeight) {
      Canvas.element.width = window.innerWidth;
      Canvas.element.height = relHeight / relWidth * window.innerWidth;
    } else {
      Canvas.element.width = relWidth / relHeight * window.innerHeight;
      Canvas.element.height = window.innerHeight;
    }
  };
}
<commit_msg>Update resize.js<commit_after>if (!Canvas.resize) {
  Canvas.resize = function(relWidth, relHeight) {
    var envWidth = window.innerWidth;
    var envHeight = window.innerHeight;

    if(!envWidth  || envWidth  === 0) envWidth  = document.documentElement.clientWidth;
    if(!envHeight || envHeight === 0) envHeight = document.documentElement.clientHeight;

    if (!Canvas.configured) {
      console.warn("KAPhy warning - Attempted to resize when not configured");
      return;
    }

    Canvas.relWidth = relWidth;
    Canvas.relHeight = relHeight;

    if (envWidth / envHeight < relWidth / relHeight) {
      Canvas.element.width = envWidth;
      Canvas.element.height = relHeight / relWidth * envWidth;
    } else {
      Canvas.element.width = relWidth / relHeight * envHeight;
      Canvas.element.height = envHeight;
    }
  };
}
<|endoftext|>


<commit_before>/**
 * Created by ogiba on 21.08.2017.
 */

$(document).ready(function () {
    $("#playground").css("padding-top", "" + $(".nav-bar").height() + "px");
});

function allowDrop(ev) {
    ev.preventDefault();
}

function drag(ev) {
    ev.dataTransfer.setData("text", ev.target.id);
}

function makePlace(ev) {
    ev.preventDefault();
}

function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    var targetItem = ev.target;
    var parentList = targetItem.parentNode;

    if (ev.target.parentElement.localName === "ul") {
        var draggedItem = document.getElementById(data);

        ev.target.parentElement.childNodes.forEach(function (item, index) {
            console.log(index);
            if (item.id === targetItem.id) {
                if (draggedItem.parentElement.className === "tree-container") {
                    var label = "";
                    draggedItem.childNodes.forEach(function (item, i) {
                        item.childNodes.forEach(function (childrenItem, j) {
                            if (childrenItem.localName === "label") {
                                label = childrenItem.innerText;
                            }
                        });
                    });
                    draggedItem.innerHTML = label;
                }

                parentList.insertBefore(draggedItem, parentList.childNodes[index]);
            }
        });
    } else {
        var droppedItem = document.getElementById(data);
        var parentElement = ev.target.parentElement;
        if (parentElement.classList[0] !== "draggableTest" &&
            parentElement.localName !== "li" &&
            parentElement.id.indexOf("container_") === -1 &&
            parentElement.childNodes[1].childNodes.length < 2) {

            if (droppedItem.parentElement.localName === "ul")
                rebuildItem(droppedItem)

            if (parentElement.localName === "td" && parentElement.parentElement.localName == "tr") {
                console.log("col:"+parentElement.cellIndex+" row:"+parentElement.parentElement.rowIndex);

                if (parentElement.parentElement.rowIndex == 0) {
                    addNewRow();
                }
            }

            ev.target.appendChild(droppedItem);
        }
    }
}

function rebuildItem(elem) {
    var text = elem.childNodes[0];
    elem.innerHTML = "";

    $("<div/>", {
        id: "container_img_" + elem.id,
        "style": "height: 50%; text-align: center;"
    }).appendTo(elem);

    $("<img>", {
    }).appendTo("#container_img_" + elem.id);

    $("<div/>", {
        id: "container_" + elem.id,
        "style": "height: 50%; line-height: 1;"
    }).appendTo(elem);

    $("<label/>", {
        text: text.data,
        "style": "margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);

    $("<div/>", {
        text: "00.00.0000",
        "style": "font-size: 14px; margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);

    $("<div/>", {
        text: "00.00.0000",
        "style": "font-size: 14px; margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);
}

function addNewRow() {
    var tbody = $(".table").children()[0];
    if (tbody.localName === "tbody") {
        var newRow = $(tbody.children[0].cloneNode(true));

        newRow.prependTo(tbody);
    }
}

function addNewColumn() {
    var tbody = $(".table").children()[0];
    if (tbody.localName === "tbody") {
        tbody.children.forEach(function(item){
            console.log(item);
        });

        // newRow.prependTo(tbody);
    }
}
<commit_msg>Implemented method which add new column when user place person-item in first column<commit_after>/**
 * Created by ogiba on 21.08.2017.
 */

$(document).ready(function () {
    $("#playground").css("padding-top", "" + $(".nav-bar").height() + "px");
});

function allowDrop(ev) {
    ev.preventDefault();
}

function drag(ev) {
    ev.dataTransfer.setData("text", ev.target.id);
}

function makePlace(ev) {
    ev.preventDefault();
}

function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    var targetItem = ev.target;
    var parentList = targetItem.parentNode;

    if (ev.target.parentElement.localName === "ul") {
        var draggedItem = document.getElementById(data);

        ev.target.parentElement.childNodes.forEach(function (item, index) {
            console.log(index);
            if (item.id === targetItem.id) {
                if (draggedItem.parentElement.className === "tree-container") {
                    var label = "";
                    draggedItem.childNodes.forEach(function (item, i) {
                        item.childNodes.forEach(function (childrenItem, j) {
                            if (childrenItem.localName === "label") {
                                label = childrenItem.innerText;
                            }
                        });
                    });
                    draggedItem.innerHTML = label;
                }

                parentList.insertBefore(draggedItem, parentList.childNodes[index]);
            }
        });
    } else {
        var droppedItem = document.getElementById(data);
        var parentElement = ev.target.parentElement;
        if (parentElement.classList[0] !== "draggableTest" &&
            parentElement.localName !== "li" &&
            parentElement.id.indexOf("container_") === -1 &&
            parentElement.childNodes[1].childNodes.length < 2) {

            if (droppedItem.parentElement.localName === "ul")
                rebuildItem(droppedItem)

            if (parentElement.localName === "td" && parentElement.parentElement.localName == "tr") {
                console.log("col:"+parentElement.cellIndex+" row:"+parentElement.parentElement.rowIndex);

                if (parentElement.parentElement.rowIndex == 0) {
                    addNewRow();
                }

                if (parentElement.cellIndex == 0) {
                    addNewColumn()
                }
            }

            ev.target.appendChild(droppedItem);
        }
    }
}

function rebuildItem(elem) {
    var text = elem.childNodes[0];
    elem.innerHTML = "";

    $("<div/>", {
        id: "container_img_" + elem.id,
        "style": "height: 50%; text-align: center;"
    }).appendTo(elem);

    $("<img>", {
    }).appendTo("#container_img_" + elem.id);

    $("<div/>", {
        id: "container_" + elem.id,
        "style": "height: 50%; line-height: 1;"
    }).appendTo(elem);

    $("<label/>", {
        text: text.data,
        "style": "margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);

    $("<div/>", {
        text: "00.00.0000",
        "style": "font-size: 14px; margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);

    $("<div/>", {
        text: "00.00.0000",
        "style": "font-size: 14px; margin-bottom: 0;"
    }).appendTo("#container_" + elem.id);
}

function addNewRow() {
    var tbody = $(".table").children()[0];
    if (tbody.localName === "tbody") {
        var newRow = $(tbody.children[0].cloneNode(true));

        newRow.prependTo(tbody);
    }
}

function addNewColumn() {
    var tbody = $(".table").children()[0];
    if (tbody.localName === "tbody") {
        Array.from(tbody.children).forEach(function(item){
            var clonedCell = $(item.children[0].cloneNode(true));
            clonedCell.prependTo(item);
        });
    }
}
<|endoftext|>


<commit_before>(function() {
	function fontFace(face) {
		if (tinymce.isOpera) {
			return "'" + face + "'";
		} else {
			return face;
		}
	}

	function findContainer(selector) {
		var container;
		if (tinymce.is(selector, 'string')) {
			container = editor.dom.select(selector)[0];
		} else {
			container = selector;
		}
		if (container.firstChild) {
			container = container.firstChild;
		}
		return container;
	}

	function setSelection(startSelector, startOffset, endSelector, endOffset) {
		if (!endSelector) {
			endSelector = startSelector;
			endOffset = startOffset;
		}
		var startContainer = findContainer(startSelector);
		var endContainer = findContainer(endSelector);
		var rng = editor.dom.createRng();

		function setRange(container, offset, start) {
			offset = offset || 0;

			if (offset === 'after') {
				if (start) {
					rng.setStartAfter(container);
				} else {
					rng.setEndAfter(container);
				}
				return;
			} else if (offset === 'afterNextCharacter') {
				container = container.nextSibling;
				offset = 1;
			}
			if (start) {
				rng.setStart(container, offset);
			} else {
				rng.setEnd(container, offset);
			}
		}

		setRange(startContainer, startOffset, true);
		setRange(endContainer, endOffset, false);
		editor.selection.setRng(rng);
	}

	function trimContent(content) {
		return content.replace(/^
 <\/p>\n?/, '').replace(/\n?
 <\/p>$/, '');
	}

	/**
	 * Fakes a key event.
	 *
	 * @param {Element/String} e DOM element object or element id to send fake event to.
	 * @param {String} na Event name to fake like "keydown".
	 * @param {Object} o Optional object with data to send with the event like keyCode and charCode.
	 */
	function fakeKeyEvent(e, na, o) {
		var ev;

		o = tinymce.extend({
			keyCode : 13,
			charCode : 0
		}, o);

		e = tinymce.DOM.get(e);

		if (e.fireEvent) {
			ev = document.createEventObject();
			tinymce.extend(ev, o);
			e.fireEvent('on' + na, ev);
			return;
		}

		if (document.createEvent) {
			try {
				// Fails in Safari
				ev = document.createEvent('KeyEvents');
				ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode);
			} catch (ex) {
				ev = document.createEvent('Events');
				ev.initEvent(na, true, true);

				ev.keyCode = o.keyCode;
				ev.charCode = o.charCode;
			}
		} else {
			ev = document.createEvent('UIEvents');

			if (ev.initUIEvent) {
				ev.initUIEvent(na, true, true, window, 1);
			}

			ev.keyCode = o.keyCode;
			ev.charCode = o.charCode;
		}

		e.dispatchEvent(ev);
	}

	function normalizeRng(rng) {
		if (rng.startContainer.nodeType == 3) {
			if (rng.startOffset === 0) {
				rng.setStartBefore(rng.startContainer);
			} else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) {
				rng.setStartAfter(rng.startContainer);
			}
		}

		if (rng.endContainer.nodeType == 3) {
			if (rng.endOffset === 0) {
				rng.setEndBefore(rng.endContainer);
			} else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) {
				rng.setEndAfter(rng.endContainer);
			}
		}

		return rng;
	}

	// TODO: Replace this with the new event logic in 3.5
	function type(chr) {
		var editor = tinymce.activeEditor, keyCode, charCode, evt, startElm, rng;

		function fakeEvent(target, type, evt) {
			editor.dom.fire(target, type, evt);
		}

		// Numeric keyCode
		if (typeof(chr) == "number") {
			charCode = keyCode = chr;
		} else if (typeof(chr) == "string") {
			// String value
			if (chr == '\b') {
				keyCode = 8;
				charCode = chr.charCodeAt(0);
			} else if (chr == '\n') {
				keyCode = 13;
				charCode = chr.charCodeAt(0);
			} else {
				charCode = chr.charCodeAt(0);
				keyCode = charCode;
			}
		} else {
			evt = chr;

			if (evt.charCode) {
				chr = String.fromCharCode(evt.charCode);
			}

			if (evt.keyCode) {
				keyCode = evt.keyCode;
			}
		}

		evt = evt || {keyCode: keyCode, charCode: charCode};

		startElm = editor.selection.getStart();
		fakeEvent(startElm, 'keydown', evt);
		fakeEvent(startElm, 'keypress', evt);

		if (!evt.isDefaultPrevented()) {
			if (keyCode == 8) {
				if (editor.getDoc().selection) {
					rng = editor.getDoc().selection.createRange();

					if (rng.text.length === 0) {
						rng.moveStart('character', -1);
						rng.select();
					}

					rng.execCommand('Delete', false, null);
				} else {
					rng = editor.selection.getRng();

					if (rng.startContainer.nodeType == 1 && rng.collapsed) {
						var nodes = rng.startContainer.childNodes, lastNode = nodes[nodes.length - 1];

						// If caret is at
abc|
 and after the abc text node then move it to the end of the text node
						// Expand the range to include the last char
ab[c]
 since IE 11 doesn't delete otherwise
						if (rng.startOffset >= nodes.length - 1 && lastNode && lastNode.nodeType == 3 && lastNode.data.length > 0) {
							rng.setStart(lastNode, lastNode.data.length - 1);
							rng.setEnd(lastNode, lastNode.data.length);
							editor.selection.setRng(rng);
						}
					}

					editor.getDoc().execCommand('Delete', false, null);
				}
			} else if (typeof(chr) == 'string') {
				rng = editor.selection.getRng(true);

				if (rng.startContainer.nodeType == 3 && rng.collapsed) {
					rng.startContainer.insertData(rng.startOffset, chr);
					rng.setStart(rng.startContainer, rng.startOffset + 1);
					rng.collapse(true);
					editor.selection.setRng(rng);
				} else {
					rng.insertNode(editor.getDoc().createTextNode(chr));
				}
			}
		}

		fakeEvent(startElm, 'keyup', evt);
	}

	function cleanHtml(html) {
		html = html.toLowerCase().replace(/[\r\n]+/gi, '');
		html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, '');
		html = html.replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|
]+data-mce-bogus[^>]+><\/div>/gi, '');
		html = html.replace(/ style="([^"]+)"/gi, function(val1, val2) {
			val2 = val2.replace(/;$/, '');
			return ' style="' + val2.replace(/\:([^ ])/g, ': $1') + ';"';
		});

		return html;
	}

	function normalizeHtml(html) {
		var writer = new tinymce.html.Writer();

		new tinymce.html.SaxParser({
			validate: false,
			comment: writer.comment,
			cdata: writer.cdata,
			text: writer.text,
			end: writer.end,
			pi: writer.pi,
			doctype: writer.doctype,

			start: function(name, attrs, empty) {
				attrs.sort(function(a, b) {
					if (a.name === b.name) {
						return 0;
					}

					return a.name > b.name ? 1 : -1;
				});

				writer.start(name, attrs, empty);
			}
		}).parse(html);

		return writer.getContent();
	}

	/**
	 * Measures the x, y, w, h of the specified element/control relative to the view element.
	 */
	function rect(ctrl) {
		var outerRect, innerRect;

		if (ctrl.nodeType) {
			innerRect = ctrl.getBoundingClientRect();
		} else {
			innerRect = ctrl.getEl().getBoundingClientRect();
		}

		outerRect = document.getElementById('view').getBoundingClientRect();

		return [
			Math.round(innerRect.left - outerRect.left),
			Math.round(innerRect.top - outerRect.top),
			Math.round(innerRect.right - innerRect.left),
			Math.round(innerRect.bottom - innerRect.top)
		];
	}

	function size(ctrl) {
		return rect(ctrl).slice(2);
	}

	function resetScroll(elm) {
		elm.scrollTop = 0;
		elm.scrollLeft = 0;
	}

	// Needed since fonts render differently on different platforms
	function nearlyEqualRects(rect1, rect2, diff) {
		diff = diff || 1;

		for (var i = 0; i < 4; i++) {
			if (Math.abs(rect1[i] - rect2[i]) > diff) {
				deepEqual(rect1, rect2);
				return;
			}
		}

		ok(true);
	}

	function getFrontmostWindow() {
		return editor.windowManager.windows[editor.windowManager.windows.length - 1];
	}

	function pressArrowKey(evt) {
		var dom = editor.dom, target = editor.selection.getNode();

		evt = tinymce.extend({keyCode: 37}, evt);

		dom.fire(target, 'keydown', evt);
		dom.fire(target, 'keypress', evt);
		dom.fire(target, 'keyup', evt);
	}

	function pressEnter(evt) {
		var dom = editor.dom, target = editor.selection.getNode();

		evt = tinymce.extend({keyCode: 13}, evt);

		dom.fire(target, 'keydown', evt);
		dom.fire(target, 'keypress', evt);
		dom.fire(target, 'keyup', evt);
	}

	function trimBrsOnIE(html) {
		return html.replace(/
]*>/gi, '');
	}

	function patch(proto, name, patchFunc) {
		var originalFunc = proto[name];
		var originalFuncs = proto.__originalFuncs;

		if (!originalFuncs) {
			proto.__originalFuncs = originalFuncs = {};
		}

		if (!originalFuncs[name]) {
			originalFuncs[name] = originalFunc;
		} else {
			originalFunc = originalFuncs[name];
		}

		proto[name] = function() {
			var args = Array.prototype.slice.call(arguments);
			args.unshift(originalFunc);
			return patchFunc.apply(this, args);
		};
	}

	function unpatch(proto, name) {
		var originalFuncs = proto.__originalFuncs;

		if (!originalFuncs) {
			return;
		}

		if (name) {
			proto[name] = originalFuncs[name];
			delete originalFuncs[name];
		} else {
			for (var key in originalFuncs) {
				proto[key] = originalFuncs[key];
			}

			delete proto.__originalFuncs;
		}
	}

	function triggerElementChange(element){
		var evt;

		if ("createEvent" in document) {
			evt = document.createEvent("HTMLEvents");
			evt.initEvent("change", false, true);
			element.dispatchEvent(evt);
		} else {
			element.fireEvent("onchange");
		}
	}

	window.Utils = {
		fontFace: fontFace,
		findContainer: findContainer,
		setSelection: setSelection,
		trimContent: trimContent,
		fakeKeyEvent: fakeKeyEvent,
		normalizeRng: normalizeRng,
		type: type,
		cleanHtml: cleanHtml,
		normalizeHtml: normalizeHtml,
		rect: rect,
		size: size,
		resetScroll: resetScroll,
		nearlyEqualRects: nearlyEqualRects,
		getFrontmostWindow: getFrontmostWindow,
		pressArrowKey: pressArrowKey,
		pressEnter: pressEnter,
		trimBrsOnIE: trimBrsOnIE,
		patch: patch,
		unpatch: unpatch,
		triggerElementChange: triggerElementChange
	};
})();
<commit_msg>Rewrote type to produce proper key codes based on US standard keyboards.<commit_after>(function() {
	function fontFace(face) {
		if (tinymce.isOpera) {
			return "'" + face + "'";
		} else {
			return face;
		}
	}

	function findContainer(selector) {
		var container;
		if (tinymce.is(selector, 'string')) {
			container = editor.dom.select(selector)[0];
		} else {
			container = selector;
		}
		if (container.firstChild) {
			container = container.firstChild;
		}
		return container;
	}

	function setSelection(startSelector, startOffset, endSelector, endOffset) {
		if (!endSelector) {
			endSelector = startSelector;
			endOffset = startOffset;
		}
		var startContainer = findContainer(startSelector);
		var endContainer = findContainer(endSelector);
		var rng = editor.dom.createRng();

		function setRange(container, offset, start) {
			offset = offset || 0;

			if (offset === 'after') {
				if (start) {
					rng.setStartAfter(container);
				} else {
					rng.setEndAfter(container);
				}
				return;
			} else if (offset === 'afterNextCharacter') {
				container = container.nextSibling;
				offset = 1;
			}
			if (start) {
				rng.setStart(container, offset);
			} else {
				rng.setEnd(container, offset);
			}
		}

		setRange(startContainer, startOffset, true);
		setRange(endContainer, endOffset, false);
		editor.selection.setRng(rng);
	}

	function trimContent(content) {
		return content.replace(/^
 <\/p>\n?/, '').replace(/\n?
 <\/p>$/, '');
	}

	/**
	 * Fakes a key event.
	 *
	 * @param {Element/String} e DOM element object or element id to send fake event to.
	 * @param {String} na Event name to fake like "keydown".
	 * @param {Object} o Optional object with data to send with the event like keyCode and charCode.
	 */
	function fakeKeyEvent(e, na, o) {
		var ev;

		o = tinymce.extend({
			keyCode : 13,
			charCode : 0
		}, o);

		e = tinymce.DOM.get(e);

		if (e.fireEvent) {
			ev = document.createEventObject();
			tinymce.extend(ev, o);
			e.fireEvent('on' + na, ev);
			return;
		}

		if (document.createEvent) {
			try {
				// Fails in Safari
				ev = document.createEvent('KeyEvents');
				ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode);
			} catch (ex) {
				ev = document.createEvent('Events');
				ev.initEvent(na, true, true);

				ev.keyCode = o.keyCode;
				ev.charCode = o.charCode;
			}
		} else {
			ev = document.createEvent('UIEvents');

			if (ev.initUIEvent) {
				ev.initUIEvent(na, true, true, window, 1);
			}

			ev.keyCode = o.keyCode;
			ev.charCode = o.charCode;
		}

		e.dispatchEvent(ev);
	}

	function normalizeRng(rng) {
		if (rng.startContainer.nodeType == 3) {
			if (rng.startOffset === 0) {
				rng.setStartBefore(rng.startContainer);
			} else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) {
				rng.setStartAfter(rng.startContainer);
			}
		}

		if (rng.endContainer.nodeType == 3) {
			if (rng.endOffset === 0) {
				rng.setEndBefore(rng.endContainer);
			} else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) {
				rng.setEndAfter(rng.endContainer);
			}
		}

		return rng;
	}

	// TODO: Replace this with the new event logic in 3.5
	function type(chr) {
		var editor = tinymce.activeEditor, keyCode, charCode, evt, startElm, rng;

		function charCodeToKeyCode(charCode) {
			var lookup = {
				'0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57,'a': 65, 'b': 66, 'c': 67,
				'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73, 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81,
				'r': 82, 's': 83, 't': 84, 'u': 85,	'v': 86, 'w': 87, 'x': 88, 'y': 89, ' ': 32, ',': 188, '-': 189, '.': 190, '/': 191, '\\': 220,
				'[': 219, ']': 221, '\'': 222, ';': 186, '=': 187, ')': 41
			};

			return lookup[String.fromCharCode(charCode)];
		}

		function fakeEvent(target, type, evt) {
			editor.dom.fire(target, type, evt);
		}

		// Numeric keyCode
		if (typeof(chr) == "number") {
			charCode = chr;
			keyCode = charCodeToKeyCode(charCode);
		} else if (typeof(chr) == "string") {
			// String value
			if (chr == '\b') {
				keyCode = 8;
				charCode = chr.charCodeAt(0);
			} else if (chr == '\n') {
				keyCode = 13;
				charCode = chr.charCodeAt(0);
			} else {
				charCode = chr.charCodeAt(0);
				keyCode = charCodeToKeyCode(charCode);
			}
		} else {
			evt = chr;

			if (evt.charCode) {
				chr = String.fromCharCode(evt.charCode);
			}

			if (evt.keyCode) {
				keyCode = evt.keyCode;
			}
		}

		evt = evt || {keyCode: keyCode, charCode: charCode};

		startElm = editor.selection.getStart();
		fakeEvent(startElm, 'keydown', evt);
		fakeEvent(startElm, 'keypress', evt);

		if (!evt.isDefaultPrevented()) {
			if (keyCode == 8) {
				if (editor.getDoc().selection) {
					rng = editor.getDoc().selection.createRange();

					if (rng.text.length === 0) {
						rng.moveStart('character', -1);
						rng.select();
					}

					rng.execCommand('Delete', false, null);
				} else {
					rng = editor.selection.getRng();

					if (rng.startContainer.nodeType == 1 && rng.collapsed) {
						var nodes = rng.startContainer.childNodes, lastNode = nodes[nodes.length - 1];

						// If caret is at
abc|
 and after the abc text node then move it to the end of the text node
						// Expand the range to include the last char
ab[c]
 since IE 11 doesn't delete otherwise
						if (rng.startOffset >= nodes.length - 1 && lastNode && lastNode.nodeType == 3 && lastNode.data.length > 0) {
							rng.setStart(lastNode, lastNode.data.length - 1);
							rng.setEnd(lastNode, lastNode.data.length);
							editor.selection.setRng(rng);
						}
					}

					editor.getDoc().execCommand('Delete', false, null);
				}
			} else if (typeof(chr) == 'string') {
				rng = editor.selection.getRng(true);

				if (rng.startContainer.nodeType == 3 && rng.collapsed) {
					rng.startContainer.insertData(rng.startOffset, chr);
					rng.setStart(rng.startContainer, rng.startOffset + 1);
					rng.collapse(true);
					editor.selection.setRng(rng);
				} else {
					rng.insertNode(editor.getDoc().createTextNode(chr));
				}
			}
		}

		fakeEvent(startElm, 'keyup', evt);
	}

	function cleanHtml(html) {
		html = html.toLowerCase().replace(/[\r\n]+/gi, '');
		html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, '');
		html = html.replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|
]+data-mce-bogus[^>]+><\/div>/gi, '');
		html = html.replace(/ style="([^"]+)"/gi, function(val1, val2) {
			val2 = val2.replace(/;$/, '');
			return ' style="' + val2.replace(/\:([^ ])/g, ': $1') + ';"';
		});

		return html;
	}

	function normalizeHtml(html) {
		var writer = new tinymce.html.Writer();

		new tinymce.html.SaxParser({
			validate: false,
			comment: writer.comment,
			cdata: writer.cdata,
			text: writer.text,
			end: writer.end,
			pi: writer.pi,
			doctype: writer.doctype,

			start: function(name, attrs, empty) {
				attrs.sort(function(a, b) {
					if (a.name === b.name) {
						return 0;
					}

					return a.name > b.name ? 1 : -1;
				});

				writer.start(name, attrs, empty);
			}
		}).parse(html);

		return writer.getContent();
	}

	/**
	 * Measures the x, y, w, h of the specified element/control relative to the view element.
	 */
	function rect(ctrl) {
		var outerRect, innerRect;

		if (ctrl.nodeType) {
			innerRect = ctrl.getBoundingClientRect();
		} else {
			innerRect = ctrl.getEl().getBoundingClientRect();
		}

		outerRect = document.getElementById('view').getBoundingClientRect();

		return [
			Math.round(innerRect.left - outerRect.left),
			Math.round(innerRect.top - outerRect.top),
			Math.round(innerRect.right - innerRect.left),
			Math.round(innerRect.bottom - innerRect.top)
		];
	}

	function size(ctrl) {
		return rect(ctrl).slice(2);
	}

	function resetScroll(elm) {
		elm.scrollTop = 0;
		elm.scrollLeft = 0;
	}

	// Needed since fonts render differently on different platforms
	function nearlyEqualRects(rect1, rect2, diff) {
		diff = diff || 1;

		for (var i = 0; i < 4; i++) {
			if (Math.abs(rect1[i] - rect2[i]) > diff) {
				deepEqual(rect1, rect2);
				return;
			}
		}

		ok(true);
	}

	function getFrontmostWindow() {
		return editor.windowManager.windows[editor.windowManager.windows.length - 1];
	}

	function pressArrowKey(evt) {
		var dom = editor.dom, target = editor.selection.getNode();

		evt = tinymce.extend({keyCode: 37}, evt);

		dom.fire(target, 'keydown', evt);
		dom.fire(target, 'keypress', evt);
		dom.fire(target, 'keyup', evt);
	}

	function pressEnter(evt) {
		var dom = editor.dom, target = editor.selection.getNode();

		evt = tinymce.extend({keyCode: 13}, evt);

		dom.fire(target, 'keydown', evt);
		dom.fire(target, 'keypress', evt);
		dom.fire(target, 'keyup', evt);
	}

	function trimBrsOnIE(html) {
		return html.replace(/
]*>/gi, '');
	}

	function patch(proto, name, patchFunc) {
		var originalFunc = proto[name];
		var originalFuncs = proto.__originalFuncs;

		if (!originalFuncs) {
			proto.__originalFuncs = originalFuncs = {};
		}

		if (!originalFuncs[name]) {
			originalFuncs[name] = originalFunc;
		} else {
			originalFunc = originalFuncs[name];
		}

		proto[name] = function() {
			var args = Array.prototype.slice.call(arguments);
			args.unshift(originalFunc);
			return patchFunc.apply(this, args);
		};
	}

	function unpatch(proto, name) {
		var originalFuncs = proto.__originalFuncs;

		if (!originalFuncs) {
			return;
		}

		if (name) {
			proto[name] = originalFuncs[name];
			delete originalFuncs[name];
		} else {
			for (var key in originalFuncs) {
				proto[key] = originalFuncs[key];
			}

			delete proto.__originalFuncs;
		}
	}

	function triggerElementChange(element){
		var evt;

		if ("createEvent" in document) {
			evt = document.createEvent("HTMLEvents");
			evt.initEvent("change", false, true);
			element.dispatchEvent(evt);
		} else {
			element.fireEvent("onchange");
		}
	}

	window.Utils = {
		fontFace: fontFace,
		findContainer: findContainer,
		setSelection: setSelection,
		trimContent: trimContent,
		fakeKeyEvent: fakeKeyEvent,
		normalizeRng: normalizeRng,
		type: type,
		cleanHtml: cleanHtml,
		normalizeHtml: normalizeHtml,
		rect: rect,
		size: size,
		resetScroll: resetScroll,
		nearlyEqualRects: nearlyEqualRects,
		getFrontmostWindow: getFrontmostWindow,
		pressArrowKey: pressArrowKey,
		pressEnter: pressEnter,
		trimBrsOnIE: trimBrsOnIE,
		patch: patch,
		unpatch: unpatch,
		triggerElementChange: triggerElementChange
	};
})();
<|endoftext|>


<commit_before>/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ScrollMode, SpreadMode } from './base_viewer';
import { CursorTool } from './pdf_cursor_tools';
import { PDFSinglePageViewer } from './pdf_single_page_viewer';
import { SCROLLBAR_PADDING } from './ui_utils';

/**
 * @typedef {Object} SecondaryToolbarOptions
 * @property {HTMLDivElement} toolbar - Container for the secondary toolbar.
 * @property {HTMLButtonElement} toggleButton - Button to toggle the visibility
 *   of the secondary toolbar.
 * @property {HTMLDivElement} toolbarButtonContainer - Container where all the
 *   toolbar buttons are placed. The maximum height of the toolbar is controlled
 *   dynamically by adjusting the 'max-height' CSS property of this DOM element.
 * @property {HTMLButtonElement} presentationModeButton - Button for entering
 *   presentation mode.
 * @property {HTMLButtonElement} openFileButton - Button to open a file.
 * @property {HTMLButtonElement} printButton - Button to print the document.
 * @property {HTMLButtonElement} downloadButton - Button to download the
 *   document.
 * @property {HTMLLinkElement} viewBookmarkButton - Button to obtain a bookmark
 *   link to the current location in the document.
 * @property {HTMLButtonElement} firstPageButton - Button to go to the first
 *   page in the document.
 * @property {HTMLButtonElement} lastPageButton - Button to go to the last page
 *   in the document.
 * @property {HTMLButtonElement} pageRotateCwButton - Button to rotate the pages
 *   clockwise.
 * @property {HTMLButtonElement} pageRotateCcwButton - Button to rotate the
 *   pages counterclockwise.
 * @property {HTMLButtonElement} cursorSelectToolButton - Button to enable the
 *   select tool.
 * @property {HTMLButtonElement} cursorHandToolButton - Button to enable the
 *   hand tool.
 * @property {HTMLButtonElement} documentPropertiesButton - Button for opening
 *   the document properties dialog.
 */

class SecondaryToolbar {
  /**
   * @param {SecondaryToolbarOptions} options
   * @param {HTMLDivElement} mainContainer
   * @param {EventBus} eventBus
   */
  constructor(options, mainContainer, eventBus) {
    this.toolbar = options.toolbar;
    this.toggleButton = options.toggleButton;
    this.toolbarButtonContainer = options.toolbarButtonContainer;
    this.buttons = [
      { element: options.presentationModeButton, eventName: 'presentationmode',
        close: true, },
      { element: options.openFileButton, eventName: 'openfile', close: true, },
      { element: options.printButton, eventName: 'print', close: true, },
      { element: options.downloadButton, eventName: 'download', close: true, },
      { element: options.viewBookmarkButton, eventName: null, close: true, },
      { element: options.firstPageButton, eventName: 'firstpage',
        close: true, },
      { element: options.lastPageButton, eventName: 'lastpage', close: true, },
      { element: options.pageRotateCwButton, eventName: 'rotatecw',
        close: false, },
      { element: options.pageRotateCcwButton, eventName: 'rotateccw',
        close: false, },
      { element: options.cursorSelectToolButton, eventName: 'switchcursortool',
        eventDetails: { tool: CursorTool.SELECT, }, close: true, },
      { element: options.cursorHandToolButton, eventName: 'switchcursortool',
        eventDetails: { tool: CursorTool.HAND, }, close: true, },
      { element: options.scrollVerticalButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.VERTICAL, }, close: true, },
      { element: options.scrollHorizontalButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.HORIZONTAL, }, close: true, },
      { element: options.scrollWrappedButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.WRAPPED, }, close: true, },
      { element: options.spreadNoneButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.NONE, }, close: true, },
      { element: options.spreadOddButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.ODD, }, close: true, },
      { element: options.spreadEvenButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.EVEN, }, close: true, },
      { element: options.documentPropertiesButton,
        eventName: 'documentproperties', close: true, },
    ];
    this.items = {
      firstPage: options.firstPageButton,
      lastPage: options.lastPageButton,
      pageRotateCw: options.pageRotateCwButton,
      pageRotateCcw: options.pageRotateCcwButton,
    };

    this.mainContainer = mainContainer;
    this.eventBus = eventBus;

    this.opened = false;
    this.containerHeight = null;
    this.previousContainerHeight = null;

    this.reset();

    // Bind the event listeners for click, cursor tool, and scroll/spread mode
    // actions.
    this._bindClickListeners();
    this._bindCursorToolsListener(options);
    this._bindScrollModeListener(options);
    this._bindSpreadModeListener(options);

    // Bind the event listener for adjusting the 'max-height' of the toolbar.
    this.eventBus.on('resize', this._setMaxHeight.bind(this));

    // Hide the Scroll/Spread mode buttons, when they're not applicable to the
    // current `BaseViewer` instance (in particular `PDFSinglePageViewer`).
    this.eventBus.on('baseviewerinit', (evt) => {
      if (evt.source instanceof PDFSinglePageViewer) {
        this.toolbarButtonContainer.classList.add('hiddenScrollModeButtons');
        this.toolbarButtonContainer.classList.add('hiddenSpreadModeButtons');
      } else {
        this.toolbarButtonContainer.classList.remove('hiddenScrollModeButtons');
        this.toolbarButtonContainer.classList.remove('hiddenSpreadModeButtons');
      }
    });
  }

  /**
   * @return {boolean}
   */
  get isOpen() {
    return this.opened;
  }

  setPageNumber(pageNumber) {
    this.pageNumber = pageNumber;
    this._updateUIState();
  }

  setPagesCount(pagesCount) {
    this.pagesCount = pagesCount;
    this._updateUIState();
  }

  reset() {
    this.pageNumber = 0;
    this.pagesCount = 0;
    this._updateUIState();

    // Reset the Scroll/Spread buttons too, since they're document specific.
    this.eventBus.dispatch('secondarytoolbarreset', { source: this, });
  }

  _updateUIState() {
    this.items.firstPage.disabled = (this.pageNumber <= 1);
    this.items.lastPage.disabled = (this.pageNumber >= this.pagesCount);
    this.items.pageRotateCw.disabled = this.pagesCount === 0;
    this.items.pageRotateCcw.disabled = this.pagesCount === 0;
  }

  _bindClickListeners() {
    // Button to toggle the visibility of the secondary toolbar.
    this.toggleButton.addEventListener('click', this.toggle.bind(this));

    // All items within the secondary toolbar.
    for (let button in this.buttons) {
      let { element, eventName, close, eventDetails, } = this.buttons[button];

      element.addEventListener('click', (evt) => {
        if (eventName !== null) {
          let details = { source: this, };
          for (let property in eventDetails) {
            details[property] = eventDetails[property];
          }
          this.eventBus.dispatch(eventName, details);
        }
        if (close) {
          this.close();
        }
      });
    }
  }

  _bindCursorToolsListener(buttons) {
    this.eventBus.on('cursortoolchanged', function(evt) {
      buttons.cursorSelectToolButton.classList.remove('toggled');
      buttons.cursorHandToolButton.classList.remove('toggled');

      switch (evt.tool) {
        case CursorTool.SELECT:
          buttons.cursorSelectToolButton.classList.add('toggled');
          break;
        case CursorTool.HAND:
          buttons.cursorHandToolButton.classList.add('toggled');
          break;
      }
    });
  }

  _bindScrollModeListener(buttons) {
    function scrollModeChanged(evt) {
      buttons.scrollVerticalButton.classList.remove('toggled');
      buttons.scrollHorizontalButton.classList.remove('toggled');
      buttons.scrollWrappedButton.classList.remove('toggled');

      switch (evt.mode) {
        case ScrollMode.VERTICAL:
          buttons.scrollVerticalButton.classList.add('toggled');
          break;
        case ScrollMode.HORIZONTAL:
          buttons.scrollHorizontalButton.classList.add('toggled');
          break;
        case ScrollMode.WRAPPED:
          buttons.scrollWrappedButton.classList.add('toggled');
          break;
      }
    }
    this.eventBus.on('scrollmodechanged', scrollModeChanged);

    this.eventBus.on('secondarytoolbarreset', (evt) => {
      if (evt.source === this) {
        scrollModeChanged({ mode: ScrollMode.VERTICAL, });
      }
    });
  }

  _bindSpreadModeListener(buttons) {
    function spreadModeChanged(evt) {
      buttons.spreadNoneButton.classList.remove('toggled');
      buttons.spreadOddButton.classList.remove('toggled');
      buttons.spreadEvenButton.classList.remove('toggled');

      switch (evt.mode) {
        case SpreadMode.NONE:
          buttons.spreadNoneButton.classList.add('toggled');
          break;
        case SpreadMode.ODD:
          buttons.spreadOddButton.classList.add('toggled');
          break;
        case SpreadMode.EVEN:
          buttons.spreadEvenButton.classList.add('toggled');
          break;
      }
    }
    this.eventBus.on('spreadmodechanged', spreadModeChanged);

    this.eventBus.on('secondarytoolbarreset', (evt) => {
      if (evt.source === this) {
        spreadModeChanged({ mode: SpreadMode.NONE, });
      }
    });
  }

  open() {
    if (this.opened) {
      return;
    }
    this.opened = true;
    this._setMaxHeight();

    this.toggleButton.classList.add('toggled');
    this.toolbar.classList.remove('hidden');
  }

  close() {
    if (!this.opened) {
      return;
    }
    this.opened = false;
    this.toolbar.classList.add('hidden');
    this.toggleButton.classList.remove('toggled');
  }

  toggle() {
    if (this.opened) {
      this.close();
    } else {
      this.open();
    }
  }

  /**
   * @private
   */
  _setMaxHeight() {
    if (!this.opened) {
      return; // Only adjust the 'max-height' if the toolbar is visible.
    }
    this.containerHeight = this.mainContainer.clientHeight;

    if (this.containerHeight === this.previousContainerHeight) {
      return;
    }
    this.toolbarButtonContainer.setAttribute('style',
      'max-height: ' + (this.containerHeight - SCROLLBAR_PADDING) + 'px;');

    this.previousContainerHeight = this.containerHeight;
  }
}

export {
  SecondaryToolbar,
};
<commit_msg>Temporarily disable the Spread mode buttons when horizontal scrolling is enabled<commit_after>/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ScrollMode, SpreadMode } from './base_viewer';
import { CursorTool } from './pdf_cursor_tools';
import { PDFSinglePageViewer } from './pdf_single_page_viewer';
import { SCROLLBAR_PADDING } from './ui_utils';

/**
 * @typedef {Object} SecondaryToolbarOptions
 * @property {HTMLDivElement} toolbar - Container for the secondary toolbar.
 * @property {HTMLButtonElement} toggleButton - Button to toggle the visibility
 *   of the secondary toolbar.
 * @property {HTMLDivElement} toolbarButtonContainer - Container where all the
 *   toolbar buttons are placed. The maximum height of the toolbar is controlled
 *   dynamically by adjusting the 'max-height' CSS property of this DOM element.
 * @property {HTMLButtonElement} presentationModeButton - Button for entering
 *   presentation mode.
 * @property {HTMLButtonElement} openFileButton - Button to open a file.
 * @property {HTMLButtonElement} printButton - Button to print the document.
 * @property {HTMLButtonElement} downloadButton - Button to download the
 *   document.
 * @property {HTMLLinkElement} viewBookmarkButton - Button to obtain a bookmark
 *   link to the current location in the document.
 * @property {HTMLButtonElement} firstPageButton - Button to go to the first
 *   page in the document.
 * @property {HTMLButtonElement} lastPageButton - Button to go to the last page
 *   in the document.
 * @property {HTMLButtonElement} pageRotateCwButton - Button to rotate the pages
 *   clockwise.
 * @property {HTMLButtonElement} pageRotateCcwButton - Button to rotate the
 *   pages counterclockwise.
 * @property {HTMLButtonElement} cursorSelectToolButton - Button to enable the
 *   select tool.
 * @property {HTMLButtonElement} cursorHandToolButton - Button to enable the
 *   hand tool.
 * @property {HTMLButtonElement} documentPropertiesButton - Button for opening
 *   the document properties dialog.
 */

class SecondaryToolbar {
  /**
   * @param {SecondaryToolbarOptions} options
   * @param {HTMLDivElement} mainContainer
   * @param {EventBus} eventBus
   */
  constructor(options, mainContainer, eventBus) {
    this.toolbar = options.toolbar;
    this.toggleButton = options.toggleButton;
    this.toolbarButtonContainer = options.toolbarButtonContainer;
    this.buttons = [
      { element: options.presentationModeButton, eventName: 'presentationmode',
        close: true, },
      { element: options.openFileButton, eventName: 'openfile', close: true, },
      { element: options.printButton, eventName: 'print', close: true, },
      { element: options.downloadButton, eventName: 'download', close: true, },
      { element: options.viewBookmarkButton, eventName: null, close: true, },
      { element: options.firstPageButton, eventName: 'firstpage',
        close: true, },
      { element: options.lastPageButton, eventName: 'lastpage', close: true, },
      { element: options.pageRotateCwButton, eventName: 'rotatecw',
        close: false, },
      { element: options.pageRotateCcwButton, eventName: 'rotateccw',
        close: false, },
      { element: options.cursorSelectToolButton, eventName: 'switchcursortool',
        eventDetails: { tool: CursorTool.SELECT, }, close: true, },
      { element: options.cursorHandToolButton, eventName: 'switchcursortool',
        eventDetails: { tool: CursorTool.HAND, }, close: true, },
      { element: options.scrollVerticalButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.VERTICAL, }, close: true, },
      { element: options.scrollHorizontalButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.HORIZONTAL, }, close: true, },
      { element: options.scrollWrappedButton, eventName: 'switchscrollmode',
        eventDetails: { mode: ScrollMode.WRAPPED, }, close: true, },
      { element: options.spreadNoneButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.NONE, }, close: true, },
      { element: options.spreadOddButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.ODD, }, close: true, },
      { element: options.spreadEvenButton, eventName: 'switchspreadmode',
        eventDetails: { mode: SpreadMode.EVEN, }, close: true, },
      { element: options.documentPropertiesButton,
        eventName: 'documentproperties', close: true, },
    ];
    this.items = {
      firstPage: options.firstPageButton,
      lastPage: options.lastPageButton,
      pageRotateCw: options.pageRotateCwButton,
      pageRotateCcw: options.pageRotateCcwButton,
    };

    this.mainContainer = mainContainer;
    this.eventBus = eventBus;

    this.opened = false;
    this.containerHeight = null;
    this.previousContainerHeight = null;

    this.reset();

    // Bind the event listeners for click, cursor tool, and scroll/spread mode
    // actions.
    this._bindClickListeners();
    this._bindCursorToolsListener(options);
    this._bindScrollModeListener(options);
    this._bindSpreadModeListener(options);

    // Bind the event listener for adjusting the 'max-height' of the toolbar.
    this.eventBus.on('resize', this._setMaxHeight.bind(this));

    // Hide the Scroll/Spread mode buttons, when they're not applicable to the
    // current `BaseViewer` instance (in particular `PDFSinglePageViewer`).
    this.eventBus.on('baseviewerinit', (evt) => {
      if (evt.source instanceof PDFSinglePageViewer) {
        this.toolbarButtonContainer.classList.add('hiddenScrollModeButtons');
        this.toolbarButtonContainer.classList.add('hiddenSpreadModeButtons');
      } else {
        this.toolbarButtonContainer.classList.remove('hiddenScrollModeButtons');
        this.toolbarButtonContainer.classList.remove('hiddenSpreadModeButtons');
      }
    });
  }

  /**
   * @return {boolean}
   */
  get isOpen() {
    return this.opened;
  }

  setPageNumber(pageNumber) {
    this.pageNumber = pageNumber;
    this._updateUIState();
  }

  setPagesCount(pagesCount) {
    this.pagesCount = pagesCount;
    this._updateUIState();
  }

  reset() {
    this.pageNumber = 0;
    this.pagesCount = 0;
    this._updateUIState();

    // Reset the Scroll/Spread buttons too, since they're document specific.
    this.eventBus.dispatch('secondarytoolbarreset', { source: this, });
  }

  _updateUIState() {
    this.items.firstPage.disabled = (this.pageNumber <= 1);
    this.items.lastPage.disabled = (this.pageNumber >= this.pagesCount);
    this.items.pageRotateCw.disabled = this.pagesCount === 0;
    this.items.pageRotateCcw.disabled = this.pagesCount === 0;
  }

  _bindClickListeners() {
    // Button to toggle the visibility of the secondary toolbar.
    this.toggleButton.addEventListener('click', this.toggle.bind(this));

    // All items within the secondary toolbar.
    for (let button in this.buttons) {
      let { element, eventName, close, eventDetails, } = this.buttons[button];

      element.addEventListener('click', (evt) => {
        if (eventName !== null) {
          let details = { source: this, };
          for (let property in eventDetails) {
            details[property] = eventDetails[property];
          }
          this.eventBus.dispatch(eventName, details);
        }
        if (close) {
          this.close();
        }
      });
    }
  }

  _bindCursorToolsListener(buttons) {
    this.eventBus.on('cursortoolchanged', function(evt) {
      buttons.cursorSelectToolButton.classList.remove('toggled');
      buttons.cursorHandToolButton.classList.remove('toggled');

      switch (evt.tool) {
        case CursorTool.SELECT:
          buttons.cursorSelectToolButton.classList.add('toggled');
          break;
        case CursorTool.HAND:
          buttons.cursorHandToolButton.classList.add('toggled');
          break;
      }
    });
  }

  _bindScrollModeListener(buttons) {
    function scrollModeChanged(evt) {
      buttons.scrollVerticalButton.classList.remove('toggled');
      buttons.scrollHorizontalButton.classList.remove('toggled');
      buttons.scrollWrappedButton.classList.remove('toggled');

      switch (evt.mode) {
        case ScrollMode.VERTICAL:
          buttons.scrollVerticalButton.classList.add('toggled');
          break;
        case ScrollMode.HORIZONTAL:
          buttons.scrollHorizontalButton.classList.add('toggled');
          break;
        case ScrollMode.WRAPPED:
          buttons.scrollWrappedButton.classList.add('toggled');
          break;
      }

      // Temporarily *disable* the Spread buttons when horizontal scrolling is
      // enabled, since the non-default Spread modes doesn't affect the layout.
      const isScrollModeHorizontal = (evt.mode === ScrollMode.HORIZONTAL);
      buttons.spreadNoneButton.disabled = isScrollModeHorizontal;
      buttons.spreadOddButton.disabled = isScrollModeHorizontal;
      buttons.spreadEvenButton.disabled = isScrollModeHorizontal;
    }
    this.eventBus.on('scrollmodechanged', scrollModeChanged);

    this.eventBus.on('secondarytoolbarreset', (evt) => {
      if (evt.source === this) {
        scrollModeChanged({ mode: ScrollMode.VERTICAL, });
      }
    });
  }

  _bindSpreadModeListener(buttons) {
    function spreadModeChanged(evt) {
      buttons.spreadNoneButton.classList.remove('toggled');
      buttons.spreadOddButton.classList.remove('toggled');
      buttons.spreadEvenButton.classList.remove('toggled');

      switch (evt.mode) {
        case SpreadMode.NONE:
          buttons.spreadNoneButton.classList.add('toggled');
          break;
        case SpreadMode.ODD:
          buttons.spreadOddButton.classList.add('toggled');
          break;
        case SpreadMode.EVEN:
          buttons.spreadEvenButton.classList.add('toggled');
          break;
      }
    }
    this.eventBus.on('spreadmodechanged', spreadModeChanged);

    this.eventBus.on('secondarytoolbarreset', (evt) => {
      if (evt.source === this) {
        spreadModeChanged({ mode: SpreadMode.NONE, });
      }
    });
  }

  open() {
    if (this.opened) {
      return;
    }
    this.opened = true;
    this._setMaxHeight();

    this.toggleButton.classList.add('toggled');
    this.toolbar.classList.remove('hidden');
  }

  close() {
    if (!this.opened) {
      return;
    }
    this.opened = false;
    this.toolbar.classList.add('hidden');
    this.toggleButton.classList.remove('toggled');
  }

  toggle() {
    if (this.opened) {
      this.close();
    } else {
      this.open();
    }
  }

  /**
   * @private
   */
  _setMaxHeight() {
    if (!this.opened) {
      return; // Only adjust the 'max-height' if the toolbar is visible.
    }
    this.containerHeight = this.mainContainer.clientHeight;

    if (this.containerHeight === this.previousContainerHeight) {
      return;
    }
    this.toolbarButtonContainer.setAttribute('style',
      'max-height: ' + (this.containerHeight - SCROLLBAR_PADDING) + 'px;');

    this.previousContainerHeight = this.containerHeight;
  }
}

export {
  SecondaryToolbar,
};
<|endoftext|>


<commit_before>/*
* Utility for websocket
*
*/
import { message } from 'antd';

/**
* Initiate a ws connection.
* The default path `do-not-proxy` means the ws do not need to be proxied.
* This is very important for AnyProxy‘s own server, such as WEB UI,
* and the websocket detail panel in it, to prevent a recursive proxy.
* @param {wsPort} wsPort the port of websocket
* @param {key} path the path of the ws url
*
*/
export function initWs(wsPort = location.port, path = 'do-not-proxy') {
  if(!WebSocket){
    throw (new Error('WebSocket is not supportted on this browser'));
  }

  const wsClient = new WebSocket(`ws://${location.hostname}:${wsPort}/${path}`);

  wsClient.onerror = (error) => {
    console.error(error);
    message.error('error happened when setup websocket');
  };

  wsClient.onopen = (e) => {
    console.info('websocket opened: ', e);
  };

  wsClient.onclose = (e) => {
    console.info('websocket closed: ', e);
  };

  return wsClient;
}

export default {
  initWs: initWs
};
<commit_msg>Update WsUtil.js<commit_after>/*
* Utility for websocket
*
*/
import { message } from 'antd';

/**
* Initiate a ws connection.
* The default path `do-not-proxy` means the ws do not need to be proxied.
* This is very important for AnyProxy‘s own server, such as WEB UI,
* and the websocket detail panel in it, to prevent a recursive proxy.
* @param {wsPort} wsPort the port of websocket
* @param {key} path the path of the ws url
*
*/
export function initWs(wsPort = location.port, path = 'do-not-proxy') {
  if(!WebSocket){
    throw (new Error('WebSocket is not supported on this browser'));
  }

  const wsClient = new WebSocket(`ws://${location.hostname}:${wsPort}/${path}`);

  wsClient.onerror = (error) => {
    console.error(error);
    message.error('error happened when setup websocket');
  };

  wsClient.onopen = (e) => {
    console.info('websocket opened: ', e);
  };

  wsClient.onclose = (e) => {
    console.info('websocket closed: ', e);
  };

  return wsClient;
}

export default {
  initWs: initWs
};
<|endoftext|>


<commit_before><commit_msg>Add .offOne method to remove one listener for a certain event<commit_after><|endoftext|>


<commit_before>/*******************************************************************************
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

//Default behaviour for npm test - app will be closed with endRun() so timeout not needed
var timeout = false;
var agent = true;

process.argv.forEach(function(elem) {

	//Running globally with node-hc - testing agent runs and doesn't crash process
	if (elem == '-g'){
		timeout = true;
		agent = false;
	}

	//Running with node-hc and including agent - this should fail
	if (elem == '-f'){
		agent = true;
		timeout = true;
	}
});

var appmetrics;

//If running global test, run long enough to ensure the agent has loaded and process doesn't crash
if (timeout) {
	var duration_secs = process.argv[2] || 10; //Default 10 seconds for global tests
	setTimeout(function(){
		clearInterval(ih);
	}, duration_secs*1000);
}

//If being run from other test, start the agent and make available
if (agent) {
	appmetrics = require('../');

	// Make agent visible for other script files.
	module.exports.appmetrics = appmetrics;
}

module.exports.start = function start() {
	appmetrics.start();
}

//Write a string to memory on timer
var test = null;
var ih = setInterval(function() {
  var dummy = new Buffer(1024*1024);
  dummy.write("hello");
  test = dummy.toString()[0];
}, 100);

module.exports.endRun = function(){
	//console.log("end Run");
	appmetrics.stop();
	//console.log("appmetrics stopped");
	clearInterval(ih);
	//console.log("interval cleared");
}
<commit_msg>remove debug<commit_after>/*******************************************************************************
 * Copyright 2015 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

//Default behaviour for npm test - app will be closed with endRun() so timeout not needed
var timeout = false;
var agent = true;

process.argv.forEach(function(elem) {

	//Running globally with node-hc - testing agent runs and doesn't crash process
	if (elem == '-g'){
		timeout = true;
		agent = false;
	}

	//Running with node-hc and including agent - this should fail
	if (elem == '-f'){
		agent = true;
		timeout = true;
	}
});

var appmetrics;

//If running global test, run long enough to ensure the agent has loaded and process doesn't crash
if (timeout) {
	var duration_secs = process.argv[2] || 10; //Default 10 seconds for global tests
	setTimeout(function(){
		clearInterval(ih);
	}, duration_secs*1000);
}

//If being run from other test, start the agent and make available
if (agent) {
	appmetrics = require('../');

	// Make agent visible for other script files.
	module.exports.appmetrics = appmetrics;
}

module.exports.start = function start() {
	appmetrics.start();
}

//Write a string to memory on timer
var test = null;
var ih = setInterval(function() {
  var dummy = new Buffer(1024*1024);
  dummy.write("hello");
  test = dummy.toString()[0];
}, 100);

module.exports.endRun = function(){
	appmetrics.stop();
	clearInterval(ih);
}
<|endoftext|>


<commit_before>import webpack from 'webpack';
import ZopfliPlugin from "zopfli-webpack-plugin";

import configuration, { opts } from './base-config';
import RuleGenerator from './rules';

const rules = new RuleGenerator(opts.dev);

const clientConfiguration = {
  ...configuration
};

if (opts.dev) {
  clientConfiguration.devtool = 'eval';
}

clientConfiguration.module.rules = [
  ...configuration.module.rules,
  rules.clientJs,
  ...rules.clientLibFixes,
  rules.htmlForDevRender
];

let additionalPlugins = [
  new webpack.IgnorePlugin(/^ioredis$/)
];

if (opts.dev === false) {
  additionalPlugins = [
    ...additionalPlugins,
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false, // Suppress uglification warnings
        pure_getters: true,
        unsafe: true,
        unsafe_comps: true,
        screw_ie8: true
      },
      sourceMap: false,
      output: {
        comments: false,
      },
      exclude: [/\.min\.js$/gi] // skip pre-minified libs
    }),
    new webpack.optimize.AggressiveMergingPlugin({ minSizeReduce: true }),
    new ZopfliPlugin({
      asset: "[path].gz[query]",
      algorithm: "zopfli",
      minRatio: 0.8
    })
  ];
}

clientConfiguration.plugins = [
  ...configuration.plugins,
  ...additionalPlugins
];

export default clientConfiguration;
<commit_msg>🚑Inject env-vars into clientside build<commit_after>import webpack from 'webpack';
import ZopfliPlugin from "zopfli-webpack-plugin";

import configuration, { opts } from './base-config';
import RuleGenerator from './rules';

const rules = new RuleGenerator(opts.dev);

const clientConfiguration = {
  ...configuration
};

if (opts.dev) {
  clientConfiguration.devtool = 'eval';
}

clientConfiguration.module.rules = [
  ...configuration.module.rules,
  rules.clientJs,
  ...rules.clientLibFixes,
  rules.htmlForDevRender
];

let additionalPlugins = [
  new webpack.IgnorePlugin(/^ioredis$/)
];

if (opts.dev === false) {
  additionalPlugins = [
    ...additionalPlugins,
    new webpack.EnvironmentPlugin([
      'API_HOST', 'NODE_ENV', 'MAPBOX_ACCESS_TOKEN', 'GOOGLE_ANALYTICS_ID', 'GOOGLE_TAG_MANAGER_ID', 'INTERCOM_APP_ID',
    ]),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false, // Suppress uglification warnings
        pure_getters: true,
        unsafe: true,
        unsafe_comps: true,
        screw_ie8: true
      },
      sourceMap: false,
      output: {
        comments: false,
      },
      exclude: [/\.min\.js$/gi] // skip pre-minified libs
    }),
    new webpack.optimize.AggressiveMergingPlugin({ minSizeReduce: true }),
    new ZopfliPlugin({
      asset: "[path].gz[query]",
      algorithm: "zopfli",
      minRatio: 0.8
    })
  ];
}

clientConfiguration.plugins = [
  ...configuration.plugins,
  ...additionalPlugins
];

export default clientConfiguration;
<|endoftext|>


<commit_before>Frontend.Dialog = Class.extend({
    /**
     * History keeper
     *
     * @type object
     */
    _history: {
        upcoming: null,
        entries: []
    },

    /**
     * Modal instance
     *
     * @type object
     */
    _modal: null,

    /**
     * Indicator if modal is currently open
     *
     * @type bool
     */
    _modalOpen: false,

    /**
     * Default configuration
     *
     * @type object
     */
    _defaultConfig: {
        selectTab: false,
        additionalClasses: false,
        initController: true,
        replaceTarget: false,
        preventHistory: false,
        onLoadComplete: false,
        onDialogClose: false
    },

    /**
     * Configuration
     *
     * @type object
     */
    _config: {},

    /**
     * Open dialog from action
     *
     * @param  object  url             Request URL in CakePHP style
     * @param  object  requestOptions  Request options for loadJsonAction
     * @return void
     */
    loadDialog: function(url, requestOptions) {
        if (!this._checkForModalTemplate()) {
            return false;
        }

        this._config = {};
        jQuery.extend(this._config, this._defaultConfig, requestOptions, {
            onComplete: function(controller, response) {
                if (response.data.redirect) {
                    var redirectUrl = response.data.redirect;
                    if (typeof redirectUrl === 'object') {
                        redirectUrl = App.Main.Router.url(redirectUrl);
                    }

                    window.location = redirectUrl;
                    // Force reloading for urls containing an anchor
                    window.location.reload(true);
                    return
                }
                // Error handling
                if (!response.data.html) {
                    return console.error('No response HTML available.');
                }

                // Initialize new dialog
                this._modal = $('.modal');
                // Content setter
                this._setContent(response.data.html);

                selectedTab = null;
                // Tab selector
                if (this._config.selectTab) {
                    $(this._config.selectTab).tab('show');
                    selectedTab = this._config.selectTab;
                }

                // Large modal option
                if (this._config.additionalClasses) {
                    $('.modal-dialog', this._modal).addClass(this._config.additionalClasses);
                }

                // Modal Initialize
                this._modal.modal({
                    backdrop: true,
                    keyboard: true,
                    focus: true,
                    show: true
                });

                // History and events
                this._addHistory(url, this._config, selectedTab);
                this._registerHandler();

                if (typeof this._config.onLoadComplete === 'function') {
                    this._config.onLoadComplete(controller, response);
                }

                App.Main.UIBlocker.unblockElement($(this._getBlockElement()));

                if (response.data.closeDialog) {
                    this.close();
                }
            }.bind(this)
        });

        App.Main.UIBlocker.blockElement($(this._getBlockElement()));
        App.Main.loadJsonAction(this._ensureDialogAction(url), this._config);
    },

    /**
     * Closes the dialog
     *
     * @protected
     */
    close: function() {
        this._modal.modal('hide');
    },

    /**
     * Determine which element to block the ui for
     *
     * @return string
     */
    _getBlockElement: function() {
        var blockElement = 'body';
        if (this._modalOpen) {
            blockElement = '.modal-dialog';
        }

        return blockElement;
    },

    /**
     * Set content of the modal
     *
     * @param  string  content  HTML Content
     * @return void
     */
    _setContent: function(content) {
        this._modal.html(content);
    },

    /**
     * Modal existence checker
     *
     * @return bool
     */
    _checkForModalTemplate: function()
    {
        if (!$('.modal').length) {
            console.error('You need to load the modal template through FrontendBrigeHelper::includeModal function into DOM.');
            return false;
        }

        return true;
    },

    /**
     * Add item to history
     *
     * @param  object  url              Request URL in CakePHP style
     * @param  object  config           the config object of the initial call
     * @param  string  selectedTab      Selected tab
     * @return void
     */
    _addHistory: function(url, config, selectedTab) {
        var preventUpcoming = config.preventHistory;
        if (this._history.upcoming && !preventUpcoming) {
            this._history.entries.push(this._history.upcoming);
        }
        if (preventUpcoming && this._history.entries.length > 0) {
            url = null;
        }

        if (url) {
            url = {
                url: url,
                title: $('.modal-title', this._modal).html(),
                selectedTab: selectedTab,
                additionalClasses: config.additionalClasses
            };
        }
        this._history.upcoming = url;
    },

    /**
     * Bind handlers and do conditional stuff.
     *
     * @return void
     */
    _registerHandler: function() {
        this._modal.off('hidden.bs.modal').on('hidden.bs.modal', function(e) {
            this._cleanupModal();
            this._history = {
                upcoming: null,
                entries: []
            };
            $('.modal-dialog', this._modal).removeClass('modal-lg');
            this._modalOpen = false;

            if (typeof this._config.onDialogClose === 'function') {
                this._config.onDialogClose(this);
            }
        }.bind(this));

        this._modal.on('shown.bs.modal', function () {
            this._modalOpen = true;
        }.bind(this));

        $(document).on('keyup', function(e) {
            if (!this._modal) {
                return;
            }

            // Escape key
            if (e.keyCode === 27) {
                this.close();
            }
        }.bind(this));

        $('.modal-header .close, .modal-header .close-btn, .modal-footer .cancel-button', this._modal).off('click').on('click', function(e) {
            e.preventDefault();
            this.close();
        }.bind(this));

        $('form', this._modal).off('submit').on('submit', function(e) {
            var $target = $(e.currentTarget);
            if($target.data('ajax-submit') === 0) {
                return;
            }

            e.preventDefault();

            this._cleanupModal();
            App.Main.UIBlocker.blockElement($(this._getBlockElement()));
            var url = $target.attr('action');

            var formData = null;
            if (!!window.FormData) {
                formData = new FormData(e.currentTarget);
            } else {
                formData = $target.serialize();
            }

            this.loadDialog(url, {
                data: formData,
                preventHistory: true
            });
        }.bind(this));

        if (this._history.entries.length > 0) {
            $('.modal-back', this._modal).show();
        } else {
            $('.modal-back', this._modal).hide();
        }

        $('.modal-back', this._modal).off('click').on('click', function(e) {
            if (this._history.entries.length <= 0) {
                return;
            }

            this._cleanupModal();

            App.Main.UIBlocker.blockElement($(this._getBlockElement()));
            var historyEntry = this._history.entries.pop();
            this.loadDialog(historyEntry.url, {
                preventHistory: true,
                modalTitle: historyEntry.title,
                selectTab: historyEntry.selectedTab,
                additionalClasses: historyEntry.additionalClasses
            });
            App.Main.UIBlocker.unblockElement($(this._getBlockElement()));
        }.bind(this));

        $('.nav-tabs.historized a[data-toggle="tab"]', this._modal).on('shown.bs.tab', function (e) {
            // Update tab in history
            if (typeof $(e.target).attr('class') === 'string' && this._history.upcoming) {
                this._history.upcoming.selectedTab = '.' + $(e.target).attr('class');
            }
        }.bind(this))
    },

    /**
     * Ensure addition of json_action=1 and dialog_action=1 at a url which is no cakephp conform array.
     *
     * @param   mixed  url  URL to check for
     * @return  mixed
     */
    _ensureDialogAction: function(url) {
        if (typeof url === 'object') {
            if (url.hasOwnProperty('query')) {
                url.query.dialog_action = 1;
            } else {
                url.query = {
                    dialog_action: 1
                }
            }

            return url;
        }

        if (typeof url !== 'string') {
            return url;
        }

        if (url.indexOf('json_action=1') !== -1 && url.indexOf('dialog_action=1') !== -1) {
            return url;
        }

        if (url.indexOf('?') !== -1) {
            url += '&';
        } else {
            url += '?';
        }
        url += 'json_action=1&dialog_action=1';

        return url;
    },

    /**
     * Removes controller instance from instance keeper.
     *
     * @return void
     */
    _cleanupModal: function() {
        if (!this._modal) {
            return;
        }

        App.Main.cleanControllerInstances(this._modal);
    }
});<commit_msg>Fix configuration not being passed to new dialog<commit_after>Frontend.Dialog = Class.extend({
    /**
     * History keeper
     *
     * @type object
     */
    _history: {
        upcoming: null,
        entries: []
    },

    /**
     * Modal instance
     *
     * @type object
     */
    _modal: null,

    /**
     * Indicator if modal is currently open
     *
     * @type bool
     */
    _modalOpen: false,

    /**
     * Default configuration
     *
     * @type object
     */
    _defaultConfig: {
        selectTab: false,
        additionalClasses: false,
        initController: true,
        replaceTarget: false,
        preventHistory: false,
        onLoadComplete: false,
        onDialogClose: false
    },

    /**
     * Configuration
     *
     * @type object
     */
    _config: {},

    /**
     * Open dialog from action
     *
     * @param  object  url             Request URL in CakePHP style
     * @param  object  requestOptions  Request options for loadJsonAction
     * @return void
     */
    loadDialog: function(url, requestOptions) {
        if (!this._checkForModalTemplate()) {
            return false;
        }

        this._config = {};
        jQuery.extend(this._config, this._defaultConfig, requestOptions, {
            onComplete: function(controller, response) {
                if (response.data.redirect) {
                    var redirectUrl = response.data.redirect;
                    if (typeof redirectUrl === 'object') {
                        redirectUrl = App.Main.Router.url(redirectUrl);
                    }

                    window.location = redirectUrl;
                    // Force reloading for urls containing an anchor
                    window.location.reload(true);
                    return
                }
                // Error handling
                if (!response.data.html) {
                    return console.error('No response HTML available.');
                }

                // Initialize new dialog
                this._modal = $('.modal');
                // Content setter
                this._setContent(response.data.html);

                selectedTab = null;
                // Tab selector
                if (this._config.selectTab) {
                    $(this._config.selectTab).tab('show');
                    selectedTab = this._config.selectTab;
                }

                // Large modal option
                if (this._config.additionalClasses) {
                    $('.modal-dialog', this._modal).addClass(this._config.additionalClasses);
                }

                // Modal Initialize
                this._modal.modal({
                    backdrop: true,
                    keyboard: true,
                    focus: true,
                    show: true
                });

                // History and events
                this._addHistory(url, this._config, selectedTab);
                this._registerHandler();

                if (typeof this._config.onLoadComplete === 'function') {
                    this._config.onLoadComplete(controller, response);
                }

                App.Main.UIBlocker.unblockElement($(this._getBlockElement()));

                if (response.data.closeDialog) {
                    this.close();
                }
            }.bind(this)
        });

        App.Main.UIBlocker.blockElement($(this._getBlockElement()));
        App.Main.loadJsonAction(this._ensureDialogAction(url), this._config);
    },

    /**
     * Closes the dialog
     *
     * @protected
     */
    close: function() {
        this._modal.modal('hide');
    },

    /**
     * Determine which element to block the ui for
     *
     * @return string
     */
    _getBlockElement: function() {
        var blockElement = 'body';
        if (this._modalOpen) {
            blockElement = '.modal-dialog';
        }

        return blockElement;
    },

    /**
     * Set content of the modal
     *
     * @param  string  content  HTML Content
     * @return void
     */
    _setContent: function(content) {
        this._modal.html(content);
    },

    /**
     * Modal existence checker
     *
     * @return bool
     */
    _checkForModalTemplate: function()
    {
        if (!$('.modal').length) {
            console.error('You need to load the modal template through FrontendBrigeHelper::includeModal function into DOM.');
            return false;
        }

        return true;
    },

    /**
     * Add item to history
     *
     * @param  object  url              Request URL in CakePHP style
     * @param  object  config           the config object of the initial call
     * @param  string  selectedTab      Selected tab
     * @return void
     */
    _addHistory: function(url, config, selectedTab) {
        var preventUpcoming = config.preventHistory;
        if (this._history.upcoming && !preventUpcoming) {
            this._history.entries.push(this._history.upcoming);
        }
        if (preventUpcoming && this._history.entries.length > 0) {
            url = null;
        }

        if (url) {
            url = {
                url: url,
                title: $('.modal-title', this._modal).html(),
                selectedTab: selectedTab,
                additionalClasses: config.additionalClasses
            };
        }
        this._history.upcoming = url;
    },

    /**
     * Bind handlers and do conditional stuff.
     *
     * @return void
     */
    _registerHandler: function() {
        this._modal.off('hidden.bs.modal').on('hidden.bs.modal', function(e) {
            this._cleanupModal();
            this._history = {
                upcoming: null,
                entries: []
            };
            $('.modal-dialog', this._modal).removeClass('modal-lg');
            this._modalOpen = false;

            if (typeof this._config.onDialogClose === 'function') {
                this._config.onDialogClose(this);
            }
        }.bind(this));

        this._modal.on('shown.bs.modal', function () {
            this._modalOpen = true;
        }.bind(this));

        $(document).on('keyup', function(e) {
            if (!this._modal) {
                return;
            }

            // Escape key
            if (e.keyCode === 27) {
                this.close();
            }
        }.bind(this));

        $('.modal-header .close, .modal-header .close-btn, .modal-footer .cancel-button', this._modal).off('click').on('click', function(e) {
            e.preventDefault();
            this.close();
        }.bind(this));

        $('form', this._modal).off('submit').on('submit', function(e) {
            var $target = $(e.currentTarget);
            if($target.data('ajax-submit') === 0) {
                return;
            }

            e.preventDefault();

            this._cleanupModal();
            App.Main.UIBlocker.blockElement($(this._getBlockElement()));
            var url = $target.attr('action');

            var formData = null;
            if (!!window.FormData) {
                formData = new FormData(e.currentTarget);
            } else {
                formData = $target.serialize();
            }

            var config = this._config;

            jQuery.extend(config, {
                data: formData,
                preventHistory: true
            });

            this.loadDialog(url, config);
        }.bind(this));

        if (this._history.entries.length > 0) {
            $('.modal-back', this._modal).show();
        } else {
            $('.modal-back', this._modal).hide();
        }

        $('.modal-back', this._modal).off('click').on('click', function(e) {
            if (this._history.entries.length <= 0) {
                return;
            }

            this._cleanupModal();

            App.Main.UIBlocker.blockElement($(this._getBlockElement()));
            var historyEntry = this._history.entries.pop();
            this.loadDialog(historyEntry.url, {
                preventHistory: true,
                modalTitle: historyEntry.title,
                selectTab: historyEntry.selectedTab,
                additionalClasses: historyEntry.additionalClasses
            });
            App.Main.UIBlocker.unblockElement($(this._getBlockElement()));
        }.bind(this));

        $('.nav-tabs.historized a[data-toggle="tab"]', this._modal).on('shown.bs.tab', function (e) {
            // Update tab in history
            if (typeof $(e.target).attr('class') === 'string' && this._history.upcoming) {
                this._history.upcoming.selectedTab = '.' + $(e.target).attr('class');
            }
        }.bind(this))
    },

    /**
     * Ensure addition of json_action=1 and dialog_action=1 at a url which is no cakephp conform array.
     *
     * @param   mixed  url  URL to check for
     * @return  mixed
     */
    _ensureDialogAction: function(url) {
        if (typeof url === 'object') {
            if (url.hasOwnProperty('query')) {
                url.query.dialog_action = 1;
            } else {
                url.query = {
                    dialog_action: 1
                }
            }

            return url;
        }

        if (typeof url !== 'string') {
            return url;
        }

        if (url.indexOf('json_action=1') !== -1 && url.indexOf('dialog_action=1') !== -1) {
            return url;
        }

        if (url.indexOf('?') !== -1) {
            url += '&';
        } else {
            url += '?';
        }
        url += 'json_action=1&dialog_action=1';

        return url;
    },

    /**
     * Removes controller instance from instance keeper.
     *
     * @return void
     */
    _cleanupModal: function() {
        if (!this._modal) {
            return;
        }

        App.Main.cleanControllerInstances(this._modal);
    }
});
<|endoftext|>