<commit_before>import React, { Component } from 'react';

// utils
import {Drawer} from '../../common/button/button.js';
import Article from '../../common/article/article.js';
import Module from '../../common/module/module.js';
import Progress from '../../common/progress/progress.js';
import request from '../../common/utils/request.js';
import api from '../../common/utils/api.js';

// styles
import style from './home.css';

// assets
import Cassini from '../../assets/images/CASSINI_THE_GRAND_FINALE.jpg';
import Dawn from '../../assets/images/dawn.jpg';
import Curiosity from '../../assets/images/curiosity.jpg';
import Space from '../../assets/images/space.jpg';
import SunriseSpacewalk from '../../assets/images/sunrise-spacewalk.jpg';
import Github from '../../assets/images/github.png';
import Gmail from '../../assets/images/gmail.png';

// article
const TDATA = [
  {
    title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
    subtitle: 'AUGUST 11, 2016',
    content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
    image: Cassini
  },
  {
    title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
    subtitle: 'AUGUST 11, 2016',
    content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
    image: Dawn
  },
  {
    title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
    subtitle: 'AUGUST 11, 2016',
    content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
    image: Curiosity
  },
  {
    title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
    subtitle: 'AUGUST 11, 2016',
    content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
    image: Space
  }
]

class Home extends Component {

  constructor(props) {
    super(props);
    this.state = {
      articles: [],
      posterHeight: document.documentElement.clientHeight - (document.documentElement.clientWidth < 1025 ? 56 : 90)
    };

    this.onclick = this.onclick.bind(this);
    this._resizePosterHeight = this._resizePosterHeight.bind(this);
    window.addEventListener('resize', this._resizePosterHeight, false);
  }

  componentDidMount() {
    request({
      url: api.article.listall,
      method: 'post'
    }).then(res => {
      this.setState({articles: res || []});
    });
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this._resizePosterHeight, false);
  }

  _resizePosterHeight() {
    this.setState({posterHeight: document.documentElement.clientHeight - (document.documentElement.clientWidth < 1025 ? 56 : 90)});
  }

  onclick(Id) {
    const {history} = this.props;
    history.push("/articles/detail", {Id});
  }

  render() {
    const {articles} = this.state;
    // TODO: THIS IS UGLY
    return (

          {
            /*

 */
          }

            {articles.map((item, index) => {
              return (

 this.onclick(item.Id)}
                  key={index}
                  image={TDATA[index % 4].image}
                  title={item.Title}
                  subtitle={item.Subtitle}
                  content={item.Summary}
                >

              );
            })}

          <Module
            image={Dawn}
            title="Check My Skills"
            subtitle="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
          >

              <Progress title="HTML" percent="60%" explain="heard of it"></Progress>
              <Progress title="JavaScript" percent="80%" explain="did ring a bell"></Progress>
              <Progress title="CSS" percent="40%" explain="yeah yeah i know that"></Progress>
              <Progress title="React Native" percent="50%" explain=" had a try and failed"></Progress>
              <Progress title="Python" percent="40%" explain=" wrote some porn site crawlers with it"></Progress>
              <Progress title="Java" percent="30%" explain="wrote some shit with it"></Progress>
              <Progress title="React" percent="70%" explain="read the docs then quit"></Progress>
              <Progress title="Angular" percent="60%" explain="fail to migrate to Angular2 and quit"></Progress>
              <Progress title="iOS" percent="40%" explain="apis too long, quit"></Progress>
              <Progress title="Algorithm" percent="20%" explain="huh??"></Progress>

          </Module>
          <Module
            title="Contact Me"
            subtitle="open for new opportunity"
          >
          </Module>
          <Module
            image={SunriseSpacewalk}
            title="Contact Me"
            subtitle="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
          >

              <Drawer image={Github} info="https://github.com/MillerDix"></Drawer>
              <Drawer image={Gmail} info="zlw2579@gmail.com"></Drawer>

          </Module>

    );
  }
}

export default Home;
<commit_msg>delete data faker is god<commit_after>import React, { Component } from 'react';

// utils
import {Drawer} from '../../common/button/button.js';
import Article from '../../common/article/article.js';
import Module from '../../common/module/module.js';
import Progress from '../../common/progress/progress.js';
import request from '../../common/utils/request.js';
import api from '../../common/utils/api.js';

// styles
import style from './home.css';

// assets
import Cassini from '../../assets/images/CASSINI_THE_GRAND_FINALE.jpg';
import Dawn from '../../assets/images/dawn.jpg';
import Curiosity from '../../assets/images/curiosity.jpg';
import Space from '../../assets/images/space.jpg';
import SunriseSpacewalk from '../../assets/images/sunrise-spacewalk.jpg';
import Github from '../../assets/images/github.png';
import Gmail from '../../assets/images/gmail.png';

// article
// const TDATA = [
//   {
//     title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
//     subtitle: 'AUGUST 11, 2016',
//     content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
//     image: Cassini
//   },
//   {
//     title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
//     subtitle: 'AUGUST 11, 2016',
//     content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
//     image: Dawn
//   },
//   {
//     title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
//     subtitle: 'AUGUST 11, 2016',
//     content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
//     image: Curiosity
//   },
//   {
//     title: '5 GREAT LESSONS YOU CAN LEARN FROM MUSIC',
//     subtitle: 'AUGUST 11, 2016',
//     content: 'Omnes enim iucundum motum, quo sensus hilaretur. Ego vero isti, inquam, permitto. Qui est in parvis malis. Quid turpius quam sapientis',
//     image: Space
//   }
// ]

class Home extends Component {

  constructor(props) {
    super(props);
    this.state = {
      articles: [],
      posterHeight: document.documentElement.clientHeight - (document.documentElement.clientWidth < 1025 ? 56 : 90)
    };

    this.onclick = this.onclick.bind(this);
    this._resizePosterHeight = this._resizePosterHeight.bind(this);
    window.addEventListener('resize', this._resizePosterHeight, false);
  }

  componentDidMount() {
    request({
      url: api.article.listall,
      method: 'post'
    }).then(res => {
      this.setState({articles: res || []});
    });
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this._resizePosterHeight, false);
  }

  _resizePosterHeight() {
    this.setState({posterHeight: document.documentElement.clientHeight - (document.documentElement.clientWidth < 1025 ? 56 : 90)});
  }

  onclick(Id) {
    const {history} = this.props;
    history.push("/articles/detail", {Id});
  }

  render() {
    const {articles} = this.state;
    // TODO: THIS IS UGLY
    return (

          {
            /*

 */
          }

            {articles.map((item, index) => {
              return (

 this.onclick(item.Id)}
                  key={index}
                  image={item.Poster}
                  title={item.Title}
                  subtitle={item.Subtitle}
                  content={item.Summary}
                >

              );
            })}

          <Module
            image={Dawn}
            title="Check My Skills"
            subtitle="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
          >

              <Progress title="HTML" percent="60%" explain="heard of it"></Progress>
              <Progress title="JavaScript" percent="80%" explain="did ring a bell"></Progress>
              <Progress title="CSS" percent="40%" explain="yeah yeah i know that"></Progress>
              <Progress title="React Native" percent="50%" explain=" had a try and failed"></Progress>
              <Progress title="Python" percent="40%" explain=" wrote some porn site crawlers with it"></Progress>
              <Progress title="Java" percent="30%" explain="wrote some shit with it"></Progress>
              <Progress title="React" percent="70%" explain="read the docs then quit"></Progress>
              <Progress title="Angular" percent="60%" explain="fail to migrate to Angular2 and quit"></Progress>
              <Progress title="iOS" percent="40%" explain="apis too long, quit"></Progress>
              <Progress title="Algorithm" percent="20%" explain="huh??"></Progress>

          </Module>
          <Module
            title="Contact Me"
            subtitle="open for new opportunity"
          >
          </Module>
          <Module
            image={SunriseSpacewalk}
            title="Contact Me"
            subtitle="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
          >

              <Drawer image={Github} info="https://github.com/MillerDix"></Drawer>
              <Drawer image={Gmail} info="zlw2579@gmail.com"></Drawer>

          </Module>

    );
  }
}

