<commit_before>$(document).ready(function() {

  // Source: http://viralpatel.net/blogs/jquery-get-text-element-without-child-element/
  jQuery.fn.textOnly = function() {
    return $(this)
      .clone()
      .children()
      .remove()
      .end() // Go back to cloned element
      .text();
  };

  // Functions showing action buttons for in-line editing

  function showUpdateFeatureButton() {
    $(this).off("click");
    $(this).parent().next().show();
  }

  function showRenameValueButton() {
    $(this).off("click");
    $(this).find(".rename-value").show();
  }

  // Functions invoking AJAX actions

  function deleteFeature(clickEvent) {
    clickEvent.preventDefault();
    featureItem = $(this).parent();
    featureName = featureItem.find(".feature-name").text();
    route = jsFeatureRoutes.controllers.Features.deleteFeature(featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      statusCode: {
        200: function() {
          featureItem.fadeOut(2000);
          alertBlock = $("<span>").addClass("text-success")
            .text("Feature successfully deleted!");
          $("#interaction-block").html(alertBlock);
          },
        400: function() {
          alertBlock = $("<span>").addClass("text-danger")
            .text("Can't delete feature.");
          $("#interaction-block").html(alertBlock);
          }
      }
    });
  }

  function updateFeatureName(clickEvent) {
    clickEvent.preventDefault();
    nameField = $(this).parent().find(".name");
    oldName = nameField.data("name");
    newName = nameField.text();
    featureItem = $("a[href='#" + oldName + "']");
    editBlock = $("#"+oldName);
    updateButton = $(this);
    route = jsFeatureRoutes.controllers.Features.updateFeatureName(oldName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "name": newName },
      statusCode: {
        200: function() {
          nameField.data("name", newName);
          featureItem.attr("href", "#"+newName)
            .text(newName);
          editBlock.attr("id", newName);
          editBlock
            .find(".name").attr("data-name", newName);
          editBlock
            .find(".name").text(newName);
          alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("Name successfully updated!");
          alertBlock.insertAfter(updateButton);
          updateButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
        },
        400: function() {
          nameField.text(oldName);
          updateButton.hide();
          alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("A feature with that name already exists.");
          nameField.append(alertBlock);
          alertBlock.fadeOut(5000);
        }
      }
    });
    nameField.on("click", showUpdateFeatureButton);
  }

  function updateFeatureDescription(clickEvent) {
    clickEvent.preventDefault();
    feature = $(this).parent().find("h3").text();
    editBlock = $("#"+feature);
    descriptionField = $(this).parent().find(".description");
    newDescription = descriptionField.text();
    updateButton = $(this);
    route = jsFeatureRoutes.controllers.Features
      .updateFeatureDescription(feature);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "description": newDescription },
      success: function(result) {
        editBlock.find(".description").text(newDescription);
        alertBlock = $("<span>").addClass("text-success")
          .css("padding-left", "10px")
          .text("Description successfully updated!");
        alertBlock.insertAfter(updateButton);
        updateButton.fadeOut(5000);
        alertBlock.fadeOut(5000);
      }
    });
    descriptionField.on("click", showUpdateFeatureButton);
  }

  function updateFeatureType(clickEvent) {
    clickEvent.preventDefault();
    updateButton = $(this);
    featureName = updateButton.parents("form").data("feature");
    newType = updateButton.parents("form").find("input:checked").val();
    route = jsFeatureRoutes.controllers.Features
      .updateFeatureType(featureName);
    editBlock = $("#"+featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": newType },
      statusCode: {
        200: function() {
          oldType = newType === "complex" ? "atomic" : "complex";
          editBlock.find("input[value='" + oldType + "']")
            .removeAttr('checked');
          editBlock.find("input[value='" + newType + "']")
            .attr('checked', true);
          updateButton.hide();
          alertBlock = $("<span>").addClass("text-success")
            .text("Type successfully updated!");
          updateButton.parent().append(alertBlock);
          alertBlock.fadeOut(2000);
        },
        400: function() {
          alertBlock = $("<span>").addClass("text-danger")
            .text("Type not updated.");
          updateButton.parent().append(alertBlock);
        }
      }
    });
  }

  function deleteTarget(clickEvent) {
    clickEvent.preventDefault();
    deleteTargetButton = $(this);
    targetForm = deleteTargetButton.parents("form");
    featureName = targetForm.data("feature");
    featureType = targetForm.data("type");
    targetName = targetForm.data("target");
    editBlock = $("#"+featureName);
    route = jsFeatureRoutes.controllers.Features.deleteTarget(
      featureName, targetName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": featureType },
      statusCode: {
        200: function() {
          alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("OK!");
          alertBlock.insertAfter(deleteTargetButton);
          deleteTargetButton.remove();
          targetForm.fadeOut(3000);
          setTimeout(function() { targetForm.remove(); }, 3500);
          editBlock.find("form[data-target='" + targetName + "']").remove();
        },
        400: function() {
          alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Error. Could not remove target from feature.");
          alertBlock.insertAfter(deleteTargetButton);
        }
      }
    });
  }

  function addTargets(clickEvent) {
    clickEvent.preventDefault();
    addTargetsButton = $(this);
    targetsForm = addTargetsButton.parent("form");
    featureName = targetsForm.data("feature");
    featureType = targetsForm.data("type");
    inputField = targetsForm.find("input[name='target']");
    target = inputField.val();
    editBlock = $("#"+featureName);
    route = jsFeatureRoutes.controllers.Features.addTargets(featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": featureType, "target": target },
      statusCode: {
        200: function() {
          newTargetForm = $("<form>").attr("role", "form")
            .attr("data-feature", featureName)
            .attr("data-type", featureType)
            .attr("data-target", target);
          newTargetName = $("<div>").addClass("target-name").text(target);
          newDeleteButton = $("<button>")
            .css("margin-left", "5px")
            .addClass("btn btn-xs btn-warning delete-target")
            .attr("type", "submit")
            .text("Delete");
          newDeleteButton.on("click", deleteTarget);
          newDeleteButton.hide();
          newTargetName.append(newDeleteButton);
          newTargetName.on("mouseenter", function() {
            $(this).parent().find(".delete-target").show();
          });
          newTargetName.on("mouseleave", function() {
            $(this).parent().find(".delete-target").hide();
          });
          newTargetForm.html(newTargetName);
          targetsForm.before(newTargetForm);
          inputField.val("");
          alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("OK!");
          alertBlock.insertAfter(addTargetsButton).fadeOut(5000);
          addTargetsButton.attr("disabled", true);
          interactionBlock = targetsForm.parents("#interaction-block");
          editBlock.html(interactionBlock.html());
          editBlock.find(".text-success").remove();
        },
        400: function() {
          inputField.val("");
          alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Error. Could not add target.");
          alertBlock.insertAfter(addTargetsButton).fadeOut(5000);
          addTargetsButton.attr("disabled", true);
        }
      }
    });
  }

  function renameValue(clickEvent) {
    clickEvent.preventDefault();
    renameButton = $(this);
    value = $(this).parent();
    oldName = value.data("value");
    newName = $.trim(value.textOnly());
    route = jsValueRoutes.controllers.Values.renameValue(oldName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "name": newName },
      statusCode: {
        200: function() {
          $("form[data-target='" + oldName + "'] > .target-name")
            .each(function() {
              deleteButton = $(this).children("button");
              $(this).empty();
              $(this).text(newName);
              deleteButton.css("margin-left", "5px");
              $(this).append(deleteButton);
              $(this).parent().attr("data-target", newName);
            });
          value.data("value", newName);
          alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("Rename successful!");
          alertBlock.insertAfter(renameButton);
          renameButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
          value.on("click", showRenameValueButton);
        },
        400: function() {
          alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Value not renamed.");
          alertBlock.insertAfter(renameButton);
          renameButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
          value.on("click", showRenameValueButton);
        }
      }
    });
  }

  // Hide some elements by default

  $("#new-feature").hide();
  $(".edit-feature").hide();
  $(".delete-feature").hide();
  $(".rename-value").hide();

  // Set up event handlers

  $("#new-feature-button").on("click", function(event) {
    event.preventDefault();
    $(".alert").hide();
    $(this).hide();
    $("#interaction-block").html($("#new-feature").html());
  });

  $(".delete-feature").on("click", deleteFeature);

  $(".feature-item").on("mouseenter click", function() {
    deleteButton = $(this).find(".delete-feature");
    deleteButton.show();
  });

  $(".feature-item").on("mouseleave", function() {
    deleteButton = $(this).find(".delete-feature");
    deleteButton.hide();
  });

  $(".feature-name").on("click", function(event) {
    event.preventDefault();
    $(".alert").hide();

    $("#interaction-block").html($("#"+$(this).text()).html());

    $(".update-feature-name").on("click", updateFeatureName);
    $(".update-feature-name").hide();
    $(".name").on("click", showUpdateFeatureButton);

    $(".update-feature-description").on("click", updateFeatureDescription);
    $(".update-feature-description").hide();
    $(".description").on("click", showUpdateFeatureButton);

    $(".update-type").hide();
    $(":radio").on("change", function() {
      updateButton = $(this).parents("form").find("button");
      updateButton.one("click", updateFeatureType);
      updateButton.show();
    });

    $(".delete-target").hide();
    $(".delete-target").on("click", deleteTarget);
    $(".target-name").on("mouseenter", function() {
      $(this).find(".delete-target").show();
    });
    $(".target-name").on("mouseleave", function() {
      $(this).find(".delete-target").hide();
    });

    $(".add-targets").attr("disabled", true);
    $(".add-targets").on("click", addTargets);
    $("input[name='target']").on("keyup", function() {
      addTargetsButton = $(".add-targets");
      if (addTargetsButton.is(":disabled")) {
        if ($(this).val()) {
          addTargetsButton.removeAttr("disabled");
        }
      } else {
        if (!$(this).val()) {
          addTargetsButton.attr("disabled", true);
        }
      }
    });

    $("#new-feature-button").show();
  });

  $(".rename-value").on("click", renameValue);
  $(".value").on("click", showRenameValueButton);

  // Functionality for filtering global feature and value lists

  // - Gather list of all features:
  featureNames = $(".feature-name").map(function() {
    return $(this);
  });

  $("#feature-filter").on("keyup", function(event) {
    // - Get current input:
    currentInput = $(this).val().toLowerCase();
    // - Match list items against current input:
    featureNames.each(function() {
      featureItem = $(this).parent();
      featureName = $(this).text();
      if (featureName.toLowerCase().indexOf(currentInput) === -1) {
        featureItem.hide();
      } else {
        if (!featureItem.is(":visible")) {
          featureItem.show();
        }
      }
    });
  });

  // - Gather list of all values:
  valueNames = $(".value").map(function() {
    return $(this);
  });

  $("#value-filter").on("keyup", function(event) {
    // - Get current input:
    currentInput = $(this).val().toLowerCase();
    // - Match list items against current input:
    valueNames.each(function() {
      value = $(this);
      valueName = value.textOnly();
      if (valueName.toLowerCase().indexOf(currentInput) === -1) {
        value.hide();
      } else {
        if (!value.is(":visible")) {
          value.show();
        }
      }
    });
  });

});
<commit_msg>Refactor: Make variables defined by functions local to these functions (by using `var`).<commit_after>$(document).ready(function() {

  // Source: http://viralpatel.net/blogs/jquery-get-text-element-without-child-element/
  jQuery.fn.textOnly = function() {
    return $(this)
      .clone()
      .children()
      .remove()
      .end() // Go back to cloned element
      .text();
  };

  // Functions showing action buttons for in-line editing

  function showUpdateFeatureButton() {
    $(this).off("click");
    $(this).parent().next().show();
  }

  function showRenameValueButton() {
    $(this).off("click");
    $(this).find(".rename-value").show();
  }

  // Functions invoking AJAX actions

  function deleteFeature(clickEvent) {
    clickEvent.preventDefault();
    var featureItem = $(this).parent();
    var featureName = featureItem.find(".feature-name").text();
    var route = jsFeatureRoutes.controllers.Features
      .deleteFeature(featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      statusCode: {
        200: function() {
          featureItem.fadeOut(2000);
          var alertBlock = $("<span>").addClass("text-success")
            .text("Feature successfully deleted!");
          $("#interaction-block").html(alertBlock);
          },
        400: function() {
          var alertBlock = $("<span>").addClass("text-danger")
            .text("Can't delete feature.");
          $("#interaction-block").html(alertBlock);
          }
      }
    });
  }

  function updateFeatureName(clickEvent) {
    clickEvent.preventDefault();
    var nameField = $(this).parent().find(".name");
    var oldName = nameField.data("name");
    var newName = nameField.text();
    var featureItem = $("a[href='#" + oldName + "']");
    var editBlock = $("#"+oldName);
    var updateButton = $(this);
    var route = jsFeatureRoutes.controllers.Features
      .updateFeatureName(oldName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "name": newName },
      statusCode: {
        200: function() {
          nameField.data("name", newName);
          featureItem.attr("href", "#"+newName)
            .text(newName);
          editBlock.attr("id", newName);
          editBlock
            .find(".name").attr("data-name", newName);
          editBlock
            .find(".name").text(newName);
          var alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("Name successfully updated!");
          alertBlock.insertAfter(updateButton);
          updateButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
        },
        400: function() {
          nameField.text(oldName);
          updateButton.hide();
          var alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("A feature with that name already exists.");
          nameField.append(alertBlock);
          alertBlock.fadeOut(5000);
        }
      }
    });
    nameField.on("click", showUpdateFeatureButton);
  }

  function updateFeatureDescription(clickEvent) {
    clickEvent.preventDefault();
    var feature = $(this).parent().find("h3").text();
    var editBlock = $("#"+feature);
    var descriptionField = $(this).parent().find(".description");
    var newDescription = descriptionField.text();
    var updateButton = $(this);
    var route = jsFeatureRoutes.controllers.Features
      .updateFeatureDescription(feature);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "description": newDescription },
      success: function(result) {
        editBlock.find(".description").text(newDescription);
        var alertBlock = $("<span>").addClass("text-success")
          .css("padding-left", "10px")
          .text("Description successfully updated!");
        alertBlock.insertAfter(updateButton);
        updateButton.fadeOut(5000);
        alertBlock.fadeOut(5000);
      }
    });
    descriptionField.on("click", showUpdateFeatureButton);
  }

  function updateFeatureType(clickEvent) {
    clickEvent.preventDefault();
    var updateButton = $(this);
    var featureName = updateButton.parents("form").data("feature");
    var newType = updateButton.parents("form").find("input:checked").val();
    var route = jsFeatureRoutes.controllers.Features
      .updateFeatureType(featureName);
    var editBlock = $("#"+featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": newType },
      statusCode: {
        200: function() {
          var oldType = newType === "complex" ? "atomic" : "complex";
          editBlock.find("input[value='" + oldType + "']")
            .removeAttr('checked');
          editBlock.find("input[value='" + newType + "']")
            .attr('checked', true);
          updateButton.hide();
          var alertBlock = $("<span>").addClass("text-success")
            .text("Type successfully updated!");
          updateButton.parent().append(alertBlock);
          alertBlock.fadeOut(2000);
        },
        400: function() {
          var alertBlock = $("<span>").addClass("text-danger")
            .text("Type not updated.");
          updateButton.parent().append(alertBlock);
        }
      }
    });
  }

  function deleteTarget(clickEvent) {
    clickEvent.preventDefault();
    var deleteTargetButton = $(this);
    var targetForm = deleteTargetButton.parents("form");
    var featureName = targetForm.data("feature");
    var featureType = targetForm.data("type");
    var targetName = targetForm.data("target");
    var editBlock = $("#"+featureName);
    var route = jsFeatureRoutes.controllers.Features.deleteTarget(
      featureName, targetName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": featureType },
      statusCode: {
        200: function() {
          var alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("OK!");
          alertBlock.insertAfter(deleteTargetButton);
          deleteTargetButton.remove();
          targetForm.fadeOut(3000);
          setTimeout(function() { targetForm.remove(); }, 3500);
          editBlock.find("form[data-target='" + targetName + "']").remove();
        },
        400: function() {
          var alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Error. Could not remove target from feature.");
          alertBlock.insertAfter(deleteTargetButton);
        }
      }
    });
  }

  function addTargets(clickEvent) {
    clickEvent.preventDefault();
    var addTargetsButton = $(this);
    var targetsForm = addTargetsButton.parent("form");
    var featureName = targetsForm.data("feature");
    var featureType = targetsForm.data("type");
    var inputField = targetsForm.find("input[name='target']");
    var target = inputField.val();
    var editBlock = $("#"+featureName);
    var route = jsFeatureRoutes.controllers.Features.addTargets(featureName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "type": featureType, "target": target },
      statusCode: {
        200: function() {
          var newTargetForm = $("<form>").attr("role", "form")
            .attr("data-feature", featureName)
            .attr("data-type", featureType)
            .attr("data-target", target);
          var newTargetName = $("<div>").addClass("target-name").text(target);
          var newDeleteButton = $("<button>")
            .css("margin-left", "5px")
            .addClass("btn btn-xs btn-warning delete-target")
            .attr("type", "submit")
            .text("Delete");
          newDeleteButton.on("click", deleteTarget);
          newDeleteButton.hide();
          newTargetName.append(newDeleteButton);
          newTargetName.on("mouseenter", function() {
            $(this).parent().find(".delete-target").show();
          });
          newTargetName.on("mouseleave", function() {
            $(this).parent().find(".delete-target").hide();
          });
          newTargetForm.html(newTargetName);
          targetsForm.before(newTargetForm);
          inputField.val("");
          var alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("OK!");
          alertBlock.insertAfter(addTargetsButton).fadeOut(5000);
          addTargetsButton.attr("disabled", true);
          interactionBlock = targetsForm.parents("#interaction-block");
          editBlock.html(interactionBlock.html());
          editBlock.find(".text-success").remove();
        },
        400: function() {
          inputField.val("");
          var alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Error. Could not add target.");
          alertBlock.insertAfter(addTargetsButton).fadeOut(5000);
          addTargetsButton.attr("disabled", true);
        }
      }
    });
  }

  function renameValue(clickEvent) {
    clickEvent.preventDefault();
    var renameButton = $(this);
    var value = $(this).parent();
    var oldName = value.data("value");
    var newName = $.trim(value.textOnly());
    var route = jsValueRoutes.controllers.Values.renameValue(oldName);
    $.ajax({
      url: route.url,
      type: route.type,
      data: { "name": newName },
      statusCode: {
        200: function() {
          $("form[data-target='" + oldName + "'] > .target-name")
            .each(function() {
              var deleteButton = $(this).children("button");
              $(this).empty();
              $(this).text(newName);
              deleteButton.css("margin-left", "5px");
              $(this).append(deleteButton);
              $(this).parent().attr("data-target", newName);
            });
          value.data("value", newName);
          var alertBlock = $("<span>").addClass("text-success")
            .css("padding-left", "10px")
            .text("Rename successful!");
          alertBlock.insertAfter(renameButton);
          renameButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
          value.on("click", showRenameValueButton);
        },
        400: function() {
          var alertBlock = $("<span>").addClass("text-danger")
            .css("padding-left", "10px")
            .text("Value not renamed.");
          alertBlock.insertAfter(renameButton);
          renameButton.fadeOut(5000);
          alertBlock.fadeOut(5000);
          value.on("click", showRenameValueButton);
        }
      }
    });
  }

  // Hide some elements by default

  $("#new-feature").hide();
  $(".edit-feature").hide();
  $(".delete-feature").hide();
  $(".rename-value").hide();

  // Set up event handlers

  $("#new-feature-button").on("click", function(event) {
    event.preventDefault();
    $(".alert").hide();
    $(this).hide();
    $("#interaction-block").html($("#new-feature").html());
  });

  $(".delete-feature").on("click", deleteFeature);

  $(".feature-item").on("mouseenter click", function() {
    deleteButton = $(this).find(".delete-feature");
    deleteButton.show();
  });

  $(".feature-item").on("mouseleave", function() {
    deleteButton = $(this).find(".delete-feature");
    deleteButton.hide();
  });

  $(".feature-name").on("click", function(event) {
    event.preventDefault();
    $(".alert").hide();

    $("#interaction-block").html($("#"+$(this).text()).html());

    $(".update-feature-name").on("click", updateFeatureName);
    $(".update-feature-name").hide();
    $(".name").on("click", showUpdateFeatureButton);

    $(".update-feature-description").on("click", updateFeatureDescription);
    $(".update-feature-description").hide();
    $(".description").on("click", showUpdateFeatureButton);

    $(".update-type").hide();
    $(":radio").on("change", function() {
      updateButton = $(this).parents("form").find("button");
      updateButton.one("click", updateFeatureType);
      updateButton.show();
    });

    $(".delete-target").hide();
    $(".delete-target").on("click", deleteTarget);
    $(".target-name").on("mouseenter", function() {
      $(this).find(".delete-target").show();
    });
    $(".target-name").on("mouseleave", function() {
      $(this).find(".delete-target").hide();
    });

    $(".add-targets").attr("disabled", true);
    $(".add-targets").on("click", addTargets);
    $("input[name='target']").on("keyup", function() {
      addTargetsButton = $(".add-targets");
      if (addTargetsButton.is(":disabled")) {
        if ($(this).val()) {
          addTargetsButton.removeAttr("disabled");
        }
      } else {
        if (!$(this).val()) {
          addTargetsButton.attr("disabled", true);
        }
      }
    });

    $("#new-feature-button").show();
  });

  $(".rename-value").on("click", renameValue);
  $(".value").on("click", showRenameValueButton);

  // Functionality for filtering global feature and value lists

  // - Gather list of all features:
  featureNames = $(".feature-name").map(function() {
    return $(this);
  });

  $("#feature-filter").on("keyup", function(event) {
    // - Get current input:
    currentInput = $(this).val().toLowerCase();
    // - Match list items against current input:
    featureNames.each(function() {
      featureItem = $(this).parent();
      featureName = $(this).text();
      if (featureName.toLowerCase().indexOf(currentInput) === -1) {
        featureItem.hide();
      } else {
        if (!featureItem.is(":visible")) {
          featureItem.show();
        }
      }
    });
  });

  // - Gather list of all values:
  valueNames = $(".value").map(function() {
    return $(this);
  });

  $("#value-filter").on("keyup", function(event) {
    // - Get current input:
    currentInput = $(this).val().toLowerCase();
    // - Match list items against current input:
    valueNames.each(function() {
      value = $(this);
      valueName = value.textOnly();
      if (valueName.toLowerCase().indexOf(currentInput) === -1) {
        value.hide();
      } else {
        if (!value.is(":visible")) {
          value.show();
        }
      }
    });
  });

});
<|endoftext|>


