<commit_before>(function () {

  var margin = {
    top: 25,
    right: 100,
    bottom: 25,
    left: 100
  },
    width = 800,
    height = 400;

  var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

  var y = d3.scale.linear()
    .rangeRound([height, 0]);

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(function (d) {
      var format = d3.time.format("%x-%X");
      return format(new Date(d));
    });

  var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

  var line = d3.svg.line()
    .interpolate("linear")
    .x(function (d) {
      return x((new Date(d.date)).getTime()) + x.rangeBand() / 2;
    })
    .y(function (d) {
      return y(d.duration);
    });

  var color = d3.scale.ordinal()
    .range(["#272822", "#F92672", "#66D9EF", "#A6E22E", "#FD971F"]);

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var tooltip = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

  //read live data from cloudant
  var db = new PouchDB('http://pouchdb.cloudant.com/performance_results');
  db.allDocs({"include_docs": true}).then(function(docs) {

    var data = new Array();
    for (var i = 0; i < docs.rows.length; i++) {
      if (docs.rows[i].doc.branch === 'master' &&
          docs.rows[i].doc.pull_request === 'false') {
        data.push(docs.rows[i]);
      }
    }
    var varNames = ["basic-inserts", "all-docs-skip-limit",
      "all-docs-startkey-endkey", "basic-gets",
      "bulk-inserts"
    ];
    color.domain(varNames);

    var resultsData = varNames.map(function (name) {
      return {
        name: name,
        details: data.map(function (d) {
          return {
            name: name,
            date: d.doc.date,
            duration: d.doc[name].duration,
            branch: d.doc.branch,
            client: d.doc.client,
            commit: d.doc.commit.substr(0,6)
          };
        })
      };
    });

    x.domain(data.map(function (d) {
      var da = new Date(d.doc.date)
      return da.getTime();
    }));

    y.domain([
      d3.min(resultsData, function (c) {
        return d3.min(c.details, function (d) {
          return d.duration;
        });
      }),
      d3.max(resultsData, function (c) {
        return d3.max(c.details, function (d) {
          return d.duration;
        });
      })
    ]);

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Duration");

    var results = svg.selectAll(".results")
      .data(resultsData)
      .enter().append("g")
      .attr("class", "results");

    results.append("path")
      .attr("class", "line")
      .attr("d", function (d) {
        return line(d.details);
      })
      .style("stroke", function (d) {
        return color(d.name);
      })
      .style("stroke-width", "1px")
      .style("fill", "none")

	function formatDetails(d) {
      details = [];
      duration = "Duration: " + d.duration;
      branch = "Branch: " + d.branch;
      commit = "Commit: " + d.commit;
      if (d.client.engine != undefined) {
        engine = "Engine: " + d.client.engine.name +" "+ d.client.engine.version;
        browser = "Browser: " + d.client.browser.name +" "+ d.client.browser.version;
        details = [duration, branch, commit, engine, browser];
      }
      else {
        client = "Client: " + d.client;
        details = [duration, branch, commit, client];
      }
      details = details.join("
");
      return details;
    }

    results.selectAll(".point")
      .data(function (d) {
        return d.details;
      })
      .enter().append("circle")
      .attr("class", "point")
      .attr("cx", function (d) {
        var da = new Date(d.date)
        return x(da.getTime()) + x.rangeBand() / 2;
      })
      .attr("cy", function (d) {
        return y(d.duration);
      })
      .attr("r", "3px")
      .style("fill", function (d) {
        return color(d.name);
      })
      .on("mouseover", function(d) {
        var details = formatDetails(d);
        tooltip.transition()
          .duration(250)
          .style("opacity", .9);
        tooltip.html(details)
          .style("left", (d3.event.pageX - 30) + "px")
          .style("top", (d3.event.pageY + 15) + "px");
        d3.select(this)
          .attr("r", "4px");
      })
      .on("mouseout", function(d) {
        tooltip.transition()
          .duration(250)
          .style("opacity", 0);
        d3.select(this)
          .attr("r", "3px");
      });

    var legend = svg.selectAll(".legend")
      .data(varNames.slice().reverse())
      .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function (d, i) {
        return "translate(55," + i * 20 + ")";
      });

    legend.append("rect")
      .attr("x", width - 10)
      .attr("width", 10)
      .attr("height", 10)
      .style("fill", color)

    legend.append("text")
      .attr("x", width - 12)
      .attr("y", 6)
      .attr("dy", ".35em")
      .style("text-anchor", "end")
      .text(function (d) {
        return d;
      });

  });
})();
<commit_msg>(#2) - Made the x-axis readable<commit_after>(function () {

  var margin = {
    top: 25,
    right: 100,
    bottom: 100,
    left: 100
  },
    width = 800,
    height = 400;

  var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

  var y = d3.scale.linear()
    .rangeRound([height, 0]);

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(function (d) {
      var format = d3.time.format("%x - %H:%m");
      return format(new Date(d));
    });

  var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

  var line = d3.svg.line()
    .interpolate("linear")
    .x(function (d) {
      return x((new Date(d.date)).getTime()) + x.rangeBand() / 2;
    })
    .y(function (d) {
      return y(d.duration);
    });

  var color = d3.scale.ordinal()
    .range(["#272822", "#F92672", "#66D9EF", "#A6E22E", "#FD971F"]);

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var tooltip = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

  //read live data from cloudant
  var db = new PouchDB('http://pouchdb.cloudant.com/performance_results');
  db.allDocs({"include_docs": true}).then(function(docs) {

    var data = new Array();
    for (var i = 0; i < docs.rows.length; i++) {
      if (docs.rows[i].doc.branch === 'master' &&
          docs.rows[i].doc.pull_request === 'false') {
        data.push(docs.rows[i]);
      }
    }
    var varNames = ["basic-inserts", "all-docs-skip-limit",
      "all-docs-startkey-endkey", "basic-gets",
      "bulk-inserts"
    ];
    color.domain(varNames);

    var resultsData = varNames.map(function (name) {
      return {
        name: name,
        details: data.map(function (d) {
          return {
            name: name,
            date: d.doc.date,
            duration: d.doc[name].duration,
            branch: d.doc.branch,
            client: d.doc.client,
            commit: d.doc.commit.substr(0,6)
          };
        })
      };
    });

    x.domain(data.map(function (d) {
      var da = new Date(d.doc.date)
      return da.getTime();
    }));

    y.domain([
      d3.min(resultsData, function (c) {
        return d3.min(c.details, function (d) {
          return d.duration;
        });
      }),
      d3.max(resultsData, function (c) {
        return d3.max(c.details, function (d) {
          return d.duration;
        });
      })
    ]);

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
      .selectAll("text")
        .style("text-anchor", "end")
        .attr("dx", "-1em")
        .attr("dy", ".15em")
        .attr("transform", function(d) {
            return "rotate(-30)"
            });

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Duration (ms)");

    var results = svg.selectAll(".results")
      .data(resultsData)
      .enter().append("g")
      .attr("class", "results");

    results.append("path")
      .attr("class", "line")
      .attr("d", function (d) {
        return line(d.details);
      })
      .style("stroke", function (d) {
        return color(d.name);
      })
      .style("stroke-width", "1px")
      .style("fill", "none")

	function formatDetails(d) {
      details = [];
      duration = "Duration: " + d.duration;
      branch = "Branch: " + d.branch;
      commit = "Commit: " + d.commit;
      if (d.client.engine != undefined) {
        engine = "Engine: " + d.client.engine.name +" "+ d.client.engine.version;
        browser = "Browser: " + d.client.browser.name +" "+ d.client.browser.version;
        details = [duration, branch, commit, engine, browser];
      }
      else {
        client = "Client: " + d.client;
        details = [duration, branch, commit, client];
      }
      details = details.join("
");
      return details;
    }

    results.selectAll(".point")
      .data(function (d) {
        return d.details;
      })
      .enter().append("circle")
      .attr("class", "point")
      .attr("cx", function (d) {
        var da = new Date(d.date)
        return x(da.getTime()) + x.rangeBand() / 2;
      })
      .attr("cy", function (d) {
        return y(d.duration);
      })
      .attr("r", "3px")
      .style("fill", function (d) {
        return color(d.name);
      })
      .on("mouseover", function(d) {
        var details = formatDetails(d);
        tooltip.transition()
          .duration(250)
          .style("opacity", .9);
        tooltip.html(details)
          .style("left", (d3.event.pageX - 30) + "px")
          .style("top", (d3.event.pageY + 15) + "px");
        d3.select(this)
          .attr("r", "4px");
      })
      .on("mouseout", function(d) {
        tooltip.transition()
          .duration(250)
          .style("opacity", 0);
        d3.select(this)
          .attr("r", "3px");
      });

    var legend = svg.selectAll(".legend")
      .data(varNames.slice().reverse())
      .enter().append("g")
      .attr("class", "legend")
      .attr("transform", function (d, i) {
        return "translate(55," + i * 20 + ")";
      });

    legend.append("rect")
      .attr("x", width - 10)
      .attr("width", 10)
      .attr("height", 10)
      .style("fill", color)

    legend.append("text")
      .attr("x", width - 12)
      .attr("y", 6)
      .attr("dy", ".35em")
      .style("text-anchor", "end")
      .text(function (d) {
        return d;
      });

  });
})();
<|endoftext|>


<commit_before>{"225":{"department":"SBIO","number":"225","title":"Bio-chips, Imaging and Nanomedicine (EE 225, MATSCI 382)","description":"The course covers state-of-the-art and emerging bio-sensors, bio-chips, imaging modalities, and nano-therapies which will be studied in the context of human physiology including the nervous system, circulatory system and immune system. Medical diagnostics will be divided into bio-chips (in-vitro diagnostics) and medical and molecular imaging (in-vivo imaging). In-depth discussion on cancer and cardiovascular diseases and the role of diagnostics and nano-therapies.","min_units":3,"max_units":3,"gers":[],"id":210886,"primary":[[],[{"instructors":[],"meeting-times":[{"days":[false,true,false,true,false],"start":"12:50:00 PM","end":"2:05:00 PM","location":"300-300"}],"id":55430}],[]],"primary_type":"Lecture"},
"241":{"department":"SBIO","number":"241","title":"Biological Macromolecules (BIOC 241, BIOPHYS 241)","description":"The physical and chemical basis of macromolecular function. Topics include: forces that stabilize macromolecular structure and their complexes; thermodynamics and statistical mechanics of macromolecular folding, binding, and allostery; diffusional processes; kinetics of enzymatic processes; the relationship of these principles to practical application in experimental design and interpretation. The class emphasizes interactive learning, and is divided equally among lectures, in-class group problem solving, and discussion of current and classical literature.  Enrollment limited to 50. Prerequisites: Background in biochemistry and physical chemistry recommended but material available for those with deficiency in these areas; undergraduates with consent of instructor only.","min_units":3,"max_units":5,"gers":[],"id":127288,"primary":[[],[],[{"instructors":[],"meeting-times":[{"days":[true,false,true,false,true],"start":"1:15:00 PM","end":"3:15:00 PM","location":"Li Ka Shing Center, room 130"}],"id":56211}]],"primary_type":"Lecture"}}<commit_msg>Delete SBIO.js<commit_after><|endoftext|>


