<commit_before>

TimelineRow = (function()
{
	var row_template = "								\

						\

		\
			<canvas class='TimelineRowCanvas'></canvas>		\

";

	function TimelineRow(name, width, parent_node)
	{
		this.Name = name;

		// Create the row HTML and add to the parent
		this.ContainerNode = DOM.Node.CreateHTML(row_template);
		this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData");
		this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel");
		this.LabelNode.innerHTML = name;
		this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas");
		parent_node.appendChild(this.ContainerNode);

		// Setup the canvas
		this.Ctx = this.CanvasNode.getContext("2d");
		this.SetSize(width);
		this.Clear();

		// Frame index to start at when looking for first visible sample
		this.StartFrameIndex = 0;

		this.FrameHistory = null;
		this.VisibleFrames = [ ];

		// Sample the mouse is currently hovering over
		this.HoverSample = null;

		// Currently selected sample
		this.SelectedSample = null;
	}

	TimelineRow.prototype.SetSize = function(width)
	{
		// Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects.
		this.CanvasNode.width = width;
		this.CanvasNode.height = 18;
	}

	TimelineRow.prototype.Clear = function()
	{
		// Outer box is background colour, inner box shows the boundary between thread rows
		this.Ctx.fillStyle = "#2C2C2C"
		this.Ctx.fillRect(0, 0, this.CanvasNode.width, this.CanvasNode.height);
		this.Ctx.fillStyle = "#666"
		var b = 1;
		this.Ctx.fillRect(b, b * 2, this.CanvasNode.width - b * 3, this.CanvasNode.height - b);
	}

	TimelineRow.prototype.SetVisibleFrames = function(frame_history, time_range)
	{
		// Allow null frame history to use existing ones
		// Keep track of frame history for redraw events
		if (frame_history != null)
			this.FrameHistory = frame_history;
		if (this.FrameHistory == null)
			return;

		// Clear previous visible list
		this.VisibleFrames = [ ];
		if (this.FrameHistory.length == 0)
			return;

		// First do a back-track in case the time range moves negatively
		var start_frame_index = this.StartFrameIndex;
		while (start_frame_index > 0)
		{
			// The frame history can be reset outside this class
			// This also catches the overflow to the end of the frame list below when a thread stops sending samples
			if (start_frame_index < this.FrameHistory.length)
			{
				var frame = this.FrameHistory[start_frame_index];
				if (time_range.Start_us > frame.StartTime_us)
					break;
			}
			start_frame_index--;
		}

		// Then search from this point for the first visible frame
		while (start_frame_index < this.FrameHistory.length)
		{
			var frame = this.FrameHistory[start_frame_index];
			if (frame.EndTime_us > time_range.Start_us)
				break;
			start_frame_index++;
		}

		// Gather all frames up to the end point
		this.StartFrameIndex = start_frame_index;
		for (var i = start_frame_index; i < this.FrameHistory.length; i++)
		{
			var frame = this.FrameHistory[i];
			if (frame.StartTime_us > time_range.End_us)
				break;
			this.VisibleFrames.push(frame);
		}
	}

	TimelineRow.prototype.Draw = function(time_range)
	{
		this.Clear();

		// Draw all root samples in the visible frame set
		for (var i in this.VisibleFrames)
		{
			var frame = this.VisibleFrames[i];

			for (var j in frame.Samples)
				DrawSample(this, time_range, frame.Samples[j]);
		}
	}

	TimelineRow.prototype.UpdateHoverSample = function(mouse_state, time_range, x_offset)
	{
		var hover = GetSampleAtPosition(this, mouse_state, time_range, x_offset);
		if (hover)
			this.SetHoverSample(hover[1], time_range);
		return hover;
	}

	TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, time_range, x_offset)
	{
		var select = GetSampleAtPosition(this, mouse_state, time_range, x_offset);
		if (select)
			this.SetSelectedSample(select[1], time_range);
		return select;
	}

	TimelineRow.prototype.SetHoverSample = function(sample, time_range)
	{
		if (sample != this.HoverSample)
		{
			// Discard old highlight
			// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
			var old_sample = this.HoverSample;
			this.HoverSample = null;
			DrawSample(this, time_range, old_sample);

			// Add new highlight
			this.HoverSample = sample;
			DrawSample(this, time_range, sample);
		}
	}

	TimelineRow.prototype.SetSelectedSample = function(sample, time_range)
	{
		if (sample != this.SelectedSample)
		{
			// Discard old highlight
			// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
			var old_sample = this.SelectedSample;
			this.SelectedSample = null;
			DrawSample(this, time_range, old_sample);

			// Add new highlight
			this.SelectedSample = sample;
			DrawSample(this, time_range, sample);
		}
	}

	function GetSampleAtPosition(self, mouse_state, time_range, x_offset)
	{
		// Get the time the mouse is over
		var x = mouse_state.Position[0] - x_offset;
		var time_us = time_range.Start_us + x / time_range.usPerPixel;

		// Search for the first frame to intersect this time
		for (var i in self.VisibleFrames)
		{
			var frame = self.VisibleFrames[i];
			if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us)
			{
				// Search for the sample that intersects this time
				for (var j in frame.Samples)
				{
					var sample = frame.Samples[j];
					if (time_us >= sample.cpu_us_start && time_us < sample.cpu_us_start + sample.cpu_us_length)
						return [ frame, sample ];
				}
			}
		}

		return null;
	}

	function DrawSample(self, time_range, sample)
	{
		if (sample == null)
			return;

		// Determine location of the sample
		var offset_x = time_range.PixelOffset(sample.cpu_us_start);
		var size_x = time_range.PixelSize(sample.cpu_us_length);

		// Clip to padded range
		size_x = Math.min(offset_x + size_x, self.CanvasNode.width - 5) - offset_x;
		offset_x = Math.max(offset_x, 4);

		var offset_y = 5;
		var size_y = 10;

		// Normal rendering
		var ctx = self.Ctx;
		ctx.fillStyle = "#BBB";
		ctx.fillRect(offset_x, offset_y, size_x, size_y);

		var b = (sample == self.HoverSample) ? 255 : 0;
		var r = (sample == self.SelectedSample) ? 255 : 0;

		// Highlight rendering
		if (b + r > 0)
		{
			ctx.beginPath();
			ctx.rect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
			ctx.lineWidth = 1;
			ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")";
			ctx.stroke();
		}
	}

	return TimelineRow;
})();
<commit_msg>Move clamp out of frame search inner loop.<commit_after>

