<commit_before>var graph = require('fbgraph');
var _ = require('underscore');
var json2csv = require('json2csv');
var async = require('async');
var fs = require('fs');

var config = {
  version: '2.5',
  post_limit: 25,
  comment_limit: 500, // If this is too big, you may get errors stating "Please reduce the amount of data you're asking for, then retry your request"
  refresh_interval_ms: 1000*60,
  backup_interval_ms: 1000*60*10,
  alive_age_ms: 1000*60*60*24,
  state_backup_file: './monitor.backup',
  post_export_file: './monitor_posts.csv',
  comment_export_file: './monitor_comments.csv',
  access_token: 'APP_ID|APP_SECRET',
  sources: [
    'some_fb_page',
    'some_fb_group',
    'not_a_user_though',
    'nytimes'
  ],
  debug: false
}

var state = {
  requests: [],
  live_posts: {},
  posts: {},
  comments: {}
};

var post_params = {
  fields: 'id,created_time,updated_time,message,comments.summary(true).filter(stream),likes.summary(true),shares',
  limit: config.post_limit
};

var comment_params = {
  fields: 'id,created_time,from,likes.summary(true),message',
  limit: config.comment_limit,
  filter: 'stream' // Get comment replies too
};

function format_post(source_id) {
  return function(post) {
    return {
      source_id: source_id,
      id: post.id,
      created_time: post.created_time,
      updated_time: post.updated_time,
      likes: post.likes.summary.total_count,
      comments: post.comments.summary.total_count,
      shares: post.shares ? post.shares.count
                          : 0,
      message: post.message
    };
  };
}

function format_comment(post_id) {
  return function(comment) {
    return {
      post_id: post_id,
      id: comment.id,
      created_time: comment.created_time,
      from_id: comment.from.id,
      likes: comment.likes.summary.total_count,
      message: comment.message
    };
  };
}

function handle_error(err) {
  if(!err) {
    return;
  }

  if(typeof err === 'object') {
    console.error(err);
    err = err.message;
  } else {
    err = err.toString();
    console.error(err);
  }

  if(!debug) {
    throw new Error(err);
  }
}

function record_request() {
  state.requests.push(new Date());
}

function log_requests() {
  state.requests = state.requests.filter(function(when) {
    var ms_per_hour = 1000*60*60;
    return (new Date() - when) < ms_per_hour;
  });
  console.log(state.requests.length + ' requests in the last hour.');
}

function updated_recently(post) {
  return (new Date() - new Date(post.updated_time)) < config.alive_age_ms;
}

function get(path, params, after, callback) {
  if(after) {
    params = _.clone(params);
    params.after = after;
  }

  graph.get(path, params, function(err, res) {
    if(err && err.is_transient) {
      console.log('Got a transient error; retrying ' + path);
      return get(path, params, after, callback);
    }

    if(err) {
      console.error('Got an error while processing ' + path);
      return handle_error(err);
    }

    if(!res) {
      return handle_error('No response object!');
    }

    record_request();
    callback(res);
    module.exports.debug.res = res;
  });
}

// Takes an array of objects of the form
//   [ {id: 'foo', ...}, {id: 'bar', ...}, ...]
// and produces
//   { 'foo': {id: 'foo', ...},
//     'bar': {id: 'bar', ...} }
function use_ids_as_keys(arr) {
  var ids = _.map(arr, function(obj) {
    return obj.id;
  });
  return _.object(_.zip(ids, arr));
}

function get_comments(post_id, after, callback) {
  get('/' + post_id + '/comments', comment_params, after, function(res) {
    console.log('Got ' + res.data.length + ' comments');
    var new_comments_arr = _.map(res.data, format_comment(post_id));
    state.comments = _.extend(state.comments,
                              use_ids_as_keys(new_comments_arr));

    if(res && res.paging && res.paging.cursors && res.paging.cursors.after) {
      var new_after = res.paging.cursors.after;
      if(after !== new_after) {
        get_comments(post_id, new_after, callback);
      }
    } else {
      console.log('Got all comments.');
      callback();
    }
  });
}

// Just the last 25
function get_posts(source_id) {
  get('/' + source_id + '/feed', post_params, null, function(res) {
    console.log('Got ' + res.data.length + ' posts from ' + source_id);

    var post_records = _.map(res.data, format_post(source_id));

    _.each(post_records, function(post) {
      var maybe_new_comments = !state.posts[post.id] ||
                               (state.posts[post.id].updated_time !==
                                post.updated_time)
      if(maybe_new_comments) {
        state.live_posts[post.id] = post;
      }
      state.posts[post.id] = post;
    });
  });
}

function get_all_comments(post, callback) {
  // By passing null here, the first request gets the oldest comments, and
  // recursive requests get all of them.
  get_comments(post.id, null, callback);
}

function prune_posts() {
  var alive_and_dying = _.partition(_.values(state.live_posts),
                                    updated_recently);
  state.live_posts = use_ids_as_keys(alive_and_dying[0]);
  async.each(alive_and_dying[1], get_all_comments);
  console.log('Pruned ' + alive_and_dying[1].length + ' posts. ' +
              alive_and_dying[0].length + ' posts remain alive.');
}

function check_all_sources() {
  _.each(config.sources, get_posts);
}

var save = fs.writeFile;

// Save the state of this tool so it can be restarted easily.
function export_state(filename) {
  if(!filename) {
    filename = config.state_backup_file;
  }
  save(filename, JSON.stringify(state), handle_error);
}

function load_state(filename, callback) {
  if(typeof filename === 'function') {
    callback = filename;
    filename = null;
  }

  if(!filename) {
    filename = config.state_backup_file;
  }

  fs.readFile(filename, function(err, data) {
    if(err && callback) {
      return callback(err);
    }

    if(err) {
      return handle_error(err);
    }

    state = JSON.parse(data)

    if(callback) {
      callback();
    }
  });
}

function save_live_post_comments(callback) {
  async.each(state.live_posts, get_all_comments, callback);
}

function save_csv(filename, obj) {
  var arr = _.values(obj);
  json2csv({ data: arr, fields: _.keys(arr[0]) }, function(err, csv) {
    if(err) {
      return handle_error(err);
    }
    save(filename, csv, handle_error);
  });
}

// Export all data as two csv files, including comments from live posts
function export_all() {
  save_live_post_comments(function(err) {
    if(err) {
      return handle_error(err);
    }
    save_csv(config.post_export_file, state.posts);
    save_csv(config.comment_export_file, state.comments);
    console.log('Export complete');
  });
}

function initialize() {
  graph.setVersion(config.version);
  graph.setAccessToken(config.access_token);
}

function start(new_config) {
  if(new_config) {
    config = _.extend(config, new_config);
  }
  initialize();
  setInterval(check_all_sources, config.refresh_interval_ms);
  setInterval(prune_posts, config.refresh_interval_ms);
  setInterval(log_requests, config.refresh_interval_ms);
  setInterval(export_state, config.backup_interval_ms);
}

// Note: like most debug tools, you have to call monitor.debug.initialize()
// before this.
function test_sources() {
  var posts = 0;
  async.each(config.sources, function(source_id, callback) {
    get('/' + source_id + '/feed', post_params, null, function(res) {
      if(res.data.length === 0) {
        callback(new Error('Source ' + source_id + ' returned 0 posts'));
      } else {
        posts += res.data.length;
        callback();
      }
    });
  }, function(err) {
    if(err) {
      handle_error(err);
    } else {
      console.log('Successfully got posts (' + posts + ') from all sources (' + config.sources.length + ').');
      if(config.sources.length * config.post_limit !== posts) {
        console.log('Warning: hoped to get ' + (config.sources.length * config.post_limit) + ' posts, but only got ' + posts + '.');
      }
    }
  });
}

// For testing rate limits
function getlots(n) {
  _.each(_.range(n), function() {
    get('/nytimes/feed', post_params, null, function() {});
  });
}

function get_state() {
  return state;
}

module.exports = {
  start: start,
  export_all: export_all,
  load_state: load_state,
  debug: {
    initialize: initialize,
    getlots: getlots,
    test_sources: test_sources,
    save_live_post_comments: save_live_post_comments,
    export_state: export_state,
    get_state: get_state
  }
};
<commit_msg>Fix typo<commit_after>var graph = require('fbgraph');
var _ = require('underscore');
var json2csv = require('json2csv');
var async = require('async');
var fs = require('fs');

var config = {
  version: '2.5',
  post_limit: 25,
  comment_limit: 500, // If this is too big, you may get errors stating "Please reduce the amount of data you're asking for, then retry your request"
  refresh_interval_ms: 1000*60,
  backup_interval_ms: 1000*60*10,
  alive_age_ms: 1000*60*60*24,
  state_backup_file: './monitor.backup',
  post_export_file: './monitor_posts.csv',
  comment_export_file: './monitor_comments.csv',
  access_token: 'APP_ID|APP_SECRET',
  sources: [
    'some_fb_page',
    'some_fb_group',
    'not_a_user_though',
    'nytimes'
  ],
  debug: false
}

var state = {
  requests: [],
  live_posts: {},
  posts: {},
  comments: {}
};

var post_params = {
  fields: 'id,created_time,updated_time,message,comments.summary(true).filter(stream),likes.summary(true),shares',
  limit: config.post_limit
};

