<commit_before>/*!
    Underscore.js templates as a standalone implementation.
    JavaScript micro-templating, similar to John Resig's implementation.
    Underscore templates documentation: http://documentcloud.github.com/underscore/#template
    Modifyed by marlun78
*/
(function () {

    'use strict';

    // By default, Underscore uses ERB-style template delimiters, change the
    // following template settings to use alternative delimiters.
    var settings = {
        evaluate: /<%([\s\S]+?)%>/g,
        interpolate: /<%=([\s\S]+?)%>/g,
        escape: /<%-([\s\S]+?)%>/g
    };

    // When customizing `templateSettings`, if you don't want to define an
    // interpolation, evaluation or escaping regex, we need one that is
    // guaranteed not to match.
    var noMatch = /.^/;

    // Certain characters need to be escaped so that they can be put into a
    // string literal.
    var escapes = {
        '\\': '\\',
        "'": "'",
        'r': '\r',
        'n': '\n',
        't': '\t',
        'u2028': '\u2028',
        'u2029': '\u2029'
    };

    for (var p in escapes) {
        escapes[escapes[p]] = p;
    }

    var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
    var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;

    var tmpl = function (text, data, objectName) {
        settings.variable = objectName;

        // Compile the template source, taking care to escape characters that
        // cannot be included in a string literal and then unescape them in code
        // blocks.
        var source = "__p+='" + text
            .replace(escaper, function (match) {
                return '\\' + escapes[match];
            })
            .replace(settings.escape || noMatch, function (match, code) {
                return "'+\n_.escape(" + unescape(code) + ")+\n'";
            })
            .replace(settings.interpolate || noMatch, function (match, code) {
                return "'+\n(" + unescape(code) + ")+\n'";
            })
            .replace(settings.evaluate || noMatch, function (match, code) {
                return "';\n" + unescape(code) + "\n;__p+='";
            }) + "';\n";

        // If a variable is not specified, place data values in local scope.
        if (!settings.variable) {
            source = 'with(obj||{}){\n' + source + '}\n';
        }

        source = "var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n";

        var render = new Function(settings.variable || 'obj', source);

        if (data) {
            return render(data);
        }

        var template = function (data) {
            return render.call(this, data);
        };

        // Provide the compiled function source as a convenience for build time
        // precompilation.
        template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

        return template;
    };

    window.tmpl = tmpl;

}());<commit_msg>Add source URL to underscore templates file<commit_after>/*!
    Underscore.js templates as a standalone implementation.
    JavaScript micro-templating, similar to John Resig's implementation.
    Underscore templates documentation: http://documentcloud.github.com/underscore/#template
    Modifyed by marlun78
    Source: https://gist.github.com/marlun78/2701678
*/
(function () {

    'use strict';

    // By default, Underscore uses ERB-style template delimiters, change the
    // following template settings to use alternative delimiters.
    var settings = {
        evaluate: /<%([\s\S]+?)%>/g,
        interpolate: /<%=([\s\S]+?)%>/g,
        escape: /<%-([\s\S]+?)%>/g
    };

    // When customizing `templateSettings`, if you don't want to define an
    // interpolation, evaluation or escaping regex, we need one that is
    // guaranteed not to match.
    var noMatch = /.^/;

    // Certain characters need to be escaped so that they can be put into a
    // string literal.
    var escapes = {
        '\\': '\\',
        "'": "'",
        'r': '\r',
        'n': '\n',
        't': '\t',
        'u2028': '\u2028',
        'u2029': '\u2029'
    };

    for (var p in escapes) {
        escapes[escapes[p]] = p;
    }

    var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
    var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;

    var tmpl = function (text, data, objectName) {
        settings.variable = objectName;

        // Compile the template source, taking care to escape characters that
        // cannot be included in a string literal and then unescape them in code
        // blocks.
        var source = "__p+='" + text
            .replace(escaper, function (match) {
                return '\\' + escapes[match];
            })
            .replace(settings.escape || noMatch, function (match, code) {
                return "'+\n_.escape(" + unescape(code) + ")+\n'";
            })
            .replace(settings.interpolate || noMatch, function (match, code) {
                return "'+\n(" + unescape(code) + ")+\n'";
            })
            .replace(settings.evaluate || noMatch, function (match, code) {
                return "';\n" + unescape(code) + "\n;__p+='";
            }) + "';\n";

        // If a variable is not specified, place data values in local scope.
        if (!settings.variable) {
            source = 'with(obj||{}){\n' + source + '}\n';
        }

        source = "var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n";

        var render = new Function(settings.variable || 'obj', source);

        if (data) {
            return render(data);
        }

        var template = function (data) {
            return render.call(this, data);
        };

        // Provide the compiled function source as a convenience for build time
        // precompilation.
        template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

        return template;
    };

    window.tmpl = tmpl;

}());
<|endoftext|>


<commit_before>/* global app:true, io:false */

var socket;
var myname;
var block_id;
var blockData;
var ledArray;
var ledData;
var imageData;
var imageTime;
var frameData;
var current_frame=0;

var frameTimer;

function frameTimerFunc()
{
  if (current_frame < imageData.length) {
    current_frame = current_frame + 1;
    getVideo(imageData[current_frame]);
    draw(frameData);
  }else{
    frameTimerStop();
  }
}

function frameTimerStop() {  clearInterval(frameTimer);  }

(function() {
  'use strict';

  //var blockdata = [];
  //var qs = new Querystring();
  //var block_id = qs.get("myBlock");

  socket = io.connect();
  socket.on('connect', function(){
  	socket.emit('/replay/#join');
  });

  socket.on('/replay/#postdata', function(data){
  	blockData = [];
	ledData = [];
	blockData.push(data[0]); // time
	blockData.push(data[1]); // owner
	blockData.push(data[2]); // experiment id
	blockData.push(data[3]); // pattern id
	ledArray = data[4];

	// image dir = 171.65.102.132:3001/blockid/

	//console.dir(ledData);

	var info_p = document.createElement("p");
	var info_node_0 = document.createTextNode("Block ID: "+block_id);
	var info_node_1 = document.createTextNode("Block Start Time: "+data[0]);
	var info_node_2 = document.createTextNode("Owner : "+data[1]);
	var info_node_3 = document.createTextNode("Experiment : experiment id "+data[2]);
	var info_node_4 = document.createTextNode("Pattern : "+ (data[3] == 0 ? "Freeform Experiment" : "pattern id "+data[3]));
	info_p.appendChild(info_node_0);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_1);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_2);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_3);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_4);

	var info_box_div = document.getElementById('info_box');
	info_box_div.appendChild(info_p);

	//console.log(ledData);
	//console.log(ledData.length);
	//console.log(ledTime);
	//console.log(ledTime.length);

	socket.emit('/replay/#callimglist', { targetBlock: block_id});
  });

  socket.on('/replay/#postimglist', function(data){
	imageData = [];
	imageTime = [];
	data.forEach(function(filename){
	    var res = filename.split(".");
	    imageTime.push(Math.round(parseInt(res[0])/100)*100);
	    imageData.push(filename);
	});

	//console.log(imageData);
	//console.log(imageData.length);
	//console.log(imageTime);
	//console.log(imageTime.length);

	for (var i=0;i<data[4].length/2;i++)
	{
	  //ledData.push(data[4][i*2]);
	  //ledTime.push(Math.round(parseInt(data[4][i*2+1])));

	  var led = new Object();
	  led.id = i;
	  led.time = parseInt(data[4][i*2+1]);
	  led.msec = led.time - imageTime[0];
	  led.arrow = data[4][i*2];

	  ledData.push(led);
	}

	setupCanvas();
	getVideo(imageData[current_frame]);
	datavis_width = imageData.length * datavis_gap+50;

	frameData = [];
	for (var i=0;i<=imageData.length;i++){
		var frame = new Object();
		frame.id = i;
		frame.msec_original = imageTime[i];
		frame.msec = imageTime[i]-imageTime[0];

		var d = new Date(0);
		d.setTime(imageTime[i]);
		frame.time = d;

		frame.ledarray = [];
		if (i<imageData.length) {
		    frame.ledarray = ledData.filter(function (el) {
			  return el.time >= imageTime[i] &&
			  el.time < imageTime[i+1];
		    });
		}
		frameData.push(frame);
	}
	console.dir(frameData);
	draw(frameData);
	//var path = '../../Dropbox/live-gallery/'+targetBlock;
  });

  socket.on('/replay/#newUser', function(user) {
      myname = user;
      console.log('>>> DB connected' + block_id);
  });

  socket.on('message', function (message) {
      console.log(message);
  });

  socket.on('disconnect', function() {
      console.log('>>> DB disconnected');
  });

  app = app || {};

  app.Reset = Backbone.Model.extend({
    defaults: {
      success: false,
      errors: [],
      errfor: {},
      blockid: undefined,
      confitm: ''
    },
    url: function(){
      return '/account/replay/' + this.get('block_id') +'/';
    }
  });

  app.MovieView = Backbone.View.extend({
    el: '#playback_nav',
    template: _.template( $('#tmpl-movie_nav').html() ),
    events: {
      'click .btn-play': 'reqPlay',
      'click .btn-stop': 'reqStop',
      'click .btn-first': 'reqFirstFrame'
    },
    initialize: function() {
      //this.model = new app.Blocks();
      //this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template);
    },
    reqPlay: function() {
      frameTimer=setInterval(function(){frameTimerFunc()},200);
    },
    reqStop: function() {
      frameTimerStop();
    },
    reqFirstFrame: function() {
      frameTimerStop();
      current_frame = 0;
      getVideo(imageData[current_frame]);
      draw(frameData);
    }
  });

  app.Router = Backbone.Router.extend({
    routes: {
      'account/replay/': 'default',
      'account/replay/load/:block/': 'load'
    },
    default: function(){
      console.log(" >>default view, nothing loaded");
    },
    load: function(block) {
      console.log(">> loading block "+block);
      block_id = block;
      socket.emit('/replay/#callblock', { targetBlock: block});
    }
  });

  $(document).ready(function() {
    app.movieView = new app.MovieView();
    app.router = new app.Router();
    Backbone.history.start({ pushState: true });
  });
}());

/////////////////////////////
// Canvas setup functions
/////////////////////////////

var 	shape_bg,
	shape_stage,
	g_ledL,
	g_ledR,
	g_ledU,
	g_ledD,
	led_L,
	led_R,
	led_U,
	led_D,
	vid_canvas,
	svg_led,
	vid_context,
	svg_data_vis;

var 	vid_width = 640,
	vid_height = 480,
	m = 20,
	degrees = 180 / Math.PI,
	datavis_width = 1600,
	datavis_height = 100,
	datavis_gap = 5;

