<commit_before>/**
= SettingsGUI =

The user interface for (a number of) settings.
*/

var Fontana = window.Fontana || {};
Fontana.config = Fontana.config || {};

Fontana.config.SettingsGUI = (function ($) {
    var SettingsGUI;

    SettingsGUI = function (container, settings) {
        var self = this;
        this.container = container;
        this.settings = settings;
        this.fields = [ 'twitter_search', 'effect', 'message_animate_interval',
                        'custom_css', 'font_face', 'text_color',
                        'special_color', 'bg_color',
                        'bg_image', 'box_bg',
                        'embed', 'preset'];
    };

    /**
     * Load settings from the url
     */
    SettingsGUI.prototype.loadSettingsFromUrl = function () {
        var params, i, pair, key, value;
        params = window.location.search.substring(1).replace(/\+/g, ' ').split('&');
        for (i = 0; i < params.length; i++) {
            pair = params[i].split('=')
            key = decodeURIComponent(pair[0]);
            value = decodeURIComponent(pair[1]);
            if ($.inArray(key, this.fields) > -1) {
                this.settings.set(key, value);
            }
        }

        if(this.settings.get('embed') == 'true') {
            $("#header, #footer").hide();
        }
    };

    /**
     * Generate the url for the current settings
     */
    SettingsGUI.prototype.generateSettingsUrl = function (query) {
        var url;
        query = query || {'preset': 'true'};
        url = location.protocol + '//' + location.host + location.pathname;
        $.each(this.fields, function (i, key) {
            var value = $('#' + key).val();
            if (value) {
                query[key] = value;
            }
        });
        return url + '?' + $.param(query);
    };

    SettingsGUI.prototype.generateEmbedCode = function () {
        // first generate the url
        var url = this.generateSettingsUrl({'embed': 'true'}) + '&utm_source=site&utm_medium=embed&utm_campaign=twitterfontana';
        return 'Powered by Twitter Fontana';
    };

    /**
     * Handle a value change in a settings form by updating
     * the settings object and updating the settings url field.
     */
    SettingsGUI.prototype.handleFormChange = function (el) {
        var val = $(el).val();

        switch(el.name) {
            // minimal effect duration
            case 'message_animate_interval':
                el.value = Math.max(val, 750);
                break;
        }

        this.settings.set(el.name, val);
        this.updatePresetUrl();
        this.updateEmbedCode();
    };

    /**
     * Load and initialize the settings panel
     */
    SettingsGUI.prototype.draw = function () {
        var self = this;

        this.container.empty();

        $.get('partials/settings.html', function (html) {
            // Hide the settings panel in case of preset
            if(self.settings.get('embed') != 'true') {
                $("body").addClass("settings");
            }

            self.container[0].innerHTML =html;

            if(self.settings.get('embed') == 'true' || self.settings.get('preset') == 'true') {
                self.toggle(false);
            }

            // Prefill the inputs with the current settings
            $.each(self.fields, function (i, key) {
                var field = $('#' + key);
                if (field) {
                    field.val(self.settings.get(key));
                }
            });
            self.updatePresetUrl();
            self.updateEmbedCode();

            // Listen for change events on the inputs
            $(':input', self.container).change(function () {
                self.handleFormChange.call(self, this);
            });

            // Listen to submit events on the forms
            $('form', self.container).submit(function (e) {
                e.preventDefault();
                $(':input', this).each(function () {
                    self.handleFormChange.call(self, this);
                });
            });

            // Show/Hide advanced search operators on click
            $('.search-operators', self.container).click(function (e) {
                e.preventDefault();
                $('#search_operators').slideToggle();
                return false;
            });

            // Toggle the embed html/preset url textareas on label click
            $('label', $('#embedForm')).click(function (e) {
                $('label', $('#embedForm')).removeClass('active')
                $(this).addClass('active');
                $('#embed_html, #preset_url').hide();
                $('#' + $(this).attr('for')).show();
            });

            // Select the settings url on click
            $('#embed_html, #preset_url').click(function (e) {
                $(this).select();
            });

            // Initialize the color pickers
            $('.color', self.container).each(function () {
                var input = $(this);
                var pickerElement = $('

').insertAfter($(this));
                var swatch = $('

').insertAfter($(this));
                var picker = $.farbtastic(pickerElement);

                pickerElement.hide();
                swatch.click(function () { input.focus(); });
                swatch.css('background-color', input.val());
                picker.setColor(input.val());

                input.focus(function () {
                    pickerElement.fadeIn('fast');
                    picker.linkTo(function (color) {
                        swatch.css('background-color', color);
                        input.val(color).change();
                    });
                });

                input.blur(function () {
                    pickerElement.fadeOut('fast');
                    picker.setColor(input.val());
                    swatch.css('background-color', input.val());
                });

                input.keyup(function () {
                    picker.setColor(input.val());
                    swatch.css('background-color', input.val());
                });
            });

            // enable the tweet button
            try {
                twttr.widgets.load();
            } catch (e) { }
        });
    };

    /**
    * Update preset url field
    */
    SettingsGUI.prototype.updatePresetUrl = function() {
         $('#preset_url').val(this.generateSettingsUrl() + '&utm_source=site&utm_medium=preset&utm_campaign=twitterfontana');
         $('#share-on-twitter').attr('data-url', this.generateSettingsUrl() + '&utm_source=twitter&utm_medium=preset&utm_campaign=twitterfontana');
    };

    /**
    * Update embed code field
    */
    SettingsGUI.prototype.updateEmbedCode = function() {
         $('#embed_html').val(this.generateEmbedCode());
    };

    /**
     * Show/Hide the settings panel.
     */
    SettingsGUI.prototype.toggle = function(show) {
        $('body').toggleClass('settings', show);
        $(window).trigger('resize');
    };

    return SettingsGUI;
}(window.jQuery));
<commit_msg>Added callback option to settings-gui draw<commit_after>/**
= SettingsGUI =

The user interface for (a number of) settings.
*/

var Fontana = window.Fontana || {};
Fontana.config = Fontana.config || {};

Fontana.config.SettingsGUI = (function ($) {
    var SettingsGUI;

    SettingsGUI = function (container, settings) {
        var self = this;
        this.container = container;
        this.settings = settings;
        this.fields = [ 'twitter_search', 'effect', 'message_animate_interval',
                        'custom_css', 'font_face', 'text_color',
                        'special_color', 'bg_color',
                        'bg_image', 'box_bg',
                        'embed', 'preset'];
    };

    /**
     * Load settings from the url
     */
    SettingsGUI.prototype.loadSettingsFromUrl = function () {
        var params, i, pair, key, value;
        params = window.location.search.substring(1).replace(/\+/g, ' ').split('&');
        for (i = 0; i < params.length; i++) {
            pair = params[i].split('=')
            key = decodeURIComponent(pair[0]);
            value = decodeURIComponent(pair[1]);
            if ($.inArray(key, this.fields) > -1) {
                this.settings.set(key, value);
            }
        }

        if(this.settings.get('embed') == 'true') {
            $("#header, #footer").hide();
        }
    };

    /**
     * Generate the url for the current settings
     */
    SettingsGUI.prototype.generateSettingsUrl = function (query) {
        var url;
        query = query || {'preset': 'true'};
        url = location.protocol + '//' + location.host + location.pathname;
        $.each(this.fields, function (i, key) {
            var value = $('#' + key).val();
            if (value) {
                query[key] = value;
            }
        });
        return url + '?' + $.param(query);
    };

    SettingsGUI.prototype.generateEmbedCode = function () {
        // first generate the url
        var url = this.generateSettingsUrl({'embed': 'true'}) + '&utm_source=site&utm_medium=embed&utm_campaign=twitterfontana';
        return 'Powered by Twitter Fontana';
    };

    /**
     * Handle a value change in a settings form by updating
     * the settings object and updating the settings url field.
     */
    SettingsGUI.prototype.handleFormChange = function (el) {
        var val = $(el).val();

        switch(el.name) {
            // minimal effect duration
            case 'message_animate_interval':
                el.value = Math.max(val, 750);
                break;
        }

        this.settings.set(el.name, val);
        this.updatePresetUrl();
        this.updateEmbedCode();
    };

    /**
     * Load and initialize the settings panel
     */
    SettingsGUI.prototype.draw = function (callback) {
        var self = this;

        this.container.empty();

        $.get('partials/settings.html', function (html) {
            // Hide the settings panel in case of preset
            if(self.settings.get('embed') != 'true') {
                $("body").addClass("settings");
            }

            self.container[0].innerHTML =html;

            if(self.settings.get('embed') == 'true' || self.settings.get('preset') == 'true') {
                self.toggle(false);
            }

            // Prefill the inputs with the current settings
            $.each(self.fields, function (i, key) {
                var field = $('#' + key);
                if (field) {
                    field.val(self.settings.get(key));
                }
            });
            self.updatePresetUrl();
            self.updateEmbedCode();

            // Listen for change events on the inputs
            $(':input', self.container).change(function () {
                self.handleFormChange.call(self, this);
            });

            // Listen to submit events on the forms
            $('form', self.container).submit(function (e) {
                e.preventDefault();
                $(':input', this).each(function () {
                    self.handleFormChange.call(self, this);
                });
            });

            // Show/Hide advanced search operators on click
            $('.search-operators', self.container).click(function (e) {
                e.preventDefault();
                $('#search_operators').slideToggle();
                return false;
            });

            // Toggle the embed html/preset url textareas on label click
            $('label', $('#embedForm')).click(function (e) {
                $('label', $('#embedForm')).removeClass('active')
                $(this).addClass('active');
                $('#embed_html, #preset_url').hide();
                $('#' + $(this).attr('for')).show();
            });

            // Select the settings url on click
            $('#embed_html, #preset_url').click(function (e) {
                $(this).select();
            });

            // Initialize the color pickers
            $('.color', self.container).each(function () {
                var input = $(this);
                var pickerElement = $('

').insertAfter($(this));
                var swatch = $('

').insertAfter($(this));
                var picker = $.farbtastic(pickerElement);

                pickerElement.hide();
                swatch.click(function () { input.focus(); });
                swatch.css('background-color', input.val());
                picker.setColor(input.val());

                input.focus(function () {
                    pickerElement.fadeIn('fast');
                    picker.linkTo(function (color) {
                        swatch.css('background-color', color);
                        input.val(color).change();
                    });
                });

                input.blur(function () {
                    pickerElement.fadeOut('fast');
                    picker.setColor(input.val());
                    swatch.css('background-color', input.val());
                });

                input.keyup(function () {
                    picker.setColor(input.val());
                    swatch.css('background-color', input.val());
                });
            });

            // enable the tweet button
            try {
                twttr.widgets.load();
            } catch (e) { }

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

    /**
    * Update preset url field
    */
    SettingsGUI.prototype.updatePresetUrl = function() {
         $('#preset_url').val(this.generateSettingsUrl() + '&utm_source=site&utm_medium=preset&utm_campaign=twitterfontana');
         $('#share-on-twitter').attr('data-url', this.generateSettingsUrl() + '&utm_source=twitter&utm_medium=preset&utm_campaign=twitterfontana');
    };

    /**
    * Update embed code field
    */
    SettingsGUI.prototype.updateEmbedCode = function() {
         $('#embed_html').val(this.generateEmbedCode());
    };

    /**
     * Show/Hide the settings panel.
     */
    SettingsGUI.prototype.toggle = function(show) {
        $('body').toggleClass('settings', show);
        $(window).trigger('resize');
    };

    return SettingsGUI;
}(window.jQuery));
<|endoftext|>


<commit_before>/*
 * This script runs on the CLASS PAGE of WeBWorK.  It colors in the due
 * dates depending on how much time is remaining
 */
// TODO: update timer colors when countdowns reach certain threshholds?
// TODO: rewrite timer creation code to be more portable?
var TIME_WARNING = 3600*24*2; // 2 days
var TIME_FLASHING = 3600*12; // 12 hours

$(document).ready(function() {

	// add colored formatting to some dates as a notification
    var curdate = Date.now();
    $(".problem_set_table tr td:nth-child(3)").each(function() {
        var text = $(this).text();

        // search for a date
        if (text.search(/\d{2}\/\d{2}\/\d{4}/) !== -1) {
            // give it position: relative so that the timer sticks to it correctly
            $(this).css("position", "relative");

            // found a valid date!
            // get the components
            var regex = /(\d{2})\/(\d{2})\/(\d{4}).+(\d{2}):(\d{2})(\w{2})/
            var match = regex.exec(text);

            // construct a Date object using the matches
            var duedate = new Date(parseInt(match[3], 10), parseInt(match[1]-1, 10), parseInt(match[2], 10), ((match[6] === "pm") ? parseInt(match[4])+12 : parseInt(match[4])), parseInt(match[5]));

            var remainingTime = Math.floor((duedate - curdate) / 1000); // in seconds

            if (remainingTime > 0) {
                // only apply the below if the assignment is open
                if (text.substr(0,4).toLowerCase() === "open") {

                    // add a timer <span>, which has the duedate countdown saved to it (measured in seconds)
                    // also save the exact time the assignment is due, in seconds past epoch
                    $("<span/>", {"class": "timer-hidden"}).data("seconds", remainingTime).data("duedate", Math.floor(duedate/1000)).appendTo($(this));
                    // only style if the assignment has not been completed
                    if ($(this).children(".completionPercentage:first-child").text() === "100") {

                        // completed assignments are colored green and override the other styling formats
                        $(this).siblings().andSelf().addClass("WWsuccess");

                    } else {
                        // style the due date text depending on how much time is left
                        if (remainingTime < TIME_FLASHING) { // 12 hours left
                            $(this).parent().addClass("WWflashing");
                        } else if (remainingTime < TIME_WARNING) {  // 48 hours left
                            $(this).parent().addClass("WWwarning");
                        } else {
                            $(this).parent().addClass("WWnormal");
                        }
                    }
                } else {
                    $(this).parent().addClass("disabled");
                }

                incrementTimer(this);
            }
        }

    });

    /*
     * Since the timer doesn't execute when the tab is not visible,
     * the page detects when it regains focus and updates the timer
     * so that the countdowns remain accurate
     */
    document.addEventListener("visibilitychange", function() {
      if (!document.hidden) { // the tab with WeBWorK in it has regained focus and is no longer hidden
        // update timers
        var curtime = Math.floor(Date.now() / 1000); // the new current time, in seconds past epoch
        $(".timer-hidden").each(function() {
          $(this).data("seconds", $(this).data("duedate") - curtime); // calculate the new amount of time remaining
        });
      }
    }, false);

    // update times
    window.setInterval(function(){
        // get the corresponding table rows
        $(".timer-hidden").each(function() {
            incrementTimer(this);
        });
    }, 1000);

    /*
     * Sets a timer to the correct time
     * used when timer is first created and
     * when the tab is focused on again
     * after being hidden
     */
    function setTimer(element) {
      console.log(element.parent());
    }

    /*
     * Displays the time remaining before an open assignment is due
     */
    function incrementTimer(element) {
            // check if it has a timer attached
            var time = $(element).data("seconds");
            if (typeof time === "number") {
                time--;

                if (time < 0) {
                    // delete the timer, it is no longer needed
                    $(element).remove();
                } else {
                    $(element).data("seconds", time);
                    if (time > 3600*24) {
                        // more than 1 day remaining
                        var days = Math.floor(time/3600/24);
                        $(element).text(days + (days == 1 ? " day" : " days"));
                    } else {
                        // less than one day remaining
                        var hours = "00"+Math.floor(time/3600);
                        time -= hours*3600;
                        var minutes = "00"+Math.floor(time/60);
                        time -= minutes*60;
                        var seconds = "00"+Math.floor(time);
                        // display the remaining time (in the form hh : mm : ss)
                        $(element).text(hours.substr(hours.length-2) + " : " + minutes.substr(minutes.length-2) + " : " + seconds.substr(seconds.length-2));
                    }
                }
            }
    }

});
<commit_msg>Added countdown to time before an assignment opens,<commit_after>/*
 * This script runs on the CLASS PAGE of WeBWorK.  It colors in the due
 * dates depending on how much time is remaining
 */
// TODO: update timer colors when countdowns reach certain threshholds?
// TODO: rewrite timer creation code to be more portable?
var TIME_WARNING = 3600*24*2; // 2 days
var TIME_FLASHING = 3600*12; // 12 hours

$(document).ready(function() {

	// add colored formatting to some dates as a notification
    var curdate = Date.now();
    $(".problem_set_table tr td:nth-child(3)").each(function() {
        var text = $(this).text();

        // search for a date
        if (text.search(/\d{2}\/\d{2}\/\d{4}/) !== -1) {
            // give it position: relative so that the timer sticks to it correctly
            $(this).css("position", "relative");

            // found a valid date!
            // get the components
            var regex = /(\d{2})\/(\d{2})\/(\d{4}).+(\d{2}):(\d{2})(\w{2})/
            var match = regex.exec(text);

            // construct a Date object using the matches
            var duedate = new Date(parseInt(match[3], 10), parseInt(match[1]-1, 10), parseInt(match[2], 10), ((match[6] === "pm") ? parseInt(match[4])+12 : parseInt(match[4])), parseInt(match[5]));

            var remainingTime = Math.floor((duedate - curdate) / 1000); // in seconds

            if (remainingTime > 0) {
                // only apply the below if the assignment is open

                if (text.substr(0,4).toLowerCase() === "open") {

                    // add a timer <span>, which has the duedate countdown saved to it (measured in seconds)
                    // also save the exact time the assignment is due, in seconds past epoch
                    $("<span/>", {"class": "timer-hidden"}).data("seconds", remainingTime).data("duedate", Math.floor(duedate/1000)).appendTo($(this));
                    // only style if the assignment has not been completed
                    if ($(this).children(".completionPercentage:first-child").text() === "100") {

                        // completed assignments are colored green and override the other styling formats
                        $(this).siblings().andSelf().addClass("WWsuccess");

                    } else {
                        // style the due date text depending on how much time is left
                        if (remainingTime < TIME_FLASHING) { // 12 hours left
                            $(this).parent().addClass("WWflashing");
                        } else if (remainingTime < TIME_WARNING) {  // 48 hours left
                            $(this).parent().addClass("WWwarning");
                        } else {
                            $(this).parent().addClass("WWnormal");
                        }
                    }
                } else {
                    if (text.substr(0,12).toLowerCase() === "will open on") {

                         $("<span/>", {"class": "timer-hidden"}).data("seconds", remainingTime).data("duedate", Math.floor(duedate/1000)).appendTo($(this));

                        $(this).parent().addClass("WWnormal");
                    } else {
                    $(this).parent().addClass("disabled");
                }
                }

                incrementTimer(this);
            }
        }

    });

    /*
     * Since the timer doesn't execute when the tab is not visible,
     * the page detects when it regains focus and updates the timer
     * so that the countdowns remain accurate
     */
    document.addEventListener("visibilitychange", function() {
      if (!document.hidden) { // the tab with WeBWorK in it has regained focus and is no longer hidden
        // update timers
        var curtime = Math.floor(Date.now() / 1000); // the new current time, in seconds past epoch
        $(".timer-hidden").each(function() {
          $(this).data("seconds", $(this).data("duedate") - curtime); // calculate the new amount of time remaining
        });
      }
    }, false);

    // update times
    window.setInterval(function(){
        // get the corresponding table rows
        $(".timer-hidden").each(function() {
            incrementTimer(this);
        });
    }, 1000);

    /*
     * Sets a timer to the correct time
     * used when timer is first created and
     * when the tab is focused on again
     * after being hidden
     */
    function setTimer(element) {
      console.log(element.parent());
    }

    /*
     * Displays the time remaining before an open assignment is due
     */
    function incrementTimer(element) {
            // check if it has a timer attached
            var time = $(element).data("seconds");
            if (typeof time === "number") {
                time--;

                if (time < 0) {
                    // delete the timer, it is no longer needed
                    $(element).remove();
                } else {
                    $(element).data("seconds", time);
                    if (time > 3600*24) {
                        // more than 1 day remaining
                        var days = Math.floor(time/3600/24);
                        $(element).text(days + (days == 1 ? " day" : " days"));
                    } else {
                        // less than one day remaining
                        var hours = "00"+Math.floor(time/3600);
                        time -= hours*3600;
                        var minutes = "00"+Math.floor(time/60);
                        time -= minutes*60;
                        var seconds = "00"+Math.floor(time);
                        // display the remaining time (in the form hh : mm : ss)
                        $(element).text(hours.substr(hours.length-2) + " : " + minutes.substr(minutes.length-2) + " : " + seconds.substr(seconds.length-2));
                    }
                }
            }
    }

});
<|endoftext|>


<commit_before>import gulpif from 'gulp-if';
import sprity from 'sprity';
import svgSprite from 'gulp-svg-sprite';
import plumber from 'gulp-plumber';

module.exports = {
    fn: function (gulp, configuration) {
         gulp.src(configuration.image.path.src + '/**/*.svg')
            .pipe(plumber())
            .pipe(svgSprite(configuration.image.svgsprityconfig))
            .on('error',function (error) {
                console.log(error.toString());
            })
            .pipe(gulp.dest('.'))

         sprity.src(configuration.image.sprity)
            .on('error', function (err) {
                console.log(err.toString());
            })
            .pipe(gulpif('*.png', gulp.dest(configuration.image.path.dest),
                gulp.dest(configuration.style.path.src.css)))

        return;
    }
}

<commit_msg>[BUGFIX] Fix a small bug<commit_after>import gulpif from 'gulp-if';
import sprity from 'sprity';
import svgSprite from 'gulp-svg-sprite';
import plumber from 'gulp-plumber';

module.exports = {
    fn: function (gulp, configuration) {
         gulp.src(configuration.image.path.src + '/**/*.svg')
            .pipe(plumber())
            .pipe(svgSprite(configuration.image.svgsprityconfig))
            .on('error',function (error) {
                console.log(error.toString());
            })
            .pipe(gulp.dest('.'))

        return sprity.src(configuration.image.sprity)
            .on('error', function (err) {
                console.log(err.toString());
            })
            .pipe(gulpif('*.png', gulp.dest(configuration.image.path.dest),
                gulp.dest(configuration.style.path.src.css)))

    }
}

<|endoftext|>


<commit_before>import Point from './models/Point';
import {average} from './utils';

const AVG_DIST_THRESHOLD = 30; // bigger = more lenient
const LENGTH_RATIO_THRESHOLD = 0.5; // 0 to 1, bigger = more lenient
const COSINE_SIMILARITY_THRESHOLD = 0;  // -1 to 1, smaller = more lenient
const START_AND_END_DIST_THRESHOLD = 100; // bigger = more lenient

class StrokeMatcher {

  getMatchingStroke(rawPoints, strokes) {
    const points = this._stripDuplicates(rawPoints);
    if (points.length < 2) return null;

    let closestStroke = null;
    let bestAvgDist = 0;
    for (const stroke of strokes) {
      const avgDist = stroke.getAverageDistance(points);
      if (avgDist < bestAvgDist || !closestStroke) {
        closestStroke = stroke;
        bestAvgDist = avgDist;
      }
    }

    const withinDistThresh = bestAvgDist < AVG_DIST_THRESHOLD;
    const lengthRatio = this._getLength(points) / closestStroke.getLength();
    const withinLengthThresh = lengthRatio > (1 - LENGTH_RATIO_THRESHOLD) && lengthRatio < (1 + LENGTH_RATIO_THRESHOLD);
    const startAndEndMatch = this._startAndEndMatches(points, closestStroke);
    const directionMatches = this._directionMatches(points, closestStroke);

    if (withinDistThresh && withinLengthThresh && startAndEndMatch && directionMatches) {
      return closestStroke;
    }
    return null;
  }

  _startAndEndMatches(points, closestStroke) {
    const startingDist = Point.getDistance(closestStroke.getStartingPoint(), points[0]);
    const endingDist = Point.getDistance(closestStroke.getEndingPoint(), points[points.length - 1]);
    return startingDist < START_AND_END_DIST_THRESHOLD && endingDist < START_AND_END_DIST_THRESHOLD;
  }

  _directionMatches(points, stroke) {
    const edgeVectors = this._getEdgeVectors(points);
    const strokeVectors = stroke.getVectors();
    const similarities = [];
    for (const edgeVector of edgeVectors) {
      const strokeSimilarities = strokeVectors.map(strokeVector => Point.cosineSimilarity(strokeVector, edgeVector));
      const maxSimilarity = Math.max.apply(Math, strokeSimilarities);
      similarities.push(maxSimilarity);
    }
    const avgSimilarity = average(similarities);
    return avgSimilarity > COSINE_SIMILARITY_THRESHOLD;
  }

  _stripDuplicates(points) {
    if (points.length < 2) return points;
    const dedupedPoints = [points[0]];
    for (const point of points.slice(1)) {
      if (!point.equals(dedupedPoints[dedupedPoints.length - 1])) {
        dedupedPoints.push(point);
      }
    }
    return dedupedPoints;
  }

  _getLength(points) {
    let length = 0;
    let lastPoint = points[0];
    for (const point of points) {
      length += Point.getDistance(point, lastPoint);
      lastPoint = point;
    }
    return length;
  }

  // returns a list of the direction of all segments in the line connecting the points
  _getEdgeVectors(points) {
    const vectors = [];
    const lastPoint = points[0];
    for (const point of points.slice(1)) {
      vectors.push(point.subtract(lastPoint));
    }
    return vectors;
  }
}

export default StrokeMatcher;
<commit_msg>more lenient matching<commit_after>import Point from './models/Point';
import {average} from './utils';

const AVG_DIST_THRESHOLD = 50; // bigger = more lenient
const LENGTH_RATIO_THRESHOLD = 0.5; // 0 to 1, bigger = more lenient
const COSINE_SIMILARITY_THRESHOLD = 0;  // -1 to 1, smaller = more lenient
const START_AND_END_DIST_THRESHOLD = 100; // bigger = more lenient

class StrokeMatcher {

  getMatchingStroke(rawPoints, strokes) {
    const points = this._stripDuplicates(rawPoints);
    if (points.length < 2) return null;

    let closestStroke = null;
    let bestAvgDist = 0;
    for (const stroke of strokes) {
      const avgDist = stroke.getAverageDistance(points);
      if (avgDist < bestAvgDist || !closestStroke) {
        closestStroke = stroke;
        bestAvgDist = avgDist;
      }
    }

    const withinDistThresh = bestAvgDist < AVG_DIST_THRESHOLD;
    const lengthRatio = this._getLength(points) / closestStroke.getLength();
    const withinLengthThresh = lengthRatio > (1 - LENGTH_RATIO_THRESHOLD) && lengthRatio < (1 + LENGTH_RATIO_THRESHOLD);
    const startAndEndMatch = this._startAndEndMatches(points, closestStroke);
    const directionMatches = this._directionMatches(points, closestStroke);

    if (withinDistThresh && withinLengthThresh && startAndEndMatch && directionMatches) {
      return closestStroke;
    }
    return null;
  }

  _startAndEndMatches(points, closestStroke) {
    const startingDist = Point.getDistance(closestStroke.getStartingPoint(), points[0]);
    const endingDist = Point.getDistance(closestStroke.getEndingPoint(), points[points.length - 1]);
    return startingDist < START_AND_END_DIST_THRESHOLD && endingDist < START_AND_END_DIST_THRESHOLD;
  }

  _directionMatches(points, stroke) {
    const edgeVectors = this._getEdgeVectors(points);
    const strokeVectors = stroke.getVectors();
    const similarities = [];
    for (const edgeVector of edgeVectors) {
      const strokeSimilarities = strokeVectors.map(strokeVector => Point.cosineSimilarity(strokeVector, edgeVector));
      const maxSimilarity = Math.max.apply(Math, strokeSimilarities);
      similarities.push(maxSimilarity);
    }
    const avgSimilarity = average(similarities);
    return avgSimilarity > COSINE_SIMILARITY_THRESHOLD;
  }

  _stripDuplicates(points) {
    if (points.length < 2) return points;
    const dedupedPoints = [points[0]];
    for (const point of points.slice(1)) {
      if (!point.equals(dedupedPoints[dedupedPoints.length - 1])) {
        dedupedPoints.push(point);
      }
    }
    return dedupedPoints;
  }

  _getLength(points) {
    let length = 0;
    let lastPoint = points[0];
    for (const point of points) {
      length += Point.getDistance(point, lastPoint);
      lastPoint = point;
    }
    return length;
  }

  // returns a list of the direction of all segments in the line connecting the points
  _getEdgeVectors(points) {
    const vectors = [];
    const lastPoint = points[0];
    for (const point of points.slice(1)) {
      vectors.push(point.subtract(lastPoint));
    }
    return vectors;
  }
}

export default StrokeMatcher;
<|endoftext|>


<commit_before>function Start()
{
    Read_XML();
    setInterval(Read_XML, 60 * 100);
}

function Read_XML() {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            Scan(this);
        }
    };
    xhttp.open("GET", "scan.xml", true);
    xhttp.send();
}

//global variable array
var Computers = [];
var IPs = [];
var Scan_Time = "today";

function Scan(xml) {
    //the general xml handler
    var xmlDoc = xml.responseXML;

    //write down the scan time
    Scan_Time = xmlDoc.getElementsByTagName("nmaprun")[0].getAttribute("startstr");
    var Num_Hosts = xmlDoc.getElementsByTagName("host").length;
    var Computers_New = [];
    //looping throught the xml entries
    for (var i = 0; i < Num_Hosts; i++) {
        var Host_Element = xmlDoc.getElementsByTagName("host")[i];
        var IP_Address = Host_Element.getElementsByTagName("address")[0].getAttribute("addr");
        var Hostname_Element = Host_Element.getElementsByTagName("hostnames")[0].getElementsByTagName("hostname")[0];
        if (Hostname_Element) {
            var Hostname = Hostname_Element.getAttribute("name");
            var s = Hostname.split(".");
            Hostname = s[0];
        } else {
            var Hostname = "Unknown";
        }

        var Ports_Element = Host_Element.getElementsByTagName("ports")[0].getElementsByTagName("port");
        for (var j = 0; j < Ports_Element.length; j++) {
            var Port_ID = Ports_Element[j].getAttribute("portid");
            var Port_Status = Ports_Element[j].getElementsByTagName("state")[0].getAttribute("state");
            //console.log("Name: " + Hostname + " Portid: " + Port_ID + " status: " + Port_Status);
        }
        var SSH_State = Ports_Element[0].getElementsByTagName("state")[0].getAttribute("state");
        var HTTP_State = Ports_Element[1].getElementsByTagName("state")[0].getAttribute("state");

        //add all to main array
        var Computer = {
            "Hostname": Hostname,
            "IP": IP_Address,
            "SSH": SSH_State,
            "HTTP": HTTP_State,
            "Deactivated" : false
        };
        IPs.push(IP_Address)
        Computers_New.push(Computer);
    } //end for loop
    //var IP_Address = xmlDoc.getElementsByTagName("nmaprun")[0].childNodes[0].nodeName;
    Computers_New.sort(function(a, b) {
        return dot2num(a.IP) - dot2num(b.IP);
    });

    //check for lost computers
    var missed = false;
    for (var old_c in Computers) {
            var Computer_Counter = 0;
        for(var new_c in Computers_New)
        {

            if (Computers_New[new_c].IP == Computers[old_c].IP)
            {
                //we found the same IP so we can move on
                missed = false;
                break;
            }
            Computer_Counter++;
            if (Computer_Counter == Computers_New.length)
            {
                //The computer is not here - we looking in the complete array
                var Missing_Computer = {
                    "Hostname": Computers[old_c].Hostname,
                    "IP": Computers[old_c].IP,
                    "SSH": Computers[old_c].SHH,
                    "HTTP": Computers[old_c].HTTP,
                    "Deactivated" : true
                };
                missed = true;
                Computers_New.push(Missing_Computer);
            }
        }

    }
    Computers = Computers_New;
    Display();
}

function Remove_Old_Elements()
{
    //remove the old headline
    var r1 = document.getElementById("Scan_Time_ID");
    if (r1) {
        r1.parentNode.removeChild(r1);
    }

    var r2 = document.getElementById("parent");
    if (r2) {
        r2.parentNode.removeChild(r2);
    }
/*
    //remove the old entries
    var r3 = document.getElementById("parent");
    if (r3)
    {
        while (r3.hasChildNodes())
        {
            r3.removeChild(r2.lastChild);
        }
    }*/
}

function Display() {
    //preparations for the page design
    //remove all older elements
    console.log("Display called");
    //add the headline for the scantime
    //remove the old headline first
    Remove_Old_Elements()

    var Scan_Time_Headline = document.createElement("h1");
    Scan_Time_Headline.id = "Scan_Time_ID"
    Scan_Time_Headline.innerHTML = "Scan time: " + Scan_Time;
    document.body.appendChild(Scan_Time_Headline);

    var parent_div = document.createElement("div")
    parent_div.id = "parent"
    document.body.appendChild(parent_div);

    var Num_Hosts = Computers.length;

    for (var i in Computers) {
        var child_input = document.createElement("div");
        child_input.className = "Computer_Entry";
        child_input.id = "child" + i.toString();
        var Elem_IP = document.createElement("p");
        Elem_IP.innerHTML = "IP: " + Computers[i].IP;
        var Elem_Name = document.createElement("p");
        Elem_Name.innerHTML = "Name: " + Computers[i].Hostname;

        document.getElementById(parent_div.id).appendChild(child_input);
        document.getElementById(child_input.id).appendChild(Elem_IP);
        document.getElementById(child_input.id).appendChild(Elem_Name);

        if (Computers[i].Deactivated == true){
            child_input.style.backgroundColor = "DarkGrey";
        }

        if (Computers[i].SSH == "open") {
            Elem_SSH_Img = document.createElement("img");
            Elem_SSH_Img.src = "img/SSH_logo.png";
            Elem_SSH_Img.id = "port_logo";
            //Elem_SSH_Img.style = "width:80px;margin:10px";
            document.getElementById(child_input.id).appendChild(Elem_SSH_Img);
        }

        if (Computers[i].HTTP == "open") {
            Elem_HTTP_Img = document.createElement("img");
            Elem_HTTP_Img.src = "img/HTTP_logo.png";
            Elem_HTTP_Img.id = "port_logo";
            //Elem_HTTP_Img.style = "width:80px";
            document.getElementById(child_input.id).appendChild(Elem_HTTP_Img);
        }
    } //end for loop
} //end function Display

//IP address conversion
function dot2num(dot) {
    var d = dot.split('.');
    return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]);
}

function num2dot(num) {
    var d = num % 256;
    for (var i = 3; i > 0; i--) {
        num = Math.floor(num / 256);
        d = num % 256 + '.' + d;
    }
    return d;
}
<commit_msg>cosmetics for working version<commit_after>function Start()
{
    Read_XML();
    setInterval(Read_XML, 60 * 1000);
}

function Read_XML() {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            Scan(this);
        }
    };
    xhttp.open("GET", "scan.xml", true);
    xhttp.send();
}

//global variable array
var Computers = [];
var IPs = [];
var Scan_Time = "today";

function Scan(xml) {
    //the general xml handler
    var xmlDoc = xml.responseXML;

    //write down the scan time
    Scan_Time = xmlDoc.getElementsByTagName("nmaprun")[0].getAttribute("startstr");
    var Num_Hosts = xmlDoc.getElementsByTagName("host").length;
    var Computers_New = [];
    //looping throught the xml entries
    for (var i = 0; i < Num_Hosts; i++) {
        var Host_Element = xmlDoc.getElementsByTagName("host")[i];
        var IP_Address = Host_Element.getElementsByTagName("address")[0].getAttribute("addr");
        var Hostname_Element = Host_Element.getElementsByTagName("hostnames")[0].getElementsByTagName("hostname")[0];
        if (Hostname_Element) {
            var Hostname = Hostname_Element.getAttribute("name");
            var s = Hostname.split(".");
            Hostname = s[0];
        } else {
            var Hostname = "Unknown";
        }

        var Ports_Element = Host_Element.getElementsByTagName("ports")[0].getElementsByTagName("port");
        for (var j = 0; j < Ports_Element.length; j++) {
            var Port_ID = Ports_Element[j].getAttribute("portid");
            var Port_Status = Ports_Element[j].getElementsByTagName("state")[0].getAttribute("state");
        }
        var SSH_State = Ports_Element[0].getElementsByTagName("state")[0].getAttribute("state");
        var HTTP_State = Ports_Element[1].getElementsByTagName("state")[0].getAttribute("state");

        //add all to main array
        var Computer = {
            "Hostname": Hostname,
            "IP": IP_Address,
            "SSH": SSH_State,
            "HTTP": HTTP_State,
            "Deactivated" : false
        };
        IPs.push(IP_Address)
        Computers_New.push(Computer);
    } //end for loop

    //check for lost/deactivated computers (they should be displayed grayed out)
    var missed = false;
    for (var old_c in Computers) {
            var Computer_Counter = 0;
        for(var new_c in Computers_New)
        {

            if (Computers_New[new_c].IP == Computers[old_c].IP)
            {
                //we found the same IP so we can move on
                missed = false;
                break;
            }
            Computer_Counter++;
            if (Computer_Counter == Computers_New.length)
            {
                //The computer is not here - we looking in the complete array
                var Missing_Computer = {
                    "Hostname": Computers[old_c].Hostname,
                    "IP": Computers[old_c].IP,
                    "SSH": Computers[old_c].SHH,
                    "HTTP": Computers[old_c].HTTP,
                    "Deactivated" : true
                };
                missed = true;
                Computers_New.push(Missing_Computer);
            }
        }

    }
    Computers_New.sort(function(a, b) {
        return dot2num(a.IP) - dot2num(b.IP);
    });
    Computers = Computers_New;
    Display();
}

function Remove_Old_Elements()
{
    //remove the old headline
    var r1 = document.getElementById("Scan_Time_ID");
    if (r1) {
        r1.parentNode.removeChild(r1);
    }

    var r2 = document.getElementById("parent");
    if (r2) {
        r2.parentNode.removeChild(r2);
    }
/*
    //remove the old entries
    var r3 = document.getElementById("parent");
    if (r3)
    {
        while (r3.hasChildNodes())
        {
            r3.removeChild(r2.lastChild);
        }
    }*/
}

function Display() {
    //remove all older elements
    //remove the old headline first
    Remove_Old_Elements()

    var Scan_Time_Headline = document.createElement("h1");
    Scan_Time_Headline.id = "Scan_Time_ID"
    Scan_Time_Headline.innerHTML = "Scan time: " + Scan_Time;
    document.body.appendChild(Scan_Time_Headline);
    var parent_div = document.createElement("div")
    parent_div.id = "parent"
    document.body.appendChild(parent_div);
    var Num_Hosts = Computers.length;

    for (var i in Computers) {
        var child_input = document.createElement("div");
        child_input.className = "Computer_Entry";
        child_input.id = "child" + i.toString();
        var Elem_IP = document.createElement("p");
        Elem_IP.innerHTML = "IP: " + Computers[i].IP;
        var Elem_Name = document.createElement("p");
        Elem_Name.innerHTML = "Name: " + Computers[i].Hostname;

        document.getElementById(parent_div.id).appendChild(child_input);
        document.getElementById(child_input.id).appendChild(Elem_IP);
        document.getElementById(child_input.id).appendChild(Elem_Name);

        if (Computers[i].Deactivated == true){
            child_input.style.backgroundColor = "DarkGrey";
        }

        if (Computers[i].SSH == "open") {
            Elem_SSH_Img = document.createElement("img");
            Elem_SSH_Img.src = "img/SSH_logo.png";
            Elem_SSH_Img.id = "port_logo";
            //Elem_SSH_Img.style = "width:80px;margin:10px";
            document.getElementById(child_input.id).appendChild(Elem_SSH_Img);
        }

        if (Computers[i].HTTP == "open") {
            Elem_HTTP_Img = document.createElement("img");
            Elem_HTTP_Img.src = "img/HTTP_logo.png";
            Elem_HTTP_Img.id = "port_logo";
            //Elem_HTTP_Img.style = "width:80px";
            document.getElementById(child_input.id).appendChild(Elem_HTTP_Img);
        }
    } //end for loop
} //end function Display

//IP address conversion
function dot2num(dot) {
    var d = dot.split('.');
    return ((((((+d[0]) * 256) + (+d[1])) * 256) + (+d[2])) * 256) + (+d[3]);
}

function num2dot(num) {
    var d = num % 256;
    for (var i = 3; i > 0; i--) {
        num = Math.floor(num / 256);
        d = num % 256 + '.' + d;
    }
    return d;
}
<|endoftext|>


<commit_before>/**
* TabNavigator lets you create tabs for static and dynamic content.
* @name TabNavigator
* @class TabNavigator
* @augments ch.Controllers
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {Number} [conf.selected] Selects a child that will be open when component was loaded. By default, the value is 1.
* @returns itself
* @factorized
* @exampleDescription Create a new Tab Navigator without configuration.
* @example
* var widget = $(".example").tabNavigator();
* @exampleDescription Create a new Tab Navigator with configuration.
* @example
* var widget = $(".example").tabNavigator({
*     "selected": 2
* });
* @see ch.Controllers
*/

ch.tabNavigator = function (conf) {

	/**
	* Reference to a internal component instance, saves all the information and configuration properties.
	* @private
	* @name ch.TabNavigator#that
	* @type object
	*/
	var that = this;

	conf = ch.clon(conf);

	that.conf = conf;

/**
*	Inheritance
*/

	that = ch.controllers.call(that);
	that.parent = ch.clon(that);

/**
*	Private Members
*/

	// Add CSS class to the main element
	that.$element.addClass("ch-tabNavigator");

	/**
	* The actual location hash, is used to know if there's a specific tab selected.
	* @private
	* @name ch.TabNavigator#hash
	* @type string
	*/
	var hash = window.location.hash.replace("#!/", ""),

	/**
	* A boolean property to know if the some tag should be selected.
	* @private
	* @name ch.TabNavigator#hashed
	* @type boolean
	* @default false
	*/
		hashed = false,

	/**
	* Get wich tab is selected.
	* @private
	* @name ch.TabNavigator#selected
	* @type number
	*/
		//selected = conf.selected - 1 || conf.value - 1 || 0,
		selected = conf.selected - 1 || conf.value - 1 || -1,

	/**
	* Create controller's children.
	* @private
	* @name ch.TabNavigator#createTabs
	* @function
	*/
		createTabs = function () {

			// Children
			that.$triggers.find("a").each(function (i, e) {

				// Tab context
				var tab = {};
					tab.uid = that.uid + "#" + i;
					tab.type = "tab";
					tab.element = e;
					tab.$element = $(e);
					tab.controller = that["public"];

				// Tab configuration
				var config = {};
					config.open = (selected === i);
					config.onShow = function () { selected = i; };

				if (ch.utils.hasOwn(that.conf, "cache")) { config.cache = that.conf.cache; }

				/**
				* Fired when the content of one dynamic tab loads.
				* @name ch.TabNavigator#onContentLoad
				* @event
				* @public
				*/
				if (ch.utils.hasOwn(that.conf, "onContentLoad")) { config.onContentLoad = that.conf.onContentLoad; }

				/**
				* Fired when the content of one dynamic tab did not load.
				* @name ch.TabNavigator#onContentError
				* @event
				* @public
				*/
				if (ch.utils.hasOwn(that.conf, "onContentError")) { config.onContentError = that.conf.onContentError; }

				// Create Tabs
				that.children.push(ch.tab.call(tab, config));

				// Bind new click to have control
				$(e).on("click", function (event) {
					that.prevent(event);
					select(i + 1);
				});

			});

			return;

		},

	/**
	* Select a child to show its content.
	* @name ch.TabNavigator#select
	* @private
	* @function
	*/
		select = function (tab) {

			// If select a tab that don't exist do nothing
			if(!that.children[tab - 1]){ return; }

			tab = that.children[tab - 1];

			// Don't click me if I'm open
			if (tab === that.children[selected]) { return; }

			// Hide my open bro
			$.each(that.children, function (i, e) {
				if (tab !== e) { e.innerHide(); }
			});

			// shows the tab
			tab.innerShow();

			//Change location hash
			window.location.hash = "#!/" + tab.$content.attr("id");

			/**
			* Fired when a tab is selected.
			* @name ch.TabNavigator#onSelect
			* @event
			* @public
			*/
			that.callbacks("onSelect");

			// new callback
			that.trigger("select");

			return that;

		};

/**
*	Protected Members
*/

	/**
	* The component's triggers container.
	* @protected
	* @name ch.TabNavigator#$triggers
	* @type jQuery
	*/
	that.$triggers = that.$element.children(":first").addClass("ch-tabNavigator-triggers").attr("role", "tablist");

	/**
	* The component's content.
	* @protected
	* @name ch.TabNavigator#$content
	* @type jQuery
	*/
	that.$content = that.$triggers.next().addClass("ch-tabNavigator-content ch-box").attr("role", "presentation");

/**
*	Public Members
*/

	/**
	* @borrows ch.Object#uid as ch.TabNavigator#uid
	*/

	/**
	* @borrows ch.Object#element as ch.TabNavigator#element
	*/

	/**
	* @borrows ch.Object#type as ch.TabNavigator#type
	*/

	/**
	* Children instances associated to this controller.
	* @public
	* @name ch.TabNavigator#children
	* @type collection
	*/
	that["public"].children = that.children;

	/**
	* Select a specific child or get the selected tab.
	* @public
	* @name ch.TabNavigator#select
	* @function
	* @param {number} tab Tab's index.
	* @exampleDescription Create a TabNavigator
	* @example
	* widget.select(2);
	*
	* // Get selected page
	* var selected = widget.select();
	*/
	that["public"].select = function (tab) {
		// Returns selected tab instead set it
		// Getter
		if (!tab) {
			return (selected + 1);
		}

		// Setter
		select(tab);
		return that["public"];

	};

	/**
	* Deprecated - Returns the selected child's index.
	* @public
	* @name ch.TabNavigator#getSelected
	* @function
	* @returns {number} selected Tab's index.
	* @deprecated
	*/
	that["public"].getSelected = function () { return (selected + 1); };

/**
*	Default event delegation
*/

	createTabs();

	// If hash open that tab
	for(var i = that.children.length; i--;) {
		if (that.children[i].$content.attr("id") === hash) {
			select(i+1);
			hashed = true;
			break;
		}
	};

	// Shows the first tab if not hash or it's hash and it isn't from the current tab
	if( !hash || ( hash && !hashed ) ){
		that.children[0].innerShow();
	}

	/**
	* Triggers when the component is ready to use (Since 0.8.0).
	* @name ch.TabNavigator#ready
	* @event
	* @public
	* @since 0.8.0
	* @example
	* // Following the first example, using widget as tabNavigator's instance controller:
	* widget.on("ready",function () {
	*	this.show();
	* });
	*/
	//This avoit to trigger execute after the component was instanciated
	setTimeout(function(){that.trigger("ready")}, 50);

	return that;

};

ch.factory("tabNavigator");

/**
* Tab is a simple unit of content for TabNavigators.
* @abstract
* @name Tab
* @class Tab
* @augments ch.Navs
* @memberOf ch
* @param {object} conf Object with configuration properties
* @returns itself
* @ignore
*/

ch.tab = function (conf) {
	/**
	* Reference to a internal component instance, saves all the information and configuration properties.
	* @private
	* @name ch.Tab#that
	* @type object
	* @ignore
	*/
	var that = this;

	conf = ch.clon(conf);
	conf.icon = false;

	that.conf = conf;

/**
*	Inheritance
*/

	that = ch.uiobject.call(that);
	that.parent = ch.clon(that);

/**
*	Private Members
*/
	/**
	* Creates the basic structure for the tab's content.
	* @private
	* @name ch.Tab#createContent
	* @function
	* @ignore
	*/
	var createContent = function () {

		var href = that.element.href.split("#"),
			controller = that.$element.parents(".ch-tabNavigator"),
			content = controller.find("#" + href[1]);

		// If there are a tabContent...
		if (content.length > 0) {
			return content;

		// If tabContent doesn't exists
		} else {
			/**
			* Content configuration property.
			* @public
			* @name ch.Tab#source
			* @type string
			* @ignore
			*/
			that.source = that.element.href;

			var id = (href.length === 2) ? href[1] : "ch-tab" + that.uid.replace("#", "-");

			// Create tabContent
			return $("
").appendTo(controller.children().eq(1));
		}

	};

/**
*	Protected Members
*/
	/**
	* Reference to the trigger element.
	* @protected
	* @name ch.Tab#$trigger
	* @type jQuery
	* @ignore
	*/
	that.$trigger = that.$element;

	/**
	* The component's content.
	* @protected
	* @name ch.Tab#$content
	* @type jQuery
	* @ignore
	*/
	that.$content = createContent();

	/**
	* Shows component's content.
	* @protected
	* @function
	* @name ch.Tab#innerShow
	* @returns itself
	* @ignore
	*/
	that.innerShow = function (event) {
		that.prevent(event);

		that.active = true;

		// Load my content if I'need an ajax request
		if (ch.utils.hasOwn(that, "source")) { that.content(); }

		// Show me
		that.$trigger.addClass("ch-" + that["type"] + "-trigger-on");

		// Set me as hidden false
		that.$content
				.attr("aria-hidden", "false")
				.removeClass("ch-hide");

		return that;
	};

	/**
	* Hides component's content.
	* @protected
	* @function
	* @name ch.Tab#innerHide
	* @returns itself
	* @ignore
	*/
	that.innerHide = function (event) {
		that.prevent(event);

		if (!that.active) { return; }

		that.active = false;

		// Hide me
		that.$trigger.removeClass("ch-" + that["type"] + "-trigger-on");

		// Set all inactive tabs as hidden
		that.$content
				.attr("aria-hidden", "true")
				.addClass("ch-hide");

		return that;
	};

	/**
	* This callback is triggered when async data is loaded into component's content, when ajax content comes back.
	* @protected
	* @name ch.Tab#contentCallback
	* @ignore
	*/
	that["public"].on("contentLoad", function (event, context) {

		that.$content.html(that.staticContent);

		if (ch.utils.hasOwn(conf, "onContentLoad")) {
			conf.onContentLoad.call(context, that.staticContent);
		}

	});

	/**
	* This callback is triggered when async request fails.
	* @public
	* @name contentCallback
	* @returns {Chico-UI Object}
	* @memberOf ch.TabNavigator
	* @ignore
	*/
	that["public"].on("contentError", function (event, data) {

		that.$content.html(that.staticContent);

		// Get the original that.source
		var originalSource = that.source;

		if (ch.utils.hasOwn(conf, "onContentError")) {
			conf.onContentError.call(data.context, data.jqXHR, data.textStatus, data.errorThrown);
		}

		// Reset content configuration
		that.source = originalSource;

		that.staticContent = undefined;

	});

/**
*	Public Members
*/

/**
*	Default event delegation
*/

	// Add the attributes for WAI-ARIA to the tabs and tabpanel
	that.$content.attr({
		"role": "tabpanel",
		"aria-hidden": that.$content.hasClass("ch-hide"),
		"class": "ch-hide"
	});

	that.$trigger.attr({
		"role": "tab",
		"arial-controls": that.$content.attr("id"),
		"class": "ch-tab-trigger"
	});

	return that;
}<commit_msg>#714 Tabs: the first tab doesn't show, #711 Tabs: .select() method should be getter and setter<commit_after>/**
* TabNavigator lets you create tabs for static and dynamic content.
* @name TabNavigator
* @class TabNavigator
* @augments ch.Controllers
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {Number} [conf.selected] Selects a child that will be open when component was loaded. By default, the value is 1.
* @returns itself
* @factorized
* @exampleDescription Create a new Tab Navigator without configuration.
* @example
* var widget = $(".example").tabNavigator();
* @exampleDescription Create a new Tab Navigator with configuration.
* @example
* var widget = $(".example").tabNavigator({
*     "selected": 2
* });
* @see ch.Controllers
*/

ch.tabNavigator = function (conf) {

	/**
	* Reference to a internal component instance, saves all the information and configuration properties.
	* @private
	* @name ch.TabNavigator#that
	* @type object
	*/
	var that = this;

	conf = ch.clon(conf);

	that.conf = conf;

/**
*	Inheritance
*/

	that = ch.controllers.call(that);
	that.parent = ch.clon(that);

/**
*	Private Members
*/

	// Add CSS class to the main element
	that.$element.addClass("ch-tabNavigator");

	/**
	* The actual location hash, is used to know if there's a specific tab selected.
	* @private
	* @name ch.TabNavigator#hash
	* @type string
	*/
	var hash = window.location.hash.replace("#!/", ""),

	/**
	* A boolean property to know if the some tag should be selected.
	* @private
	* @name ch.TabNavigator#hashed
	* @type boolean
	* @default false
	*/
		hashed = false,

	/**
	* Get wich tab is selected.
	* @private
	* @name ch.TabNavigator#selected
	* @type number
	*/
		selected = conf.selected || conf.value || undefined,

	/**
	* Create controller's children.
	* @private
	* @name ch.TabNavigator#createTabs
	* @function
	*/
		createTabs = function () {

			// Children
			that.$triggers.find("a").each(function (i, e) {

				// Tab context
				var tab = {};
					tab.uid = that.uid + "#" + i;
					tab.type = "tab";
					tab.element = e;
					tab.$element = $(e);
					tab.controller = that["public"];

				// Tab configuration
				var config = {};
					config.open = (selected === i);
					config.onShow = function () { selected = i; };

				if (ch.utils.hasOwn(that.conf, "cache")) { config.cache = that.conf.cache; }

				/**
				* Fired when the content of one dynamic tab loads.
				* @name ch.TabNavigator#contentLoad
				* @event
				* @public
				*/
				if (ch.utils.hasOwn(that.conf, "onContentLoad")) { config.onContentLoad = that.conf.onContentLoad; }

				/**
				* Fired when the content of one dynamic tab did not load.
				* @name ch.TabNavigator#contentError
				* @event
				* @public
				*/
				if (ch.utils.hasOwn(that.conf, "onContentError")) { config.onContentError = that.conf.onContentError; }

				// Create Tabs
				that.children.push(ch.tab.call(tab, config));

				// Bind new click to have control
				$(e).on("click", function (event) {
					that.prevent(event);
					select(i);
				});

			});

			return;

		},

	/**
	* Select a child to show its content.
	* @name ch.TabNavigator#select
	* @private
	* @function
	*/
		select = function (index) {

				// Sets the tab's index
			var tab = that.children[index];

			// If select a tab that doesn't exist do nothing
			// Don't click me if I'm open
			if (!tab || index === selected) {
				return that;
			}

			// Hides the open tab
			if (typeof selected !== "undefined") {
				that.children[selected].innerHide();
			}

			// Shows the current tab
			tab.innerShow();

			// Updated selected index
			selected = index;

			//Change location hash
			window.location.hash = "#!/" + tab.$content.attr("id");

			/**
			* Fired when a tab is selected.
			* @name ch.TabNavigator#select
			* @event
			* @public
			*/
			that.trigger("select");

			// Callback
			that.callbacks("onSelect");

			return that;
		};

/**
*	Protected Members
*/

	/**
	* The component's triggers container.
	* @protected
	* @name ch.TabNavigator#$triggers
	* @type jQuery
	*/
	that.$triggers = that.$element.children(":first").addClass("ch-tabNavigator-triggers").attr("role", "tablist");

	/**
	* The component's content.
	* @protected
	* @name ch.TabNavigator#$content
	* @type jQuery
	*/
	that.$content = that.$triggers.next().addClass("ch-tabNavigator-content ch-box").attr("role", "presentation");

/**
*	Public Members
*/

	/**
	* @borrows ch.Object#uid as ch.TabNavigator#uid
	*/

	/**
	* @borrows ch.Object#element as ch.TabNavigator#element
	*/

	/**
	* @borrows ch.Object#type as ch.TabNavigator#type
	*/

	/**
	* Children instances associated to this controller.
	* @public
	* @name ch.TabNavigator#children
	* @type collection
	*/
	that["public"].children = that.children;

	/**
	* Select a specific tab or get the selected tab.
	* @public
	* @name ch.TabNavigator#select
	* @function
	* @param {Number} [tab] Tab's index.
	* @exampleDescription Selects a specific tab
	* @example
	* widget.select(2);
	* @exampleDescription Returns the selected tab's index
	* @example
	* var selected = widget.select();
	*/
	that["public"].select = function (tab) {
		// Returns selected tab instead set it
		// Getter
		if (!parseInt(tab)) {
			return selected;
		}

		// Setter
		select(tab -= 1);
		return that["public"];

	};

/**
*	Default event delegation
*/

	createTabs();

	// If hash open that tab
	for(var i = that.children.length; i--;) {
		if (that.children[i].$content.attr("id") === hash) {
			select(i);
			hashed = true;
			break;
		}
	};

	// Shows the first tab if not hash or it's hash and it isn't from the current tab
	if( !hash || ( hash && !hashed ) ){
		that.children[0].innerShow();
	}

	/**
	* Triggers when the component is ready to use (Since 0.8.0).
	* @name ch.TabNavigator#ready
	* @event
	* @public
	* @since 0.8.0
	* @example
	* // Following the first example, using widget as tabNavigator's instance controller:
	* widget.on("ready",function () {
	*	this.show();
	* });
	*/
	//This avoit to trigger execute after the component was instanciated
	setTimeout(function(){that.trigger("ready")}, 50);

	return that;

};

ch.factory("tabNavigator");

/**
* Tab is a simple unit of content for TabNavigators.
* @abstract
* @name Tab
* @class Tab
* @augments ch.Navs
* @memberOf ch
* @param {object} conf Object with configuration properties
* @returns itself
* @ignore
*/

ch.tab = function (conf) {
	/**
	* Reference to a internal component instance, saves all the information and configuration properties.
	* @private
	* @name ch.Tab#that
	* @type object
	* @ignore
	*/
	var that = this;

	conf = ch.clon(conf);
	conf.icon = false;

	that.conf = conf;

/**
*	Inheritance
*/

	that = ch.uiobject.call(that);
	that.parent = ch.clon(that);

/**
*	Private Members
*/
	/**
	* Creates the basic structure for the tab's content.
	* @private
	* @name ch.Tab#createContent
	* @function
	* @ignore
	*/
	var createContent = function () {

		var href = that.element.href.split("#"),
			controller = that.$element.parents(".ch-tabNavigator"),
			content = controller.find("#" + href[1]);

		// If there are a tabContent...
		if (content.length > 0) {
			return content;

		// If tabContent doesn't exists
		} else {
			/**
			* Content configuration property.
			* @public
			* @name ch.Tab#source
			* @type string
			* @ignore
			*/
			that.source = that.element.href;

			var id = (href.length === 2) ? href[1] : "ch-tab" + that.uid.replace("#", "-");

			// Create tabContent
			return $("
").appendTo(controller.children().eq(1));
		}

	};

/**
*	Protected Members
*/
	/**
	* Reference to the trigger element.
	* @protected
	* @name ch.Tab#$trigger
	* @type jQuery
	* @ignore
	*/
	that.$trigger = that.$element;

	/**
	* The component's content.
	* @protected
	* @name ch.Tab#$content
	* @type jQuery
	* @ignore
	*/
	that.$content = createContent();

	/**
	* Shows component's content.
	* @protected
	* @function
	* @name ch.Tab#innerShow
	* @returns itself
	* @ignore
	*/
	that.innerShow = function (event) {
		that.prevent(event);

		that.active = true;

		// Load my content if I'need an ajax request
		if (ch.utils.hasOwn(that, "source")) { that.content(); }

		// Show me
		that.$trigger.addClass("ch-" + that["type"] + "-trigger-on");

		// Set me as hidden false
		that.$content
				.attr("aria-hidden", "false")
				.removeClass("ch-hide");

		return that;
	};

	/**
	* Hides component's content.
	* @protected
	* @function
	* @name ch.Tab#innerHide
	* @returns itself
	* @ignore
	*/
	that.innerHide = function (event) {
		that.prevent(event);

		if (!that.active) { return; }

		that.active = false;

		// Hide me
		that.$trigger.removeClass("ch-" + that["type"] + "-trigger-on");

		// Set all inactive tabs as hidden
		that.$content
				.attr("aria-hidden", "true")
				.addClass("ch-hide");

		return that;
	};

	/**
	* This callback is triggered when async data is loaded into component's content, when ajax content comes back.
	* @protected
	* @name ch.Tab#contentCallback
	* @ignore
	*/
	that["public"].on("contentLoad", function (event, context) {

		that.$content.html(that.staticContent);

		if (ch.utils.hasOwn(conf, "onContentLoad")) {
			conf.onContentLoad.call(context, that.staticContent);
		}

	});

	/**
	* This callback is triggered when async request fails.
	* @public
	* @name contentCallback
	* @returns {Chico-UI Object}
	* @memberOf ch.TabNavigator
	* @ignore
	*/
	that["public"].on("contentError", function (event, data) {

		that.$content.html(that.staticContent);

		// Get the original that.source
		var originalSource = that.source;

		if (ch.utils.hasOwn(conf, "onContentError")) {
			conf.onContentError.call(data.context, data.jqXHR, data.textStatus, data.errorThrown);
		}

		// Reset content configuration
		that.source = originalSource;

		that.staticContent = undefined;

	});

/**
*	Public Members
*/

/**
*	Default event delegation
*/

	// Add the attributes for WAI-ARIA to the tabs and tabpanel
	// By default is hidden
	that.$content.attr({
		"role": "tabpanel",
		"aria-hidden": true,
		"class": "ch-hide"
	});

	that.$trigger.attr({
		"role": "tab",
		"arial-controls": that.$content.attr("id"),
		"class": "ch-tab-trigger"
	});

	return that;
}<|endoftext|>


<commit_before>var async = require('async'),
    path = require('path'),
    fs = require('fs'),
    ncp = require('ncp'),
    handlebars = require('handlebars'),
    moment = require('moment'),
    less = require('less'),
    util = require('util'),
    uglify = require('uglify-js'),
    config = require('./config'),
    templates = {};

exports.generateReport = generateReport;
exports.saveToFile = saveToFile;
exports.templates = templates;

function generateReport() {
  util.print("Generating Report Files...\n");
  async.auto({
      loadTemplates: function(callback) {
        // console.log('loadTemplates');
        // async code to load and register hbs templates
        loadTemplates(callback);
      },
      createDirs: function(callback) {
        // console.log('createDirs');
        // async code to create directories to store files
        createDirs(callback);
      },
      parseLess: function(callback) {
        // console.log('parseLess');
        // parse less
        parseLess(callback);
      },
      minifyJs: function(callback) {
        // console.log('minifyJs');
        // concat and minify js
        minifyJs(callback);
      },
      registerHbsHelpers: function(callback) {
        // console.log('registerHbsHelpers');
        // register helpers
        registerHbsHelpers(callback);
      },
      saveCss: ['createDirs', 'parseLess', function(callback,  results) {
        // console.log('saveCss');
        // after creating directories and parsing less,
        // save the css to file
        saveToFile(results.parseLess, config.reportCssFile, callback);
      }],
      saveJs: ['createDirs', 'minifyJs', function(callback, results) {
        // console.log('saveJs');
        // after creating directories and minifying js,
        // save the js to file
        saveToFile(results.minifyJs.code, config.reportJsFile, callback);
      }],
      saveFonts: ['createDirs', function(callback) {
        // console.log('saveFonts');
        // after creating directories,
        // copy fonts to fonts dir
        saveFonts(callback);
      }]
  }, function(err, results) {
    if (err) throw err;
      // console.log('err = ', err);
      // console.log('results = ', results);
  });
}

function loadTemplates(callback) {
  async.waterfall([
    function (cb) {
      fs.readdir(config.templatesDir, function (err, files) {
        if (err) cb(err);
        cb(null, files);
      });
    },
    function (files, cb) {
      async.filter(files, filterTemplateFile, function (templateFiles) {
        cb(null, templateFiles);
      });
    },
    function (templateFiles, cb) {
      async.each(templateFiles, registerTemplate, function (err) {
        if (err) cb(err);
        cb();
      });
    }
  ], function (err, result) {
    // result
    if (err) callback(err);
    callback(null, templates);
  });
}

function saveFonts(callback) {
  ncp(config.bsFontsDir, config.reportFontsDir, function (err) {
    if (err) callback(err);
    callback(null, 'done');
  });
}

function filterTemplateFile(file, cb) {
  cb(file.substr(-3) === '.mu');
}

function registerTemplate(templateFile, cb) {
  var filePath = path.join(config.templatesDir, templateFile);
  var templateName = templateFile.replace('.mu', '');
  fs.readFile(filePath, {encoding: 'utf8'}, function (err, data) {
    if (err) cb(err);
    if (templateName.indexOf('_') === 0) {
      handlebars.registerPartial(templateName, data);
    } else {
      templates[templateName] = handlebars.compile(data);
    }
    cb(null);
  });
}

function createDirs (callback) {
  var dirs = [config.reportDir, config.reportJsDir, config.reportFontsDir, config.reportCssDir];
  dirs.forEach(function(dir) {
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir);
    }
  });
  callback(null, 'done');
}

function parseLess (callback) {
  var stylesheet = fs.readFileSync(config.reportLessFile, {encoding: 'utf8'});
  var parser = new(less.Parser)({
    paths: [config.stylesDir, config.bsLessDir],
  });
  parser.parse(stylesheet, function(err, tree) {
    if (err) callback(err);
    var outCss = tree.toCSS({
      compress: true
    });
    callback(null, outCss);
  });
}

function minifyJs (callback) {
  var jsReqs = [
    path.join(config.nodeModulesDir, 'chart.js', 'Chart.js'),
    path.join(config.nodeModulesDir, 'bootstrap', 'dist', 'js', 'bootstrap.js')
  ];
  callback(null, uglify.minify(jsReqs));
}

function registerHbsHelpers (callback) {
  function getDurationObj(durationInMilliseconds) {
    var dur = moment.duration(durationInMilliseconds, 'ms');
    return {
      duration: dur,
      hrs: dur.get('h'),
      min: dur.get('m'),
      sec: dur.get('s'),
      ms: dur.get('ms')
    };
  }

  handlebars.registerHelper('formatSummarySuiteCount', function (context) {
    return context === 1 ? 'Suite' : 'Suites';
  });

  handlebars.registerHelper('formatSummaryTestCount', function (context) {
    return context === 1 ? 'Test' : 'Tests';
  });

  handlebars.registerHelper('formatSummaryDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context;
        }
        return dur.sec + '.' + dur.ms;
      }
      return dur.min + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec);
    }
    return dur.hrs + ':' + (dur.min < 10 ? ('0' + dur.min) : dur.min);
  });

  handlebars.registerHelper('getSummaryDurationUnits', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return 'MS';
        }
        return 'SEC';
      }
      return 'MIN';
    }
    return 'HRS';
  });

  handlebars.registerHelper('formatDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context + ' ms';
        }
        return dur.sec + '.' + dur.ms + ' s';
      }
      return dur.min + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec) + '.' + dur.ms + ' m';
    }
    return dur.hrs + ':' + (dur.min < 10 ? ('0' + dur.min) : dur.min) + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec) + '.' + dur.ms + ' h';
  });

  handlebars.registerHelper('dateFormat', function(context, format) {
    if (format === "fromNow") {
      return moment(context).fromNow();
    } else {
      return moment(context).format(format);
    }
  });

  callback(null, 'done');
}