var comment_params = {
  fields: 'id,created_time,from,likes.summary(true),message',
  limit: config.comment_limit,
  filter: 'stream' // Get comment replies too
};

function format_post(source_id) {
  return function(post) {
    return {
      source_id: source_id,
      id: post.id,
      created_time: post.created_time,
      updated_time: post.updated_time,
      likes: post.likes.summary.total_count,
      comments: post.comments.summary.total_count,
      shares: post.shares ? post.shares.count
                          : 0,
      message: post.message
    };
  };
}

function format_comment(post_id) {
  return function(comment) {
    return {
      post_id: post_id,
      id: comment.id,
      created_time: comment.created_time,
      from_id: comment.from.id,
      likes: comment.likes.summary.total_count,
      message: comment.message
    };
  };
}

function handle_error(err) {
  if(!err) {
    return;
  }

  if(typeof err === 'object') {
    console.error(err);
    err = err.message;
  } else {
    err = err.toString();
    console.error(err);
  }

  if(!config.debug) {
    throw new Error(err);
  }
}

function record_request() {
  state.requests.push(new Date());
}

function log_requests() {
  state.requests = state.requests.filter(function(when) {
    var ms_per_hour = 1000*60*60;
    return (new Date() - when) < ms_per_hour;
  });
  console.log(state.requests.length + ' requests in the last hour.');
}

function updated_recently(post) {
  return (new Date() - new Date(post.updated_time)) < config.alive_age_ms;
}

function get(path, params, after, callback) {
  if(after) {
    params = _.clone(params);
    params.after = after;
  }

  graph.get(path, params, function(err, res) {
    if(err && err.is_transient) {
      console.log('Got a transient error; retrying ' + path);
      return get(path, params, after, callback);
    }

    if(err) {
      console.error('Got an error while processing ' + path);
      return handle_error(err);
    }

    if(!res) {
      return handle_error('No response object!');
    }

    record_request();
    callback(res);
    module.exports.debug.res = res;
  });
}

// Takes an array of objects of the form
//   [ {id: 'foo', ...}, {id: 'bar', ...}, ...]
// and produces
//   { 'foo': {id: 'foo', ...},
//     'bar': {id: 'bar', ...} }
function use_ids_as_keys(arr) {
  var ids = _.map(arr, function(obj) {
    return obj.id;
  });
  return _.object(_.zip(ids, arr));
}

function get_comments(post_id, after, callback) {
  get('/' + post_id + '/comments', comment_params, after, function(res) {
    console.log('Got ' + res.data.length + ' comments');
    var new_comments_arr = _.map(res.data, format_comment(post_id));
    state.comments = _.extend(state.comments,
                              use_ids_as_keys(new_comments_arr));

    if(res && res.paging && res.paging.cursors && res.paging.cursors.after) {
      var new_after = res.paging.cursors.after;
      if(after !== new_after) {
        get_comments(post_id, new_after, callback);
      }
    } else {
      console.log('Got all comments.');
      callback();
    }
  });
}

// Just the last 25
function get_posts(source_id) {
  get('/' + source_id + '/feed', post_params, null, function(res) {
    console.log('Got ' + res.data.length + ' posts from ' + source_id);

    var post_records = _.map(res.data, format_post(source_id));

    _.each(post_records, function(post) {
      var maybe_new_comments = !state.posts[post.id] ||
                               (state.posts[post.id].updated_time !==
                                post.updated_time)
      if(maybe_new_comments) {
        state.live_posts[post.id] = post;
      }
      state.posts[post.id] = post;
    });
  });
}

function get_all_comments(post, callback) {
  // By passing null here, the first request gets the oldest comments, and
  // recursive requests get all of them.
  get_comments(post.id, null, callback);
}

function prune_posts() {
  var alive_and_dying = _.partition(_.values(state.live_posts),
                                    updated_recently);
  state.live_posts = use_ids_as_keys(alive_and_dying[0]);
  async.each(alive_and_dying[1], get_all_comments);
  console.log('Pruned ' + alive_and_dying[1].length + ' posts. ' +
              alive_and_dying[0].length + ' posts remain alive.');
}

function check_all_sources() {
  _.each(config.sources, get_posts);
}

var save = fs.writeFile;

// Save the state of this tool so it can be restarted easily.
function export_state(filename) {
  if(!filename) {
    filename = config.state_backup_file;
  }
  save(filename, JSON.stringify(state), handle_error);
}

function load_state(filename, callback) {
  if(typeof filename === 'function') {
    callback = filename;
    filename = null;
  }

  if(!filename) {
    filename = config.state_backup_file;
  }

  fs.readFile(filename, function(err, data) {
    if(err && callback) {
      return callback(err);
    }

    if(err) {
      return handle_error(err);
    }

    state = JSON.parse(data)

    if(callback) {
      callback();
    }
  });
}

function save_live_post_comments(callback) {
  async.each(state.live_posts, get_all_comments, callback);
}

function save_csv(filename, obj) {
  var arr = _.values(obj);
  json2csv({ data: arr, fields: _.keys(arr[0]) }, function(err, csv) {
    if(err) {
      return handle_error(err);
    }
    save(filename, csv, handle_error);
  });
}

// Export all data as two csv files, including comments from live posts
function export_all() {
  save_live_post_comments(function(err) {
    if(err) {
      return handle_error(err);
    }
    save_csv(config.post_export_file, state.posts);
    save_csv(config.comment_export_file, state.comments);
    console.log('Export complete');
  });
}

function initialize() {
  graph.setVersion(config.version);
  graph.setAccessToken(config.access_token);
}

function start(new_config) {
  if(new_config) {
    config = _.extend(config, new_config);
  }
  initialize();
  setInterval(check_all_sources, config.refresh_interval_ms);
  setInterval(prune_posts, config.refresh_interval_ms);
  setInterval(log_requests, config.refresh_interval_ms);
  setInterval(export_state, config.backup_interval_ms);
}

// Note: like most debug tools, you have to call monitor.debug.initialize()
// before this.
function test_sources() {
  var posts = 0;
  async.each(config.sources, function(source_id, callback) {
    get('/' + source_id + '/feed', post_params, null, function(res) {
      if(res.data.length === 0) {
        callback(new Error('Source ' + source_id + ' returned 0 posts'));
      } else {
        posts += res.data.length;
        callback();
      }
    });
  }, function(err) {
    if(err) {
      handle_error(err);
    } else {
      console.log('Successfully got posts (' + posts + ') from all sources (' + config.sources.length + ').');
      if(config.sources.length * config.post_limit !== posts) {
        console.log('Warning: hoped to get ' + (config.sources.length * config.post_limit) + ' posts, but only got ' + posts + '.');
      }
    }
  });
}

// For testing rate limits
function getlots(n) {
  _.each(_.range(n), function() {
    get('/nytimes/feed', post_params, null, function() {});
  });
}

function get_state() {
  return state;
}

module.exports = {
  start: start,
  export_all: export_all,
  load_state: load_state,
  debug: {
    initialize: initialize,
    getlots: getlots,
    test_sources: test_sources,
    save_live_post_comments: save_live_post_comments,
    export_state: export_state,
    get_state: get_state
  }
};
<|endoftext|>


