<commit_before>var App = angular.module('App');

App.directive('choropleth', ['$http', function($http) {

  // Clone the loader
  var loader = $("#loader").clone();
  var hasLoader = loader.length > 0;

  // If loader is not present, do not hide the choropleth
  if (hasLoader) {
      var choropleth = $("#choropleth");
          choropleth.append(loader.show());
  }

  // Width and height of the SVG
  var width = 650,
      height = 600;

  // D3 Array Object where the data is stored as key : value
  var rateById = d3.map();

  // Projection for Map
  var projection = d3.geo.transverseMercator()
      .rotate([100 + 20 / 60, -29 - 40 / 60])
      .scale(3000)
      .translate([305, 380]);

  // Declare the path object
  var path = d3.geo.path()
        .projection(projection);

  return {
    restrict: "E",
    scope: true,
    link: function (scope, element, attrs) {
      var scale, svg, $svg, g;

      // D3 SVG Element added to the DOM
      svg = d3.select(element[0]).append("svg")
        .attr("width", width)
        .attr("height", height);

      // JQuery Object for manipulating the SVG's behavior
      $svg = $('svg');

      // Hide the loader only if the loader object exists
      hasLoader ? $svg.hide() : false;

      // Watch for change in the Entity selection
      scope.$watch('choropleth.selection.cartogram_entity', function(newVals, oldVals) {
          if (newVals !== oldVals) {
              var records = scope.dataset.records;
              records.forEach(function(record) {
                  if (newVals === record.cartogram_entity) {
                      scope.choropleth.selection = JSON.parse(JSON.stringify(record));
                  }
              });
          }
      });

      // Watch for changes in the Choropleth members items and in the selected Palette
      scope.$watchCollection('[choropleth.scale, choropleth.data_classes, palette.class_name]', function(newVals, oldVals) {
        setScale(newVals[0]);
        update(scope.dataset.records);
      });

      function quantizeLegend() {
        var w = 10, h = 250;
        var dy = h / scale.range().length;

        svg.selectAll('g#legend').remove();
        var key = d3.select("svg")
            .append("g")
            .attr('id', 'legend')
            .attr("width", w)
            .attr("height", h);

        var legend = key.selectAll('g#legend')
            .data(scale.range())
            .enter()
            .append('g').attr('class', 'quantity');

        legend.append('rect')
            .attr("y", function(d, i) {return (h - dy) - i * dy;})
            .attr("width", 10)
            .attr('height', dy)
            .attr("fill", function(d) {return d; })
            .attr('transform', 'translate(3,345)');

        legend.append('text')
            .attr("x", w + 5 )
            .attr("y", function(d, i) {return (h - dy) - i * dy;} )
            .attr('dy', dy/2 + 5)
            .style("font-size", "10px")
            .attr('transform', 'translate(3,345)')
            .text(function(d,i) {
                var extent = scale.invertExtent(d);
                var format = d3.format("0.2f");
                return format(+extent[0])+ " - " + format(+extent[1]);
            });
      }

      function logarithmicLegend() {
        var w = 10, h = 250;

        d3.select('g#legend').remove();

        var key = d3.select("svg")
            .append("g")
            .attr('id', 'legend')
            .attr("width", w)
            .attr("height", h);

        var scaleId = scope.choropleth.scale;
        var y = d3.scale.log()
          .range([h, 0])
          .domain(scale.domain());

        key.append("rect")
          .attr("width", w)
          .attr("height", h)
          .style("fill", "url(#gradient)")
          .attr("transform", "translate(3,345)");

        var legend = key.append("defs")
            .append("svg:linearGradient")
            .attr("id", "gradient")
            .attr("x1", "100%")
            .attr("y1", "0%")
            .attr("x2", "100%")
            .attr("y2", "100%")
            .attr("spreadMethod", "pad");

        legend.append("stop")
          .attr("offset", "0%")
          .attr("stop-color", scale.range()[scale.range().length -1])
          .attr("stop-opacity", 1);

        legend.append("stop")
        .attr("offset", "100%")
          .attr("stop-color", scale.range()[0])
          .attr("stop-opacity", 1);

        var yAxis = d3.svg.axis()
         .scale(y)
         .ticks(10, ",4s")
         .tickSize(5, 0)
         .orient("right");

        key.append("g")
          .attr("class", "y axis")
          .attr("transform", "translate(15, 345)")
          .call(yAxis)
        .selectAll('text')
          .attr('y', 0)
          .attr('x', 9)
          .attr('dy', '.35em')
          .style('text-anchor', 'start');
      }

      function legend(scaleId) {
        if (attrs.hideLegend) {
          return false;
        }

        if (0 === scaleId) {
          quantizeLegend();
        } else if (1 === scaleId) {
          logarithmicLegend();
        }
        return true;
      }

      // Determines correct scale for the current ScaleID
      function setScale(scaleId) {
        var range = scope.choropleth.data_classes;

        switch(scaleId) {
          // Quantize
          case 0:
            if (scope.palette && scope.choropleth.data_classes) {
              var min = scope.dataset.domain.min,
                  max = scope.dataset.domain.max;

              scale = d3.scale.quantize()
                .domain([min, max])
                .range(d3.range(range).map(function(i) {
                  return colorbrewer[scope.palette.class_name][range][i];
                }));

              legend(scaleId);
            }
            break;

          // Non-Quantize
          case 1:
            if (scope.palette && range) {
              var palette = colorbrewer[scope.palette.class_name][range];

              var color1 = palette[0];
                  color2 = palette[palette.length -1];

              var min = scope.dataset.domain.non_zero_min,
                  max = scope.dataset.domain.non_zero_max;

              scale = d3.scale.log()
                .domain([min, max])
                .range([color1, color2])
                .clamp(true);

              legend(scaleId);
            }
            break;

          // No scale
          default:
            scale = function() {
              return "none";
            };
        }
        return scale;
      }

      //Updates the SVG/Path elements accordingly
      function update(data) {
        svg.select("#entities").selectAll("path")
          .attr('fill', function(d) {
            return fill(d);
          });
      }

      // Helper function to determine the fill color base on the dataset record value
      function fill(d) {
         value = rateById.get(d.properties.fips);
         if (value === null) {
           // Fill with white instead of none.
           // Helps with hover events where the value is null
           return '#FFF';
         }
         return scale(rateById.get(d.properties.fips));
      }

      // Performs the initial drawing operation for the Choropleth
      function ready(texas) {
        svg.append("g")
          .attr("id", "entities")
        .selectAll("path")
         .data(topojson.feature(texas, texas.objects.counties).features)
        .enter().append("path")
          .attr("id", function(d) { return d.properties.fips; })
          .attr("fill", function(d) { return fill(d); })
          .style('cursor', 'pointer')
          .on("mouseenter", function(d) {
            scope.choropleth.selection.cartogram_entity = d.properties.fips;
            scope.$apply();
          })
          .on("mouseleave", function(d) {
            scope.choropleth.selection = {};
            scope.$apply();
          })
          .attr("d", path);

        // Show the loader if it exists
        if (hasLoader) {
            choropleth.find('#loader').fadeOut('slow', function() {
              $(this).remove();
              $('svg').fadeIn('slow');
            });
        }
      }

      // Wrapper function for ready()
      // Grabs the TopoJson file and draws the Choropleth if the
      // call is successful
      function render(data) {
        data.forEach(function(d) {rateById.set(d.cartogram_entity, d.value); });
        $http.get('/static/JSON/texas.json')
        .success(function(data, status) {
            ready(data);
        });
      }

      // Begin execution
      render(scope.dataset.records);
    }
  };
}]);

// Live edit and generic rendering directive for Markdown
App.directive('markdown', ['$window', function($window) {
  var converter = new $window.Showdown.converter();
  return {
    restrict: 'E',
    scope: {
      description: '='
    },
    link: function(scope, element, attrs) {
      function markdownify(html) {
        var htmlText = converter.makeHtml(html);
        element.html(htmlText);
      }

      // For static viewing
      if (!scope.description) {
        markdownify(element.text());
      }

      // For live editing
      scope.$watch('description', function(newVal, oldVal) {
        if (newVal) {
          markdownify(newVal);
        }
      });
    }
  };
}]);

App.controller('AbstractController', ['$scope', '$window', '$http', 'Palettes', function AbstractController ($scope, $window, $http, Palettes) {

  $scope.tab = 1;

  $scope._success = function() {
    $window.location = "/choropleths/";
  };

  $scope.schemes = [
    {name: "Sequential", id: 1},
    {name: "Diverging", id: 2},
    {name: "Qualitivative", id: 3}
  ];

  // Update the number data_classes available based on the current palette selection
  $scope.$watch('palette', function (palette) {
    var keys, min, max;

    keys = [];
    if (palette) {
      for (var index in colorbrewer[palette.class_name]) keys.push(index);
      min = Math.min.apply(null, keys);
      max = Math.max.apply(null, keys);

      if (max < $scope.choropleth.data_classes) {
        $scope.choropleth.data_classes = max;
      }
    }

    $scope.range = {
      options: keys,
      min: min,
      max: max
    };
  });

  $scope.getSchemePalettes = function(id) {
    Palettes.query({id: id}, function(data) {
      $scope.palettes = data;
      $scope.findPalette($scope.choropleth.palette);
    });
  };

  $scope.findPalette = function(id) {
    if (0 < $scope.palettes.length) {

      // Set defaults if the choropleth doesn't already have values
      $scope.palette = $scope.palettes[0];
      $scope.choropleth.palette = $scope.palette.id;

      // Set palette if the palette id is in the palettes array
      $scope.palettes.forEach(function (value, index, array) {
        if (value.id == id) {
          $scope.palette = value;
          $scope.choropleth.palette = value.id;
        }
      });
    }
  };
}]);