function saveToFile (data, outFile, callback) {
  var writeFile;
  try {
    writeFile = fs.openSync(outFile, 'w');
    fs.writeSync(writeFile, data);
    fs.close(writeFile);
    callback(null, outFile);
  } catch (err) {
    util.print("\nError: Unable to save " + outFile + "\n" + err + "\n");
    callback(err);
  }
}<commit_msg>simpler handlebars helper for determining pluralization of titles<commit_after>var async = require('async'),
    path = require('path'),
    fs = require('fs'),
    ncp = require('ncp'),
    handlebars = require('handlebars'),
    moment = require('moment'),
    less = require('less'),
    util = require('util'),
    uglify = require('uglify-js'),
    config = require('./config'),
    templates = {};

exports.generateReport = generateReport;
exports.saveToFile = saveToFile;
exports.templates = templates;

function generateReport() {
  util.print("Generating Report Files...\n");
  async.auto({
      loadTemplates: function(callback) {
        // console.log('loadTemplates');
        // async code to load and register hbs templates
        loadTemplates(callback);
      },
      createDirs: function(callback) {
        // console.log('createDirs');
        // async code to create directories to store files
        createDirs(callback);
      },
      parseLess: function(callback) {
        // console.log('parseLess');
        // parse less
        parseLess(callback);
      },
      minifyJs: function(callback) {
        // console.log('minifyJs');
        // concat and minify js
        minifyJs(callback);
      },
      registerHbsHelpers: function(callback) {
        // console.log('registerHbsHelpers');
        // register helpers
        registerHbsHelpers(callback);
      },
      saveCss: ['createDirs', 'parseLess', function(callback,  results) {
        // console.log('saveCss');
        // after creating directories and parsing less,
        // save the css to file
        saveToFile(results.parseLess, config.reportCssFile, callback);
      }],
      saveJs: ['createDirs', 'minifyJs', function(callback, results) {
        // console.log('saveJs');
        // after creating directories and minifying js,
        // save the js to file
        saveToFile(results.minifyJs.code, config.reportJsFile, callback);
      }],
      saveFonts: ['createDirs', function(callback) {
        // console.log('saveFonts');
        // after creating directories,
        // copy fonts to fonts dir
        saveFonts(callback);
      }]
  }, function(err, results) {
    if (err) throw err;
      // console.log('err = ', err);
      // console.log('results = ', results);
  });
}