<commit_before>var playoff = {

    settings: {
        'winner_h_offset': 5,
        'loser_h_offset': 20,
        'place_winner_h_offset': 10,
        'place_loser_h_offset': 15,
        'finish_winner_h_offset': 5,
        'finish_loser_h_offset': 20,
        'winner_v_offset': -10,
        'loser_v_offset': 10,
        'place_winner_v_offset': 2,
        'place_loser_v_offset': 9,
        'finish_winner_v_offset': -4,
        'finish_loser_v_offset': 4,
        'loser_colour': '#ff0000',
        'winner_colour': '#00ff00',
        'place_loser_colour': '#dddd00',
        'place_winner_colour': '#00dddd',
        'finish_loser_colour': '#ff0000',
        'finish_winner_colour': '#00ff00'
    },

    drawLine: function(ctx, line) {
        ctx.beginPath();
        ctx.moveTo(line[0], line[1]);
        ctx.lineTo(line[2], line[3]);
        ctx.stroke();
    },

    loadSettings: function(canvas, defaults) {
        for (var setting in defaults) {
            var attr = 'data-' + setting.replace(/_/g, '-');
            var attr_value = canvas.getAttribute(attr);
            if (attr_value) {
                defaults[setting] = attr_value;
            }
        }
        return defaults;
    },

    run: function() {
        var boxes = document.getElementsByClassName('playoff_matchbox');
        var lines = {
            'winner': {},
            'loser': {},
            'place-winner': {},
            'place-loser': {},
            'finish-winner': {},
            'finish-loser': {}
        };
        var boxes_idx = {};
        for (var b = 0; b < boxes.length; b++) {
            var id = boxes[b].getAttribute('data-id');
            boxes_idx[id] = boxes[b];
            for (var attr in lines) {
                var value = boxes[b].getAttribute('data-' + attr);
                if (value) {
                    if (!lines[attr][value]) {
                        lines[attr][value] = [];
                    }
                    lines[attr][value].push(id);
                }
            }
        }
        var canvas = document.getElementById('playoff_canvas');
        this.settings = this.loadSettings(canvas, this.settings);
        var lineMethods = {
            'place-winner': 'to',
            'place-loser': 'to',
            'finish-winner': 'midpoint',
            'finish-loser': 'midpoint',
            'winner': 'midpoint',
            'loser': 'midpoint'
        };
        var lineCalculator = {
            correctLines: function(hLines, vLine, comparator) {
                for (var l1 in hLines) {
                    for (var l2 in hLines) {
                        hLines[l1][2] = comparator(hLines[l1][2], hLines[l2][2]);
                        hLines[l2][2] = hLines[l1][2];
                    }
                }
                for (var l1 in hLines) {
                    vLine[0] = vLine[2] = comparator(hLines[l1][2], vLine[2]);
                    vLine[1] = Math.min(vLine[1], hLines[l1][3]);
                    vLine[3] = Math.max(vLine[3], hLines[l1][3]);
                }
            },
            template: function() {
                return {
                    hFrom: [],
                    vFrom: [0, canvas.height, 0, 0],
                    hTo: [],
                    vTo: [canvas.width, canvas.height, canvas.width, 0],
                    midpoints: []
                }
            },
            from: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        lines.vFrom[0],
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                lines.midpoints = [
                    [lines.vFrom[0], lines.vFrom[1]],
                    [lines.vTo[0], lines.vTo[1]]
                ];
                return lines;
            },
            to: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        parseInt(box.offsetLeft),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) - hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        lines.vTo[0],
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                lines.midpoints = [
                    [lines.vFrom[0], lines.vFrom[1]],
                    [lines.vTo[0], lines.vTo[1]]
                ];
                return lines;
            },
            midpoint: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        parseInt(box.offsetLeft),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) - hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                lines.midpoints = [
                    [
                        (lines.vFrom[0] + lines.vFrom[2]) / 2,
                        (lines.vFrom[1] + lines.vFrom[3]) / 2
                    ],
                    [
                        hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
                        (lines.vFrom[1] + lines.vFrom[3]) / 2
                    ],
                    [
                        hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
                        (lines.vTo[1] + lines.vTo[3]) / 2
                    ],
                    [
                        (lines.vTo[0] + lines.vTo[2]) / 2,
                        (lines.vTo[1] + lines.vTo[3]) / 2
                    ]
                ]
                return lines;
            }
        };
        var ctx = canvas.getContext('2d');
        for (var type in lines) {
            styleType = type.replace('-', '_');
            ctx.strokeStyle = this.settings[styleType + '_colour'];
            for (var from in lines[type]) {
                var to = lines[type][from];
                from = from.split(' ');
                var linesToDraw = lineCalculator[lineMethods[type]](
                    from, to,
                    this.settings[styleType + '_h_offset'], this.settings[styleType + '_v_offset']);
                for (var l in linesToDraw.hFrom) {
                    this.drawLine(ctx, linesToDraw.hFrom[l]);
                }
                this.drawLine(ctx, linesToDraw.vFrom);
                for (var l in linesToDraw.hTo) {
                    this.drawLine(ctx, linesToDraw.hTo[l]);
                }
                this.drawLine(ctx, linesToDraw.vTo);
                for (var m = 0; m < linesToDraw.midpoints.length-1; m++) {
                    this.drawLine(ctx, [
                        linesToDraw.midpoints[m][0], linesToDraw.midpoints[m][1],
                        linesToDraw.midpoints[m+1][0], linesToDraw.midpoints[m+1][1]
                    ]);
                }
            }
        }
    }

}

playoff.run();
<commit_msg>Fix for overlapping lines if match boxes are wider than expected<commit_after>var playoff = {

    settings: {
        'winner_h_offset': 5,
        'loser_h_offset': 20,
        'place_winner_h_offset': 10,
        'place_loser_h_offset': 15,
        'finish_winner_h_offset': 5,
        'finish_loser_h_offset': 20,
        'winner_v_offset': -10,
        'loser_v_offset': 10,
        'place_winner_v_offset': 2,
        'place_loser_v_offset': 9,
        'finish_winner_v_offset': -4,
        'finish_loser_v_offset': 4,
        'loser_colour': '#ff0000',
        'winner_colour': '#00ff00',
        'place_loser_colour': '#dddd00',
        'place_winner_colour': '#00dddd',
        'finish_loser_colour': '#ff0000',
        'finish_winner_colour': '#00ff00'
    },

    drawLine: function(ctx, line) {
        ctx.beginPath();
        ctx.moveTo(line[0], line[1]);
        ctx.lineTo(line[2], line[3]);
        ctx.stroke();
    },

    loadSettings: function(canvas, defaults) {
        for (var setting in defaults) {
            var attr = 'data-' + setting.replace(/_/g, '-');
            var attr_value = canvas.getAttribute(attr);
            if (attr_value) {
                defaults[setting] = attr_value;
            }
        }
        return defaults;
    },

    run: function() {
        var boxes = document.getElementsByClassName('playoff_matchbox');
        var lines = {
            'winner': {},
            'loser': {},
            'place-winner': {},
            'place-loser': {},
            'finish-winner': {},
            'finish-loser': {}
        };
        var boxes_idx = {};
        for (var b = 0; b < boxes.length; b++) {
            var id = boxes[b].getAttribute('data-id');
            boxes_idx[id] = boxes[b];
            for (var attr in lines) {
                var value = boxes[b].getAttribute('data-' + attr);
                if (value) {
                    if (!lines[attr][value]) {
                        lines[attr][value] = [];
                    }
                    lines[attr][value].push(id);
                }
            }
        }
        var canvas = document.getElementById('playoff_canvas');
        this.settings = this.loadSettings(canvas, this.settings);
        var lineMethods = {
            'place-winner': 'to',
            'place-loser': 'to',
            'finish-winner': 'midpoint',
            'finish-loser': 'midpoint',
            'winner': 'midpoint',
            'loser': 'midpoint'
        };
        var lineCalculator = {
            correctLines: function(hLines, vLine, comparator) {
                for (var l1 in hLines) {
                    for (var l2 in hLines) {
                        hLines[l1][2] = comparator(hLines[l1][2], hLines[l2][2]);
                        hLines[l2][2] = hLines[l1][2];
                    }
                }
                for (var l1 in hLines) {
                    vLine[0] = vLine[2] = comparator(hLines[l1][2], vLine[2]);
                    vLine[1] = Math.min(vLine[1], hLines[l1][3]);
                    vLine[3] = Math.max(vLine[3], hLines[l1][3]);
                }
            },
            template: function() {
                return {
                    hFrom: [],
                    vFrom: [0, canvas.height, 0, 0],
                    hTo: [],
                    vTo: [canvas.width, canvas.height, canvas.width, 0],
                    midpoints: []
                }
            },
            from: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        lines.vFrom[0],
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                lines.midpoints = [
                    [lines.vFrom[0], lines.vFrom[1]],
                    [lines.vTo[0], lines.vTo[1]]
                ];
                return lines;
            },
            to: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        parseInt(box.offsetLeft),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) - hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        lines.vTo[0],
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                lines.midpoints = [
                    [lines.vFrom[0], lines.vFrom[1]],
                    [lines.vTo[0], lines.vTo[1]]
                ];
                return lines;
            },
            midpoint: function(from, to, hOffset, vOffset) {
                var lines = this.template();
                for (var f = 0; f < from.length; f++) {
                    var box = boxes_idx[from[f]];
                    var line = [
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hFrom.push(line);
                }
                this.correctLines(lines.hFrom, lines.vFrom, Math.max);
                for (var t = 0; t < to.length; t++) {
                    var box = boxes_idx[to[t]];
                    var line = [
                        parseInt(box.offsetLeft),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
                        Math.floor(parseInt(box.offsetLeft) - hOffset),
                        Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
                    ];
                    lines.hTo.push(line);
                }
                this.correctLines(lines.hTo, lines.vTo, Math.min);
                lines.midpoints = [
                    [
                        (lines.vFrom[0] + lines.vFrom[2]) / 2,
                        (lines.vFrom[1] + lines.vFrom[3]) / 2
                    ],
                    [
                        hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
                        (lines.vFrom[1] + lines.vFrom[3]) / 2
                    ],
                    [
                        hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
                        (lines.vTo[1] + lines.vTo[3]) / 2
                    ],
                    [
                        (lines.vTo[0] + lines.vTo[2]) / 2,
                        (lines.vTo[1] + lines.vTo[3]) / 2
                    ]
                ]
                for (var h in lines.hTo) {
                    lines.hTo[h][2] = Math.max(
                        lines.hTo[h][2],
                        lines.midpoints[2][0]
                    );
                }
                for (var h in lines.hFrom) {
                    lines.hFrom[h][2] = Math.min(
                        lines.hFrom[h][2],
                        lines.midpoints[0][0]
                    );
                }
                return lines;
            }
        };
        var ctx = canvas.getContext('2d');
        for (var type in lines) {
            styleType = type.replace('-', '_');
            ctx.strokeStyle = this.settings[styleType + '_colour'];
            for (var from in lines[type]) {
                var to = lines[type][from];
                from = from.split(' ');
                var linesToDraw = lineCalculator[lineMethods[type]](
                    from, to,
                    this.settings[styleType + '_h_offset'], this.settings[styleType + '_v_offset']);
                for (var l in linesToDraw.hFrom) {
                    this.drawLine(ctx, linesToDraw.hFrom[l]);
                }
                this.drawLine(ctx, linesToDraw.vFrom);
                for (var l in linesToDraw.hTo) {
                    this.drawLine(ctx, linesToDraw.hTo[l]);
                }
                this.drawLine(ctx, linesToDraw.vTo);
                for (var m = 0; m < linesToDraw.midpoints.length-1; m++) {
                    if (linesToDraw.midpoints[m][0] <= linesToDraw.midpoints[m+1][0]) {
                        this.drawLine(ctx, [
                            linesToDraw.midpoints[m][0], linesToDraw.midpoints[m][1],
                            linesToDraw.midpoints[m+1][0], linesToDraw.midpoints[m+1][1]
                        ]);
                    }
                }
            }
        }
    }

}