App.controller('ViewController', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function ViewController ($scope, $controller, Choropleth, Dataset, Palettes ) {
  $controller('AbstractController', {$scope: $scope});

  $scope.init = function(id) {
    Choropleth.get({id: id}, function (data) {
      $scope.choropleth = data;
      $scope.choropleth.selection = {};

      Dataset.get({id: data.dataset}, function(data) {
        $scope.dataset = data;
        $scope.scales = data.scale_options;
        $scope.hasData = true;
      });

      Palettes.query({id: data.scheme}, function (data) {
        $scope.palettes = data;
        $scope.findPalette($scope.choropleth.palette);
      });
    });
  };
}]);

App.controller('EditController', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function EditController ($scope, $controller, Choropleth, Dataset, Palettes) {
  $controller('ViewController', {$scope: $scope});

  $scope.submit = function() {
    $('#in-progress').modal();
    $scope.choropleth.$update($scope._success);
  };

  $scope.delete = function() {
    $scope.choropleth.$delete($scope._success);
  };
}]);

App.controller('MappCtrl', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function MappCtrl ($scope, $controller, Choropleth, Dataset, Palettes) {
  $controller('AbstractController', {$scope: $scope});

  $scope.init = function(id) {
    $scope.choropleth = new Choropleth();

    Dataset.get({id: id}, function(data) {
      $scope.dataset = data;
      $scope.choropleth.dataset = data.id;
      $scope.choropleth.name = data.name;
      $scope.choropleth.data_classes = 3;
      $scope.choropleth.selection = {};
      $scope.scales = data.scale_options;
      $scope.hasData = true;
    });

    Palettes.query({id: 1}, function(data) {
      $scope.palettes = data;
    });
  };

  $scope.submit = function() {
    $('#in-progress').modal();
    $scope.choropleth.$save($scope._success);
  };
}]);

<commit_msg>Choropleths are published by default. Changed the create controller name.<commit_after>var App = angular.module('App');

App.directive('choropleth', ['$http', function($http) {

  // Clone the loader
  var loader = $("#loader").clone();
  var hasLoader = loader.length > 0;

  // If loader is not present, do not hide the choropleth
  if (hasLoader) {
      var choropleth = $("#choropleth");
          choropleth.append(loader.show());
  }

  // Width and height of the SVG
  var width = 650,
      height = 600;

  // D3 Array Object where the data is stored as key : value
  var rateById = d3.map();

  // Projection for Map
  var projection = d3.geo.transverseMercator()
      .rotate([100 + 20 / 60, -29 - 40 / 60])
      .scale(3000)
      .translate([305, 380]);

  // Declare the path object
  var path = d3.geo.path()
        .projection(projection);

  return {
    restrict: "E",
    scope: true,
    link: function (scope, element, attrs) {
      var scale, svg, $svg, g;

      // D3 SVG Element added to the DOM
      svg = d3.select(element[0]).append("svg")
        .attr("width", width)
        .attr("height", height);

      // JQuery Object for manipulating the SVG's behavior
      $svg = $('svg');

      // Hide the loader only if the loader object exists
      hasLoader ? $svg.hide() : false;

      // Watch for change in the Entity selection
      scope.$watch('choropleth.selection.cartogram_entity', function(newVals, oldVals) {
          if (newVals !== oldVals) {
              var records = scope.dataset.records;
              records.forEach(function(record) {
                  if (newVals === record.cartogram_entity) {
                      scope.choropleth.selection = JSON.parse(JSON.stringify(record));
                  }
              });
          }
      });

      // Watch for changes in the Choropleth members items and in the selected Palette
      scope.$watchCollection('[choropleth.scale, choropleth.data_classes, palette.class_name]', function(newVals, oldVals) {
        setScale(newVals[0]);
        update(scope.dataset.records);
      });

      function quantizeLegend() {
        var w = 10, h = 250;
        var dy = h / scale.range().length;

        svg.selectAll('g#legend').remove();
        var key = d3.select("svg")
            .append("g")
            .attr('id', 'legend')
            .attr("width", w)
            .attr("height", h);

        var legend = key.selectAll('g#legend')
            .data(scale.range())
            .enter()
            .append('g').attr('class', 'quantity');

        legend.append('rect')
            .attr("y", function(d, i) {return (h - dy) - i * dy;})
            .attr("width", 10)
            .attr('height', dy)
            .attr("fill", function(d) {return d; })
            .attr('transform', 'translate(3,345)');

        legend.append('text')
            .attr("x", w + 5 )
            .attr("y", function(d, i) {return (h - dy) - i * dy;} )
            .attr('dy', dy/2 + 5)
            .style("font-size", "10px")
            .attr('transform', 'translate(3,345)')
            .text(function(d,i) {
                var extent = scale.invertExtent(d);
                var format = d3.format("0.2f");
                return format(+extent[0])+ " - " + format(+extent[1]);
            });
      }

      function logarithmicLegend() {
        var w = 10, h = 250;

        d3.select('g#legend').remove();

        var key = d3.select("svg")
            .append("g")
            .attr('id', 'legend')
            .attr("width", w)
            .attr("height", h);

        var scaleId = scope.choropleth.scale;
        var y = d3.scale.log()
          .range([h, 0])
          .domain(scale.domain());

        key.append("rect")
          .attr("width", w)
          .attr("height", h)
          .style("fill", "url(#gradient)")
          .attr("transform", "translate(3,345)");

        var legend = key.append("defs")
            .append("svg:linearGradient")
            .attr("id", "gradient")
            .attr("x1", "100%")
            .attr("y1", "0%")
            .attr("x2", "100%")
            .attr("y2", "100%")
            .attr("spreadMethod", "pad");

        legend.append("stop")
          .attr("offset", "0%")
          .attr("stop-color", scale.range()[scale.range().length -1])
          .attr("stop-opacity", 1);

        legend.append("stop")
        .attr("offset", "100%")
          .attr("stop-color", scale.range()[0])
          .attr("stop-opacity", 1);

        var yAxis = d3.svg.axis()
         .scale(y)
         .ticks(10, ",4s")
         .tickSize(5, 0)
         .orient("right");

        key.append("g")
          .attr("class", "y axis")
          .attr("transform", "translate(15, 345)")
          .call(yAxis)
        .selectAll('text')
          .attr('y', 0)
          .attr('x', 9)
          .attr('dy', '.35em')
          .style('text-anchor', 'start');
      }

      function legend(scaleId) {
        if (attrs.hideLegend) {
          return false;
        }

        if (0 === scaleId) {
          quantizeLegend();
        } else if (1 === scaleId) {
          logarithmicLegend();
        }
        return true;
      }

      // Determines correct scale for the current ScaleID
      function setScale(scaleId) {
        var range = scope.choropleth.data_classes;

        switch(scaleId) {
          // Quantize
          case 0:
            if (scope.palette && scope.choropleth.data_classes) {
              var min = scope.dataset.domain.min,
                  max = scope.dataset.domain.max;

              scale = d3.scale.quantize()
                .domain([min, max])
                .range(d3.range(range).map(function(i) {
                  return colorbrewer[scope.palette.class_name][range][i];
                }));

              legend(scaleId);
            }
            break;

          // Non-Quantize
          case 1:
            if (scope.palette && range) {
              var palette = colorbrewer[scope.palette.class_name][range];

              var color1 = palette[0];
                  color2 = palette[palette.length -1];

              var min = scope.dataset.domain.non_zero_min,
                  max = scope.dataset.domain.non_zero_max;

              scale = d3.scale.log()
                .domain([min, max])
                .range([color1, color2])
                .clamp(true);

              legend(scaleId);
            }
            break;

          // No scale
          default:
            scale = function() {
              return "none";
            };
        }
        return scale;
      }

      //Updates the SVG/Path elements accordingly
      function update(data) {
        svg.select("#entities").selectAll("path")
          .attr('fill', function(d) {
            return fill(d);
          });
      }

      // Helper function to determine the fill color base on the dataset record value
      function fill(d) {
         value = rateById.get(d.properties.fips);
         if (value === null) {
           // Fill with white instead of none.
           // Helps with hover events where the value is null
           return '#FFF';
         }
         return scale(rateById.get(d.properties.fips));
      }

      // Performs the initial drawing operation for the Choropleth
      function ready(texas) {
        svg.append("g")
          .attr("id", "entities")
        .selectAll("path")
         .data(topojson.feature(texas, texas.objects.counties).features)
        .enter().append("path")
          .attr("id", function(d) { return d.properties.fips; })
          .attr("fill", function(d) { return fill(d); })
          .style('cursor', 'pointer')
          .on("mouseenter", function(d) {
            scope.choropleth.selection.cartogram_entity = d.properties.fips;
            scope.$apply();
          })
          .on("mouseleave", function(d) {
            scope.choropleth.selection = {};
            scope.$apply();
          })
          .attr("d", path);

        // Show the loader if it exists
        if (hasLoader) {
            choropleth.find('#loader').fadeOut('slow', function() {
              $(this).remove();
              $('svg').fadeIn('slow');
            });
        }
      }

      // Wrapper function for ready()
      // Grabs the TopoJson file and draws the Choropleth if the
      // call is successful
      function render(data) {
        data.forEach(function(d) {rateById.set(d.cartogram_entity, d.value); });
        $http.get('/static/JSON/texas.json')
        .success(function(data, status) {
            ready(data);
        });
      }

      // Begin execution
      render(scope.dataset.records);
    }
  };
}]);

