<commit_before>$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})
$('#logs').on('show.bs.modal', function (event) {
  var link = $(event.relatedTarget);
  var stage = link.data('stage');

  $.ajax('/api/stages/' + stage + '/logs', {
        dataType: "json",
        success: function (array) {
            var dest = $('#logs').find('.modal-body');
            console.log('success ' + array);
            $.each(array, function(index, element) {
                dest.append($('' + element + '
'));
            });
        }
    });
})

var dashboard = dashboard || {};

dashboard = {
    init: function () {
        this.bootstrap.init();
        this.stages.init();
        this.feedback.init();
    },

    bootstrap: {
        init: function () {
            $('#search').on('keyup', dashboard.stages.filter);
            $('#category').on('change', dashboard.stages.filter);
        }
    },

    stages: {
        init: function () {
            dashboard.stages.reload();
            $('[data-action]').on('click', dashboard.stages.action);
            $('#loading').remove();
        },

        reload: function () {
            $.ajax('/api/stages?select=apps,comment,expire,last-modified-by,running', {
                dataType: "json",
                success: function (data) {
                    var allStages = $('#all-stages');
                    var done = [];
                    $.each(data, function (name, status) {
                        var name;
                        var oldTr;
                        var up;
                        var htmlSt;
                        var htmlName;
                        var htmlRestart;
                        var htmlMenu;
                        var newTr;
                        var actions;

                        done.push(name);
                        oldTr = allStages.find('[data-name="' + name + '"]');
                        up = status.running !== "";
                        htmlSt = "\n" +
                                 "
\n" +
                                 "    " + (up ? "up" : "down") + "\n"
                                 "
\n" +
                                 "	";
                        htmlName = "\n" +
                                   "  " + name + "	";
                        htmlRestart = " \n" +
                                      "   <button class='btn btn-light btn-sm' type='button' data-action='restart' data-stage='" + name + "'>\n" +
                                      "      Restart\n" +
                                      "   </button>\n"
                                      " 	"
                        htmlMenu = "\n" +
                                   "
\n" +
                                   "    <button type='button' class='btn btn-light btn-sm dropdown-toggle' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>More</button>\n" +
                                   "
\n" +
                                   "      Start\n" +
                                   "      Stop\n" +
                                   "      Expire in 1 week\n" +
                                   "      \n" +
                                   "         Share\n"
                                   "      Log files ...\n" +
                                   "      Remove\n"
                                   "
\n" +
                                   "
\n" +
                                   "	\n"

                        newTr = "
\n" +
                                   htmlSt +
                                   htmlName +
                                   "" + status.apps + "	\n" +
                                   "" + status.expire + "	\n" +
                                   "" + status["last-modified-by"] + "	\n" +
                                   htmlRestart +
                                   htmlMenu
                                   "
";
                        if (oldTr.length === 0) {
                            // new stage
                            allStages.append(newTr);
                            $(allStages).find('[data-name="' + name + '"]').find('[data-action]').on('click', dashboard.stages.action);
                        } else if ($(oldTr).attr("data-content-hash") !== $(newTr).attr("data-content-hash")) {
                            // updated stage
                            oldTr.replaceWith(newTr);
                            $(allStages).find('[data-name="' + name + '"]').find('[data-action]').on('click', dashboard.stages.action);
                        } else {
                            // no changes
                        }
                    });
                    $(allStages).children("tr").each(function (i, tr) {
                        var name;

                        name = $(tr).attr("data-name");
                        if (!done.includes(name)) {
                            $(tr).find('[data-action]').off('click', dashboard.stages.action);
                            tr.remove();
                        }
                    });
                    $('[data-toggle="popover"]').popover();
                    dashboard.stages.filter();
                }
            });
        },

        action: function () {
            var stage, action, arguments, i, url, box;
            if ($(this).parent().hasClass('disabled') && $(this).attr('data-stage') === null) {
                return false;
            }

            stage = $(this).attr('data-stage');
            action = $(this).attr('data-action');
            arguments = $(this).attr('data-arguments');

            $('#' + stage + ' a.action').addClass('disabled');
            box = $('#progress');
            box.modal('show');
            box.find('.modal-header').html("
" + action + " " + stage + "
");

            if (action == "restart") {
                $.post("/api/stages/" + stage + "/stop").fail(function (r) {
                    box.find('.modal-body').html('
failed: ' + r + '
');
                    dashboard.stages.reload();
                }).done(function (r) {
                    $.post("/api/stages/" + stage + "/start").fail(function (r) {
                        box.find('.modal-body').html('
failed: ' + r + '
');
                        dashboard.stages.reload();
                    }).done(function (r) {
                        // TODO: doesn't work if the browser is extremely slow (or busy)
                        // from https://stackoverflow.com/questions/51637199/bootstrap-4-open-modal-a-close-modal-a-open-modal-b-a-not-closing
                        setTimeout( function() { box.modal("hide"); }, 500 );
                        dashboard.stages.reload();
                    });
                });
            } else {
                url = "/api/stages/" + stage + "/" + action;
                if (arguments != null) {
                    url = url + "?" + arguments;
                }
                $.post(url).fail(function (r) {
                    box.find('.modal-body').html('
failed: ' + r + '
');
                    dashboard.stages.reload();
                }).done(function (r) {
                    // TODO: doesn't work if the browser is extremely slow (or busy)
                    // from https://stackoverflow.com/questions/51637199/bootstrap-4-open-modal-a-close-modal-a-open-modal-b-a-not-closing
                    setTimeout( function() { box.modal("hide"); }, 500 );
                    dashboard.stages.reload();
                });
            }
            return false;
        },

        filter: function () {
            var search = $('#search').val().toLowerCase();
            var category = $('#category').val().toLowerCase();

            found = 0;
            $('#all-stages').find('tr.stage').each(function (idx, tr) {
                $(this).toggle(true);
                found++;
            });

            $('#empty').toggle(found == 0);
        }
    },

    feedback: {
        init: function () {
            $('#feedback-submit').on('click', function (e) {
                e.preventDefault();
                var text = $('#feedback-text');
                if (text.val() !== "") {
                    $.post("/ui/feedback", { message: text.val()})
                        .done(function () {
                            text.parent().parent().prepend("
Thanks.
");
                            text.val("");
                        })
                        .fail(function () {
                            text.parent().parent()
                                .prepend("
Opps, something went wrong =(.
");
                        });
                }
            });
        }
    }
};
dashboard.init();
<commit_msg>content-hash<commit_after>$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})
$('#logs').on('show.bs.modal', function (event) {
  var link = $(event.relatedTarget);
  var stage = link.data('stage');

  $.ajax('/api/stages/' + stage + '/logs', {
        dataType: "json",
        success: function (array) {
            var dest = $('#logs').find('.modal-body');
            console.log('success ' + array);
            $.each(array, function(index, element) {
                dest.append($('' + element + '
'));
            });
        }
    });
})

// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
hashCode = function(s) {
  var h = 0, l = s.length, i = 0;
  if ( l > 0 )
    while (i < l)
      h = (h << 5) - h + s.charCodeAt(i++) | 0;
  return h;
};

var dashboard = dashboard || {};

dashboard = {
    init: function () {
        this.bootstrap.init();
        this.stages.init();
        this.feedback.init();
    },

    bootstrap: {
        init: function () {
            $('#search').on('keyup', dashboard.stages.filter);
            $('#category').on('change', dashboard.stages.filter);
        }
    },

    stages: {
        init: function () {
            dashboard.stages.reload();
            $('[data-action]').on('click', dashboard.stages.action);
            $('#loading').remove();
        },

        reload: function () {
            $.ajax('/api/stages?select=apps,comment,expire,last-modified-by,running', {
                dataType: "json",
                success: function (data) {
                    var allStages = $('#all-stages');
                    var done = [];
                    $.each(data, function (name, status) {
                        var name;
                        var oldTr;
                        var up;
                        var htmlSt;
                        var htmlName;
                        var htmlRestart;
                        var htmlMenu;
                        var newTr;
                        var newHash;
                        var actions;

                        done.push(name);
                        oldTr = allStages.find('[data-name="' + name + '"]');
                        up = status.running !== "";
                        htmlSt = "\n" +
                                 "
\n" +
                                 "    " + (up ? "up" : "down") + "\n"
                                 "
\n" +
                                 "	";
                        htmlName = "\n" +
                                   "  " + name + "	";
                        htmlRestart = " \n" +
                                      "   <button class='btn btn-light btn-sm' type='button' data-action='restart' data-stage='" + name + "'>\n" +
                                      "      Restart\n" +
                                      "   </button>\n"
                                      " 	"
                        htmlMenu = "\n" +
                                   "
\n" +
                                   "    <button type='button' class='btn btn-light btn-sm dropdown-toggle' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>More</button>\n" +
                                   "
\n" +
                                   "      Start\n" +
                                   "      Stop\n" +
                                   "      Expire in 1 week\n" +
                                   "      \n" +
                                   "         Share\n"
                                   "      Log files ...\n" +
                                   "      Remove\n"
                                   "
\n" +
                                   "
\n" +
                                   "	\n"

                        newTr = "
\n" +
                                   htmlSt +
                                   htmlName +
                                   "" + status.apps + "	\n" +
                                   "" + status.expire + "	\n" +
                                   "" + status["last-modified-by"] + "	\n" +
                                   htmlRestart +
                                   htmlMenu
                                   "
";
                        newHash = String(hashCode(newTr));
                        if (oldTr.length === 0) {
                            // new stage
                            console.log("append " + name)
                            var appended = allStages.append(newTr);
                            appended = appended.find('[data-name="' + name + '"]');
                            appended.attr("data-content-hash", newHash);
                            appended.find('[data-action]').on('click', dashboard.stages.action);
                        } else if ($(oldTr).attr("data-content-hash") !== newHash) {
                            // updated stage
                            console.log("replace " + name)
                            oldTr.replaceWith(newTr); // replaceWith returns the removed elements
                            replaced = allStages.find('[data-name="' + name + '"]');
                            replaced.attr("data-content-hash", newHash);
                            replaced.find('[data-action]').on('click', dashboard.stages.action);
                        } else {
                            console.log("same " + name)
                            // no changes
                        }
                    });
                    $(allStages).children("tr").each(function (i, tr) {
                        var name;

                        name = $(tr).attr("data-name");
                        if (!done.includes(name)) {
                            $(tr).find('[data-action]').off('click', dashboard.stages.action);
                            tr.remove();
                        }
                    });
                    $('[data-toggle="popover"]').popover();
                    dashboard.stages.filter();
                }
            });
        },

        action: function () {
            var stage, action, arguments, i, url, box;
            if ($(this).parent().hasClass('disabled') && $(this).attr('data-stage') === null) {
                return false;
            }

            stage = $(this).attr('data-stage');
            action = $(this).attr('data-action');
            arguments = $(this).attr('data-arguments');

            $('#' + stage + ' a.action').addClass('disabled');
            box = $('#progress');
            box.modal('show');
            box.find('.modal-header').html("
" + action + " " + stage + "
");

            if (action == "restart") {
                $.post("/api/stages/" + stage + "/stop").fail(function (r) {
                    box.find('.modal-body').html('
failed: ' + r + '
');
                    dashboard.stages.reload();
                }).done(function (r) {
                    $.post("/api/stages/" + stage + "/start").fail(function (r) {
                        box.find('.modal-body').html('
failed: ' + r + '
');
                        dashboard.stages.reload();
                    }).done(function (r) {
                        // TODO: doesn't work if the browser is extremely slow (or busy)
                        // from https://stackoverflow.com/questions/51637199/bootstrap-4-open-modal-a-close-modal-a-open-modal-b-a-not-closing
                        setTimeout( function() { box.modal("hide"); }, 500 );
                        dashboard.stages.reload();
                    });
                });
            } else {
                url = "/api/stages/" + stage + "/" + action;
                if (arguments != null) {
                    url = url + "?" + arguments;
                }
                $.post(url).fail(function (r) {
                    box.find('.modal-body').html('
failed: ' + r + '
');
                    dashboard.stages.reload();
                }).done(function (r) {
                    // TODO: doesn't work if the browser is extremely slow (or busy)
                    // from https://stackoverflow.com/questions/51637199/bootstrap-4-open-modal-a-close-modal-a-open-modal-b-a-not-closing
                    setTimeout( function() { box.modal("hide"); }, 500 );
                    dashboard.stages.reload();
                });
            }
            return false;
        },

        filter: function () {
            var search = $('#search').val().toLowerCase();
            var category = $('#category').val().toLowerCase();

            found = 0;
            $('#all-stages').find('tr.stage').each(function (idx, tr) {
                $(this).toggle(true);
                found++;
            });

            $('#empty').toggle(found == 0);
        }
    },

    feedback: {
        init: function () {
            $('#feedback-submit').on('click', function (e) {
                e.preventDefault();
                var text = $('#feedback-text');
                if (text.val() !== "") {
                    $.post("/ui/feedback", { message: text.val()})
                        .done(function () {
                            text.parent().parent().prepend("
Thanks.
");
                            text.val("");
                        })
                        .fail(function () {
                            text.parent().parent()
                                .prepend("
Opps, something went wrong =(.
");
                        });
                }
            });
        }
    }
};
dashboard.init();
<|endoftext|>


<commit_before>var currentTestName = "";
var currentSteps = [];
var currentStepIndex = 0;

var testCases = [];
var currentlyExecutingTest = 0;

var testMode = "manual";      // can be "auto"
var isRecursive = false;
var runningRecursive = false;

var snapshotPrefix = "";
var snapshotIndex = 0;

var advanceKey = "n";

TestCase = function (name, path, func) {
    this.name = name;
    this.path = path;
    this.func = func;
}

var currentTestCase = null;
var usePrimaryCamera = false;
var currentRecursiveTestCompleted = false;

//returns n as a string, padded to length characters with the character ch
function pad(n, length, ch) {
  ch = ch || '0';  // default is '0'
  n += '';         // convert n to string

  // returns n as is, if it is too long
  return (n.length >= length) ? n : new Array(length - n.length + 1).join(ch) + n;
}

var runOneStep = function (stepFunctor, stepIndex) {
    print("Running step " + (stepIndex + 1) + "/" + (currentSteps.length) +": " + stepFunctor.name);
    Window.displayAnnouncement("Running step " + (stepIndex + 1) + "/" + (currentSteps.length) +": " + stepFunctor.name);

    if (stepFunctor.func !== undefined) {
        stepFunctor.func();
    }

    // Not quite sure this is the definitive solution here because of the snapshot bug latency issue.
    // but this seems to work ok if the snapshot is a separate step
    if ((stepFunctor.snap !== undefined) && stepFunctor.snap) {
        print("Taking snapshot " + (stepIndex + 1));

        // Image numbers are padded to 5 digits
        // Changing this number requires changing the auto-tester C++ code!
        var NUM_DIGITS = 5;
        var currentSnapshotName = snapshotPrefix + pad(snapshotIndex, NUM_DIGITS, '0');;
        usePrimaryCamera ? Window.takeSnapshot(false, false, 0.0, currentSnapshotName) : Window.takeSecondaryCameraSnapshot(currentSnapshotName);
        ++snapshotIndex;
    }
}

var runNextStep = function () {
    // Run next step and increment only if there is one more
    if (currentStepIndex < currentSteps.length) {
        runOneStep(currentSteps[currentStepIndex], currentStepIndex);
        currentStepIndex++;
    }

    // Return true to go on or false if done
    return (currentStepIndex < currentSteps.length);
}

var testOver = function() {
    if (testMode === "manual") {
        Controller.keyPressEvent.disconnect(onKeyPressEventNextStep);
        Window.displayAnnouncement("Test " + currentTestName + " have been completed");
    }

    currentSteps = [];
    currentStepIndex = 0;
    currentTestName = "";
    currentTestCase = null;

    if (isRecursive) {
        currentRecursiveTestCompleted = true;
    } else {
        // Just stop the script
        Script.stop();
    }
}

var autoTimeStep = 2000;

var onRunAutoNext = function() {
    // run the step...
    if (!runNextStep()) {
        testOver();
        return;
    }

    // and call itself after next timer
    Script.setTimeout(
        onRunAutoNext,
        autoTimeStep
    );
}

var onKeyPressEventNextStep = function (event) {
    if (String.fromCharCode(event.key) == advanceKey.toUpperCase()) {
        if (!runNextStep()) {
            testOver();
        }
    }
}

var onRunManual = function() {
    Window.displayAnnouncement(
        "Ready to run test " + currentTestName + "\n" +
        currentSteps.length + " steps\nPress " + "'" + advanceKey + "'" + " for next steps");

    Controller.keyPressEvent.connect(onKeyPressEventNextStep);
}

var onRunAuto = function() {
    // run the next step after next timer
    Script.setTimeout(
        onRunAutoNext,
        autoTimeStep
    );
}

// Add Steps to the test case
var doAddStep = function (name, stepFunction, snapshot) {
    currentSteps.push({"index": currentSteps.length, "name": name, "func": stepFunction, "snap": snapshot })
    print("PUSHING STEP" + currentSteps.length);
}

// The following are exported methods, accessible to test scripts

// Perform is the main method of a test
//      testName - name of the test
//      testPath - path the test is executing in
//      testMain - a function that creates the test
//
// The method creates a test case in currentTestCase.
// If the test mode is manual or auto then its execution is started
module.exports.perform = function (testName, testPath, testMain) {
    currentTestCase = new TestCase(testName, testPath, testMain);

    // Manual and auto tests are run immediately, recursive tests are stored in a queue
    if (isRecursive) {
        print("Not running yet - in recursive mode");
        testCases.push(currentTestCase);
    } else if (testMode === "manual") {
        print("Begin manual test:" + testName);
        currentTestCase.func("manual");
    } else { // testMode === "auto"
        print("Begin auto test:" + testName);
        currentTestCase.func("auto");
    }
}

module.exports.setupTest = function (primaryCamera) {
    if (currentTestCase === null) {
        return;
    }

    // Clear the test case steps
    currentSteps = [];
    currentStepIndex = 0;
    currentTestName = currentTestCase.name;

    print("Setup test - " + currentTestName);

    // Hide the avatar
    MyAvatar.setEnableMeshVisible(false);

    // Zero the head position
    MyAvatar.bodyYaw =   0.0;
    MyAvatar.bodyPitch = 0.0;
    MyAvatar.bodyRoll =  0.0;
    MyAvatar.headYaw =   0.0;
    MyAvatar.headPitch = 0.0;
    MyAvatar.headRoll =  0.0;

    // resolvePath(".") returns a string that looks like "file:/" + <current folder>
    // We need the current folder
    var path = currentTestCase.path.substring(currentTestCase.path.indexOf(":") + 4);
    var pathParts = path.split("/");

    // Snapshots are saved in the user-selected folder
    // For a test running from D:/GitHub/hifi-tests/tests/content/entity/zone/create/tests.js
    // the tests are named D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg and so on
    // Date and time are not used as part of the name, to keep the path lengths to a minimum
    // (the Windows API limit is 260 characters).
    //
    snapshotPrefix = "";

    // On Windows the first part is <disk>: - this is changed
    var str = pathParts[0];
    if (str.indexOf(":") !== -1) {
        snapshotPrefix = str.replace(":", "").toUpperCase();
    }

    for (var i = 1; i < pathParts.length; ++i) {
        str = pathParts[i];

        // Replace any spaces
        str = str.replace(" ", "__");

        snapshotPrefix += "_" + str;
    }

    snapshotIndex = 0;

    var spectatorCameraConfig = Render.getConfig("SecondaryCamera");
    spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true);
    spectatorCameraConfig.resetSizeSpectatorCamera(1920, 1080);
    spectatorCameraConfig.vFoV = 45;
    Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0;
	  Render.getConfig("SecondaryCameraJob.DrawHighlight").enabled = false;

    // Configure the secondary camera
    spectatorCameraConfig.position = {x: MyAvatar.position.x, y: MyAvatar.position.y + 0.6, z: MyAvatar.position.z};
    spectatorCameraConfig.orientation = MyAvatar.orientation;

    if (primaryCamera) {
        usePrimaryCamera = true;
    }

    return spectatorCameraConfig;
}

// Add steps to the test case, take snapshot if 3rd parameter is true
module.exports.addStep = function (name, stepFunction) {
    doAddStep(name, stepFunction, false);
}

// Add steps to the test case, take snapshot
module.exports.addStepSnapshot = function (name, stepFunction) {
    doAddStep(name, stepFunction, true);
}

// The default mode is manual
// The default time between test steps may be modified through these methods
module.exports.enableAuto = function (timeStep) {
    testMode = "auto";
    if (timeStep) {
        autoTimeStep = timeStep;
    }
    print("TEST MODE AUTO SELECTED");
}

module.exports.enableRecursive = function (timeStep) {
    isRecursive = true;
    if (timeStep) {
        autoTimeStep = timeStep;
    }
    print("TEST MODE RECURSIVE SELECTED");
}

// Steps is an array of functions; each function being a test step
module.exports.runTest = function (testType) {
    // In recursive mode, this call is ignored during script load
    if (isRecursive && !runningRecursive) {
        return;
    }

    if (testType  === "auto") {
        onRunAuto();
    } else { // testType === "manual"
        onRunManual();
    }
}

module.exports.runRecursive = function () {
    print("Starting recursive tests");
    runningRecursive = true;

    currentRecursiveTestCompleted = true;
    Script.setInterval(
        function () {
            if (currentRecursiveTestCompleted) {
                currentRecursiveTestCompleted = false;
                if (testCases.length > 0) {
                    currentTestCase = testCases.pop();
                    currentTestCase.func(testMode);
                } else {
                    print("Recursive tests complete");
                    Script.stop();
                }
            }
        },
        1000
    );
}<commit_msg>Use simple avatar for tests.<commit_after>var currentTestName = "";
var currentSteps = [];
var currentStepIndex = 0;

var testCases = [];
var currentlyExecutingTest = 0;

var testMode = "manual";      // can be "auto"
var isRecursive = false;
var runningRecursive = false;

var snapshotPrefix = "";
var snapshotIndex = 0;

var advanceKey = "n";
var pathSeparator = ".";

var previousSkeletonURL;

TestCase = function (name, path, func) {
    this.name = name;
    this.path = path;
    this.func = func;
}

var currentTestCase = null;
var usePrimaryCamera = false;
var currentRecursiveTestCompleted = false;

//returns n as a string, padded to length characters with the character ch
function pad(n, length, ch) {
  ch = ch || '0';  // default is '0'
  n += '';         // convert n to string

  // returns n as is, if it is too long
  return (n.length >= length) ? n : new Array(length - n.length + 1).join(ch) + n;
}

var runOneStep = function (stepFunctor, stepIndex) {
    print("Running step " + (stepIndex + 1) + "/" + (currentSteps.length) +": " + stepFunctor.name);
    Window.displayAnnouncement("Running step " + (stepIndex + 1) + "/" + (currentSteps.length) +": " + stepFunctor.name);

    if (stepFunctor.func !== undefined) {
        stepFunctor.func();
    }

    // Not quite sure this is the definitive solution here because of the snapshot bug latency issue.
    // but this seems to work ok if the snapshot is a separate step
    if ((stepFunctor.snap !== undefined) && stepFunctor.snap) {
        print("Taking snapshot " + (stepIndex + 1));

        // Image numbers are padded to 5 digits
        // Changing this number requires changing the auto-tester C++ code!
        var NUM_DIGITS = 5;
        var currentSnapshotName = snapshotPrefix + pad(snapshotIndex, NUM_DIGITS, '0');;
        usePrimaryCamera ? Window.takeSnapshot(false, false, 0.0, currentSnapshotName) : Window.takeSecondaryCameraSnapshot(currentSnapshotName);
        ++snapshotIndex;
    }
}

var runNextStep = function () {
    // Run next step and increment only if there is one more
    if (currentStepIndex < currentSteps.length) {
        runOneStep(currentSteps[currentStepIndex], currentStepIndex);
        currentStepIndex++;
    }

    // Return true to go on or false if done
    return (currentStepIndex < currentSteps.length);
}

var testOver = function() {
    if (testMode === "manual") {
        Controller.keyPressEvent.disconnect(onKeyPressEventNextStep);
        Window.displayAnnouncement("Test " + currentTestName + " have been completed");
    }

    currentSteps = [];
    currentStepIndex = 0;
    currentTestName = "";
    currentTestCase = null;

    // Restore avatar
    MyAvatar.skeletonModelURL = previousSkeletonURL;
    MyAvatar.clearJointsData();

    if (isRecursive) {
        currentRecursiveTestCompleted = true;
    } else {
        // Just stop the script
        Script.stop();
    }
}

var autoTimeStep = 2000;

var onRunAutoNext = function() {
    // run the step...
    if (!runNextStep()) {
        testOver();
        return;
    }

    // and call itself after next timer
    Script.setTimeout(
        onRunAutoNext,
        autoTimeStep
    );
}

var onKeyPressEventNextStep = function (event) {
    if (String.fromCharCode(event.key) == advanceKey.toUpperCase()) {
        if (!runNextStep()) {
            testOver();
        }
    }
}

var onRunManual = function() {
    Window.displayAnnouncement(
        "Ready to run test " + currentTestName + "\n" +
        currentSteps.length + " steps\nPress " + "'" + advanceKey + "'" + " for next steps");

    Controller.keyPressEvent.connect(onKeyPressEventNextStep);
}

var onRunAuto = function() {
    // run the next step after next timer
    Script.setTimeout(
        onRunAutoNext,
        autoTimeStep
    );
}

// Add Steps to the test case
var doAddStep = function (name, stepFunction, snapshot) {
    currentSteps.push({"index": currentSteps.length, "name": name, "func": stepFunction, "snap": snapshot })
    print("PUSHING STEP" + currentSteps.length);
}

// The following are exported methods, accessible to test scripts

// Perform is the main method of a test
//      testName - name of the test
//      testPath - path the test is executing in
//      testMain - a function that creates the test
//
// The method creates a test case in currentTestCase.
// If the test mode is manual or auto then its execution is started
module.exports.perform = function (testName, testPath, testMain) {
    currentTestCase = new TestCase(testName, testPath, testMain);

    // Manual and auto tests are run immediately, recursive tests are stored in a queue
    if (isRecursive) {
        print("Not running yet - in recursive mode");
        testCases.push(currentTestCase);
    } else if (testMode === "manual") {
        print("Begin manual test:" + testName);
        currentTestCase.func("manual");
    } else { // testMode === "auto"
        print("Begin auto test:" + testName);
        currentTestCase.func("auto");
    }
}

module.exports.setupTest = function (primaryCamera) {
    // Use the default avatar.  This is a simple model that has no animations when in T-pose
    previousSkeletonURL = MyAvatar.skeletonModelURL;
    MyAvatar.skeletonModelURL = "https://highfidelity.com/api/v1/commerce/entity_edition/813addb9-b985-49c8-9912-36fdbb57e04a.fst?certificate_id=MEUCIQDgYR2%2BOrCh5HXeHCm%2BkR0a2JniEO%2BY4y9tbApxCAPo4wIgXZEQdI4cQc%2FstAcr9tFT9k4k%2Fbuj3ufB1aB4W0tjIJc%3D";

    // Enter T-pose
    var i, length, rotation, translation;
    for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) {
       rotation = MyAvatar.getDefaultJointRotation(i);
       translation = MyAvatar.getDefaultJointTranslation(i);
       MyAvatar.setJointData(i, rotation, translation);
    }

    if (currentTestCase === null) {
        return;
    }

    // Clear the test case steps
    currentSteps = [];
    currentStepIndex = 0;
    currentTestName = currentTestCase.name;

    print("Setup test - " + currentTestName);

    // Hide the avatar
    MyAvatar.setEnableMeshVisible(false);

    // Zero the head position
    MyAvatar.bodyYaw =   0.0;
    MyAvatar.bodyPitch = 0.0;
    MyAvatar.bodyRoll =  0.0;
    MyAvatar.headYaw =   0.0;
    MyAvatar.headPitch = 0.0;
    MyAvatar.headRoll =  0.0;

    // resolvePath(".") returns a string that looks like "file:/" + <current folder>
    // We need the current folder
    var path = currentTestCase.path.substring(currentTestCase.path.indexOf(":") + 4);
    var pathParts = path.split("/");

    // Snapshots are saved in the user-selected folder
    // For a test running from D:/GitHub/hifi-tests/tests/content/entity/zone/create/tests.js
    // the tests are named tests.content.entity.zone.create.00000.jpg and so on
    // (assuming pathSeparator is ".")
    // Date and time are not used as part of the name, to keep the path lengths to a minimum
    // (the Windows API limit is 260 characters).
    //
    // Find location of "tests"
    var testsIndex;
    for (testsIndex = pathParts.length - 1; testsIndex > 0; --testsIndex) {
        if (pathParts[testsIndex] === "tests") {
            break;
        }
    }

    snapshotPrefix = pathParts[testsIndex];
    for (var i = testsIndex + 1; i < pathParts.length; ++i) {
        snapshotPrefix += pathSeparator + pathParts[i];
    }

    snapshotIndex = 0;

    var spectatorCameraConfig = Render.getConfig("SecondaryCamera");
    spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true);
    spectatorCameraConfig.resetSizeSpectatorCamera(1920, 1080);
    spectatorCameraConfig.vFoV = 45;
    Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0;
	  Render.getConfig("SecondaryCameraJob.DrawHighlight").enabled = false;

    // Configure the secondary camera
    spectatorCameraConfig.position = {x: MyAvatar.position.x, y: MyAvatar.position.y + 0.6, z: MyAvatar.position.z};
    spectatorCameraConfig.orientation = MyAvatar.orientation;

    usePrimaryCamera = (primaryCamera !== undefined && primaryCamera);

    return spectatorCameraConfig;
}

// Add steps to the test case, take snapshot if 3rd parameter is true
module.exports.addStep = function (name, stepFunction) {
    doAddStep(name, stepFunction, false);
}

// Add steps to the test case, take snapshot
module.exports.addStepSnapshot = function (name, stepFunction) {
    doAddStep(name, stepFunction, true);
}

// The default mode is manual
// The default time between test steps may be modified through these methods
module.exports.enableAuto = function (timeStep) {
    testMode = "auto";
    if (timeStep) {
        autoTimeStep = timeStep;
    }
    print("TEST MODE AUTO SELECTED");
}

module.exports.enableRecursive = function (timeStep) {
    isRecursive = true;
    if (timeStep) {
        autoTimeStep = timeStep;
    }
    print("TEST MODE RECURSIVE SELECTED");
}

// Steps is an array of functions; each function being a test step
module.exports.runTest = function (testType) {
    // In recursive mode, this call is ignored during script load
    if (isRecursive && !runningRecursive) {
        return;
    }

    if (testType  === "auto") {
        onRunAuto();
    } else { // testType === "manual"
        onRunManual();
    }
}

module.exports.runRecursive = function () {
    print("Starting recursive tests");
    runningRecursive = true;

    currentRecursiveTestCompleted = true;
    Script.setInterval(
        function () {
            if (currentRecursiveTestCompleted) {
                currentRecursiveTestCompleted = false;
                if (testCases.length > 0) {
                    currentTestCase = testCases.pop();
                    currentTestCase.func(testMode);
                } else {
                    print("Recursive tests complete");
                    Script.stop();
                }
            }
        },
        1000
    );
}<|endoftext|>


<commit_before>// this function is also present in assignment-edit-views.js, we need to consolidate using moment.js
function formatDate(value) {
  if(!Ember.none(value)) {
    return value.getFullYear() + "/" + (value.getMonth() + 1) + "/" + value.getDate();
  } else return null;
}

FLOW.QuestionAnswerView = Ember.View.extend({
  isTextType: false,
  isOptionType: false,
  isNumberType: false,
  isBarcodeType: false,
  isDateType: false,
  isPhotoType: false,
  optionsList: [],
  content: null,
  optionChoice: null,
  inEditMode: false,
  isNotEditable: false,
  value: null,
  numberValue:null,
  date: null,
  photoUrl:null,

  init: function() {
    this._super();
    this.doInit();
  },

doInit: function() {
    var q, questionId, type;

    // TODO use filter instead: if the question is not yet there, don't do anything
    // it will be picked up later at isLoaded.
    questionId = this.content.get('questionID');
    q = FLOW.store.find(FLOW.Question, questionId);
    type = q.get('type');
    this.set('isTextType', type == 'FREE_TEXT');
    this.set('isOptionType', type == 'OPTION');
    this.set('isNumberType', type == 'NUMBER');
    this.set('isBarcodeType', type == 'BARCODE');
    this.set('isPhotoType', type == 'PHOTO');
    this.set('isDateType', type == 'DATE');
    this.set('isNotEditable', (type == 'GEO' || type == 'PHOTO' || type == 'VIDEO'));

    this.setInitialValue();
  }.observes('FLOW.questionControl.content.isLoaded'),

  setInitialValue: function() {
    var opList, opListArray, i, sizeList, q, questionId, qaValue, choice = null, date;

    questionId = this.content.get('questionID');
    q = FLOW.store.find(FLOW.Question, questionId);

    // set value
    this.set('value', this.content.get('value'));

    if(this.get('isNumberType')) {
      this.set('numberValue',this.content.get('value'));
    }

    if(this.get('isDateType') && !Ember.none(this.content.get('value'))) {
      date = new Date(parseInt(this.content.get('value'),10));
      this.set('date',formatDate(date));
    }
    // fill option list
    if(this.get('isOptionType') && q.get('optionList') !== null) {
      this.set('optionsList', []);
      opList = q.get('optionList');
      opListArray = opList.split('\n');
      sizeList = opListArray.length;

      for(i = 0; i < sizeList; i++) {
        this.get('optionsList').push(Ember.Object.create({
          isSelected: false,
          value: opListArray[i]
        }));
      }
      // set answer
      qaValue = this.content.get('value');
      this.get('optionsList').forEach(function(item) {
        if(item.get('value') == qaValue) {
          choice = item;
        }
      });
      this.set('optionChoice', choice);
    }
    if (this.get('isPhotoType') && !Ember.empty(this.content.get('value'))){
      // Since photos have a leading path from devices that we need to trim
      this.set('photoUrl',FLOW.Env.photo_url_root + this.content.get('value').split('/').pop());
    }
  },

  doEdit: function() {
    this.set('inEditMode', true);
  },

  doCancel: function() {
    // revert answer
    this.setInitialValue();
    this.set('inEditMode', false);
  },

  doSave: function() {
    var tempDate = null;
    if(this.get('isDateType')){
      if (Ember.empty(this.get('date'))){
        this.content.set('value',null);
      } else {
        tempDate = Date.parse(this.get('date'));
        if (!isNaN(tempDate)){
          this.content.set('value',tempDate);
        } else {
          this.content.set('value',null);
        }
      }
    } else if (this.get('isOptionType')){
      this.content.set('value',this.optionChoice.get('value'));
    } else if (this.get('isNumberType')){
      this.content.set('value',this.get('numberValue'));
    } else {
      this.content.set('value',this.get('value'));
    }
    FLOW.store.commit();
    this.set('inEditMode', false);
  },

  doValidateNumber: function () {
    // TODO should check for minus sign and decimal point, depending on question setting
    this.set('numberValue',this.get('numberValue').toString().replace(/[^\d.]/g, ""));
  }.observes('this.numberValue')

});<commit_msg>Issue #225 - use Ember.none instead of compare to null<commit_after>// this function is also present in assignment-edit-views.js, we need to consolidate using moment.js
function formatDate(value) {
  if(!Ember.none(value)) {
    return value.getFullYear() + "/" + (value.getMonth() + 1) + "/" + value.getDate();
  } else return null;
}

FLOW.QuestionAnswerView = Ember.View.extend({
  isTextType: false,
  isOptionType: false,
  isNumberType: false,
  isBarcodeType: false,
  isDateType: false,
  isPhotoType: false,
  optionsList: [],
  content: null,
  optionChoice: null,
  inEditMode: false,
  isNotEditable: false,
  value: null,
  numberValue:null,
  date: null,
  photoUrl:null,

  init: function() {
    this._super();
    this.doInit();
  },

doInit: function() {
    var q, questionId, type;

    // TODO use filter instead: if the question is not yet there, don't do anything
    // it will be picked up later at isLoaded.
    questionId = this.content.get('questionID');
    q = FLOW.store.find(FLOW.Question, questionId);
    type = q.get('type');
    this.set('isTextType', type == 'FREE_TEXT');
    this.set('isOptionType', type == 'OPTION');
    this.set('isNumberType', type == 'NUMBER');
    this.set('isBarcodeType', type == 'BARCODE');
    this.set('isPhotoType', type == 'PHOTO');
    this.set('isDateType', type == 'DATE');
    this.set('isNotEditable', (type == 'GEO' || type == 'PHOTO' || type == 'VIDEO'));

    this.setInitialValue();
  }.observes('FLOW.questionControl.content.isLoaded'),

  setInitialValue: function() {
    var opList, opListArray, i, sizeList, q, questionId, qaValue, choice = null, date;

    questionId = this.content.get('questionID');
    q = FLOW.store.find(FLOW.Question, questionId);

    // set value
    this.set('value', this.content.get('value'));

    if(this.get('isNumberType')) {
      this.set('numberValue',this.content.get('value'));
    }

    if(this.get('isDateType') && !Ember.none(this.content.get('value'))) {
      date = new Date(parseInt(this.content.get('value'),10));
      this.set('date',formatDate(date));
    }
    // fill option list
    if(this.get('isOptionType') && !Ember.none(q.get('optionList'))) {
      this.set('optionsList', []);
      opList = q.get('optionList');
      opListArray = opList.split('\n');
      sizeList = opListArray.length;

      for(i = 0; i < sizeList; i++) {
        this.get('optionsList').push(Ember.Object.create({
          isSelected: false,
          value: opListArray[i]
        }));
      }
      // set answer
      qaValue = this.content.get('value');
      this.get('optionsList').forEach(function(item) {
        if(item.get('value') == qaValue) {
          choice = item;
        }
      });
      this.set('optionChoice', choice);
    }
    if (this.get('isPhotoType') && !Ember.empty(this.content.get('value'))){
      // Since photos have a leading path from devices that we need to trim
      this.set('photoUrl',FLOW.Env.photo_url_root + this.content.get('value').split('/').pop());
    }
  },

  doEdit: function() {
    this.set('inEditMode', true);
  },

  doCancel: function() {
    // revert answer
    this.setInitialValue();
    this.set('inEditMode', false);
  },

  doSave: function() {
    var tempDate = null;
    if(this.get('isDateType')){
      if (Ember.empty(this.get('date'))){
        this.content.set('value',null);
      } else {
        tempDate = Date.parse(this.get('date'));
        if (!isNaN(tempDate)){
          this.content.set('value',tempDate);
        } else {
          this.content.set('value',null);
        }
      }
    } else if (this.get('isOptionType')){
      this.content.set('value',this.optionChoice.get('value'));
    } else if (this.get('isNumberType')){
      this.content.set('value',this.get('numberValue'));
    } else {
      this.content.set('value',this.get('value'));
    }
    FLOW.store.commit();
    this.set('inEditMode', false);
  },

  doValidateNumber: function () {
    // TODO should check for minus sign and decimal point, depending on question setting
    this.set('numberValue',this.get('numberValue').toString().replace(/[^\d.]/g, ""));
  }.observes('this.numberValue')

});<|endoftext|>


<commit_before>(function(document, location, history) { /* jshint maxdepth:8, boss:true */
    "use strict";

    // do not enable the plugin for old browsers
    if (typeof history.pushState !== "function") return;

    const identity = (s) => s;
    const reTitle = /<title>(.*?)<\/title>/;
    const states = []; // in-memory storage for states
    var lastState = {}, lastFormData;

    function attachNonPreventedListener(eventType, callback) {
        document.addEventListener(eventType, function(e) {
            if (!e.defaultPrevented) {
                callback(e);
            }
        }, false);
    }

    function attachCapturingListener(eventType, callback) {
        document.addEventListener(eventType, callback, true);
    }

    function dispatchAjaxifyEvent(el, eventType, eventDetail) {
        const e = document.createEvent("CustomEvent");

        e.initCustomEvent("ajaxify:" + eventType, true, true, eventDetail || null);

        return el.dispatchEvent(e);
    }

    function updateState(state, detail) {
        const body = document.body;

        if (dispatchAjaxifyEvent(body, "update", detail)) {
            // by default just swap body elements
            body.parentNode.replaceChild(state.body, body);
        }

        if (states.indexOf(lastState) < 0) {
            // if state does not exist - store it in memory
            states.push(lastState);
        }

        document.title = state.title;
    }

    function createDocument(htmlText) {
        const titleMatch = reTitle.exec(htmlText);
        const doc = document.implementation.createHTMLDocument(titleMatch && titleMatch[1] || "");

        doc.body.innerHTML = htmlText.trim().replace(titleMatch && titleMatch[0], "");

        return doc;
    }

    attachNonPreventedListener("click", (e) => {
        const body = document.body;

        for (var el = e.target; el && el !== body; el = el.parentNode) {
            if (el.nodeName.toLowerCase() === "a") {
                if (!el.target) {
                    const targetUrl = el.href;

                    if (el.getAttribute("aria-disabled") === "true") {
                        e.preventDefault();
                    } else if (targetUrl && targetUrl.indexOf("http") === 0) {
                        const currentUrl = location.href;

                        if (targetUrl === currentUrl || targetUrl.split("#")[0] !== currentUrl.split("#")[0]) {
                            if (dispatchAjaxifyEvent(el, "fetch")) {
                                // override default bahavior for links
                                e.preventDefault();
                            }
                        } else {
                            location.hash = el.hash;
                            // override default bahavior for anchors
                            e.preventDefault();
                        }
                    }
                }

                break;
            }
        }
    });

    attachNonPreventedListener("submit", (e) => {
        const el = e.target;

        if (!el.target) {
            if (el.getAttribute("aria-disabled") === "true") {
                e.preventDefault();
            } else {
                const formEnctype = el.getAttribute("enctype");

                var data;

                if (formEnctype === "multipart/form-data") {
                    data = new FormData(el);
                } else {
                    data = {};

                    for (var i = 0, field; field = el.elements[i]; ++i) {
                        const fieldType = field.type;

                        if (fieldType && field.name && !field.disabled) {
                            const fieldName = field.name;

                            if (fieldType === "select-multiple") {
                                for (var j = 0, option; option = field.options[j]; ++j) {
                                    if (option.selected) {
                                        (data[fieldName] = data[fieldName] || []).push(option.value);
                                    }
                                }
                            } else if ((fieldType !== "checkbox" && fieldType !== "radio") || field.checked) {
                                data[fieldName] = field.value;
                            }
                        }
                    }
                }

                if (!dispatchAjaxifyEvent(el, "serialize", data)) {
                    e.preventDefault();
                } else {
                    if (data instanceof FormData) {
                        lastFormData = data;
                    } else {
                        const encode = formEnctype === "text/plain" ? identity : encodeURIComponent;
                        const reSpace = encode === identity ? / /g : /%20/g;

                        lastFormData = Object.keys(data).map((key) => {
                            const name = encode(key);
                            var value = data[key];

                            if (Array.isArray(value)) {
                                value = value.map(encode).join("&" + name + "=");
                            }

                            return name + "=" + encode(value);
                        }).join("&").replace(reSpace, "+");
                    }

                    if (dispatchAjaxifyEvent(el, "fetch")) {
                        e.preventDefault();
                    }

                    lastFormData = null; // cleanup internal reference
                }
            }
        }
    });

    attachNonPreventedListener("ajaxify:fetch", (e) => {
        const el = e.target;
        const xhr = new XMLHttpRequest();
        const method = (el.method || "GET").toUpperCase();
        const nodeName = el.nodeName.toLowerCase();
        const nodeType = el.nodeType;

        var url = e.detail;

        if (nodeName === "a") {
            url = url || el.href;
        } else if (nodeName === "form") {
            url = url || el.action;

            if (method === "GET" && lastFormData) {
                url += (~url.indexOf("?") ? "&" : "?") + lastFormData;
                // for get forms append all data to url
                lastFormData = null;
            }
        }

        ["abort", "error", "load", "timeout"].forEach((type) => {
            xhr["on" + type] = () => {
                if (nodeType === 1) {
                    el.removeAttribute("aria-disabled");
                }

                var url = xhr.responseURL;
                // polyfill xhr.responseURL value
                if (!url && res && res.URL) {
                    url = xhr.getResponseHeader("Location");

                    if (url) {
                        url = location.origin + url;
                        // patch XHR object to set responseURL
                        Object.defineProperty(xhr, "responseURL", {get: () => url});
                    }
                }

                if (dispatchAjaxifyEvent(el, type, xhr) && type === "load") {
                    const doc = xhr.response;
                    const state = {body: doc.body};

                    if (doc.title) {
                        state.title = doc.title;
                    } else {
                        state.title = xhr.status + " " + xhr.statusText;
                    }

                    updateState(state, doc);

                    lastState = {}; // create a new state object

                    if (url !== location.href) {
                        history.pushState(states.length, state.title, url);
                    }
                }
            };
        });

        xhr.open(method, url, true);
        xhr.responseType = "document";

        if (dispatchAjaxifyEvent(el, "send", xhr)) {
            xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

            if (method !== "GET") {
                xhr.setRequestHeader("Content-Type", el.getAttribute("enctype") || el.enctype);
            }

            xhr.send(lastFormData);

            if (nodeType === 1) {
                el.setAttribute("aria-disabled", "true");
            }
        }
    });

    attachCapturingListener("ajaxify:update", (e) => {
        var detail = e.detail;

        if (typeof detail === "string") {
            detail = createDocument(detail);
            // override property e.detail
            Object.defineProperty(e, "detail", {get: () => detail});
        }

        lastState.body = e.target;
        lastState.title = document.title;
    });

    window.addEventListener("popstate", (e) => {
        // numeric value indicates better-ajaxify state
        if (!e.defaultPrevented && e.state >= 0) {
            const state = states[e.state];

            if (state) {
                updateState(state, state.body);

                lastState = state;
            }
        }
    });

    // update initial state address url
    history.replaceState(0, document.title);

}(window.document, window.location, window.history));
<commit_msg>ajaxify:fetch supports document object as detail<commit_after>(function(document, location, history) { /* jshint maxdepth:8, boss:true */
    "use strict";

    // do not enable the plugin for old browsers
    if (typeof history.pushState !== "function") return;

    const identity = (s) => s;
    const reTitle = /<title>(.*?)<\/title>/;
    const states = []; // in-memory storage for states
    var lastState = {}, lastFormData;

    function attachNonPreventedListener(eventType, callback) {
        document.addEventListener(eventType, function(e) {
            if (!e.defaultPrevented) {
                callback(e);
            }
        }, false);
    }

    function dispatchAjaxifyEvent(el, eventType, eventDetail) {
        const e = document.createEvent("CustomEvent");

        e.initCustomEvent("ajaxify:" + eventType, true, true, eventDetail || null);

        return el.dispatchEvent(e);
    }

    function updateState(state, detail) {
        const body = document.body;

        if (dispatchAjaxifyEvent(body, "update", detail)) {
            // by default just swap body elements
            body.parentNode.replaceChild(state.body, body);
        }

        if (states.indexOf(lastState) < 0) {
            // if state does not exist - store it in memory
            states.push(lastState);
        }

        document.title = state.title;
    }

    function createDocument(htmlText) {
        const titleMatch = reTitle.exec(htmlText);
        const doc = document.implementation.createHTMLDocument(titleMatch && titleMatch[1] || "");

        doc.body.innerHTML = htmlText.trim().replace(titleMatch && titleMatch[0], "");

        return doc;
    }

    attachNonPreventedListener("click", (e) => {
        const body = document.body;

        for (var el = e.target; el && el !== body; el = el.parentNode) {
            if (el.nodeName.toLowerCase() === "a") {
                if (!el.target) {
                    const targetUrl = el.href;

                    if (el.getAttribute("aria-disabled") === "true") {
                        e.preventDefault();
                    } else if (targetUrl && targetUrl.indexOf("http") === 0) {
                        const currentUrl = location.href;

                        if (targetUrl === currentUrl || targetUrl.split("#")[0] !== currentUrl.split("#")[0]) {
                            if (dispatchAjaxifyEvent(el, "fetch")) {
                                // override default bahavior for links
                                e.preventDefault();
                            }
                        } else {
                            location.hash = el.hash;
                            // override default bahavior for anchors
                            e.preventDefault();
                        }
                    }
                }

                break;
            }
        }
    });

    attachNonPreventedListener("submit", (e) => {
        const el = e.target;

        if (!el.target) {
            if (el.getAttribute("aria-disabled") === "true") {
                e.preventDefault();
            } else {
                const formEnctype = el.getAttribute("enctype");

                var data;

                if (formEnctype === "multipart/form-data") {
                    data = new FormData(el);
                } else {
                    data = {};

                    for (var i = 0, field; field = el.elements[i]; ++i) {
                        const fieldType = field.type;

                        if (fieldType && field.name && !field.disabled) {
                            const fieldName = field.name;

                            if (fieldType === "select-multiple") {
                                for (var j = 0, option; option = field.options[j]; ++j) {
                                    if (option.selected) {
                                        (data[fieldName] = data[fieldName] || []).push(option.value);
                                    }
                                }
                            } else if ((fieldType !== "checkbox" && fieldType !== "radio") || field.checked) {
                                data[fieldName] = field.value;
                            }
                        }
                    }
                }

                if (!dispatchAjaxifyEvent(el, "serialize", data)) {
                    e.preventDefault();
                } else {
                    if (data instanceof FormData) {
                        lastFormData = data;
                    } else {
                        const encode = formEnctype === "text/plain" ? identity : encodeURIComponent;
                        const reSpace = encode === identity ? / /g : /%20/g;

                        lastFormData = Object.keys(data).map((key) => {
                            const name = encode(key);
                            var value = data[key];

                            if (Array.isArray(value)) {
                                value = value.map(encode).join("&" + name + "=");
                            }

                            return name + "=" + encode(value);
                        }).join("&").replace(reSpace, "+");
                    }

                    if (dispatchAjaxifyEvent(el, "fetch")) {
                        e.preventDefault();
                    }

                    lastFormData = null; // cleanup internal reference
                }
            }
        }
    });

    attachNonPreventedListener("ajaxify:fetch", (e) => {
        const el = e.target;
        const detail = e.detail;

        if (detail && detail.nodeType === 9) {
            const state = {body: detail.body, title: detail.title};

            updateState(state, detail);

            lastState = {}; // create a new state object

            if (detail.URL !== location.href) {
                history.pushState(states.length, state.title, detail.URL);
            }
        } else {
            const xhr = new XMLHttpRequest();
            const method = (el.method || "GET").toUpperCase();
            const nodeName = el.nodeName.toLowerCase();
            const nodeType = el.nodeType;

            var url = detail;

            if (nodeName === "a") {
                url = url || el.href;
            } else if (nodeName === "form") {
                url = url || el.action;

                if (method === "GET" && lastFormData) {
                    url += (~url.indexOf("?") ? "&" : "?") + lastFormData;
                    // for get forms append all data to url
                    lastFormData = null;
                }
            }

            ["abort", "error", "load", "timeout"].forEach((type) => {
                xhr["on" + type] = () => {
                    if (nodeType === 1) {
                        el.removeAttribute("aria-disabled");
                    }

                    const res = xhr.response;
                    var url = xhr.responseURL;
                    // polyfill xhr.responseURL value
                    if (!url && res && res.URL) {
                        url = xhr.getResponseHeader("Location");

                        if (url) {
                            url = location.origin + url;
                            // patch XHR object to set responseURL
                            Object.defineProperty(xhr, "responseURL", {get: () => url});
                        }
                    }

                    if (dispatchAjaxifyEvent(el, type, xhr) && type === "load") {
                        const doc = xhr.response;
                        const state = {body: doc.body};

                        if (doc.title) {
                            state.title = doc.title;
                        } else {
                            state.title = xhr.status + " " + xhr.statusText;
                        }

                        updateState(state, doc);

                        lastState = {}; // create a new state object

                        if (url !== location.href) {
                            history.pushState(states.length, state.title, url);
                        }
                    }
                };
            });

            xhr.open(method, url, true);
            xhr.responseType = "document";

            if (dispatchAjaxifyEvent(el, "send", xhr)) {
                xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

                if (method !== "GET") {
                    xhr.setRequestHeader("Content-Type", el.getAttribute("enctype") || el.enctype);
                }

                xhr.send(lastFormData);

                if (nodeType === 1) {
                    el.setAttribute("aria-disabled", "true");
                }
            }
        }
    });

    document.addEventListener("ajaxify:update", (e) => {
        lastState.body = e.target;
        lastState.title = document.title;
    }, true);

    window.addEventListener("popstate", (e) => {
        // numeric value indicates better-ajaxify state
        if (!e.defaultPrevented && e.state >= 0) {
            const state = states[e.state];

            if (state) {
                updateState(state, state.body);

                lastState = state;
            }
        }
    });

    // update initial state address url
    history.replaceState(0, document.title);

}(window.document, window.location, window.history));
<|endoftext|>


<commit_before>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery', 'hljs'], function(jQuery, hljs) {
      return (root.Autobot = factory(root, jQuery, hljs));
    });
  } else if (typeof exports === 'object') {
    module.exports = factory(root, require('jquery'), require('hljs'));
  } else {
    root.Autobot = factory(root, root.jQuery, root.hljs);
  }
}(this, function (window, $, hljs) {
  Autobot.DEFAULT_OPTS = {
    keystrokeMs: 20,
    punctuationMs: 1000,
    renderTemplate: $('<pre>', {'class': 'autobot-code'})
  };
  function Autobot ($els, opts) {
    var self = this;
    self.opts = $.extend(true, null, Autobot.DEFAULT_OPTS, opts);
    self.data = {};
    self.pending = [];
    $els.each(function (i, el) {
      var $el = $(el);
      var name = $el.attr('data-autobot');
      self.data[name] = {
        name: name,
        $el: $el,
        $renderEl: $(self.opts.renderTemplate).clone().appendTo('body'),
        content: $el.html(),
        i: 0
      };
      $el.html('');
      self.pending[$el.is('data-autobot-current') ? 'unshift' : 'push'](name);
    });
  }
  Autobot.prototype.animate = function () {
    var self = this;
    self.step(self.animate.bind(self));
  };
  Autobot.prototype.step = function (fn) {
    var self = this;
    var opts = self.opts;

    if (!self.pending.length) return;
    var current = self.pending[0];
    var data = self.data[current];
    var currentContent = data.content.slice(0, data.i++);

    data.$el.html(currentContent);
    data.$renderEl.scrollBottom(function () {
      data.$renderEl.text(currentContent);
      hljs.highlightBlock(data.$renderEl[0]);
    });

    if (typeof opts.step === 'function') opts.step.call(self, data);

    if (data.i >= data.content.length) self.pending.shift();
    else {
      var next = (currentContent.match(/autobot-next=(\w+)\W$/) || [])[1];
      if (next) self.pending = self.pending
        .splice(self.pending.indexOf(next) || Infinity, 1)
        .concat(self.pending);
    }

    setTimeout(fn, self.getTimeout(currentContent));
  };
  Autobot.prototype.getTimeout = function (content) {
    var self = this;
    var opts = self.opts;
    if (content.match(/[.?!]\s$/)) return opts.punctuationMs;
    if (content.match(/\s+$/)) return 0; // skip whitespace
    return opts.keystrokeMs;
  };

  $.fn.scrollBottom = function (fn) {
    var scrolledToBottom = this.scrollTop() >= this[0].scrollHeight - this.innerHeight();
    fn();
    if (scrolledToBottom) this.scrollTop(this[0].scrollHeight - this.innerHeight());
  };

  $(function () {
    hljs.configure({classPrefix: ''});
    new Autobot($('[data-autobot]')).animate();
  });

  return Autobot;
}));
<commit_msg>.autobot-code scrolls to bottom on all resize events; hljs now optional<commit_after>(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['jquery', 'hljs'], function(jQuery, hljs) {
      return (root.Autobot = factory(root, jQuery, hljs));
    });
  } else if (typeof exports === 'object') {
    module.exports = factory(root, require('jquery'), require('hljs'));
  } else {
    root.Autobot = factory(root, root.jQuery, root.hljs);
  }
}(this, function (window, $, hljs) {
  Autobot.RENDER_CLASS = 'autobot-code';
  Autobot.RENDER_SELECTOR = '.'+Autobot.RENDER_CLASS;
  Autobot.DEFAULT_OPTS = {
    keystrokeMs: 20,
    punctuationMs: 1000,
    renderTemplate: $('<pre>', {'class': Autobot.RENDER_CLASS})
  };
  function Autobot ($els, opts) {
    var self = this;
    self.opts = $.extend(true, null, Autobot.DEFAULT_OPTS, opts);
    self.data = {};
    self.pending = [];
    $els.each(function (i, el) {
      var $el = $(el);
      var name = $el.attr('data-autobot');
      self.data[name] = {
        name: name,
        $el: $el,
        $renderEl: $(self.opts.renderTemplate).clone().appendTo('body'),
        content: $el.html(),
        i: 0
      };
      $el.html('');
      self.pending[$el.is('data-autobot-current') ? 'unshift' : 'push'](name);
    });
  }
  Autobot.prototype.animate = function () {
    var self = this;
    self.step(self.animate.bind(self));
  };
  Autobot.prototype.step = function (fn) {
    var self = this;
    var opts = self.opts;

    if (!self.pending.length) return;
    var current = self.pending[0];
    var data = self.data[current];
    var currentContent = data.content.slice(0, data.i++);

    data.$el.html(currentContent);
    data.$renderEl.text(currentContent);
    if (hljs) hljs.highlightBlock(data.$renderEl[0]);
    data.$renderEl.trigger('resize');

    if (typeof opts.step === 'function') opts.step.call(self, data);

    if (data.i >= data.content.length) self.pending.shift();
    else {
      var next = (currentContent.match(/autobot-next=(\w+)\W$/) || [])[1];
      if (next) self.pending = self.pending
        .splice(self.pending.indexOf(next) || Infinity, 1)
        .concat(self.pending);
    }

    setTimeout(fn, self.getTimeout(currentContent));
  };
  Autobot.prototype.getTimeout = function (content) {
    var self = this;
    var opts = self.opts;
    if (content.match(/[.?!]\s$/)) return opts.punctuationMs;
    if (content.match(/\s+$/)) return 0; // skip whitespace
    return opts.keystrokeMs;
  };

  $.fn.scrollBottom = function (fn) {
    var scrolledToBottom = this.scrollTop() >= this[0].scrollHeight - this.innerHeight();
    fn();
    if (scrolledToBottom) this.scrollTop(this[0].scrollHeight - this.innerHeight());
  };

  $(function () {
    $(document).on('resize', Autobot.RENDER_SELECTOR, function () {
      var $code = $(this);
      var data = $code.data();
      if (!data.autobot) data.autobot = {};
      data = data.autobot;
      var scrollTopMax = $code[0].scrollHeight - $code.innerHeight();
      if (data.scrolledToBottom) $code.scrollTop(scrollTopMax);
      data.scrolledToBottom = $code.scrollTop() >= scrollTopMax;
    });

    if (hljs) hljs.configure({classPrefix: ''});
    new Autobot($('[data-autobot]')).animate();
  });

  return Autobot;
}));
<|endoftext|>


<commit_before>"use strict";

const ccxt = require ('ccxt')
    , WsClient = require ('./WsClient')
    , {
        OrderBook,
        IndexedOrderBook,
        CountedOrderBook,
    } = require ('./OrderBook')
    , functions = require ('./functions')

module.exports = class Exchange extends ccxt.Exchange {
    constructor (options = {}) {
        super (options)
        this.newUpdates = options.newUpdates || false
    }

    inflate (data) {
        return functions.inflate (data)
    }

    inflate64 (data) {
        return functions.inflate64 (data)
    }

    gunzip (data) {
        return functions.gunzip (data)
    }

    orderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new OrderBook (snapshot, depth)
    }

    indexedOrderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new IndexedOrderBook (snapshot, depth)
    }

    countedOrderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new CountedOrderBook (snapshot, depth)
    }

    client (url) {
        this.clients = this.clients || {}
        if (!this.clients[url]) {
            const onMessage = this.handleMessage.bind (this)
            const onError = this.onError.bind (this)
            const onClose = this.onClose.bind (this)
            const onConnected = this.onConnected.bind (this)
            // decide client type here: ws / signalr / socketio
            const wsOptions = this.safeValue (this.options, 'ws', {})
            const options = this.extend (this.streaming, {
                'print': this.print ? this.print.bind (this) : this.print,
                'ping': this.ping ? this.ping.bind (this) : this.ping,
                'verbose': this.verbose,
                'throttle': ccxt.throttle (this.tokenBucket),
            }, wsOptions)
            this.clients[url] = new WsClient (url, onMessage, onError, onClose, onConnected, options)
        }
        return this.clients[url]
    }

    spawn (method, ... args) {
        (method.apply (this, args)).catch ((e) => {
            // todo: handle spawned errors
        })
    }

    delay (timeout, method, ... args) {
        setTimeout (() => {
            this.spawn (method, ... args)
        }, timeout)
    }

    watch (url, messageHash, message = undefined, subscribeHash = undefined, subscription = undefined) {
        //
        // Without comments the code of this method is short and easy:
        //
        //     const client = this.client (url)
        //     const backoffDelay = 0
        //     const future = client.future (messageHash)
        //     const connected = client.connect (backoffDelay)
        //     connected.then (() => {
        //         if (message && !client.subscriptions[subscribeHash]) {
        //             client.subscriptions[subscribeHash] = true
        //             client.send (message)
        //         }
        //     }).catch ((error) => {})
        //     return future
        //
        // The following is a longer version of this method with comments
        //
        const client = this.client (url)
        // todo: calculate the backoff using the clients cache
        const backoffDelay = 0
        //
        //  watchOrderBook ---- future ----+---------------+----→ user
        //                                 |               |
        //                                 ↓               ↑
        //                                 |               |
        //                              connect ......→ resolve
        //                                 |               |
        //                                 ↓               ↑
        //                                 |               |
        //                             subscribe -----→ receive
        //
        const future = client.future (messageHash)
        // we intentionally do not use await here to avoid unhandled exceptions
        // the policy is to make sure that 100% of promises are resolved or rejected
        // either with a call to client.resolve or client.reject with
        //  a proper exception class instance
        const connected = client.connect (backoffDelay)
        // the following is executed only if the catch-clause does not
        // catch any connection-level exceptions from the client
        // (connection established successfully)
        connected.then (() => {
            if (!client.subscriptions[subscribeHash]) {
                client.subscriptions[subscribeHash] = subscription || true
                const options = this.safeValue (this.options, 'ws')
                const rateLimit = this.safeValue (options, 'rateLimit', this.rateLimit);
                if (message) {
                    if (this.enableRateLimit && client.throttle) {
                        client.throttle (rateLimit).then (() => {
                            client.send (message)
                        }).catch ((e) => { throw e })
                    } else {
                        client.send (message)
                    }
                }
            }
        }).catch ((error) => {
            // we do nothing and don't return a resolvable value from here
            // we leave it in a rejected state to avoid triggering the
            // then-clauses that will follow (if any)
            // removing this catch will raise UnhandledPromiseRejection in JS
            // upon connection failure
        })
        return future
    }

    onConnected (client, message = undefined) {
        // for user hooks
        // console.log ('Connected to', client.url)
    }

    onError (client, error) {
        if ((client.url in this.clients) && (this.clients[client.url].error)) {
            delete this.clients[client.url]
        }
    }

    onClose (client, error) {
        if (client.error) {
            // connection closed due to an error, do nothing
        } else {
            // server disconnected a working connection
            if (this.clients[client.url]) {
                delete this.clients[client.url]
            }
        }
    }

    async close () {
        const clients = Object.values (this.clients || {})
        for (let i = 0; i < clients.length; i++) {
            const client = clients[i]
            delete this.clients[client.url]
            await client.close ()
        }
    }

    findTimeframe (timeframe) {
        const keys = Object.keys (this.timeframes);
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            if (this.timeframes[key] === timeframe) {
                return key;
            }
        }
        return undefined;
    }
}
<commit_msg>Exchange.js throttle v2<commit_after>"use strict";

const ccxt = require ('ccxt')
    , WsClient = require ('./WsClient')
    , {
        OrderBook,
        IndexedOrderBook,
        CountedOrderBook,
    } = require ('./OrderBook')
    , functions = require ('./functions')

module.exports = class Exchange extends ccxt.Exchange {
    constructor (options = {}) {
        super (options)
        this.newUpdates = options.newUpdates || false
    }

    inflate (data) {
        return functions.inflate (data)
    }

    inflate64 (data) {
        return functions.inflate64 (data)
    }

    gunzip (data) {
        return functions.gunzip (data)
    }

    orderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new OrderBook (snapshot, depth)
    }

    indexedOrderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new IndexedOrderBook (snapshot, depth)
    }

    countedOrderBook (snapshot = {}, depth = Number.MAX_SAFE_INTEGER) {
        return new CountedOrderBook (snapshot, depth)
    }

    client (url) {
        this.clients = this.clients || {}
        if (!this.clients[url]) {
            const onMessage = this.handleMessage.bind (this)
            const onError = this.onError.bind (this)
            const onClose = this.onClose.bind (this)
            const onConnected = this.onConnected.bind (this)
            // decide client type here: ws / signalr / socketio
            const wsOptions = this.safeValue (this.options, 'ws', {})
            const options = this.extend (this.streaming, {
                'print': this.print ? this.print.bind (this) : this.print,
                'ping': this.ping ? this.ping.bind (this) : this.ping,
                'verbose': this.verbose,
                'throttle': ccxt.throttle (this.tokenBucket),
            }, wsOptions)
            this.clients[url] = new WsClient (url, onMessage, onError, onClose, onConnected, options)
        }
        return this.clients[url]
    }

    spawn (method, ... args) {
        (method.apply (this, args)).catch ((e) => {
            // todo: handle spawned errors
        })
    }

    delay (timeout, method, ... args) {
        setTimeout (() => {
            this.spawn (method, ... args)
        }, timeout)
    }

    watch (url, messageHash, message = undefined, subscribeHash = undefined, subscription = undefined) {
        //
        // Without comments the code of this method is short and easy:
        //
        //     const client = this.client (url)
        //     const backoffDelay = 0
        //     const future = client.future (messageHash)
        //     const connected = client.connect (backoffDelay)
        //     connected.then (() => {
        //         if (message && !client.subscriptions[subscribeHash]) {
        //             client.subscriptions[subscribeHash] = true
        //             client.send (message)
        //         }
        //     }).catch ((error) => {})
        //     return future
        //
        // The following is a longer version of this method with comments
        //
        const client = this.client (url)
        // todo: calculate the backoff using the clients cache
        const backoffDelay = 0
        //
        //  watchOrderBook ---- future ----+---------------+----→ user
        //                                 |               |
        //                                 ↓               ↑
        //                                 |               |
        //                              connect ......→ resolve
        //                                 |               |
        //                                 ↓               ↑
        //                                 |               |
        //                             subscribe -----→ receive
        //
        const future = client.future (messageHash)
        // we intentionally do not use await here to avoid unhandled exceptions
        // the policy is to make sure that 100% of promises are resolved or rejected
        // either with a call to client.resolve or client.reject with
        //  a proper exception class instance
        const connected = client.connect (backoffDelay)
        // the following is executed only if the catch-clause does not
        // catch any connection-level exceptions from the client
        // (connection established successfully)
        connected.then (() => {
            if (!client.subscriptions[subscribeHash]) {
                client.subscriptions[subscribeHash] = subscription || true
                const options = this.safeValue (this.options, 'ws')
                const rateLimit = this.safeValue (options, 'rateLimit', this.rateLimit);
                if (message) {
                    if (this.enableRateLimit && client.throttle) {
                        // add cost here |
                        //               |
                        //               V
                        client.throttle ().then (() => {
                            client.send (message)
                        }).catch ((e) => { throw e })
                    } else {
                        client.send (message)
                    }
                }
            }
        }).catch ((error) => {
            // we do nothing and don't return a resolvable value from here
            // we leave it in a rejected state to avoid triggering the
            // then-clauses that will follow (if any)
            // removing this catch will raise UnhandledPromiseRejection in JS
            // upon connection failure
        })
        return future
    }

    onConnected (client, message = undefined) {
        // for user hooks
        // console.log ('Connected to', client.url)
    }

    onError (client, error) {
        if ((client.url in this.clients) && (this.clients[client.url].error)) {
            delete this.clients[client.url]
        }
    }

    onClose (client, error) {
        if (client.error) {
            // connection closed due to an error, do nothing
        } else {
            // server disconnected a working connection
            if (this.clients[client.url]) {
                delete this.clients[client.url]
            }
        }
    }

    async close () {
        const clients = Object.values (this.clients || {})
        for (let i = 0; i < clients.length; i++) {
            const client = clients[i]
            delete this.clients[client.url]
            await client.close ()
        }
    }

    findTimeframe (timeframe) {
        const keys = Object.keys (this.timeframes);
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            if (this.timeframes[key] === timeframe) {
                return key;
            }
        }
        return undefined;
    }
}
<|endoftext|>