playoff.run();
<|endoftext|>


<commit_before>import { updateChildNodes } from "../core/dom.js";
import { fragmentFrom } from "../core/htmlLiterals.js";
import { transmute } from "../core/template.js";
import Input from "./Input.js";
import {
  defaultState,
  firstRender,
  ids,
  inputDelegate,
  matchText,
  raiseChangeEvents,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  stateEffects,
  template,
} from "./internal.js";
import ListBox from "./ListBox.js";

/**
 * A text input box that completes text as the user types
 *
 * @inherits Input
 */
class AutoCompleteInput extends Input {
  // @ts-ignore
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      autoCompleteSelect: false,
      opened: false,
      originalText: "",
      textIndex: -1,
      texts: [],
    });
  }

  /**
   * Search the given array of text strings for one that matches `prefix`.
   *
   * This method is invoked by the auto-complete algorithm when the user types
   * characters into the input.
   *
   * The default implementation does a case-insensitive prefix search. You can
   * override this method to define custom auto-complete behavior. Return the
   * complete matching string if a match was found, or null if there was no
   * match.
   *
   * @param {string[]} texts
   * @param {string} prefix
   * @returns {string|null}
   */
  [matchText](texts, prefix) {
    if (prefix.length === 0 || !texts) {
      return null;
    }
    const prefixLowerCase = prefix.toLowerCase();
    const match = texts.find((text) =>
      text.toLowerCase().startsWith(prefixLowerCase)
    );
    return match || null;
  }

  get opened() {
    return this[state].opened;
  }
  set opened(opened) {
    this[setState]({ opened });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);
    if (this[firstRender]) {
      // In many ways it would be cleaner to do AutoComplete work in a keydown
      // listener. Unfortunately, Chrome for Android sets the keyCode on *all*
      // keydown events to a mysterious 229 value, making it impossible for us
      // to look at the keyCode and determine whether the user is typing a key
      // that should trigger AutoComplete.
      //
      // Instead, we listen to input events. That comes with its own set of
      // headaches, noted below.
      this[ids].inner.addEventListener("input", () => {
        // Gboard will generate multiple input events for a single keypress. In
        // particular, if we do AutoComplete and leave the text selected, then
        // when the user types the next key, we'll get *three* input events: one
        // for the actual change, and two other events (probably related to
        // Gboard's own AutoComplete behavior). We give the input value a chance
        // to stabilize by waiting a tick.
        setTimeout(() => {
          this[raiseChangeEvents] = true;
          /** @type {any} */
          const inner = this.inner;
          const text = this.value.toLowerCase();
          // We only AutoComplete if the user's typing at the end of the input.
          // Read the selection start and end directly off the inner element to
          // ensure they're up to date.
          const typingAtEnd =
            inner.selectionStart === text.length &&
            inner.selectionEnd === text.length;
          // Moreover, we only AutoComplete if we're sure the user's added a
          // single character to the value seen on the previous input event.
          // Among other things, we want to ensure the user can delete text from
          // the end without having AutoComplete kick in.
          const originalText = this[state].originalText;
          const userAddedText =
            text.startsWith(originalText) &&
            text.length === originalText.length + 1;
          if (typingAtEnd && userAddedText) {
            autoComplete(this);
          }
          // Remember what the user typed for next time.
          this[setState]({
            originalText: text,
          });
          this[raiseChangeEvents] = false;
        });
      });

      transmute(this[ids].accessibleList, ListBox);
    }

    // Let ARIA know whether combo box is open.
    if (changed.opened) {
      const { opened } = this[state];
      this[ids].inner.setAttribute("aria-expanded", opened.toString());
    }

    // Copy the text values to the invisible, accessible list.
    // TODO: We could defer this work to after the component gets focus.
    if (changed.texts) {
      const { texts } = this[state];
      const options =
        texts === null
          ? []
          : texts.map((text) => {
              const div = document.createElement("div");
              div.textContent = text;
              return div;
            });
      updateChildNodes(this[ids].accessibleList, options);
    }

    // Select the the accessible list item for the current text.
    if (changed.textIndex) {
      const { textIndex } = this[state];

      /** @type {any} */ const list = this[ids].accessibleList;
      if ("currentIndex" in list) {
        list.currentIndex = textIndex;
      }

      const item = list.currentItem;
      const id = item ? item.id : null;
      if (id) {
        this[inputDelegate].setAttribute("aria-activedescendant", id);
      } else {
        this[inputDelegate].removeAttribute("aria-activedescendant");
      }
    }
  }

  [rendered](/** @type {ChangedFlags} */ changed) {
    super[rendered](changed);
    const { autoCompleteSelect, originalText } = this[state];
    if (changed.originalText && autoCompleteSelect) {
      // We've finished rendering new auto-completed text.
      // Leave the auto-completed portion (after the part the user originally
      // typed) selected.
      this[setState]({
        autoCompleteSelect: false,
        selectionEnd: this[state].value.length,
        selectionStart: originalText.length,
      });

      // Dispatch an input event so that listeners can process the
      // auto-completed text.
      // @ts-ignore
      const InputEvent = window.InputEvent || Event;
      const event = new InputEvent("input", {
        // @ts-ignore
        detail: {
          originalText,
        },
      });
      this.dispatchEvent(event);
    }
  }

  [stateEffects](state, changed) {
    const effects = super[stateEffects]
      ? super[stateEffects](state, changed)
      : {};

    if (changed.valueCopy) {
      const { texts, valueCopy } = state;
      const textIndex = texts.indexOf(valueCopy);
      Object.assign(effects, { textIndex });
    }

    return effects;
  }

  get [template]() {
    const result = super[template];

    // Apply ARIA combobox attributes to the input.
    const inner = result.content.querySelector('[part~="input"]');
    if (inner) {
      inner.setAttribute("aria-autocomplete", "both");
      inner.setAttribute("aria-controls", "accessibleList");
      inner.setAttribute("role", "combobox");
    }

    // Add an accessible list.
    result.content.append(fragmentFrom.html`

    `);

    return result;
  }

  /**
   * The set of texts the input will match against.
   *
   * @type {string[]}
   */
  get texts() {
    return this[state].texts;
  }
  set texts(texts) {
    this[setState]({ texts });
  }

  // Setting the value from the outside is treated as if the user had typed the
  // value. This way the component's value can be prepopulated, and the user can
  // start typing at the end of it to get AutoComplete.
  // @ts-ignore
  get value() {
    return super.value;
  }
  set value(value) {
    super.value = value;
    // If the input has focus, we assume the user is typing, and rely on
    // the `input` event to update the originalText state.
    if (this[shadowRoot] && !this.inner.matches(":focus")) {
      this[setState]({
        originalText: value,
      });
    }
  }
}

export function autoComplete(/** @type {AutoCompleteInput} */ element) {
  const match = element[matchText](element.texts, element.value);

  // If found, update the input value to the match.
  // Leave the auto-completed portion selected.
  if (match) {
    element[setState]({
      autoCompleteSelect: true,
      value: match,
    });
  }

  return match;
}

export default AutoCompleteInput;
<commit_msg>Remove dead code.<commit_after>import { updateChildNodes } from "../core/dom.js";
import { fragmentFrom } from "../core/htmlLiterals.js";
import { transmute } from "../core/template.js";
import Input from "./Input.js";
import {
  defaultState,
  firstRender,
  ids,
  inputDelegate,
  matchText,
  raiseChangeEvents,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  template,
} from "./internal.js";
import ListBox from "./ListBox.js";

/**
 * A text input box that completes text as the user types
 *
 * @inherits Input
 */