TimelineRow = (function()
{
	var row_template = "								\

						\

		\
			<canvas class='TimelineRowCanvas'></canvas>		\

";

	function TimelineRow(name, width, parent_node)
	{
		this.Name = name;

		// Create the row HTML and add to the parent
		this.ContainerNode = DOM.Node.CreateHTML(row_template);
		this.Node = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowData");
		this.LabelNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowLabel");
		this.LabelNode.innerHTML = name;
		this.CanvasNode = DOM.Node.FindWithClass(this.ContainerNode, "TimelineRowCanvas");
		parent_node.appendChild(this.ContainerNode);

		// Setup the canvas
		this.Ctx = this.CanvasNode.getContext("2d");
		this.SetSize(width);
		this.Clear();

		// Frame index to start at when looking for first visible sample
		this.StartFrameIndex = 0;

		this.FrameHistory = null;
		this.VisibleFrames = [ ];

		// Sample the mouse is currently hovering over
		this.HoverSample = null;

		// Currently selected sample
		this.SelectedSample = null;
	}

	TimelineRow.prototype.SetSize = function(width)
	{
		// Must ALWAYS set the width/height properties together. Setting one on its own has weird side-effects.
		this.CanvasNode.width = width;
		this.CanvasNode.height = 18;
	}

	TimelineRow.prototype.Clear = function()
	{
		// Outer box is background colour, inner box shows the boundary between thread rows
		this.Ctx.fillStyle = "#2C2C2C"
		this.Ctx.fillRect(0, 0, this.CanvasNode.width, this.CanvasNode.height);
		this.Ctx.fillStyle = "#666"
		var b = 1;
		this.Ctx.fillRect(b, b * 2, this.CanvasNode.width - b * 3, this.CanvasNode.height - b);
	}

	TimelineRow.prototype.SetVisibleFrames = function(frame_history, time_range)
	{
		// Allow null frame history to use existing ones
		// Keep track of frame history for redraw events
		if (frame_history != null)
			this.FrameHistory = frame_history;
		if (this.FrameHistory == null)
			return;

		// Clear previous visible list
		this.VisibleFrames = [ ];
		if (this.FrameHistory.length == 0)
			return;

		// The frame history can be reset outside this class
		// This also catches the overflow to the end of the frame list below when a thread stops sending samples
		var max_frame = Math.max(this.FrameHistory.length - 1, 0);
		var start_frame_index = Math.min(this.StartFrameIndex, max_frame);

		// First do a back-track in case the time range moves negatively
		while (start_frame_index > 0)
		{
			var frame = this.FrameHistory[start_frame_index];
			if (time_range.Start_us > frame.StartTime_us)
				break;
			start_frame_index--;
		}

		// Then search from this point for the first visible frame
		while (start_frame_index < this.FrameHistory.length)
		{
			var frame = this.FrameHistory[start_frame_index];
			if (frame.EndTime_us > time_range.Start_us)
				break;
			start_frame_index++;
		}

		// Gather all frames up to the end point
		this.StartFrameIndex = start_frame_index;
		for (var i = start_frame_index; i < this.FrameHistory.length; i++)
		{
			var frame = this.FrameHistory[i];
			if (frame.StartTime_us > time_range.End_us)
				break;
			this.VisibleFrames.push(frame);
		}
	}

	TimelineRow.prototype.Draw = function(time_range)
	{
		this.Clear();

		// Draw all root samples in the visible frame set
		for (var i in this.VisibleFrames)
		{
			var frame = this.VisibleFrames[i];

			for (var j in frame.Samples)
				DrawSample(this, time_range, frame.Samples[j]);
		}
	}

	TimelineRow.prototype.UpdateHoverSample = function(mouse_state, time_range, x_offset)
	{
		var hover = GetSampleAtPosition(this, mouse_state, time_range, x_offset);
		if (hover)
			this.SetHoverSample(hover[1], time_range);
		return hover;
	}

	TimelineRow.prototype.UpdateSelectedSample = function(mouse_state, time_range, x_offset)
	{
		var select = GetSampleAtPosition(this, mouse_state, time_range, x_offset);
		if (select)
			this.SetSelectedSample(select[1], time_range);
		return select;
	}

	TimelineRow.prototype.SetHoverSample = function(sample, time_range)
	{
		if (sample != this.HoverSample)
		{
			// Discard old highlight
			// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
			var old_sample = this.HoverSample;
			this.HoverSample = null;
			DrawSample(this, time_range, old_sample);

			// Add new highlight
			this.HoverSample = sample;
			DrawSample(this, time_range, sample);
		}
	}

	TimelineRow.prototype.SetSelectedSample = function(sample, time_range)
	{
		if (sample != this.SelectedSample)
		{
			// Discard old highlight
			// TODO: When zoomed right out, tiny samples are anti-aliased and this becomes inaccurate
			var old_sample = this.SelectedSample;
			this.SelectedSample = null;
			DrawSample(this, time_range, old_sample);

			// Add new highlight
			this.SelectedSample = sample;
			DrawSample(this, time_range, sample);
		}
	}

	function GetSampleAtPosition(self, mouse_state, time_range, x_offset)
	{
		// Get the time the mouse is over
		var x = mouse_state.Position[0] - x_offset;
		var time_us = time_range.Start_us + x / time_range.usPerPixel;

		// Search for the first frame to intersect this time
		for (var i in self.VisibleFrames)
		{
			var frame = self.VisibleFrames[i];
			if (time_us >= frame.StartTime_us && time_us < frame.EndTime_us)
			{
				// Search for the sample that intersects this time
				for (var j in frame.Samples)
				{
					var sample = frame.Samples[j];
					if (time_us >= sample.cpu_us_start && time_us < sample.cpu_us_start + sample.cpu_us_length)
						return [ frame, sample ];
				}
			}
		}

		return null;
	}

	function DrawSample(self, time_range, sample)
	{
		if (sample == null)
			return;

		// Determine location of the sample
		var offset_x = time_range.PixelOffset(sample.cpu_us_start);
		var size_x = time_range.PixelSize(sample.cpu_us_length);

		// Clip to padded range
		size_x = Math.min(offset_x + size_x, self.CanvasNode.width - 5) - offset_x;
		offset_x = Math.max(offset_x, 4);

		var offset_y = 5;
		var size_y = 10;

		// Normal rendering
		var ctx = self.Ctx;
		ctx.fillStyle = "#BBB";
		ctx.fillRect(offset_x, offset_y, size_x, size_y);

		var b = (sample == self.HoverSample) ? 255 : 0;
		var r = (sample == self.SelectedSample) ? 255 : 0;

		// Highlight rendering
		if (b + r > 0)
		{
			ctx.beginPath();
			ctx.rect(offset_x + 0.5, offset_y + 0.5, size_x - 1, size_y - 1);
			ctx.lineWidth = 1;
			ctx.strokeStyle = "rgb(" + r + ", 0, " + b + ")";
			ctx.stroke();
		}
	}

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


<commit_before>import React, { useEffect, useRef, useCallback } from "react";

const prevent = (e) => e.preventDefault();

export const GestureDiv = ({ onMove, onClick, onWheel, children }) => {
  const elementRef = useRef();
  const state = useRef({
    prevMousePos: [0, 0],
    mouseDown: false,
    dragStarted: false,
  }).current;

  useEffect(() => {
    if (!elementRef.current) {
      return;
    }
    elementRef.current.addEventListener("selectstart", prevent);
    return () => {
      elementRef.current.removeEventListener("selectstart", prevent);
    };
  }, [elementRef.current]);

  const $onMouseDown = useCallback((event) => {
    Object.assign(state, {
      mouseDown: true,
      dragStarted: false,
      prevMousePos: [event.pageX, event.pageY],
    });
  }, []);

  const $onMouseUp = useCallback(() => {
    state.mouseDown = false;
  }, []);

  const $onMouseMove = useCallback(
    (event) => {
      if (!state.mouseDown) {
        return;
      }
      const x = event.pageX;
      const y = event.pageY;
      const dx = x - state.prevMousePos[0];
      const dy = y - state.prevMousePos[1];

      // Mousemove can occur during a legitimate click too.
      // To account for that we let some limited mousemove
      // before considering the gesture as a dragging.

      // If this is a dragging, call the onMove handler
      // and update our pixel tracking.
      if (state.dragStarted) {
        onMove({ dx, dy });
        state.prevMousePos = [x, y];
      }
      // If the "gesture" is not yet qualified as dragging,
      // see if it already qualifies by looking if the mouse
      // has travalled far enough.
      else {
        if (Math.abs(dx) >= 5 || Math.abs(dy) >= 5) {
          state.dragStarted = true;
        }
      }
    },
    [onMove]
  );

  const $onClick = useCallback(
    (event) => {
      if (state.dragStarted) {
        return;
      }
      onClick(event);
    },
    [onClick]
  );

  return (
    <div
      ref={elementRef}
      onMouseDown={$onMouseDown}
      onMouseUp={$onMouseUp}
      onMouseMove={$onMouseMove}
      onMouseLeave={$onMouseUp}
      onClick={$onClick}
      onDragStart={prevent}
      onWheel={onWheel}
      style={{ outline: "thin solid red" }}
    >
      {children}
    </div>
  );
};
<commit_msg>Use pointer events<commit_after>import React, { useEffect, useRef, useCallback } from "react";

const prevent = (e) => e.preventDefault();

export const GestureDiv = ({ onMove, onClick, onWheel, children }) => {
  const elementRef = useRef();
  const state = useRef({
    prevMousePos: [0, 0],
    mouseDown: false,
    dragStarted: false,
  }).current;

  useEffect(() => {
    if (!elementRef.current) {
      return;
    }
    elementRef.current.addEventListener("selectstart", prevent);
    return () => {
      elementRef.current.removeEventListener("selectstart", prevent);
    };
  }, [elementRef.current]);

  const $onMouseDown = useCallback((event) => {
    Object.assign(state, {
      mouseDown: true,
      dragStarted: false,
      prevMousePos: [event.pageX, event.pageY],
    });
  }, []);

  const $onMouseUp = useCallback(() => {
    state.mouseDown = false;
  }, []);

  const $onMouseMove = useCallback(
    (event) => {
      if (!state.mouseDown) {
        return;
      }
      const x = event.pageX;
      const y = event.pageY;
      const dx = x - state.prevMousePos[0];
      const dy = y - state.prevMousePos[1];

      // Mousemove can occur during a legitimate click too.
      // To account for that we let some limited mousemove
      // before considering the gesture as a dragging.

      // If this is a dragging, call the onMove handler
      // and update our pixel tracking.
      if (state.dragStarted) {
        onMove({ dx, dy });
        state.prevMousePos = [x, y];
      }
      // If the "gesture" is not yet qualified as dragging,
      // see if it already qualifies by looking if the mouse
      // has travalled far enough.
      else {
        if (Math.abs(dx) >= 5 || Math.abs(dy) >= 5) {
          state.dragStarted = true;
        }
      }
    },
    [onMove]
  );

  const $onClick = useCallback(
    (event) => {
      if (state.dragStarted) {
        return;
      }
      onClick(event);
    },
    [onClick]
  );

  return (
    <div
      ref={elementRef}
      onPointerDown={$onMouseDown}
      onPointerUp={$onMouseUp}
      onPointerMove={$onMouseMove}
      onPointerLeave={$onMouseUp}
      onClick={$onClick}
      onDragStart={prevent}
      onWheel={onWheel}
      style={{ outline: "thin solid red" }}
    >
      {children}
    </div>
  );
};
<|endoftext|>


<commit_before>angular.module('genome.pool', [])
.controller('PoolController', function($scope, d3Service, Relatives, $rootScope, $window, $location) {

  //Containers for Relatives' Data
  var circle;
  $scope.relatives = [];
  $rootScope.rels = [];
  $scope.circles = [];

  var boardHeight = $window.innerHeight;
  var boardWidth = $window.innerWidth;
  var relativesList = [];

  var createMap = function () {
    var map = new Datamap({
        element: document.getElementById('mainCanvas'),
        scope: 'world',
        geographyConfig: {
            popupOnHover: false,
            highlightOnHover: false
        },
        fills: {
            defaultFill: '#34495e'
        }
    });

    //create bubbles for each relative in the relative list, after parsing with makeNewBubbleData
    map.bubbles(relativesList, {
      popupTemplate: function (geo, data) {
          return ['
' +  data.name,
          '
Relationship: ' +  data.relationship,
          '
Similarity: ' +  data.similarity,
          '
Residence: ' +  data.residence + '',
          '
'].join('');
      }
    });
  };

  var makeNewBubbleData = function() {
    var geoInfo = {
      'United States': [36.5, -101.25, 'USA'],
      'Canada': [54.51, -100.1953, 'CAN'],
      'South America': [-11.2, -56.25, 'SAM'],
      'Europe': [48.4419, 19.07, 'EUR'],
      'India': [21.348, 78.31, 'IND'],
      'Russia': [61.17, 90.000, 'RUS'],
      'Asia': [36.15, 105.468, 'ASN'],
      'Australia': [-25.05, 134, 'AUS'],
      'Africa': [7.612, 18.6328, 'AFR']
    };

    var fills = {
        'USA': '#e74c3c',
        'RUS': '#2ecc71',
        'EUR': '#FFFC00',
        'CAN': '#3498db',
        'ASN': '#1abc9c',
        'SAM': '#FFF59D',
        'AUS': '#e67e22',
        'AFR': '#d35400'
    }

    for(var i = 0; i < $scope.relatives.length; i++) {
      for (var places in geoInfo) {
        if ($scope.relatives[i].birthplace === places) {
          relativesList.push({
            name: $scope.relatives[i].first_name + ' ' + $scope.relatives[i].last_name,
            country: $scope.relatives[i].birthplace,
            relationship: $scope.relatives[i].relationship,
            residence: $scope.relatives[i].residence,
            similarity: $scope.relatives[i].similarity,
            latitude: (geoInfo[places][0] + (Math.floor(Math.random() * 10)+1)),
            longitude: (geoInfo[places][1] + (Math.floor(Math.random() * 10)+1)),
            borderColor: fills[geoInfo[places][2]],
            borderWidth: 4,
            fills: fills[geoInfo[places][2]],
            radius: 7,
            fillOpacity: 1,
            popupOnHover: true
          })
        }
      }
    }
  };

  var moveBubblesToRegions = function() {
    d3.selectAll("circle").data($scope.circles).attr('r', function(d){return d.radius;});
  }

    //Move Bubbles Back to Center of Page
   var replaceBubblesInCenter = function() {
      $scope.circles.forEach(function(bubble){
        bubble['cx'] = bubble['oldCX'];
        bubble['cy'] = bubble['oldCY'];
        bubble['radius'] = bubble['oldRadius'];
      })
      //Explicitly restate the force layout
      var nodes = $scope.circles;
      var force = d3.layout.force()
      .nodes(nodes)
      .size([width, height])
      .gravity(0)
      .charge(0)
      .on("tick", tick)
      .start();
      d3.selectAll("circle").data($scope.circles).attr('r', function(d){return d.radius;}).call(force.drag);
  };

  //Toggle Side Nav Icons
  var whichView = function() {
    $rootScope.view = $location.$$path;
  }
  whichView();
  //End Toggle Side Nav Icons
  //Toggle Map
  var mapShowing = false;

  var toggleMap = function() {
    if(!mapShowing) {
      d3.selectAll("circle").attr("visibility", "hidden");
      createMap();
      mapShowing = true;
    } else {
      initialize();
      $rootScope.removeMap();
      mapShowing = false;
    }
  }

  $rootScope.filterRegions = function() {
    toggleMap();
  };

  $rootScope.removeMap = function () {
    d3.select("svg.datamap").remove();
  };
  //End Map Toggle

  //Pop Modal
  $scope.popModal = {
    name: '',
    similarity: '',
    image: '',
    relationship: '',
    age: 0
  };

  //pop up message displaying relative data when user clicks on a bubble
  var showRelative = function(bubble) {
    $scope.popModal.name = bubble.relative.first_name + ' ' + bubble.relative.last_name;
    $scope.popModal.similarity = (bubble.relative.similarity*100).toFixed(2) + "% of your DNA";
    $scope.popModal.relationship = bubble.relative.relationship  || "Unknown";
    $scope.popModal.age = bubble.relative.birth_year ? (new Date().getFullYear() - bubble.relative.birth_year) : "Unknown";
    $scope.popModal.image = bubble.relative.picture_url || '../../../static/assets/hipDNA.png';
    $scope.popModal.ancestry = bubble.relative.ancestry || "Unknown";
    $scope.popModal.birthplace = bubble.relative.birthplace  || "Unknown";
  };
  //End Pop Modal

  //Force and Bubble Layout Settings
  var width = $window.innerWidth //- margin.left - margin.right;
  var height = $window.innerHeight //- margin.top - margin.bottom;
  var padding = 5;
  var radius = d3.scale.sqrt().range([0, 12]);
  var colorScheme = ['#1abc9c', '#2ecc71', '#f1c40f', '#27ae60', '#3498db', '#9b59b6', '#2980b9','#8e44ad','#e67e22','#d35400','#e74c3c', '#c0392b', '#bdc3c7', '#f39c12', '#95a5a6'];
  //End force and bubble layout settings

  //Grab the pool as a canvas for our bubbles
  var svg = d3.select('.pool').append("svg")
    .attr("fill", "transparent")
    .attr("width", $window.innerWidth)
    .attr("height", $window.innerHeight)
    .attr("id", "mainCanvas")
    .append("g")
    //.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  //Create Initial bubbles
  var createBubbles = function(circleData) {
    nodes = $scope.circles;
    //Add d3 force effect to layout
    var force = d3.layout.force()
      .nodes(nodes)
      .size([width, height])
      .gravity(0)
      .charge(0)
      .on("tick", tick)
      .start();
    //Add bubbles to DOM
    circle = svg.selectAll("circle")
      .data(nodes)
      .enter().append("circle")
      .on('mouseover', function(d){

        // Get this bubble's x/y values, then augment for the tooltip
        // This will allow the tool tip to be centered in the middle of the bubble
        var xPosition = parseFloat(d3.select(this).attr("cx"));
        var yPosition = parseFloat(d3.select(this).attr("cy"));

        var similarity = d3.select(this)[0][0].__data__.relative.similarity * 100;
        svg.append("text")
          .attr("id", "tooltip")
          .attr("x", xPosition)
          .attr("y", yPosition)
          .attr("text-anchor", "middle")
          .attr("font-family", "sans-serif")
          .attr("font-size", "13px")
          .attr("font-weight", "bold")
          .attr("fill", "black")
          .attr("pointer-events", "none")
          .text(similarity.toFixed(2) + "%");

        d3.select(this)
          .attr("opacity", ".7");

      })
      .on('mouseout', function(){
        d3.select('#tooltip').remove();
        d3.select(this)
          .attr("opacity", "1");
      })
      .on('click', function(bubble) {
        $scope.$apply(
          showRelative(bubble)
        );
      })
      .attr("data-target", "#myModal")
      .attr("data-toggle", "modal")
      .attr("r", function (d) {
          return d.radius;
      })
      .style('fill', function(d) {
         return d.color;
       })
      .call(force.drag);
  };
  //End initial bubble creation

  //Control bubble entry onto DOM and magnetic resistance to each other
  function tick(e) {
    circle.each(gravity(0.8 * e.alpha))
    .each(collide(0.5))
    .attr("cx", function (d) {
      return d.x;
    })
    .attr("cy", function (d) {
      return d.y;
    });
  }

  // Move nodes toward cluster focus.
  function gravity(alpha) {
    return function (d) {
      d.y += (d.cy - d.y) * alpha;
      d.x += (d.cx - d.x) * alpha;
    };
  }

  // Resolve collisions between nodes.
  function collide(alpha) {
    var quadtree = d3.geom.quadtree(nodes);
    return function (d) {
      var r = d.radius + radius.domain()[1] + padding,
        nx1 = d.x - r,
        nx2 = d.x + r,
        ny1 = d.y - r,
        ny2 = d.y + r;
      quadtree.visit(function (quad, x1, y1, x2, y2) {
        if (quad.point && (quad.point !== d)) {
          var x = d.x - quad.point.x,
            y = d.y - quad.point.y,
            l = Math.sqrt(x * x + y * y),
            r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
          if (l < r) {
            l = (l - r) / l * alpha;
            d.x -= x *= l;
            d.y -= y *= l;
            quad.point.x += x;
            quad.point.y += y;
          }
        }
        return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
      });
    };
  }

  //After grabbing relatives from the DB, create a bubbles array based on length of relatives array
  var initialize = function() {
    //set up range of values in similarity between all relatives
    var range = [];
    $scope.relatives.map(function (relative) {
      range.push(relative.similarity);
    }).sort(function (a, b) {
      return b - a;
    });

    for (var i = 0; i < $scope.relatives.length || 0; i++) {
      var similarity = $scope.relatives[i].similarity;
      var similarRange = (range[0] - range[range.length-1]);

      if (similarRange > 0 && similarRange < 0.2) {
        if (similarity <= 0.01){
          similarity = 0.03;
        } else if (similarity > 0.01 && similarity < 0.012) {
          similarity = 0.035;
        } else if (similarity >= 0.012 && similarity < 0.015) {
          similarity = 0.04;
        } else if (similarity >= 0.015 && similarity < 0.02) {
          similarity = 0.05;
        } else if (similarity >= 0.02 && similarity < 0.025) {
          similarity = 0.055;
        } else if (similarity >= 0.025 && similarity < 0.04) {
          similarity = 0.06;
        } else if (similarity >= 0.04 && similarity < 0.049) {
          similarity = 0.065;
        } else if (similarity >= 0.05) {
          similarity = 0.07;
        }
      }
      if (similarRange >= 0.2 && similarRange < 0.5) {
        if (similarity <= 0.01){
          similarity = 0.02;
        } else if (similarity > 0.01 && similarity < 0.015) {
          similarity = 0.025;
        } else if (similarity >= 0.015 && similarity < 0.02) {
          similarity = 0.03;
        } else if (similarity >= 0.02 && similarity < 0.025) {
          similarity = 0.033;
        } else if (similarity >= 0.025 && similarity < 0.035) {
          similarity = 0.037;
        } else if (similarity >= 0.035 && similarity < 0.05) {
          similarity = 0.041;
        } else if (similarity >= 0.05 && similarity < 0.065) {
          similarity = 0.045;
        } else if (similarity >= 0.065 && similarity < 0.08) {
          similarity = 0.05;
        } else if (similarity >= 0.08 && similarity < 0.1) {
          similarity = 0.055;
        } else if (similarity >= 0.1 && similarity < 0.15) {
          similarity = 0.06;
        } else if (similarity >= 0.15 && similarity < 0.2) {
          similarity = 0.065;
        } else if (similarity >= 0.2) {
          similarity = 0.07;
        }
      }
      //Set initial bubble properties
      $scope.circles.push({
        cx: $window.innerWidth/3.5,
        cy: $window.innerHeight/2,
        color: colorScheme[Math.floor(Math.random() * colorScheme.length)],
        radius: similarity * 1000,
        relative: $scope.relatives[i]
      });
    }
    createBubbles($scope.circles);
  };

 //Grab relatives from the database, then initialize bubbles
  $scope.getRelatives = function() {
    Relatives.getRelatives()
    //Can refactor to return the promise values within the relatives factory if so desired
    .then(function(relatives) {
      //Refactor? potentially redundant addition of relatives to $scope and $rootScope.
      $scope.relatives = relatives.data.relativeList;
      //Add relatives to rootScope to allow access within other controllers
      $rootScope.rels = relatives.data.relativeList;
      initialize();
      makeNewBubbleData();
    }, function(err) {
      console.error('Error retrieving relatives: ', err);
    });
  };
  //Initialize the page with a call to getRelatives
  $scope.getRelatives();

  $rootScope.IntroOptions = {
      steps:[{
          // element: document.querySelector('#test3'),
          intro: "Welcome to the Family Pool page. Each bubble represents a relative and is sized based on percentage of shared DNA. Click on the bubbles for more information."
        },
        {
          element: document.querySelector('#path2'),
          intro: "Access the map view and the help menu from over here.",
          position: 'right'
        }
      ],
      showStepNumbers: false,
      exitOnOverlayClick: true,
      exitOnEsc:true,
      nextLabel: 'Next',
      prevLabel: 'Previous',
      skipLabel: 'Exit',
      doneLabel: 'Thanks'
  };
});
<commit_msg>Fixed styling<commit_after>angular.module('genome.pool', [])
.controller('PoolController', function($scope, d3Service, Relatives, $rootScope, $window, $location) {

  //Containers for Relatives' Data
  var circle;
  $scope.relatives = [];
  $rootScope.rels = [];
  $scope.circles = [];

  var boardHeight = $window.innerHeight;
  var boardWidth = $window.innerWidth;
  var relativesList = [];

  var createMap = function () {
    var map = new Datamap({
        element: document.getElementById('mainCanvas'),
        scope: 'world',
        geographyConfig: {
            popupOnHover: false,
            highlightOnHover: false
        },
        fills: {
            defaultFill: '#34495e'
        }
    });

    //create bubbles for each relative in the relative list, after parsing with makeNewBubbleData
    map.bubbles(relativesList, {
      popupTemplate: function (geo, data) {
          return ['
' +  data.name,
          '
Relationship: ' +  data.relationship,
          '
Similarity: ' +  data.similarity,
          '
Residence: ' +  data.residence + '',
          '
'].join('');
      }
    });
  };

  var makeNewBubbleData = function() {
    var geoInfo = {
      'United States': [36.5, -101.25, 'USA'],
      'Canada': [54.51, -100.1953, 'CAN'],
      'South America': [-11.2, -56.25, 'SAM'],
      'Europe': [48.4419, 19.07, 'EUR'],
      'India': [21.348, 78.31, 'IND'],
      'Russia': [61.17, 90.000, 'RUS'],
      'Asia': [36.15, 105.468, 'ASN'],
      'Australia': [-25.05, 134, 'AUS'],
      'Africa': [7.612, 18.6328, 'AFR']
    };

    var fills = {
        'USA': '#e74c3c',
        'RUS': '#2ecc71',
        'EUR': '#FFFC00',
        'CAN': '#3498db',
        'ASN': '#1abc9c',
        'SAM': '#FFF59D',
        'AUS': '#e67e22',
        'AFR': '#d35400'
    }

    for(var i = 0; i < $scope.relatives.length; i++) {
      for (var places in geoInfo) {
        if ($scope.relatives[i].birthplace === places) {
          relativesList.push({
            name: $scope.relatives[i].first_name + ' ' + $scope.relatives[i].last_name,
            country: $scope.relatives[i].birthplace,
            relationship: $scope.relatives[i].relationship,
            residence: $scope.relatives[i].residence,
            similarity: $scope.relatives[i].similarity,
            latitude: (geoInfo[places][0] + (Math.floor(Math.random() * 10)+1)),
            longitude: (geoInfo[places][1] + (Math.floor(Math.random() * 10)+1)),
            borderColor: fills[geoInfo[places][2]],
            borderWidth: 4,
            fills: fills[geoInfo[places][2]],
            radius: 7,
            fillOpacity: 1,
            popupOnHover: true
          })
        }
      }
    }
  };

  var moveBubblesToRegions = function() {
    d3.selectAll("circle").data($scope.circles).attr('r', function(d){return d.radius;});
  }

    //Move Bubbles Back to Center of Page
   var replaceBubblesInCenter = function() {
      $scope.circles.forEach(function(bubble){
        bubble['cx'] = bubble['oldCX'];
        bubble['cy'] = bubble['oldCY'];
        bubble['radius'] = bubble['oldRadius'];
      })
      //Explicitly restate the force layout
      var nodes = $scope.circles;
      var force = d3.layout.force()
      .nodes(nodes)
      .size([width, height])
      .gravity(0)
      .charge(0)
      .on("tick", tick)
      .start();
      d3.selectAll("circle").data($scope.circles).attr('r', function(d){return d.radius;}).call(force.drag);
  };

  //Toggle Side Nav Icons
  var whichView = function() {
    $rootScope.view = $location.$$path;
  }
  whichView();
  //End Toggle Side Nav Icons
  //Toggle Map
  var mapShowing = false;

  var toggleMap = function() {
    if(!mapShowing) {
      d3.selectAll("circle").attr("visibility", "hidden");
      createMap();
      mapShowing = true;
    } else {
      initialize();
      $rootScope.removeMap();
      mapShowing = false;
    }
  };

  $rootScope.filterRegions = function() {
    toggleMap();
  };

  $rootScope.removeMap = function () {
    d3.select("svg.datamap").remove();
  };
  //End Map Toggle

  //Pop Modal
  $scope.popModal = {
    name: '',
    similarity: '',
    image: '',
    relationship: '',
    age: 0
  };

  //pop up message displaying relative data when user clicks on a bubble
  var showRelative = function(bubble) {
    $scope.popModal.name = bubble.relative.first_name + ' ' + bubble.relative.last_name;
    $scope.popModal.similarity = (bubble.relative.similarity*100).toFixed(2) + "% of your DNA";
    $scope.popModal.relationship = bubble.relative.relationship  || "Unknown";
    $scope.popModal.age = bubble.relative.birth_year ? (new Date().getFullYear() - bubble.relative.birth_year) : "Unknown";
    $scope.popModal.image = bubble.relative.picture_url || '../../../static/assets/hipDNA.png';
    $scope.popModal.ancestry = bubble.relative.ancestry || "Unknown";
    $scope.popModal.birthplace = bubble.relative.birthplace  || "Unknown";
  };
  //End Pop Modal

  //Force and Bubble Layout Settings
  var width = $window.innerWidth //- margin.left - margin.right;
  var height = $window.innerHeight //- margin.top - margin.bottom;
  var padding = 5;
  var radius = d3.scale.sqrt().range([0, 12]);
  var colorScheme = ['#1abc9c', '#2ecc71', '#f1c40f', '#27ae60', '#3498db', '#9b59b6', '#2980b9','#8e44ad','#e67e22','#d35400','#e74c3c', '#c0392b', '#bdc3c7', '#f39c12', '#95a5a6'];
  //End force and bubble layout settings

  //Grab the pool as a canvas for our bubbles
  var svg = d3.select('.pool').append("svg")
    .attr("fill", "transparent")
    .attr("width", $window.innerWidth)
    .attr("height", $window.innerHeight)
    .attr("id", "mainCanvas")
    .append("g")
    //.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  //Create Initial bubbles
  var createBubbles = function(circleData) {
    nodes = $scope.circles;
    //Add d3 force effect to layout
    var force = d3.layout.force()
      .nodes(nodes)
      .size([width, height])
      .gravity(0)
      .charge(0)
      .on("tick", tick)
      .start();
    //Add bubbles to DOM
    circle = svg.selectAll("circle")
      .data(nodes)
      .enter().append("circle")
      .on('mouseover', function(d){

        // Get this bubble's x/y values, then augment for the tooltip
        // This will allow the tool tip to be centered in the middle of the bubble
        var xPosition = parseFloat(d3.select(this).attr("cx"));
        var yPosition = parseFloat(d3.select(this).attr("cy"));

        var similarity = d3.select(this)[0][0].__data__.relative.similarity * 100;
        svg.append("text")
          .attr("id", "tooltip")
          .attr("x", xPosition)
          .attr("y", yPosition)
          .attr("text-anchor", "middle")
          .attr("font-family", "sans-serif")
          .attr("font-size", "13px")
          .attr("font-weight", "bold")
          .attr("fill", "black")
          .attr("pointer-events", "none")
          .text(similarity.toFixed(2) + "%");

        d3.select(this)
          .attr("opacity", ".7");

      })
      .on('mouseout', function(){
        d3.select('#tooltip').remove();
        d3.select(this)
          .attr("opacity", "1");
      })
      .on('click', function(bubble) {
        $scope.$apply(
          showRelative(bubble)
        );
      })
      .attr("data-target", "#myModal")
      .attr("data-toggle", "modal")
      .attr("r", function (d) {
          return d.radius;
      })
      .style('fill', function(d) {
         return d.color;
       })
      .call(force.drag);
  };
  //End initial bubble creation

  //Control bubble entry onto DOM and magnetic resistance to each other
  function tick(e) {
    circle.each(gravity(0.8 * e.alpha))
    .each(collide(0.5))
    .attr("cx", function (d) {
      return d.x;
    })
    .attr("cy", function (d) {
      return d.y;
    });
  }

  // Move nodes toward cluster focus.
  function gravity(alpha) {
    return function (d) {
      d.y += (d.cy - d.y) * alpha;
      d.x += (d.cx - d.x) * alpha;
    };
  }

  // Resolve collisions between nodes.
  function collide(alpha) {
    var quadtree = d3.geom.quadtree(nodes);
    return function (d) {
      var r = d.radius + radius.domain()[1] + padding,
        nx1 = d.x - r,
        nx2 = d.x + r,
        ny1 = d.y - r,
        ny2 = d.y + r;
      quadtree.visit(function (quad, x1, y1, x2, y2) {
        if (quad.point && (quad.point !== d)) {
          var x = d.x - quad.point.x,
            y = d.y - quad.point.y,
            l = Math.sqrt(x * x + y * y),
            r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
          if (l < r) {
            l = (l - r) / l * alpha;
            d.x -= x *= l;
            d.y -= y *= l;
            quad.point.x += x;
            quad.point.y += y;
          }
        }
        return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
      });
    };
  }

  //After grabbing relatives from the DB, create a bubbles array based on length of relatives array
  var initialize = function() {
    //set up range of values in similarity between all relatives
    var range = [];
    $scope.relatives.map(function (relative) {
      range.push(relative.similarity);
    }).sort(function (a, b) {
      return b - a;
    });

    for (var i = 0; i < $scope.relatives.length || 0; i++) {
      var similarity = $scope.relatives[i].similarity;
      var similarRange = (range[0] - range[range.length-1]);

      if (similarRange > 0 && similarRange < 0.2) {
        if (similarity <= 0.01){
          similarity = 0.03;
        } else if (similarity > 0.01 && similarity < 0.012) {
          similarity = 0.035;
        } else if (similarity >= 0.012 && similarity < 0.015) {
          similarity = 0.04;
        } else if (similarity >= 0.015 && similarity < 0.02) {
          similarity = 0.05;
        } else if (similarity >= 0.02 && similarity < 0.025) {
          similarity = 0.055;
        } else if (similarity >= 0.025 && similarity < 0.04) {
          similarity = 0.06;
        } else if (similarity >= 0.04 && similarity < 0.049) {
          similarity = 0.065;
        } else if (similarity >= 0.05) {
          similarity = 0.07;
        }
      }
      if (similarRange >= 0.2 && similarRange < 0.5) {
        if (similarity <= 0.01){
          similarity = 0.02;
        } else if (similarity > 0.01 && similarity < 0.015) {
          similarity = 0.025;
        } else if (similarity >= 0.015 && similarity < 0.02) {
          similarity = 0.03;
        } else if (similarity >= 0.02 && similarity < 0.025) {
          similarity = 0.033;
        } else if (similarity >= 0.025 && similarity < 0.035) {
          similarity = 0.037;
        } else if (similarity >= 0.035 && similarity < 0.05) {
          similarity = 0.041;
        } else if (similarity >= 0.05 && similarity < 0.065) {
          similarity = 0.045;
        } else if (similarity >= 0.065 && similarity < 0.08) {
          similarity = 0.05;
        } else if (similarity >= 0.08 && similarity < 0.1) {
          similarity = 0.055;
        } else if (similarity >= 0.1 && similarity < 0.15) {
          similarity = 0.06;
        } else if (similarity >= 0.15 && similarity < 0.2) {
          similarity = 0.065;
        } else if (similarity >= 0.2) {
          similarity = 0.07;
        }
      }
      //Set initial bubble properties
      $scope.circles.push({
        cx: $window.innerWidth/3.5,
        cy: $window.innerHeight/2,
        color: colorScheme[Math.floor(Math.random() * colorScheme.length)],
        radius: similarity * 1000,
        relative: $scope.relatives[i]
      });
    }
    createBubbles($scope.circles);
  };

 //Grab relatives from the database, then initialize bubbles
  $scope.getRelatives = function() {
    Relatives.getRelatives()
    //Can refactor to return the promise values within the relatives factory if so desired
    .then(function(relatives) {
      //Refactor? potentially redundant addition of relatives to $scope and $rootScope.
      $scope.relatives = relatives.data.relativeList;
      //Add relatives to rootScope to allow access within other controllers
      $rootScope.rels = relatives.data.relativeList;
      initialize();
      makeNewBubbleData();
    }, function(err) {
      console.error('Error retrieving relatives: ', err);
    });
  };
  //Initialize the page with a call to getRelatives
  $scope.getRelatives();

  $rootScope.IntroOptions = {
      steps:[{
          // element: document.querySelector('#test3'),
          intro: "Welcome to the Family Pool page. Each bubble represents a relative and is sized based on percentage of shared DNA. Click on the bubbles for more information."
        },
        {
          element: document.querySelector('#path2'),
          intro: "Access the map view and the help menu from over here.",
          position: 'right'
        }
      ],
      showStepNumbers: false,
      exitOnOverlayClick: true,
      exitOnEsc:true,
      nextLabel: 'Next',
      prevLabel: 'Previous',
      skipLabel: 'Exit',
      doneLabel: 'Thanks'
  };
});
<|endoftext|>


<commit_before>// Copyright 2012 Joyent, Inc.  All rights reserved.

var assert = require('assert-plus');
var libuuid = require('libuuid');
var once = require('once');
var clone = require('clone');

var utils = require('./utils');

///--- Helpers

function copyConfig(config) {
    var cfg = utils.clone(config);

    cfg.pre = (config.pre || []).map(function (f) {
        return (f.toString());
    });
    cfg.post = (config.post || []).map(function (f) {
        return (f.toString());
    });

    return (cfg);
}

function copyOptions(options) {
    var opts = clone(options);
    opts.req_id = options.req_id || libuuid.create();

    return (opts);
}

///--- API
// All of the functions here are scoped to the file as they require an
// underlying 'fast' connection to be given to them.  The functions on
// the moray prototype are just passthroughs to all these

function createBucket(client, bucket, config, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(config, 'config');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var cfg = copyConfig(config);
    var opts = copyOptions(options);
    var req = client.rpc('createBucket', bucket, cfg, opts);
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket,
        config: cfg
    }, 'createBucket: entered');

    var cb = utils.simpleCallback({
        callback: callback,
        log: log,
        name: 'createBucket',
        request: req
    });

    req.once('end', cb);
    req.once('error', cb);
}

function getBucket(client, bucket, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var res;
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket
    }, 'getBucket: entered');

    req = client.rpc('getBucket', opts, bucket);

    req.once('message', function (obj) {
        function parseFunctor(f) {
            var fn;
            /* jsl:ignore */
            eval('fn = ' + f);
            /* jsl:end */
            return (fn);
        }
        res = {
            name: obj.name,
            index: JSON.parse(obj.index),
            pre: JSON.parse(obj.pre).map(parseFunctor),
            post: JSON.parse(obj.post).map(parseFunctor),
            options: JSON.parse(obj.options),
            mtime: new Date(obj.mtime)
        };
        log.debug({
            message: obj
        }, 'getBucket: bucket found');
    });

    req.once('end', function () {
        log.debug('getBucket: done');
        callback(null, res);
    });

    req.once('error', function (err) {
        log.debug(err, 'getBucket: failed');
        callback(err);
    });
}