<commit_before>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2015 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
var should = require('./should');

describe("analytics", function() {
	it("featureEvent", function(finish) {
		should(function() {
			Ti.Analytics.featureEvent();
		}).throw();
		should(Ti.Analytics.featureEvent("featureEvent.testButton")).be.undefined;
		should(Ti.Analytics.featureEvent("featureEvent.testButton", {
			events: "feature"
		})).be.undefined;
		finish();
	});
	it("navEvent", function(finish) {
		should(function() {
			Ti.Analytics.navEvent();
		}).throw();
		should(function() {
			Ti.Analytics.navEvent("here");
		}).throw();
		should(Ti.Analytics.navEvent("here", "there")).be.undefined;
		should(Ti.Analytics.navEvent("here", "there", "navEvent.testButton")).be.undefined;
		should(Ti.Analytics.navEvent("here", "there", "navEvent.testButton", {
			events: "nav"
		})).be.undefined;
		finish();
	});
});<commit_msg>Skip analytics unit test<commit_after>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2015 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
var should = require('./should');

describe("analytics", function() {
	it.skip("featureEvent", function(finish) {
		should(function() {
			Ti.Analytics.featureEvent();
		}).throw();
		should(Ti.Analytics.featureEvent("featureEvent.testButton")).be.undefined;
		should(Ti.Analytics.featureEvent("featureEvent.testButton", {
			events: "feature"
		})).be.undefined;
		finish();
	});
	it.skip("navEvent", function(finish) {
		should(function() {
			Ti.Analytics.navEvent();
		}).throw();
		should(function() {
			Ti.Analytics.navEvent("here");
		}).throw();
		should(Ti.Analytics.navEvent("here", "there")).be.undefined;
		should(Ti.Analytics.navEvent("here", "there", "navEvent.testButton")).be.undefined;
		should(Ti.Analytics.navEvent("here", "there", "navEvent.testButton", {
			events: "nav"
		})).be.undefined;
		finish();
	});
});
<|endoftext|>


<commit_before>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2015 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
var should = require('./should');

describe("ui_slider", function() {
	//TIMOB-9672
	it("sliderInTableViewRow", function(finish) {
		var win1 = Titanium.UI.createWindow({
			backgroundColor: "#FFFFFF"
		});
		var ds = [];
		expandableView = Ti.UI.createView({
			top: 0,
			height: 40,
			backgroundColor: "yellow"
		});
		var slider = Titanium.UI.createSlider({
			top: 0,
			min: 0,
			max: 100,
			value: 40
		});
		expandableView.add(slider);
		var row1 = Titanium.UI.createTableViewRow({
			height: 100
		});
		row1.add(expandableView);
		ds.push(row1);
		var tableView = Titanium.UI.createTableView({
			data: ds,
			scrollable: true,
			separatorColor: "transparent",
			separatorStyle: "none",
			top: 0,
			height: 80
		});
		win1.add(tableView);
		win1.open();
		should(slider.value).eql(40);
		finish();
	});
});<commit_msg>skip sliderTest temporary<commit_after>/*
 * Appcelerator Titanium Mobile
 * Copyright (c) 2011-2015 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 */
var should = require('./should');

describe("ui_slider", function() {
	//TIMOB-9672
	it.skip("sliderInTableViewRow", function(finish) {
		var win1 = Titanium.UI.createWindow({
			backgroundColor: "#FFFFFF"
		});
		var ds = [];
		expandableView = Ti.UI.createView({
			top: 0,
			height: 40,
			backgroundColor: "yellow"
		});
		var slider = Titanium.UI.createSlider({
			top: 0,
			min: 0,
			max: 100,
			value: 40
		});
		expandableView.add(slider);
		var row1 = Titanium.UI.createTableViewRow({
			height: 100
		});
		row1.add(expandableView);
		ds.push(row1);
		var tableView = Titanium.UI.createTableView({
			data: ds,
			scrollable: true,
			separatorColor: "transparent",
			separatorStyle: "none",
			top: 0,
			height: 80
		});
		win1.add(tableView);
		win1.open();
		should(slider.value).eql(40);
		finish();
	});
});<|endoftext|>


<commit_before>//= require esphinx
//= require_tree .
<commit_msg>remove called to esphinx<commit_after>//= require_tree .
<|endoftext|>


<commit_before>'use strict';

css3d.prototype.SimpleResize = function (options) {
    _.defaults(options, { resizable3d: this });

    if (this.resizer && this.resizer.resizable3d) this.resizer.destroy();
    this.resizer = new SimpleResize(options);
    this._registeredListeners.push('resizer');
};

var SimpleResize = function (options) {

    _.extend(this, options);

    this.min = this.min || 0;

    this.rootOffset = this.min;
    this.offsetX = [0.0, 0.0];

    this.resizing = false;

    this.tap = this.tap.bind(this);
    this.drag = this.drag.bind(this);
    this.pinch = this.pinch.bind(this);
    this.release = this.release.bind(this);

    // It's important to have both events:
    // e.g. MS Surface supports both depending on touch or stylus click
    if (css3d.touchIsSupported) {
        this.resizable3d.el.addEventListener('touchstart', this.tap);
    }

    this.resizable3d.el.addEventListener('mousedown', this.tap);
};

_.extend(SimpleResize.prototype, {
    tap: function (e) {

        if (e.target !== this.resizable3d.el) {
            return;
        }

        this.resizable3d.style[css3d.duration] = 0;

        this.resizing = false;
        this.reference0 = this.xpos(e);

        if (e.targetTouches && e.targetTouches.length > 1) {
            this.reference0 = getTouchPoint(e, 0);

            // Set rootOffset to the left-most touch point:
            this.rootOffset = this.reference0 - this.resizable3d.el.parentElement.getBoundingClientRect().left;

            window.removeEventListener('touchmove', this.drag);
            window.addEventListener('touchmove', this.pinch);
            window.addEventListener('touchend', this.release);
            return false; // cancel any other click handlers
        }

        // Store a reference for where each of the drag handles are:
        this.offsetX = [0, parseInt(this.resizable3d.style.width)];
        this.rootOffset = this.resizable3d.getX();

        // Store which handle we're dragging from ...
        if (this.xOffset(e) < 20) { // 20 is the number of pixels overlapping from the transparent resize areas
            this.curDragHandle = 0; // the drag handle on the left
        } else if (this.offsetX[1] - this.xOffset(e) < 20) { // within 20px of resizable el's width
            this.curDragHandle = 1; // the drag handle on the right
        } else {
            return; // we're not resizing, don't set up any other listeners etc
        }

        this.resizable3d.el.classList.add('resizing');

        if (css3d.touchIsSupported) {
            window.addEventListener('touchmove', this.drag);
            window.addEventListener('touchend', this.release);
        }

        window.addEventListener('mousemove', this.drag);
        window.addEventListener('mouseup', this.release);

        e.stopPropagation();
        e.stopImmediatePropagation();
        e.preventDefault();
        return false;
    },

    drag: function (e) {
        var x, delta;

        // 'x' & 'reference' are in the window space (clientX)
        x = this.xpos(e);
        delta = this.reference0 - x;

        if (delta > 2 || delta < -2) {
            this.resizing = true;
            this.reference0 = x;

            this.offsetX[this.curDragHandle] -= delta;

            this.resize(); // uses offsetX[n0, n1]
        }

        // e.stopPropagation();
        e.preventDefault();

        // return false;
    },

    pinch: function (e) {
        // We get here when we're __touching__ with >= 2 fingers
        this.offsetX[0] = getTouchPoint(e, 0);
        this.offsetX[1] = getTouchPoint(e, 1);

        this.resizable3d.setTranslate(this.rootOffset + this.offsetX[0] - this.reference0);
        this.resizable3d.style.width = Math.max(90, this.offsetX[1] - this.offsetX[0]) + 'px';

        e.preventDefault();
    },

    resize: function () {
        // We get here when dragging one(!) of the loop handles
        // (example) this.offsetX === [-5, 395];
        var leftAnchor = Math.min(this.offsetX[0], this.offsetX[1]) + this.rootOffset;
        leftAnchor = Math.max(0, Math.min(leftAnchor, this.max)); // bounds check

        var width = Math.max(this.offsetX[0], this.offsetX[1]) + this.rootOffset - leftAnchor;
        width = Math.max(90, Math.min(width, this.max - leftAnchor)); // bounds check
        width += 'px'; // deliberately on a new line for memory efficiency

        this.resizable3d.setTranslate(leftAnchor);
        this.resizable3d.style.width = width;
    },

    release: function (e) {
        this.resizable3d.style[css3d.duration] = null;
        this.resizable3d.el.classList.remove('resizing');

        if (css3d.touchIsSupported) {
            if (e.touches && e.touches.length < 2) {
                window.removeEventListener('touchmove', this.pinch);
            }

            window.removeEventListener('touchmove', this.drag);
            window.removeEventListener('touchend', this.release);
        }

        window.removeEventListener('mousemove', this.drag);
        window.removeEventListener('mouseup', this.release);

        this.resizing = false;
        e.preventDefault();
    },

    xOffset: function (e, finger) {
        // touch event
        if (e.touches && e.touches.length >= 1) {
            return e.touches[finger || 0].pageX - this.resizable3d.el.getBoundingClientRect().left;
        }

        // mouse event
        // offsetX doesn't exist in firefox, but getBoundingClientRect does
        return e.offsetX || e.pageX - this.resizable3d.el.getBoundingClientRect().left;
    },

    xpos: function (e) {
        // touch event
        if (e.touches && e.touches.length >= 1) {
            return e.touches[0].clientX;
        }

        // mouse event
        return e.clientX;
    },

    destroy: function () {
        if (css3d.touchIsSupported) {
            this.resizable3d.el.removeEventListener('touchstart', this.tap);
        }

        this.resizable3d.el.removeEventListener('mousedown', this.tap);

        this.resizable3d = this.tap = this.drag = this.pinch = this.release = null;
    },
});