class AutoCompleteInput extends Input {
  // @ts-ignore
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      autoCompleteSelect: false,
      opened: false,
      originalText: "",
      textIndex: -1,
      texts: [],
    });
  }

  /**
   * Search the given array of text strings for one that matches `prefix`.
   *
   * This method is invoked by the auto-complete algorithm when the user types
   * characters into the input.
   *
   * The default implementation does a case-insensitive prefix search. You can
   * override this method to define custom auto-complete behavior. Return the
   * complete matching string if a match was found, or null if there was no
   * match.
   *
   * @param {string[]} texts
   * @param {string} prefix
   * @returns {string|null}
   */
  [matchText](texts, prefix) {
    if (prefix.length === 0 || !texts) {
      return null;
    }
    const prefixLowerCase = prefix.toLowerCase();
    const match = texts.find((text) =>
      text.toLowerCase().startsWith(prefixLowerCase)
    );
    return match || null;
  }

  get opened() {
    return this[state].opened;
  }
  set opened(opened) {
    this[setState]({ opened });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);
    if (this[firstRender]) {
      // In many ways it would be cleaner to do AutoComplete work in a keydown
      // listener. Unfortunately, Chrome for Android sets the keyCode on *all*
      // keydown events to a mysterious 229 value, making it impossible for us
      // to look at the keyCode and determine whether the user is typing a key
      // that should trigger AutoComplete.
      //
      // Instead, we listen to input events. That comes with its own set of
      // headaches, noted below.
      this[ids].inner.addEventListener("input", () => {
        // Gboard will generate multiple input events for a single keypress. In
        // particular, if we do AutoComplete and leave the text selected, then
        // when the user types the next key, we'll get *three* input events: one
        // for the actual change, and two other events (probably related to
        // Gboard's own AutoComplete behavior). We give the input value a chance
        // to stabilize by waiting a tick.
        setTimeout(() => {
          this[raiseChangeEvents] = true;
          /** @type {any} */
          const inner = this.inner;
          const text = this.value.toLowerCase();
          // We only AutoComplete if the user's typing at the end of the input.
          // Read the selection start and end directly off the inner element to
          // ensure they're up to date.
          const typingAtEnd =
            inner.selectionStart === text.length &&
            inner.selectionEnd === text.length;
          // Moreover, we only AutoComplete if we're sure the user's added a
          // single character to the value seen on the previous input event.
          // Among other things, we want to ensure the user can delete text from
          // the end without having AutoComplete kick in.
          const originalText = this[state].originalText;
          const userAddedText =
            text.startsWith(originalText) &&
            text.length === originalText.length + 1;
          if (typingAtEnd && userAddedText) {
            autoComplete(this);
          }
          // Remember what the user typed for next time.
          this[setState]({
            originalText: text,
          });
          this[raiseChangeEvents] = false;
        });
      });

      transmute(this[ids].accessibleList, ListBox);
    }

    // Let ARIA know whether combo box is open.
    if (changed.opened) {
      const { opened } = this[state];
      this[ids].inner.setAttribute("aria-expanded", opened.toString());
    }

    // Copy the text values to the invisible, accessible list.
    // TODO: We could defer this work to after the component gets focus.
    if (changed.texts) {
      const { texts } = this[state];
      const options =
        texts === null
          ? []
          : texts.map((text) => {
              const div = document.createElement("div");
              div.textContent = text;
              return div;
            });
      updateChildNodes(this[ids].accessibleList, options);
    }

    // Select the the accessible list item for the current text.
    if (changed.textIndex) {
      const { textIndex } = this[state];

      /** @type {any} */ const list = this[ids].accessibleList;
      if ("currentIndex" in list) {
        list.currentIndex = textIndex;
      }

      const item = list.currentItem;
      const id = item ? item.id : null;
      if (id) {
        this[inputDelegate].setAttribute("aria-activedescendant", id);
      } else {
        this[inputDelegate].removeAttribute("aria-activedescendant");
      }
    }
  }

  [rendered](/** @type {ChangedFlags} */ changed) {
    super[rendered](changed);
    const { autoCompleteSelect, originalText } = this[state];
    if (changed.originalText && autoCompleteSelect) {
      // We've finished rendering new auto-completed text.
      // Leave the auto-completed portion (after the part the user originally
      // typed) selected.
      this[setState]({
        autoCompleteSelect: false,
        selectionEnd: this[state].value.length,
        selectionStart: originalText.length,
      });

      // Dispatch an input event so that listeners can process the
      // auto-completed text.
      // @ts-ignore
      const InputEvent = window.InputEvent || Event;
      const event = new InputEvent("input", {
        // @ts-ignore
        detail: {
          originalText,
        },
      });
      this.dispatchEvent(event);
    }
  }

  get [template]() {
    const result = super[template];

    // Apply ARIA combobox attributes to the input.
    const inner = result.content.querySelector('[part~="input"]');
    if (inner) {
      inner.setAttribute("aria-autocomplete", "both");
      inner.setAttribute("aria-controls", "accessibleList");
      inner.setAttribute("role", "combobox");
    }

    // Add an accessible list.
    result.content.append(fragmentFrom.html`

    `);

    return result;
  }

  /**
   * The set of texts the input will match against.
   *
   * @type {string[]}
   */
  get texts() {
    return this[state].texts;
  }
  set texts(texts) {
    this[setState]({ texts });
  }

  // Setting the value from the outside is treated as if the user had typed the
  // value. This way the component's value can be prepopulated, and the user can
  // start typing at the end of it to get AutoComplete.
  // @ts-ignore
  get value() {
    return super.value;
  }
  set value(value) {
    super.value = value;
    // If the input has focus, we assume the user is typing, and rely on
    // the `input` event to update the originalText state.
    if (this[shadowRoot] && !this.inner.matches(":focus")) {
      this[setState]({
        originalText: value,
      });
    }
  }
}

export function autoComplete(/** @type {AutoCompleteInput} */ element) {
  const match = element[matchText](element.texts, element.value);

  // If found, update the input value to the match.
  // Leave the auto-completed portion selected.
  if (match) {
    element[setState]({
      autoCompleteSelect: true,
      value: match,
    });
  }

  return match;
}

export default AutoCompleteInput;
<|endoftext|>


<commit_before>import React from 'react'
import { Route }            from 'react-router-dom'
import styled               from 'styled-components'

import HomeTemplate         from '../views/common/pages/HomeTemplate'
import HomeIndex            from '../views/common/pages/HomeIndex'

// 测试组件
import AsyncTable           from '../views/test/AsyncTable'                             // 测试异步表格

// 引入 '客户管理' 版块
import RepairList           from '../views/Repair/pages/RepairList'                     // 客户报修
import ClientReview         from '../views/Repair/pages/ClientReview'                   // 客户回访
import UpkeepList           from '../views/Repair/pages/UpkeepList'                     // 维修费设置
import Rectification        from '../views/Repair/pages/Rectification'                  // 整改通知

import Test           from '../views/Repair/pages/test'
import Repair           from '../views/Repair/pages/Details/Repair'
import ReturnVisit           from '../views/Repair/pages/Details/ReturnVisit'
import ReturnVisitDetail           from '../views/Repair/pages/Details/ReturnVisitDetail'
import Maintenance           from '../views/Repair/pages/Details/Maintenance'
import CorrectionDetail           from '../views/Repair/pages/Details/CorrectionDetail'

// 引入 '仓库管理' 版块
import InventoryManage      from '../views/Warehouse/pages/InventoryManage'             // 库存管理( 合并: 汇总, 入库, 出库 )
import ReceiveStatistics    from '../views/Warehouse/pages/ReceiveStatistics'           // 领用统计
import MaterialManagement   from '../views/Warehouse/pages/MaterialManagement'          // 材料管理

const HomeBox = ({ routes }) => (
    <HomeTemplate>
        {
            routes.map((route, i) => (
                <RouteWithSubRoutes key={i} {...route} />
            ))
        }
    </HomeTemplate>
)

const routes = [
    {
        path: '/home',
        component: HomeBox,
        routes: [
            {
                path: '/home/index',                            // 默认首页
                component: HomeIndex
            }
        ]
    }, {
        path: '/test',
        component: HomeBox,
        routes: [
            {
                path: '/test/table',                            // 测试 - 异步表格
                component: AsyncTable
            }
        ]
    }, {
        path: '/upkeep',
        component: HomeBox,
        routes: [
            {
                path: '/upkeep/repairList',                     // 客户管理 - 客户报修
                component: RepairList
            }, {
                path: '/upkeep/clientReview',                   // 客户管理 - 客户回访
                component: ClientReview
            }, {
                path: '/upkeep/upkeepList',                     // 客户管理 - 维修费设置
                component: UpkeepList
            }, {
                path: '/upkeep/rectification',                  // 客户管理 - 整改通知
                component: Rectification
            }, {
                path: '/upkeep/test',
                component: Test
            }, {
                path: '/upkeep/repair',
                component: Repair
            }, {
                path: '/upkeep/returnVisit',
                component: ReturnVisit
            }, {
                path: '/upkeep/returnVisitDetail',
                component: ReturnVisitDetail
            }, {
                path: '/upkeep/maintenance',
                component: Maintenance
            }, {
                path: '/upkeep/correctionDetail',
                component: CorrectionDetail
            }
        ]
    }, {
        path: '/warehouse',
        component: HomeBox,
        routes: [
            {
                path: '/warehouse/inventoryManage',             // 仓库管理 - 库存管理
                component: InventoryManage
            }, {
                path: '/warehouse/receiveStatistics',           // 仓库管理 - 领用统计
                component: ReceiveStatistics
            }, {
                path: '/warehouse/materialManagement',          // 仓库管理 - 材料管理
                component: MaterialManagement
            }
        ]
    }
]

const SubRoutes = styled.section `
    height: 100%
`

const RouteWithSubRoutes = (route) => (
    <SubRoutes>
        <Route path={route.path} render={props => (
            <route.component {...props} routes={route.routes} />
            )}
        />
    </SubRoutes>
)

export { routes, RouteWithSubRoutes }
<commit_msg>:bug: 恢复被冲掉的 HomeRouter.js 文件<commit_after>import React from 'react'
import { Route }            from 'react-router-dom'
import styled               from 'styled-components'

import HomeTemplate         from '../views/common/pages/HomeTemplate'
import HomeIndex            from '../views/common/pages/HomeIndex'

// 测试组件
import AsyncTable           from '../views/test/AsyncTable'                             // 测试异步表格