function loadTemplates(callback) {
  async.waterfall([
    function (cb) {
      fs.readdir(config.templatesDir, function (err, files) {
        if (err) cb(err);
        cb(null, files);
      });
    },
    function (files, cb) {
      async.filter(files, filterTemplateFile, function (templateFiles) {
        cb(null, templateFiles);
      });
    },
    function (templateFiles, cb) {
      async.each(templateFiles, registerTemplate, function (err) {
        if (err) cb(err);
        cb();
      });
    }
  ], function (err, result) {
    // result
    if (err) callback(err);
    callback(null, templates);
  });
}

function saveFonts(callback) {
  ncp(config.bsFontsDir, config.reportFontsDir, function (err) {
    if (err) callback(err);
    callback(null, 'done');
  });
}

function filterTemplateFile(file, cb) {
  cb(file.substr(-3) === '.mu');
}

function registerTemplate(templateFile, cb) {
  var filePath = path.join(config.templatesDir, templateFile);
  var templateName = templateFile.replace('.mu', '');
  fs.readFile(filePath, {encoding: 'utf8'}, function (err, data) {
    if (err) cb(err);
    if (templateName.indexOf('_') === 0) {
      handlebars.registerPartial(templateName, data);
    } else {
      templates[templateName] = handlebars.compile(data);
    }
    cb(null);
  });
}