// Live edit and generic rendering directive for Markdown
App.directive('markdown', ['$window', function($window) {
  var converter = new $window.Showdown.converter();
  return {
    restrict: 'E',
    scope: {
      description: '='
    },
    link: function(scope, element, attrs) {
      function markdownify(html) {
        var htmlText = converter.makeHtml(html);
        element.html(htmlText);
      }

      // For static viewing
      if (!scope.description) {
        markdownify(element.text());
      }

      // For live editing
      scope.$watch('description', function(newVal, oldVal) {
        if (newVal) {
          markdownify(newVal);
        }
      });
    }
  };
}]);

App.controller('AbstractController', ['$scope', '$window', '$http', 'Palettes', function AbstractController ($scope, $window, $http, Palettes) {

  $scope.tab = 1;

  $scope._success = function() {
    $window.location = "/choropleths/";
  };

  $scope.schemes = [
    {name: "Sequential", id: 1},
    {name: "Diverging", id: 2},
    {name: "Qualitivative", id: 3}
  ];

  // Update the number data_classes available based on the current palette selection
  $scope.$watch('palette', function (palette) {
    var keys, min, max;

    keys = [];
    if (palette) {
      for (var index in colorbrewer[palette.class_name]) keys.push(index);
      min = Math.min.apply(null, keys);
      max = Math.max.apply(null, keys);

      if (max < $scope.choropleth.data_classes) {
        $scope.choropleth.data_classes = max;
      }
    }

    $scope.range = {
      options: keys,
      min: min,
      max: max
    };
  });

  $scope.getSchemePalettes = function(id) {
    Palettes.query({id: id}, function(data) {
      $scope.palettes = data;
      $scope.findPalette($scope.choropleth.palette);
    });
  };

  $scope.findPalette = function(id) {
    if (0 < $scope.palettes.length) {

      // Set defaults if the choropleth doesn't already have values
      $scope.palette = $scope.palettes[0];
      $scope.choropleth.palette = $scope.palette.id;

      // Set palette if the palette id is in the palettes array
      $scope.palettes.forEach(function (value, index, array) {
        if (value.id == id) {
          $scope.palette = value;
          $scope.choropleth.palette = value.id;
        }
      });
    }
  };
}]);

App.controller('ViewController', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function ViewController ($scope, $controller, Choropleth, Dataset, Palettes ) {
  $controller('AbstractController', {$scope: $scope});

  $scope.init = function(id) {
    Choropleth.get({id: id}, function (data) {
      $scope.choropleth = data;
      $scope.choropleth.selection = {};

      Dataset.get({id: data.dataset}, function(data) {
        $scope.dataset = data;
        $scope.scales = data.scale_options;
        $scope.hasData = true;
      });

      Palettes.query({id: data.scheme}, function (data) {
        $scope.palettes = data;
        $scope.findPalette($scope.choropleth.palette);
      });
    });
  };
}]);

App.controller('EditController', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function EditController ($scope, $controller, Choropleth, Dataset, Palettes) {
  $controller('ViewController', {$scope: $scope});

  $scope.submit = function() {
    $('#in-progress').modal();
    $scope.choropleth.$update($scope._success);
  };

  $scope.delete = function() {
    $scope.choropleth.$delete($scope._success);
  };
}]);

App.controller('CreateController', ['$scope', '$controller', 'Choropleth', 'Dataset', 'Palettes', function MappCtrl ($scope, $controller, Choropleth, Dataset, Palettes) {
  $controller('AbstractController', {$scope: $scope});

  $scope.init = function(id) {
    $scope.choropleth = new Choropleth();

    Dataset.get({id: id}, function(data) {
      $scope.dataset = data;
      $scope.choropleth.dataset = data.id;
      $scope.choropleth.name = data.name;
      $scope.choropleth.data_classes = 3;
      $scope.choropleth.published = 1;
      $scope.choropleth.selection = {};
      $scope.scales = data.scale_options;
      $scope.hasData = true;
    });

    Palettes.query({id: 1}, function(data) {
      $scope.palettes = data;
    });
  };

  $scope.submit = function() {
    $('#in-progress').modal();
    $scope.choropleth.$save($scope._success);
  };
}]);

<|endoftext|>


<commit_before>import { state } from './index.js';
import getToolForElement from './getToolForElement.js';

/**
 * Adds a tool to an enabled element.
 *
 * @export @public @method
 * @name addToolForElement
 * @param {HTMLElement} element The element to add the tool to.
 * @param {baseTool} tool The tool to add to the element.
 * @param {configuration} [configuration] Override the default tool configuration
 */
const addToolForElement = function (element, apiTool, configuration) {
  // Instantiating the tool here makes it harder to accidentally add
  // The same tool (by reference) for multiple elements (which would reassign the tool
  // To a new element).
  const tool = new apiTool(configuration);

  const toolAlreadyAddedToElement = getToolForElement(element, tool.name);

  if (toolAlreadyAddedToElement) {
    console.warn(`${tool.name} has already been added to the target element`);

    return;
  }

  tool.element = element;
  state.tools.push(tool);
};

/**
 * Adds a tool to each element.
 * @export @public @method
 * @name addTool
 * @param {baseTool} tool The tool to add to each element.
 */
const addTool = function (apiTool, configuration) {
  const tool = new apiTool(configuration);
  const toolAlreadyAddedGlobally = state.globalTools[tool.name] !== undefined;

  console.log(state.globalTools[tool.name]);

  if (toolAlreadyAddedGlobally) {
    console.warn(`${tool.name} has already been added globally`);

    return;
  }

  state.globalTools = state.globalTools[tool.name] = {
    tool: apiTool,
    configuration
  };

  state.enabledElements.forEach((element) => {
    addToolForElement(element, apiTool);
  });
};

export { addTool, addToolForElement };
<commit_msg>Fix typo<commit_after>import { state } from './index.js';
import getToolForElement from './getToolForElement.js';

/**
 * Adds a tool to an enabled element.
 *
 * @export @public @method
 * @name addToolForElement
 * @param {HTMLElement} element The element to add the tool to.
 * @param {baseTool} tool The tool to add to the element.
 * @param {configuration} [configuration] Override the default tool configuration
 */
const addToolForElement = function (element, apiTool, configuration) {
  // Instantiating the tool here makes it harder to accidentally add
  // The same tool (by reference) for multiple elements (which would reassign the tool
  // To a new element).
  const tool = new apiTool(configuration);

  const toolAlreadyAddedToElement = getToolForElement(element, tool.name);

  if (toolAlreadyAddedToElement) {
    console.warn(`${tool.name} has already been added to the target element`);

    return;
  }

  tool.element = element;
  state.tools.push(tool);
};

/**
 * Adds a tool to each element.
 * @export @public @method
 * @name addTool
 * @param {baseTool} tool The tool to add to each element.
 */
const addTool = function (apiTool, configuration) {
  const tool = new apiTool(configuration);
  const toolAlreadyAddedGlobally = state.globalTools[tool.name] !== undefined;

  if (toolAlreadyAddedGlobally) {
    console.warn(`${tool.name} has already been added globally`);

    return;
  }

  state.globalTools[tool.name] = {
    tool: apiTool,
    configuration
  };

  state.enabledElements.forEach((element) => {
    addToolForElement(element, apiTool);
  });
};

export { addTool, addToolForElement };
<|endoftext|>


<commit_before>import * as v from './variables'