// touch point 0 is the left-most touch point
// touch point 1 is the right-most touch point
function getTouchPoint(e, point) {
    return Math[point === 1 ? 'max' : 'min'](e.touches[0].pageX, e.touches[1].pageX);
}
<commit_msg>Fix bug: window event handlers sometimes not cleaned up<commit_after>'use strict';

css3d.prototype.SimpleResize = function (options) {
    _.defaults(options, { resizable3d: this });

    if (this.resizer && this.resizer.resizable3d) this.resizer.destroy();
    this.resizer = new SimpleResize(options);
    this._registeredListeners.push('resizer');
};

var SimpleResize = function (options) {

    _.extend(this, options);

    this.min = this.min || 0;

    this.rootOffset = this.min;
    this.offsetX = [0.0, 0.0];

    this.resizing = false;

    this.tap = this.tap.bind(this);
    this.drag = this.drag.bind(this);
    this.pinch = this.pinch.bind(this);
    this.release = this.release.bind(this);

    // It's important to have both events:
    // e.g. MS Surface supports both depending on touch or stylus click
    if (css3d.touchIsSupported) {
        this.resizable3d.el.addEventListener('touchstart', this.tap);
    }

    this.resizable3d.el.addEventListener('mousedown', this.tap);
};

_.extend(SimpleResize.prototype, {
    tap: function (e) {

        if (e.target !== this.resizable3d.el) {
            return;
        }

        this.resizable3d.style[css3d.duration] = 0;

        this.resizing = false;
        this.reference0 = this.xpos(e);

        if (e.targetTouches && e.targetTouches.length > 1) {
            this.reference0 = getTouchPoint(e, 0);

            // Set rootOffset to the left-most touch point:
            this.rootOffset = this.reference0 - this.resizable3d.el.parentElement.getBoundingClientRect().left;

            window.removeEventListener('touchmove', this.drag);
            window.addEventListener('touchmove', this.pinch);
            window.addEventListener('touchend', this.release);
            return false; // cancel any other click handlers
        }

        // Store a reference for where each of the drag handles are:
        this.offsetX = [0, parseInt(this.resizable3d.style.width)];
        this.rootOffset = this.resizable3d.getX();

        // Store which handle we're dragging from ...
        if (this.xOffset(e) < 20) { // 20 is the number of pixels overlapping from the transparent resize areas
            this.curDragHandle = 0; // the drag handle on the left
        } else if (this.offsetX[1] - this.xOffset(e) < 20) { // within 20px of resizable el's width
            this.curDragHandle = 1; // the drag handle on the right
        } else {
            return; // we're not resizing, don't set up any other listeners etc
        }

        this.resizable3d.el.classList.add('resizing');

        if (css3d.touchIsSupported) {
            window.addEventListener('touchmove', this.drag);
            window.addEventListener('touchend', this.release);
        }

        window.addEventListener('mousemove', this.drag);
        window.addEventListener('mouseup', this.release);

        e.stopPropagation();
        e.stopImmediatePropagation();
        e.preventDefault();
        return false;
    },

    drag: function (e) {
        var x, delta;

        // 'x' & 'reference' are in the window space (clientX)
        x = this.xpos(e);
        delta = this.reference0 - x;

        if (delta > 2 || delta < -2) {
            this.resizing = true;
            this.reference0 = x;

            this.offsetX[this.curDragHandle] -= delta;

            this.resize(); // uses offsetX[n0, n1]
        }

        // e.stopPropagation();
        e.preventDefault();

        // return false;
    },

    pinch: function (e) {
        // We get here when we're __touching__ with >= 2 fingers
        this.offsetX[0] = getTouchPoint(e, 0);
        this.offsetX[1] = getTouchPoint(e, 1);

        this.resizable3d.setTranslate(this.rootOffset + this.offsetX[0] - this.reference0);
        this.resizable3d.style.width = Math.max(90, this.offsetX[1] - this.offsetX[0]) + 'px';

        e.preventDefault();
    },

    resize: function () {
        // We get here when dragging one(!) of the loop handles
        // (example) this.offsetX === [-5, 395];
        var leftAnchor = Math.min(this.offsetX[0], this.offsetX[1]) + this.rootOffset;
        leftAnchor = Math.max(0, Math.min(leftAnchor, this.max)); // bounds check

        var width = Math.max(this.offsetX[0], this.offsetX[1]) + this.rootOffset - leftAnchor;
        width = Math.max(90, Math.min(width, this.max - leftAnchor)); // bounds check
        width += 'px'; // deliberately on a new line for memory efficiency

        this.resizable3d.setTranslate(leftAnchor);
        this.resizable3d.style.width = width;
    },

    release: function (e) {
        this.resizable3d.style[css3d.duration] = null;
        this.resizable3d.el.classList.remove('resizing');

        if (css3d.touchIsSupported) {
            if (e.touches && e.touches.length < 2) {
                window.removeEventListener('touchmove', this.pinch);
            }

            window.removeEventListener('touchmove', this.drag);
            window.removeEventListener('touchend', this.release);
        }

        window.removeEventListener('mousemove', this.drag);
        window.removeEventListener('mouseup', this.release);

        this.resizing = false;
        e.preventDefault();
    },

    xOffset: function (e, finger) {
        // touch event
        if (e.touches && e.touches.length >= 1) {
            return e.touches[finger || 0].pageX - this.resizable3d.el.getBoundingClientRect().left;
        }

        // mouse event
        // offsetX doesn't exist in firefox, but getBoundingClientRect does
        return e.offsetX || e.pageX - this.resizable3d.el.getBoundingClientRect().left;
    },

    xpos: function (e) {
        // touch event
        if (e.touches && e.touches.length >= 1) {
            return e.touches[0].clientX;
        }

        // mouse event
        return e.clientX;
    },

    killEventHandlers: function () {
        if (css3d.touchIsSupported) {
            window.removeEventListener('touchmove', this.drag);
            window.removeEventListener('touchmove', this.pinch);
            window.removeEventListener('touchend', this.release);
        }

        window.removeEventListener('mousemove', this.drag);
        window.removeEventListener('mouseup', this.release);
    },

    destroy: function () {
        this.killEventHandlers();

        if (css3d.touchIsSupported) {
            this.resizable3d.el.removeEventListener('touchstart', this.tap);
        }

        this.resizable3d.el.removeEventListener('mousedown', this.tap);

        this.resizable3d = this.tap = this.drag = this.pinch = this.release = null;
    },
});

// touch point 0 is the left-most touch point
// touch point 1 is the right-most touch point
function getTouchPoint(e, point) {
    return Math[point === 1 ? 'max' : 'min'](e.touches[0].pageX, e.touches[1].pageX);
}
<|endoftext|>


<commit_before>'use strict';

/*
 * Purpose:
 *   Combines all the components of the interface. Creates each component, gets task
 *   data, updates components. When the user submits their work this class gets the workers
 *   annotations and other data and submits to the backend
 * Dependencies:
 *   AnnotationStages (src/annotation_stages.js), PlayBar & WorkflowBtns (src/components.js),
 *   HiddenImg (src/hidden_image.js), colormap (colormap/colormap.min.js) , Wavesurfer (lib/wavesurfer.min.js)
 * Globals variable from other files:
 *   audio.html (task related):
 *       user_id, curio_id, task_id, total_num_tasks, num_required_tasks, experiment_id, condition_id, bonus_pay
 *   colormap.min.js:
 *       magma // color scheme array that maps 0 - 255 to rgb values
 *
 */
function Annotator() {
    this.wavesurfer;
    this.playBar;
    this.stages;
    this.workflowBtns;
    this.tasks = [];
    this.currentTask;
    this.numRemainingTasks;
    this.taskStartTime;
    this.hiddenImage;
    // Boolean, true if currently sending http post request
    this.sendingResponse = false;

    // Create color map for spectrogram
    var spectrogramColorMap = colormap({
        colormap: magma,
        nshades: 256,
        format: 'rgb',
        alpha: 1
    });

    // Create wavesurfer (audio visualization component)
    var height = 256;
    this.wavesurfer = Object.create(WaveSurfer);
    this.wavesurfer.init({
        container: '.audio_visual',
        waveColor: '#FF00FF',
        progressColor: '#FF00FF',
        // For the spectrogram the height is half the number of fftSamples
        fftSamples: height * 2,
        height: height,
        colorMap: spectrogramColorMap
    });

    // Create labels (labels that appear above each region)
    var labels = Object.create(WaveSurfer.Labels);
    labels.init({
        wavesurfer: this.wavesurfer,
        container: '.labels'
    });

    // Create hiddenImage, an image that is slowly revealed to a user as they annotate
    // (only for this.currentTask.data.content.feedback === 'hiddenImage')
    this.hiddenImage = new HiddenImg('.hidden_img', 100);
    this.hiddenImage.create();

    // Create the play button and time that appear below the wavesurfer
    this.playBar = new PlayBar(this.wavesurfer);
    this.playBar.create();

    // Create the annotation stages that appear below the wavesurfer. The stages contain tags
    // the users use to label a region in the audio clip
    this.stages = new AnnotationStages(this.wavesurfer, this.hiddenImage);
    this.stages.create();

    // Create Workflow btns (submit and exit)
    this.workflowBtns = new WorkflowBtns('/experiments/' + experiment_id + '/workflow/next');
    this.workflowBtns.create();

    this.addEvents();
}