export default Home;
<|endoftext|>


<commit_before>
vizwhiz.viz.pie_scatter = function() {

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Public Variables with Default Settings
  //-------------------------------------------------------------------

  var margin = {top: 20, right: 40, bottom: 80, left: 120},
    width = window.innerWidth,
    height = window.innerHeight,
    size = {
      "width": width-margin.left-margin.right,
      "height": height-margin.top-margin.bottom,
      "x": margin.left,
      "y": margin.top
    },
    depth = null,
    value_var = null,
    id_var = null,
    text_var = null,
    xaxis_var = null,
    xaxis_label = "",
    yaxis_var = null,
    yaxis_label = "",
    size_var = null,
    nesting = [],
    stroke = 2,
    dispatch = d3.dispatch('elementMouseover', 'elementMouseout');

  //===================================================================

  function chart(selection) {
    selection.each(function(data) {

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // INIT vars & data munging
      //-------------------------------------------------------------------

      // first clone input so we know we are working with fresh data
      var cloned_data = JSON.parse(JSON.stringify(data));
      // nest the flat data by nesting array
      var nested_data = vizwhiz.utils.nest(cloned_data, nesting, true,
          [{"key":"complexity", "agg":"avg"}, {"key":"distance", "agg":"avg"}]);
      // console.log(nested_data.length)
      // return

      // Select the svg element, if it exists.
      var svg = d3.select(this).selectAll("svg").data([data]);
      var svg_enter = svg.enter().append("svg")
        .attr('width',width)
        .attr('height',height)

      // container for the visualization
      var viz_enter = svg_enter.append("g").attr("class", "viz")
        .attr("transform", "translate(" + size.x + "," + size.y + ")")

      // add grey background for viz
      viz_enter.append("rect")
        .style('stroke','#000')
        .style('stroke-width',1)
        .style('fill','#efefef')
        .attr("class","background")
        .attr('width', size.width)
        .attr('height', size.height)
        .attr('x',0)
        .attr('y',0)

        var size_scale = d3.scale.linear()
          .domain(d3.extent(nested_data, function(d){ return d[size_var]; }))
          .range([10, d3.min([width,height])/10])
          .nice()

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // X AXIS
      //-------------------------------------------------------------------
      var x_scale = d3.scale.linear()
        .domain(d3.extent(nested_data, function(d){ return d[xaxis_var]; }))
        .range([0, size.width])
        .nice()

      // enter
      var xaxis_enter = viz_enter.append("g")
        .attr("class", "xaxis")
        .attr("transform", "translate(0," + size.height + ")")

      // update
      d3.select(".xaxis").call(x_axis.scale(x_scale))

      // label
      xaxis_enter.append('text')
        .attr('width', size.width)
        .attr('x', size.width/2)
        .attr('y', 60)
        .attr('class', 'axis_title x')
        .attr("text-anchor", "middle")
        .style("font-weight", "bold")
        .attr("font-size", "14px")
        .attr("font-family", "Helvetica")
        .attr("fill", "#4c4c4c")
        .text(xaxis_label)

      //===================================================================

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // Y AXIS
      //-------------------------------------------------------------------

      var y_scale = d3.scale.linear()
        .domain(d3.extent(nested_data, function(d){ return d[yaxis_var]; }).reverse())
        .range([0, size.height])
        .nice()

      // enter
      var yaxis_enter = viz_enter.append("g")
        .attr("class", "yaxis")

      // update
      d3.select(".yaxis").call(y_axis.scale(y_scale))

      // label
      yaxis_enter.append('text')
        .attr('width', size.width)
        .attr('class', 'axis_title y')
        .attr("text-anchor", "middle")
        .style("font-weight", "bold")
        .attr("font-size", "14px")
        .attr("font-family", "Helvetica")
        .attr("fill", "#4c4c4c")
        .attr("transform", "translate(" + (-size.x+25) + "," + (size.y+size.height/2) + ") rotate(-90)")
        .text(yaxis_label)

      //===================================================================

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // NODES
      //-------------------------------------------------------------------

      var arc = d3.svg.arc()
        .innerRadius(0)
        .startAngle(0)

      var has_children = nested_data[0].num_children ? true : false;

      var nodes = d3.select("g.viz")
        .selectAll("g.circle")
        .data(nested_data, function(d){ return d[nesting[nesting.length-1]]; })

      var nodes_enter = nodes.enter().append("g")
        .attr("class", "circle")
        .attr("transform", function(d) { return "translate("+x_scale(d[xaxis_var])+","+y_scale(d[yaxis_var])+")" } )

      nodes_enter
        .append("circle")
        .style('stroke', function(d){ return d.color })
        .style('stroke-width', 3)
        .style('fill', function(d){ return d.color })
        .style("fill-opacity", function(d) {
          return d.active ? 0.75 : 0.25;
        })
        .attr("r", function(d){
          return size_scale(d[size_var]);
        })

      nodes_enter
        .append("path")
        .style('fill', function(d){ return d.color })
        .style("fill-opacity", 1)

      // update
      nodes
        .attr("transform", function(d) { return "translate("+x_scale(d[xaxis_var])+","+y_scale(d[yaxis_var])+")" } )

      nodes.selectAll("circle")
        .style('fill', function(d){ return d.color })
        .attr("r", function(d){
          return size_scale(d[size_var]);
        })
        .on("mouseover", function(d){ console.log(d); })

      nodes.selectAll("path")
        .attr("d", function(d){
          var angle = 0, radius = 0;
          if(d.num_children){
            angle = (((d.num_children_active / d.num_children)*360) * (Math.PI/180));
            radius = size_scale(d[size_var]);
          }
          return arc.endAngle(angle).outerRadius(radius)(d);
        })

      // exit
      nodes.exit().remove()

      //===================================================================

    });

    return chart;
  }

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Nest data function (needed for getting flat data ready for stacks)
  //-------------------------------------------------------------------

  function nest_data(xaxis_vals, data){
    var nest_key = nesting[nesting.length-1];
    var info_lookup = {};

    var nested = d3.nest()
      .key(function(d){ return d[nest_key]; })
      .rollup(function(leaves){
        info_lookup[leaves[0][nest_key]] = JSON.parse(JSON.stringify(leaves[0]))
        delete info_lookup[leaves[0][nest_key]][xaxis_var]
        delete info_lookup[leaves[0][nest_key]][value_var]

        var values = d3.nest()
          .key(function(d){ return d.year; })
          .rollup(function(l) { return d3.sum(l, function(d){ return d[value_var]})})
          .entries(leaves)

        // Make sure all years at least have 0 values
        years_available = values
          .reduce(function(a, b){ return a.concat(b.key)}, [])
          .filter(function(y, i, arr) { return arr.indexOf(y) == i })

        xaxis_vals.forEach(function(y){
          if(years_available.indexOf(""+y) < 0){
            values.push({"key": ""+y, "values": 0})
          }
        })

        return values.sort(function(a,b){return a.key-b.key;});
      })
      .entries(data)

    nested.forEach(function(d, i){
      d["total"] = d3.sum(d.values, function(dd){ return dd.values; })
      nested[i] = vizwhiz.utils.merge(d, info_lookup[d.key]);
    })
    return nested

    return nested.sort(function(a,b){
      if(a[sort]<b[sort]) return -1;
      if(a[sort]>b[sort]) return 1;
      return 0;
    });

  }

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Helper function used to create stack polygon
  //-------------------------------------------------------------------

  var stack = d3.layout.stack()
    .offset("zero")
    .values(function(d) { return d.values; })
    .x(function(d) { return parseInt(d.key); })
    .y(function(d) { return d.values; });

  //===================================================================

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // X & Y Axis formatting help
  //-------------------------------------------------------------------

  var x_axis = d3.svg.axis()
    .tickSize(0)
    .tickPadding(15)
    .orient('bottom')
    .tickFormat(function(d, i) {
      d3.select(this)
        .attr("text-anchor","middle")
        .style("font-weight","bold")
        .attr("font-size","12px")
        .attr("font-family","Helvetica")
        .attr("fill","#4c4c4c")
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0)
        .attr("x2", 0)
        .attr("y1", 0+stroke)
        .attr("y2", -size.height-stroke)
        .attr("stroke", "#ccc")
        .attr("stroke-width",stroke/2)
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0)
        .attr("x2", 0)
        .attr("y1", 1)
        .attr("y2", 10)
        .attr("stroke", "#000")
        .attr("stroke-width",1)
      return d
    });

  var y_axis = d3.svg.axis()
    .tickSize(0)
    .tickPadding(15)
    .orient('left')
    .tickFormat(function(d, i) {
      d3.select(this)
        .attr("text-anchor","middle")
        .style("font-weight","bold")
        .attr("font-size","12px")
        .attr("font-family","Helvetica")
        .attr("fill","#4c4c4c")
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0+stroke)
        .attr("x2", 0+size.width-stroke)
        .attr("y1", 0)
        .attr("y2", 0)
        .attr("stroke", "#ccc")
        .attr("stroke-width",stroke/2)
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", -10)
        .attr("x2", 0-stroke)
        .attr("y1", 0)
        .attr("y2", 0)
        .attr("stroke", "#4c4c4c")
        .attr("stroke-width",stroke)
      // return parseFloat(d.toFixed(3))
      return vizwhiz.utils.format_num(d, false, 3, true)
    });

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Expose Public Variables
  //-------------------------------------------------------------------

  chart.dispatch = dispatch;

  chart.width = function(x) {
    if (!arguments.length) return width;
    width = x;
    return chart;
  };

  chart.height = function(x) {
    if (!arguments.length) return height;
    height = x;
    return chart;
  };

  chart.depth = function(x) {
    if (!arguments.length) return depth;
    depth = x;
    return chart;
  };

  chart.value_var = function(x) {
    if (!arguments.length) return value_var;
    value_var = x;
    return chart;
  };

  chart.id_var = function(x) {
    if (!arguments.length) return id_var;
    id_var = x;
    return chart;
  };

  chart.text_var = function(x) {
    if (!arguments.length) return text_var;
    text_var = x;
    return chart;
  };

  chart.xaxis_var = function(x) {
    if (!arguments.length) return xaxis_var;
    xaxis_var = x;
    return chart;
  };

  chart.xaxis_label = function(x) {
    if (!arguments.length) return xaxis_label;
    xaxis_label = x;
    return chart;
  };

  chart.yaxis_var = function(x) {
    if (!arguments.length) return yaxis_var;
    yaxis_var = x;
    return chart;
  };

  chart.yaxis_label = function(x) {
    if (!arguments.length) return yaxis_label;
    yaxis_label = x;
    return chart;
  };

  chart.size_var = function(x) {
    if (!arguments.length) return size_var;
    size_var = x;
    return chart;
  };

  chart.nesting = function(x) {
    if (!arguments.length) return nesting;
    nesting = x;
    return chart;
  };

  chart.margin = function(x) {
    if (!arguments.length) return margin;
    margin.top    = typeof x.top    != 'undefined' ? x.top    : margin.top;
    margin.right  = typeof x.right  != 'undefined' ? x.right  : margin.right;
    margin.bottom = typeof x.bottom != 'undefined' ? x.bottom : margin.bottom;
    margin.left   = typeof x.left   != 'undefined' ? x.left   : margin.left;
    size.width    = width-margin.left-margin.right;
    size.height   = height-margin.top-margin.bottom;
    size.x        = margin.left;
    size.y        = margin.top;
    return chart;
  };

  //===================================================================

  return chart;
};<commit_msg>changes<commit_after>
vizwhiz.viz.pie_scatter = function() {

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Public Variables with Default Settings
  //-------------------------------------------------------------------

  var margin = {top: 20, right: 40, bottom: 80, left: 120},
    width = window.innerWidth,
    height = window.innerHeight,
    size = {
      "width": width-margin.left-margin.right,
      "height": height-margin.top-margin.bottom,
      "x": margin.left,
      "y": margin.top
    },
    depth = null,
    value_var = null,
    id_var = null,
    text_var = null,
    xaxis_var = null,
    xaxis_label = "",
    yaxis_var = null,
    yaxis_label = "",
    size_var = null,
    nesting = [],
    stroke = 2,
    dispatch = d3.dispatch('elementMouseover', 'elementMouseout');

  //===================================================================

  function chart(selection) {
    selection.each(function(data) {

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // INIT vars & data munging
      //-------------------------------------------------------------------

      // first clone input so we know we are working with fresh data
      var cloned_data = JSON.parse(JSON.stringify(data));
      // nest the flat data by nesting array
      var nested_data = vizwhiz.utils.nest(cloned_data, nesting, true,
          [{"key":"complexity", "agg":"avg"}, {"key":"distance", "agg":"avg"}]);
      // console.log(nested_data.length)
      // return

      // Select the svg element, if it exists.
      var svg = d3.select(this).selectAll("svg").data([data]);
      var svg_enter = svg.enter().append("svg")
        .attr('width',width)
        .attr('height',height)

      // container for the visualization
      var viz_enter = svg_enter.append("g").attr("class", "viz")
        .attr("transform", "translate(" + size.x + "," + size.y + ")")

      // add grey background for viz
      viz_enter.append("rect")
        .style('stroke','#000')
        .style('stroke-width',1)
        .style('fill','#efefef')
        .attr("class","background")
        .attr('width', size.width)
        .attr('height', size.height)
        .attr('x',0)
        .attr('y',0)

        var size_scale = d3.scale.linear()
          .domain(d3.extent(nested_data, function(d){ return d[size_var]; }))
          .range([10, d3.min([width,height])/10])
          .nice()

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // X AXIS
      //-------------------------------------------------------------------
      var x_scale = d3.scale.linear()
        .domain(d3.extent(nested_data, function(d){ return d[xaxis_var]; }))
        .range([0, size.width])
        .nice()

      // enter
      var xaxis_enter = viz_enter.append("g")
        .attr("class", "xaxis")
        .attr("transform", "translate(0," + size.height + ")")

      // update
      d3.select(".xaxis").call(x_axis.scale(x_scale))

      // label
      xaxis_enter.append('text')
        .attr('width', size.width)
        .attr('x', size.width/2)
        .attr('y', 60)
        .attr('class', 'axis_title x')
        .attr("text-anchor", "middle")
        .style("font-weight", "bold")
        .attr("font-size", "14px")
        .attr("font-family", "Helvetica")
        .attr("fill", "#4c4c4c")
        .text(xaxis_label)

      //===================================================================

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // Y AXIS
      //-------------------------------------------------------------------

      var y_scale = d3.scale.linear()
        .domain(d3.extent(nested_data, function(d){ return d[yaxis_var]; }).reverse())
        .range([0, size.height])
        .nice()

      // enter
      var yaxis_enter = viz_enter.append("g")
        .attr("class", "yaxis")

      // update
      d3.select(".yaxis").call(y_axis.scale(y_scale))

      // label
      yaxis_enter.append('text')
        .attr('width', size.width)
        .attr('class', 'axis_title y')
        .attr("text-anchor", "middle")
        .style("font-weight", "bold")
        .attr("font-size", "14px")
        .attr("font-family", "Helvetica")
        .attr("fill", "#4c4c4c")
        .attr("transform", "translate(" + (-size.x+25) + "," + (size.y+size.height/2) + ") rotate(-90)")
        .text(yaxis_label)

      //===================================================================

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // NODES
      //-------------------------------------------------------------------

      var arc = d3.svg.arc()
        .innerRadius(0)
        .startAngle(0)

      var has_children = nested_data[0].num_children ? true : false;

      var nodes = d3.select("g.viz")
        .selectAll("g.circle")
        .data(nested_data, function(d){ return d[nesting[nesting.length-1]]; })

      var nodes_enter = nodes.enter().append("g")
        .attr("class", "circle")
        .attr("transform", function(d) { return "translate("+x_scale(d[xaxis_var])+","+y_scale(d[yaxis_var])+")" } )

      nodes_enter
        .append("circle")
        .style('stroke', function(d){ return d.color })
        .style('stroke-width', 3)
        .style('fill', function(d){ return d.color })
        .style("fill-opacity", function(d) {
          return d.active ? 0.75 : 0.25;
        })
        .attr("r", function(d){
          return size_scale(d[size_var]);
        })

      nodes_enter
        .append("path")
        .style('fill', function(d){ return d.color })
        .style("fill-opacity", 1)

      // update
      nodes
        .attr("transform", function(d) { return "translate("+x_scale(d[xaxis_var])+","+y_scale(d[yaxis_var])+")" } )

      nodes.selectAll("circle")
        .style('fill', function(d){ return d.color })
        .attr("r", function(d){
          return size_scale(d[size_var]);
        })
        .on("mouseover", function(d){ console.log(d); })

      nodes.selectAll("path")
        .attr("d", function(d){
          var angle = 0, radius = 0;
          if(d.num_children){
            angle = (((d.num_children_active / d.num_children)*360) * (Math.PI/180));
            radius = size_scale(d[size_var]);
          }
          return arc.endAngle(angle).outerRadius(radius)(d);
        })

      // exit
      nodes.exit().remove()

      //===================================================================

      //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // TICKS
      //-------------------------------------------------------------------

      var ticks = d3.select("g.viz")
        .selectAll("g.ticks")
        .data(nested_data, function(d){ return d[nesting[nesting.length-1]]; })

      var ticks_enter = ticks.enter().append("g")
        .attr("class", "ticks")

      // x ticks
      ticks_enter.append("line")
        .attr("x1", -10)
        .attr("x2", 0)
        .attr("y1", function(d){
          return y_scale(d[yaxis_var])
        })
        .attr("y2", function(d){
          return y_scale(d[yaxis_var])
        })
        .attr("stroke", function(d){
          return d.color;
        })
        .attr("stroke-width", stroke)

      // y ticks
      ticks_enter.append("line")
        .attr("x1", function(d){
          return x_scale(d[xaxis_var])
        })
        .attr("x2", function(d){
          return x_scale(d[xaxis_var])
        })
        .attr("y1", size.height)
        .attr("y2", size.height + 10)
        .attr("stroke", function(d){
          return d.color;
        })
        .attr("stroke-width", stroke)

      //===================================================================

    });

    return chart;
  }

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Nest data function (needed for getting flat data ready for stacks)
  //-------------------------------------------------------------------

  function nest_data(xaxis_vals, data){
    var nest_key = nesting[nesting.length-1];
    var info_lookup = {};

    var nested = d3.nest()
      .key(function(d){ return d[nest_key]; })
      .rollup(function(leaves){
        info_lookup[leaves[0][nest_key]] = JSON.parse(JSON.stringify(leaves[0]))
        delete info_lookup[leaves[0][nest_key]][xaxis_var]
        delete info_lookup[leaves[0][nest_key]][value_var]

        var values = d3.nest()
          .key(function(d){ return d.year; })
          .rollup(function(l) { return d3.sum(l, function(d){ return d[value_var]})})
          .entries(leaves)

        // Make sure all years at least have 0 values
        years_available = values
          .reduce(function(a, b){ return a.concat(b.key)}, [])
          .filter(function(y, i, arr) { return arr.indexOf(y) == i })

        xaxis_vals.forEach(function(y){
          if(years_available.indexOf(""+y) < 0){
            values.push({"key": ""+y, "values": 0})
          }
        })

        return values.sort(function(a,b){return a.key-b.key;});
      })
      .entries(data)

    nested.forEach(function(d, i){
      d["total"] = d3.sum(d.values, function(dd){ return dd.values; })
      nested[i] = vizwhiz.utils.merge(d, info_lookup[d.key]);
    })
    return nested

    return nested.sort(function(a,b){
      if(a[sort]<b[sort]) return -1;
      if(a[sort]>b[sort]) return 1;
      return 0;
    });

  }

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Helper function used to create stack polygon
  //-------------------------------------------------------------------

  var stack = d3.layout.stack()
    .offset("zero")
    .values(function(d) { return d.values; })
    .x(function(d) { return parseInt(d.key); })
    .y(function(d) { return d.values; });

  //===================================================================

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // X & Y Axis formatting help
  //-------------------------------------------------------------------

  var x_axis = d3.svg.axis()
    .tickSize(0)
    .tickPadding(15)
    .orient('bottom')
    .tickFormat(function(d, i) {
      d3.select(this)
        .attr("text-anchor","middle")
        .style("font-weight","bold")
        .attr("font-size","12px")
        .attr("font-family","Helvetica")
        .attr("fill","#4c4c4c")
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0)
        .attr("x2", 0)
        .attr("y1", 0+stroke)
        .attr("y2", -size.height-stroke)
        .attr("stroke", "#ccc")
        .attr("stroke-width",stroke/2)
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0)
        .attr("x2", 0)
        .attr("y1", 1)
        .attr("y2", 10)
        .attr("stroke", "#000")
        .attr("stroke-width",1)
      return d
    });

  var y_axis = d3.svg.axis()
    .tickSize(0)
    .tickPadding(15)
    .orient('left')
    .tickFormat(function(d, i) {
      d3.select(this)
        .attr("text-anchor","middle")
        .style("font-weight","bold")
        .attr("font-size","12px")
        .attr("font-family","Helvetica")
        .attr("fill","#4c4c4c")
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", 0+stroke)
        .attr("x2", 0+size.width-stroke)
        .attr("y1", 0)
        .attr("y2", 0)
        .attr("stroke", "#ccc")
        .attr("stroke-width",stroke/2)
      d3.select(this.parentNode).append("line")
        .attr("class","tick_line")
        .attr("x1", -10)
        .attr("x2", 0-stroke)
        .attr("y1", 0)
        .attr("y2", 0)
        .attr("stroke", "#4c4c4c")
        .attr("stroke-width",stroke)
      // return parseFloat(d.toFixed(3))
      return vizwhiz.utils.format_num(d, false, 3, true)
    });

  //===================================================================

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Expose Public Variables
  //-------------------------------------------------------------------

  chart.dispatch = dispatch;

  chart.width = function(x) {
    if (!arguments.length) return width;
    width = x;
    return chart;
  };

  chart.height = function(x) {
    if (!arguments.length) return height;
    height = x;
    return chart;
  };

  chart.depth = function(x) {
    if (!arguments.length) return depth;
    depth = x;
    return chart;
  };

  chart.value_var = function(x) {
    if (!arguments.length) return value_var;
    value_var = x;
    return chart;
  };

  chart.id_var = function(x) {
    if (!arguments.length) return id_var;
    id_var = x;
    return chart;
  };

  chart.text_var = function(x) {
    if (!arguments.length) return text_var;
    text_var = x;
    return chart;
  };

  chart.xaxis_var = function(x) {
    if (!arguments.length) return xaxis_var;
    xaxis_var = x;
    return chart;
  };

  chart.xaxis_label = function(x) {
    if (!arguments.length) return xaxis_label;
    xaxis_label = x;
    return chart;
  };

  chart.yaxis_var = function(x) {
    if (!arguments.length) return yaxis_var;
    yaxis_var = x;
    return chart;
  };

  chart.yaxis_label = function(x) {
    if (!arguments.length) return yaxis_label;
    yaxis_label = x;
    return chart;
  };

  chart.size_var = function(x) {
    if (!arguments.length) return size_var;
    size_var = x;
    return chart;
  };

  chart.nesting = function(x) {
    if (!arguments.length) return nesting;
    nesting = x;
    return chart;
  };

  chart.margin = function(x) {
    if (!arguments.length) return margin;
    margin.top    = typeof x.top    != 'undefined' ? x.top    : margin.top;
    margin.right  = typeof x.right  != 'undefined' ? x.right  : margin.right;
    margin.bottom = typeof x.bottom != 'undefined' ? x.bottom : margin.bottom;
    margin.left   = typeof x.left   != 'undefined' ? x.left   : margin.left;
    size.width    = width-margin.left-margin.right;
    size.height   = height-margin.top-margin.bottom;
    size.x        = margin.left;
    size.y        = margin.top;
    return chart;
  };

  //===================================================================

  return chart;
};<|endoftext|>