function setupCanvas() { // called in init
	vid_canvas = d3.select("#canvasArea").append("canvas")
		.attr("class", "display-canvas")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	svg_led = d3.select("#canvasArea").append("svg:svg")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	vid_context = vid_canvas.node().getContext("2d");

	led_L = svg_led.append("svg:g").
		append("svg:polygon")
		.attr("points", "26,230 66,260 26,290")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_R = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "654,230 654,290 614,260")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_U = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,26 370,26 340,66")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_D = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,494 340,454 370,494")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	//window.setInterval(getVideo, 1000/10);

	/*d3.timer(function(){
		joystick_draw();
		drawObjects();
	});*/
}

function resetCanvas(e) { // on resize events
    max_val = (document.getElementById("joystickArea").offsetWidth-100)/2;
    console.log('max_val :' + max_val);

    // resize the canvas - this clears the canvas
    control_canvas.width = document.getElementById("joystickArea").offsetWidth-20;
    control_canvas.height = control_canvas.width;
    halfWidth = (control_canvas.width)/2;
    halfHeight = (control_canvas.height)/2;

    rect_joy = control_canvas.getBoundingClientRect();

    // make sure we scroll to the top left.
    window.scrollTo(0, 0);
}

function updateLEDs(arrow){
	led_U.style("opacity",arrow.int1);
	led_L.style("opacity",arrow.int2);
	led_D.style("opacity",arrow.int3);
	led_R.style("opacity",arrow.int4);
}

var draw = function(d3data){
    d3.select("svg")
	.remove();

    /*var tip = d3.tip()
	.attr('class', 'd3-tip')
	.direction('e')
	.offset([0,10])
	.html(function(d) {
		return "time: "+d.time;
	});*/

    svg_data_vis = d3.select("#data_vis_box").append("svg")
	    .attr("width", datavis_width)
	    .attr("height", datavis_height)
	    .append("g");
	    //.attr("transform", "translate(4, 4)");

    //svg.call(tip);

    var frame = svg_data_vis.selectAll(".frame")
	    .data(d3data)
    .enter().append("g")
	    .attr("class", "block")
	    .attr("transform", function (d,i)
	    {
		    var dx = i*datavis_gap;
		    var dy = 20;
		    return "translate(" + dx + ","+ dy + ")";
	    });
	    //.on('mouseover', tip.show)
	    //.on('mouseout', tip.hide);

    frame.append("rect")
	    .attr("class", function (d,i)
	    {
		    var class_name;
		    if(i==current_frame){
		      class_name="current-frame";
		    }
		    else{
		      if (d.ledarray.length>0){
			    class_name="frame-with-led";
		      }else{
			    class_name="frame-no-led";
		      }

		    }
		    return class_name;
	    })
	    .attr("width", 2)
	    .attr("height", 5);
	    /*.attr("led", function (d){
		    if (d.ledarray.length>0){
			    return true;
		    }else{
			    return false
		    }
	    });*/
	    //.on('mouseover', tip.show)
	    //.on('mouseout', tip.hide)
	    //.on('click', mouseclick);

    /*block.append("text")
	    .attr("class", function (d)
	    {
		    var class_name;
		    if (d.username==myname){
			    class_name="my-block-name";
		    }else{
			    class_name="not-my-block-name";
		    }
		    return class_name;
	    })
	    .attr("x",10)
	    .attr("y",14)
	    .attr("dy", ".3em")
	    .text(function(d) {
		    var h = d.time.getHours();
		    var ampm = (h<12 ? "a":"p");
		    //h=("0" + h%12).slice(-2);
		    h=(h%12);
		    if(h==0){h="12";}
		    var m = d.time.getMinutes();
		    m=("0" + m).slice(-2);
		    return h+":"+m+ampm;
	    })
	    .on('mouseover', tip.show)
	    .on('mouseout', tip.hide);*/

    /*block.append('text')
	    .attr("class", function (d){
		    var class_name;
		    if (d.username==myname){
			    class_name="my-block-name";
		    }else{
			    class_name="not-my-block-name";
		    }
		    return class_name;})
	    .attr('font-family', 'FontAwesome')
	    .style('font-size', '150%' )
	    .attr("x",22)
	    .attr("y",44)
	    .text(function(d) { return '\uf023' })
	    .style("display", function(d) {
		    return d.lock == true ? null : "none";
	    });*/
    /*var currentframe_g = document.getElementsByClassName("current-frame");
	currentframe_g.scrollIntoView(true);*/
  }

function getVideo(frame_img_name){
	getVidFrame("http://171.65.102.132:3001/" + block_id +"/"+ frame_img_name, function(image) {
		vid_context.clearRect(20, 20, vid_width+20, vid_height+20);
		vid_context.drawImage(image, 20, 20, vid_width, vid_height);
	});
}

function getVidFrame(path, callback) {
	var image = new Image;
	image.src = path;
	image.onload = function() {
		callback(image);
	    //console.log(new Date().getTime());
	};
}
<commit_msg>array<commit_after>/* global app:true, io:false */

var socket;
var myname;
var block_id;
var blockData;
var ledArray;
var ledData;
var imageData;
var imageTime;
var frameData;
var current_frame=0;

var frameTimer;

function frameTimerFunc()
{
  if (current_frame < imageData.length) {
    current_frame = current_frame + 1;
    getVideo(imageData[current_frame]);
    draw(frameData);
  }else{
    frameTimerStop();
  }
}

function frameTimerStop() {  clearInterval(frameTimer);  }

(function() {
  'use strict';

  //var blockdata = [];
  //var qs = new Querystring();
  //var block_id = qs.get("myBlock");

  socket = io.connect();
  socket.on('connect', function(){
  	socket.emit('/replay/#join');
  });

  socket.on('/replay/#postdata', function(data){
  	blockData = [];
	ledData = [];
	blockData.push(data[0]); // time
	blockData.push(data[1]); // owner
	blockData.push(data[2]); // experiment id
	blockData.push(data[3]); // pattern id
	ledArray = data[4];

	// image dir = 171.65.102.132:3001/blockid/

	//console.dir(ledData);

	var info_p = document.createElement("p");
	var info_node_0 = document.createTextNode("Block ID: "+block_id);
	var info_node_1 = document.createTextNode("Block Start Time: "+data[0]);
	var info_node_2 = document.createTextNode("Owner : "+data[1]);
	var info_node_3 = document.createTextNode("Experiment : experiment id "+data[2]);
	var info_node_4 = document.createTextNode("Pattern : "+ (data[3] == 0 ? "Freeform Experiment" : "pattern id "+data[3]));
	info_p.appendChild(info_node_0);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_1);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_2);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_3);
	info_p.appendChild(document.createElement("br"));
	info_p.appendChild(info_node_4);

	var info_box_div = document.getElementById('info_box');
	info_box_div.appendChild(info_p);

	//console.log(ledData);
	//console.log(ledData.length);
	//console.log(ledTime);
	//console.log(ledTime.length);

	socket.emit('/replay/#callimglist', { targetBlock: block_id});
  });

  socket.on('/replay/#postimglist', function(data){
	imageData = [];
	imageTime = [];
	data.forEach(function(filename){
	    var res = filename.split(".");
	    imageTime.push(Math.round(parseInt(res[0])/100)*100);
	    imageData.push(filename);
	});

	//console.log(imageData);
	//console.log(imageData.length);
	//console.log(imageTime);
	//console.log(imageTime.length);

	for (var i=0;i<data[4].length/2;i++)
	{
	  //ledData.push(data[4][i*2]);
	  //ledTime.push(Math.round(parseInt(data[4][i*2+1])));

	  var led = new Object();
	  led.id = i;
	  led.time = parseInt(ledArray[i*2+1]);
	  led.msec = led.time - imageTime[0];
	  led.arrow = data[4][i*2];

	  ledData.push(led);
	}

	setupCanvas();
	getVideo(imageData[current_frame]);
	datavis_width = imageData.length * datavis_gap+50;

	frameData = [];
	for (var i=0;i<=imageData.length;i++){
		var frame = new Object();
		frame.id = i;
		frame.msec_original = imageTime[i];
		frame.msec = imageTime[i]-imageTime[0];

		var d = new Date(0);
		d.setTime(imageTime[i]);
		frame.time = d;

		frame.ledarray = [];
		if (i<imageData.length) {
		    frame.ledarray = ledData.filter(function (el) {
			  return el.time >= imageTime[i] &&
			  el.time < imageTime[i+1];
		    });
		}
		frameData.push(frame);
	}
	console.dir(frameData);
	draw(frameData);
	//var path = '../../Dropbox/live-gallery/'+targetBlock;
  });

  socket.on('/replay/#newUser', function(user) {
      myname = user;
      console.log('>>> DB connected' + block_id);
  });

  socket.on('message', function (message) {
      console.log(message);
  });

  socket.on('disconnect', function() {
      console.log('>>> DB disconnected');
  });

  app = app || {};

  app.Reset = Backbone.Model.extend({
    defaults: {
      success: false,
      errors: [],
      errfor: {},
      blockid: undefined,
      confitm: ''
    },
    url: function(){
      return '/account/replay/' + this.get('block_id') +'/';
    }
  });

  app.MovieView = Backbone.View.extend({
    el: '#playback_nav',
    template: _.template( $('#tmpl-movie_nav').html() ),
    events: {
      'click .btn-play': 'reqPlay',
      'click .btn-stop': 'reqStop',
      'click .btn-first': 'reqFirstFrame'
    },
    initialize: function() {
      //this.model = new app.Blocks();
      //this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template);
    },
    reqPlay: function() {
      frameTimer=setInterval(function(){frameTimerFunc()},200);
    },
    reqStop: function() {
      frameTimerStop();
    },
    reqFirstFrame: function() {
      frameTimerStop();
      current_frame = 0;
      getVideo(imageData[current_frame]);
      draw(frameData);
    }
  });

  app.Router = Backbone.Router.extend({
    routes: {
      'account/replay/': 'default',
      'account/replay/load/:block/': 'load'
    },
    default: function(){
      console.log(" >>default view, nothing loaded");
    },
    load: function(block) {
      console.log(">> loading block "+block);
      block_id = block;
      socket.emit('/replay/#callblock', { targetBlock: block});
    }
  });

  $(document).ready(function() {
    app.movieView = new app.MovieView();
    app.router = new app.Router();
    Backbone.history.start({ pushState: true });
  });
}());

/////////////////////////////
// Canvas setup functions
/////////////////////////////

var 	shape_bg,
	shape_stage,
	g_ledL,
	g_ledR,
	g_ledU,
	g_ledD,
	led_L,
	led_R,
	led_U,
	led_D,
	vid_canvas,
	svg_led,
	vid_context,
	svg_data_vis;

var 	vid_width = 640,
	vid_height = 480,
	m = 20,
	degrees = 180 / Math.PI,
	datavis_width = 1600,
	datavis_height = 100,
	datavis_gap = 5;