const styles = {
  '@global': {
    '*': {
      boxSizing: 'border-box'
    },
    body: {
      margin: 0,
      padding: 0,
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      lineHeight: `${v.lnht * 2}rem`,
      backgroundColor: v.white.string(),
      color: v.black.string()
    },
    'h1, h2, h3, h4, h5, h6': {
      fontFamily: v.headerFont,
      textRendering: 'optimizeLegibility',
      padding: 0
    },
    h1: {
      color: v.blu.darken(0.2).string(),
      fontSize: `${v.ms5}rem`,
      lineHeight: `${6 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${4 * v.lnht}rem`,
        left: '0'
      }
    },
    h2: {
      color: v.blu.darken(0.1).string(),
      fontSize: `${v.ms4}rem`,
      lineHeight: `${4 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${3 * v.lnht}rem`,
        left: '0'
      }
    },
    h3: {
      color: v.blu.darken(0.08).string(),
      fontSize: `${v.ms3}rem`,
      lineHeight: `${3 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${2 * v.lnht}rem`,
        left: '0'
      }
    },
    h4: {
      color: v.blu.darken(0.05).string(),
      fontSize: `${v.ms2}rem`,
      lineHeight: `${3 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${2 * v.lnht}rem`,
        left: '0'
      }
    },
    h5: {
      color: v.blu.darken(0.02).string(),
      fontSize: `${v.ms1}rem`,
      lineHeight: `${2 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${2 * v.lnht}rem`,
        left: '0'
      }
    },
    h6: {
      color: v.blu.string(),
      fontSize: `${v.ms0}rem`,
      lineHeight: `${2 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${v.lnht}rem`,
        left: '0'
      }
    },
    p: {
      fontSize: `${v.ms1}rem`,
      lineHeight: `${2 * v.lnht}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${2 * v.lnht}rem`,
        left: '0'
      },
      padding: 0
    },
    a: {
      '&:link, &:visited, &:hover, &:active': {
        textDecoration: 'underline',
        TextDecorationSkipInk: 'auto'
      },
      '&:link, &:visited': {
        color: v.blu.string()
      },
      '&:hover': {
        color: v.blu.darken(0.15).string()
      },
      '&:active': {
        color: v.ong.string()
      },
      '&.active': {
        '&:link, &:visited': {
          color: v.ong.string()
        },
        '&:hover': {
          color: v.ong.darken(0.15).string()
        },
        '&:active': {
          color: v.ong.darken(0.25).string()
        }
      },
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      }
    },
    input: {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      boxSizing: 'border-box',
      height: `${3 * v.lnht}rem`,
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      },
      '&[type=checkbox], &[type=radio]': {
        height: 'inherit',
        verticalAlign: 'baseline'
      }
    },
    button: {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      border: {
        width: '1px',
        style: 'solid',
        color: v.blu.darken(0.15).alpha(0.3).string()
      },
      background: 'none',
      backgroundColor: v.white.darken(0.05).string(),
      padding: `${v.lnht}rem`,
      borderRadius: '3px',
      '&:hover': {
        backgroundColor: v.white.darken(0.1).string()
      },
      '&:active': {
        backgroundColor: v.white.darken(0.15).string(),
        boxShadow: {
          inset: 'inset',
          x: 0,
          y: 0,
          blur: `${v.lnht}rem`,
          spread: null,
          color: v.white.darken(0.25).string()
        }
      },
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      }
    },
    'select, optgroup, textarea': {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`
    },
    select: {
      height: `${3 * v.lnht}rem`
    },
    'code, kbd, samp, var': {
      fontFamily: 'monospace, monospace',
      backgroundColor: v.white.darken(0.1).string(),
      color: v.black.lighten(5).string(),
      padding: {
        top: `${v.lnht / 7}rem`,
        right: `${v.lnht / 2}rem`,
        bottom: `${v.lnht / 7}rem`,
        left: `${v.lnht / 2}rem`
      }
    }
  }
}

export default styles
<commit_msg>Update styles to improve typography<commit_after>import * as v from './variables'

const styles = {
  '@global': {
    '*': {
      boxSizing: 'border-box'
    },
    body: {
      margin: 0,
      padding: 0,
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      lineHeight: v.lnht * 2,
      backgroundColor: v.white.string(),
      color: v.black.string(),
      fontKerning: 'normal',
      fontVariantLigatures: 'common-ligatures contextual',
      fontFeatureSettings: ['"kern" 1', '"liga" 1', '"clig" 1', '"calt" 1']
    },
    'h1, h2, h3, h4, h5, h6': {
      fontFamily: v.headerFont,
      textRendering: 'optimizeLegibility',
      margin: {
        top: `${(v.lnht * 2) * (v.ms1 * 2)}rem`, // base line height * double base font size
        right: '0',
        bottom: `${(v.lnht * 2) * v.ms1}rem`, // base line height * font size
        left: '0'
      },
      padding: 0
    },
    'h1, h2': {
      marginTop: 0
    },
    h1: {
      color: v.blu.darken(0.2).string(),
      fontSize: `${v.ms5}rem`
    },
    h2: {
      color: v.blu.darken(0.1).string(),
      fontSize: `${v.ms4}rem`
    },
    h3: {
      color: v.blu.darken(0.08).string(),
      fontSize: `${v.ms3}rem`
    },
    h4: {
      color: v.blu.darken(0.05).string(),
      fontSize: `${v.ms2}rem`
    },
    h5: {
      color: v.blu.darken(0.02).string(),
      fontSize: `${v.ms1}rem`
    },
    h6: {
      color: v.blu.string(),
      fontSize: `${v.ms0}rem`
    },
    p: {
      fontSize: `${v.ms1}rem`,
      margin: {
        top: '0',
        right: '0',
        bottom: `${(v.lnht * 2) * v.ms1}rem`, // base line height * base font size
        left: '0'
      },
      padding: 0
    },
    a: {
      '&:link, &:visited, &:hover, &:active': {
        textDecoration: 'underline',
        TextDecorationSkipInk: 'auto'
      },
      '&:link, &:visited': {
        color: v.blu.string()
      },
      '&:hover': {
        color: v.blu.darken(0.15).string()
      },
      '&:active': {
        color: v.ong.string()
      },
      '&.active': {
        '&:link, &:visited': {
          color: v.ong.string()
        },
        '&:hover': {
          color: v.ong.darken(0.15).string()
        },
        '&:active': {
          color: v.ong.darken(0.25).string()
        }
      },
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      }
    },
    input: {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      boxSizing: 'border-box',
      height: `${3 * v.lnht}rem`,
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      },
      '&[type=checkbox], &[type=radio]': {
        height: 'inherit',
        verticalAlign: 'baseline'
      }
    },
    button: {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`,
      border: {
        width: '1px',
        style: 'solid',
        color: v.blu.darken(0.15).alpha(0.3).string()
      },
      background: 'none',
      backgroundColor: v.white.darken(0.05).string(),
      padding: `${v.lnht}rem`,
      borderRadius: '3px',
      '&:hover': {
        backgroundColor: v.white.darken(0.1).string()
      },
      '&:active': {
        backgroundColor: v.white.darken(0.15).string(),
        boxShadow: {
          inset: 'inset',
          x: 0,
          y: 0,
          blur: `${v.lnht}rem`,
          spread: null,
          color: v.white.darken(0.25).string()
        }
      },
      '&:focus': {
        outline: {
          width: 1,
          style: 'solid',
          color: v.blu.darken(0.15).alpha(0.3).string()
        }
      }
    },
    'select, optgroup, textarea': {
      fontFamily: v.mainFont,
      fontSize: `${v.ms1}rem`
    },
    select: {
      height: `${3 * v.lnht}rem`
    },
    'code, kbd, samp, var': {
      fontFamily: 'monospace, monospace',
      backgroundColor: v.white.darken(0.1).string(),
      color: v.black.lighten(5).string(),
      padding: {
        top: `${v.lnht / 7}rem`,
        right: `${v.lnht / 2}rem`,
        bottom: `${v.lnht / 7}rem`,
        left: `${v.lnht / 2}rem`
      }
    }
  }
}

export default styles
<|endoftext|>


<commit_before>
export const buildCA = (cert, {commonName}) => {
  cert.setExtensions([{
    name: 'subjectKeyIdentifier'
  }, {
    name: 'authorityKeyIdentifier',
    keyIdentifier: true,
    authorityCertIssuer: true,
    serialNumber: true
  }, {
    name: 'basicConstraints',
    critical: true,
    cA: true
  }, {
    name: 'keyUsage',
    critical: true,
    keyCertSign: true,
    cRLSign: true
  }, {
    name: 'certificatePolicies',
    value: `Reliance on this certificate by any party assumes acceptance of the
    then applicable standard terms and conditions of use, certificate policy and
    certification practice statements.`
  }]);
};

export const genReq = (csr, {commonName}) => {
  // csr.setAttributes([{
  //   name: 'challengePassword',
  //   value: 'password'
  // }, {
  //   name: 'unstructuredName',
  //   value: 'My Company, Inc.'
  // }, {
  //   name: 'extensionRequest',
  //   extensions: [{
  //     name: 'subjectAltName',
  //     altNames: [{
  //       // 2 is DNS type
  //       type: 2,
  //       value: 'test.domain.com'
  //     }, {
  //       type: 2,
  //       value: 'other.domain.com',
  //     }, {
  //       type: 2,
  //       value: 'www.domain.net'
  //     }]
  //   }]
  // }]);
};

export const signReq = (cert, {type, commonName, ca}) => {
  switch (type) {
    case 'client':
      cert.setExtensions([{
        name: 'basicConstraints',
        critical: true,
        cA: false
      }, {
        name: 'subjectKeyIdentifier'
      }, {
        name: 'authorityKeyIdentifier',
        keyIdentifier: ca.cert.generateSubjectKeyIdentifier().getBytes()
        // authorityCertIssuer: this._ca.cert.issuer, // not-iPad
        // serialNumber: this._ca.cert.serialNumber // not-iPad
      }, {
        name: 'extKeyUsage',
        critical: true,
        serverAuth: true,
        clientAuth: true
      }, {
        name: 'keyUsage',
        critical: true,
        digitalSignature: true,
        keyEncipherment: true
      }, {
        id: '1.2.840.113635.100.6.10.2',
        value: String.fromCharCode(0x0005) + String.fromCharCode(0x0000)
      }]);
      break;
    default:
      throw new Error('Type not supported');
  }
};
<commit_msg>fix(test): fix tests<commit_after>
export const buildCA = (cert, {commonName}) => {
  cert.setExtensions([{
    name: 'subjectKeyIdentifier'
  }, {
    name: 'authorityKeyIdentifier',
    keyIdentifier: true,
    authorityCertIssuer: true,
    serialNumber: true
  }, {
    name: 'basicConstraints',
    critical: true,
    cA: true
  }, {
    name: 'keyUsage',
    critical: true,
    keyCertSign: true,
    cRLSign: true
  }, {
    id: '2.5.29.32',
    name: 'certificatePolicies',
    value: `Reliance on this certificate by any party assumes acceptance of the
    then applicable standard terms and conditions of use, certificate policy and
    certification practice statements.`
  }]);
};

export const genReq = (csr, {commonName}) => {
  // csr.setAttributes([{
  //   name: 'challengePassword',
  //   value: 'password'
  // }, {
  //   name: 'unstructuredName',
  //   value: 'My Company, Inc.'
  // }, {
  //   name: 'extensionRequest',
  //   extensions: [{
  //     name: 'subjectAltName',
  //     altNames: [{
  //       // 2 is DNS type
  //       type: 2,
  //       value: 'test.domain.com'
  //     }, {
  //       type: 2,
  //       value: 'other.domain.com',
  //     }, {
  //       type: 2,
  //       value: 'www.domain.net'
  //     }]
  //   }]
  // }]);
};