function listBuckets(client, options, callback) {
    assert.object(client, 'client');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var res = [];
    var log = utils.childLogger(client, opts);

    log.debug('listBuckets: entered');

    req = client.rpc('listBuckets', opts);

    req.on('message', function (obj) {
        log.debug({
            message: obj
        }, 'listBuckets: bucket found');

        function parseFunctor(f) {
            var fn;
            /* jsl:ignore */
            eval('fn = ' + f);
            /* jsl:end */
            return (fn);
        }

        res.push({
            name: obj.name,
            index: JSON.parse(obj.index),
            pre: JSON.parse(obj.pre).map(parseFunctor),
            post: JSON.parse(obj.post).map(parseFunctor),
            options: JSON.parse(obj.options),
            mtime: new Date(obj.mtime)
        });
    });

    req.once('end', function () {
        log.debug('listBuckets: done');
        callback(null, res);
    });

    req.once('error', function (err) {
        log.debug(err, 'listBuckets: failed');
        callback(err);
    });
}

function updateBucket(client, bucket, config, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(config, 'config');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var cfg = copyConfig(config);
    var opts = copyOptions(options);
    var req = client.rpc('updateBucket', bucket, cfg, opts);
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket,
        config: cfg
    }, 'updateBucket: entered');

    var cb = utils.simpleCallback({
        callback: callback,
        log: log,
        name: 'updateBucket',
        request: req
    });

    req.once('end', cb);
    req.once('error', cb);
}