function setupCanvas() { // called in init
	vid_canvas = d3.select("#canvasArea").append("canvas")
		.attr("class", "display-canvas")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	svg_led = d3.select("#canvasArea").append("svg:svg")
		.attr("width", vid_width+40)
		.attr("height", vid_height+40)
		.style("position", "absolute")
		.style("top", 0)
		.style("left", 0);

	vid_context = vid_canvas.node().getContext("2d");

	led_L = svg_led.append("svg:g").
		append("svg:polygon")
		.attr("points", "26,230 66,260 26,290")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_R = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "654,230 654,290 614,260")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_U = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,26 370,26 340,66")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	led_D = svg_led.append("svg:g")
		.append("svg:polygon")
		.attr("points", "310,494 340,454 370,494")
		.style("fill", "#ffffff")
		.style("opacity", "0");

	//window.setInterval(getVideo, 1000/10);

	/*d3.timer(function(){
		joystick_draw();
		drawObjects();
	});*/
}

function resetCanvas(e) { // on resize events
    max_val = (document.getElementById("joystickArea").offsetWidth-100)/2;
    console.log('max_val :' + max_val);

    // resize the canvas - this clears the canvas
    control_canvas.width = document.getElementById("joystickArea").offsetWidth-20;
    control_canvas.height = control_canvas.width;
    halfWidth = (control_canvas.width)/2;
    halfHeight = (control_canvas.height)/2;

    rect_joy = control_canvas.getBoundingClientRect();

    // make sure we scroll to the top left.
    window.scrollTo(0, 0);
}

function updateLEDs(arrow){
	led_U.style("opacity",arrow.int1);
	led_L.style("opacity",arrow.int2);
	led_D.style("opacity",arrow.int3);
	led_R.style("opacity",arrow.int4);
}

var draw = function(d3data){
    d3.select("svg")
	.remove();

    /*var tip = d3.tip()
	.attr('class', 'd3-tip')
	.direction('e')
	.offset([0,10])
	.html(function(d) {
		return "time: "+d.time;
	});*/

    svg_data_vis = d3.select("#data_vis_box").append("svg")
	    .attr("width", datavis_width)
	    .attr("height", datavis_height)
	    .append("g");
	    //.attr("transform", "translate(4, 4)");

    //svg.call(tip);

    var frame = svg_data_vis.selectAll(".frame")
	    .data(d3data)
    .enter().append("g")
	    .attr("class", "block")
	    .attr("transform", function (d,i)
	    {
		    var dx = i*datavis_gap;
		    var dy = 20;
		    return "translate(" + dx + ","+ dy + ")";
	    });
	    //.on('mouseover', tip.show)
	    //.on('mouseout', tip.hide);

    frame.append("rect")
	    .attr("class", function (d,i)
	    {
		    var class_name;
		    if(i==current_frame){
		      class_name="current-frame";
		    }
		    else{
		      if (d.ledarray.length>0){
			    class_name="frame-with-led";
		      }else{
			    class_name="frame-no-led";
		      }

		    }
		    return class_name;
	    })
	    .attr("width", 2)
	    .attr("height", 5);
	    /*.attr("led", function (d){
		    if (d.ledarray.length>0){
			    return true;
		    }else{
			    return false
		    }
	    });*/
	    //.on('mouseover', tip.show)
	    //.on('mouseout', tip.hide)
	    //.on('click', mouseclick);

    /*block.append("text")
	    .attr("class", function (d)
	    {
		    var class_name;
		    if (d.username==myname){
			    class_name="my-block-name";
		    }else{
			    class_name="not-my-block-name";
		    }
		    return class_name;
	    })
	    .attr("x",10)
	    .attr("y",14)
	    .attr("dy", ".3em")
	    .text(function(d) {
		    var h = d.time.getHours();
		    var ampm = (h<12 ? "a":"p");
		    //h=("0" + h%12).slice(-2);
		    h=(h%12);
		    if(h==0){h="12";}
		    var m = d.time.getMinutes();
		    m=("0" + m).slice(-2);
		    return h+":"+m+ampm;
	    })
	    .on('mouseover', tip.show)
	    .on('mouseout', tip.hide);*/

    /*block.append('text')
	    .attr("class", function (d){
		    var class_name;
		    if (d.username==myname){
			    class_name="my-block-name";
		    }else{
			    class_name="not-my-block-name";
		    }
		    return class_name;})
	    .attr('font-family', 'FontAwesome')
	    .style('font-size', '150%' )
	    .attr("x",22)
	    .attr("y",44)
	    .text(function(d) { return '\uf023' })
	    .style("display", function(d) {
		    return d.lock == true ? null : "none";
	    });*/
    /*var currentframe_g = document.getElementsByClassName("current-frame");
	currentframe_g.scrollIntoView(true);*/
  }

function getVideo(frame_img_name){
	getVidFrame("http://171.65.102.132:3001/" + block_id +"/"+ frame_img_name, function(image) {
		vid_context.clearRect(20, 20, vid_width+20, vid_height+20);
		vid_context.drawImage(image, 20, 20, vid_width, vid_height);
	});
}

function getVidFrame(path, callback) {
	var image = new Image;
	image.src = path;
	image.onload = function() {
		callback(image);
	    //console.log(new Date().getTime());
	};
}
<|endoftext|>


<commit_before>var Accessibility = {};
var AccessibilityTestRunner = {};
var Animation = {};
var ApplicationTestRunner = {};
var Audits = {};
var Audits2 = {};
var AuditsTestRunner = {};
var Bindings = {};
var BindingsTestRunner = {};
var CPUProfilerTestRunner = {};
var Changes = {};
var CmModes = {};
var ColorPicker = {};
var Common = {};
var Components = {};
var ConsoleCounters = {};
var ConsoleModel = {};
var ConsoleTestRunner = {};
var CookieTable = {};
var Coverage = {};
var CoverageTestRunner = {};
var DataGrid = {};
var DataGridTestRunner = {};
var DeviceModeTestRunner = {};
var Devices = {};
var Diff = {};
var Elements = {};
var ElementsTestRunner = {};
var Emulation = {};
var EventListeners = {}
var Extensions = {};
var ExtensionsTestRunner = {};
var Formatter = {};
var FormatterWorker = {};
var HARImporter = {};
var HeapProfilerTestRunner = {};
var HeapSnapshotModel = {};
var HeapSnapshotWorker = {};
var Help = {};
var Host = {};
var InlineEditor = {};
var LayerViewer = {};
var Layers = {};
var LayersTestRunner = {};
var Main = {};
var MobileThrottling = {};
var Network = {};
var NetworkLog = {};
var NetworkPriorities = {};
var NetworkTestRunner = {};
var ObjectUI = {};
var PerfUI = {};
var PerformanceTestRunner = {};
var Persistence = {};
var ProductRegistry = {};
var ProductRegistryImpl = {};
var Profiler = {};
var Protocol = {};
var QuickOpen = {};
var Resources = {};
var SDK = {};
var SDKTestRunner = {};
var Screencast = {};
var Security = {};
var Services = {};
var Settings = {};
var Snippets = {};
var SourceFrame = {};
var Sources = {};
var SourcesTestRunner = {};
var TestRunner = {};
var TextEditor = {};
var TextUtils = {};
var Timeline = {};
var TimelineModel = {};
var UI = {};
var Workspace = {};
var WorkspaceDiff = {};
<commit_msg>remove Animations from chrome-devtools' user test shim (#26494)<commit_after>var Accessibility = {};
var AccessibilityTestRunner = {};
var ApplicationTestRunner = {};
var Audits = {};
var Audits2 = {};
var AuditsTestRunner = {};
var Bindings = {};
var BindingsTestRunner = {};
var CPUProfilerTestRunner = {};
var Changes = {};
var CmModes = {};
var ColorPicker = {};
var Common = {};
var Components = {};
var ConsoleCounters = {};
var ConsoleModel = {};
var ConsoleTestRunner = {};
var CookieTable = {};
var Coverage = {};
var CoverageTestRunner = {};
var DataGrid = {};
var DataGridTestRunner = {};
var DeviceModeTestRunner = {};
var Devices = {};
var Diff = {};
var Elements = {};
var ElementsTestRunner = {};
var Emulation = {};
var EventListeners = {}
var Extensions = {};
var ExtensionsTestRunner = {};
var Formatter = {};
var FormatterWorker = {};
var HARImporter = {};
var HeapProfilerTestRunner = {};
var HeapSnapshotModel = {};
var HeapSnapshotWorker = {};
var Help = {};
var Host = {};
var InlineEditor = {};
var LayerViewer = {};
var Layers = {};
var LayersTestRunner = {};
var Main = {};
var MobileThrottling = {};
var Network = {};
var NetworkLog = {};
var NetworkPriorities = {};
var NetworkTestRunner = {};
var ObjectUI = {};
var PerfUI = {};
var PerformanceTestRunner = {};
var Persistence = {};
var ProductRegistry = {};
var ProductRegistryImpl = {};
var Profiler = {};
var Protocol = {};
var QuickOpen = {};
var Resources = {};
var SDK = {};
var SDKTestRunner = {};
var Screencast = {};
var Security = {};
var Services = {};
var Settings = {};
var Snippets = {};
var SourceFrame = {};
var Sources = {};
var SourcesTestRunner = {};
var TestRunner = {};
var TextEditor = {};
var TextUtils = {};
var Timeline = {};
var TimelineModel = {};
var UI = {};
var Workspace = {};
var WorkspaceDiff = {};
<|endoftext|>


<commit_before>import { h } from 'preact'
import styled from 'styled-components'

const Team = ({ rank, name, points, competitors }) => {
  return (
    <Card>
      <Rank>{rank}</Rank>

{name}

          {competitors.map(competitor => (

              {competitor.name} - {competitor.total_points.toFixed(1)}

          ))}

      <Points>{points}</Points>
    </Card>
  )
}

const Card = styled.div`
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  color: #555;
  font-size: 32px;
  display: flex;
  margin-bottom: 30px;
  padding: 15px 20px;
  width: 500px;
`

const Rank = styled.div`
  width: 10%;
`

const Details = styled.div`
  flex-basis: 0;
  flex-grow: 1;

  ul {
    padding: 0;
    margin: 0;
    color: #999;
    font-size: 14px;
    font-style: italic;
  }
`

const Points = styled.div`
  text-align: center;
  width: 20%;
`

export default Team
<commit_msg>Round team score to single decimal place<commit_after>import { h } from 'preact'
import styled from 'styled-components'

const Team = ({ rank, name, points, competitors }) => {
  return (
    <Card>
      <Rank>{rank}</Rank>

{name}

          {competitors.map(competitor => (

              {competitor.name} - {competitor.total_points.toFixed(1)}

          ))}

      <Points>{points.toFixed(1)}</Points>
    </Card>
  )
}