function createDirs (callback) {
  var dirs = [config.reportDir, config.reportJsDir, config.reportFontsDir, config.reportCssDir];
  dirs.forEach(function(dir) {
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir);
    }
  });
  callback(null, 'done');
}

function parseLess (callback) {
  var stylesheet = fs.readFileSync(config.reportLessFile, {encoding: 'utf8'});
  var parser = new(less.Parser)({
    paths: [config.stylesDir, config.bsLessDir],
  });
  parser.parse(stylesheet, function(err, tree) {
    if (err) callback(err);
    var outCss = tree.toCSS({
      compress: true
    });
    callback(null, outCss);
  });
}

function minifyJs (callback) {
  var jsReqs = [
    path.join(config.nodeModulesDir, 'chart.js', 'Chart.js'),
    path.join(config.nodeModulesDir, 'bootstrap', 'dist', 'js', 'bootstrap.js')
  ];
  callback(null, uglify.minify(jsReqs));
}

function registerHbsHelpers (callback) {
  function getDurationObj(durationInMilliseconds) {
    var dur = moment.duration(durationInMilliseconds, 'ms');
    return {
      duration: dur,
      hrs: dur.get('h'),
      min: dur.get('m'),
      sec: dur.get('s'),
      ms: dur.get('ms')
    };
  }

  handlebars.registerHelper('getPlural', function (context) {
    return context === 1 ? '' : 's';
  });

  handlebars.registerHelper('formatSummaryDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context;
        }
        return dur.sec + '.' + dur.ms;
      }
      return dur.min + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec);
    }
    return dur.hrs + ':' + (dur.min < 10 ? ('0' + dur.min) : dur.min);
  });

  handlebars.registerHelper('getSummaryDurationUnits', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return 'MS';
        }
        return 'SEC';
      }
      return 'MIN';
    }
    return 'HRS';
  });

  handlebars.registerHelper('formatDuration', function (context) {
    var dur = getDurationObj(context);
    if (dur.hrs  < 1) {
      if (dur.min < 1) {
        if (dur.sec < 1) {
          return context + ' ms';
        }
        return dur.sec + '.' + dur.ms + ' s';
      }
      return dur.min + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec) + '.' + dur.ms + ' m';
    }
    return dur.hrs + ':' + (dur.min < 10 ? ('0' + dur.min) : dur.min) + ':' + (dur.sec < 10 ? ('0' + dur.sec) : dur.sec) + '.' + dur.ms + ' h';
  });

  handlebars.registerHelper('dateFormat', function(context, format) {
    if (format === "fromNow") {
      return moment(context).fromNow();
    } else {
      return moment(context).format(format);
    }
  });

  callback(null, 'done');
}