// 引入 '客户管理' 版块
import RepairList           from '../views/Repair/pages/RepairList'                     // 客户报修
import ClientReview         from '../views/Repair/pages/ClientReview'                   // 客户回访
import UpkeepList           from '../views/Repair/pages/UpkeepList'                     // 维修费设置
import Rectification        from '../views/Repair/pages/Rectification'                  // 整改通知
import ClientReviewDetails  from '../views/Repair/pages/Details/ClientReviewDetails'    // [详情]'客户回访'

// 引入 '仓库管理' 版块
import InventoryManage      from '../views/Warehouse/pages/InventoryManage'             // 库存管理( 合并: 汇总, 入库, 出库 )
import ReceiveStatistics    from '../views/Warehouse/pages/ReceiveStatistics'           // 领用统计
import MaterialManagement   from '../views/Warehouse/pages/MaterialManagement'          // 材料管理

const HomeBox = ({ routes }) => (
    <HomeTemplate>
        {
            routes.map((route, i) => (
                <RouteWithSubRoutes key={i} {...route} />
            ))
        }
    </HomeTemplate>
)

const routes = [
    {
        path: '/home',
        component: HomeBox,
        routes: [
            {
                path: '/home/index',                            // 默认首页
                component: HomeIndex
            }
        ]
    }, {
        path: '/test',
        component: HomeBox,
        routes: [
            {
                path: '/test/table',                            // 测试 - 异步表格
                component: AsyncTable
            }
        ]
    }, {
        path: '/upkeep',
        component: HomeBox,
        routes: [
            {
                path: '/upkeep/repairList',                     // 客户管理 - 客户报修
                component: RepairList
            }, {
                path: '/upkeep/clientReview',                   // 客户管理 - 客户回访
                component: ClientReview
            }, {
                path: '/upkeep/upkeepList',                     // 客户管理 - 维修费设置
                component: UpkeepList
            }, {
                path: '/upkeep/rectification',                  // 客户管理 - 整改通知
                component: Rectification
            }, {
                path: '/upkeep/clientReviewDetails/:id',        // [详情] - '客户回访'
                component: ClientReviewDetails
            }
        ]
    }, {
        path: '/warehouse',
        component: HomeBox,
        routes: [
            {
                path: '/warehouse/inventoryManage',             // 仓库管理 - 库存管理
                component: InventoryManage
            }, {
                path: '/warehouse/receiveStatistics',           // 仓库管理 - 领用统计
                component: ReceiveStatistics
            }, {
                path: '/warehouse/materialManagement',          // 仓库管理 - 材料管理
                component: MaterialManagement
            }
        ]
    }
]

const SubRoutes = styled.section `
    height: 100%
`

const RouteWithSubRoutes = (route) => (
    <SubRoutes>
        <Route path={route.path} render={props => (
            <route.component {...props} routes={route.routes} />
            )}
        />
    </SubRoutes>
)

export { routes, RouteWithSubRoutes }
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { routerRedux } from 'dva/router'
import { connect } from 'dva'
import { Row, Col, Button, Popconfirm } from 'antd'
import { Page } from 'components'
import queryString from 'query-string'
import List from './List'
import Filter from './Filter'
import Modal from './Modal'

const User = ({
  location, dispatch, user, loading,
}) => {
  location.query = queryString.parse(location.search)
  const { query, pathname } = location
  const {
    list, pagination, currentItem, modalVisible, modalType, isMotion, selectedRowKeys,
  } = user

  const handleRefresh = (newQuery) => {
    dispatch(routerRedux.push({
      pathname,
      search: queryString.stringify({
        ...query,
        ...newQuery,
      }),
    }))
  }

  const modalProps = {
    item: modalType === 'create' ? {} : currentItem,
    visible: modalVisible,
    maskClosable: false,
    confirmLoading: loading.effects['user/update'],
    title: `${modalType === 'create' ? 'Create User' : 'Update User'}`,
    wrapClassName: 'vertical-center-modal',
    onOk (data) {
      dispatch({
        type: `user/${modalType}`,
        payload: data,
      })
        .then(() => {
          handleRefresh()
        })
    },
    onCancel () {
      dispatch({
        type: 'user/hideModal',
      })
    },
  }

  const listProps = {
    dataSource: list,
    loading: loading.effects['user/query'],
    pagination,
    location,
    isMotion,
    onChange (page) {
      handleRefresh({
        page: page.current,
        pageSize: page.pageSize,
      })
    },
    onDeleteItem (id) {
      dispatch({
        type: 'user/delete',
        payload: id,
      })
        .then(() => {
          handleRefresh({
            page: (list.length === 1 && pagination.current > 1) ? pagination.current - 1 : pagination.current,
          })
        })
    },
    onEditItem (item) {
      dispatch({
        type: 'user/showModal',
        payload: {
          modalType: 'update',
          currentItem: item,
        },
      })
    },
    rowSelection: {
      selectedRowKeys,
      onChange: (keys) => {
        dispatch({
          type: 'user/updateState',
          payload: {
            selectedRowKeys: keys,
          },
        })
      },
    },
  }

  const filterProps = {
    isMotion,
    filter: {
      ...query,
    },
    onFilterChange (value) {
      handleRefresh({
        ...value,
        page: 1,
      })
    },
    onAdd () {
      dispatch({
        type: 'user/showModal',
        payload: {
          modalType: 'create',
        },
      })
    },
    switchIsMotion () {
      dispatch({ type: 'user/switchIsMotion' })
    },
  }

  const handleDeleteItems = () => {
    dispatch({
      type: 'user/multiDelete',
      payload: {
        ids: selectedRowKeys,
      },
    })
      .then(() => {
        handleRefresh({
          page: (list.length === selectedRowKeys.length && pagination.current > 1) ? pagination.current - 1 : pagination.current,
        })
      })
  }

  return (
    <Page inner>
      <Filter {...filterProps} />
      {
        selectedRowKeys.length > 0 &&
        <Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}>
          <Col>
            {`Selected ${selectedRowKeys.length} items `}
            <Popconfirm title="Are you sure delete these items?" placement="left" onConfirm={handleDeleteItems}>
              <Button type="primary" style={{ marginLeft: 8 }}>Remove</Button>
            </Popconfirm>
          </Col>
        </Row>
      }
      <List {...listProps} />
      {modalVisible && <Modal {...modalProps} />}
    </Page>
  )
}

User.propTypes = {
  user: PropTypes.object,
  location: PropTypes.object,
  dispatch: PropTypes.func,
  loading: PropTypes.object,
}

export default connect(({ user, loading }) => ({ user, loading }))(User)
<commit_msg>comfirmLoding<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { routerRedux } from 'dva/router'
import { connect } from 'dva'
import { Row, Col, Button, Popconfirm } from 'antd'
import { Page } from 'components'
import queryString from 'query-string'
import List from './List'
import Filter from './Filter'
import Modal from './Modal'

const User = ({
  location, dispatch, user, loading,
}) => {
  location.query = queryString.parse(location.search)
  const { query, pathname } = location
  const {
    list, pagination, currentItem, modalVisible, modalType, isMotion, selectedRowKeys,
  } = user

  const handleRefresh = (newQuery) => {
    dispatch(routerRedux.push({
      pathname,
      search: queryString.stringify({
        ...query,
        ...newQuery,
      }),
    }))
  }

  const modalProps = {
    item: modalType === 'create' ? {} : currentItem,
    visible: modalVisible,
    maskClosable: false,
    confirmLoading: loading.effects[`user/${modalType}`],
    title: `${modalType === 'create' ? 'Create User' : 'Update User'}`,
    wrapClassName: 'vertical-center-modal',
    onOk (data) {
      dispatch({
        type: `user/${modalType}`,
        payload: data,
      })
        .then(() => {
          handleRefresh()
        })
    },
    onCancel () {
      dispatch({
        type: 'user/hideModal',
      })
    },
  }

  const listProps = {
    dataSource: list,
    loading: loading.effects['user/query'],
    pagination,
    location,
    isMotion,
    onChange (page) {
      handleRefresh({
        page: page.current,
        pageSize: page.pageSize,
      })
    },
    onDeleteItem (id) {
      dispatch({
        type: 'user/delete',
        payload: id,
      })
        .then(() => {
          handleRefresh({
            page: (list.length === 1 && pagination.current > 1) ? pagination.current - 1 : pagination.current,
          })
        })
    },
    onEditItem (item) {
      dispatch({
        type: 'user/showModal',
        payload: {
          modalType: 'update',
          currentItem: item,
        },
      })
    },
    rowSelection: {
      selectedRowKeys,
      onChange: (keys) => {
        dispatch({
          type: 'user/updateState',
          payload: {
            selectedRowKeys: keys,
          },
        })
      },
    },
  }

  const filterProps = {
    isMotion,
    filter: {
      ...query,
    },
    onFilterChange (value) {
      handleRefresh({
        ...value,
        page: 1,
      })
    },
    onAdd () {
      dispatch({
        type: 'user/showModal',
        payload: {
          modalType: 'create',
        },
      })
    },
    switchIsMotion () {
      dispatch({ type: 'user/switchIsMotion' })
    },
  }

  const handleDeleteItems = () => {
    dispatch({
      type: 'user/multiDelete',
      payload: {
        ids: selectedRowKeys,
      },
    })
      .then(() => {
        handleRefresh({
          page: (list.length === selectedRowKeys.length && pagination.current > 1) ? pagination.current - 1 : pagination.current,
        })
      })
  }

  return (
    <Page inner>
      <Filter {...filterProps} />
      {
        selectedRowKeys.length > 0 &&
        <Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}>
          <Col>
            {`Selected ${selectedRowKeys.length} items `}
            <Popconfirm title="Are you sure delete these items?" placement="left" onConfirm={handleDeleteItems}>
              <Button type="primary" style={{ marginLeft: 8 }}>Remove</Button>
            </Popconfirm>
          </Col>
        </Row>
      }
      <List {...listProps} />
      {modalVisible && <Modal {...modalProps} />}
    </Page>
  )
}