const Card = styled.div`
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  color: #555;
  font-size: 32px;
  display: flex;
  margin-bottom: 30px;
  padding: 15px 20px;
  width: 500px;
`

const Rank = styled.div`
  width: 10%;
`

const Details = styled.div`
  flex-basis: 0;
  flex-grow: 1;

  ul {
    padding: 0;
    margin: 0;
    color: #999;
    font-size: 14px;
    font-style: italic;
  }
`

const Points = styled.div`
  text-align: center;
  width: 20%;
`

export default Team
<|endoftext|>


<commit_before>(function () {
  'use strict';

  // Categorys controller
  angular
    .module('categorys')
    .controller('CategoryPollsController', CategoryPollsController);

  CategoryPollsController.$inject = [
    '$scope',
    '$translate',
    'categoryResolve',
    'Action',
    'ngDialog'
  ];

  function CategoryPollsController(
    $scope,
    $translate,
    category,
    Action,
    dialog
  ) {
    var vm = this;
    vm.category = category;

    // Infinity scroll
    $scope.stopped = false;
    $scope.busy = false;
    vm.page = 0;
    vm.sort = '-created';
    vm.language = $translate.use();
    vm.polls = [];

    handleLoadPolls();
    vm.handleLoadPolls = handleLoadPolls;
    function handleLoadPolls() {
      if ($scope.stopped || $scope.busy) return;
      $scope.busy = true;
      Action.get_category_polls(vm.category._id, vm.page, vm.language, vm.sort)
        .then(res => {
          if (!res.data.length || res.data.length === 0) {
            $scope.stopped = true;
            $scope.busy = false;
            return;
          }
          // Load options và tính vote cho các opt trong polls
          var promises = [];
          res.data.forEach(poll => {
            promises.push(prepareShowingData(poll));
          });
          return Promise.all(promises);
        })
        .then(results => {
          // Gán data vào list hiện tại
          results = results || [];
          vm.polls = _.union(vm.polls, results);
          vm.page += 1;
          $scope.busy = false;
          if (results.length < 10) { $scope.stopped = true; }
          if (!$scope.$$phase) $scope.$digest();
        })
        .catch(err => {
          $scope.busy = false;
          $scope.stopped = true;
          $scope.handleShowMessage(err.message, true);
        });
    }

    function prepareShowingData(poll) {
      return new Promise((resolve, reject) => {
        poll = Action.process_before_show(poll);
        return resolve(poll);
      });
    }

    $scope.poll_filter = poll => {
      if (poll.isPublic) {
        return true;
      } else {
        return poll.isCurrentUserOwner;
      }
    };
    $scope.delete_poll = (poll) => {
      if (!poll.isCurrentUserOwner) {
        $scope.handleShowMessage('MS_CM_AUTH_ERROR', true);
        return;
      }
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_DELETE',
        type: 3,
        button: 'LB_DELETE'
      }, confirm => {
        vm.polls = _.without(vm.polls, poll);
        Action.delete_poll(poll);
      });
    };
    $scope.report_poll = (poll) => {
      if (poll.reported) {
        $scope.handleShowMessageWithParam('MS_CM_REPORT_EXIST_ERROR', { title: poll.title }, true);
        return;
      }
      dialog.openConfirm({
        scope: $scope,
        templateUrl: 'modules/core/client/views/templates/report.dialog.template.html'
      }).then(reason => {
        handle_confirm(reason);
      }, reject => {
      });
      function handle_confirm(reason) {
        Action.save_report(poll, reason)
          .then(res => {
            poll.reported = (res) ? true : false;
            $scope.handleShowMessageWithParam('MS_CM_REPORT_SUCCESS', { title: poll.title }, false);
          })
          .catch(err => {
            $scope.handleShowMessage(err.message, true);
          });
      }
    };
    $scope.bookmark_poll = (poll) => {
      if (poll.bookmarked) {
        $scope.handleShowMessageWithParam('MS_CM_BOOKMARK_EXIST_ERROR', { title: poll.title }, true);
        return;
      }
      Action.save_bookmark(poll._id)
        .then(res => {
          poll.bookmarked = (res) ? true : false;
          $scope.handleShowMessageWithParam('MS_CM_BOOKMARK_SUCCESS', { title: poll.title }, false);
        })
        .catch(err => {
          $scope.handleShowMessage(err.message, true);
        });
    };
    $scope.follow_poll = (poll) => {
      if (!$scope.isLogged) {
        $scope.handleShowMessage('MS_CM_LOGIN_ERROR', true);
        return;
      }
      Action.save_follow(poll.follow)
        .then(res => {
          if (res) {
            poll.follow = res;
          } else {
            poll.follow = { poll: poll._id };
          }
        })
        .catch(err => {
          $scope.handleShowMessage(err.message, true);
        });
    };
  }
}());
<commit_msg>Commited from lenh at Mon, Sep 25, 2017  2:49:07 PM<commit_after>(function () {
  'use strict';

  // Categorys controller
  angular
    .module('categorys')
    .controller('CategoryPollsController', CategoryPollsController);

  CategoryPollsController.$inject = [
    '$scope',
    '$translate',
    'categoryResolve',
    'Action',
    'ngDialog'
  ];

  function CategoryPollsController(
    $scope,
    $translate,
    category,
    Action,
    dialog
  ) {
    var vm = this;
    vm.category = category;

    // Infinity scroll
    $scope.stopped = false;
    $scope.busy = false;
    vm.page = 0;
    vm.sort = '-created';
    vm.language = $translate.use();
    vm.polls = [];

    handleLoadPolls();
    vm.handleLoadPolls = handleLoadPolls;
    function handleLoadPolls() {
      if ($scope.stopped || $scope.busy) return;
      $scope.busy = true;
      Action.get_category_polls(vm.category._id, vm.page, vm.language, vm.sort)
        .then(res => {
          if (!res.data.length || res.data.length === 0) {
            $scope.stopped = true;
            $scope.busy = false;
            return;
          }
          // Load options và tính vote cho các opt trong polls
          var promises = [];
          res.data.forEach(poll => {
            promises.push(prepareShowingData(poll));
          });
          return Promise.all(promises);
        })
        .then(results => {
          // Gán data vào list hiện tại
          results = results || [];
          vm.polls = _.union(vm.polls, results);
          vm.page += 1;
          $scope.busy = false;
          if (results.length < 10) { $scope.stopped = true; }
          if (!$scope.$$phase) $scope.$digest();
        })
        .catch(err => {
          $scope.busy = false;
          $scope.stopped = true;
          $scope.handleShowMessage(err.message, true);
        });
    }
    vm.handleChangeSortType = handleChangeSortType;
    function handleChangeSortType() {
      vm.page = 0;
      $scope.busy = $scope.stopped = false;
      vm.polls = [];
      handleLoadPolls();
    }
    function prepareShowingData(poll) {
      return new Promise((resolve, reject) => {
        poll = Action.process_before_show(poll);
        return resolve(poll);
      });
    }

    $scope.poll_filter = poll => {
      if (poll.isPublic) {
        return true;
      } else {
        return poll.isCurrentUserOwner;
      }
    };
    $scope.delete_poll = (poll) => {
      if (!poll.isCurrentUserOwner) {
        $scope.handleShowMessage('MS_CM_AUTH_ERROR', true);
        return;
      }
      // Gọi function show dialog từ scope cha
      $scope.handleShowConfirm({
        content: 'LB_POLLS_CONFIRM_DELETE',
        type: 3,
        button: 'LB_DELETE'
      }, confirm => {
        vm.polls = _.without(vm.polls, poll);
        Action.delete_poll(poll);
      });
    };
    $scope.report_poll = (poll) => {
      if (poll.reported) {
        $scope.handleShowMessageWithParam('MS_CM_REPORT_EXIST_ERROR', { title: poll.title }, true);
        return;
      }
      dialog.openConfirm({
        scope: $scope,
        templateUrl: 'modules/core/client/views/templates/report.dialog.template.html'
      }).then(reason => {
        handle_confirm(reason);
      }, reject => {
      });
      function handle_confirm(reason) {
        Action.save_report(poll, reason)
          .then(res => {
            poll.reported = (res) ? true : false;
            $scope.handleShowMessageWithParam('MS_CM_REPORT_SUCCESS', { title: poll.title }, false);
          })
          .catch(err => {
            $scope.handleShowMessage(err.message, true);
          });
      }
    };
    $scope.bookmark_poll = (poll) => {
      if (poll.bookmarked) {
        $scope.handleShowMessageWithParam('MS_CM_BOOKMARK_EXIST_ERROR', { title: poll.title }, true);
        return;
      }
      Action.save_bookmark(poll._id)
        .then(res => {
          poll.bookmarked = (res) ? true : false;
          $scope.handleShowMessageWithParam('MS_CM_BOOKMARK_SUCCESS', { title: poll.title }, false);
        })
        .catch(err => {
          $scope.handleShowMessage(err.message, true);
        });
    };
    $scope.follow_poll = (poll) => {
      if (!$scope.isLogged) {
        $scope.handleShowMessage('MS_CM_LOGIN_ERROR', true);
        return;
      }
      Action.save_follow(poll.follow)
        .then(res => {
          if (res) {
            poll.follow = res;
          } else {
            poll.follow = { poll: poll._id };
          }
        })
        .catch(err => {
          $scope.handleShowMessage(err.message, true);
        });
    };
  }
}());
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { FastField, Field } from 'formik';
import { Box, Flex, Text, BoxProps } from 'rebass/styled-components';
import bows from 'bows';
import map from 'lodash/map';
import min from 'lodash/min';
import isInteger from 'lodash/isInteger';
import sortedLastIndexBy from 'lodash/sortedLastIndexBy';
import DeleteOutlineRoundedIcon from '@material-ui/icons/DeleteOutlineRounded';

import { fieldsAreValid, getFieldError, getCondensedUnits } from '../../core/forms';
import i18next from '../../core/language';
import { useFieldArray } from '../../core/hooks';
import { Body2, Headline, OrderedList, Title } from '../../components/elements/FontStyles';
import RadioGroup from '../../components/elements/RadioGroup';
import PopoverLabel from '../../components/elements/PopoverLabel';
import TextInput from '../../components/elements/TextInput';
import Icon from '../../components/elements/Icon';
import Button from '../../components/elements/Button';

import {
  deviceMeta,
  defaultUnits,
  insulinTypeOptions,
  trainingOptions,
} from './prescriptionFormConstants';

import {
  inputStyles,
  inlineInputStyles,
  fieldsetStyles,
  wideFieldsetStyles,
  borderedFieldsetStyles,
} from './prescriptionFormStyles';
import { MS_IN_MIN, MS_IN_HOUR, MS_IN_DAY } from '../../core/constants';