export const signReq = (cert, {type, commonName, ca}) => {
  switch (type) {
    case 'client':
      cert.setExtensions([{
        name: 'basicConstraints',
        critical: true,
        cA: false
      }, {
        name: 'subjectKeyIdentifier'
      }, {
        name: 'authorityKeyIdentifier',
        keyIdentifier: ca.cert.generateSubjectKeyIdentifier().getBytes()
        // authorityCertIssuer: this._ca.cert.issuer, // not-iPad
        // serialNumber: this._ca.cert.serialNumber // not-iPad
      }, {
        name: 'extKeyUsage',
        critical: true,
        serverAuth: true,
        clientAuth: true
      }, {
        name: 'keyUsage',
        critical: true,
        digitalSignature: true,
        keyEncipherment: true
      }, {
        id: '1.2.840.113635.100.6.10.2',
        value: String.fromCharCode(0x0005) + String.fromCharCode(0x0000)
      }]);
      break;
    default:
      throw new Error('Type not supported');
  }
};
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var TextItem = this.TextItem = Item.extend({
	beans: true,

	initialize: function() {
		this.base();
		point = Point.read(arguments, 0, 1);
		this.content = null;
		this.setCharacterStyle(this._project.getCurrentStyle());
		this.setParagraphStyle();
	},

	getCharacterStyle: function() {
		return this._characterStyle;
	},

	setCharacterStyle: function(style) {
		this._characterStyle = CharacterStyle.create(this, style);
	},

	getParagraphStyle: function() {
		return this._paragraphStyle;
	},

	setParagraphStyle: function(style) {
		this._paragraphStyle = ParagraphStyle.create(this, style);
	}
});
<commit_msg>Remove unused code that seems to stem from PointText.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var TextItem = this.TextItem = Item.extend({
	beans: true,

	initialize: function() {
		this.base();
		this.content = null;
		this.setCharacterStyle(this._project.getCurrentStyle());
		this.setParagraphStyle();
	},

	getCharacterStyle: function() {
		return this._characterStyle;
	},

	setCharacterStyle: function(style) {
		this._characterStyle = CharacterStyle.create(this, style);
	},

	getParagraphStyle: function() {
		return this._paragraphStyle;
	},

	setParagraphStyle: function(style) {
		this._paragraphStyle = ParagraphStyle.create(this, style);
	}
});
<|endoftext|>


<commit_before>import {Color} from 'flockn/types';
import serializable from 'flockn/mixins/serializable';

class TextureLabel {
  constructor(texture) {
    // Default value for `label`
    this.drawable = false;
    this.font = {
      size: 10,
      name: 'Arial',
      color: Color.black(),
      decoration: []
    };

    this.align = {
      x: 'center',
      y: 'center'
    };
    this.width = 0;
    this.height = 0;

    var text = '';

    Object.defineProperty(this, 'text', {
      get: function () {
        return text;
      },
      set: function (value) {
        text = value;

        // Calculate the size of the label and update the dimensions
        // TODO: This should be handled somewhere else, but I'm not sure where
        var tmpElem = document.createElement('div');
        tmpElem.innerText = text;
        tmpElem.style.position = 'absolute';
        tmpElem.style.left = '-9999px';
        tmpElem.style.top = '-9999px';
        tmpElem.style.fontSize = this.font.size + 'px';
        tmpElem.style.fontFamily = this.font.name;
        tmpElem.style.color = this.font.color;

        this.font.decoration.forEach(decoration => {
          switch (decoration) {
            case 'bold':
              tmpElem.style.fontWeight = 'bold';
              break;
            case 'italic':
              tmpElem.style.fontStyle = 'italic';
              break;
            case 'underline':
              tmpElem.style.textDecoration = 'underline';
              break;
            default:
              break;
          }
        });

        document.body.appendChild(tmpElem);

        this.width = tmpElem.clientWidth;
        this.height = tmpElem.clientHeight;
        this.drawable = true;

        document.body.removeChild(tmpElem);

        texture.trigger('label-loaded');
      }
    });
  }
}

serializable(TextureLabel);

export default TextureLabel;
<commit_msg>:fire: Update texture label dependencies<commit_after>import {Color} from '../types';
import serializable from '../mixins/serializable';

class TextureLabel {
  constructor(texture) {
    // Default value for `label`
    this.drawable = false;
    this.font = {
      size: 10,
      name: 'Arial',
      color: Color.black(),
      decoration: []
    };

    this.align = {
      x: 'center',
      y: 'center'
    };
    this.width = 0;
    this.height = 0;

    var text = '';

    Object.defineProperty(this, 'text', {
      get: function () {
        return text;
      },
      set: function (value) {
        text = value;

        // Calculate the size of the label and update the dimensions
        // TODO: This should be handled somewhere else, but I'm not sure where
        var tmpElem = document.createElement('div');
        tmpElem.innerText = text;
        tmpElem.style.position = 'absolute';
        tmpElem.style.left = '-9999px';
        tmpElem.style.top = '-9999px';
        tmpElem.style.fontSize = this.font.size + 'px';
        tmpElem.style.fontFamily = this.font.name;
        tmpElem.style.color = this.font.color;

        this.font.decoration.forEach(decoration => {
          switch (decoration) {
            case 'bold':
              tmpElem.style.fontWeight = 'bold';
              break;
            case 'italic':
              tmpElem.style.fontStyle = 'italic';
              break;
            case 'underline':
              tmpElem.style.textDecoration = 'underline';
              break;
            default:
              break;
          }
        });

        document.body.appendChild(tmpElem);

        this.width = tmpElem.clientWidth;
        this.height = tmpElem.clientHeight;
        this.drawable = true;

        document.body.removeChild(tmpElem);

        texture.trigger('label-loaded');
      }
    });
  }
}

serializable(TextureLabel);

export default TextureLabel;
<|endoftext|>


<commit_before>L.Handler.Draw = L.Handler.extend({
	initialize: function (map, options) {
		this._map = map;
		this._container = map._container;
		this._pane = map._panes.overlayPane;

		L.Util.setOptions(this, options);
	},

	addHooks: function () {
		if (this._map) {
			L.DomUtil.disableTextSelection();
			this._label = L.DomUtil.create('div', 'leaflet-draw-label', this._pane);
		}
	},

	removeHooks: function () {
		if (this._map) {
			L.DomUtil.enableTextSelection();
			this._pane.removeChild(this._label);
		}
	},

	_updateLabel: function (pos, text, subtext) {
		L.DomUtil.setPosition(this._label, pos);
		this._label.innerHTML =
			'' + text + '' +
			'
' +
			'' + subtext + '';
	}
});<commit_msg>Delete the _label property<commit_after>L.Handler.Draw = L.Handler.extend({
	initialize: function (map, options) {
		this._map = map;
		this._container = map._container;
		this._pane = map._panes.overlayPane;

		L.Util.setOptions(this, options);
	},

	addHooks: function () {
		if (this._map) {
			L.DomUtil.disableTextSelection();
			this._label = L.DomUtil.create('div', 'leaflet-draw-label', this._pane);
		}
	},

	removeHooks: function () {
		if (this._map) {
			L.DomUtil.enableTextSelection();
			this._pane.removeChild(this._label);
			delete this._label;
		}
	},

	_updateLabel: function (pos, text, subtext) {
		L.DomUtil.setPosition(this._label, pos);
		this._label.innerHTML =
			'' + text + '' +
			'
' +
			'' + subtext + '';
	}
});<|endoftext|>


<commit_before>/** (July 2012, Erik Weitnauer)
The html-plugin will load and display an arbitrary number of html pages. To proceed to the next, the
user might either press a button on the page or a specific key. Afterwards, the page get hidden and
the plugin will wait of a specified time before it proceeds.

documentation: docs.jspsych.org
*/

jsPsych.plugins.html = (function() {

  var plugin = {};

  plugin.info = {
    name: 'html',
    description: '',
    parameters: {
      url: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: undefined,
        no_function: false,
        description: ''
      },
      cont_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: null,
        no_function: false,
        description: ''
      },
      cont_btn: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: null,
        no_function: false,
        description: ''
      },
      check_fn: {
        type: [jsPsych.plugins.parameterType.FUNCTION],
        default: 'function() { return true; }',
        no_function: false,
        description: ''
      },
      force_refresh: {
        type: [jsPsych.plugins.parameterType.BOOL],
        default: false,
        no_function: false,
        description: ''
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    // default parameters
    trial.check_fn = trial.check_fn || function() { return true; }
    trial.force_refresh = (typeof trial.force_refresh === 'undefined') ? false : trial.force_refresh

    // if any trial variables are functions
    // this evaluates the function and replaces
    // it with the output of the function
    trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial, ["check_fn"]);

    var url = trial.url;
    if (trial.force_refresh) {
      url = trial.url + "?time=" + (new Date().getTime());
    }

    load(display_element, url, function() {
      var t0 = (new Date()).getTime();
      var finish = function() {
        if (trial.check_fn && !trial.check_fn(display_element)) { return };
        if (trial.cont_key) { document.removeEventListener('keydown', key_listener); }
        var trial_data = {
          rt: (new Date()).getTime() - t0,
          url: trial.url
        };
        display_element.innerHTML = '';
        jsPsych.finishTrial(trial_data);
      };
      if (trial.cont_btn) { display_element.querySelector('#'+trial.cont_btn).addEventListener('click', finish); }
      if (trial.cont_key) {
        var key_listener = function(e) {
          if (e.which == trial.cont_key) finish();
        };
        display_element.addEventListener('keydown', key_listener);
      }
    });
  };

  // helper to load via XMLHttpRequest
  function load(element, file, callback){
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", file, true);
    xmlhttp.onreadystatechange = function(){
        if(xmlhttp.status == 200 && xmlhttp.readyState == 4){ //Check if loaded
            element.innerHTML = xmlHttp.responseText;
            callback();
        }
    }
    xmlhttp.send();
  }

  return plugin;
})();
<commit_msg>fix spelling error xmlHttp to xmlhttp<commit_after>/** (July 2012, Erik Weitnauer)
The html-plugin will load and display an arbitrary number of html pages. To proceed to the next, the
user might either press a button on the page or a specific key. Afterwards, the page get hidden and
the plugin will wait of a specified time before it proceeds.

documentation: docs.jspsych.org
*/