function deleteBucket(client, bucket, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket
    }, 'delBucket: entered');

    req = client.rpc('delBucket', bucket, options);

    req.once('end', function () {
        log.debug('delBucket: done');
        callback();
    });

    req.once('error', function (err) {
        log.debug(err, 'delBucket: failed');
        callback(err);
    });
}

function putBucket(client, b, cfg, options, cb) {
    assert.object(client, 'client');
    assert.string(b, 'bucket');
    assert.object(cfg, 'config');
    assert.object(options, 'options');
    assert.func(cb, 'callback');

    var opts = copyOptions(options);

    var _cb = once(function put_callback(err) {
        // MANTA-1342: multiple racers doing putBucket
        // get this back b/c there's no way to be idempotent
        // with tables in postgres.  So we just check for that
        // error code and eat it -- this is somewhat dangerous
        // if two callers weren't doing the same putBucket, but
        // that's not really ever what we see in practice.
        if (err && err.name !== 'BucketConflictError') {
            cb(err);
        } else {
            cb();
        }
    });

    getBucket(client, b, opts, function (err, bucket) {
        if (err) {
            if (err.name === 'BucketNotFoundError') {
                createBucket(client, b, cfg, opts, _cb);
            } else {
                _cb(err);
            }
        } else {
            // MANTA-897 - short circuit client side if
            // versions are equivalent
            var v = bucket.options.version;
            var v2 = (cfg.options || {}).version || 0;
            if (v !== 0 && v === v2) {
                _cb();
            } else {
                updateBucket(client, b, cfg, opts, _cb);
            }
        }
    });
}