const t = i18next.t.bind(i18next);
const log = bows('PrescriptionTherapySettings');

const fieldsetPropTypes = {
  ...BoxProps,
  meta: PropTypes.object.isRequired,
  t: PropTypes.func.isRequired,
};

const convertMsPer24ToTimeString = msPer24 => {
  const hours = `0${new Date(msPer24).getUTCHours()}`.slice(-2);
  const minutes = `0${new Date(msPer24).getUTCMinutes()}`.slice(-2);
  return `${hours}:${minutes}`;
};

const convertTimeStringToMsPer24 = timeString => {
  const [hours, minutes] = map(timeString.split(':'), val => parseInt(val, 10));
  return (hours * MS_IN_HOUR) + (minutes * MS_IN_MIN);
}

export const PatientInfo = props => {
  const { t, meta, ...themeProps } = props;

  const {
    firstName,
    lastName,
    birthday,
    email,
  } = meta;

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Headline mb={2}>{t('Tidepool Loop Order Form and Treatment Plan')}</Headline>
      <Text>{firstName.value} {lastName.value}</Text>
      <Text>{t('Date of Birth:')} {birthday.value}</Text>
      <Text>{t('Email:')} {email.value}</Text>
    </Box>
  );
};

PatientInfo.propTypes = fieldsetPropTypes;

export const PatientTraining = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const pumpId = meta.initialSettings.pumpId.value;
  const pumpMeta = deviceMeta(pumpId, bgUnits);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Body2>
        {t('Request for certified pump trainer (CPT) in-person training. Required (TBD) for patients new to {{pumpId}}.', {
          pumpId: pumpMeta.manufacturerName,
        })}
      </Body2>
      <FastField
        as={RadioGroup}
        variant="vertical"
        id="training"
        name="training"
        options={trainingOptions}
        error={getFieldError(meta.training)}
      />
    </Box>
  );
};

PatientTraining.propTypes = fieldsetPropTypes;

export const InModuleTrainingNotification = props => {
  const { t, meta, ...themeProps } = props;

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Body2>
        {t('You have selected Tidepool Loop in-app tutorial self start. A request will not be sent for this patient to receive CPT training.')}
      </Body2>
    </Box>
  );
};

InModuleTrainingNotification.propTypes = fieldsetPropTypes;

export const GlucoseSettings = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const cgmType = meta.initialSettings.cgmType.value;
  const cgmMeta = deviceMeta(cgmType, bgUnits);

  const [refs, setRefs] = React.useState([]);
  const [focusedId, setFocusedId] = React.useState();

  const [bloodGlucoseTargetSchedule, , { move, remove, replace, push }] = useFieldArray({ name: 'initialSettings.bloodGlucoseTargetSchedule' });
  const schedulesLength = bloodGlucoseTargetSchedule.value.length;

  React.useEffect(() => {
    // add or remove refs as the schedule length changes
    setRefs(refs => (
      Array(schedulesLength).fill().map((_, i) => refs[i] || React.createRef())
    ));
  }, [schedulesLength]);

  React.useEffect(() => {
    isInteger(focusedId) && refs[focusedId].current.focus();
  }, [focusedId]);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Title mb={3}>{t('Glucose Settings')}</Title>
      <Box px={3}>
        <PopoverLabel
          id='correction-range'
          label={t('Correction Range')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('The correction range is the glucose range that you would like the app to correct your glucose to by adjusting insulin dosing.')}
              </Body2>
            </Box>
          )}
        />

        <Box p={3} mb={3} bg="lightestGrey">
          <Box>
            {map(bloodGlucoseTargetSchedule.value, (schedule, index) => (
              <Flex key={index} alignItems="flex-start" mb={3}>
                <Field
                  as={TextInput}
                  label={index === 0 && t('Start Time')}
                  type="time"
                  readOnly={index === 0}
                  step={MS_IN_MIN * 30 / 1000}
                  value={convertMsPer24ToTimeString(schedule.start, 'hh:mm')}
                  onChange={e => {
                    const start = convertTimeStringToMsPer24(e.target.value);
                    const newValue = {...bloodGlucoseTargetSchedule.value[index], start};
                    const valuesCopy = [...bloodGlucoseTargetSchedule.value];
                    valuesCopy.splice(index, 1);
                    const newPos = sortedLastIndexBy(valuesCopy, newValue, function(o) { return o.start; });
                    replace(index, newValue);
                    move(index, newPos);
                    setFocusedId(newPos);
                  }}
                  onFocus={() => setFocusedId(index)}
                  onBlur={() => setFocusedId(undefined)}
                  innerRef={refs[index]}
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.start`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.start`}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'start')}
                  {...inlineInputStyles}
                />
                <Field
                  as={TextInput}
                  label={index === 0 && t('Lower Target')}
                  type="number"
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.low`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.low`}
                  suffix={meta.initialSettings.bloodGlucoseUnits.value}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'low')}
                  {...inlineInputStyles}
                />
                <Text ml={3} mr={1} mt={index === 0 ? '33px' : '12px'}>-</Text>
                <Field
                  as={TextInput}
                  label={index === 0 && t('Upper Target')}
                  type="number"
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.high`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.high`}
                  suffix={meta.initialSettings.bloodGlucoseUnits.value}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'high')}
                  {...inlineInputStyles}
                />
                <Icon
                  mx={2}
                  mt={2}
                  variant="button"
                  label="Delete"
                  icon={DeleteOutlineRoundedIcon}
                  onClick={() => remove(index)}
                  disabled={index === 0}
                  sx={{
                    visibility: index === 0 ? 'hidden' : 'visible',
                  }}
                />
              </Flex>
            ))}
            <Button
              variant="secondary"
              disabled={(() => {
                const lastSchedule = bloodGlucoseTargetSchedule.value[bloodGlucoseTargetSchedule.value.length - 1];
                return lastSchedule.start >= (MS_IN_DAY - (MS_IN_MIN * 30));
              })()}
              onClick={() => {
                const lastSchedule = bloodGlucoseTargetSchedule.value[bloodGlucoseTargetSchedule.value.length - 1];
                return push({
                  ...lastSchedule,
                  start: lastSchedule.start + (MS_IN_MIN * 30),
                });
              }}
            >
              {t('Add an additional correction range')}
            </Button>
          </Box>
        </Box>

        <PopoverLabel
            id='suspend-threshold'
            label={t('Suspend Threshold')}
            mb={2}
            popoverContent={(
              <Box p={3}>
                <Body2>
                  {t('When your glucose is predicted to go below this value, the app will recommend a basal rate of 0 U/h and will not recommend a bolus.')}
                </Body2>
              </Box>
            )}
          />
          <FastField
            as={TextInput}
            type="number"
            id="initialSettings.suspendThreshold.value"
            name="initialSettings.suspendThreshold.value"
            suffix={meta.initialSettings.bloodGlucoseUnits.value}
            error={getFieldError(meta.initialSettings.suspendThreshold)}
            {...cgmMeta.ranges.suspendThreshold}
            {...inputStyles}
          />
      </Box>
    </Box>
  );
};

GlucoseSettings.propTypes = fieldsetPropTypes;

export const InsulinSettings = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const pumpId = meta.initialSettings.pumpId.value;
  const pumpMeta = deviceMeta(pumpId, bgUnits);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Title mb={3}>{t('Insulin Settings')}</Title>
      <Box px={3}>
        <PopoverLabel
          id='insulin-model'
          label={t('Insulin Model')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Tidepool Loop assumes that the insulin it has delivered is actively working to lower your glucose for 6 hours. This setting cannot be changed.')}
              </Body2>
              <Body2>
                {t('You can choose how Tidepool Loop measures the insulin’s peak activity according to one of these two insulin models that you’ll select now.')}
              </Body2>
              <Body2>
                <OrderedList>

                    {t('Rapid-Acting - Adult Model')}

                    {t('This model assumes peak insulin activity at {{minutes}} minutes.', { minutes: 75 })}

                    {t('Rapid-Acting - Child Model')}

                    {t('This model assumes peak insulin activity at {{minutes}} minutes.', { minutes: 65 })}

                </OrderedList>
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={RadioGroup}
          variant="horizontal"
          id="initialSettings.insulinType"
          name="initialSettings.insulinType"
          options={insulinTypeOptions}
          error={getFieldError(meta.initialSettings.insulinType)}
          mb={3}
        />

        <PopoverLabel
          id='max-basal'
          label={t('Max Basal')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Maximum basal rate is the automatically adjusted basal rate that Tidepool Loop is allowed to enact to help reach your correction range.')}
              </Body2>
              <Body2>
                {t('For first time users of an automated system, Tidepool suggests you start with 3x your highest basal rate.')}
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={TextInput}
          type="number"
          id="initialSettings.basalRateMaximum.value"
          name="initialSettings.basalRateMaximum.value"
          suffix={getCondensedUnits(defaultUnits.basalRate)}
          error={getFieldError(meta.initialSettings.basalRateMaximum.value)}
          {...pumpMeta.ranges.basalRateMaximum}
          {...inputStyles}
        />

        <PopoverLabel
          id='max-bolus'
          label={t('Max Bolus')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Maximum bolus is the highest bolus amount that you will allow Tidepool Loop to recommend at one time to cover carbs or bring down high glucose.')}
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={TextInput}
          type="number"
          id="initialSettings.bolusAmountMaximum.value"
          name="initialSettings.bolusAmountMaximum.value"
          suffix={getCondensedUnits(defaultUnits.bolusAmount)}
          error={getFieldError(meta.initialSettings.bolusAmountMaximum.value)}
          {...pumpMeta.ranges.bolusAmountMaximum}
          {...inputStyles}
        />
      </Box>
    </Box>
  );
};

InsulinSettings.propTypes = fieldsetPropTypes;

export const TherapySettings = translate()(props => (
  <Box>
    <PatientInfo mb={4} {...props} />
    <PatientTraining mt={0} mb={4} {...props} />
    {props.meta.training.value === 'inModule' && <InModuleTrainingNotification mt={0} mb={4} {...props} />}
    <GlucoseSettings mt={0} mb={4} {...props} />
    <InsulinSettings mt={0} {...props} />
  </Box>
));

const therapySettingsFormSteps = (meta) => ({
  label: t('Enter Therapy Settings'),
  disableComplete: !fieldsAreValid(['training'], meta),
  panelContent: <TherapySettings meta={meta} />
});

export default therapySettingsFormSteps;
<commit_msg>[WEB-821] Use FastField for schedules instead of Field<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { FastField } from 'formik';
import { Box, Flex, Text, BoxProps } from 'rebass/styled-components';
import bows from 'bows';
import map from 'lodash/map';
import isInteger from 'lodash/isInteger';
import sortedLastIndexBy from 'lodash/sortedLastIndexBy';
import DeleteOutlineRoundedIcon from '@material-ui/icons/DeleteOutlineRounded';

import { fieldsAreValid, getFieldError, getCondensedUnits } from '../../core/forms';
import i18next from '../../core/language';
import { useFieldArray } from '../../core/hooks';
import { Body2, Headline, OrderedList, Title } from '../../components/elements/FontStyles';
import RadioGroup from '../../components/elements/RadioGroup';
import PopoverLabel from '../../components/elements/PopoverLabel';
import TextInput from '../../components/elements/TextInput';
import Icon from '../../components/elements/Icon';
import Button from '../../components/elements/Button';

import {
  deviceMeta,
  defaultUnits,
  insulinTypeOptions,
  trainingOptions,
} from './prescriptionFormConstants';

import {
  inputStyles,
  inlineInputStyles,
  fieldsetStyles,
  wideFieldsetStyles,
  borderedFieldsetStyles,
} from './prescriptionFormStyles';
import { MS_IN_MIN, MS_IN_HOUR, MS_IN_DAY } from '../../core/constants';

const t = i18next.t.bind(i18next);
const log = bows('PrescriptionTherapySettings');

const fieldsetPropTypes = {
  ...BoxProps,
  meta: PropTypes.object.isRequired,
  t: PropTypes.func.isRequired,
};

const convertMsPer24ToTimeString = msPer24 => {
  const hours = `0${new Date(msPer24).getUTCHours()}`.slice(-2);
  const minutes = `0${new Date(msPer24).getUTCMinutes()}`.slice(-2);
  return `${hours}:${minutes}`;
};

const convertTimeStringToMsPer24 = timeString => {
  const [hours, minutes] = map(timeString.split(':'), val => parseInt(val, 10));
  return (hours * MS_IN_HOUR) + (minutes * MS_IN_MIN);
}

export const PatientInfo = props => {
  const { t, meta, ...themeProps } = props;

  const {
    firstName,
    lastName,
    birthday,
    email,
  } = meta;

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Headline mb={2}>{t('Tidepool Loop Order Form and Treatment Plan')}</Headline>
      <Text>{firstName.value} {lastName.value}</Text>
      <Text>{t('Date of Birth:')} {birthday.value}</Text>
      <Text>{t('Email:')} {email.value}</Text>
    </Box>
  );
};