<commit_before>/*
 * AngularJS modules to be used together with
 * https://github.com/jrief/django-angular
 *
 * Copyright (c) 2014 Jacob Rief
 * Licensed under the MIT license.
 */

(function(angular, undefined) {
'use strict';

// Correct Angular's form.FormController behavior after rendering bound forms.
angular.module('ng.django.forms', []).directive('form', function() {
	return {
		restrict: 'E',
		scope: 'isolate',
		priority: -1,
		link: function(scope, element, attrs) {
			var form = scope[attrs.name];
			var fields = angular.element(element).find('input');
			angular.forEach(fields, function(field) {
				if (form[field.name] !== undefined) {
					// restore the field's content from the rendered content of bound fields
					form[field.name].$setViewValue(field.defaultValue);
				}
			});
			// restore the form's pristine state
			form.$setPristine();
		}
	};
});

// Add three-way data-binding for AngularJS with Django using websockets.
angular.module('ng.django.websocket', []).provider('djangoWebsocket', function() {
	var _prefix;
	var _console = { log: noop, warn: noop, error: noop };

	function noop() {}

	this.prefix = function(prefix) {
		_prefix = prefix;
		return this;
	};

	this.debug = function(debug) {
		if (debug) {
			_console = console;
		}
		return this;
	};

	this.$get = ['$window', '$q', '$timeout', function($window, $q, $timeout) {
		var ws, deferred, timer = null, interval = null, scope, channels, collection;

		function connect(uri) {
			try {
				_console.log("Connecting to "+uri);
				deferred = $q.defer();
				ws = new WebSocket(uri);
				ws.onopen = on_open;
				ws.onmessage = on_message;
				ws.onerror = on_error;
				ws.onclose = on_close;
				timer = null;
			} catch (err) {
				deferred.reject(new Error(err));
			}
		}

		function on_open(evt) {
			_console.log('Connected');
			interval = 3000;
			deferred.resolve();
		}

		function on_close(evt) {
			_console.log("Connection closed");
			if (!timer && interval) {
				timer = $timeout(function() {
					connect(ws.url);
				}, interval);
				interval = Math.min(interval + 1000, 90000);
			}
		}

		function on_error(evt) {
			_console.error("Websocket connection is broken!");
			deferred.reject(new Error(evt));
		}

		function on_message(evt) {
			try {
				var server_data = JSON.parse(evt.data);
				scope.$apply(function() {
					angular.extend(scope[collection], server_data);
				});
			} catch(e) {
				_console.warn('Data received by server is invalid JSON: ' + evt.data);
			}
		}

		function listener(newValue, oldValue) {
			if (newValue !== undefined) {
				ws.send(JSON.stringify(newValue));
			}
		}

		return {
			connect: function(scope_, channels_, collection_) {
				var parts = [], location = $window.location;
				scope = scope_;
				channels = channels_;
				collection = collection_;
				parts.push(location.protocol === 'https' ? 'wss:' : 'ws:');
				parts.push('//');
				parts.push(location.host);
				parts.push(_prefix);
				parts.push(location.pathname);
				parts.push('?');
				parts.push(channels.join('&'));
				connect(parts.join(''));
				scope[collection] = scope[collection] || {};
				deferred.promise.then(function() {
					scope.$watchCollection(collection, listener);
				});
				return deferred.promise;
			}
		};
	}];
});

})(window.angular);
<commit_msg>Reduced reconnection interval<commit_after>/*
 * AngularJS modules to be used together with
 * https://github.com/jrief/django-angular
 *
 * Copyright (c) 2014 Jacob Rief
 * Licensed under the MIT license.
 */