jsPsych.plugins.html = (function() {

  var plugin = {};

  plugin.info = {
    name: 'html',
    description: '',
    parameters: {
      url: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: undefined,
        no_function: false,
        description: ''
      },
      cont_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: null,
        no_function: false,
        description: ''
      },
      cont_btn: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: null,
        no_function: false,
        description: ''
      },
      check_fn: {
        type: [jsPsych.plugins.parameterType.FUNCTION],
        default: 'function() { return true; }',
        no_function: false,
        description: ''
      },
      force_refresh: {
        type: [jsPsych.plugins.parameterType.BOOL],
        default: false,
        no_function: false,
        description: ''
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    // default parameters
    trial.check_fn = trial.check_fn || function() { return true; }
    trial.force_refresh = (typeof trial.force_refresh === 'undefined') ? false : trial.force_refresh

    // if any trial variables are functions
    // this evaluates the function and replaces
    // it with the output of the function
    trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial, ["check_fn"]);

    var url = trial.url;
    if (trial.force_refresh) {
      url = trial.url + "?time=" + (new Date().getTime());
    }

    load(display_element, url, function() {
      var t0 = (new Date()).getTime();
      var finish = function() {
        if (trial.check_fn && !trial.check_fn(display_element)) { return };
        if (trial.cont_key) { document.removeEventListener('keydown', key_listener); }
        var trial_data = {
          rt: (new Date()).getTime() - t0,
          url: trial.url
        };
        display_element.innerHTML = '';
        jsPsych.finishTrial(trial_data);
      };
      if (trial.cont_btn) { display_element.querySelector('#'+trial.cont_btn).addEventListener('click', finish); }
      if (trial.cont_key) {
        var key_listener = function(e) {
          if (e.which == trial.cont_key) finish();
        };
        display_element.addEventListener('keydown', key_listener);
      }
    });
  };

  // helper to load via XMLHttpRequest
  function load(element, file, callback){
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", file, true);
    xmlhttp.onreadystatechange = function(){
        if(xmlhttp.status == 200 && xmlhttp.readyState == 4){ //Check if loaded
            element.innerHTML = xmlhttp.responseText;
            callback();
        }
    }
    xmlhttp.send();
  }

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


<commit_before>// TODO: Add events to UI.ListPicker
// IMPROVE: shall we move activate() and deactivate() to Picker itself?

UI.ListPicker = function() {}
UI.ListPicker.prototype = new UI.Picker();

Eventable(UI.ListPicker, ['activate', 'deactivate', 'select']);

UI.ListPicker.prototype.initListPicker = function(input, options)
{
  this.setDefaultOptions({autoMarkFirst: false});
  this.initPicker(input, options);
  this.container.addClassName('list-picker');

  this.relativeElement.setAttribute('autocomplete', 'off');
  this.relativeElement.addEventListener('focus',    this.activate.bind(this),    false);
  this.relativeElement.addEventListener('click',    this.activate.bind(this),    false);
  this.relativeElement.addEventListener('blur',     this.deactivate.bind(this),  false);
  this.relativeElement.addEventListener('keypress', this.keypress_cb.bind(this), false);
  this.relativeElement.addEventListener('keyup',    this.keyup_cb.bind(this),    false);

  this.list = document.createElement('ul');
  this.list.addEventListener('click', this.click_cb.bind(this), false);
  this.setContent(this.list);
}

// items

UI.ListPicker.prototype.setItems = function(items)
{
  if (typeof items == 'string') {
    this.list.innerHTML = items;
  }
  else if (items.nodeName == '#document-fragment')
  {
    this.list.innerHTML = '';
    this.list.appendChild(items);
  }
  else
  {
    this.list.innerHTML = '';

    for (var i=0, len=items.length; i<len; i++) {
      this.list.appendChild(items[i]);
    }
  }

  if (this.options.autoMarkFirst && this.hasItems()) {
    this.markSelection(this.getItem(0));
  }
  else {
    this.unmarkSelection();
  }
}

UI.ListPicker.prototype.getItems = function() {
  return this.list.getElementsByTagName('li');
}

UI.ListPicker.prototype.getItem = function(index) {
  return this.list.getElementsByTagName('li')[index];
}

UI.ListPicker.prototype.hasItems = function(items) {
  return this.getItems().length > 0;
}

// selection

UI.ListPicker.prototype.markSelection = function(item)
{
  if (item)
  {
    if (this.selection) {
      this.selection.removeClassName('selected');
    }
    this.selection = item;
    this.selection.addClassName('selected');
  }
},

UI.ListPicker.prototype.unmarkSelection = function()
{
  if (this.selection)
  {
    this.selection.removeClassName('selected');
    this.selection = null;
  }
},

UI.ListPicker.prototype.moveSelectionUp = function()
{
  var item, items;

  if (this.selection)
  {
    item = this.selection.get ?
      this.selection.get('previousElementSibling') :
      this.selection.previousElementSibling;
  }
  else if (this.hasItems())
  {
    items = this.getItems();
    item  = items[items.length - 1];
  }

  this.markSelection(item)
},

UI.ListPicker.prototype.moveSelectionDown = function()
{
  var item;

  if (this.selection)
  {
    item = this.selection.get ?
      this.selection.get('nextElementSibling') :
      this.selection.nextElementSibling;
  }
  else if (this.hasItems()) {
    item = this.getItems()[0];
  }

  this.markSelection(item)
},

UI.ListPicker.prototype.selectSelection = function()
{
  this.hide();

  if (this.selection)
  {
    var event = this.createEvent('select');
    event.target = this.selection;
    this.dispatchEvent(event);
    this.unmarkSelection();
  }
}

// display

UI.ListPicker.prototype.showOrHide = function()
{
  var method = this.hasItems() ? 'show' : 'hide';
  this[method]();
}

UI.ListPicker.prototype.activate = function(event)
{
//  if (this.dispatchEvent('activate') && this.hasItems()) {
  if (this.hasItems()) {
    this.show();
  }
}

UI.ListPicker.prototype.deactivate = function(event) {
//  this.dispatchEvent('deactivate');
}

UI.ListPicker.prototype.cancel = function(event)
{
  this.hide();

  if (event)
  {
    event.stopPropagation();
    event.preventDefault();
  }
}

// callbacks

// IMPROVE: search for a parent LI if target isn't an LI.
UI.ListPicker.prototype.click_cb = function(event)
{
  if (event.target.tagName.toLowerCase() == 'li')
  {
    this.selection = event.target;
    this.selectSelection();
  }
}

UI.ListPicker.prototype.keypress_cb = function(event)
{
  switch(event.keyCode)
  {
    case 27: // esc
      this.cancel(event);
      return;

    case 13: // enter
      if (this.displayed())
      {
        this.selectSelection();
        event.stopPropagation();
        event.preventDefault();
      }
      return;
  }
}

UI.ListPicker.prototype.keyup_cb = function(event)
{
  switch(event.keyCode)
  {
    case 38: this.moveSelectionUp();   return; // up
    case 40: this.moveSelectionDown(); return; // down
    case 13: return;                           // enter
    case 27: this.cancel(event); return;       // esc
  }
  this.showOrHide();
}

<commit_msg>Added clearItems() method to UI.ListPicker to empty the list picker.<commit_after>// TODO: Dispatch activate/deactivate events
// IMPROVE: shall we move activate/deactivate to UI.Picker itself?

UI.ListPicker = function() {}
UI.ListPicker.prototype = new UI.Picker();

Eventable(UI.ListPicker, ['activate', 'deactivate', 'select']);

UI.ListPicker.prototype.initListPicker = function(input, options)
{
  this.setDefaultOptions({autoMarkFirst: false});
  this.initPicker(input, options);
  this.container.addClassName('list-picker');

  this.relativeElement.setAttribute('autocomplete', 'off');
  this.relativeElement.addEventListener('focus',    this.activate.bind(this),    false);
  this.relativeElement.addEventListener('click',    this.activate.bind(this),    false);
  this.relativeElement.addEventListener('blur',     this.deactivate.bind(this),  false);
  this.relativeElement.addEventListener('keypress', this.keypress_cb.bind(this), false);
  this.relativeElement.addEventListener('keyup',    this.keyup_cb.bind(this),    false);

  this.list = document.createElement('ul');
  this.list.addEventListener('click', this.click_cb.bind(this), false);
  this.setContent(this.list);
}

// items