User.propTypes = {
  user: PropTypes.object,
  location: PropTypes.object,
  dispatch: PropTypes.func,
  loading: PropTypes.object,
}

export default connect(({ user, loading }) => ({ user, loading }))(User)
<|endoftext|>


<commit_before>/* Richard Meadows 2012 */

/* -------- Includes -------- */

var exec = require('child_process').exec;
var _ = require('underscore');

/* -------- Helper Functions -------- */

/**
 * Performs a n-point moving average on array.
 */
function moving_average(array, n) {
    var nums = [];

	for (i in array) {
		/* If this item in the array is a number */
		if (_.isNumber(array[i])) {
			nums.push(array[i]);
			if (nums.length > n) { nums.splice(0,1); } /* Remove the first element of the array */
			/* Take the average of the n items in this array */
			var sum = _.reduce(nums, function(memo, num){ return memo + num; }, 0);
			array[i] = sum/nums.length;
		}
	}

	return array;
}
/**
 * Performs a n-point maximum on array.
 */
function moving_maximum(array, n) {
	var nums = [];

	for (i in array) {
		if (_.isNumber(array[i])) {
			nums.push(array[i]);
			if (nums.length > n) { nums.splice(0,1); } /* Remove the first element of the array */
			/* Take the average of the n items in this array */
			var maximum = _.max(nums);
			array[i] = maximum;
		}
	}

	return array;
}
/**
 * Applys an n-point moving filter to a set of series.
 */
function apply_moving_filter(set, filter, n) {
    if (!_.isNumber(n)) { n = 3; }

    for (series in set) { /* For each series */
        /* Apply the filter */
        set[series] = filter(set[series], n);
    }

    return set;
}
/**
 * Returns the string to give to gnuplot based on the value of options.time.
 */
function time_format(options) {
	if (_.isString(options.time)) {
		/* Translate the string we've been given into a format */
		switch(options.time) {
			case 'days':
			case 'Days':
				return "%d/%m";
			case 'hours':
			case 'Hours':
				return "%H:%M";
			default: /* Presume we've been given a gnuplot-readable time format string */
				return options.time;
		}
	} else { /* Just default to hours */
		return "%H:%M";
	}
}
/**
 * Sets up gnuplot based on the properties we're given in the options object.
 */
function setup_gnuplot(gnuplot, options) {
	if (options.format === 'svg') { /* Setup gnuplot for SVG */
		gnuplot.stdin.write('set term svg fname \"Helvetica\" fsize 14\n');
	} else { /* Default: setup Gnuplot output to postscript so ps2pdf can interpret it */
		gnuplot.stdin.write('set term postscript landscape enhanced color dashed \"Helvetica\" 14\n');
	}

	/* Formatting Options */
	if (options.time) {
		gnuplot.stdin.write('set xdata time\n');
		gnuplot.stdin.write('set timefmt "%s"\n');
		gnuplot.stdin.write('set format x "' + time_format(options.time) + '"\n');
		gnuplot.stdin.write('set xlabel "time"\n');
	}
	if (options.title) {
		gnuplot.stdin.write('set title "'+options.title+'"\n');
	}
	if (options.logscale) {
		gnuplot.stdin.write('set logscale y\n');
	}
	if (options.xlabel) {
		gnuplot.stdin.write('set xlabel "'+options.xlabel+'"\n');
	}
	if (options.ylabel) {
		gnuplot.stdin.write('set ylabel "'+options.ylabel+'"\n');
	}

	/* Setup ticks */
	gnuplot.stdin.write('set grid xtics ytics mxtics\n');
	gnuplot.stdin.write('set mxtics\n');

	if (options.nokey) {
		gnuplot.stdin.write('set nokey\n');
	}
}
/**
 * Called after Gnuplot has finished.
 */
function post_gnuplot_processing(error, stdout, stderr) {
	/* Print stuff */
	console.log('stdout: ' + stdout);
	console.log('stderr: ' + stderr);
	if (error !== null) {
		console.log('exec error: ' + error);
	}
}

/* -------- Public Functions -------- */

/**
 * Plots data to a PDF file. If it does not exist, the PDF file will be created, otherwise this plot will
 * be appended as a new page.
 */
function plot(options) {
	/* Required Options */
	if (!options.data || !options.filename) {
		throw("The options object must have 'data' and 'filename' properties!");
		return;
	}
	/* Translate data into an object if needs be */
	if (_.isArray(options.data)) {
		if (_.isEqual(_.flatten(options.data), options.data)) { /* If it's a one-dimentional array */
			options.data = { 'Series 1': options.data };
		}
	}

	/* Defaults */
	if (!options.style) {
		options.style = 'lines'; /* Default to lines */
	}

	/* Apply moving averages and maximums */
	if (options.moving_avg) {
		options.data = apply_moving_filter(options.data, moving_average, options.moving_avg);
	}
	if (options.moving_max) {
		options.data = apply_moving_filter(options.data, moving_maximum, options.moving_max);
	}

	/* Execute Gnuplot specifing a function to be called when it terminates */
	if (options.format === 'svg') {
		gnuplot = exec('gnuplot > '+options.filename, post_gnuplot_processing);
	} else {
		gnuplot = exec('gnuplot | ps2pdf - '+options.filename, post_gnuplot_processing);
	}

	/* Sets up gnuplot based on the properties we've been given in the options object */
	setup_gnuplot(gnuplot, options);

	/* Get an array containing all the series */
	var series = _.keys(options.data);
	/* Reject series that are functions or come from higher up the protoype chain */
	var i;
	for (i = 0; i < series.length; i += 1) {
		if (!options.data.hasOwnProperty(series[i]) || typeof options.data[series[i]] === 'function') {
			delete series[i]; /* undefine this element */
		}
	}
	/* Filter out any undefined elements */
	series = _.filter(series, function() { return true; });

	/* Print the command to actually do the plot */
	gnuplot.stdin.write('plot');
	for (i = 1; i <= series.length; i += 1) { /* For each series */
		/* Instruct gnuplot to plot this series */
		gnuplot.stdin.write('\'-\' using 1:2 title\''+series[i]+'\' with '+options.style+' lt 1 lc '+i);
		/* If another series is to follow, add a comma */
		if (i < series.length) { gnuplot.stdin.write(','); }
	}
	gnuplot.stdin.write('\n');

	/* Print out the data */
	for (i = 0; i < series.length; i += 1) { /* For each series */
		for (key in options.data[series[i]]) {
			gnuplot.stdin.write(key+' '+options.data[series[i]][key]+'\n');
		}
		/* Terminate the data */
		gnuplot.stdin.write('e\n');
	}

	gnuplot.stdin.end();
}

/* -------- Exports -------- */

exports.plot = plot;
<commit_msg>Fix off by one error in series titles.<commit_after>/* Richard Meadows 2012 */

/* -------- Includes -------- */

var exec = require('child_process').exec;
var _ = require('underscore');

/* -------- Helper Functions -------- */

/**
 * Performs a n-point moving average on array.
 */
function moving_average(array, n) {
    var nums = [];

	for (i in array) {
		/* If this item in the array is a number */
		if (_.isNumber(array[i])) {
			nums.push(array[i]);
			if (nums.length > n) { nums.splice(0,1); } /* Remove the first element of the array */
			/* Take the average of the n items in this array */
			var sum = _.reduce(nums, function(memo, num){ return memo + num; }, 0);
			array[i] = sum/nums.length;
		}
	}

	return array;
}
/**
 * Performs a n-point maximum on array.
 */
function moving_maximum(array, n) {
	var nums = [];

	for (i in array) {
		if (_.isNumber(array[i])) {
			nums.push(array[i]);
			if (nums.length > n) { nums.splice(0,1); } /* Remove the first element of the array */
			/* Take the average of the n items in this array */
			var maximum = _.max(nums);
			array[i] = maximum;
		}
	}

	return array;
}
/**
 * Applys an n-point moving filter to a set of series.
 */
function apply_moving_filter(set, filter, n) {
    if (!_.isNumber(n)) { n = 3; }

    for (series in set) { /* For each series */
        /* Apply the filter */
        set[series] = filter(set[series], n);
    }

    return set;
}
/**
 * Returns the string to give to gnuplot based on the value of options.time.
 */
function time_format(options) {
	if (_.isString(options.time)) {
		/* Translate the string we've been given into a format */
		switch(options.time) {
			case 'days':
			case 'Days':
				return "%d/%m";
			case 'hours':
			case 'Hours':
				return "%H:%M";
			default: /* Presume we've been given a gnuplot-readable time format string */
				return options.time;
		}
	} else { /* Just default to hours */
		return "%H:%M";
	}
}
/**
 * Sets up gnuplot based on the properties we're given in the options object.
 */