<commit_before>'use strict';

/* jshint esnext: true */

const exec = require('child-process-promise').exec;
const path = require('path');
const fs = require('fs-extra-promise');
const thaliConfig = require('../package.json');
const os = require('os');
const http = require('http');
const url = require('url');
const assert = require('assert');

const versions =
{
  xcode: '7.3.1',
  xcodeCommandLineTools: ' ',
  macOS: '10.12.1',
  node: '6.9.1',
  npm: '3.10.8',
  brew: '1.1.',
  ruby: '2.3.0p0',
  wget: '1.18',
  jxcore: '0.3.1.6',
  androidHome: ' ',
  androidBuildTools: '25.0.0',
  androidPlatform: 'android-23',
  // We don't have an easy way to identify the version of the support libraries
  // we have but if they were installed recently enough then they will have
  // what we need.
  androidSupportLibraries: '40.0.0',
  cordovaAndroidSetMinSDK: '22',
  get cordovaAndroidSetBuildToolsVersion() {
    return this.androidBuildTools;
  },
  get cordovaAndroidSetCompileSdkVersion() {
    return this.androidPlatform;
  },
  python: '2.7.10',
  cordova: '6.4.0',
  java: '1.8.0_102',
  git: '2.10.0',
  swiftlint: '0.13.0',
  btconnectorlib2: '0.3.5',
  jxcoreCordova: '0.1.6',
  sinopiaNode: ' ',
  sinopiaJxCore: ' '
};