UI.ListPicker.prototype.setItems = function(items)
{
  if (typeof items == 'string') {
    this.list.innerHTML = items;
  }
  else if (items.nodeName == '#document-fragment')
  {
    this.list.innerHTML = '';
    this.list.appendChild(items);
  }
  else
  {
    this.list.innerHTML = '';

    for (var i=0, len=items.length; i<len; i++) {
      this.list.appendChild(items[i]);
    }
  }

  if (this.options.autoMarkFirst && this.hasItems()) {
    this.markSelection(this.getItem(0));
  }
  else {
    this.unmarkSelection();
  }
}

UI.ListPicker.prototype.getItems = function() {
  return this.list.getElementsByTagName('li');
}

UI.ListPicker.prototype.getItem = function(index) {
  return this.list.getElementsByTagName('li')[index];
}

UI.ListPicker.prototype.hasItems = function(items) {
  return this.getItems().length > 0;
}

UI.ListPicker.prototype.clearItems = function() {
  this.list.innerHTML = '';
}

// selection

UI.ListPicker.prototype.markSelection = function(item)
{
  if (item)
  {
    if (this.selection) {
      this.selection.removeClassName('selected');
    }
    this.selection = item;
    this.selection.addClassName('selected');
  }
},

UI.ListPicker.prototype.unmarkSelection = function()
{
  if (this.selection)
  {
    this.selection.removeClassName('selected');
    this.selection = null;
  }
},

UI.ListPicker.prototype.moveSelectionUp = function()
{
  var item, items;

  if (this.selection)
  {
    item = this.selection.get ?
      this.selection.get('previousElementSibling') :
      this.selection.previousElementSibling;
  }
  else if (this.hasItems())
  {
    items = this.getItems();
    item  = items[items.length - 1];
  }

  this.markSelection(item)
},

UI.ListPicker.prototype.moveSelectionDown = function()
{
  var item;

  if (this.selection)
  {
    item = this.selection.get ?
      this.selection.get('nextElementSibling') :
      this.selection.nextElementSibling;
  }
  else if (this.hasItems()) {
    item = this.getItems()[0];
  }

  this.markSelection(item)
},

UI.ListPicker.prototype.selectSelection = function()
{
  this.hide();

  if (this.selection)
  {
    var event = this.createEvent('select');
    event.target = this.selection;
    this.dispatchEvent(event);
    this.unmarkSelection();
  }
}

// display

UI.ListPicker.prototype.showOrHide = function()
{
  var method = this.hasItems() ? 'show' : 'hide';
  this[method]();
}

UI.ListPicker.prototype.activate = function(event)
{
//  if (!this.dispatchEvent('activate') && this.hasItems()) {
  if (this.hasItems()) {
    this.show();
  }
}

UI.ListPicker.prototype.deactivate = function(event) {
//  this.dispatchEvent('deactivate');
}

UI.ListPicker.prototype.cancel = function(event)
{
  this.hide();

  if (event)
  {
    event.stopPropagation();
    event.preventDefault();
  }
}

// callbacks

// IMPROVE: search for a parent LI if target isn't an LI.
UI.ListPicker.prototype.click_cb = function(event)
{
  if (event.target.tagName.toLowerCase() == 'li')
  {
    this.selection = event.target;
    this.selectSelection();
  }
}

UI.ListPicker.prototype.keypress_cb = function(event)
{
  switch(event.keyCode)
  {
    case 27: // esc
      this.cancel(event);
      return;

    case 13: // enter
      if (this.displayed())
      {
        this.selectSelection();
        event.stopPropagation();
        event.preventDefault();
      }
      return;
  }
}

UI.ListPicker.prototype.keyup_cb = function(event)
{
  switch(event.keyCode)
  {
    case 38: this.moveSelectionUp();   return; // up
    case 40: this.moveSelectionDown(); return; // down
    case 13: return;                           // enter
    case 27: this.cancel(event); return;       // esc
  }
  this.showOrHide();
}

<|endoftext|>


<commit_before>///
// Dependencies
///

import React, { Component } from 'react';

import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
import FormControl from '../forms/FormControl';

import Form from '../forms/Form';

///
// View
///

class LoginFormView extends Component {
	render() {
		return (
			<Form
				name="login"
				className="login-form"
				onSubmit={this.props.onSubmit}
			>
				<FormGroup controlId="emailField">
					<ControlLabel>Email</ControlLabel>
					<FormControl
						name="email"
						type="text"
						placeholder="example@example.com"
					/>
				</FormGroup>
				<FormGroup controlId="passwordField">
					<ControlLabel>Password</ControlLabel>
					<FormControl
						name="password"
						type="password"
					/>
				</FormGroup>
				{this.props.children}
			</Form>
		);
	}
}

export default LoginFormView;

<commit_msg>Refactoring / code cleanup<commit_after>///
// Dependencies
///

import React, { Component } from 'react';

import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';

import Form from '../forms/Form';
import FormControl from '../forms/FormControl';

///
// View
///

class LoginFormView extends Component {
	render() {
		return (
			<Form
				name="login"
				className="login-form"
				onSubmit={this.props.onSubmit}
			>
				<FormGroup controlId="emailField">
					<ControlLabel>Email</ControlLabel>
					<FormControl
						name="email"
						type="text"
						placeholder="example@example.com"
					/>
				</FormGroup>
				<FormGroup controlId="passwordField">
					<ControlLabel>Password</ControlLabel>
					<FormControl
						name="password"
						type="password"
					/>
				</FormGroup>
				{this.props.children}
			</Form>
		);
	}
}

export default LoginFormView;

<|endoftext|>


<commit_before>import {
  T,
  add,
  all,
  always,
  applySpec,
  assoc,
  complement,
  concat,
  cond,
  converge,
  curry,
  difference,
  dissoc,
  divide,
  equals,
  evolve,
  flatten,
  flip,
  identity,
  ifElse,
  includes,
  invertObj,
  is,
  head,
  keys,
  length,
  lensPath,
  lensProp,
  map,
  mergeRight,
  nthArg,
  objOf,
  omit,
  over,
  path,
  pick,
  pipe,
  prop,
  reject,
  set,
  subtract,
  sum,
  uniqBy,
  useWith,
  values,
} from 'ramda';
import { containsParentInNode, isNodeWithoutParents } from 'validations/node';

import { createNodeCombinations } from './combinations';
import { filterNodeParents } from './node-parents';
import { findNodeById } from './node';
import { roundValue } from './math';

const propId = prop('id');
const propCpt = prop('cpt');
const propStates = prop('states');
const propParents = prop('parents');
const isArray = is(Array);
const nthArg0 = nthArg(0);
const nthArg1 = nthArg(1);
const includesFlipped = flip(includes);
const getCptValue = converge(divide, [always(1), length]);
const getCptValueRounded = pipe(getCptValue, roundValue);
const mapAndFlatten = pipe(map, flatten);
const uniqByWhen = uniqBy(prop('when'));
const objectLenght = pipe(keys, length);
const sumValues = pipe(values, sum);
const getFirstKey = pipe(keys, head);
const getMissingValueFromCpt = pipe(sumValues, subtract(1));
const filterCptWhenByKeys = useWith(evolve, [pipe(pick, objOf('when'))]);
const mapWhen = useWith(map, [filterCptWhenByKeys, identity]);
const hasOnlyThisParent = useWith(all, [pipe(propId, equals), propParents]);
const getValueForEachCpt = useWith(pipe(divide, roundValue), [identity, length]);
const hasNoParent = useWith(complement(containsParentInNode), [
  propId,
  identity,
]);

const getMissingValueForEachCpt = converge(divide, [
  pipe(nthArg0, getMissingValueFromCpt),
  pipe(nthArg0, objectLenght),
]);

const fixCptFloatingPoint = converge(
  over,
  [
    pipe(nthArg0, getFirstKey, lensProp),
    pipe(nthArg0, getMissingValueFromCpt, roundValue, add),
    nthArg0,
  ],
);

const balanceCptValues = converge(pipe(map, fixCptFloatingPoint), [
  pipe(nthArg0, getMissingValueForEachCpt, roundValue, add),
  nthArg0,
]);

const addKeyAndValueInWhen = (key, value) =>
  set(lensPath(['when', key]), value);

const addNewStatesInCptWithValue = (value, states, cpt) =>
  mergeRight(
    cpt,
    map(always(getValueForEachCpt(value, states)), invertObj(states)),
  );

const getDiffStates = applySpec({
  deletedStates: flip(difference),
  newStates: difference,
});

const renamePropKey = curry((oldKey, newKey, obj) =>
  assoc(newKey, prop(oldKey, obj), dissoc(oldKey, obj)));

export const updateCptValue = (cpt, value, state, index) => {
  if (isArray(cpt)) {
    return set(lensPath([index, 'then', state]), value, cpt);
  }
  return assoc(state, value, cpt);
};

export const createCpt = converge(pipe(map, fixCptFloatingPoint), [
  pipe(nthArg0, getCptValueRounded, always),
  pipe(nthArg0, invertObj),
]);

const cptObjectToArray = (nodeParent, node) =>
  map(
    state => ({
      when: objOf(propId(nodeParent), state),
      then: propCpt(node),
    }),
    propStates(nodeParent),
  );

const addCptArrayItem = (nodeParent, node) => {
  const id = propId(nodeParent);

  return mapAndFlatten(
    newState => map(addKeyAndValueInWhen(id, newState), propCpt(node)),
    propStates(nodeParent),
  );
};

export const addNodeParentInCpt = ifElse(
  pipe(nthArg1, isNodeWithoutParents),
  cptObjectToArray,
  addCptArrayItem,
);

const removeParentInCptWhenNodeHasOneParent = path(['cpt', 0, 'then']);