Annotator.prototype = {
    addWaveSurferEvents: function() {
        var my = this;

        // function that moves the vertical progress bar to the current time in the audio clip
        var updateProgressBar = function () {
            var progress = my.wavesurfer.getCurrentTime() / my.wavesurfer.getDuration();
            my.wavesurfer.seekTo(progress);
        };

        // Update vertical progress bar to the currentTime when the sound clip is
        // finished or paused since it is only updated on audioprocess
        this.wavesurfer.on('pause', updateProgressBar);
        this.wavesurfer.on('finish', updateProgressBar);

        // When a new sound file is loaded into the wavesurfer update the  play bar, update the
        // annotation stages back to stage 1, update when the user started the task, update the workflow buttons.
        // Also if the user is suppose to get hidden image feedback, append that component to the page
        this.wavesurfer.on('ready', function () {
            my.playBar.update();
            my.stages.updateStage(1);
            my.updateTaskTime();
            my.workflowBtns.update();
            if (my.currentTask.attributes.data_content.feedback === 'hiddenImage') {
                my.hiddenImage.append(static_url + my.currentTask.attributes.data_content.img_url);
            }
        });

        this.wavesurfer.on('click', function (e) {
            my.stages.clickDeselectCurrentRegion();
        });
    },

    updateTaskTime: function() {
        this.taskStartTime = new Date().getTime();
    },

    // Update the #task-progress-bar element to contain how many task the user has completed out of the total.
    // The element will also contain a message about how much bonus pay they will get for the clip if they have
    // completed more then the required amount
    updateProgress: function() {
        var currentTaskNumber = (total_num_tasks - this.numRemainingTasks) + 1;
        var bonusText = 'Bonus for this clip: ' + bonus_pay * 100 + ' cents | ';
        var text = (currentTaskNumber > num_required_tasks) ? bonusText + 'Progress: ' : 'Progress: ';
        var progress = $('', {
            text: text
        });
        var value = $('', {
            text: currentTaskNumber + ' / ' + total_num_tasks
        });
        $('#task-progress-bar').html([progress, value]);
    },

    // If the user has completed more than the required amount of tasks, show the exit button
    updateExitBtnFlag: function() {
        var numCompletedTasks = total_num_tasks - this.numRemainingTasks;
        var shouldShowExit = experiment_id && (numCompletedTasks >= num_required_tasks)
        this.workflowBtns.setExitBtnFlag(shouldShowExit);
    },

    // Event Handler, if the user clicks submit annotations call submitAnnotations
    addWorkflowBtnEvents: function() {
        $(this.workflowBtns).on('submit-annotations', this.submitAnnotations.bind(this));
    },

    addEvents: function() {
        this.addWaveSurferEvents();
        this.addWorkflowBtnEvents();
    },

    // Update the task specific data of the interfaces components
    update: function() {
        this.currentTask = this.tasks.shift();

        // In the case where the api returns the content field as a string, convert it to JSON
        if (typeof this.currentTask.attributes.data_content === 'string') {
            this.currentTask.attributes.data_content = JSON.parse(this.currentTask.attributes.data_content);
        }

        var my = this;

        var mainUpdate = function(annotationSolutions) {
            // Update the task progress text to show the user how many taks they have completed
            my.updateProgress();
            // Update weither the exit button should be shown
            my.updateExitBtnFlag();

            // Update the different tags the user can use to annotate, also update the solutions to the
            // annotation task if the user is suppose to recieve feedback
            var proximityTags = my.currentTask.attributes.data_content.proximity_tag;
            var annotationTags = my.currentTask.attributes.data_content.annotation_tag;
            var tutorialVideoURL = my.currentTask.attributes.data_content.tutorial_video_url;
            var alwaysShowTags = my.currentTask.attributes.data_content.always_show_tags;
            var instructions = my.currentTask.attributes.data_content.instructions;
            my.stages.reset(
                proximityTags,
                annotationTags,
                annotationSolutions,
                alwaysShowTags
            );

            // set video url
            $('#tutorial-video').attr('src', tutorialVideoURL);

            // add instructions
            var instructionsContainer = $('#instructions-container');
            if (typeof instructions !== "undefined") {
                $('#instructions-modal').openModal();
                instructions.forEach(function (instruction, index) {
                    if (index==0) {
                        // first instruction is the header
                        var instr = $('
', {
                            html: instruction
                        });
                    } else {
                        var instr = $('
', {
                            "class": "instruction",
                            html: instruction
                        });
                    }
                    instructionsContainer.append(instr);
                });
            }
            else
            {
                $('#instructions-container').hide();
                $('#instructions-btn').hide();
            }

            // Update the visualization type and the feedback type and load in the new audio clip
            my.wavesurfer.params.visualization = my.currentTask.attributes.data_content.visualization; // invisible, spectrogram, waveform
            my.wavesurfer.params.feedback = my.currentTask.attributes.data_content.feedback; // hiddenImage, silent, notify, none
            my.wavesurfer.load(my.currentTask.attributes.data_url);
        };

        if (this.currentTask.attributes.data_content.feedback !== 'none') {
            // If the current task gives the user feedback, load the tasks solutions and then update
            // interface components
            $.getJSON(static_url + this.currentTask.attributes.data_content.annotation_solutions_url)
            .done(function(data) {
                mainUpdate(data);
            })
            .fail(function() {
                alert('Error: Unable to retrieve annotation solution set');
            });
        } else {
            // If not, there is no need to make an additional request. Just update task specific data right away
            mainUpdate({});
        }
    },

    // Update the interface with the next task's data
    loadNextTask: function() {
        var my = this;
        if (this.tasks.length === 0) {
            // If there is no more tasks in the list load 3 more
            var data = {
                task: task_id,
                page_size: 3
            }
            if (experiment_id) {
                data.experiment = experiment_id;
                data.condition = condition_id;
            } else {
                data.public = 'True';
            }
            $.getJSON('/api/route/', data)
            .done(function(data) {
                if (data.data.length > 0) {
                    my.numRemainingTasks = data.meta.pagination.count;
                    my.tasks = data.data;
                    my.update();
                } else {
                    // If there is no more tasks for the user to complete
                    if (experiment_id) {
                        console.log("No more tasks.")
                        window.location = '/experiments/' + experiment_id + '/workflow/next';
                    } else {
                        $('#ending_modal p').html(
                            'Wow! You\'ve labeled all the sound clips for this project.' +
                            'Thanks so much for helping us out!'
                        );
                        $('#ending_modal').openModal({dismissible: false});
                    }
                }
            })
            .fail(function() {
                alert('Error: Unable to retrieve more tasks');
            });
        } else {
            // Update the interface to the next task in the list (this.tasks)
            this.update();
        }
    },

    // Collect data about users annotations and submit it to the backend
    submitAnnotations: function() {
        // Check if all the regions have been labeled before submitting
        if (this.stages.annotationDataValidationCheck()) {
            if (this.sendingResponse) {
                // If it is already sending a post with the data, do nothing
                return;
            }
            this.sendingResponse = true;
            // Get data about the annotations the user has created
            var content = {
                task_start_time: this.taskStartTime,
                task_end_time: new Date().getTime(),
                visualization: this.wavesurfer.params.visualization,
                annotations: this.stages.getAnnotations(),
                deleted_annotations: this.stages.getDeletedAnnotations(),
                // List of the different types of actions they took to create the annotations
                annotation_events: this.stages.getEvents(),
                // List of actions the user took to play and pause the audio
                play_events: this.playBar.getEvents(),
                // Boolean, if at the end, the user was shown what city the clip was recorded in
                final_solution_shown: this.stages.aboveThreshold()
            };

            if (this.stages.aboveThreshold()) {
                // If the user is suppose to recieve feedback and got enough of the annotations correct
                // display the city the clip was recorded for 2 seconds and then submit their work
                var my = this;
                this.stages.displaySolution();
                setTimeout(function() {
                    my.post(content);
                }, 2000);
            } else {
                this.post(content);
            }
        }
    },

    // Make POST request, passing back the content data. On success load in the next task
    post: function (content) {
        $('#loading_modal').openModal({dismissible: false});
        var my = this;
        var data;

        if(!experiment_id) {
            data = {
                data: {
                    type: "Response",
                    attributes: {
                        "content": content
                    },
                    relationships: {
                        owner: {"data": {type: "User", id: user_id}},
                        data: {"data": {type: "Data", id: this.currentTask.attributes.data_id}},
                        task: {"data": {type: "Task", id: task_id}},
                        curio: {"data": {type: "Curio", id: curio_id}}
                    }
                },
                csrfmiddlewaretoken: csrftoken
            };
        } else {
            data = {
                data: {
                    type: "Response",
                    attributes: {
                        "content": content
                    },
                    relationships: {
                        owner: {"data": {type: "User", id: user_id}},
                        data: {"data": {type: "Data", id: this.currentTask.attributes.data_id}},
                        task: {"data": {type: "Task", id: task_id}},
                        curio: {"data": {type: "Curio", id: curio_id}},
                        experiment: { "data" : { type : "Experiment", id: experiment_id }},
                        condition: { "data" : { type : "Condition", id: condition_id }}
                    }
                },
                csrfmiddlewaretoken: csrftoken
            };
        }

        $.ajax({
            type: 'POST',
            url: '/api/response/',
            contentType: 'application/vnd.api+json',
            data: JSON.stringify(data)
        })
        .done(function(data) {
            my.numRemainingTasks -= 1;
            // If the last task had a hiddenImage component, remove it
            if (my.currentTask.attributes.data_content.feedback === 'hiddenImage') {
                my.hiddenImage.remove();
            }
            my.loadNextTask();
        })
        .fail(function() {
            alert('Error: Unable to Submit Annotations');
        })
        .always(function() {
            // No longer sending response
            my.sendingResponse = false;
            setTimeout(function(){$('#loading_modal').closeModal();}, 2000);
        });
    }

};

function main() {
    // Create all the components
    var annotator = new Annotator();
    // Load the first audio annotation task
    annotator.loadNextTask();
}
main();
<commit_msg>Empty instructions container before populating<commit_after>'use strict';

/*
 * Purpose:
 *   Combines all the components of the interface. Creates each component, gets task
 *   data, updates components. When the user submits their work this class gets the workers
 *   annotations and other data and submits to the backend
 * Dependencies:
 *   AnnotationStages (src/annotation_stages.js), PlayBar & WorkflowBtns (src/components.js),
 *   HiddenImg (src/hidden_image.js), colormap (colormap/colormap.min.js) , Wavesurfer (lib/wavesurfer.min.js)
 * Globals variable from other files:
 *   audio.html (task related):
 *       user_id, curio_id, task_id, total_num_tasks, num_required_tasks, experiment_id, condition_id, bonus_pay
 *   colormap.min.js:
 *       magma // color scheme array that maps 0 - 255 to rgb values
 *
 */