(function(angular, undefined) {
'use strict';

// Correct Angular's form.FormController behavior after rendering bound forms.
angular.module('ng.django.forms', []).directive('form', function() {
	return {
		restrict: 'E',
		scope: 'isolate',
		priority: -1,
		link: function(scope, element, attrs) {
			var form = scope[attrs.name];
			var fields = angular.element(element).find('input');
			angular.forEach(fields, function(field) {
				if (form[field.name] !== undefined) {
					// restore the field's content from the rendered content of bound fields
					form[field.name].$setViewValue(field.defaultValue);
				}
			});
			// restore the form's pristine state
			form.$setPristine();
		}
	};
});

// Add three-way data-binding for AngularJS with Django using websockets.
angular.module('ng.django.websocket', []).provider('djangoWebsocket', function() {
	var _prefix;
	var _console = { log: noop, warn: noop, error: noop };

	function noop() {}

	this.prefix = function(prefix) {
		_prefix = prefix;
		return this;
	};

	this.debug = function(debug) {
		if (debug) {
			_console = console;
		}
		return this;
	};

	this.$get = ['$window', '$q', '$timeout', function($window, $q, $timeout) {
		var ws, deferred, timer = null, interval = null, scope, channels, collection;

		function connect(uri) {
			try {
				_console.log("Connecting to "+uri);
				deferred = $q.defer();
				ws = new WebSocket(uri);
				ws.onopen = on_open;
				ws.onmessage = on_message;
				ws.onerror = on_error;
				ws.onclose = on_close;
				timer = null;
			} catch (err) {
				deferred.reject(new Error(err));
			}
		}

		function on_open(evt) {
			_console.log('Connected');
			interval = 500;
			deferred.resolve();
		}

		function on_close(evt) {
			_console.log("Connection closed");
			if (!timer && interval) {
				timer = $timeout(function() {
					connect(ws.url);
				}, interval);
				interval = Math.min(interval + 500, 5000);
			}
		}

		function on_error(evt) {
			_console.error("Websocket connection is broken!");
			deferred.reject(new Error(evt));
		}

		function on_message(evt) {
			try {
				var server_data = JSON.parse(evt.data);
				scope.$apply(function() {
					angular.extend(scope[collection], server_data);
				});
			} catch(e) {
				_console.warn('Data received by server is invalid JSON: ' + evt.data);
			}
		}

		function listener(newValue, oldValue) {
			if (newValue !== undefined) {
				ws.send(JSON.stringify(newValue));
			}
		}

		return {
			connect: function(scope_, channels_, collection_) {
				var parts = [], location = $window.location;
				scope = scope_;
				channels = channels_;
				collection = collection_;
				parts.push(location.protocol === 'https' ? 'wss:' : 'ws:');
				parts.push('//');
				parts.push(location.host);
				parts.push(_prefix);
				parts.push(location.pathname);
				parts.push('?');
				parts.push(channels.join('&'));
				connect(parts.join(''));
				scope[collection] = scope[collection] || {};
				deferred.promise.then(function() {
					scope.$watchCollection(collection, listener);
				});
				return deferred.promise;
			}
		};
	}];
});

})(window.angular);
<|endoftext|>