function saveToFile (data, outFile, callback) {
  var writeFile;
  try {
    writeFile = fs.openSync(outFile, 'w');
    fs.writeSync(writeFile, data);
    fs.close(writeFile);
    callback(null, outFile);
  } catch (err) {
    util.print("\nError: Unable to save " + outFile + "\n" + err + "\n");
    callback(err);
  }
}<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var debug = require('debug')('pdf-viewer');
var document = require('global/document');
var domready = require('domready');
var struct = require('observ-struct');
var value = require('observ');
var window = require('global/window');

var atom = struct({
  debug: value(true),
  href: value(null),
  pdf: struct({
    document: value(null),
    page: value(null)
  }),
  pages: struct({
    current: value(1),
    total: value(0),
  }),
  scale: value(1),
  progress: value(0),
});

window.atom = atom;
window.debug = require('debug');

// When the state atom is updated to toggle debugging call the correct methods
// on the debug module.
atom.debug(function debugchange(value) {
  // TODO(jasoncampbell): reload page so changes are picked up.
  if (value) {
    window.debug.enable('pdf-*');
  } else {
    window.debug.disable();
  }
});

// Global cache of the canvas element.
var canvas = null;

domready(function ondomready() {
  debug('domready');

  // Initial DOM Node setup.
  canvas = document.createElement('canvas');
  canvas.setAttribute('class','pdf-canvas');
  document.body.style.margin = '0px';
  document.body.style.padding = '0px';
  document.body.appendChild(canvas);

  // Watch for changes on the atom.href value, when it updates load the PDF file
  // located at that location.
  // Trigger with: atom.href.set(value)
  atom.href(function hrefchange(href) {
    debug('loading pdf file: %s', href);
    PDFJS
      .getDocument(href, null, password, progress)
      .then(setPDF, error);
  });

  // Watch for page number changes and asyncronosly load the page from PDF.js
  // APIs.
  // Trigger with: atom.pages.current.set(value)
  atom.pages.current(function pagechange(current) {
    var total = atom.pages.total();

    // Skip invalid operations.
    if (current === 0 || !atom.pdf.document() || current > total) {
      return;
    }

    debug('loading page: %s of %s', current, total);

    var pdf = atom.pdf.document();
    var success = atom.pdf.page.set.bind(null);
    pdf.getPage(current).then(success, error);
  });

  // Watch for changes on the PDF.js page object. When it is updated trigger a
  // render.
  // TODO(jasoncampbell): To prevent rendering errors with frequent state
  // updates renders should be queued in a raf.
  atom.pdf.page(function pagechange(page) {
    debug('rendering page');
    // TODO(jasoncampbell): Use state set scale instead of defaulting to 1.0.
    var scale = window.innerWidth/page.getViewport(1.0).width;
    var viewport = page.getViewport(scale);

    canvas.height = viewport.height;
    canvas.width = viewport.width;

    page.render({
      canvasContext: canvas.getContext('2d'),
      viewport: viewport
    }).promise.then(noop, error);
  });
});