function Annotator() {
    this.wavesurfer;
    this.playBar;
    this.stages;
    this.workflowBtns;
    this.tasks = [];
    this.currentTask;
    this.numRemainingTasks;
    this.taskStartTime;
    this.hiddenImage;
    // Boolean, true if currently sending http post request
    this.sendingResponse = false;

    // Create color map for spectrogram
    var spectrogramColorMap = colormap({
        colormap: magma,
        nshades: 256,
        format: 'rgb',
        alpha: 1
    });

    // Create wavesurfer (audio visualization component)
    var height = 256;
    this.wavesurfer = Object.create(WaveSurfer);
    this.wavesurfer.init({
        container: '.audio_visual',
        waveColor: '#FF00FF',
        progressColor: '#FF00FF',
        // For the spectrogram the height is half the number of fftSamples
        fftSamples: height * 2,
        height: height,
        colorMap: spectrogramColorMap
    });

    // Create labels (labels that appear above each region)
    var labels = Object.create(WaveSurfer.Labels);
    labels.init({
        wavesurfer: this.wavesurfer,
        container: '.labels'
    });

    // Create hiddenImage, an image that is slowly revealed to a user as they annotate
    // (only for this.currentTask.data.content.feedback === 'hiddenImage')
    this.hiddenImage = new HiddenImg('.hidden_img', 100);
    this.hiddenImage.create();

    // Create the play button and time that appear below the wavesurfer
    this.playBar = new PlayBar(this.wavesurfer);
    this.playBar.create();

    // Create the annotation stages that appear below the wavesurfer. The stages contain tags
    // the users use to label a region in the audio clip
    this.stages = new AnnotationStages(this.wavesurfer, this.hiddenImage);
    this.stages.create();

    // Create Workflow btns (submit and exit)
    this.workflowBtns = new WorkflowBtns('/experiments/' + experiment_id + '/workflow/next');
    this.workflowBtns.create();

    this.addEvents();
}

Annotator.prototype = {
    addWaveSurferEvents: function() {
        var my = this;

        // function that moves the vertical progress bar to the current time in the audio clip
        var updateProgressBar = function () {
            var progress = my.wavesurfer.getCurrentTime() / my.wavesurfer.getDuration();
            my.wavesurfer.seekTo(progress);
        };

        // Update vertical progress bar to the currentTime when the sound clip is
        // finished or paused since it is only updated on audioprocess
        this.wavesurfer.on('pause', updateProgressBar);
        this.wavesurfer.on('finish', updateProgressBar);

        // When a new sound file is loaded into the wavesurfer update the  play bar, update the
        // annotation stages back to stage 1, update when the user started the task, update the workflow buttons.
        // Also if the user is suppose to get hidden image feedback, append that component to the page
        this.wavesurfer.on('ready', function () {
            my.playBar.update();
            my.stages.updateStage(1);
            my.updateTaskTime();
            my.workflowBtns.update();
            if (my.currentTask.attributes.data_content.feedback === 'hiddenImage') {
                my.hiddenImage.append(static_url + my.currentTask.attributes.data_content.img_url);
            }
        });

        this.wavesurfer.on('click', function (e) {
            my.stages.clickDeselectCurrentRegion();
        });
    },

    updateTaskTime: function() {
        this.taskStartTime = new Date().getTime();
    },

    // Update the #task-progress-bar element to contain how many task the user has completed out of the total.
    // The element will also contain a message about how much bonus pay they will get for the clip if they have
    // completed more then the required amount
    updateProgress: function() {
        var currentTaskNumber = (total_num_tasks - this.numRemainingTasks) + 1;
        var bonusText = 'Bonus for this clip: ' + bonus_pay * 100 + ' cents | ';
        var text = (currentTaskNumber > num_required_tasks) ? bonusText + 'Progress: ' : 'Progress: ';
        var progress = $('', {
            text: text
        });
        var value = $('', {
            text: currentTaskNumber + ' / ' + total_num_tasks
        });
        $('#task-progress-bar').html([progress, value]);
    },

    // If the user has completed more than the required amount of tasks, show the exit button
    updateExitBtnFlag: function() {
        var numCompletedTasks = total_num_tasks - this.numRemainingTasks;
        var shouldShowExit = experiment_id && (numCompletedTasks >= num_required_tasks)
        this.workflowBtns.setExitBtnFlag(shouldShowExit);
    },

    // Event Handler, if the user clicks submit annotations call submitAnnotations
    addWorkflowBtnEvents: function() {
        $(this.workflowBtns).on('submit-annotations', this.submitAnnotations.bind(this));
    },

    addEvents: function() {
        this.addWaveSurferEvents();
        this.addWorkflowBtnEvents();
    },

    // Update the task specific data of the interfaces components
    update: function() {
        this.currentTask = this.tasks.shift();

        // In the case where the api returns the content field as a string, convert it to JSON
        if (typeof this.currentTask.attributes.data_content === 'string') {
            this.currentTask.attributes.data_content = JSON.parse(this.currentTask.attributes.data_content);
        }

        var my = this;

        var mainUpdate = function(annotationSolutions) {
            // Update the task progress text to show the user how many taks they have completed
            my.updateProgress();
            // Update weither the exit button should be shown
            my.updateExitBtnFlag();

            // Update the different tags the user can use to annotate, also update the solutions to the
            // annotation task if the user is suppose to recieve feedback
            var proximityTags = my.currentTask.attributes.data_content.proximity_tag;
            var annotationTags = my.currentTask.attributes.data_content.annotation_tag;
            var tutorialVideoURL = my.currentTask.attributes.data_content.tutorial_video_url;
            var alwaysShowTags = my.currentTask.attributes.data_content.always_show_tags;
            var instructions = my.currentTask.attributes.data_content.instructions;
            my.stages.reset(
                proximityTags,
                annotationTags,
                annotationSolutions,
                alwaysShowTags
            );

            // set video url
            $('#tutorial-video').attr('src', tutorialVideoURL);

            // add instructions
            var instructionsContainer = $('#instructions-container');
            instructionsContainer.empty();
            if (typeof instructions !== "undefined") {
                $('#instructions-modal').openModal();
                instructions.forEach(function (instruction, index) {
                    if (index==0) {
                        // first instruction is the header
                        var instr = $('
', {
                            html: instruction
                        });
                    } else {
                        var instr = $('
', {
                            "class": "instruction",
                            html: instruction
                        });
                    }
                    instructionsContainer.append(instr);
                });
            }
            else
            {
                $('#instructions-container').hide();
                $('#instructions-btn').hide();
            }

            // Update the visualization type and the feedback type and load in the new audio clip
            my.wavesurfer.params.visualization = my.currentTask.attributes.data_content.visualization; // invisible, spectrogram, waveform
            my.wavesurfer.params.feedback = my.currentTask.attributes.data_content.feedback; // hiddenImage, silent, notify, none
            my.wavesurfer.load(my.currentTask.attributes.data_url);
        };

        if (this.currentTask.attributes.data_content.feedback !== 'none') {
            // If the current task gives the user feedback, load the tasks solutions and then update
            // interface components
            $.getJSON(static_url + this.currentTask.attributes.data_content.annotation_solutions_url)
            .done(function(data) {
                mainUpdate(data);
            })
            .fail(function() {
                alert('Error: Unable to retrieve annotation solution set');
            });
        } else {
            // If not, there is no need to make an additional request. Just update task specific data right away
            mainUpdate({});
        }
    },

    // Update the interface with the next task's data
    loadNextTask: function() {
        var my = this;
        if (this.tasks.length === 0) {
            // If there is no more tasks in the list load 3 more
            var data = {
                task: task_id,
                page_size: 3
            }
            if (experiment_id) {
                data.experiment = experiment_id;
                data.condition = condition_id;
            } else {
                data.public = 'True';
            }
            $.getJSON('/api/route/', data)
            .done(function(data) {
                if (data.data.length > 0) {
                    my.numRemainingTasks = data.meta.pagination.count;
                    my.tasks = data.data;
                    my.update();
                } else {
                    // If there is no more tasks for the user to complete
                    if (experiment_id) {
                        console.log("No more tasks.")
                        window.location = '/experiments/' + experiment_id + '/workflow/next';
                    } else {
                        $('#ending_modal p').html(
                            'Wow! You\'ve labeled all the sound clips for this project.' +
                            'Thanks so much for helping us out!'
                        );
                        $('#ending_modal').openModal({dismissible: false});
                    }
                }
            })
            .fail(function() {
                alert('Error: Unable to retrieve more tasks');
            });
        } else {
            // Update the interface to the next task in the list (this.tasks)
            this.update();
        }
    },

    // Collect data about users annotations and submit it to the backend
    submitAnnotations: function() {
        // Check if all the regions have been labeled before submitting
        if (this.stages.annotationDataValidationCheck()) {
            if (this.sendingResponse) {
                // If it is already sending a post with the data, do nothing
                return;
            }
            this.sendingResponse = true;
            // Get data about the annotations the user has created
            var content = {
                task_start_time: this.taskStartTime,
                task_end_time: new Date().getTime(),
                visualization: this.wavesurfer.params.visualization,
                annotations: this.stages.getAnnotations(),
                deleted_annotations: this.stages.getDeletedAnnotations(),
                // List of the different types of actions they took to create the annotations
                annotation_events: this.stages.getEvents(),
                // List of actions the user took to play and pause the audio
                play_events: this.playBar.getEvents(),
                // Boolean, if at the end, the user was shown what city the clip was recorded in
                final_solution_shown: this.stages.aboveThreshold()
            };

            if (this.stages.aboveThreshold()) {
                // If the user is suppose to recieve feedback and got enough of the annotations correct
                // display the city the clip was recorded for 2 seconds and then submit their work
                var my = this;
                this.stages.displaySolution();
                setTimeout(function() {
                    my.post(content);
                }, 2000);
            } else {
                this.post(content);
            }
        }
    },

    // Make POST request, passing back the content data. On success load in the next task
    post: function (content) {
        $('#loading_modal').openModal({dismissible: false});
        var my = this;
        var data;

        if(!experiment_id) {
            data = {
                data: {
                    type: "Response",
                    attributes: {
                        "content": content
                    },
                    relationships: {
                        owner: {"data": {type: "User", id: user_id}},
                        data: {"data": {type: "Data", id: this.currentTask.attributes.data_id}},
                        task: {"data": {type: "Task", id: task_id}},
                        curio: {"data": {type: "Curio", id: curio_id}}
                    }
                },
                csrfmiddlewaretoken: csrftoken
            };
        } else {
            data = {
                data: {
                    type: "Response",
                    attributes: {
                        "content": content
                    },
                    relationships: {
                        owner: {"data": {type: "User", id: user_id}},
                        data: {"data": {type: "Data", id: this.currentTask.attributes.data_id}},
                        task: {"data": {type: "Task", id: task_id}},
                        curio: {"data": {type: "Curio", id: curio_id}},
                        experiment: { "data" : { type : "Experiment", id: experiment_id }},
                        condition: { "data" : { type : "Condition", id: condition_id }}
                    }
                },
                csrfmiddlewaretoken: csrftoken
            };
        }

        $.ajax({
            type: 'POST',
            url: '/api/response/',
            contentType: 'application/vnd.api+json',
            data: JSON.stringify(data)
        })
        .done(function(data) {
            my.numRemainingTasks -= 1;
            // If the last task had a hiddenImage component, remove it
            if (my.currentTask.attributes.data_content.feedback === 'hiddenImage') {
                my.hiddenImage.remove();
            }
            my.loadNextTask();
        })
        .fail(function() {
            alert('Error: Unable to Submit Annotations');
        })
        .always(function() {
            // No longer sending response
            my.sendingResponse = false;
            setTimeout(function(){$('#loading_modal').closeModal();}, 2000);
        });
    }

};