///--- Exports

module.exports = {
    createBucket: createBucket,
    getBucket: getBucket,
    listBuckets: listBuckets,
    updateBucket: updateBucket,
    deleteBucket: deleteBucket,
    putBucket: putBucket
};
<commit_msg>MORAY-250: Handle reindex_active in bucket ops<commit_after>// Copyright 2012 Joyent, Inc.  All rights reserved.

var assert = require('assert-plus');
var libuuid = require('libuuid');
var once = require('once');
var clone = require('clone');

var utils = require('./utils');

///--- Helpers

function copyConfig(config) {
    var cfg = utils.clone(config);

    cfg.pre = (config.pre || []).map(function (f) {
        return (f.toString());
    });
    cfg.post = (config.post || []).map(function (f) {
        return (f.toString());
    });

    return (cfg);
}

function copyOptions(options) {
    var opts = clone(options);
    opts.req_id = options.req_id || libuuid.create();

    return (opts);
}

function formatBucket(obj) {
    function parseFunctor(f) {
        var fn;
        /* jsl:ignore */
        eval('fn = ' + f);
        /* jsl:end */
        return (fn);
    }
    var res = {
        name: obj.name,
        index: JSON.parse(obj.index),
        pre: JSON.parse(obj.pre).map(parseFunctor),
        post: JSON.parse(obj.post).map(parseFunctor),
        options: JSON.parse(obj.options),
        mtime: new Date(obj.mtime)
    };
    if (obj.reindex_active) {
        res.reindex_active = JSON.parse(obj.reindex_active);
    }
    return (res);
}