const removeParentInCptWhenNodeHasMoreThanOneParent = (nodeParent, node) =>
  uniqByWhen(
    mapWhen(filterNodeParents(propId(nodeParent), node), propCpt(node)),
  );

export const removeNodeParentInCpt = cond([
  [hasNoParent, pipe(nthArg1, propCpt)],
  [hasOnlyThisParent, pipe(nthArg1, removeParentInCptWhenNodeHasOneParent)],
  [T, removeParentInCptWhenNodeHasMoreThanOneParent],
]);

const changeCptStates = curry((nextStates, cpt) => {
  const { deletedStates, newStates } = getDiffStates(nextStates, keys(cpt));
  const valueFromDeletedStates = roundValue(sumValues(pick(deletedStates, cpt)));

  return balanceCptValues(
    omit(
      deletedStates,
      addNewStatesInCptWithValue(valueFromDeletedStates, newStates, cpt),
    ),
  );
});

const updateStatesInCptObject = useWith(changeCptStates, [identity, propCpt]);

const updateStatesInCptArray = (nextStates, node) =>
  map(
    evolve({
      then: changeCptStates(nextStates),
    }),
    propCpt(node),
  );

export const updateStatesInCpt = ifElse(
  pipe(nthArg1, isNodeWithoutParents),
  updateStatesInCptObject,
  updateStatesInCptArray,
);

export const updateNodeParentIdInCpt = (nodeId, nextId, node) =>
  map(
    evolve({
      when: renamePropKey(nodeId, nextId),
    }),
    propCpt(node),
  );

const createWhensForNodeWithStates = (parentId, newStates, nodes, node) => {
  const nodeParents = map(id => findNodeById(id, nodes), node.parents);
  const parents = map(
    ({ id, states }) => ({
      id,
      states: equals(id, parentId) ? newStates : states,
    }),
    nodeParents,
  );

  return createNodeCombinations(parents);
};

const removeCptsForNodeWithStates = (parentId, deletedStates, node) =>
  reject(
    pipe(path(['when', parentId]), includesFlipped(deletedStates)),
    propCpt(node),
  );

const createCptsForNodeWithStates = (parentId, newStates, nodes, node) =>
  map(
    pipe(objOf('when'), assoc('then', createCpt(propStates(node)))),
    createWhensForNodeWithStates(parentId, newStates, nodes, node),
  );

export const updateNodeParentStatesInCpt = curry(
  (parentId, nextStates, nodes, node) => {
    const nodeParent = findNodeById(parentId, nodes);
    const { deletedStates, newStates } = getDiffStates(
      nextStates,
      propStates(nodeParent),
    );

    return concat(
      removeCptsForNodeWithStates(parentId, deletedStates, node),
      createCptsForNodeWithStates(parentId, newStates, nodes, node),
    );
  },
);
<commit_msg>Update node cpt utils<commit_after>import {
  T,
  add,
  all,
  always,
  applySpec,
  assoc,
  complement,
  concat,
  cond,
  converge,
  curry,
  difference,
  dissoc,
  divide,
  equals,
  evolve,
  flatten,
  flip,
  identity,
  ifElse,
  includes,
  invertObj,
  is,
  head,
  keys,
  length,
  lensPath,
  lensProp,
  map,
  mergeRight,
  nthArg,
  objOf,
  omit,
  over,
  path,
  pick,
  pipe,
  prop,
  reject,
  set,
  subtract,
  sum,
  uniqBy,
  useWith,
  values,
} from 'ramda';
import { containsParentInNode, isNodeWithoutParents } from 'validations/node';

import { createNodeCombinations } from './combinations';
import { filterNodeParents } from './node-parents';
import { findNodeById } from './node';
import { roundValue } from './math';

const propId = prop('id');
const propCpt = prop('cpt');
const propStates = prop('states');
const propParents = prop('parents');
const isArray = is(Array);
const nthArg0 = nthArg(0);
const nthArg1 = nthArg(1);
const includesFlipped = flip(includes);
const getCptValue = converge(divide, [always(1), length]);
const getCptValueRounded = pipe(getCptValue, roundValue);
const mapAndFlatten = pipe(map, flatten);
const uniqByWhen = uniqBy(prop('when'));
const objectLenght = pipe(keys, length);
const sumValues = pipe(values, sum);
const getFirstKey = pipe(keys, head);
const getMissingValueFromCpt = pipe(sumValues, subtract(1));
const filterCptWhenByKeys = useWith(evolve, [pipe(pick, objOf('when'))]);
const mapWhen = useWith(map, [filterCptWhenByKeys, identity]);
const hasOnlyThisParent = useWith(all, [pipe(propId, equals), propParents]);
const getValueForEachCpt = useWith(pipe(divide, roundValue), [identity, length]);
const hasNoParent = useWith(complement(containsParentInNode), [propId, identity]);
const getMissingValueForEachCpt = converge(divide, [getMissingValueFromCpt, objectLenght]);

const fixCptFloatingPoint = converge(
  over,
  [
    pipe(getFirstKey, lensProp),
    pipe(getMissingValueFromCpt, roundValue, add),
    nthArg0,
  ],
);

const balanceCptValues = converge(pipe(map, fixCptFloatingPoint), [
  pipe(getMissingValueForEachCpt, roundValue, add),
  nthArg0,
]);

const addKeyAndValueInWhen = (key, value) =>
  set(lensPath(['when', key]), value);

const addNewStatesInCptWithValue = (value, states, cpt) =>
  mergeRight(
    cpt,
    map(always(getValueForEachCpt(value, states)), invertObj(states)),
  );

const getDiffStates = applySpec({
  deletedStates: flip(difference),
  newStates: difference,
});

const renamePropKey = curry((oldKey, newKey, obj) =>
  assoc(newKey, prop(oldKey, obj), dissoc(oldKey, obj)));

export const updateCptValue = (cpt, value, state, index) => {
  if (isArray(cpt)) {
    return set(lensPath([index, 'then', state]), value, cpt);
  }
  return assoc(state, value, cpt);
};

export const createCpt = converge(pipe(map, fixCptFloatingPoint), [
  pipe(getCptValueRounded, always),
  invertObj,
]);

const cptObjectToArray = (nodeParent, node) =>
  map(
    state => ({
      when: objOf(propId(nodeParent), state),
      then: propCpt(node),
    }),
    propStates(nodeParent),
  );

const addCptArrayItem = (nodeParent, node) => {
  const id = propId(nodeParent);

  return mapAndFlatten(
    newState => map(addKeyAndValueInWhen(id, newState), propCpt(node)),
    propStates(nodeParent),
  );
};

export const addNodeParentInCpt = ifElse(
  pipe(nthArg1, isNodeWithoutParents),
  cptObjectToArray,
  addCptArrayItem,
);

const removeParentInCptWhenNodeHasOneParent = path(['cpt', 0, 'then']);

const removeParentInCptWhenNodeHasMoreThanOneParent = (nodeParent, node) =>
  uniqByWhen(
    mapWhen(filterNodeParents(propId(nodeParent), node), propCpt(node)),
  );

export const removeNodeParentInCpt = cond([
  [hasNoParent, pipe(nthArg1, propCpt)],
  [hasOnlyThisParent, pipe(nthArg1, removeParentInCptWhenNodeHasOneParent)],
  [T, removeParentInCptWhenNodeHasMoreThanOneParent],
]);

const changeCptStates = curry((nextStates, cpt) => {
  const { deletedStates, newStates } = getDiffStates(nextStates, keys(cpt));
  const valueFromDeletedStates = roundValue(sumValues(pick(deletedStates, cpt)));

  return balanceCptValues(
    omit(
      deletedStates,
      addNewStatesInCptWithValue(valueFromDeletedStates, newStates, cpt),
    ),
  );
});

const updateStatesInCptObject = useWith(changeCptStates, [identity, propCpt]);

const updateStatesInCptArray = (nextStates, node) =>
  map(
    evolve({
      then: changeCptStates(nextStates),
    }),
    propCpt(node),
  );

export const updateStatesInCpt = ifElse(
  pipe(nthArg1, isNodeWithoutParents),
  updateStatesInCptObject,
  updateStatesInCptArray,
);

export const updateNodeParentIdInCpt = (nodeId, nextId, node) =>
  map(
    evolve({
      when: renamePropKey(nodeId, nextId),
    }),
    propCpt(node),
  );

const createWhensForNodeWithStates = (parentId, newStates, nodes, node) => {
  const nodeParents = map(id => findNodeById(id, nodes), node.parents);
  const parents = map(
    ({ id, states }) => ({
      id,
      states: equals(id, parentId) ? newStates : states,
    }),
    nodeParents,
  );

  return createNodeCombinations(parents);
};

const removeCptsForNodeWithStates = (parentId, deletedStates, node) =>
  reject(
    pipe(path(['when', parentId]), includesFlipped(deletedStates)),
    propCpt(node),
  );

const createCptsForNodeWithStates = (parentId, newStates, nodes, node) =>
  map(
    pipe(objOf('when'), assoc('then', createCpt(propStates(node)))),
    createWhensForNodeWithStates(parentId, newStates, nodes, node),
  );

export const updateNodeParentStatesInCpt = curry(
  (parentId, nextStates, nodes, node) => {
    const nodeParent = findNodeById(parentId, nodes);
    const { deletedStates, newStates } = getDiffStates(
      nextStates,
      propStates(nodeParent),
    );

    return concat(
      removeCptsForNodeWithStates(parentId, deletedStates, node),
      createCptsForNodeWithStates(parentId, newStates, nodes, node),
    );
  },
);
<|endoftext|>