function main() {
    // Create all the components
    var annotator = new Annotator();
    // Load the first audio annotation task
    annotator.loadNextTask();
}
main();
<|endoftext|>


<commit_before>// use strict
'use strict';

// require dependencies
const config     = require ('config');
const sitemap    = require ('sitemap');
const controller = require ('controller');

/**
 * build meta controller
 */
class metaController extends controller {
  /**
   * construct example controller class
   */
  constructor () {
    // run super eden
    super ();

    // bind private variables
    this._sitemap = null;

    // bind private methods
    this._compile    = this._compile.bind (this);
    this._generate   = this._generate.bind (this);
    this._middleware = this._middleware.bind (this);

    // run middleware
    this.eden.router.use (this._middleware);

    // on render
    this.eden.pre ('view.compile', this._compile);

    // generate sitemap
    if (config.get ('sitemap') && config.get ('sitemap.enabled')) {
      // generate on interval
      this._generate ();

      // set interval
      if (config.get ('sitemap.interval')) setInterval (this._generate, config.get ('sitemap.interval'));
    }
  }

  /**
   * create sitemap
   *
   * @param  {Request}   req
   * @param  {Response}  res
   *
   * @route  {get} /sitemap.xml
   *
   * @return {Promise}
   */
  async sitemap (req, res, next) {
    // check sitemap
    if (!this._sitemap) return next ();

    // return sitemap
    this._sitemap.toXML ((err, xml) => {
      // check error
      if (err) return next ();

      // set header
      res.header ('Content-Type', 'application/xml');

      // send xml
      res.send (xml);
    });
  }

  /**
   * generates xml
   *
   * @return {Promise}
   */
  async _generate () {
    // create sitemap
    let map = {
      'hostname'  : 'https://' + config.get ('domain'),
      'cacheTime' : config.get ('sitemap.cache') || 600000,
      'urls'      : [
        {
          'url'        : '',
          'priority'   : 1,
          'changefreq' : 'daily'
        }
      ]
    };

    // hook generate
    await this.eden.hook ('sitemap', map);

    // create
    this._sitemap = sitemap.createSitemap (map);
  }

  /**
   * compile view
   *
   * @param  {Object}  render
   *
   * @return {Promise}
   */
  async _compile (render) {
    // check meta
    let meta = render.state.meta || {};

    // delete meta
    delete render.state.meta;

    // create head
    if (!render.page.head) render.page.head = '';

    // loop meta
    for (let type in meta) {
      // loop for name
      for (let name in meta[type]) {
        // add to head
        render.page.head += '<' + type;

        // loop for tags
        for (let tag in meta[type][name]) {
          // add tag
          render.page.head += ' ' + tag + '="' + meta[type][name][tag] + '"';
        }

        // close tag
        render.page.head += ' />';
      }
    }
  }

  /**
   * build middleware
   *
   * @param  {request}  req Express request
   * @param  {response} res Express response
   * @param  {function} next Next callback
   *
   * @private
   */
  async _middleware (req, res, next) {
    // create meta function
    req.meta = res.meta = (type, opts) => {
      // check type and opts
      if (typeof type === 'object' || !opts) {
        // set variables
        opts = type;
        type = 'meta';
      }

      // get name
      let name = opts.name || opts.property || opts[Object.keys (opts)[0]];

      // check type
      if (type === 'title')       return req.title (opts);
      if (type === 'image')       return req.image (opts);
      if (type === 'description') return req.description (opts);

      // check opts
      if (typeof opts === 'string') {
        opts = {
          'name'    : type,
          'content' : opts
        };
        type = 'meta';
      }

      // check if meta exists
      if (!res.locals.meta) res.locals.meta = {};

      // check meta type
      if (!res.locals.meta[type]) res.locals.meta[type] = {};

      // set meta
      res.locals.meta[type][name] = opts;

      // return req
      return res;
    };

    // create description
    req.og = res.og = (name, content) => {
      // add description
      req.meta ({
        'property' : 'og:' + name,
        'content'  : content
      });

      // return req
      return res;
    };

    // create description
    req.twitter = res.twitter = (name, content) => {
      // add description
      req.meta ({
        'name'    : 'twitter:' + name,
        'content' : content
      });

      // return req
      return res;
    };

    // create description
    req.title = res.title = (title) => {
      // set title
      res.locals.page = res.locals.page || {};

      // set title
      res.locals.page.title = title;

      // add description
      req.og ('title', title);
      req.twitter ('title', title);

      // return req
      return res;
    };

    // create description
    req.description = res.description = (description) => {
      // add description
      req.og ('description', description);
      req.twitter ('description', description);

      // return req
      return res;
    };

    // create description
    req.image = res.image = (url) => {
      // add image
      req.og ('image', url);
      req.twitter ('image', url);

      // return req
      return res;
    };

    // set default title
    req.title (config.get ('title'));

    // set default
    req.og ('url', '//' + config.get ('domain') + req.url);
    req.og ('locale', req.language);
    req.twitter ('card', 'summary');
    req.twitter ('site', config.get ('title'));

    // run next
    next ();
  }
}

/**
 * export meta controller
 *
 * @type {metaController}
 */
exports = module.exports = metaController;
<commit_msg>assign sitemap to eden to allow sitemap manipulation at core<commit_after>// use strict
'use strict';

// require dependencies
const config     = require ('config');
const sitemap    = require ('sitemap');
const controller = require ('controller');

/**
 * build meta controller
 */
class metaController extends controller {
  /**
   * construct example controller class
   */
  constructor () {
    // run super eden
    super ();

    // bind private variables
    this._sitemap = null;

    // bind private methods
    this._compile    = this._compile.bind (this);
    this._generate   = this._generate.bind (this);
    this._middleware = this._middleware.bind (this);

    // run middleware
    this.eden.router.use (this._middleware);

    // on render
    this.eden.pre ('view.compile', this._compile);

    // generate sitemap
    if (config.get ('sitemap') && config.get ('sitemap.enabled')) {
      // generate on interval
      this._generate ();

      // set interval
      if (config.get ('sitemap.interval')) setInterval (this._generate, config.get ('sitemap.interval'));
    }
  }

  /**
   * create sitemap
   *
   * @param  {Request}   req
   * @param  {Response}  res
   *
   * @route  {get} /sitemap.xml
   *
   * @return {Promise}
   */
  async sitemap (req, res, next) {
    // check sitemap
    if (!this._sitemap) return next ();

    // return sitemap
    this._sitemap.toXML ((err, xml) => {
      // check error
      if (err) return next ();

      // set header
      res.header ('Content-Type', 'application/xml');

      // send xml
      res.send (xml);
    });
  }

  /**
   * generates xml
   *
   * @return {Promise}
   */
  _generate () {
    // create sitemap
    let map = {
      'hostname'  : 'https://' + config.get ('domain'),
      'cacheTime' : config.get ('sitemap.cache') || 600000,
      'urls'      : [
        {
          'url'        : '',
          'priority'   : 1,
          'changefreq' : 'daily'
        }
      ]
    };

    // create
    this._sitemap = sitemap.createSitemap (map);

    // add to eden
    this.eden.sitemap = this._sitemap;
  }

  /**
   * compile view
   *
   * @param  {Object}  render
   *
   * @return {Promise}
   */
  async _compile (render) {
    // check meta
    let meta = render.state.meta || {};

    // delete meta
    delete render.state.meta;

    // create head
    if (!render.page.head) render.page.head = '';

    // loop meta
    for (let type in meta) {
      // loop for name
      for (let name in meta[type]) {
        // add to head
        render.page.head += '<' + type;

        // loop for tags
        for (let tag in meta[type][name]) {
          // add tag
          render.page.head += ' ' + tag + '="' + meta[type][name][tag] + '"';
        }

        // close tag
        render.page.head += ' />';
      }
    }
  }

  /**
   * build middleware
   *
   * @param  {request}  req Express request
   * @param  {response} res Express response
   * @param  {function} next Next callback
   *
   * @private
   */
  async _middleware (req, res, next) {
    // create meta function
    req.meta = res.meta = (type, opts) => {
      // check type and opts
      if (typeof type === 'object' || !opts) {
        // set variables
        opts = type;
        type = 'meta';
      }

      // get name
      let name = opts.name || opts.property || opts[Object.keys (opts)[0]];

      // check type
      if (type === 'title')       return req.title (opts);
      if (type === 'image')       return req.image (opts);
      if (type === 'description') return req.description (opts);

      // check opts
      if (typeof opts === 'string') {
        opts = {
          'name'    : type,
          'content' : opts
        };
        type = 'meta';
      }

      // check if meta exists
      if (!res.locals.meta) res.locals.meta = {};

      // check meta type
      if (!res.locals.meta[type]) res.locals.meta[type] = {};

      // set meta
      res.locals.meta[type][name] = opts;

      // return req
      return res;
    };

    // create description
    req.og = res.og = (name, content) => {
      // add description
      req.meta ({
        'property' : 'og:' + name,
        'content'  : content
      });

      // return req
      return res;
    };

    // create description
    req.twitter = res.twitter = (name, content) => {
      // add description
      req.meta ({
        'name'    : 'twitter:' + name,
        'content' : content
      });

      // return req
      return res;
    };

    // create description
    req.title = res.title = (title) => {
      // set title
      res.locals.page = res.locals.page || {};

      // set title
      res.locals.page.title = title;

      // add description
      req.og ('title', title);
      req.twitter ('title', title);

      // return req
      return res;
    };

    // create description
    req.description = res.description = (description) => {
      // add description
      req.og ('description', description);
      req.twitter ('description', description);

      // return req
      return res;
    };

    // create description
    req.image = res.image = (url) => {
      // add image
      req.og ('image', url);
      req.twitter ('image', url);

      // return req
      return res;
    };

    // set default title
    req.title (config.get ('title'));

    // set default
    req.og ('url', '//' + config.get ('domain') + req.url);
    req.og ('locale', req.language);
    req.twitter ('card', 'summary');
    req.twitter ('site', config.get ('title'));

    // run next
    next ();
  }
}