PatientInfo.propTypes = fieldsetPropTypes;

export const PatientTraining = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const pumpId = meta.initialSettings.pumpId.value;
  const pumpMeta = deviceMeta(pumpId, bgUnits);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Body2>
        {t('Request for certified pump trainer (CPT) in-person training. Required (TBD) for patients new to {{pumpId}}.', {
          pumpId: pumpMeta.manufacturerName,
        })}
      </Body2>
      <FastField
        as={RadioGroup}
        variant="vertical"
        id="training"
        name="training"
        options={trainingOptions}
        error={getFieldError(meta.training)}
      />
    </Box>
  );
};

PatientTraining.propTypes = fieldsetPropTypes;

export const InModuleTrainingNotification = props => {
  const { t, meta, ...themeProps } = props;

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Body2>
        {t('You have selected Tidepool Loop in-app tutorial self start. A request will not be sent for this patient to receive CPT training.')}
      </Body2>
    </Box>
  );
};

InModuleTrainingNotification.propTypes = fieldsetPropTypes;

export const GlucoseSettings = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const cgmType = meta.initialSettings.cgmType.value;
  const cgmMeta = deviceMeta(cgmType, bgUnits);

  const [refs, setRefs] = React.useState([]);
  const [focusedId, setFocusedId] = React.useState();

  const [bloodGlucoseTargetSchedule, , { move, remove, replace, push }] = useFieldArray({ name: 'initialSettings.bloodGlucoseTargetSchedule' });
  const schedulesLength = bloodGlucoseTargetSchedule.value.length;

  React.useEffect(() => {
    // add or remove refs as the schedule length changes
    setRefs(refs => (
      Array(schedulesLength).fill().map((_, i) => refs[i] || React.createRef())
    ));
  }, [schedulesLength]);

  React.useEffect(() => {
    isInteger(focusedId) && refs[focusedId].current.focus();
  }, [focusedId]);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Title mb={3}>{t('Glucose Settings')}</Title>
      <Box px={3}>
        <PopoverLabel
          id='correction-range'
          label={t('Correction Range')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('The correction range is the glucose range that you would like the app to correct your glucose to by adjusting insulin dosing.')}
              </Body2>
            </Box>
          )}
        />

        <Box p={3} mb={3} bg="lightestGrey">
          <Box>
            {map(bloodGlucoseTargetSchedule.value, (schedule, index) => (
              <Flex key={index} alignItems="flex-start" mb={3}>
                <FastField
                  as={TextInput}
                  label={index === 0 && t('Start Time')}
                  type="time"
                  readOnly={index === 0}
                  step={MS_IN_MIN * 30 / 1000}
                  value={convertMsPer24ToTimeString(schedule.start, 'hh:mm')}
                  onChange={e => {
                    const start = convertTimeStringToMsPer24(e.target.value);
                    const newValue = {...bloodGlucoseTargetSchedule.value[index], start};
                    const valuesCopy = [...bloodGlucoseTargetSchedule.value];
                    valuesCopy.splice(index, 1);
                    const newPos = sortedLastIndexBy(valuesCopy, newValue, function(o) { return o.start; });
                    replace(index, newValue);
                    move(index, newPos);
                    setFocusedId(newPos);
                  }}
                  onFocus={() => setFocusedId(index)}
                  onBlur={() => setFocusedId(undefined)}
                  innerRef={refs[index]}
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.start`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.start`}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'start')}
                  {...inlineInputStyles}
                />
                <FastField
                  as={TextInput}
                  label={index === 0 && t('Lower Target')}
                  type="number"
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.low`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.low`}
                  suffix={meta.initialSettings.bloodGlucoseUnits.value}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'low')}
                  {...inlineInputStyles}
                />
                <Text ml={3} mr={1} mt={index === 0 ? '33px' : '12px'}>-</Text>
                <FastField
                  as={TextInput}
                  label={index === 0 && t('Upper Target')}
                  type="number"
                  id={`initialSettings.bloodGlucoseTargetSchedule.${index}.high`}
                  name={`initialSettings.bloodGlucoseTargetSchedule.${index}.high`}
                  suffix={meta.initialSettings.bloodGlucoseUnits.value}
                  error={getFieldError(meta.initialSettings.bloodGlucoseTargetSchedule, index, 'high')}
                  {...inlineInputStyles}
                />
                <Icon
                  mx={2}
                  mt={2}
                  variant="button"
                  label="Delete"
                  icon={DeleteOutlineRoundedIcon}
                  onClick={() => remove(index)}
                  disabled={index === 0}
                  sx={{
                    visibility: index === 0 ? 'hidden' : 'visible',
                  }}
                />
              </Flex>
            ))}
            <Button
              variant="secondary"
              disabled={(() => {
                const lastSchedule = bloodGlucoseTargetSchedule.value[bloodGlucoseTargetSchedule.value.length - 1];
                return lastSchedule.start >= (MS_IN_DAY - (MS_IN_MIN * 30));
              })()}
              onClick={() => {
                const lastSchedule = bloodGlucoseTargetSchedule.value[bloodGlucoseTargetSchedule.value.length - 1];
                return push({
                  ...lastSchedule,
                  start: lastSchedule.start + (MS_IN_MIN * 30),
                });
              }}
            >
              {t('Add an additional correction range')}
            </Button>
          </Box>
        </Box>

        <PopoverLabel
            id='suspend-threshold'
            label={t('Suspend Threshold')}
            mb={2}
            popoverContent={(
              <Box p={3}>
                <Body2>
                  {t('When your glucose is predicted to go below this value, the app will recommend a basal rate of 0 U/h and will not recommend a bolus.')}
                </Body2>
              </Box>
            )}
          />
          <FastField
            as={TextInput}
            type="number"
            id="initialSettings.suspendThreshold.value"
            name="initialSettings.suspendThreshold.value"
            suffix={meta.initialSettings.bloodGlucoseUnits.value}
            error={getFieldError(meta.initialSettings.suspendThreshold)}
            {...cgmMeta.ranges.suspendThreshold}
            {...inputStyles}
          />
      </Box>
    </Box>
  );
};

GlucoseSettings.propTypes = fieldsetPropTypes;