///--- API
// All of the functions here are scoped to the file as they require an
// underlying 'fast' connection to be given to them.  The functions on
// the moray prototype are just passthroughs to all these

function createBucket(client, bucket, config, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(config, 'config');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var cfg = copyConfig(config);
    var opts = copyOptions(options);
    var req = client.rpc('createBucket', bucket, cfg, opts);
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket,
        config: cfg
    }, 'createBucket: entered');

    var cb = utils.simpleCallback({
        callback: callback,
        log: log,
        name: 'createBucket',
        request: req
    });

    req.once('end', cb);
    req.once('error', cb);
}

function getBucket(client, bucket, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var res;
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket
    }, 'getBucket: entered');

    req = client.rpc('getBucket', opts, bucket);

    req.once('message', function (obj) {
        res = formatBucket(obj);
        log.debug({
            message: obj
        }, 'getBucket: bucket found');
    });

    req.once('end', function () {
        log.debug('getBucket: done');
        callback(null, res);
    });

    req.once('error', function (err) {
        log.debug(err, 'getBucket: failed');
        callback(err);
    });
}

function listBuckets(client, options, callback) {
    assert.object(client, 'client');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var res = [];
    var log = utils.childLogger(client, opts);

    log.debug('listBuckets: entered');

    req = client.rpc('listBuckets', opts);

    req.on('message', function (obj) {
        res.push(formatBucket(obj));
        log.debug({
            message: obj
        }, 'listBuckets: bucket found');
    });

    req.once('end', function () {
        log.debug('listBuckets: done');
        callback(null, res);
    });

    req.once('error', function (err) {
        log.debug(err, 'listBuckets: failed');
        callback(err);
    });
}

function updateBucket(client, bucket, config, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(config, 'config');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var cfg = copyConfig(config);
    var opts = copyOptions(options);
    var req = client.rpc('updateBucket', bucket, cfg, opts);
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket,
        config: cfg
    }, 'updateBucket: entered');

    var cb = utils.simpleCallback({
        callback: callback,
        log: log,
        name: 'updateBucket',
        request: req
    });

    req.once('end', cb);
    req.once('error', cb);
}

function deleteBucket(client, bucket, options, callback) {
    assert.object(client, 'client');
    assert.string(bucket, 'bucket');
    assert.object(options, 'options');
    assert.func(callback, 'callback');

    callback = once(callback);

    var opts = copyOptions(options);
    var req;
    var log = utils.childLogger(client, opts);

    log.debug({
        bucket: bucket
    }, 'delBucket: entered');

    req = client.rpc('delBucket', bucket, options);

    req.once('end', function () {
        log.debug('delBucket: done');
        callback();
    });

    req.once('error', function (err) {
        log.debug(err, 'delBucket: failed');
        callback(err);
    });
}

function putBucket(client, b, cfg, options, cb) {
    assert.object(client, 'client');
    assert.string(b, 'bucket');
    assert.object(cfg, 'config');
    assert.object(options, 'options');
    assert.func(cb, 'callback');

    var opts = copyOptions(options);

    var _cb = once(function put_callback(err) {
        // MANTA-1342: multiple racers doing putBucket
        // get this back b/c there's no way to be idempotent
        // with tables in postgres.  So we just check for that
        // error code and eat it -- this is somewhat dangerous
        // if two callers weren't doing the same putBucket, but
        // that's not really ever what we see in practice.
        if (err && err.name !== 'BucketConflictError') {
            cb(err);
        } else {
            cb();
        }
    });

    getBucket(client, b, opts, function (err, bucket) {
        if (err) {
            if (err.name === 'BucketNotFoundError') {
                createBucket(client, b, cfg, opts, _cb);
            } else {
                _cb(err);
            }
        } else {
            // MANTA-897 - short circuit client side if
            // versions are equivalent
            var v = bucket.options.version;
            var v2 = (cfg.options || {}).version || 0;
            if (v !== 0 && v === v2) {
                _cb();
            } else {
                updateBucket(client, b, cfg, opts, _cb);
            }
        }
    });
}

///--- Exports

module.exports = {
    createBucket: createBucket,
    getBucket: getBucket,
    listBuckets: listBuckets,
    updateBucket: updateBucket,
    deleteBucket: deleteBucket,
    putBucket: putBucket
};
<|endoftext|>


<commit_before>/*jslint plusplus: true, vars: true, nomen: true */
/*global brackets, define */