function setPDF(pdf) {
  atom.pdf.document.set(pdf);
  atom.pages.total.set(pdf.numPages);
  atom.pages.current.set(1);
}

function progress(update) {
  var float = (update.loaded/update.total) * 100;
  var value = Math.floor(float);

  // For some reason the update.loaded value above can be higher than the
  // update.total value, in that case we can assume the progress is 100%.
  if (value > 100) {
    value = 100;
  }

  atom.progress.set(value);
}

function password() {
  debug('password required');
}

// TODO(jasoncampbell): Add better error reporting and exception capturing.
function error(err) {
  debug('error: %s', err.stack);
}

function noop() {}
<commit_msg>reader/android: updates pixel ratio and debug logging<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var document = require('global/document');
var domready = require('domready');
var struct = require('observ-struct');
var value = require('observ');
var window = require('global/window');

var atom = struct({
  debug: value(true),
  href: value(null),
  pdf: struct({
    document: value(null),
    page: value(null)
  }),
  pages: struct({
    current: value(1),
    total: value(0),
  }),
  scale: value(1),
  progress: value(0),
});

window.atom = atom;

// Global cache of the canvas element.
var canvas = null;

domready(function ondomready() {
  debug('domready');

  // Initial DOM Node setup.
  canvas = document.createElement('canvas');
  canvas.setAttribute('class','pdf-canvas');
  document.body.style.margin = '0px';
  document.body.style.padding = '0px';
  document.body.appendChild(canvas);

  // Watch for changes on the atom.href value, when it updates load the PDF file
  // located at that location.
  // Trigger with: atom.href.set(value)
  atom.href(function hrefchange(href) {
    debug('loading pdf file: %s', href);
    PDFJS
      .getDocument(href, null, password, progress)
      .then(setPDF, error);
  });

  // Watch for page number changes and asyncronosly load the page from PDF.js
  // APIs.
  // Trigger with: atom.pages.current.set(value)
  atom.pages.current(function pagechange(current) {
    var total = atom.pages.total();

    // Skip invalid operations.
    if (current === 0 || !atom.pdf.document() || current > total) {
      return;
    }

    debug('loading page: %s of %s', current, total);

    var pdf = atom.pdf.document();
    var success = atom.pdf.page.set.bind(null);
    pdf.getPage(current).then(success, error);
  });

  // Watch for changes on the PDF.js page object. When it is updated trigger a
  // render.
  // TODO(jasoncampbell): To prevent rendering errors with frequent state
  // updates renders should be queued in a raf.
  atom.pdf.page(function pagechange(page) {
    debug('rendering page');
    var ratio = window.devicePixelRatio || 1.0;
    // TODO(jasoncampbell): Use state set scale instead of defaulting to 1.0.
    var scale = window.innerWidth/page.getViewport(ratio).width;
    var viewport = page.getViewport(scale);

    canvas.height = viewport.height;
    canvas.width = viewport.width;

    page.render({
      canvasContext: canvas.getContext('2d'),
      viewport: viewport
    }).promise.then(noop, error);
  });
});