export const InsulinSettings = props => {
  const { t, meta, ...themeProps } = props;
  const bgUnits = meta.initialSettings.bloodGlucoseUnits.value;
  const pumpId = meta.initialSettings.pumpId.value;
  const pumpMeta = deviceMeta(pumpId, bgUnits);

  return (
    <Box {...fieldsetStyles} {...wideFieldsetStyles} {...borderedFieldsetStyles} {...themeProps}>
      <Title mb={3}>{t('Insulin Settings')}</Title>
      <Box px={3}>
        <PopoverLabel
          id='insulin-model'
          label={t('Insulin Model')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Tidepool Loop assumes that the insulin it has delivered is actively working to lower your glucose for 6 hours. This setting cannot be changed.')}
              </Body2>
              <Body2>
                {t('You can choose how Tidepool Loop measures the insulin’s peak activity according to one of these two insulin models that you’ll select now.')}
              </Body2>
              <Body2>
                <OrderedList>

                    {t('Rapid-Acting - Adult Model')}

                    {t('This model assumes peak insulin activity at {{minutes}} minutes.', { minutes: 75 })}

                    {t('Rapid-Acting - Child Model')}

                    {t('This model assumes peak insulin activity at {{minutes}} minutes.', { minutes: 65 })}

                </OrderedList>
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={RadioGroup}
          variant="horizontal"
          id="initialSettings.insulinType"
          name="initialSettings.insulinType"
          options={insulinTypeOptions}
          error={getFieldError(meta.initialSettings.insulinType)}
          mb={3}
        />

        <PopoverLabel
          id='max-basal'
          label={t('Max Basal')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Maximum basal rate is the automatically adjusted basal rate that Tidepool Loop is allowed to enact to help reach your correction range.')}
              </Body2>
              <Body2>
                {t('For first time users of an automated system, Tidepool suggests you start with 3x your highest basal rate.')}
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={TextInput}
          type="number"
          id="initialSettings.basalRateMaximum.value"
          name="initialSettings.basalRateMaximum.value"
          suffix={getCondensedUnits(defaultUnits.basalRate)}
          error={getFieldError(meta.initialSettings.basalRateMaximum.value)}
          {...pumpMeta.ranges.basalRateMaximum}
          {...inputStyles}
        />

        <PopoverLabel
          id='max-bolus'
          label={t('Max Bolus')}
          mb={2}
          popoverContent={(
            <Box p={3}>
              <Body2>
                {t('Maximum bolus is the highest bolus amount that you will allow Tidepool Loop to recommend at one time to cover carbs or bring down high glucose.')}
              </Body2>
            </Box>
          )}
        />
        <FastField
          as={TextInput}
          type="number"
          id="initialSettings.bolusAmountMaximum.value"
          name="initialSettings.bolusAmountMaximum.value"
          suffix={getCondensedUnits(defaultUnits.bolusAmount)}
          error={getFieldError(meta.initialSettings.bolusAmountMaximum.value)}
          {...pumpMeta.ranges.bolusAmountMaximum}
          {...inputStyles}
        />
      </Box>
    </Box>
  );
};

InsulinSettings.propTypes = fieldsetPropTypes;

export const TherapySettings = translate()(props => (
  <Box>
    <PatientInfo mb={4} {...props} />
    <PatientTraining mt={0} mb={4} {...props} />
    {props.meta.training.value === 'inModule' && <InModuleTrainingNotification mt={0} mb={4} {...props} />}
    <GlucoseSettings mt={0} mb={4} {...props} />
    <InsulinSettings mt={0} {...props} />
  </Box>
));

const therapySettingsFormSteps = (meta) => ({
  label: t('Enter Therapy Settings'),
  disableComplete: !fieldsAreValid(['training'], meta),
  panelContent: <TherapySettings meta={meta} />
});

export default therapySettingsFormSteps;
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimManifestService', ManifestService);

  ManifestService.$inject = ['$q', 'dimBungieService', '$http', 'toaster', 'dimSettingsService', '$translate'];

  function ManifestService($q, dimBungieService, $http, toaster, dimSettingsService, $translate) {
    // Testing flags
    const alwaysLoadRemote = false;

    let manifestPromise = null;

    const makeStatement = _.memoize(function(table, db) {
      return db.prepare(`select json from ${table} where id = ?`);
    });

    const service = {
      isLoaded: true,
      isError: false,
      statusText: null,
      version: null,

      // This tells users to reload the extension. It fires no more
      // often than every 10 seconds, and only warns if the manifest
      // version has actually changed.
      warnMissingDefinition: _.debounce(function() {
        dimBungieService.getManifest()
          .then(function(data) {
            const language = dimSettingsService.language;
            const path = data.mobileWorldContentPaths[language] || data.mobileWorldContentPaths.en;

            // The manifest has updated!
            if (path !== service.version) {
              toaster.pop('error',
                          $translate.instant('Manfiest.Outdated'),
                          $translate.instant('Manfiest.OutdatedExplanation'));
            }
          });
      }, 10000, true),

      getManifest: function() {
        if (manifestPromise) {
          return manifestPromise;
        }

        service.isLoaded = false;

        // Clear out the old manifest file now that we use
        // indexedDB. We can remove this after a few releases, but we
        // want to save disk for our users.
        deleteOldManifestFile();

        manifestPromise = dimBungieService.getManifest()
          .then(function(data) {
            const language = dimSettingsService.language;
            const path = data.mobileWorldContentPaths[language] || data.mobileWorldContentPaths.en;

            // Use the path as the version, rather than the "version" field, because
            // Bungie can update the manifest file without changing that version.
            const version = path;
            service.version = version;

            return loadManifestFromCache(version)
              .catch(function(e) {
                return loadManifestRemote(version, language, path);
              })
              .then(function(typedArray) {
                service.statusText = $translate.instant('Manifest.Build') + '...';
                const db = new SQL.Database(typedArray);
                // do a small request, just to test it out
                service.getAllRecords(db, 'DestinyRaceDefinition');
                return db;
              });
          })
          .catch((e) => {
            let message = e.message || e;
            if (e.status && e.status !== 200) {
              message = $translate.instant('Manifest.BungieDown', { error: e.statusText });
            }
            service.statusText = $translate.instant('Manifest.Error', { error: message });
            manifestPromise = null;
            service.isError = true;
            deleteManifestFile();
            throw e;
          });

        return manifestPromise;
      },

      getRecord: function(db, table, id) {
        const statement = makeStatement(table, db);
        // The ID in sqlite is a signed 32-bit int, while the id we
        // use is unsigned, so we must convert
        const sqlId = new Int32Array([id])[0];
        const result = statement.get([sqlId]);
        statement.reset();
        if (result.length) {
          return JSON.parse(result[0]);
        }
        return null;
      },

      getAllRecords: function(db, table) {
        const rows = db.exec(`SELECT json FROM ${table}`);
        const result = {};
        rows[0].values.forEach((row) => {
          const obj = JSON.parse(row);
          result[obj.hash] = obj;
        });
        return result;
      }
    };

    return service;

    /**
     * Returns a promise for the manifest data as a Uint8Array. Will cache it on succcess.
     */
    function loadManifestRemote(version, language, path) {
      service.statusText = $translate.instant('Manifest.Download') + '...';
      return $http.get("https://www.bungie.net" + path, { responseType: "blob" })
        .then(function(response) {
          service.statusText = $translate.instant('Manifest.Unzip') + '...';
          return unzipManifest(response.data);
        })
        .then(function(arraybuffer) {
          service.statusText = $translate.instant('Manifest.Save') + '...';

          var typedArray = new Uint8Array(arraybuffer);
          idbKeyval.set('dimManifest', typedArray)
            .then(() => {
              console.log("Sucessfully stored " + typedArray.length + " byte manifest file.");
              localStorage.setItem('manifest-version', version);
            })
            .catch((e) => console.error('Error saving manifest file', e));

          return typedArray;
        });
    }

    function getLocalManifestFile() {
      return $q((resolve, reject) => {
        const requestFileSystem = (window.requestFileSystem || window.webkitRequestFileSystem);
        // Ask for 60MB of temporary space. If Chrome gets rid of it we can always redownload.
        requestFileSystem(window.TEMPORARY, 60 * 1024 * 1024, (fs) => {
          fs.root.getFile('dimManifest', { create: true, exclusive: false }, (f) => resolve(f), (e) => reject(e));
        }, (e) => reject(e));
      });
    }

    function deleteOldManifestFile() {
      return getLocalManifestFile().then((fileEntry) => {
        return $q((resolve, reject) => {
          fileEntry.remove(resolve, reject);
        });
      });
    }

    function deleteManifestFile() {
      localStorage.removeItem('manifest-version');
      idbKeyval.delete('dimManifest');
    }

    /**
     * Returns a promise for the cached manifest of the specified
     * version as a Uint8Array, or rejects.
     */
    function loadManifestFromCache(version) {
      if (alwaysLoadRemote) {
        return $q.reject(new Error("Testing - always load remote"));
      }

      service.statusText = $translate.instant('Manifest.Load') + '...';
      var currentManifestVersion = localStorage.getItem('manifest-version');
      if (currentManifestVersion === version) {
        // One version of this used chrome.storage.local with a
        // base64-encoded string, which is a bit slower. We may need
        // to do that again if the requestFileSystem API gets
        // removed.
        return idbKeyval.get('dimManifest').then((typedArray) => {
          if (!typedArray) {
            throw new Error("Empty cached manifest file");
          }
          return typedArray;
        });
      } else {
        _gaq.push(['_trackEvent', 'Manifest', 'Need New Manifest']);
        return $q.reject(new Error("version mismatch: " + version + ' ' + currentManifestVersion));
      }
    }

    /**
     * Unzip a file from a ZIP Blob into an ArrayBuffer. Returns a promise.
     */
    function unzipManifest(blob) {
      return $q(function(resolve, reject) {
        zip.useWebWorkers = true;
        zip.workerScriptsPath = "vendor/zip.js/WebContent/";
        zip.createReader(new zip.BlobReader(blob), function(zipReader) {
          // get all entries from the zip
          zipReader.getEntries(function(entries) {
            if (entries.length) {
              entries[0].getData(new zip.BlobWriter(), function(blob) {
                var blobReader = new FileReader();
                blobReader.addEventListener("error", (e) => { reject(e); });
                blobReader.addEventListener("load", function() {
                  zipReader.close(function() {
                    resolve(blobReader.result);
                  });
                });
                blobReader.readAsArrayBuffer(blob);
              });
            }
          });
        }, function(error) {
          reject(error);
        });
      });
    }
  }
})();
<commit_msg>Removed comments<commit_after>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimManifestService', ManifestService);

  ManifestService.$inject = ['$q', 'dimBungieService', '$http', 'toaster', 'dimSettingsService', '$translate'];

  function ManifestService($q, dimBungieService, $http, toaster, dimSettingsService, $translate) {
    // Testing flags
    const alwaysLoadRemote = false;

    let manifestPromise = null;

    const makeStatement = _.memoize(function(table, db) {
      return db.prepare(`select json from ${table} where id = ?`);
    });

    const service = {
      isLoaded: true,
      isError: false,
      statusText: null,
      version: null,

      // This tells users to reload the extension. It fires no more
      // often than every 10 seconds, and only warns if the manifest
      // version has actually changed.
      warnMissingDefinition: _.debounce(function() {
        dimBungieService.getManifest()
          .then(function(data) {
            const language = dimSettingsService.language;
            const path = data.mobileWorldContentPaths[language] || data.mobileWorldContentPaths.en;

            // The manifest has updated!
            if (path !== service.version) {
              toaster.pop('error',
                          $translate.instant('Manfiest.Outdated'),
                          $translate.instant('Manfiest.OutdatedExplanation'));
            }
          });
      }, 10000, true),

      getManifest: function() {
        if (manifestPromise) {
          return manifestPromise;
        }

        service.isLoaded = false;

        // Clear out the old manifest file now that we use
        // indexedDB. We can remove this after a few releases, but we
        // want to save disk for our users.
        deleteOldManifestFile();

        manifestPromise = dimBungieService.getManifest()
          .then(function(data) {
            const language = dimSettingsService.language;
            const path = data.mobileWorldContentPaths[language] || data.mobileWorldContentPaths.en;

            // Use the path as the version, rather than the "version" field, because
            // Bungie can update the manifest file without changing that version.
            const version = path;
            service.version = version;

            return loadManifestFromCache(version)
              .catch(function(e) {
                return loadManifestRemote(version, language, path);
              })
              .then(function(typedArray) {
                service.statusText = $translate.instant('Manifest.Build') + '...';
                const db = new SQL.Database(typedArray);
                // do a small request, just to test it out
                service.getAllRecords(db, 'DestinyRaceDefinition');
                return db;
              });
          })
          .catch((e) => {
            let message = e.message || e;
            if (e.status && e.status !== 200) {
              message = $translate.instant('Manifest.BungieDown', { error: e.statusText });
            }
            service.statusText = $translate.instant('Manifest.Error', { error: message });
            manifestPromise = null;
            service.isError = true;
            deleteManifestFile();
            throw e;
          });

        return manifestPromise;
      },

      getRecord: function(db, table, id) {
        const statement = makeStatement(table, db);
        // The ID in sqlite is a signed 32-bit int, while the id we
        // use is unsigned, so we must convert
        const sqlId = new Int32Array([id])[0];
        const result = statement.get([sqlId]);
        statement.reset();
        if (result.length) {
          return JSON.parse(result[0]);
        }
        return null;
      },

      getAllRecords: function(db, table) {
        const rows = db.exec(`SELECT json FROM ${table}`);
        const result = {};
        rows[0].values.forEach((row) => {
          const obj = JSON.parse(row);
          result[obj.hash] = obj;
        });
        return result;
      }
    };

    return service;

    /**
     * Returns a promise for the manifest data as a Uint8Array. Will cache it on succcess.
     */
    function loadManifestRemote(version, language, path) {
      service.statusText = $translate.instant('Manifest.Download') + '...';
      return $http.get("https://www.bungie.net" + path, { responseType: "blob" })
        .then(function(response) {
          service.statusText = $translate.instant('Manifest.Unzip') + '...';
          return unzipManifest(response.data);
        })
        .then(function(arraybuffer) {
          service.statusText = $translate.instant('Manifest.Save') + '...';

          var typedArray = new Uint8Array(arraybuffer);
          idbKeyval.set('dimManifest', typedArray)
            .then(() => {
              console.log("Sucessfully stored " + typedArray.length + " byte manifest file.");
              localStorage.setItem('manifest-version', version);
            })
            .catch((e) => console.error('Error saving manifest file', e));

          return typedArray;
        });
    }

    function getLocalManifestFile() {
      return $q((resolve, reject) => {
        const requestFileSystem = (window.requestFileSystem || window.webkitRequestFileSystem);
        // Ask for 60MB of temporary space. If Chrome gets rid of it we can always redownload.
        requestFileSystem(window.TEMPORARY, 60 * 1024 * 1024, (fs) => {
          fs.root.getFile('dimManifest', { create: true, exclusive: false }, (f) => resolve(f), (e) => reject(e));
        }, (e) => reject(e));
      });
    }

    function deleteOldManifestFile() {
      return getLocalManifestFile().then((fileEntry) => {
        return $q((resolve, reject) => {
          fileEntry.remove(resolve, reject);
        });
      });
    }

    function deleteManifestFile() {
      localStorage.removeItem('manifest-version');
      idbKeyval.delete('dimManifest');
    }

    /**
     * Returns a promise for the cached manifest of the specified
     * version as a Uint8Array, or rejects.
     */
    function loadManifestFromCache(version) {
      if (alwaysLoadRemote) {
        return $q.reject(new Error("Testing - always load remote"));
      }

      service.statusText = $translate.instant('Manifest.Load') + '...';
      var currentManifestVersion = localStorage.getItem('manifest-version');
      if (currentManifestVersion === version) {
        return idbKeyval.get('dimManifest').then((typedArray) => {
          if (!typedArray) {
            throw new Error("Empty cached manifest file");
          }
          return typedArray;
        });
      } else {
        _gaq.push(['_trackEvent', 'Manifest', 'Need New Manifest']);
        return $q.reject(new Error("version mismatch: " + version + ' ' + currentManifestVersion));
      }
    }

    /**
     * Unzip a file from a ZIP Blob into an ArrayBuffer. Returns a promise.
     */
    function unzipManifest(blob) {
      return $q(function(resolve, reject) {
        zip.useWebWorkers = true;
        zip.workerScriptsPath = "vendor/zip.js/WebContent/";
        zip.createReader(new zip.BlobReader(blob), function(zipReader) {
          // get all entries from the zip
          zipReader.getEntries(function(entries) {
            if (entries.length) {
              entries[0].getData(new zip.BlobWriter(), function(blob) {
                var blobReader = new FileReader();
                blobReader.addEventListener("error", (e) => { reject(e); });
                blobReader.addEventListener("load", function() {
                  zipReader.close(function() {
                    resolve(blobReader.result);
                  });
                });
                blobReader.readAsArrayBuffer(blob);
              });
            }
          });
        }, function(error) {
          reject(error);
        });
      });
    }
  }
})();
<|endoftext|>