<commit_before>/*
 * http://rollbar.com/
 */

var extend = require('extend')
  , integration = require('../integration')
  , load = require('load-script')
  , onError = require('on-error');

var Rollbar = module.exports = integration('Rollbar');

Rollbar.prototype.key = 'accessToken';

Rollbar.prototype.defaults = {
  accessToken: null,
  identify: true
};

Rollbar.prototype.initialize = function (options, ready) {
  var rollbarOptions = extend({}, options);
  window._rollbar = window._rollbar ||
                    window._ratchet ||
                    [options.accessToken, rollbarOptions];
  load('//d37gvrvc0wt4s1.cloudfront.net/js/1/rollbar.min.js');

  // Attach the window `onerror` event.
  onError(function() {
    window._rollbar.push(arguments);
  });

  // Rollbar is ready right away since window._rollbar is available
  ready();
};

Rollbar.prototype.identify = function (id, traits, options) {
  // Don't set any person metadata if identify is false
  if (!this.options.identify) return;

  traits = traits || {};
  if (id) traits.id = id;

  if (window._rollbar.shift) {
    var extraParams = window._rollbar[1] || {};
    extend(extraParams, {person: traits});
    window._rollbar[1] = extraParams;
  } else {
    var extraParams = window._rollbar.extraParams || {};
    extend(extraParams, {person: traits});
    window._rollbar.extraParams = extraParams;
  }
};
<commit_msg>code review feedback<commit_after>/*
 * http://rollbar.com/
 */