module.exports.versions = versions;

function sinopiaVersionCheck(sinopiaUrl) {
  return new Promise((resolve, reject) => {
    var parsedUrl = url.parse(sinopiaUrl);
    if (parsedUrl.protocol !== 'http:') {
      return Promise.reject();
    }
    var testThaliUrl =
      sinopiaUrl + (sinopiaUrl.endsWith('/') ? '' : '/') + 'thali';
    http.get(testThaliUrl, (res) => {
      let result = '';
      res.on('data', (chunk) => result += chunk);
      res.on('end', () => {
        try {
          var thaliResponse = JSON.parse(result);
          thaliResponse.name === 'thali' ? resolve() : reject();
        } catch (e) {
          reject(e);
        }
      });
      res.on('error', (err) => reject(err));
    });
  });
}

function boolToPromise(result) {
  return result ? Promise.resolve() : Promise.reject();
}

const commandsAndResults =
{
  xcode: {
    platform: ['darwin'],
    versionCheck: 'xcodebuild -version',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('Xcode '+ version + '\n'))
  },
  xcodeCommandLineTools: {
    platform: ['darwin'],
    // I couldn't find any reliable way to validate which versions of the
    // tools are installed. The best I could do was find out which directory
    // they are supposed to be in. I tried http://stackoverflow.com/questions/15371925/how-to-check-if-command-line-tools-is-installed
    // and xcode-select -p returns a directory inside of XCode and none of
    // the pkgutil commands worked properly on my machine.
    versionCheck: () => fs.readdirAsync('/Library/Developer/CommandLineTools'),
    versionValidate:
      (result, version) => boolToPromise(result && result.length === 2 &&
      result[0] === 'Library' && result[1] === 'usr')
  },
  macOS: {
    platform: ['darwin'],
    versionCheck: 'sw_vers -productVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  node: {
    versionCheck: 'node -v',
    versionValidate:
      (result, version) => boolToPromise('v' + version === result.trim())
  },
  npm: {
    versionCheck: 'npm -v',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  brew: {
    platform: ['darwin'],
    versionCheck: 'brew -v',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('Homebrew ' + version))
  },
  ruby: {
    platform: ['darwin'],
    versionCheck: 'ruby -v',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('ruby ' + version + ' '))
  },
  wget: {
    versionCheck: 'wget -V',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('GNU Wget ' + version + ' '))
  },
  jxcore: {
    versionCheck: 'jx -jxv',
    versionValidate:
      (result, version) => boolToPromise('v' + version === result.trim())
  },
  androidHome: {
    versionCheck: () => process.env.ANDROID_HOME,
    versionValidate:
      (result, version) => {
        if (result) {
          return fs.readdirAsync(result);
        } else {
          return Promise.reject();
        }
      }
  },
  androidBuildTools: {
    versionCheck: () => fs.readdirAsync(path.join(process.env.ANDROID_HOME,
      'build-tools')),
    versionValidate:
      (result, version) => boolToPromise(result.indexOf(version) !== -1)
  },
  androidPlatform: {
    versionCheck: () => fs.readdirAsync(path.join(process.env.ANDROID_HOME,
      'platforms')),
    versionValidate:
      (result, version) => boolToPromise(result.indexOf(version) !== -1)
  },
  androidSupportLibraries: {
    versionCheck: () => {
      const sourcePropertiesLocation =
        path.join(process.env.ANDROID_HOME,
          'extras/android/m2repository/source.properties');
      return fs.readFileAsync(sourcePropertiesLocation, 'utf8')
        .then((sourcePropertiesFileContents) => {
          const regEx =
            sourcePropertiesFileContents.match(/^Pkg\.Revision=(.*)$/m);
          if (!regEx[1]) {
            return Promise.reject();
          }
          return Promise.resolve(regEx[1]);
        });
    },
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetMinSDK: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvMinSdkVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetBuildToolsVersion: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvBuildToolsVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetCompileSdkVersion: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvCompileSdkVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  python: {
    versionCheck: 'python -V',
    checkStdErr: true, // http://bugs.python.org/issue28160 - fixed in 3.4
    versionValidate:
      (result, version) => boolToPromise('Python ' + version === result.trim())
  },
  cordova: {
    versionCheck: 'cordova -v',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  java: {
    versionCheck: 'java -version',
    checkStdErr: true, // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8166116
    versionValidate:
      (result, version) => boolToPromise(result.startsWith('java version "' +
                                          version + '"\n'))
  },
  git: {
    versionCheck: 'git --version',
    versionValidate:
      (result, version) => boolToPromise(
                                    'git version ' + version === result.trim())
  },
  swiftlint: {
    platform: ['darwin'],
    versionCheck: 'swiftlint version',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  btconnectorlib2: {
    versionCheck: () =>
      Promise.resolve(thaliConfig.thaliInstall.btconnectorlib2),
    versionValidate:
      (result, version) => boolToPromise(version === result)
  },
  jxcoreCordova: {
    versionCheck: () =>
      Promise.resolve(thaliConfig.thaliInstall['jxcore-cordova']),
    versionValidate:
      (result, version) => boolToPromise(version === result)
  },
  sinopiaNode: {
    versionCheck: 'npm get registry',
    versionValidate:
      (result, version) => sinopiaVersionCheck(result.trim())
  },
  sinopiaJxCore: {
    // The first time jx npm is run it can do an install, to simplify things
    // we just call it twice so the second call will be normal and give us the
    // url
    versionCheck: 'jx npm get registry > /dev/null && jx npm get registry',
    versionValidate:
      (result, version) => sinopiaVersionCheck(result.trim())
  }

};

function execAndCheck(command, checkStdErr, version, validator) {
  return exec(command)
    .then((result) => {
      const output = checkStdErr ? result.stderr : result.stdout;
      return validator(output, version);
    })
    .then(() => Promise.resolve(true))
    .catch(() => Promise.reject(new Error('Command: ' + command + ' failed')));
}

function promiseTry(fn) {
  try {
    return Promise.resolve(fn());
  } catch (error) {
    return Promise.reject(error);
  }
}

/**
 * Checks if the named object is installed with the named version, if any. If
 * versionNumber isn't given then we default to checking the versions global
 * object.
 * @param {string} objectName Name of the object to validate
 * @param {string} [versionNumber] An optional string specifying the desired
 * version. If omitted we will check the versions structure.
 * @returns {Promise<Error|boolean>} If the desired object is found at the
 * desired version then a resolve will be returned set to true. Otherwise an
 * error will be returned specifying what went wrong.
 */
function checkVersion(objectName, versionNumber) {
  const desiredVersion = versionNumber ? versionNumber : versions[objectName];
  const commandAndResult = commandsAndResults[objectName];
  if (!commandAndResult) {
    return Promise.reject(
      new Error('Unrecognized objectName in commandsAndResults'));
  }
  if (!desiredVersion) {
    return Promise.reject(new Error('Unrecognized objectName in versions'));
  }
  if (commandAndResult.platform &&
      !commandAndResult.platform.includes(os.platform()))
  {
    return Promise.reject(new Error('Requested object is not supported on ' +
      'this platform'));
  }
  if (typeof commandAndResult.versionCheck === 'function') {
    return promiseTry(commandAndResult.versionCheck)
      .catch(() =>
        Promise.reject(new Error('Version Check failed on ' + objectName)))
      .then((versionCheckResult) =>
        commandAndResult.versionValidate(versionCheckResult, desiredVersion))
      .then(() => Promise.resolve(true))
      .catch(() => Promise.reject(
                        new Error('Version not installed of ' + objectName)));
  }
  return execAndCheck(commandAndResult.versionCheck,
                      commandAndResult.checkStdErr,
                      desiredVersion,
                      commandAndResult.versionValidate);
}

module.exports.checkVersion = checkVersion;

function processCommandsAndResults(commandsAndResults) {
  let passed = true;
  let errorMessage = '';
  let runningPromise = Object.getOwnPropertyNames(commandsAndResults)
        .reduce((runningPromise, name) => {
          if (commandsAndResults[name].platform &&
            !commandsAndResults[name].platform.includes(os.platform())) {
            return runningPromise;
          }
          return runningPromise.then(() =>
            checkVersion(name)
              .catch((err) => {
                passed = false;
                errorMessage += '\n' + 'Object Name: ' + name + ' : ' + err;
                return Promise.resolve();
              }));
        }, Promise.resolve());
  return runningPromise
    .then(() => passed ? Promise.resolve() : Promise.reject(errorMessage));
}

// Detects if we were called from the command line
if (require.main === module) {
  if (os.platform() !== 'darwin') {
    console.log('WARNING: WE ONLY SUPPORT OS/X AS A BUILD PLATFORM, USING ANY' +
      'OTHER PLATFORM IS NOT OFFICIALLY SUPPORTED. WE STILL CHECK A FEW ' +
      'THINGS BUT YOU ARE REALLY ON YOUR OWN');
  }
  assert.deepStrictEqual(Object.getOwnPropertyNames(versions),
                         Object.getOwnPropertyNames(commandsAndResults),
                         'Versions and commandsAndResults keys must be equal');
  processCommandsAndResults(commandsAndResults)
    .then(() => {
      // Good to clean this up in case we have changed the version of jxcore
      const home = process.env.HOME;
      const jx = path.join(home, '.jx');
      const jxc = path.join(home, '.jxc');
      const nodeGyp = path.join(home, '.node-gyp');
      return Promise.all([fs.removeAsync(jx), fs.removeAsync(jxc),
                         fs.removeAsync(nodeGyp)]);
    })
    .then(() => {
      console.log('Environment validated');
      process.exit(0);
    })
    .catch((err) => {
      console.log('Environment not valid: ' + err);
      process.exit(-1);
    });
}
<commit_msg>update jxcore to 0.3.1.7 update jxcore-cordova to 0.1.7<commit_after>'use strict';

/* jshint esnext: true */

const exec = require('child-process-promise').exec;
const path = require('path');
const fs = require('fs-extra-promise');
const thaliConfig = require('../package.json');
const os = require('os');
const http = require('http');
const url = require('url');
const assert = require('assert');

const versions =
{
  xcode: '7.3.1',
  xcodeCommandLineTools: ' ',
  macOS: '10.12.1',
  node: '6.9.1',
  npm: '3.10.8',
  brew: '1.1.',
  ruby: '2.3.0p0',
  wget: '1.18',
  jxcore: '0.3.1.7',
  androidHome: ' ',
  androidBuildTools: '25.0.0',
  androidPlatform: 'android-23',
  // We don't have an easy way to identify the version of the support libraries
  // we have but if they were installed recently enough then they will have
  // what we need.
  androidSupportLibraries: '40.0.0',
  cordovaAndroidSetMinSDK: '22',
  get cordovaAndroidSetBuildToolsVersion() {
    return this.androidBuildTools;
  },
  get cordovaAndroidSetCompileSdkVersion() {
    return this.androidPlatform;
  },
  python: '2.7.10',
  cordova: '6.4.0',
  java: '1.8.0_102',
  git: '2.10.0',
  swiftlint: '0.13.0',
  btconnectorlib2: '0.3.5',
  jxcoreCordova: '0.1.7',
  sinopiaNode: ' ',
  sinopiaJxCore: ' '
};

module.exports.versions = versions;

function sinopiaVersionCheck(sinopiaUrl) {
  return new Promise((resolve, reject) => {
    var parsedUrl = url.parse(sinopiaUrl);
    if (parsedUrl.protocol !== 'http:') {
      return Promise.reject();
    }
    var testThaliUrl =
      sinopiaUrl + (sinopiaUrl.endsWith('/') ? '' : '/') + 'thali';
    http.get(testThaliUrl, (res) => {
      let result = '';
      res.on('data', (chunk) => result += chunk);
      res.on('end', () => {
        try {
          var thaliResponse = JSON.parse(result);
          thaliResponse.name === 'thali' ? resolve() : reject();
        } catch (e) {
          reject(e);
        }
      });
      res.on('error', (err) => reject(err));
    });
  });
}

function boolToPromise(result) {
  return result ? Promise.resolve() : Promise.reject();
}

const commandsAndResults =
{
  xcode: {
    platform: ['darwin'],
    versionCheck: 'xcodebuild -version',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('Xcode '+ version + '\n'))
  },
  xcodeCommandLineTools: {
    platform: ['darwin'],
    // I couldn't find any reliable way to validate which versions of the
    // tools are installed. The best I could do was find out which directory
    // they are supposed to be in. I tried http://stackoverflow.com/questions/15371925/how-to-check-if-command-line-tools-is-installed
    // and xcode-select -p returns a directory inside of XCode and none of
    // the pkgutil commands worked properly on my machine.
    versionCheck: () => fs.readdirAsync('/Library/Developer/CommandLineTools'),
    versionValidate:
      (result, version) => boolToPromise(result && result.length === 2 &&
      result[0] === 'Library' && result[1] === 'usr')
  },
  macOS: {
    platform: ['darwin'],
    versionCheck: 'sw_vers -productVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  node: {
    versionCheck: 'node -v',
    versionValidate:
      (result, version) => boolToPromise('v' + version === result.trim())
  },
  npm: {
    versionCheck: 'npm -v',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  brew: {
    platform: ['darwin'],
    versionCheck: 'brew -v',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('Homebrew ' + version))
  },
  ruby: {
    platform: ['darwin'],
    versionCheck: 'ruby -v',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('ruby ' + version + ' '))
  },
  wget: {
    versionCheck: 'wget -V',
    versionValidate:
      (result, version) =>
        boolToPromise(result.startsWith('GNU Wget ' + version + ' '))
  },
  jxcore: {
    versionCheck: 'jx -jxv',
    versionValidate:
      (result, version) => boolToPromise('v' + version === result.trim())
  },
  androidHome: {
    versionCheck: () => process.env.ANDROID_HOME,
    versionValidate:
      (result, version) => {
        if (result) {
          return fs.readdirAsync(result);
        } else {
          return Promise.reject();
        }
      }
  },
  androidBuildTools: {
    versionCheck: () => fs.readdirAsync(path.join(process.env.ANDROID_HOME,
      'build-tools')),
    versionValidate:
      (result, version) => boolToPromise(result.indexOf(version) !== -1)
  },
  androidPlatform: {
    versionCheck: () => fs.readdirAsync(path.join(process.env.ANDROID_HOME,
      'platforms')),
    versionValidate:
      (result, version) => boolToPromise(result.indexOf(version) !== -1)
  },
  androidSupportLibraries: {
    versionCheck: () => {
      const sourcePropertiesLocation =
        path.join(process.env.ANDROID_HOME,
          'extras/android/m2repository/source.properties');
      return fs.readFileAsync(sourcePropertiesLocation, 'utf8')
        .then((sourcePropertiesFileContents) => {
          const regEx =
            sourcePropertiesFileContents.match(/^Pkg\.Revision=(.*)$/m);
          if (!regEx[1]) {
            return Promise.reject();
          }
          return Promise.resolve(regEx[1]);
        });
    },
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetMinSDK: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvMinSdkVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetBuildToolsVersion: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvBuildToolsVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  cordovaAndroidSetCompileSdkVersion: {
    versionCheck: 'echo $ORG_GRADLE_PROJECT_cdvCompileSdkVersion',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  python: {
    versionCheck: 'python -V',
    checkStdErr: true, // http://bugs.python.org/issue28160 - fixed in 3.4
    versionValidate:
      (result, version) => boolToPromise('Python ' + version === result.trim())
  },
  cordova: {
    versionCheck: 'cordova -v',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  java: {
    versionCheck: 'java -version',
    checkStdErr: true, // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8166116
    versionValidate:
      (result, version) => boolToPromise(result.startsWith('java version "' +
                                          version + '"\n'))
  },
  git: {
    versionCheck: 'git --version',
    versionValidate:
      (result, version) => boolToPromise(
                                    'git version ' + version === result.trim())
  },
  swiftlint: {
    platform: ['darwin'],
    versionCheck: 'swiftlint version',
    versionValidate:
      (result, version) => boolToPromise(version === result.trim())
  },
  btconnectorlib2: {
    versionCheck: () =>
      Promise.resolve(thaliConfig.thaliInstall.btconnectorlib2),
    versionValidate:
      (result, version) => boolToPromise(version === result)
  },
  jxcoreCordova: {
    versionCheck: () =>
      Promise.resolve(thaliConfig.thaliInstall['jxcore-cordova']),
    versionValidate:
      (result, version) => boolToPromise(version === result)
  },
  sinopiaNode: {
    versionCheck: 'npm get registry',
    versionValidate:
      (result, version) => sinopiaVersionCheck(result.trim())
  },
  sinopiaJxCore: {
    // The first time jx npm is run it can do an install, to simplify things
    // we just call it twice so the second call will be normal and give us the
    // url
    versionCheck: 'jx npm get registry > /dev/null && jx npm get registry',
    versionValidate:
      (result, version) => sinopiaVersionCheck(result.trim())
  }

};

function execAndCheck(command, checkStdErr, version, validator) {
  return exec(command)
    .then((result) => {
      const output = checkStdErr ? result.stderr : result.stdout;
      return validator(output, version);
    })
    .then(() => Promise.resolve(true))
    .catch(() => Promise.reject(new Error('Command: ' + command + ' failed')));
}

function promiseTry(fn) {
  try {
    return Promise.resolve(fn());
  } catch (error) {
    return Promise.reject(error);
  }
}

/**
 * Checks if the named object is installed with the named version, if any. If
 * versionNumber isn't given then we default to checking the versions global
 * object.
 * @param {string} objectName Name of the object to validate
 * @param {string} [versionNumber] An optional string specifying the desired
 * version. If omitted we will check the versions structure.
 * @returns {Promise<Error|boolean>} If the desired object is found at the
 * desired version then a resolve will be returned set to true. Otherwise an
 * error will be returned specifying what went wrong.
 */
function checkVersion(objectName, versionNumber) {
  const desiredVersion = versionNumber ? versionNumber : versions[objectName];
  const commandAndResult = commandsAndResults[objectName];
  if (!commandAndResult) {
    return Promise.reject(
      new Error('Unrecognized objectName in commandsAndResults'));
  }
  if (!desiredVersion) {
    return Promise.reject(new Error('Unrecognized objectName in versions'));
  }
  if (commandAndResult.platform &&
      !commandAndResult.platform.includes(os.platform()))
  {
    return Promise.reject(new Error('Requested object is not supported on ' +
      'this platform'));
  }
  if (typeof commandAndResult.versionCheck === 'function') {
    return promiseTry(commandAndResult.versionCheck)
      .catch(() =>
        Promise.reject(new Error('Version Check failed on ' + objectName)))
      .then((versionCheckResult) =>
        commandAndResult.versionValidate(versionCheckResult, desiredVersion))
      .then(() => Promise.resolve(true))
      .catch(() => Promise.reject(
                        new Error('Version not installed of ' + objectName)));
  }
  return execAndCheck(commandAndResult.versionCheck,
                      commandAndResult.checkStdErr,
                      desiredVersion,
                      commandAndResult.versionValidate);
}

module.exports.checkVersion = checkVersion;

function processCommandsAndResults(commandsAndResults) {
  let passed = true;
  let errorMessage = '';
  let runningPromise = Object.getOwnPropertyNames(commandsAndResults)
        .reduce((runningPromise, name) => {
          if (commandsAndResults[name].platform &&
            !commandsAndResults[name].platform.includes(os.platform())) {
            return runningPromise;
          }
          return runningPromise.then(() =>
            checkVersion(name)
              .catch((err) => {
                passed = false;
                errorMessage += '\n' + 'Object Name: ' + name + ' : ' + err;
                return Promise.resolve();
              }));
        }, Promise.resolve());
  return runningPromise
    .then(() => passed ? Promise.resolve() : Promise.reject(errorMessage));
}

// Detects if we were called from the command line
if (require.main === module) {
  if (os.platform() !== 'darwin') {
    console.log('WARNING: WE ONLY SUPPORT OS/X AS A BUILD PLATFORM, USING ANY' +
      'OTHER PLATFORM IS NOT OFFICIALLY SUPPORTED. WE STILL CHECK A FEW ' +
      'THINGS BUT YOU ARE REALLY ON YOUR OWN');
  }
  assert.deepStrictEqual(Object.getOwnPropertyNames(versions),
                         Object.getOwnPropertyNames(commandsAndResults),
                         'Versions and commandsAndResults keys must be equal');
  processCommandsAndResults(commandsAndResults)
    .then(() => {
      // Good to clean this up in case we have changed the version of jxcore
      const home = process.env.HOME;
      const jx = path.join(home, '.jx');
      const jxc = path.join(home, '.jxc');
      const nodeGyp = path.join(home, '.node-gyp');
      return Promise.all([fs.removeAsync(jx), fs.removeAsync(jxc),
                         fs.removeAsync(nodeGyp)]);
    })
    .then(() => {
      console.log('Environment validated');
      process.exit(0);
    })
    .catch((err) => {
      console.log('Environment not valid: ' + err);
      process.exit(-1);
    });
}
<|endoftext|>


<commit_before>import { React, observer, styled } from 'vendor';
import { Icon } from 'shared';
import { colors } from 'theme';
import SettingsIcon from '../../components/icons/settings';
import PublishScores from '../../components/buttons/publish-scores';
import { CornerTriangle } from '../../components/dropped-question';

const Bar = styled.div`
  display: flex;
  justify-content: space-between;
  padding: 1rem 2.4rem;
  border-top: 1px solid ${colors.neutral.pale};
  border-bottom: 1px solid ${colors.neutral.pale};
`;

const StyledSettingsIcon = styled(SettingsIcon)`
    border: 1px solid ${colors.neutral.pale};
    padding: 10px;
    margin-left: 20px;
`;

const StyledQuestionWrapper = styled.div`
  position: relative;
`;

const StyledButton = styled.button`
  border: 1px solid ${colors.neutral.pale};
  width: 6rem;
  height: 6rem;
  font-size: 1.2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: ${colors.neutral.thin};
  background-color: ${({ current }) => current ? colors.neutral.light : 'white'};
  h6 {
    color: ${colors.link};
  }
  & + & {
    border-left: 0;
  }
`;

const QuestionsWrapper = styled.div`
  display: flex;
`;

const Controls = styled.div`
  display: flex;
  align-items: center;
  & .btn.btn-plain {
    border: 1px solid ${colors.neutral.pale};
    padding: 10px;
    margin-left: 20px;
    color: ${colors.neutral.grayblue}
  }
`;

const StyledPublishScores = styled(PublishScores)`
    && {
      padding: 10px;
    }
`;

const Question = observer(({ heading, ux, index }) => {
    const stats = ux.showOnlyAttempted ? heading.gradedStats : heading.gradedStatsWithUnAttemptedResponses;
    const progress = ux.showOnlyAttempted ? heading.gradedProgress : heading.gradedProgressWithUnAttemptedResponses;
    const everyQuestionDropped = heading.everyQuestionDropped;

    return (

            {
                Boolean(everyQuestionDropped) &&
        <CornerTriangle color="blue"
            tooltip={heading.everyQuestionZeroed ?
                'Question dropped: question is worth 0 points' : (heading.everyQuestionFullCredit ?
                'Question dropped: full credit assigned for this question' :
                'Question dropped: some questions worth 0 points, others assigned full credit')}
        />
            }
        </StyledQuestionWrapper>
    );
});

const QuestionsBar = observer(({ ux }) => {
    return (
        <Bar data-test-id="questions-bar" className="questions-bar">
            <QuestionsWrapper>
                {ux.headings.map((heading, index) => <Question key={index} heading={heading} ux={ux} index={index} />)}
            </QuestionsWrapper>
            <Controls>

            {
                Boolean(everyQuestionDropped) &&
        <CornerTriangle color="blue"
            tooltip={heading.everyQuestionZeroed ?
                'Question dropped: question is worth 0 points' : (heading.everyQuestionFullCredit ?
                    'Question dropped: full credit assigned for this question' :
                    'Question dropped: some questions worth 0 points, others assigned full credit')}
        />
            }
        </StyledQuestionWrapper>
    );
});

const QuestionsBar = observer(({ ux }) => {
    return (
        <Bar data-test-id="questions-bar" className="questions-bar">
            <QuestionsWrapper>
                {ux.headings.map((heading, index) => <Question key={index} heading={heading} ux={ux} index={index} />)}
            </QuestionsWrapper>
            <Controls>


<commit_before>import React from 'react';
import glamorous from 'glamorous';
import drawer from 'components/drawer';
import config from 'config';
import { browserHistory } from 'react-router';
import mobx from 'mobx';
import { observer } from 'mobx-react';
import menu from './menu';

export default context => {
  const { tr, theme } = context;
  const Drawer = drawer(context);
  const Menu = menu(context);

  const store = mobx.observable({
    open: false,
    toggle(){
      this.open = !this.open;
    },
    navChange: mobx.action(function(menuItem) {
      browserHistory.push(menuItem.route);
      this.open = false;
    }),
  });

  const ButtonLeftView = glamorous('button')({
    margin: 10,
    background: 'transparent',
    border: 0,
  });

  function BurgerIcon() {
    return (
      <svg id="burger-icon" version="1.1" viewBox="0 0 32 32" width="40px" height="50px">
        <path
          fill={theme.palette.textPrimaryOnPrimary}
          d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2  s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2  S29.104,22,28,22z"
        />
      </svg>
    );
  }

  function IconLeft({onLeftIconButtonTouchTap}) {
    return (
      <ButtonLeftView onClick={onLeftIconButtonTouchTap}>
        <BurgerIcon />
      </ButtonLeftView>
    );
  }

  const TitleView = glamorous('div')({
    fontSize: 34,
    fontWeight: 'bold',
    margin: 10,
  });

  const AppBarView = glamorous('div')({
    display: 'flex',
    justifyContent: 'flex-start',
    alignItems: 'center',
  }, (props, theme) => ({
    backgroundColor: theme.palette.primary,
    color: theme.palette.textPrimaryOnPrimary
  }));

  function AppBar({ onLeftIconButtonTouchTap }) {
    return (
      <AppBarView>
        <IconLeft onLeftIconButtonTouchTap={onLeftIconButtonTouchTap} />
        <TitleView>{tr.t(config.title)}</TitleView>
      </AppBarView>
    );
  }
  function NavBar({ authenticated }) {
    return (

        <AppBar
          onLeftIconButtonTouchTap={() => {
            store.toggle();
          }}
        />
        <Drawer open={store.open} store={store}>
          <Menu authenticated={authenticated} navChange={item => store.navChange(item)} />
        </Drawer>

    );
  }

  return observer(NavBar);
};
<commit_msg>can click on navbar title to route to home<commit_after>import React from "react";
import glamorous from "glamorous";
import drawer from "components/drawer";
import config from "config";
import { browserHistory } from "react-router";
import mobx from "mobx";
import { observer } from "mobx-react";
import menu from "./menu";
import button from "mdlean/lib/button";

export default context => {
  const { tr, theme } = context;
  //const palette = {theme};
  const Drawer = drawer(context);
  const Menu = menu(context);
  const TitleButton = button(context);
  const store = mobx.observable({
    open: false,
    toggle() {
      this.open = !this.open;
    },
    navChange: mobx.action(function(menuItem) {
      browserHistory.push(menuItem.route);
      this.open = false;
    })
  });

  const ButtonLeftView = glamorous("button")({
    margin: 10,
    background: "transparent",
    border: 0
  });

  function BurgerIcon() {
    return (
      <svg
        id="burger-icon"
        version="1.1"
        viewBox="0 0 32 32"
        width="40px"
        height="50px"
      >
        <path
          fill={theme.palette.textPrimaryOnPrimary}
          d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2  s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2  S29.104,22,28,22z"
        />
      </svg>
    );
  }

  function IconLeft({ onDrawerClick }) {
    return (
      <ButtonLeftView onClick={onDrawerClick}>
        <BurgerIcon />
      </ButtonLeftView>
    );
  }

  const TitleView = glamorous(TitleButton)({
    fontSize: 34,
    fontWeight: "bold",
    margin: 10,
    color: theme.palette.textPrimaryOnPrimary
  });

  const AppBarView = glamorous("div")(
    (props, theme) => ({
      display: "flex",
      justifyContent: "flex-start",
      alignItems: "center",
      backgroundColor: theme.palette.primary,
      color: theme.palette.textPrimaryOnPrimary
    })
  );

  function AppBar({ onDrawerClick, onTitleClick }) {
    return (
      <AppBarView>
        <IconLeft onDrawerClick={onDrawerClick} />
        <TitleView onClick={onTitleClick}>{tr.t(config.title)}</TitleView>
      </AppBarView>
    );
  }
  function NavBar({ authenticated }) {
    return (

        <AppBar
          onDrawerClick={() => {
            store.toggle();
          }}
          onTitleClick={() => {
            store.navChange({ route: "/" });
          }}
        />
        <Drawer open={store.open} store={store}>
          <Menu
            authenticated={authenticated}
            navChange={item => store.navChange(item)}
          />
        </Drawer>

    );
  }

  return observer(NavBar);
};
<|endoftext|>


<commit_before>import React, {PureComponent} from 'react';

class FeatureViewer extends PureComponent {
  constructor(props, context) {
    super(props, context);

    this.bindedOnValueChange = this.onValueChange.bind(this);
  }

  editableFields() {
    return ['opening', 'buildstart', 'closure', 'name']
  }

  visibleFields() {
    return this.editableFields().concat(['length']);
  }

  onValueChange(e) {
    const key = e.target.attributes.name.value;
    let value = e.target.innerText;

    const oldValue = this.props.feature.properties[key];
    if (typeof oldValue != 'string') value = parseInt(value);

    if (value == oldValue) return;

    if (this.props.onFeatureChange) this.props.onFeatureChange(this.props.feature, key, value);
  }

  render() {
    const properties = this.props.feature ? this.props.feature.properties : null;
    const content = this.props.feature ?

{`${properties.klass} Id: ${properties.id}`}

              { Object.entries(properties).map((entry) => {
                const key = entry[0];
                const value = entry[1];
                if (!this.visibleFields().includes(key)) return;

                return (
                  <tr key={`${properties.id}_${key}`} className="c-table__row">
                    {key}
                    {value}

                )
              }) }

 : 'Ningún elemento seleccionado';

    return (

Elemento seleccionado

        { content }

      )
  }
}

export default FeatureViewer
<commit_msg>Makes that changes are triggered once Enter is pressed<commit_after>import React, {PureComponent} from 'react';

class FeatureViewer extends PureComponent {
  constructor(props, context) {
    super(props, context);

    this.bindedOnValueChange = this.onValueChange.bind(this);
  }

  editableFields() {
    return ['opening', 'buildstart', 'closure', 'name']
  }

  visibleFields() {
    return this.editableFields().concat(['length']);
  }

  onValueChange(e) {
    if (e.key != 'Enter') return;

    const key = e.target.attributes.name.value;
    let value = e.target.innerText;

    const oldValue = this.props.feature.properties[key];
    if (typeof oldValue != 'string') value = parseInt(value);

    if (value == oldValue) return;

    if (this.props.onFeatureChange) this.props.onFeatureChange(this.props.feature, key, value);
  }

  render() {
    const properties = this.props.feature ? this.props.feature.properties : null;
    const content = this.props.feature ?

{`${properties.klass} Id: ${properties.id}`}

              { Object.entries(properties).map((entry) => {
                const key = entry[0];
                const value = entry[1];
                if (!this.visibleFields().includes(key)) return;

                return (
                  <tr key={`${properties.id}_${key}`} className="c-table__row">
                    {key}
                    {value}

                )
              }) }

 : 'Ningún elemento seleccionado';

    return (

Elemento seleccionado

        { content }

      )
  }
}

export default FeatureViewer
<|endoftext|>


<commit_before>var uglify = require("uglify-js"),
    fs = require("fs"),
    p = require("path"),
    files = [
        "lzma_worker.js",
        "lzma.js",
    ];

function filesize(path, cb)
{
    return fs.statSync(path).size;
}

function sort_obj(obj)
{
    return Object.keys(obj).sort(function sorter(a, b)
    {
        return obj[b] - obj[a];
    });
}

function calculate_minify_value(params)
{
    Object.keys(params).forEach(function oneach(param)
    {
        params[param] = param.length * params[param];
    });
}

function base54_64(arr)
{
    var chars = "_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
        i,
        len = arr.length,
        new_names = [],
        str,
        pos,
        base;

    for (i = 0; i < len; i += 1) {
        pos = i;
        str = "";
        base = 54;
        do {
            ///NOTE: This is not perfect. It skips the first char (_) on the last place when more than one letter.
            str += chars[pos % base];
            pos = Math.floor(pos / base);
            /// After the first character, we can use numbers.
            base = 64;
        } while (pos > 0);
        new_names[i] = str;
    }

    return new_names;
}

function minify_parameters(code)
{
    var params = {},
        ignore = ["LZMA_WORKER", "callback_num", "on_progress", "on_finish"],
        sorted_params,
        new_names,
        prop_regex = /\.[_$a-z]*[_$][_$a-z0-9]*/ig;

    /// We want to replace parameters that have an underscore or a dollar sign.
    code.replace(prop_regex, function calc(param)
    {
        param = param.substr(1);
        if (ignore.indexOf(param) === -1) {
            if (!params[param]) {
                params[param] = 1;
            } else {
                params[param] += 1;
            }
        }
    });

    //console.log(params);
    //console.log(Object.keys(params).length);
    calculate_minify_value(params);
    sorted_params = sort_obj(params);

    //console.log(params);
    //console.log(sorted_params);
    /*
    sorted_params.forEach(function (param)
    {
        console.log(param, params[param])
    });
    */
    new_names = base54_64(sorted_params);
    /*
    sorted_params.forEach(function (param, i)
    {
        console.log(param, params[param], new_names[i])
    });
    */

    code = code.replace(prop_regex, function calc(param)
    {
        var index,
            partial_param = param.substr(1);
        index = sorted_params.indexOf(partial_param);
        //console.log(param, index);
        if (index > -1) {
            return "." + new_names[index];
        }
        return param;
    });

    return code;
}

files.forEach(function oneach(file)
{
    var full_path = p.join(__dirname, "src", file),
        min_path = p.join(__dirname, "src", p.basename(file, p.extname(file)) + "-min" + p.extname(file)),
        result,
        orig_size = filesize(full_path),
        min_size;

    console.log(file);

    result = uglify.minify(full_path, {
        mangle: true,
        compress: {
            sequences: true,
            dead_code: true,
            conditionals: true,
            booleans: true,
            unused: true,
            loops: true,
            if_return: true,
            join_vars: true,
        }
    });

    result.code = minify_parameters(result.code);

    fs.writeFileSync(min_path, result.code);

    min_size = filesize(min_path);

    console.log("Original size: " + orig_size + " bytes");
    console.log("Minified size: " + min_size + " bytes");
    console.log("Compression:   " + (orig_size / min_size).toFixed(2) + " x smaller");
    console.log("");
});
<commit_msg>Minify more props.<commit_after>var uglify = require("uglify-js"),
    fs = require("fs"),
    p = require("path"),
    files = [
        "lzma_worker.js",
        "lzma.js",
    ];

function filesize(path, cb)
{
    return fs.statSync(path).size;
}

function sort_obj(obj)
{
    return Object.keys(obj).sort(function sorter(a, b)
    {
        return obj[b] - obj[a];
    });
}

function calculate_minify_value(params)
{
    Object.keys(params).forEach(function oneach(param)
    {
        params[param] = param.length * params[param];
    });
}

function base54_64(arr)
{
    var chars = "_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
        i,
        len = arr.length,
        new_names = [],
        str,
        pos,
        base;

    for (i = 0; i < len; i += 1) {
        pos = i;
        str = "";
        base = 54;
        do {
            ///NOTE: This is not perfect. It skips the first char (_) on the last place when more than one letter.
            str += chars[pos % base];
            pos = Math.floor(pos / base);
            /// After the first character, we can use numbers.
            base = 64;
        } while (pos > 0);
        new_names[i] = str;
    }

    return new_names;
}

function minify_parameters(code)
{
    var params = {},
        ignore = ["LZMA_WORKER", "callback_num", "on_progress", "on_finish"],
        sorted_params,
        new_names,
        prop_regex = /\.([_$a-zA-Z]*[_$][_$a-zA-Z0-9]*|kFixHashSize|kNumHashDirectBytes|kMinMatchCheck|outBytesProcessed|decoder|encoder|nowPos64|outSize|alive|processedInSize|finished|inBytesProcessed|processedInSize|[Ss]tate|prevByte|Prev1IsChar|Prev2|BackPrev|PosPrev|Models|backRes|properties|processedOutSize|tempPrices|backRes|repLens|Price|Backs[0123]|NumBitLevels|Range|Stream|explicitLength|count|pos|buf|chunker|rep[0-3s]|Code|Low|data|mode|output)/g;

    /// We want to replace parameters that have an underscore or a dollar sign.
    code.replace(prop_regex, function calc(param)
    {
        param = param.substr(1);
        if (ignore.indexOf(param) === -1) {
            if (!params[param]) {
                params[param] = 1;
            } else {
                params[param] += 1;
            }
        }
    });

    //console.log(params);
    //console.log(Object.keys(params).length);
    calculate_minify_value(params);
    sorted_params = sort_obj(params);

    //console.log(params);
    //console.log(sorted_params);
    /*
    sorted_params.forEach(function (param)
    {
        console.log(param, params[param])
    });
    */
    new_names = base54_64(sorted_params);
    /*
    sorted_params.forEach(function (param, i)
    {
        console.log(param, params[param], new_names[i])
    });
    */

    code = code.replace(prop_regex, function calc(param)
    {
        var index,
            partial_param = param.substr(1);
        index = sorted_params.indexOf(partial_param);
        //console.log(param, index);
        if (index > -1) {
            return "." + new_names[index];
        }
        return param;
    });

    return code;
}

files.forEach(function oneach(file)
{
    var full_path = p.join(__dirname, "src", file),
        min_path = p.join(__dirname, "src", p.basename(file, p.extname(file)) + "-min" + p.extname(file)),
        result,
        orig_size = filesize(full_path),
        min_size;

    console.log(file);

    result = uglify.minify(full_path, {
        mangle: true,
        compress: {
            sequences: true,
            dead_code: true,
            conditionals: true,
            booleans: true,
            unused: true,
            loops: true,
            if_return: true,
            join_vars: true,
        }
    });

    result.code = minify_parameters(result.code);

    fs.writeFileSync(min_path, result.code);

    min_size = filesize(min_path);

    console.log("Original size: " + orig_size + " bytes");
    console.log("Minified size: " + min_size + " bytes");
    console.log("Compression:   " + (orig_size / min_size).toFixed(2) + " x smaller");
    console.log("");
});
<|endoftext|>


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

	function tmpl() {
		return Template.instance();
	}

	function getExercise(forceRefresh = false) {
		return tmpl().isResult.get() && !forceRefresh ? Progressor.results.findOne().exercise : Progressor.exercises.findOne();
	}

	function getResult() {
		if (tmpl().isResult.get())
			return Progressor.results.findOne();
	}

	function getExecutionResults() {
		if (tmpl().isResult.get() || (Progressor.results.find().count() && Progressor.exercises.findOne().lastEdited.getTime() === Progressor.results.findOne().exercise.lastEdited.getTime()))
			return Progressor.results.findOne().results;
		else
			return tmpl().executionResults.get();
	}

	Template.programmingSolve.onCreated(function () {
		this.isResult = new ReactiveVar(false);
		this.executionStatus = new ReactiveVar(0x0);
		this.executionResults = new ReactiveVar([]);
		this.blacklist = new ReactiveVar(null);
		this.blacklistMatches = new ReactiveVar([]);
		this.showSolution = new ReactiveVar(false);
		Session.set('fragment', null);
		Session.set('solution', null);
	});

	Template.programmingSolve.onRendered(function () {
		this.autorun(() => {
			const result = Progressor.results.findOne(), exercise = Tracker.nonreactive(getExercise);
			if (result)
				Session.set('fragment', result.fragment);
			else if (exercise && exercise.fragment)
				Session.set('fragment', exercise.fragment);
			else
				Meteor.call('getFragment', getExercise().programmingLanguage, { _id: getExercise()._id }, Progressor.handleError((e, r) => Session.set('fragment', !e ? r : null)));
		});
	});

	Template.programmingSolve.helpers(
		{
			safeExercise(exerciseOrResult) {
				tmpl().isResult.set(!!exerciseOrResult.exercise_id);
				return exerciseOrResult.exercise_id ? exerciseOrResult.exercise : exerciseOrResult;
			},
			isResult: () => tmpl().isResult.get(),
			exerciseSearchData: () => ({ _id: getExercise().programmingLanguage }),
			exerciseSolveData: () => ({ _id: getResult() ? getResult().exercise_id : getExercise()._id }),
			changedAfterSolved: () => getExercise(true) && getResult() && getExercise(true).lastEdited > getResult().solved,
			resultSolved: () => getResult().solved,
			codeMirrorThemes() {
				const user = Meteor.user(), userTheme = user && user.profile && user.profile.codeMirrorTheme ? user.profile.codeMirrorTheme : Progressor.getCodeMirrorDefaultTheme();
				return _.map(Progressor.getCodeMirrorThemes(), theme => ({ _id: theme, isActive: theme === userTheme }));
			},
			codeMirrorOptions(isSolution = false) {
				const programmingLanguage = Progressor.getProgrammingLanguage(getExercise().programmingLanguage);
				return _.extend({}, Progressor.getCodeMirrorConfiguration(), { //https://codemirror.net/doc/manual.html
					autofocus: true,
					readOnly: tmpl().isResult.get() || isSolution ? 'nocursor' : false,
					mode: programmingLanguage ? programmingLanguage.codeMirror : 'text/plain'
				});
			},
			executionDisabled: () => tmpl().executionStatus.get() !== 0x0,
			blackListMessage: () => tmpl().blacklistMatches.get().length ? i18n('exercise.blacklistMatchMessage', tmpl().blacklistMatches.get().join(', ')) : null,
			testCaseSignature: c => Progressor.getTestCaseSignature(getExercise(), c),
			testCaseExpectedOutput: c => Progressor.getExpectedTestCaseOutput(getExercise(), c),
			testCasesEvaluated: () => Progressor.isExerciseEvaluated(getExercise(), getExecutionResults()),
			testCaseSuccess: c => Progressor.isTestCaseSuccess(getExercise(), c, getExecutionResults()),
			testCaseActualOutput: c => Progressor.getActualTestCaseOutput(getExercise(), c, getExecutionResults()),
			executionFatal: () => Progressor.isExerciseFatal(getExercise(), getExecutionResults()),
			showSolution: () => getExercise().solutionVisible && tmpl().showSolution.get()
		});

	Template.programmingSolve.events(
		{
			'click #button-execute'(event, template) {
				template.showSolution.set(false);
				const exercise = getExercise();
				setTimeout(() => template.$('#table-testcases').css('opacity', 1 / 3), 1);
				template.executionStatus.set(template.executionStatus.get() | 0x1);
				Meteor.call('execute', exercise.programmingLanguage, { _id: exercise._id }, Session.get('fragment'), Progressor.handleError((error, result) => {
					template.executionResults.set(!error ? result : []);
					template.$('#table-testcases').css('opacity', 1);
					template.executionStatus.set(template.executionStatus.get() & ~0x1);
				}));
			},
			'click #button-solution'(event, template)  {
				Session.set('solution', getExercise().solution);
				template.showSolution.set(true);
			},
			'click #button-close'(event, template)  {
				template.showSolution.set(false);
			},
			'keyup .CodeMirror': _.throttle(function (event, template) {
				if (!template.blacklist.get()) {
					template.blacklist.set([]);
					Meteor.call('getBlacklist', getExercise().programmingLanguage, Progressor.handleError((e, r) => template.blacklist.set(!e ? r : null)));
				} else {
					const fragment = Session.get('fragment');
					template.blacklistMatches.set(_.filter(template.blacklist.get(), blk => fragment.indexOf(blk) >= 0));
					template.executionStatus.set(template.blacklistMatches.get().length ? template.executionStatus.get() | 0x2 : template.executionStatus.get() & ~0x2);
				}
			}, 500),
			'change #select-codemirror-themes'(event) {
				const theme = $(event.currentTarget).val();
				tmpl().$('.CodeMirror')[0].CodeMirror.setOption('theme', theme);
				Meteor.users.update(Meteor.userId(), { $set: { 'profile.codeMirrorTheme': theme } });
			}
		});

})();
<commit_msg>bug fix codemirror<commit_after>(function () {
	'use strict';

	function tmpl() {
		return Template.instance();
	}

	function getExercise(forceRefresh = false) {
		return tmpl().isResult.get() && !forceRefresh ? Progressor.results.findOne().exercise : Progressor.exercises.findOne();
	}

	function getResult() {
		if (tmpl().isResult.get())
			return Progressor.results.findOne();
	}

	function getExecutionResults() {
		if (tmpl().isResult.get() || (Progressor.results.find().count() && Progressor.exercises.findOne().lastEdited.getTime() === Progressor.results.findOne().exercise.lastEdited.getTime()))
			return Progressor.results.findOne().results;
		else
			return tmpl().executionResults.get();
	}

	Template.programmingSolve.onCreated(function () {
		this.isResult = new ReactiveVar(false);
		this.executionStatus = new ReactiveVar(0x0);
		this.executionResults = new ReactiveVar([]);
		this.blacklist = new ReactiveVar(null);
		this.blacklistMatches = new ReactiveVar([]);
		this.showSolution = new ReactiveVar(false);
		Session.set('fragment', null);
		Session.set('solution', null);
	});

	Template.programmingSolve.onRendered(function () {
		this.autorun(() => {
			const result = Progressor.results.findOne(), exercise = Tracker.nonreactive(getExercise);
			if (result)
				Session.set('fragment', result.fragment);
			else if (exercise && exercise.fragment)
				Session.set('fragment', exercise.fragment);
			else
				Meteor.call('getFragment', getExercise().programmingLanguage, { _id: getExercise()._id }, Progressor.handleError((e, r) => Session.set('fragment', !e ? r : null)));
		});
	});

	Template.programmingSolve.helpers(
		{
			safeExercise(exerciseOrResult) {
				tmpl().isResult.set(!!exerciseOrResult.exercise_id);
				return exerciseOrResult.exercise_id ? exerciseOrResult.exercise : exerciseOrResult;
			},
			isResult: () => tmpl().isResult.get(),
			exerciseSearchData: () => ({ _id: getExercise().programmingLanguage }),
			exerciseSolveData: () => ({ _id: getResult() ? getResult().exercise_id : getExercise()._id }),
			changedAfterSolved: () => getExercise(true) && getResult() && getExercise(true).lastEdited > getResult().solved,
			resultSolved: () => getResult().solved,
			codeMirrorThemes() {
				const user = Meteor.user(), userTheme = user && user.profile && user.profile.codeMirrorTheme ? user.profile.codeMirrorTheme : Progressor.getCodeMirrorDefaultTheme();
				return _.map(Progressor.getCodeMirrorThemes(), theme => ({ _id: theme, isActive: theme === userTheme }));
			},
			codeMirrorOptions(isSolution = false) {
				const programmingLanguage = Progressor.getProgrammingLanguage(getExercise().programmingLanguage);
				return _.extend({}, Progressor.getCodeMirrorConfiguration(), { //https://codemirror.net/doc/manual.html
					autofocus: true,
					readOnly: tmpl().isResult.get() || isSolution ? 'nocursor' : false,
					mode: programmingLanguage ? programmingLanguage.codeMirror : 'text/plain'
				});
			},
			executionDisabled: () => tmpl().executionStatus.get() !== 0x0,
			blackListMessage: () => tmpl().blacklistMatches.get().length ? i18n('exercise.blacklistMatchMessage', tmpl().blacklistMatches.get().join(', ')) : null,
			testCaseSignature: c => Progressor.getTestCaseSignature(getExercise(), c),
			testCaseExpectedOutput: c => Progressor.getExpectedTestCaseOutput(getExercise(), c),
			testCasesEvaluated: () => Progressor.isExerciseEvaluated(getExercise(), getExecutionResults()),
			testCaseSuccess: c => Progressor.isTestCaseSuccess(getExercise(), c, getExecutionResults()),
			testCaseActualOutput: c => Progressor.getActualTestCaseOutput(getExercise(), c, getExecutionResults()),
			executionFatal: () => Progressor.isExerciseFatal(getExercise(), getExecutionResults()),
			showSolution: () => getExercise().solutionVisible && tmpl().showSolution.get()
		});

	Template.programmingSolve.events(
		{
			'click #button-execute'(event, template) {
				template.showSolution.set(false);
				const exercise = getExercise();
				setTimeout(() => template.$('#table-testcases').css('opacity', 1 / 3), 1);
				template.executionStatus.set(template.executionStatus.get() | 0x1);
				Meteor.call('execute', exercise.programmingLanguage, { _id: exercise._id }, Session.get('fragment'), Progressor.handleError((error, result) => {
					template.executionResults.set(!error ? result : []);
					template.$('#table-testcases').css('opacity', 1);
					template.executionStatus.set(template.executionStatus.get() & ~0x1);
				}));
			},
			'click #button-solution'(event, template)  {
				Session.set('solution', getExercise().solution);
				template.showSolution.set(true);
			},
			'click #button-close'(event, template)  {
				template.showSolution.set(false);
			},
			'keyup .CodeMirror': _.throttle(function (event, template) {
				if (!template.blacklist.get()) {
					template.blacklist.set([]);
					Meteor.call('getBlacklist', getExercise().programmingLanguage, Progressor.handleError((e, r) => template.blacklist.set(!e ? r : null)));
				} else {
					const fragment = Session.get('fragment');
					template.blacklistMatches.set(_.filter(template.blacklist.get(), blk => fragment.indexOf(blk) >= 0));
					template.executionStatus.set(template.blacklistMatches.get().length ? template.executionStatus.get() | 0x2 : template.executionStatus.get() & ~0x2);
				}
			}, 500),
			'change #select-codemirror-themes'(event, template) {
				const theme = $(event.currentTarget).val();
				template.$('.CodeMirror')[0].CodeMirror.setOption('theme', theme);
				Meteor.users.update(Meteor.userId(), { $set: { 'profile.codeMirrorTheme': theme } });
			}
		});

})();
<|endoftext|>