define(function (require, exports, module) {
    "use strict";

    var q               = require("../thirdparty/q"),
        FileSystem      = brackets.getModule("filesystem/FileSystem"),
        FileUtils       = brackets.getModule("file/FileUtils"),
        ProjectManager  = brackets.getModule("project/ProjectManager"),
        Preferences     = require("./Preferences");

    var FILE_STATUS = {
        STAGED: "FILE_STAGED",
        NEWFILE: "FILE_NEWFILE",
        MODIFIED: "FILE_MODIFIED",
        DELETED: "FILE_DELETED",
        RENAMED: "FILE_RENAMED",
        UNTRACKED: "FILE_UNTRACKED"
    };

    function uniqSorted(arr) {
        var rv = [];
        arr.forEach(function (i) {
            if (rv.indexOf(i) === -1) {
                rv.push(i);
            }
        });
        return rv.sort();
    }

    function escapeShellArg(str) {
        // From: http://phpjs.org/functions
        // +   original by: Felix Geisendoerfer (http://www.debuggable.com/felix)
        // +   improved by: Brett Zamir (http://brett-zamir.me)
        // *     example 1: escapeshellarg("kevin's birthday");
        // *     returns 1: "'kevin\'s birthday'"
        if (typeof str !== "string") {
            throw new Error("escapeShellArg argument is not a string: " + typeof str);
        }
        if (str.length === 0) {
            return str;
        }
        if (brackets.platform !== "win") {
            str = str.replace(/[^\\]'/g, function (m) {
                return m.slice(0, 1) + "\\\'";
            });
            return "'" + str + "'";
        } else {
            // http://stackoverflow.com/questions/7760545/cmd-escape-double-quotes-in-parameter
            str = str.replace(/"/g, function () {
                return "\"\"\"";
            });
            return "\"" + str + "\"";
        }
    }

    function GitControl(options) {
        this._isHandlerRunning = false;
        this._queue = [];
        this.options = options;

        if (Preferences.get("gitIsInSystemPath")) {
            this._git = "git";
        } else {
            this._git = "\"" + Preferences.get("gitPath") + "\"";
        }
    }

    GitControl.FILE_STATUS = FILE_STATUS;

    GitControl.prototype = {

        _processQueue: function () {
            var self = this;
            if (self._isHandlerRunning || self._queue.length === 0) {
                return;
            }
            self._isHandlerRunning = true;

            var queueItem = self._queue.shift(),
                method = queueItem[0],
                promise = queueItem[1],
                cmd = queueItem[2],
                args = queueItem[3],
                opts = queueItem[4];

            self.options.handler(method, cmd, args, opts).then(function (result) {
                promise.resolve(result);
                self._isHandlerRunning = false;
                self._processQueue();
            }).fail(function (ex) {
                promise.reject(ex);
                self._isHandlerRunning = false;
                self._processQueue();
            });
        },

        _pushToQueue: function (method, cmd, args, opts) {
            if (!args) { args = []; }
            if (!opts) { opts = {}; }
            if (typeof args === "string") { args = [args]; }

            var rv = q.defer();
            this._queue.push([method, rv, cmd, args, opts]);
            this._processQueue();
            return rv.promise;
        },

        executeCommand: function (cmd, args, opts) {
            return this._pushToQueue("execute", cmd, args, opts);
        },

        spawnCommand: function (cmd, args, opts) {
            return this._pushToQueue("spawn", cmd, args, opts);
        },

        bashVersion: function () {
            if (brackets.platform === "win") {
                var cmd = "\"" + Preferences.get("msysgitPath") + "bin\\sh.exe" + "\"";
                return this.executeCommand(cmd + " --version");
            } else {
                return q().thenReject();
            }
        },

        bashOpen: function (folder) {
            if (brackets.platform === "win") {
                var cmd = "\"" + Preferences.get("msysgitPath") + "Git Bash.vbs" + "\"";
                var arg = " \"" + folder + "\"";
                return this.executeCommand(cmd + arg);
            } else {
                return q().thenReject();
            }
        },

        terminalOpen: function (folder, customCmd) {
            var cmd;
            if (customCmd) {
                cmd = customCmd.replace("$1", escapeShellArg(folder));
            } else {
                cmd = Preferences.get("extensionDirectory") + "shell/" +
                    (brackets.platform === "mac" ? "terminal.osa" : "terminal.sh");
                cmd = escapeShellArg(cmd) + " " + escapeShellArg(folder);
            }
            return this.executeCommand(cmd);
        },

        getVersion: function () {
            return this.executeCommand(this._git, "--version").then(function (output) {
                var io = output.indexOf("git version");
                return output.substring(io !== -1 ? io + "git version".length : 0).trim();
            });
        },

        getRepositoryRoot: function () {
            var self = this;
            return this.executeCommand(this._git + " rev-parse --show-toplevel").then(function (output) {
                // Git returns directory name without trailing slash
                if (output.length > 0) { output = output.trim() + "/"; }
                // Check if it's a cygwin installation.
                if (brackets.platform === "win" && output[0] === "/") {
                    // Convert to Windows path with cygpath.
                    return self.executeCommand("\"" + Preferences.get("msysgitPath") +
                                               "\\bin\\cygpath" + "\" -m \"" + output + "\"").then(function (output) {
                        return output;
                    });
                }
                return output;
            });
        },

        getCommitsAhead: function () {
            return this.executeCommand(this._git + " rev-list HEAD --not --remotes").then(function (output) {
                if (output.trim().length === 0) {
                    return [];
                } else {
                    return output.split("\n");
                }
            });
        },

        getLastCommitMessage: function () {
            return this.executeCommand(this._git + " log -1 --pretty=%B").then(function (output) {
                return output.trim();
            });
        },

        getBranchName: function () {
            return this.executeCommand(this._git + " rev-parse --abbrev-ref HEAD");
        },

        getGitConfig: function (str) {
            return this.executeCommand(this._git + " config " + str.replace(/\s/g, ""));
        },

        setGitConfig: function (str, val) {
            return this.executeCommand(this._git + " config " + str.replace(/\s/g, "") + " " + escapeShellArg(val));
        },

        getBranches: function () {
            return this.executeCommand(this._git + " branch").then(function (stdout) {
                return stdout.split("\n").map(function (l) {
                    return l.trim();
                });
            });
        },

        checkoutBranch: function (branchName) {
            return this.executeCommand(this._git + " checkout " + branchName);
        },

        createBranch: function (branchName) {
            return this.executeCommand(this._git + " checkout -b " + branchName);
        },

        getRemotes: function () {
            return this.executeCommand(this._git + " remote -v").then(function (stdout) {
                return $.unique(stdout.replace(/\((push|fetch)\)/g, "").split("\n")).map(function (l) {
                    return l.trim().split("\t");
                });
            });
        },

        getGitStatus: function () {
            function unquote(str) {
                if (str[0] === "\"" && str[str.length - 1] === "\"") {
                    str = str.substring(1, str.length - 1);
                }
                return str;
            }

            return this.executeCommand(this._git + " status -u --porcelain").then(function (stdout) {
                if (stdout.length === 0) {
                    return [];
                }

                var results = [],
                    lines = stdout.split("\n");
                lines.forEach(function (line) {
                    var statusStaged = line.substring(0, 1),
                        statusUnstaged = line.substring(1, 2),
                        status = [],
                        file = unquote(line.substring(3));

                    switch (statusStaged) {
                    case " ":
                        break;
                    case "?":
                        status.push(FILE_STATUS.UNTRACKED);
                        break;
                    case "A":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.NEWFILE);
                        break;
                    case "D":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.DELETED);
                        break;
                    case "M":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.MODIFIED);
                        break;
                    case "R":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.RENAMED);
                        break;
                    default:
                        throw new Error("Unexpected status: " + statusStaged);
                    }

                    switch (statusUnstaged) {
                    case " ":
                        break;
                    case "?":
                        status.push(FILE_STATUS.UNTRACKED);
                        break;
                    case "D":
                        status.push(FILE_STATUS.DELETED);
                        break;
                    case "M":
                        status.push(FILE_STATUS.MODIFIED);
                        break;
                    default:
                        throw new Error("Unexpected status: " + statusStaged);
                    }

                    results.push({
                        status: uniqSorted(status),
                        file: file,
                        name: file.substring(file.lastIndexOf("/") + 1)
                    });
                });
                return results.sort(function (a, b) {
                    if (a.file < b.file) {
                        return -1;
                    }
                    if (a.file > b.file) {
                        return 1;
                    }
                    return 0;
                });
            });
        },

        gitAdd: function (file, updateIndex) {
            var cmd = this._git + " add ";
            if (updateIndex) {
                cmd += "-u ";
            }
            cmd += "\"" + file + "\"";
            return this.executeCommand(cmd);
        },

        gitUndoFile: function (file) {
            return this.executeCommand(this._git + " checkout \"" + file + "\"");
        },

        gitCommit: function (message, amend) {
            var self = this,
                lines = message.split("\n");

            var cmd = self._git + " commit";
            if (amend) { cmd += " --amend --reset-author"; }

            if (lines.length === 1) {
                return self.executeCommand(cmd + " -m " + escapeShellArg(message));
            } else {
                // TODO: maybe use git commit --file=-
                var result = q.defer(),
                    fileEntry = FileSystem.getFileForPath(ProjectManager.getProjectRoot().fullPath + ".bracketsGitTemp");
                q.when(FileUtils.writeText(fileEntry, message)).then(function () {
                    return self.executeCommand(cmd + " -F .bracketsGitTemp");
                }).then(function (res) {
                    fileEntry.unlink(function () {
                        result.resolve(res);
                    });
                }).fail(function (err) {
                    fileEntry.unlink(function () {
                        result.reject(err);
                    });
                });
                return result.promise;
            }
        },

        gitReset: function () {
            return this.executeCommand(this._git + " reset");
        },

        gitDiff: function (file) {
            return this.executeCommand(this._git + " diff --no-color -U0 \"" + file + "\"");
        },

        gitDiffSingle: function (file) {
            return this.executeCommand(this._git + " diff --no-color \"" + file + "\"");
        },

        gitDiffStaged: function () {
            return this.executeCommand(this._git + " diff --no-color --staged");
        },

        gitPush: function (remote) {
            remote = remote || "";
            return this.executeCommand(this._git + " push --porcelain " + escapeShellArg(remote));
        },

        gitPushUpstream: function (upstream, branch) {
            return this.executeCommand(this._git + " push --porcelain --set-upstream " + escapeShellArg(upstream) + " " + escapeShellArg(branch));
        },

        gitPull: function (remote) {
            remote = remote || "";
            return this.executeCommand(this._git + " pull --ff-only " + escapeShellArg(remote));
        },

        gitInit: function () {
            return this.executeCommand(this._git + " init");
        },

        gitClone: function (remoteGitUrl, destinationFolder) {
            return this.executeCommand(this._git + " clone " + escapeShellArg(remoteGitUrl) + " " + escapeShellArg(destinationFolder));
        },

        gitHistory: function (branch) {
            var separator = "_._",
                items  = ["hashShort", "hash", "author", "date", "message"],
                format = ["%h",        "%H",   "%an",    "%ai",  "%s"     ].join(separator);
            return this.executeCommand(this._git + " log " + branch + " --format=" + escapeShellArg(format)).then(function (stdout) {
                return stdout.length === 0 ? [] : stdout.split("\n").map(function (line) {
                    var result = {},
                        data = line.split(separator);
                    items.forEach(function (name, i) {
                        result[name] = data[i];
                    });
                    return result;
                });
            });
        },

        getFilesFromCommit: function (hash) {
            return this.executeCommand(this._git + " diff --name-only " + hash + "~ " + hash).then(function (stdout) {
                return stdout.length === 0 ? [] : stdout.split("\n");
            });
        },

        getDiffOfFileFromCommit: function (hash, file) {
            return this.executeCommand(this._git + " diff --no-color " + hash + "~ " + hash + " " + escapeShellArg(file));
        },

        remoteAdd: function (remote, url) {
            return this.executeCommand(this._git + " remote add " + escapeShellArg(remote) + " " + escapeShellArg(url));
        }

    };

    module.exports = GitControl;
});
<commit_msg>Fix #185<commit_after>/*jslint plusplus: true, vars: true, nomen: true */
/*global brackets, define */