var extend = require('extend')
  , clone = require('clone')
  , integration = require('../integration')
  , load = require('load-script')
  , onError = require('on-error');

var Rollbar = module.exports = integration('Rollbar');

Rollbar.prototype.key = 'accessToken';

Rollbar.prototype.defaults = {
  accessToken: null,
  identify: true
};

Rollbar.prototype.initialize = function (options, ready) {
  var rollbarOptions = clone(options);
  window._rollbar = window._rollbar ||
                    window._ratchet ||
                    [options.accessToken, rollbarOptions];
  load('//d37gvrvc0wt4s1.cloudfront.net/js/1/rollbar.min.js');

  // Attach the window `onerror` event.
  onError(function() {
    window._rollbar.push(arguments);
  });

  // Rollbar is ready right away since window._rollbar is available
  ready();
};

Rollbar.prototype.identify = function (id, traits, options) {
  // Don't set any person metadata if identify is false
  if (!this.options.identify) return;

  traits = traits || {};
  if (id) traits.id = id;

  if (window._rollbar.shift) {
    var extraParams = window._rollbar[1] || {};
    extend(extraParams, {person: traits});
    window._rollbar[1] = extraParams;
  } else {
    var extraParams = window._rollbar.extraParams || {};
    extend(extraParams, {person: traits});
    window._rollbar.extraParams = extraParams;
  }
};
<|endoftext|>