<commit_before>/**
 * @fileoverview Utilities function for debug.
 *
 * NOTE: these code are stripped using compiler prefix feature.
 * See more detail in tools/strip_debug.txt file.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.debug');
goog.provide('ydn.debug.ILogger');

goog.require('goog.debug.Console');
goog.require('goog.debug.DivConsole');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('goog.events.BrowserEvent'); // cause compile error if not.
goog.require('goog.functions');

/**
 * General debug console
 * @type {goog.debug.Console}
 */
ydn.debug.logger_console = null;

/**
 *
 * @type {goog.debug.DivConsole}
 */
ydn.debug.logger_div = null;

/**
 * @type {goog.debug.Logger} logger.
 */
ydn.debug.logger = goog.debug.Logger.getLogger('ydn.debug');

/**
 * Predefined level are: 'ALL' (0) 'FINEST' (300), 'FINER' (ALL), 'FINE' (500),
 * 'CONFIG' (700), 'INFO' (800), 'WARNING', (900)
 * @param {string=} scope eg: 'ydn.db'.
 * @param {string|number=} level
 * @param {Element=} ele display target DIV. If not provided, it log to console.
 */
ydn.debug.log = function(scope, level, ele) {

  scope = scope || 'ydn';
  var log_level = goog.isNumber(level) ? new goog.debug.Logger.Level(
    'log', level) :
      goog.isString(level) ? goog.debug.Logger.Level.getPredefinedLevel(
        level.toUpperCase()) :
          goog.debug.Logger.Level.FINE;

  goog.debug.Logger.getLogger(scope).setLevel(log_level);

  if (goog.isDef(ele)) {
    if (!ydn.debug.logger_div) {
      ydn.debug.logger_div = new goog.debug.DivConsole(ele);
      ydn.debug.logger_div.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  } else {
    if (!ydn.debug.logger_console && !ydn.debug.logger_div) {
      ydn.debug.logger_console = new goog.debug.Console();
      ydn.debug.logger_console.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  }
};

/**
 * @interface
 */
ydn.debug.ILogger = function() {};

/**
 * @param {Object} x
 */
ydn.debug.ILogger.prototype.log = function(x) {};

/**
 * @type {ydn.debug.ILogger}
 */
ydn.debug.ILogger.instance = null;

/**
 * Log data.
 * @param {Object} data
 */
ydn.debug.ILogger.log = function(data) {
  if (ydn.debug.ILogger.instance) {
    setTimeout(function() {
      ydn.debug.ILogger.instance.log(data);
    }, 10);
  }
};

<commit_msg>module panel.<commit_after>/**
 * @fileoverview Utilities function for debug.
 *
 * NOTE: these code are stripped using compiler prefix feature.
 * See more detail in tools/strip_debug.txt file.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.debug');
goog.provide('ydn.debug.ILogger');

goog.require('goog.debug.Console');
goog.require('goog.debug.DivConsole');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('goog.events.BrowserEvent'); // cause compile error if not.
goog.require('goog.functions');

/**
 * General debug console
 * @type {goog.debug.Console}
 */
ydn.debug.logger_console = null;

/**
 *
 * @type {goog.debug.DivConsole}
 */
ydn.debug.logger_div = null;

/**
 * @type {goog.debug.Logger} logger.
 */
ydn.debug.logger = goog.debug.Logger.getLogger('ydn.debug');

/**
 * Predefined level are: 'ALL' (0) 'FINEST' (300), 'FINER' (ALL), 'FINE' (500),
 * 'CONFIG' (700), 'INFO' (800), 'WARNING', (900)
 * @param {string=} scope eg: 'ydn.db'.
 * @param {string|number=} level
 * @param {Element=} ele display target DIV. If not provided, it log to console.
 */
ydn.debug.log = function(scope, level, ele) {

  scope = scope || 'ydn';
  var log_level = goog.isNumber(level) ? new goog.debug.Logger.Level(
    'log', level) :
      goog.isString(level) ? goog.debug.Logger.Level.getPredefinedLevel(
        level.toUpperCase()) :
          goog.debug.Logger.Level.FINE;

  goog.debug.Logger.getLogger(scope).setLevel(log_level);

  if (goog.isDef(ele)) {
    if (!ydn.debug.logger_div) {
      ydn.debug.logger_div = new goog.debug.DivConsole(ele);
      ydn.debug.logger_div.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  } else {
    if (!ydn.debug.logger_console && !ydn.debug.logger_div) {
      ydn.debug.logger_console = new goog.debug.Console();
      ydn.debug.logger_console.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  }
};

/**
 * Set capturing status on default console.
 * @param {boolean} val
 */
ydn.debug.captureOnConsole = function(val) {
  ydn.debug.logger_console.setCapturing(val);
};

/**
 * @interface
 */
ydn.debug.ILogger = function() {};

/**
 * @param {Object} x
 */
ydn.debug.ILogger.prototype.log = function(x) {};

/**
 * @type {ydn.debug.ILogger}
 */
ydn.debug.ILogger.instance = null;

/**
 * Log data.
 * @param {Object} data
 */
ydn.debug.ILogger.log = function(data) {
  if (ydn.debug.ILogger.instance) {
    setTimeout(function() {
      ydn.debug.ILogger.instance.log(data);
    }, 10);
  }
};

<|endoftext|>


<commit_before>'use strict';
const fs = require('fs');
const EventEmitter = require('events');
const helpers = require('./LogfileHelpers');
const Archiver = require('archiver');
const Path = require('path');

//Export LogPuller as a module
module.exports = class LogPuller extends EventEmitter{
  constructor(device) {
    super();
    //var self = this;
    console.log("Created new logpuller");
    this.cancelled = false;
    this.options = {};
  }
  set_log_options(options){
    //@param {object} options - log puller options

    /*Sample options object:
    {
      output_path:"",
      log_folder_name: "",
      solo_logs:false,
      controller_logs:false,
      collect_all_logs:false,
      num_logs:0,
      create_zip:false,
      log_notes:""
    };*/
    this.options = options;
  }

  set_device(device){
    this.device = device;
  }

  set_progress_callback(progressCallback){
    //@param {function} progressCallback - function(int)
    //Callback is called as progress is made on a given task
    //Currently used to update the progress bar and add message
    this.progressCallback = progressCallback;
  }

  start_log_pull(){
    //This method does a few things:
    // - Emits event to notify UI that log pulling has started
    // - Calls pull_logs for Solo and/or controller, depending on selected option
    var self = this;
    console.log("start_log_pull");
    console.log('emitting start-pull');
    if (this.create_log_folders(self) == false){
      this.emit('folder-already-exists');
      return;
    };
    this.emit('start-pull');  //notify UI that the log pull has started

    self.pull_logs(self.device.controller_connection, "controller", self.options.controller_log_folder_path, self.options.controller_logs, ()=>{
      self.pull_logs(self.device.solo_connection, "solo", self.options.solo_log_folder_path, self.options.solo_logs, ()=>{
        // This is the callback called by this.pull_logs when it finishes pulling logs
        self.add_log_notes(()=>{  // Add log notes (skips if there are none)
          self.zip_logs_dir(()=>{ // zip up the logs folder
            console.log("Completed start_log_pull()");
            self.progressCallback(100, "All logs pulled");
            setTimeout(1000, self.progressCallback(0));
            this.emit('cancelled');
          });
        });
      });
    });
  };

  pull_logs(connection, device_name, path, option, cb){
    //@param {object} connection - SSH connection
    //@param {String} log_folder_path - path to log folder
    //@param {function} cb - callback to call when complete
    //using passed connection, this function sets up sftp connection
    //pulls log files into correct path, then calls callback
    var self = this;

    if (!self.cancelled && option){
      var log_folder_path = path;  //depending on options, this folder will already exists when it's created with create_log_folders()
      connection.sftp((err, sftp)=>{
        console.log("Trying to connect to pull logs");
        if (err) {
          self.cancel();
          throw err;
        }
        let base_path = '/log';

        // partially apply our progressCallback before passing it to asyncFilePull
        let progress = percentage => self.progressCallback(percentage, `Transfering files from ${device_name}...`);
        let isCancelled = ()=>{  // Wrap this method so we don't have to pass context to the helper function to call this method
          return self.isCancelled();
        }
        sftp.readdir(base_path, (err, list)=>{  // /log contains logs on solo and controller
          if (err) {
            console.log("Couldn't find /log directory to pull files from");
            self.cancel();
            throw err;
          }
          let file_list = helpers.fileListFromDirList(list, self.options.collect_all_logs, self.options.num_logs);
          helpers.asyncFilePull(sftp, file_list, base_path, log_folder_path, isCancelled, progress, ()=>{
            if (device_name == "solo"){ // If we're pulling logs from Solo, check to see if we have any R10C data available
              base_path = '/data/r10c';
              sftp.readdir(base_path, (err, list)=>{
                if (err) {
                  console.log("couldn't find R10C data on Solo");
                  cb();
                } else {
                  console.log(list);
                  file_list = helpers.fileListFromDirList(list, true, null);
                  console.log("Should be some text files: " + file_list);
                  helpers.asyncFilePull(sftp, file_list, base_path, Path.dirname(log_folder_path) + "/geodata", isCancelled, ()=>{},()=>{
                    cb();
                  });
                }
              })
            } else cb(); // If we're not pulling from Solo (we're pulling from controller), return here
          });
        });
      });
    } else {
      console.log("didn't want to collect logs from ", device_name);
      cb();
    }
  };

  cancel(){
    //Cancels any current logpull operation
    console.log("LogPuller.cancel()");
    this.progressCallback(0, "Log transfer cancelled");
    setTimeout(2500, this.progressCallback(0, ''));
    this.emit('cancelled');
    this.cancelled = true;
  };

  isCancelled(){
    return this.cancelled;
  }

  create_log_folders(context){
    //Synchronous log folder creation
    //Top level folder is provided in options.log_folder_name (timestamp-based)
    //Creates subfolders as needed for controller and solo logs based on options
    // Returns true if successful, false if not
    var self = context;
    console.log("create_log_folder() called - trying to create a folder for the logs");
    console.log("folder name: " + self.options.log_folder_name);
    try {
      fs.mkdirSync(self.options.log_folder_name);
      if(this.options.controller_logs){
        this.options.controller_log_folder_path = self.options.log_folder_name + "/controller";
        fs.mkdirSync(this.options.controller_log_folder_path);
      }
      if (this.options.solo_logs){
        this.options.solo_log_folder_path = self.options.log_folder_name + "/solo";
        fs.mkdirSync(this.options.solo_log_folder_path);
        fs.mkdirSync(this.options.log_folder_name + "/geodata");
      }
      return true;
    } catch(err){
      console.log("err in folder creation: ", err);
      if (err.code != 'EEXIST'){
        console.log("Unknown error trying to create log folder - ", err);
      } else {
          console.log("Log folder already exists. ");
      }
      return false;
    }
  };

  add_log_notes(cb){
    let versions = this.device.versions;
    let version_strings = [
      "Sololink: " + versions.sololink_version,
      "Gimbal: " + versions.gimbal_version,
      "AccessoryKit: " + versions.ak_version,
      "Shotmanager: " + versions.shotmanager_version,
      "Pixhawk: " + versions.pixhawk_version,
      "Controller: " + versions.controller_version
    ];

    let version_message = "Retrieved: " + helpers.generate_date_string() + "\n\nVersions \n–––––––––\n" + version_strings.join("\n");
    if (this.options.log_notes.length >1){
      version_message += "\n\n Notes\n––––––––\n" + this.options.log_notes;
    }

    fs.writeFile(this.options.log_folder_name + "/Notes.txt", version_message, (err)=>{
      console.log(this.options.log_folder_name + "/Notes.txt");
      if (err){
        console.log("Failed to write notes.txt");
        console.log(err);
      }
      cb();
    });
  }

  zip_logs_dir(cb){
    //Zip the log files
    if (this.options.create_zip && !this.cancelled){
      console.log("zipping logfiles...");
      var zipdir_path = this.options.log_folder_name;  //step up one level from the folder with logs
      console.log("zipdir_path - ", zipdir_path);
      var zipdir = "/" + this.options.log_folder_name.split("/")[this.options.log_folder_name.split("/").length - 1];  //get just the timestamp filename
      console.log("zipdir - ", zipdir);
      var zipfile = this.options.output_path + zipdir + ".zip";
      console.log('zipfile - ', zipfile);
      var zipper = Archiver.create('zip', {});

      var out_stream = fs.createWriteStream(zipfile, {flags: 'w'});
      zipper.pipe(out_stream);
      zipper.directory(zipdir_path, zipdir); //put the files in the root of the zipdir
      console.log("made it past zipper.directory...");
      out_stream.on('close', ()=>{ //When we call finalize() below, the 'close' event will be emitted.
        console.log("zipfile write stream closed");
        cb();
      })
      zipper.finalize();
    } else cb();
  }
  //end of class
};
<commit_msg>create unique log folder names<commit_after>'use strict';
const fs = require('fs');
const EventEmitter = require('events');
const helpers = require('./LogfileHelpers');
const Archiver = require('archiver');
const Path = require('path');

//Export LogPuller as a module
module.exports = class LogPuller extends EventEmitter{
  constructor(device) {
    super();
    //var self = this;
    console.log("Created new logpuller");
    this.cancelled = false;
    this.options = {};
  }
  set_log_options(options){
    //@param {object} options - log puller options

    /*Sample options object:
    {
      output_path:"",
      log_folder_name: "",
      solo_logs:false,
      controller_logs:false,
      collect_all_logs:false,
      num_logs:0,
      create_zip:false,
      log_notes:""
    };*/
    this.options = options;
  }

  set_device(device){
    this.device = device;
  }

  set_progress_callback(progressCallback){
    //@param {function} progressCallback - function(int)
    //Callback is called as progress is made on a given task
    //Currently used to update the progress bar and add message
    this.progressCallback = progressCallback;
  }

  start_log_pull(){
    //This method does a few things:
    // - Emits event to notify UI that log pulling has started
    // - Calls pull_logs for Solo and/or controller, depending on selected option
    var self = this;
    console.log("start_log_pull");
    console.log('emitting start-pull');
    if (this.create_log_folders(self) == false){
      this.emit('folder-already-exists');
      return;
    };
    this.emit('start-pull');  //notify UI that the log pull has started

    self.pull_logs(self.device.controller_connection, "controller", self.options.controller_log_folder_path, self.options.controller_logs, ()=>{
      self.pull_logs(self.device.solo_connection, "solo", self.options.solo_log_folder_path, self.options.solo_logs, ()=>{
        // This is the callback called by this.pull_logs when it finishes pulling logs
        self.add_log_notes(()=>{  // Add log notes (skips if there are none)
          self.zip_logs_dir(()=>{ // zip up the logs folder
            console.log("Completed start_log_pull()");
            self.progressCallback(100, "All logs pulled");
            setTimeout(1000, self.progressCallback(0));
            this.emit('cancelled');
          });
        });
      });
    });
  };

  pull_logs(connection, device_name, path, option, cb){
    //@param {object} connection - SSH connection
    //@param {String} log_folder_path - path to log folder
    //@param {function} cb - callback to call when complete
    //using passed connection, this function sets up sftp connection
    //pulls log files into correct path, then calls callback
    var self = this;

    if (!self.cancelled && option){
      var log_folder_path = path;  //depending on options, this folder will already exists when it's created with create_log_folders()
      connection.sftp((err, sftp)=>{
        console.log("Trying to connect to pull logs");
        if (err) {
          self.cancel();
          throw err;
        }
        let base_path = '/log';

        // partially apply our progressCallback before passing it to asyncFilePull
        let progress = percentage => self.progressCallback(percentage, `Transfering files from ${device_name}...`);
        let isCancelled = ()=>{  // Wrap this method so we don't have to pass context to the helper function to call this method
          return self.isCancelled();
        }
        sftp.readdir(base_path, (err, list)=>{  // /log contains logs on solo and controller
          if (err) {
            console.log("Couldn't find /log directory to pull files from");
            self.cancel();
            throw err;
          }
          let file_list = helpers.fileListFromDirList(list, self.options.collect_all_logs, self.options.num_logs);
          helpers.asyncFilePull(sftp, file_list, base_path, log_folder_path, isCancelled, progress, ()=>{
            if (device_name == "solo"){ // If we're pulling logs from Solo, check to see if we have any R10C data available
              base_path = '/data/r10c';
              sftp.readdir(base_path, (err, list)=>{
                if (err) {
                  console.log("couldn't find R10C data on Solo");
                  cb();
                } else {
                  console.log(list);
                  file_list = helpers.fileListFromDirList(list, true, null);
                  console.log("Should be some text files: " + file_list);
                  helpers.asyncFilePull(sftp, file_list, base_path, Path.dirname(log_folder_path) + "/geodata", isCancelled, ()=>{},()=>{
                    cb();
                  });
                }
              })
            } else cb(); // If we're not pulling from Solo (we're pulling from controller), return here
          });
        });
      });
    } else {
      console.log("didn't want to collect logs from ", device_name);
      cb();
    }
  };

  cancel(){
    //Cancels any current logpull operation
    console.log("LogPuller.cancel()");
    this.progressCallback(0, "Log transfer cancelled");
    setTimeout(2500, this.progressCallback(0, ''));
    this.emit('cancelled');
    this.cancelled = true;
  };

  isCancelled(){
    return this.cancelled;
  }

  create_log_folders(context){
    //Synchronous log folder creation
    //Top level folder is provided in options.log_folder_name (timestamp-based)
    //Creates subfolders as needed for controller and solo logs based on options
    // Returns true if successful, false if not
    var self = context;
    console.log("create_log_folder() called - trying to create a folder for the logs");
    console.log("folder name: " + self.options.log_folder_name);

    let created = false;
    let count = 2;
    while (!created){
      try {
        fs.mkdirSync(self.options.log_folder_name);
        created = true;  //the log folder was created successfully
        if(this.options.controller_logs){
          this.options.controller_log_folder_path = self.options.log_folder_name + "/controller";
          fs.mkdirSync(this.options.controller_log_folder_path);
        }
        if (this.options.solo_logs){
          this.options.solo_log_folder_path = self.options.log_folder_name + "/solo";
          fs.mkdirSync(this.options.solo_log_folder_path);
          fs.mkdirSync(this.options.log_folder_name + "/geodata");
        }
      } catch(err){
        if(err.code != 'EEXIST') {  // We had an erro trying to create the log folder
          console.log("Unknown error trying to create log folder - ", err);
          return false
        } else {
          let dirName = self.options.log_folder_name;
          let newDirName = dirName
          if (dirName.endsWith(")")){
            newDirName = dirName.slice(0, dirName.lastIndexOf('('));
          }
          console.log(`newDirName: ${newDirName}`);
          self.options.log_folder_name = newDirName + ` (${count})`;
          count += 1;
        }
      }
    }
    // success creating the log folders; return true
    return true;
  };

  add_log_notes(cb){
    let versions = this.device.versions;
    let version_strings = [
      "Sololink: " + versions.sololink_version,
      "Gimbal: " + versions.gimbal_version,
      "AccessoryKit: " + versions.ak_version,
      "Shotmanager: " + versions.shotmanager_version,
      "Pixhawk: " + versions.pixhawk_version,
      "Controller: " + versions.controller_version
    ];

    let version_message = "Retrieved: " + helpers.generate_date_string() + "\n\nVersions \n–––––––––\n" + version_strings.join("\n");
    if (this.options.log_notes.length >1){
      version_message += "\n\n Notes\n––––––––\n" + this.options.log_notes;
    }

    fs.writeFile(this.options.log_folder_name + "/Notes.txt", version_message, (err)=>{
      console.log(this.options.log_folder_name + "/Notes.txt");
      if (err){
        console.log("Failed to write notes.txt");
        console.log(err);
      }
      cb();
    });
  }

  zip_logs_dir(cb){
    //Zip the log files
    if (this.options.create_zip && !this.cancelled){
      console.log("zipping logfiles...");
      var zipdir_path = this.options.log_folder_name;  //step up one level from the folder with logs
      console.log("zipdir_path - ", zipdir_path);
      var zipdir = "/" + this.options.log_folder_name.split("/")[this.options.log_folder_name.split("/").length - 1];  //get just the timestamp filename
      console.log("zipdir - ", zipdir);
      var zipfile = this.options.output_path + zipdir + ".zip";
      console.log('zipfile - ', zipfile);
      var zipper = Archiver.create('zip', {});

      var out_stream = fs.createWriteStream(zipfile, {flags: 'w'});
      zipper.pipe(out_stream);
      zipper.directory(zipdir_path, zipdir); //put the files in the root of the zipdir
      console.log("made it past zipper.directory...");
      out_stream.on('close', ()=>{ //When we call finalize() below, the 'close' event will be emitted.
        console.log("zipfile write stream closed");
        cb();
      })
      zipper.finalize();
    } else cb();
  }
  //end of class
};
<|endoftext|>


<commit_before>var groups = []

// Save attempts to POST or PUT to /groups/
function save(idx) {
    var targets = []
    $.each($("#targetsTable").DataTable().rows().data(), function(i, target) {
        targets.push({
            first_name: unescapeHtml(target[0]),
            last_name: unescapeHtml(target[1]),
            email: unescapeHtml(target[2]),
            position: unescapeHtml(target[3])
        })
    })
    var group = {
            name: $("#name").val(),
            targets: targets
        }
        // Submit the group
    if (idx != -1) {
        // If we're just editing an existing group,
        // we need to PUT /groups/:id
        group.id = groups[idx].id
        api.groupId.put(group)
            .success(function(data) {
                successFlash("Group updated successfully!")
                load()
                dismiss()
                $("#modal").modal('hide')
            })
            .error(function(data) {
                modalError(data.responseJSON.message)
            })
    } else {
        // Else, if this is a new group, POST it
        // to /groups
        api.groups.post(group)
            .success(function(data) {
                successFlash("Group added successfully!")
                load()
                dismiss()
                $("#modal").modal('hide')
            })
            .error(function(data) {
                modalError(data.responseJSON.message)
            })
    }
}

function dismiss() {
    $("#targetsTable").dataTable().DataTable().clear().draw()
    $("#name").val("")
    $("#modal\\.flashes").empty()
}

function edit(idx) {
    targets = $("#targetsTable").dataTable({
        destroy: true, // Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy
        columnDefs: [{
            orderable: false,
            targets: "no-sort"
        }]
    })
    $("#modalSubmit").unbind('click').click(function() {
        save(idx)
    })
    if (idx == -1) {
        group = {}
    } else {
        group = groups[idx]
        $("#name").val(group.name)
        $.each(group.targets, function(i, record) {
            targets.DataTable()
                .row.add([
                    escapeHtml(record.first_name),
                    escapeHtml(record.last_name),
                    escapeHtml(record.email),
                    escapeHtml(record.position),
                    ''
                ]).draw()
        });
    }
    // Handle file uploads
    $("#csvupload").fileupload({
        dataType: "json",
        add: function(e, data) {
            $("#modal\\.flashes").empty()
            var acceptFileTypes = /(csv|txt)$/i;
            var filename = data.originalFiles[0]['name']
            if (filename && !acceptFileTypes.test(filename.split(".").pop())) {
                modalError("Unsupported file extension (use .csv or .txt)")
                return false;
            }
            data.submit();
        },
        done: function(e, data) {
            $.each(data.result, function(i, record) {
                targets.DataTable()
                    .row.add([
                        escapeHtml(record.first_name),
                        escapeHtml(record.last_name),
                        escapeHtml(record.email),
                        escapeHtml(record.position),
                        ''
                    ]).draw()
            });
        }
    })
}

function deleteGroup(idx) {
    if (confirm("Delete " + groups[idx].name + "?")) {
        api.groupId.delete(groups[idx].id)
            .success(function(data) {
                successFlash(data.message)
                load()
            })
    }
}

function load() {
    $("#groupTable").hide()
    $("#emptyMessage").hide()
    $("#loading").show()
    api.groups.get()
        .success(function(gs) {
            $("#loading").hide()
            if (gs.length > 0) {
                groups = gs
                $("#emptyMessage").hide()
                $("#groupTable").show()
                groupTable = $("#groupTable").DataTable({
                    destroy: true,
                    columnDefs: [{
                        orderable: false,
                        targets: "no-sort"
                    }]
                });
                groupTable.clear();
                $.each(groups, function(i, group) {
                    var targets = ""
                    $.each(group.targets, function(i, target) {
                        targets += target.email + ", "
                        if (targets.length > 50) {
                            targets = targets.slice(0, -3) + "..."
                            return false;
                        }
                    })
                    groupTable.row.add([
                        escapeHtml(group.name),
                        escapeHtml(targets),
                        moment(group.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
                        "
<button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\
                    \
                    </button>\
                    <button class='btn btn-danger' onclick='deleteGroup(" + i + ")'>\
                    \
                    </button>
"
                    ]).draw()
                })
            } else {
                $("#emptyMessage").show()
            }
        })
        .error(function() {
            errorFlash("Error fetching groups")
        })
}

$(document).ready(function() {
    load()
    // Setup the event listeners
    // Handle manual additions
    $("#targetForm").submit(function() {
        // Create new data row.
        var emailInput = escapeHtml($("#email").val()).toLowerCase();
        var newRow = [
            escapeHtml($("#firstName").val()),
            escapeHtml($("#lastName").val()),
            emailInput,
            escapeHtml($("#position").val()),
            ''
        ];

        // Check table to see if email already exists.
        var targetsTable = targets.DataTable();
        var existingRowIndex = targetsTable
            .column(2, {order: "index"}) // Email column
            .data()
            .indexOf(emailInput);

        // Update or add new row as necessary.
        if (existingRowIndex >= 0) {
            targetsTable
                .row(existingRowIndex, {order: "index"})
                .data(newRow);
        } else {
            targetsTable.row.add(newRow);
        }
        targetsTable.draw();

        // Reset user input.
        $("#targetForm>div>input").val('');
        $("#firstName").focus();
        return false;
    });
    // Handle Deletion
    $("#targetsTable").on("click", "span>i.fa-trash-o", function() {
        targets.DataTable()
            .row($(this).parents('tr'))
            .remove()
            .draw();
    });
    $("#modal").on("hide.bs.modal", function() {
        dismiss();
    });
});
<commit_msg>Add/update table when adding targets via CSV too<commit_after>var groups = []

// Save attempts to POST or PUT to /groups/
function save(idx) {
    var targets = []
    $.each($("#targetsTable").DataTable().rows().data(), function(i, target) {
        targets.push({
            first_name: unescapeHtml(target[0]),
            last_name: unescapeHtml(target[1]),
            email: unescapeHtml(target[2]),
            position: unescapeHtml(target[3])
        })
    })
    var group = {
            name: $("#name").val(),
            targets: targets
        }
        // Submit the group
    if (idx != -1) {
        // If we're just editing an existing group,
        // we need to PUT /groups/:id
        group.id = groups[idx].id
        api.groupId.put(group)
            .success(function(data) {
                successFlash("Group updated successfully!")
                load()
                dismiss()
                $("#modal").modal('hide')
            })
            .error(function(data) {
                modalError(data.responseJSON.message)
            })
    } else {
        // Else, if this is a new group, POST it
        // to /groups
        api.groups.post(group)
            .success(function(data) {
                successFlash("Group added successfully!")
                load()
                dismiss()
                $("#modal").modal('hide')
            })
            .error(function(data) {
                modalError(data.responseJSON.message)
            })
    }
}

function dismiss() {
    $("#targetsTable").dataTable().DataTable().clear().draw()
    $("#name").val("")
    $("#modal\\.flashes").empty()
}

function edit(idx) {
    targets = $("#targetsTable").dataTable({
        destroy: true, // Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy
        columnDefs: [{
            orderable: false,
            targets: "no-sort"
        }]
    })
    $("#modalSubmit").unbind('click').click(function() {
        save(idx)
    })
    if (idx == -1) {
        group = {}
    } else {
        group = groups[idx]
        $("#name").val(group.name)
        $.each(group.targets, function(i, record) {
            targets.DataTable()
                .row.add([
                    escapeHtml(record.first_name),
                    escapeHtml(record.last_name),
                    escapeHtml(record.email),
                    escapeHtml(record.position),
                    ''
                ]).draw()
        });
    }
    // Handle file uploads
    $("#csvupload").fileupload({
        dataType: "json",
        add: function(e, data) {
            $("#modal\\.flashes").empty()
            var acceptFileTypes = /(csv|txt)$/i;
            var filename = data.originalFiles[0]['name']
            if (filename && !acceptFileTypes.test(filename.split(".").pop())) {
                modalError("Unsupported file extension (use .csv or .txt)")
                return false;
            }
            data.submit();
        },
        done: function(e, data) {
            $.each(data.result, function(i, record) {
                addTarget(
                    record.first_name,
                    record.last_name,
                    record.email,
                    record.position);
            });
        }
    })
}

function deleteGroup(idx) {
    if (confirm("Delete " + groups[idx].name + "?")) {
        api.groupId.delete(groups[idx].id)
            .success(function(data) {
                successFlash(data.message)
                load()
            })
    }
}

function addTarget(firstNameInput, lastNameInput, emailInput, positionInput) {
    // Create new data row.
    var email = escapeHtml(emailInput).toLowerCase();
    var newRow = [
        escapeHtml(firstNameInput),
        escapeHtml(lastNameInput),
        email,
        escapeHtml(positionInput),
        ''
    ];

    // Check table to see if email already exists.
    var targetsTable = targets.DataTable();
    var existingRowIndex = targetsTable
        .column(2, {order: "index"}) // Email column has index of 2
        .data()
        .indexOf(email);

    // Update or add new row as necessary.
    if (existingRowIndex >= 0) {
        targetsTable
            .row(existingRowIndex, {order: "index"})
            .data(newRow);
    } else {
        targetsTable.row.add(newRow);
    }
    targetsTable.draw();
}

function load() {
    $("#groupTable").hide()
    $("#emptyMessage").hide()
    $("#loading").show()
    api.groups.get()
        .success(function(gs) {
            $("#loading").hide()
            if (gs.length > 0) {
                groups = gs
                $("#emptyMessage").hide()
                $("#groupTable").show()
                groupTable = $("#groupTable").DataTable({
                    destroy: true,
                    columnDefs: [{
                        orderable: false,
                        targets: "no-sort"
                    }]
                });
                groupTable.clear();
                $.each(groups, function(i, group) {
                    var targets = ""
                    $.each(group.targets, function(i, target) {
                        targets += target.email + ", "
                        if (targets.length > 50) {
                            targets = targets.slice(0, -3) + "..."
                            return false;
                        }
                    })
                    groupTable.row.add([
                        escapeHtml(group.name),
                        escapeHtml(targets),
                        moment(group.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
                        "
<button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\
                    \
                    </button>\
                    <button class='btn btn-danger' onclick='deleteGroup(" + i + ")'>\
                    \
                    </button>
"
                    ]).draw()
                })
            } else {
                $("#emptyMessage").show()
            }
        })
        .error(function() {
            errorFlash("Error fetching groups")
        })
}

$(document).ready(function() {
    load()
    // Setup the event listeners
    // Handle manual additions
    $("#targetForm").submit(function() {
        addTarget(
            $("#firstName").val(),
            $("#lastName").val(),
            $("#email").val(),
            $("#position").val());

        // Reset user input.
        $("#targetForm>div>input").val('');
        $("#firstName").focus();
        return false;
    });
    // Handle Deletion
    $("#targetsTable").on("click", "span>i.fa-trash-o", function() {
        targets.DataTable()
            .row($(this).parents('tr'))
            .remove()
            .draw();
    });
    $("#modal").on("hide.bs.modal", function() {
        dismiss();
    });
});
<|endoftext|>