function setPDF(pdf) {
  atom.pdf.document.set(pdf);
  atom.pages.total.set(pdf.numPages);
  atom.pages.current.set(1);
}

function progress(update) {
  var float = (update.loaded/update.total) * 100;
  var value = Math.floor(float);

  // For some reason the update.loaded value above can be higher than the
  // update.total value, in that case we can assume the progress is 100%.
  if (value > 100) {
    value = 100;
  }

  atom.progress.set(value);
}

function password() {
  debug('password required');
}

// TODO(jasoncampbell): Add better error reporting and exception capturing.
function error(err) {
  debug('error: %s', err.stack);
}

function noop() {}

function debug(template, args) {
  // Noop if debugging is disabled.
  if (!atom.debug()) {
    return;
  }

  template = 'pdf-viewer: ' + template;
  console.log.apply(console, arguments);
}
<|endoftext|>


<commit_before>// Set up for file references
var scriptsFile = __dirname + '/scripts.json',
    competitionsFile = __dirname + '/competitions.json',
    contributionsFile = __dirname + '/contributions.json';

// Load in all the files
var scripts = require(scriptsFile),
    competitions = require(competitionsFile),
    contributions = require(contributionsFile),
    fs = require('fs'),
    fetchRepoStats = require('./fetchRepoStats'),
    fetchMDNStats = require('./fetchMDNStats');