<commit_before>/**
 * debounce
 * @param {integer} milliseconds This param indicates the number of milliseconds
 *     to wait after the last call before calling the original function .
 * @return {function} This returns a function that when called will wait the
 *     indicated number of milliseconds after the last call before
 *     calling the original function.
 */
Function.prototype.debounce = function (milliseconds) {
    var baseFunction = this,
        timer = null,
        wait = milliseconds;

    return function () {
        var self = this,
            args = arguments;

        function complete() {
            baseFunction.apply(self, args);
            timer = null;
        }

        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(complete, wait);
    };
};

/**
* throttle
* @param {integer} milliseconds This param indicates the number of milliseconds
*     to wait between calls before calling the original function.
* @return {function} This returns a function that when called will wait the
*     indicated number of milliseconds between calls before
*     calling the original function.
*/
Function.prototype.throttle = function (milliseconds) {
    var baseFunction = this,
        lastEventTimestamp = null,
        limit = milliseconds;

    return function () {
        var self = this,
            args = arguments,
            now = Date.now();

        if (!lastEventTimestamp || now - lastEventTimestamp >= limit) {
            lastEventTimestamp = now;
            baseFunction.apply(self, args);
        }
    };
};<commit_msg>Update to latest limit.js<commit_after>/**
 * debounce
 * @param {integer} milliseconds This param indicates the number of milliseconds
 *     to wait after the last call before calling the original function.
 * @param {object} What "this" refers to in the returned function.
 * @return {function} This returns a function that when called will wait the
 *     indicated number of milliseconds after the last call before
 *     calling the original function.
 */
Function.prototype.debounce = function (milliseconds, context) {
    var baseFunction = this,
        timer = null,
        wait = milliseconds;

    return function () {
        var self = context || this,
            args = arguments;

        function complete() {
            baseFunction.apply(self, args);
            timer = null;
        }

        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(complete, wait);
    };
};

/**
* throttle
* @param {integer} milliseconds This param indicates the number of milliseconds
*     to wait between calls before calling the original function.
* @param {object} What "this" refers to in the returned function.
* @return {function} This returns a function that when called will wait the
*     indicated number of milliseconds between calls before
*     calling the original function.
*/
Function.prototype.throttle = function (milliseconds, context) {
    var baseFunction = this,
        lastEventTimestamp = null,
        limit = milliseconds;

    return function () {
        var self = context || this,
            args = arguments,
            now = Date.now();

        if (!lastEventTimestamp || now - lastEventTimestamp >= limit) {
            lastEventTimestamp = now;
            baseFunction.apply(self, args);
        }
    };
};<|endoftext|>


<commit_before>import { useState, useEffect } from 'react'
import { uniqueId, property } from 'lodash'

export function useFetchReleaseNotes () {
  const [ notes, setNotes ] = useState([])
  const [ loadingNotes, setLoadingNotes ] = useState(false)
  const [ count, setCount ] = useState(1)

  useEffect(() => {
    setLoadingNotes(true)
    const controller = new AbortController()
    const signal = controller.signal
    fetch('/api/release_notes', {credentials: 'same-origin', signal})
      .then((response) => {
        if (response.ok) {
          return response.json()
            .then(property('release_notes'))
            .then(notes => {
            setLoadingNotes(false)
            setNotes(notes)
          })
        }
      }).catch(err => {
        setNotes([])
        setLoadingNotes(false)
      })
  }, [count])

  return [
    loadingNotes,
    notes,
    () => setCount(count + 1)
  ]
}

export function useCreateEditReleaseNote(isEdit) {
  const [ note, setNote ] = useState()
  const [ requestInProgress, setRequestInProgress] = useState(false)
  const [ response, setResponse] = useState()
  useEffect(() => {
    if (note) {
      console.log('note', note)
      const controller = new AbortController()
      const signal = controller.signal
      setRequestInProgress(true)
      const body = JSON.stringify(note)
      const method = isEdit ? 'PATCH' : 'POST'
      const baseURL = '/api/release_notes'
      const url = isEdit ? `${baseURL}/${note.id}` : baseURL
      const req = fetch(url, {
        method,
        body,
        credentials: 'same-origin',
        signal
      })
      req.then(response => {
        if(response.ok) {
          return response.json()
        } else {
          throw response.status
        }
      }).then((result) => {
        setNote()
        setResponse(result)
        setRequestInProgress(false)
      }).catch((err) => {
        setRequestInProgress(false)
        if (err !== 'cancelled') {
          console.error('err', err)
        }
      })
      return () => {
        controller.abort()
      }
    }
  }, [note, isEdit])

  return [setNote, requestInProgress, response]
}
<commit_msg>self review<commit_after>import { useState, useEffect } from 'react'
import { uniqueId, property } from 'lodash'

export function useFetchReleaseNotes () {
  const [ notes, setNotes ] = useState([])
  const [ loadingNotes, setLoadingNotes ] = useState(false)
  const [ count, setCount ] = useState(1)

  useEffect(() => {
    setLoadingNotes(true)
    const controller = new AbortController()
    const signal = controller.signal
    fetch('/api/release_notes', {credentials: 'same-origin', signal})
      .then((response) => {
        if (response.ok) {
          return response.json()
            .then(property('release_notes'))
            .then(notes => {
              setLoadingNotes(false)
              setNotes(notes)
            })
        }
      }).catch(err => {
        setNotes([])
        setLoadingNotes(false)
      })
  }, [count])

  return [
    loadingNotes,
    notes,
    () => setCount(count + 1)
  ]
}

export function useCreateEditReleaseNote(isEdit) {
  const [ note, setNote ] = useState()
  const [ requestInProgress, setRequestInProgress] = useState(false)
  const [ response, setResponse] = useState()
  useEffect(() => {
    if (note) {
      const controller = new AbortController()
      const signal = controller.signal
      setRequestInProgress(true)
      const body = JSON.stringify(note)
      const method = isEdit ? 'PATCH' : 'POST'
      const baseURL = '/api/release_notes'
      const url = isEdit ? `${baseURL}/${note.id}` : baseURL
      const req = fetch(url, {
        method,
        body,
        credentials: 'same-origin',
        signal
      })
      req.then(response => {
        if(response.ok) {
          return response.json()
        } else {
          throw response.status
        }
      }).then((result) => {
        setNote()
        setResponse(result)
        setRequestInProgress(false)
      }).catch((err) => {
        setRequestInProgress(false)
        if (err !== 'cancelled') {
          console.error('err', err)
        }
      })
      return () => {
        controller.abort()
      }
    }
  }, [note, isEdit])

  return [setNote, requestInProgress, response]
}
<|endoftext|>