/**
 * export meta controller
 *
 * @type {metaController}
 */
exports = module.exports = metaController;
<|endoftext|>


<commit_before>const { request, proxy, captcha } = require('smappi-cl');

/**
 * Test of simple list
 *
 * @example
 *   list_simple()
 *   // => ['str_1', 'str_2']
 *
 */
function list_simple () {
    return ['str_1', 'str_2'];
}

/**
 * Test of list with regexp
 *
 * @example
 *   list_regexp()
 *   // => [/str_\d+/, 'str_2']
 *
 */
function list_regexp () {
    return ['str_123', 'str_2'];
}

/**
 * Test of list
 *
 * @example
 *   list_mask()
 *   // => ['str_1', ..., 'str_5']
 *   list_mask()
 *   // => ['str_1', ...[], 'str_4', 'str_5']
 *
 */
function list_mask () {
    return ['str_1', 'str_2', 'str_3', 'str_4', 'str_5'];
}

/**
 * Test of list twice
 *
 * @example
 *   list_twice()
 *   // => ['str_1', ..., 'str_4', ..., 123]
 *   list_twice()
 *   // => ['str_1', ...[], 'str_4', ..., 123]
 *   list_twice()
 *   // => ['str_1', ...[], 'str_4', ..., ..., 55, 123]
 *
 */
function list_twice () {
    return ['str_1', 'str_2', 'str_3', 'str_4', 444, 55, 123];
}

/**
 * Test of obj
 *
 * @example
 *   obj()
 *   // => {'a': 1, 'b': 2, ...{}, 'e': 5}
 *
 */
function obj () {
    return {'a': 1, 'b': 2, 'e': 5}
}

/**
 * Test of obj
 *
 * @example
 *   obj_nested()
 *   // => {one: {...{}}, ...{}}
 *
 */
function obj_nested () {
    return {one: {a: 1, b: 2}, two: 2}
}

/**
 * Test of str
 *
 * @example
 *   str()
 *   // => 'abc...(x|y).+z'
 *
 */
function str () {
    return 'abcdefxyz'
}

/**
 * Test of short str
 *
 * @example
 *   str_short()
 *   // => 'abc...xyz'
 *
 */
function str_short ()
{
    return 'abcxyz'
}

/**
 * Test list of strings
 *
 * @example
 *   list_strings()
 *   // => ['a1', 'a2', 'b...', ...]
 *
 */
function list_strings ()
{
    return ['a1', 'a2', 'b3', 'c4']
}

/**
 * Test of deferred return with mixed types
 *
 * @example
 *   defer_return_mixed()
 *   // => [{a: 1}, {...{}, b: [..., 42, ...]}, ...]
 *
 */
function defer_return_mixed ()
{
    let self = this;
    setTimeout(() => {
        // console.log('self.return for defer_return_mixed', self.return)
        self.return([{a: 1}, {a: 2, b: [41, 42, 43]}, {a: 3}]);
    }, 1000);
}

/**
 * Test of mixed types
 *
 * @example
 *   mixed()
 *   // => [{a: 1}, {...{}, b: [..., 42, ...]}, ...]
 *
 */
function mixed ()
{
    return [{a: 1}, {a: 2, b: [41, 42, 43]}, {a: 3}]
}

/**
 * Test of deferred return
 *
 * @example
 *   var x = 123
 *   defer_return(x)
 *   // => 234
 *   defer_return(42)
 *   // => 153
 *
 */
function defer_return (x)
{
    var self = this;
    setTimeout(function () {
        // console.log('self.return for defer_return!!!!!!!!!', self.return)
        return self.return(x + 111)
    }, 1000);
}

/**
 * Test of RegExp values
 *
 * @example
 *   regexp_value()
 *   // => {'pid': /\d+/}
 *
 */
function regexp_value ()
{
    return {'pid': process.pid}
}

/**
 * Test of null
 *
 * @example
 *   null_test()
 *   // => null
 *
 */
function null_test ()
{
    return null
}

/**
 * Test of undefined
 *
 * @example
 *   undefined_test()
 *   // => undefined
 *
 */
function undefined_test () {}

/**
 * Test of lambda in doctest
 *
 * @example
 *   (function () { let obj = lambda_test(); delete obj.deleteItProperty; return obj } )()
 *   // => {id: 1, name: 'test'}
 *
 */
function lambda_test () {
    return {id: 1, name: 'test', deleteItProperty: 'deleteItProperty'}
}

/**
 * Mask and simple obj
 *
 * @example
 *   smpObj()
 *   // => [..., {a: 1}]
 */
function smpObj () {
    return [1, 2, {a: 1}]
}

/**
 * Mask and complex obj
 *
 * @example
 *   complexObj()
 *   // => [1, 2, 3, ...[], {a: 1, b: 2, c: /\d+/, ...{}, z: 'Hello ...'}, 7, 8, ...]
 */
function complexObj () {
    return [
        1, 2, 3, 4, 5, 6, {
            a: 1,
            b: 2,
            c: 3,
            d: 4, e: 5,
            z: 'Hello World'
        },
        7, 8, 9, 10
    ]
}

/**
 * Check GET request
 *
 * @example
 *   getRequest('get')
 *   // => "Hello, get!"
 */
function getRequest (name) {
    return request.get('https://json.smappi.org/adw0rd/example/greeting', {name: name}).json()
}

/**
 * Check POST request
 *
 * @example
 *   postRequest('post')
 *   // => "Hello, post!"
 */
function postRequest (name) {
    return request.post('https://json.smappi.org/adw0rd/example/greeting', {name: name}).json()
}

/**
 * Check correct User-Agent
 *
 * @example
 *   checkUserAgent()
 *   // => {'user_agent': '...Mozilla...AppleWebKit...Chrome...Safari...', ...{}}
 */
function checkUserAgent () {
    return request.get('https://tools.adw0rd.com/browsinfo/?json').json()
}

/**
 * Check withProxy method
 *
 * @example
 *   withProxy(2)
 *   // => 2
 */
function withProxy (numbers) {
    var hosts = request.get(
        'https://json.smappi.org/adw0rd/smappi-proxy-test/withProxy',
        {numbers: numbers}
    ).json();
    return hosts.length;
}

/**
 * Check next proxy (list of unique proxies)
 *
 * @example
 *   nextProxy(2)
 *   // => 2
 */
function nextProxy (numbers) {
    var hosts = request.get(
        'https://json.smappi.org/adw0rd/smappi-proxy-test/next',
        {numbers: numbers}
    ).json();
    hosts = hosts.filter((val, idx, self) => self.indexOf(val) === idx);  // UNIQUE
    return hosts.length;
}

// function skipProxy () {
//     // next + skip
// }

// function followLocation () {
//     // 301, 302 redirects
// }

// function followLocationDisabled () {
//     //
// }

// function checkDnsLocation () {
//     //
// }

/**
 * Check Captcha Request (multipart/form-data with send binary file)
 *
 * @example
 *   captchaSolve()
 *   // => {state: true, solve: '33216'}
 */
function captchaSolve () {
    let captchaContent = require('fs').readFileSync(
        require('path').join(__dirname, 'captcha.png')
    );
    let captchaResult = captcha(captchaContent).resolve();
    console.log('captchaResult', captchaResult)
    return captchaResult;
}
<commit_msg>Small cleanup tests<commit_after>const { request, proxy, captcha } = require('smappi-cl');

/**
 * Test of simple list
 *
 * @example
 *   list_simple()
 *   // => ['str_1', 'str_2']
 *
 */
function list_simple () {
    return ['str_1', 'str_2'];
}

/**
 * Test of list with regexp
 *
 * @example
 *   list_regexp()
 *   // => [/str_\d+/, 'str_2']
 *
 */
function list_regexp () {
    return ['str_123', 'str_2'];
}

/**
 * Test of list
 *
 * @example
 *   list_mask()
 *   // => ['str_1', ..., 'str_5']
 *   list_mask()
 *   // => ['str_1', ...[], 'str_4', 'str_5']
 *
 */
function list_mask () {
    return ['str_1', 'str_2', 'str_3', 'str_4', 'str_5'];
}

/**
 * Test of list twice
 *
 * @example
 *   list_twice()
 *   // => ['str_1', ..., 'str_4', ..., 123]
 *   list_twice()
 *   // => ['str_1', ...[], 'str_4', ..., 123]
 *   list_twice()
 *   // => ['str_1', ...[], 'str_4', ..., ..., 55, 123]
 *
 */
function list_twice () {
    return ['str_1', 'str_2', 'str_3', 'str_4', 444, 55, 123];
}

/**
 * Test of obj
 *
 * @example
 *   obj()
 *   // => {'a': 1, 'b': 2, ...{}, 'e': 5}
 *
 */
function obj () {
    return {'a': 1, 'b': 2, 'e': 5}
}

/**
 * Test of obj
 *
 * @example
 *   obj_nested()
 *   // => {one: {...{}}, ...{}}
 *
 */
function obj_nested () {
    return {one: {a: 1, b: 2}, two: 2}
}

/**
 * Test of str
 *
 * @example
 *   str()
 *   // => 'abc...(x|y).+z'
 *
 */
function str () {
    return 'abcdefxyz'
}

/**
 * Test of short str
 *
 * @example
 *   str_short()
 *   // => 'abc...xyz'
 *
 */
function str_short ()
{
    return 'abcxyz'
}

/**
 * Test list of strings
 *
 * @example
 *   list_strings()
 *   // => ['a1', 'a2', 'b...', ...]
 *
 */
function list_strings ()
{
    return ['a1', 'a2', 'b3', 'c4']
}

/**
 * Test of deferred return with mixed types
 *
 * @example
 *   defer_return_mixed()
 *   // => [{a: 1}, {...{}, b: [..., 42, ...]}, ...]
 *
 */
function defer_return_mixed ()
{
    setTimeout(() => {
        this.return([{a: 1}, {a: 2, b: [41, 42, 43]}, {a: 3}]);
    }, 2000);
}