// Generate a srcUrl and pageUrl for scripts, competitions, and contributions
scripts.forEach(saveSrcUrl);
scripts.forEach(savePageUrl);
competitions.forEach(saveSrcUrl);
competitions.forEach(savePageUrl);
contributions.forEach(saveSrcUrl);
contributions.forEach(savePageUrl);

// Now and every hour from now, update file stats
var second = 1000,
    everyHour = second * 60 * 60;
function noop() {}
function updateScript(script) {
  // If it not a gist, update it
  var github = script.github || '';
  if (github.indexOf('gist') === -1) {
    fetchRepoStats(github, function (err, data) {
      // If there is no error, update the script
      if (!err) {
        if (data.stars !== undefined) { script.stars = data.stars; }
        if (data.forks !== undefined) { script.forks = data.forks; }
      }
    });
  }
}
function updateCompetition(competition) {
  // If it an mdn file, update it
  var mdn = competition.mdn;
  if (mdn) {
    fetchMDNStats(mdn, function (err, data) {
      // If there is no error, update the competition
      if (!err) {
        if (data.views !== undefined) { competition.views = data.views; }
        if (data.likes !== undefined) { competition.likes = data.likes; }
      }
    });
  }
}
function updateStats() {
  // Update each of the types
  scripts.forEach(updateScript);
  competitions.forEach(updateCompetition);
  contributions.forEach(updateScript);

  // If we are being run standalone, save file
  if (!module.parent) {
    // Sort the scripts and contributions by stars then forks
    function sortRepos(a, b) {
      var aStars = a.stars,
          bStars = b.stars;
      if (aStars < bStars) { return 1; }
      if (aStars > bStars) { return -1; }

      var aForks = a.forks,
          bForks = b.forks;
      if (aForks < bForks) { return 1; }
      if (aForks > bForks) { return -1; }

      var aName = a.name,
          bName = b.name;
      if (aName > bName) { return 1; }
      if (aName < bName) { return -1; }
      return 0;
    }
    scripts.sort(sortRepos);
    contributions.sort(sortRepos);

    // In a minute, save the updates to their respective JSON files
    setTimeout(function () {
      fs.writeFile(scriptsFile, JSON.stringify(scripts, null, 2), noop);
      fs.writeFile(competitionsFile, JSON.stringify(competitions, null, 2), noop);
      fs.writeFile(contributionsFile, JSON.stringify(contributions, null, 2), noop);
    }, 1000);
  }
}
// console.log('*** WARNING: OFFLINE FETCH IS DISABLED ***');
setInterval(updateStats, everyHour);
process.nextTick(updateStats);

console.log(scripts);
module.exports = {
  "scripts": scripts,
  "competitions": competitions,
  "contributions": contributions
};

function getSrcUrl(item) {
  // Get the github info and url fallback
  var github = item.github,
      srcUrl = item.srcUrl;

  // If there is a srcUrl, return now
  if (srcUrl !== undefined) {
    return srcUrl;
  }

  // If there is a github repo, use that
  if (github !== undefined) {
    // If the item is a gist, return it as such
    var isGist = github.indexOf('gist') > -1;
    if (isGist) {
      srcUrl = 'https://gist.github.com/' + github.slice(5);
    } else {
    // Otherwise, go with the normal github flow
      srcUrl = 'https://github.com/' + github;
    }
  }

  return srcUrl;
}

function getPageUrl(item) {
  var srcUrl = getSrcUrl(item),
      mdn = item.mdn,
      pageUrl = item.pageUrl;

  // If the pageUrl is not defined
  if (pageUrl === undefined) {
    // and mdn is defined, use that
    if (mdn !== undefined) {
      pageUrl = 'https://developer.mozilla.org/en-US/demos/detail/' + mdn;
    } else {
    // Otherwise, fallback to the srcUrl
      pageUrl = srcUrl;
    }
  }

  return pageUrl;
}

function saveSrcUrl(item) {
  var srcUrl = getSrcUrl(item);
  item.srcUrl = srcUrl;
}

function savePageUrl(item) {
  var pageUrl = getPageUrl(item);
  item.pageUrl = pageUrl;
}<commit_msg>Removing annoying console.log in projects<commit_after>// Set up for file references
var scriptsFile = __dirname + '/scripts.json',
    competitionsFile = __dirname + '/competitions.json',
    contributionsFile = __dirname + '/contributions.json';

// Load in all the files
var scripts = require(scriptsFile),
    competitions = require(competitionsFile),
    contributions = require(contributionsFile),
    fs = require('fs'),
    fetchRepoStats = require('./fetchRepoStats'),
    fetchMDNStats = require('./fetchMDNStats');

// Generate a srcUrl and pageUrl for scripts, competitions, and contributions
scripts.forEach(saveSrcUrl);
scripts.forEach(savePageUrl);
competitions.forEach(saveSrcUrl);
competitions.forEach(savePageUrl);
contributions.forEach(saveSrcUrl);
contributions.forEach(savePageUrl);

// Now and every hour from now, update file stats
var second = 1000,
    everyHour = second * 60 * 60;
function noop() {}
function updateScript(script) {
  // If it not a gist, update it
  var github = script.github || '';
  if (github.indexOf('gist') === -1) {
    fetchRepoStats(github, function (err, data) {
      // If there is no error, update the script
      if (!err) {
        if (data.stars !== undefined) { script.stars = data.stars; }
        if (data.forks !== undefined) { script.forks = data.forks; }
      }
    });
  }
}
function updateCompetition(competition) {
  // If it an mdn file, update it
  var mdn = competition.mdn;
  if (mdn) {
    fetchMDNStats(mdn, function (err, data) {
      // If there is no error, update the competition
      if (!err) {
        if (data.views !== undefined) { competition.views = data.views; }
        if (data.likes !== undefined) { competition.likes = data.likes; }
      }
    });
  }
}
function updateStats() {
  // Update each of the types
  scripts.forEach(updateScript);
  competitions.forEach(updateCompetition);
  contributions.forEach(updateScript);

  // If we are being run standalone, save file
  if (!module.parent) {
    // Sort the scripts and contributions by stars then forks
    function sortRepos(a, b) {
      var aStars = a.stars,
          bStars = b.stars;
      if (aStars < bStars) { return 1; }
      if (aStars > bStars) { return -1; }

      var aForks = a.forks,
          bForks = b.forks;
      if (aForks < bForks) { return 1; }
      if (aForks > bForks) { return -1; }

      var aName = a.name,
          bName = b.name;
      if (aName > bName) { return 1; }
      if (aName < bName) { return -1; }
      return 0;
    }
    scripts.sort(sortRepos);
    contributions.sort(sortRepos);

    // In a minute, save the updates to their respective JSON files
    setTimeout(function () {
      fs.writeFile(scriptsFile, JSON.stringify(scripts, null, 2), noop);
      fs.writeFile(competitionsFile, JSON.stringify(competitions, null, 2), noop);
      fs.writeFile(contributionsFile, JSON.stringify(contributions, null, 2), noop);
    }, 1000);
  }
}
// console.log('*** WARNING: OFFLINE FETCH IS DISABLED ***');
setInterval(updateStats, everyHour);
process.nextTick(updateStats);

module.exports = {
  "scripts": scripts,
  "competitions": competitions,
  "contributions": contributions
};

function getSrcUrl(item) {
  // Get the github info and url fallback
  var github = item.github,
      srcUrl = item.srcUrl;

  // If there is a srcUrl, return now
  if (srcUrl !== undefined) {
    return srcUrl;
  }

  // If there is a github repo, use that
  if (github !== undefined) {
    // If the item is a gist, return it as such
    var isGist = github.indexOf('gist') > -1;
    if (isGist) {
      srcUrl = 'https://gist.github.com/' + github.slice(5);
    } else {
    // Otherwise, go with the normal github flow
      srcUrl = 'https://github.com/' + github;
    }
  }

  return srcUrl;
}

function getPageUrl(item) {
  var srcUrl = getSrcUrl(item),
      mdn = item.mdn,
      pageUrl = item.pageUrl;

  // If the pageUrl is not defined
  if (pageUrl === undefined) {
    // and mdn is defined, use that
    if (mdn !== undefined) {
      pageUrl = 'https://developer.mozilla.org/en-US/demos/detail/' + mdn;
    } else {
    // Otherwise, fallback to the srcUrl
      pageUrl = srcUrl;
    }
  }

  return pageUrl;
}

function saveSrcUrl(item) {
  var srcUrl = getSrcUrl(item);
  item.srcUrl = srcUrl;
}

function savePageUrl(item) {
  var pageUrl = getPageUrl(item);
  item.pageUrl = pageUrl;
}<|endoftext|>