define(function (require, exports, module) {
    "use strict";

    var q               = require("../thirdparty/q"),
        FileSystem      = brackets.getModule("filesystem/FileSystem"),
        FileUtils       = brackets.getModule("file/FileUtils"),
        ProjectManager  = brackets.getModule("project/ProjectManager"),
        Preferences     = require("./Preferences");

    var FILE_STATUS = {
        STAGED: "FILE_STAGED",
        NEWFILE: "FILE_NEWFILE",
        MODIFIED: "FILE_MODIFIED",
        DELETED: "FILE_DELETED",
        RENAMED: "FILE_RENAMED",
        UNTRACKED: "FILE_UNTRACKED"
    };

    function uniqSorted(arr) {
        var rv = [];
        arr.forEach(function (i) {
            if (rv.indexOf(i) === -1) {
                rv.push(i);
            }
        });
        return rv.sort();
    }

    function escapeShellArg(str) {
        // From: http://phpjs.org/functions
        // +   original by: Felix Geisendoerfer (http://www.debuggable.com/felix)
        // +   improved by: Brett Zamir (http://brett-zamir.me)
        // *     example 1: escapeshellarg("kevin's birthday");
        // *     returns 1: "'kevin\'s birthday'"
        if (typeof str !== "string") {
            throw new Error("escapeShellArg argument is not a string: " + typeof str);
        }
        if (str.length === 0) {
            return str;
        }
        if (brackets.platform !== "win") {
            str = str.replace(/[^\\]'/g, function (m) {
                return m.slice(0, 1) + "\\\'";
            });
            return "'" + str + "'";
        } else {
            // http://stackoverflow.com/questions/7760545/cmd-escape-double-quotes-in-parameter
            str = str.replace(/"/g, function () {
                return "\"\"\"";
            });
            return "\"" + str + "\"";
        }
    }

    function GitControl(options) {
        this._isHandlerRunning = false;
        this._queue = [];
        this.options = options;

        if (Preferences.get("gitIsInSystemPath")) {
            this._git = "git";
        } else {
            this._git = "\"" + Preferences.get("gitPath") + "\"";
        }
    }

    GitControl.FILE_STATUS = FILE_STATUS;

    GitControl.prototype = {

        _processQueue: function () {
            var self = this;
            if (self._isHandlerRunning || self._queue.length === 0) {
                return;
            }
            self._isHandlerRunning = true;

            var queueItem = self._queue.shift(),
                method = queueItem[0],
                promise = queueItem[1],
                cmd = queueItem[2],
                args = queueItem[3],
                opts = queueItem[4];

            self.options.handler(method, cmd, args, opts).then(function (result) {
                promise.resolve(result);
                self._isHandlerRunning = false;
                self._processQueue();
            }).fail(function (ex) {
                promise.reject(ex);
                self._isHandlerRunning = false;
                self._processQueue();
            });
        },

        _pushToQueue: function (method, cmd, args, opts) {
            if (!args) { args = []; }
            if (!opts) { opts = {}; }
            if (typeof args === "string") { args = [args]; }

            var rv = q.defer();
            this._queue.push([method, rv, cmd, args, opts]);
            this._processQueue();
            return rv.promise;
        },

        executeCommand: function (cmd, args, opts) {
            return this._pushToQueue("execute", cmd, args, opts);
        },

        spawnCommand: function (cmd, args, opts) {
            return this._pushToQueue("spawn", cmd, args, opts);
        },

        bashVersion: function () {
            if (brackets.platform === "win") {
                var cmd = "\"" + Preferences.get("msysgitPath") + "bin\\sh.exe" + "\"";
                return this.executeCommand(cmd + " --version");
            } else {
                return q().thenReject();
            }
        },

        bashOpen: function (folder) {
            if (brackets.platform === "win") {
                var cmd = "\"" + Preferences.get("msysgitPath") + "Git Bash.vbs" + "\"";
                var arg = " \"" + folder + "\"";
                return this.executeCommand(cmd + arg);
            } else {
                return q().thenReject();
            }
        },

        terminalOpen: function (folder, customCmd) {
            var cmd;
            if (customCmd) {
                cmd = customCmd.replace("$1", escapeShellArg(folder));
            } else {
                cmd = Preferences.get("extensionDirectory") + "shell/" +
                    (brackets.platform === "mac" ? "terminal.osa" : "terminal.sh");
                cmd = escapeShellArg(cmd) + " " + escapeShellArg(folder);
            }
            return this.executeCommand(cmd);
        },

        getVersion: function () {
            return this.executeCommand(this._git, "--version").then(function (output) {
                var io = output.indexOf("git version");
                return output.substring(io !== -1 ? io + "git version".length : 0).trim();
            });
        },

        getRepositoryRoot: function () {
            var self = this;
            return this.executeCommand(this._git + " rev-parse --show-toplevel").then(function (output) {
                // Git returns directory name without trailing slash
                if (output.length > 0) { output = output.trim() + "/"; }
                // Check if it's a cygwin installation.
                if (brackets.platform === "win" && output[0] === "/") {
                    // Convert to Windows path with cygpath.
                    return self.executeCommand("\"" + Preferences.get("msysgitPath") +
                                               "\\bin\\cygpath" + "\" -m \"" + output + "\"").then(function (output) {
                        return output;
                    });
                }
                return output;
            });
        },

        getCommitsAhead: function () {
            return this.executeCommand(this._git + " rev-list HEAD --not --remotes").then(function (output) {
                if (output.trim().length === 0) {
                    return [];
                } else {
                    return output.split("\n");
                }
            });
        },

        getLastCommitMessage: function () {
            return this.executeCommand(this._git + " log -1 --pretty=%B").then(function (output) {
                return output.trim();
            });
        },

        getBranchName: function () {
            return this.executeCommand(this._git + " rev-parse --abbrev-ref HEAD");
        },

        getGitConfig: function (str) {
            return this.executeCommand(this._git + " config " + str.replace(/\s/g, ""));
        },

        setGitConfig: function (str, val) {
            return this.executeCommand(this._git + " config " + str.replace(/\s/g, "") + " " + escapeShellArg(val));
        },

        getBranches: function () {
            return this.executeCommand(this._git + " branch").then(function (stdout) {
                return stdout.split("\n").map(function (l) {
                    return l.trim();
                });
            });
        },

        checkoutBranch: function (branchName) {
            return this.executeCommand(this._git + " checkout " + branchName);
        },

        createBranch: function (branchName) {
            return this.executeCommand(this._git + " checkout -b " + branchName);
        },

        getRemotes: function () {
            return this.executeCommand(this._git + " remote -v").then(function (stdout) {
                return $.unique(stdout.replace(/\((push|fetch)\)/g, "").split("\n")).map(function (l) {
                    return l.trim().split("\t");
                });
            });
        },

        getGitStatus: function () {
            function unquote(str) {
                if (str[0] === "\"" && str[str.length - 1] === "\"") {
                    str = str.substring(1, str.length - 1);
                }
                return str;
            }

            return this.executeCommand(this._git + " status -u --porcelain").then(function (stdout) {
                if (stdout.length === 0) {
                    return [];
                }

                var results = [],
                    lines = stdout.split("\n");
                lines.forEach(function (line) {
                    var statusStaged = line.substring(0, 1),
                        statusUnstaged = line.substring(1, 2),
                        status = [],
                        file = unquote(line.substring(3));

                    switch (statusStaged) {
                    case " ":
                        break;
                    case "?":
                        status.push(FILE_STATUS.UNTRACKED);
                        break;
                    case "A":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.NEWFILE);
                        break;
                    case "D":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.DELETED);
                        break;
                    case "M":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.MODIFIED);
                        break;
                    case "R":
                        status.push(FILE_STATUS.STAGED, FILE_STATUS.RENAMED);
                        break;
                    default:
                        throw new Error("Unexpected status: " + statusStaged);
                    }

                    switch (statusUnstaged) {
                    case " ":
                        break;
                    case "?":
                        status.push(FILE_STATUS.UNTRACKED);
                        break;
                    case "D":
                        status.push(FILE_STATUS.DELETED);
                        break;
                    case "M":
                        status.push(FILE_STATUS.MODIFIED);
                        break;
                    default:
                        throw new Error("Unexpected status: " + statusStaged);
                    }

                    results.push({
                        status: uniqSorted(status),
                        file: file,
                        name: file.substring(file.lastIndexOf("/") + 1)
                    });
                });
                return results.sort(function (a, b) {
                    if (a.file < b.file) {
                        return -1;
                    }
                    if (a.file > b.file) {
                        return 1;
                    }
                    return 0;
                });
            });
        },

        gitAdd: function (file, updateIndex) {
            var cmd = this._git + " add ";
            if (updateIndex) {
                cmd += "-u ";
            }
            cmd += "\"" + file + "\"";
            return this.executeCommand(cmd);
        },

        gitUndoFile: function (file) {
            return this.executeCommand(this._git + " checkout \"" + file + "\"");
        },

        gitCommit: function (message, amend) {
            var self = this,
                lines = message.split("\n");

            var cmd = self._git + " commit";
            if (amend) { cmd += " --amend --reset-author"; }

            if (lines.length === 1) {
                return self.executeCommand(cmd + " -m " + escapeShellArg(message));
            } else {
                // TODO: maybe use git commit --file=-
                var result = q.defer(),
                    fileEntry = FileSystem.getFileForPath(ProjectManager.getProjectRoot().fullPath + ".bracketsGitTemp");
                q.when(FileUtils.writeText(fileEntry, message)).then(function () {
                    return self.executeCommand(cmd + " -F .bracketsGitTemp");
                }).then(function (res) {
                    fileEntry.unlink(function () {
                        result.resolve(res);
                    });
                }).fail(function (err) {
                    fileEntry.unlink(function () {
                        result.reject(err);
                    });
                });
                return result.promise;
            }
        },

        gitReset: function () {
            return this.executeCommand(this._git + " reset");
        },

        gitDiff: function (file) {
            return this.executeCommand(this._git + " diff --no-color -U0 \"" + file + "\"");
        },

        gitDiffSingle: function (file) {
            return this.executeCommand(this._git + " diff --no-color \"" + file + "\"");
        },

        gitDiffStaged: function () {
            return this.executeCommand(this._git + " diff --no-color --staged");
        },

        gitPush: function (remote) {
            remote = remote || "";
            return this.executeCommand(this._git + " push --porcelain " + escapeShellArg(remote));
        },

        gitPushUpstream: function (upstream, branch) {
            return this.executeCommand(this._git + " push --porcelain --set-upstream " + escapeShellArg(upstream) + " " + escapeShellArg(branch));
        },

        gitPull: function (remote) {
            remote = remote || "";
            return this.executeCommand(this._git + " pull --ff-only " + escapeShellArg(remote));
        },

        gitInit: function () {
            return this.executeCommand(this._git + " init");
        },

        gitClone: function (remoteGitUrl, destinationFolder) {
            return this.executeCommand(this._git + " clone " + escapeShellArg(remoteGitUrl) + " " + escapeShellArg(destinationFolder));
        },

        gitHistory: function (branch) {
            var separator = "_._",
                items  = ["hashShort", "hash", "author", "date", "message"],
                format = ["%h",        "%H",   "%an",    "%ai",  "%s"     ].join(separator);
            return this.executeCommand(this._git + " log " + branch + " --format=" + escapeShellArg(format)).then(function (stdout) {
                return stdout.length === 0 ? [] : stdout.split("\n").map(function (line) {
                    var result = {},
                        data = line.split(separator);
                    items.forEach(function (name, i) {
                        result[name] = data[i];
                    });
                    return result;
                });
            });
        },

        getFilesFromCommit: function (hash) {
            return this.executeCommand(this._git + " diff --name-only " + escapeShellArg(hash + "^!")).then(function (stdout) {
                return stdout.length === 0 ? [] : stdout.split("\n");
            });
        },

        getDiffOfFileFromCommit: function (hash, file) {
            return this.executeCommand(this._git + " diff --no-color " + escapeShellArg(hash + "^!") + " " + escapeShellArg(file));
        },

        remoteAdd: function (remote, url) {
            return this.executeCommand(this._git + " remote add " + escapeShellArg(remote) + " " + escapeShellArg(url));
        }

    };

    module.exports = GitControl;
});
<|endoftext|>