/**
 * Test of mixed types
 *
 * @example
 *   mixed()
 *   // => [{a: 1}, {...{}, b: [..., 42, ...]}, ...]
 *
 */
function mixed ()
{
    return [{a: 1}, {a: 2, b: [41, 42, 43]}, {a: 3}]
}

/**
 * Test of deferred return
 *
 * @example
 *   var x = 123
 *   defer_return(x)
 *   // => 234
 *   defer_return(42)
 *   // => 153
 *
 */
function defer_return (x)
{
    var self = this;
    setTimeout(function () {
        return self.return(x + 111)
    }, 1000);
}

/**
 * Test of RegExp values
 *
 * @example
 *   regexp_value()
 *   // => {'pid': /\d+/}
 *
 */
function regexp_value ()
{
    return {'pid': process.pid}
}

/**
 * Test of null
 *
 * @example
 *   null_test()
 *   // => null
 *
 */
function null_test ()
{
    return null
}

/**
 * Test of undefined
 *
 * @example
 *   undefined_test()
 *   // => undefined
 *
 */
function undefined_test () {}

/**
 * Test of lambda in doctest
 *
 * @example
 *   (function () { let obj = lambda_test(); delete obj.deleteItProperty; return obj } )()
 *   // => {id: 1, name: 'test'}
 *
 */
function lambda_test () {
    return {id: 1, name: 'test', deleteItProperty: 'deleteItProperty'}
}

/**
 * Mask and simple obj
 *
 * @example
 *   smpObj()
 *   // => [..., {a: 1}]
 */
function smpObj () {
    return [1, 2, {a: 1}]
}

/**
 * Mask and complex obj
 *
 * @example
 *   complexObj()
 *   // => [1, 2, 3, ...[], {a: 1, b: 2, c: /\d+/, ...{}, z: 'Hello ...'}, 7, 8, ...]
 */
function complexObj () {
    return [
        1, 2, 3, 4, 5, 6, {
            a: 1,
            b: 2,
            c: 3,
            d: 4, e: 5,
            z: 'Hello World'
        },
        7, 8, 9, 10
    ]
}

/**
 * Check GET request
 *
 * @example
 *   getRequest('get')
 *   // => "Hello, get!"
 */
function getRequest (name) {
    return request.get('https://json.smappi.org/adw0rd/example/greeting', {name: name}).json()
}

/**
 * Check POST request
 *
 * @example
 *   postRequest('post')
 *   // => "Hello, post!"
 */
function postRequest (name) {
    return request.post('https://json.smappi.org/adw0rd/example/greeting', {name: name}).json()
}

/**
 * Check correct User-Agent
 *
 * @example
 *   checkUserAgent()
 *   // => {'user_agent': '...Mozilla...AppleWebKit...Chrome...Safari...', ...{}}
 */
function checkUserAgent () {
    return request.get('https://tools.adw0rd.com/browsinfo/?json').json()
}

/**
 * Check withProxy method
 *
 * @example
 *   withProxy(2)
 *   // => 2
 */
function withProxy (numbers) {
    var hosts = request.get(
        'https://json.smappi.org/adw0rd/smappi-proxy-test/withProxy',
        {numbers: numbers}
    ).json();
    return hosts.length;
}

/**
 * Check next proxy (list of unique proxies)
 *
 * @example
 *   nextProxy(2)
 *   // => 2
 */
function nextProxy (numbers) {
    var hosts = request.get(
        'https://json.smappi.org/adw0rd/smappi-proxy-test/next',
        {numbers: numbers}
    ).json();
    hosts = hosts.filter((val, idx, self) => self.indexOf(val) === idx);  // UNIQUE
    return hosts.length;
}

// function skipProxy () {
//     // next + skip
// }

// function followLocation () {
//     // 301, 302 redirects
// }

// function followLocationDisabled () {
//     //
// }

// function checkDnsLocation () {
//     //
// }

/**
 * Check Captcha Request (multipart/form-data with send binary file)
 *
 * @example
 *   captchaSolve()
 *   // => {state: true, solve: '33216'}
 */
function captchaSolve () {
    let captchaContent = require('fs').readFileSync(
        require('path').join(__dirname, 'captcha.png')
    );
    let captchaResult = captcha(captchaContent).resolve();
    console.log('captchaResult', captchaResult)
    return captchaResult;
}
<|endoftext|>


<commit_before>'use strict';
const { isMainThread, parentPort } = require('worker_threads');
const Redis = require('../../service/cache/redis');

class IDCardRenewer {
  constructor() {
    this.disposed = false;
    this.redis = null;
    this.refreshTimer = null;
    this.nodeIdKey = null;
    this.refreshDelay = 2000;

    this.parentPort = parentPort;
  }

  async init(config) {
    this.disposed = false;
    this.nodeIdKey = config.nodeIdKey;
    this.refreshDelay = config.refreshDelay;

    /**
    * Since we do not have access to the Kuzzle Context we can't use kuzzle.ask('core:cache:internal:*',...),
    * so we need to have an instance of Redis similar to the one used in the Cache Engine
    */
    try {
      await this.initRedis(config.redis);
    }
    catch (error) {
      this.parentPort.postMessage({
        error: `Failed to connect to redis, could not refresh ID card: ${error.message}`
      });
      return;
    }

    /**
     * Early renew, otherwise we need to wait the refreshDelay before the first ID Card renewal
     * which could be enough to make the ID Card expire.
     */
    await this.renewIDCard();
    this.refreshTimer = setInterval(
      this.renewIDCard.bind(this),
      this.refreshDelay);
  }

  async initRedis({ config, name } = {}) {
    this.redis = new Redis(config, name);
    await this.redis.init();
  }

  async renewIDCard() {
    try {
      if (!this.disposed) {
        const refreshed = await this.redis.commands.pexpire(
          this.nodeIdKey,
          this.refreshDelay * 1.5);
        // Unable to refresh the key in time before it expires
        // => this node is too slow, we need to remove it from the cluster
        if (refreshed === 0) {
          await this.dispose();
          this.parentPort.postMessage({ error: 'Node too slow: ID card expired' });
        }
      }
    }
    catch (error) {
      await this.dispose();
      this.parentPort.postMessage({
        error: `Failed to refresh ID Card: ${error.message}`
      });
    }
  }

  async dispose() {
    if (!this.disposed) {
      this.disposed = true;
      clearInterval(this.refreshTimer);
      this.refreshTimer = null;
      try {
        await this.redis.commands.del(this.nodeIdKey);
      }
      catch (error) {
        // eslint-disable-next-line no-console
        console.error(`Could not delete key ${this.nodeIdKey} from redis: ${error.message}`);
      }
    }
  }
}

if (!isMainThread) {
  const idCardRenewer = new IDCardRenewer();
  parentPort.on('message', async (message) => {
    if (message.action === 'start') {
      // Simulate basic global Kuzzle Context
      global.kuzzle = { ...message.kuzzle };
      global.kuzzle.log = {
        debug: console.debug, // eslint-disable-line no-console
        error: console.error, // eslint-disable-line no-console
        info: console.info, // eslint-disable-line no-console
        warn: console.warn, // eslint-disable-line no-console
      };

      return idCardRenewer.init(message).catch();
    } else if (message.action === 'dispose') {
      return idCardRenewer.dispose().catch();
    }
  });
}

module.exports = IDCardRenewer;
<commit_msg>slight enhancements<commit_after>'use strict';
const { isMainThread, parentPort } = require('worker_threads');
const Redis = require('../../service/cache/redis');

class IDCardRenewer {
  constructor() {
    this.disposed = false;
    this.redis = null;
    this.refreshTimer = null;
    this.nodeIdKey = null;
    this.refreshDelay = 2000;

    this.parentPort = parentPort;
  }

  async init(config) {
    this.disposed = false;
    this.nodeIdKey = config.nodeIdKey;
    this.refreshDelay = config.refreshDelay;

    /**
    * Since we do not have access to the Kuzzle Context we can't use kuzzle.ask('core:cache:internal:*',...),
    * so we need to have an instance of Redis similar to the one used in the Cache Engine
    */
    try {
      await this.initRedis(config.redis);
    }
    catch (error) {
      this.parentPort.postMessage({
        error: `Failed to connect to redis, could not refresh ID card: ${error.message}`
      });
      return;
    }

    /**
     * Early renew, otherwise we need to wait the refreshDelay before the first ID Card renewal
     * which could be enough to make the ID Card expire.
     */
    await this.renewIDCard();
    if (!this.disposeed) {
      this.refreshTimer = setInterval(
        this.renewIDCard.bind(this),
        this.refreshDelay);
    }
  }

  async initRedis({ config, name } = {}) {
    this.redis = new Redis(config, name);
    await this.redis.init();
  }

  async renewIDCard() {
    try {
      if (!this.disposed) {
        const refreshed = await this.redis.commands.pexpire(
          this.nodeIdKey,
          this.refreshDelay * 1.5);
        // Unable to refresh the key in time before it expires
        // => this node is too slow, we need to remove it from the cluster
        if (refreshed === 0) {
          await this.dispose();
          this.parentPort.postMessage({ error: 'Node too slow: ID card expired' });
        }
      }
    }
    catch (error) {
      await this.dispose();
      this.parentPort.postMessage({
        error: `Failed to refresh ID Card: ${error.message}`
      });
    }
  }

  async dispose() {
    if (!this.disposed) {
      this.disposed = true;
      clearInterval(this.refreshTimer);
      this.refreshTimer = null;
      try {
        await this.redis.commands.del(this.nodeIdKey);
      }
      catch (error) {
        // eslint-disable-next-line no-console
        console.error(`Could not delete key ${this.nodeIdKey} from redis: ${error.message}`);
      }
    }
  }
}

if (!isMainThread) {
  const idCardRenewer = new IDCardRenewer();
  parentPort.on('message', async (message) => {
    if (message.action === 'start') {
      // Simulate basic global Kuzzle Context
      global.kuzzle = { ...message.kuzzle };
      global.kuzzle.log = {
        debug: console.debug, // eslint-disable-line no-console
        error: console.error, // eslint-disable-line no-console
        info: console.info, // eslint-disable-line no-console
        warn: console.warn, // eslint-disable-line no-console
      };
      // Should never throw
      await idCardRenewer.init(message);
    } else if (message.action === 'dispose') {
      // Should never throw
      await idCardRenewer.dispose();
    }
  });
}

module.exports = IDCardRenewer;
<|endoftext|>