function setup_gnuplot(gnuplot, options) {
	if (options.format === 'svg') { /* Setup gnuplot for SVG */
		gnuplot.stdin.write('set term svg fname \"Helvetica\" fsize 14\n');
	} else { /* Default: setup Gnuplot output to postscript so ps2pdf can interpret it */
		gnuplot.stdin.write('set term postscript landscape enhanced color dashed \"Helvetica\" 14\n');
	}

	/* Formatting Options */
	if (options.time) {
		gnuplot.stdin.write('set xdata time\n');
		gnuplot.stdin.write('set timefmt "%s"\n');
		gnuplot.stdin.write('set format x "' + time_format(options.time) + '"\n');
		gnuplot.stdin.write('set xlabel "time"\n');
	}
	if (options.title) {
		gnuplot.stdin.write('set title "'+options.title+'"\n');
	}
	if (options.logscale) {
		gnuplot.stdin.write('set logscale y\n');
	}
	if (options.xlabel) {
		gnuplot.stdin.write('set xlabel "'+options.xlabel+'"\n');
	}
	if (options.ylabel) {
		gnuplot.stdin.write('set ylabel "'+options.ylabel+'"\n');
	}

	/* Setup ticks */
	gnuplot.stdin.write('set grid xtics ytics mxtics\n');
	gnuplot.stdin.write('set mxtics\n');

	if (options.nokey) {
		gnuplot.stdin.write('set nokey\n');
	}
}
/**
 * Called after Gnuplot has finished.
 */
function post_gnuplot_processing(error, stdout, stderr) {
	/* Print stuff */
	console.log('stdout: ' + stdout);
	console.log('stderr: ' + stderr);
	if (error !== null) {
		console.log('exec error: ' + error);
	}
}

/* -------- Public Functions -------- */

/**
 * Plots data to a PDF file. If it does not exist, the PDF file will be created, otherwise this plot will
 * be appended as a new page.
 */
function plot(options) {
	/* Required Options */
	if (!options.data || !options.filename) {
		throw("The options object must have 'data' and 'filename' properties!");
		return;
	}
	/* Translate data into an object if needs be */
	if (_.isArray(options.data)) {
		if (_.isEqual(_.flatten(options.data), options.data)) { /* If it's a one-dimentional array */
			options.data = { 'Series 1': options.data };
		}
	}

	/* Defaults */
	if (!options.style) {
		options.style = 'lines'; /* Default to lines */
	}

	/* Apply moving averages and maximums */
	if (options.moving_avg) {
		options.data = apply_moving_filter(options.data, moving_average, options.moving_avg);
	}
	if (options.moving_max) {
		options.data = apply_moving_filter(options.data, moving_maximum, options.moving_max);
	}

	/* Execute Gnuplot specifing a function to be called when it terminates */
	if (options.format === 'svg') {
		gnuplot = exec('gnuplot > '+options.filename, post_gnuplot_processing);
	} else {
		gnuplot = exec('gnuplot | ps2pdf - '+options.filename, post_gnuplot_processing);
	}

	/* Sets up gnuplot based on the properties we've been given in the options object */
	setup_gnuplot(gnuplot, options);

	/* Get an array containing all the series */
	var series = _.keys(options.data);
	/* Reject series that are functions or come from higher up the protoype chain */
	var i;
	for (i = 0; i < series.length; i += 1) {
		if (!options.data.hasOwnProperty(series[i]) || typeof options.data[series[i]] === 'function') {
			delete series[i]; /* undefine this element */
		}
	}
	/* Filter out any undefined elements */
	series = _.filter(series, function() { return true; });

	/* Print the command to actually do the plot */
	gnuplot.stdin.write('plot');
	for (i = 1; i <= series.length; i += 1) { /* For each series */
		/* Instruct gnuplot to plot this series */
		gnuplot.stdin.write('\'-\' using 1:2 title\''+series[i - 1]+'\' with '+options.style+' lt 1 lc '+i);
		/* If another series is to follow, add a comma */
		if (i < series.length) { gnuplot.stdin.write(','); }
	}
	gnuplot.stdin.write('\n');

	/* Print out the data */
	for (i = 0; i < series.length; i += 1) { /* For each series */
		for (key in options.data[series[i]]) {
			gnuplot.stdin.write(key+' '+options.data[series[i]][key]+'\n');
		}
		/* Terminate the data */
		gnuplot.stdin.write('e\n');
	}

	gnuplot.stdin.end();
}

/* -------- Exports -------- */

exports.plot = plot;
<|endoftext|>


<commit_before>#! /usr/bin/env node

const fse = require('fs-extra'),
      figlet = require('figlet'),
      controller = require('../heredocs/controller.js'),
      model = require('../heredocs/model.js'),
      views = [require('../heredocs/views/create.js'),
              require('../heredocs/views/edit.js'),
              require('../heredocs/views/details.js'),
              require('../heredocs/views/delete.js'),
              require('../heredocs/views/index.js')],
      substitute = require('./substitute.js');

const version = require('../../package.json').version,
      CONTROLLERS = "Controllers",
      MODELS = "Models",
      VIEWS = "Views";

let model_name;
if (!process.argv[2]) {
  console.log("Please pass a ModelName as a command-line argument.");
  process.exit(1);
}
else {
  model_name = process.argv[2];
}

process.title = "aspnet-scaffolder";

console.log(figlet.textSync(process.title, {
  font: 'Big',
  horizontalLayout: 'fitted',
  verticalLayout: 'Default'
}), '\nASP.NET Scaffolding Tool v' + version + '\n')

// Ensure appropriate directory structure exists.
// For views, we must have ./Views/${model_name}.
[CONTROLLERS, MODELS, VIEWS].forEach((dir) => {
  fse.ensureDir(`${process.cwd()}/${dir}`, (err) => {
    console.log(err);

    fse.ensureDir(`${process.cwd()}/${VIEWS}/${model_name}`, (err) => {
      console.log(err);

      /* **************************************************************** */
      // Create Model
      fse.writeFile(`${process.cwd()}/${MODELS}/${model_name}.cs`, substitute.model(model.model, model_name, "${CLASS}"), 'utf-8', (err) => {
        if (err) console.log(err);
        console.log(`Created ${MODELS}/${model_name}.cs.`);
      });

      // With model name, create controller
      fse.writeFile(`${process.cwd()}/${CONTROLLERS}/${model_name}Controller.cs`, substitute.controller(controller.controller, model_name), 'utf-8', (err) => {
        if (err) console.log(err);
        console.log(`Created ${CONTROLLERS}/${model_name}Controller.`);
      });

      // Create views
      views.forEach((view) => {
        fse.writeFile(`${process.cwd()}/${VIEWS}/${model_name}/${view.name}`, substitute.view(view.template, model_name), 'utf-8', (err) => {
          if (err) console.log(err);
          console.log(`Created ${VIEWS}/${model_name}/${view.name}.`);
        });
      });
    });
  });
});

<commit_msg>var fix<commit_after>#! /usr/bin/env node

const fse = require('fs-extra'),
      figlet = require('figlet'),
      controller = require('../heredocs/controller.js'),
      model = require('../heredocs/model.js'),
      views = [require('../heredocs/views/create.js'),
              require('../heredocs/views/edit.js'),
              require('../heredocs/views/details.js'),
              require('../heredocs/views/delete.js'),
              require('../heredocs/views/index.js')],
      substitute = require('./substitute.js');

const CONTROLLERS = "Controllers",
      MODELS = "Models",
      VIEWS = "Views",
      version = require('../../package.json').version;

let model_name;
if (!process.argv[2]) {
  console.log("Please pass a ModelName as a command-line argument.");
  process.exit(1);
}
else {
  model_name = process.argv[2];
}

process.title = "aspnet-scaffolder";

console.log(figlet.textSync(process.title, {
  font: 'Big',
  horizontalLayout: 'fitted',
  verticalLayout: 'Default'
}), '\nASP.NET Scaffolding Tool v' + version + '\n');

// Ensure appropriate directory structure exists.
// For views, we must have ./Views/${model_name}.
[CONTROLLERS, MODELS, VIEWS].forEach((dir) => {
  fse.ensureDir(`${process.cwd()}/${dir}`, (err) => {
    console.log(err);

    fse.ensureDir(`${process.cwd()}/${VIEWS}/${model_name}`, (err) => {
      console.log(err);

      /* **************************************************************** */
      // Create Model
      fse.writeFile(`${process.cwd()}/${MODELS}/${model_name}.cs`, substitute.model(model.model, model_name, "${CLASS}"), 'utf-8', (err) => {
        if (err) console.log(err);
        console.log(`Created ${MODELS}/${model_name}.cs.`);
      });

      // With model name, create controller
      fse.writeFile(`${process.cwd()}/${CONTROLLERS}/${model_name}Controller.cs`, substitute.controller(controller.controller, model_name), 'utf-8', (err) => {
        if (err) console.log(err);
        console.log(`Created ${CONTROLLERS}/${model_name}Controller.`);
      });

      // Create views
      views.forEach((view) => {
        fse.writeFile(`${process.cwd()}/${VIEWS}/${model_name}/${view.name}`, substitute.view(view.template, model_name), 'utf-8', (err) => {
          if (err) console.log(err);
          console.log(`Created ${VIEWS}/${model_name}/${view.name}.`);
        });
      });
    });
  });
});

<|endoftext|>