<commit_before>let co = require("co");
let _ = require("lodash");

let moduleLogger = log.child({ component: "ratp" });

let ratpApi = requireFromRoot("ratp/api");
let ratpDb = requireFromRoot("ratp/database");

let normaliseName = (name) => {
  return name.toString().toLowerCase()
    .replace(/\s+/g, "_")
    .replace(/\-/g, "_")
    .replace(/[^\w\_]+/g, "-")
    .replace(/\_\_+/g, "_")
    .replace(/^_+/, "")
    .replace(/_+$/, "");
};

let ratp = {
  getLineId(lineCode) {
    return ratpDb.getField("SELECT _id FROM line WHERE code = ?", "_id", lineCode);
  },

  getLineCode(lineId) {
    return ratpDb.getField("SELECT code FROM line WHERE code = ?", "code", lineId);
  },

  getAllLines() {
    return ratpDb.getRows("SELECT _id as id, code, name FROM line ORDER BY length(code), code");
  },

  getStationId(stationName) {
    let normalisedName = normaliseName(stationName);
    return ratpDb.getField("SELECT _id FROM stopplace WHERE normalized_name = ?", "_id", normalisedName);
  },

  getStationName(stationId) {
    return ratpDb.getField("SELECT name FROM stopplace WHERE _id = ?", "name", stationId);
  },

  getAllStations() {
    return ratpDb.getRows("SELECT _id as id, name, normalized_name as normalisedName FROM stopplace");
  },

  getAllStationsOnLine(lineCode) {
    return ratpDb.getRows("SELECT " +
      "stopplace._id as id, stopplace.name, stopplace.normalized_name as normalisedName " +
      "FROM stopplace " +
      "INNER JOIN stoppoint on stopplace._id = stoppoint.stoparea_id " +
      "WHERE stoppoint.line_id = (SELECT _id FROM line WHERE line.code = ?) " +
      "ORDER BY y, x ASC",
      lineCode);
  },

  getDirectionId(directionName) {
    let normalisedName = normaliseName(directionName);
    return ratpDb.getField("SELECT _id FROM direction WHERE normalized_name = ?", "_id", normalisedName);
  },

  getDirectionName(directionId) {
    return ratpDb.getField("SELECT name FROM direction WHERE _id = ?", "name", directionId);
  },

  getDirectionIdForLine(directionName, lineCode) {
    let normalisedName = normaliseName(directionName);
    return ratpDb.getField(
      "SELECT _id FROM direction WHERE normalized_name = ? " +
      "AND line_id = (SELECT _id FROM line WHERE line.code = ?)",
      "_id",
      normalisedName, lineCode);
  },

  getDirectionsForLine(lineCode) {
    return ratpDb.getRows("SELECT _id as id, name, normalized_name as normalisedName FROM direction " +
      "WHERE line_id = (SELECT _id FROM line WHERE line.code = ?)",
      lineCode);
  },

  getNextStops(stationId, lineId, directionId) {
    return co(function* () {
      let response = yield ratpApi.query(`cmd=getNextStopsRealtime` +
        `&stopArea=${stationId}&line=${lineId}&direction=${directionId}`);
      let now = new Date().getTime();
      response.nextStopsOnLines[0].nextStops
        .forEach((stop) => {
          if (stop.waitingTimeRaw === "Service termine"
            || stop.waitingTimeRaw === "Train arrete") {
            return;
          }
          if (stop.waitingTime < -60) {
            moduleLogger.debug(stop);
            return Promise.reject(new Error("The RATP API returned bogus data: waitingTime < -60"));
          }
          let stopTime = new Date(stop.nextStopTime).getTime();
          let minutesUntilStop = Math.ceil((stopTime - now) / 1000 / 60);
          if (minutesUntilStop < -1 || minutesUntilStop > 120) {
            moduleLogger.debug(stop);
            return Promise.reject(new Error("The RATP API returned bogus data: impossible nextStopTime"));
          }
        });
      let stops = response.nextStopsOnLines[0].nextStops
        .filter((stop) => stop.bStopInStation && stop.nextStopTime
          && stop.waitingTimeRaw !== "Service termine"
          && stop.waitingTimeRaw !== "Train arrete")
        .map((stop) => {
          let stopTime = new Date(stop.nextStopTime).getTime();
          let minutesUntilStop = Math.ceil((stopTime - now) / 1000 / 60);
          let message = minutesUntilStop + " mins";
          if (message === "1 mins") {
            message = "1 min";
          }
          if (stop.waitingTimeRaw === "Train a l'approche"
            || stop.waitingTime === -60
            || message === "0 mins") {
            message = "< 1 min";
          }
          if (stop.waitingTimeRaw === "Train a quai") {
            message = "À quai";
          }
          if (stop.waitingTimeRaw === "Train retarde") {
            message += " (R)";
          }
          return {
            destination: stop.destinationName,
            waitingTime: stop.waitingTime,
            nextStopTime: stop.nextStopTime,
            rawMessage: stop.waitingTimeRaw,
            message,
            delayed: stop.waitingTimeRaw === "Train retarde",
          };
        });
      return stops;
    });
  },

  getIssuesForAll() {
    return co(function* () {
      let events = yield ratpApi.query(`cmd=getTrafficSituation` +
        `&networkType=all&category=all`);
      let issues = [];
      events.events
        .filter((event) => !!event.incidents)
        .forEach((event) => {
          issues = _.union(issues, event.incidents);
        }) || [];
      return issues;
    });
  },

  getIssuesForMetro() {
    return co(function* () {
      let events = yield ratpApi.query(`cmd=getTrafficSituation` +
        `&networkType=groupoflines&network=1`);
      let issues = [];
      events.events
        .filter((event) => !!event.incidents)
        .forEach((event) => {
          issues = _.union(issues, event.incidents);
        }) || [];
      return issues;
    });
  },

  getIssuesForLine(lineCode) {
    return co(function* () {
      let issues = yield ratp.getIssuesForMetro();
      let relevantIssues = issues
        .filter((issue) => {
          let concernsLine = false;
          if (!issue.lines) {
            return false;
          }
          issue.lines.forEach((line) => {
            if (line.name === lineCode) {
              concernsLine = true;
            }
          });
          return concernsLine;
        }) || [];
      return relevantIssues;
    });
  },
};

export default ratp;
<commit_msg>getNextStops: Improve the debug log calls<commit_after>let co = require("co");
let _ = require("lodash");

let moduleLogger = log.child({ component: "ratp" });

let ratpApi = requireFromRoot("ratp/api");
let ratpDb = requireFromRoot("ratp/database");

let normaliseName = (name) => {
  return name.toString().toLowerCase()
    .replace(/\s+/g, "_")
    .replace(/\-/g, "_")
    .replace(/[^\w\_]+/g, "-")
    .replace(/\_\_+/g, "_")
    .replace(/^_+/, "")
    .replace(/_+$/, "");
};

let ratp = {
  getLineId(lineCode) {
    return ratpDb.getField("SELECT _id FROM line WHERE code = ?", "_id", lineCode);
  },

  getLineCode(lineId) {
    return ratpDb.getField("SELECT code FROM line WHERE code = ?", "code", lineId);
  },

  getAllLines() {
    return ratpDb.getRows("SELECT _id as id, code, name FROM line ORDER BY length(code), code");
  },

  getStationId(stationName) {
    let normalisedName = normaliseName(stationName);
    return ratpDb.getField("SELECT _id FROM stopplace WHERE normalized_name = ?", "_id", normalisedName);
  },

  getStationName(stationId) {
    return ratpDb.getField("SELECT name FROM stopplace WHERE _id = ?", "name", stationId);
  },

  getAllStations() {
    return ratpDb.getRows("SELECT _id as id, name, normalized_name as normalisedName FROM stopplace");
  },

  getAllStationsOnLine(lineCode) {
    return ratpDb.getRows("SELECT " +
      "stopplace._id as id, stopplace.name, stopplace.normalized_name as normalisedName " +
      "FROM stopplace " +
      "INNER JOIN stoppoint on stopplace._id = stoppoint.stoparea_id " +
      "WHERE stoppoint.line_id = (SELECT _id FROM line WHERE line.code = ?) " +
      "ORDER BY y, x ASC",
      lineCode);
  },

  getDirectionId(directionName) {
    let normalisedName = normaliseName(directionName);
    return ratpDb.getField("SELECT _id FROM direction WHERE normalized_name = ?", "_id", normalisedName);
  },

  getDirectionName(directionId) {
    return ratpDb.getField("SELECT name FROM direction WHERE _id = ?", "name", directionId);
  },

  getDirectionIdForLine(directionName, lineCode) {
    let normalisedName = normaliseName(directionName);
    return ratpDb.getField(
      "SELECT _id FROM direction WHERE normalized_name = ? " +
      "AND line_id = (SELECT _id FROM line WHERE line.code = ?)",
      "_id",
      normalisedName, lineCode);
  },

  getDirectionsForLine(lineCode) {
    return ratpDb.getRows("SELECT _id as id, name, normalized_name as normalisedName FROM direction " +
      "WHERE line_id = (SELECT _id FROM line WHERE line.code = ?)",
      lineCode);
  },

  getNextStops(stationId, lineId, directionId) {
    return co(function* () {
      let response = yield ratpApi.query(`cmd=getNextStopsRealtime` +
        `&stopArea=${stationId}&line=${lineId}&direction=${directionId}`);
      let now = new Date().getTime();
      response.nextStopsOnLines[0].nextStops
        .forEach((stop) => {
          if (stop.waitingTimeRaw === "Service termine"
            || stop.waitingTimeRaw === "Train arrete") {
            return;
          }
          if (stop.waitingTime < -60) {
            moduleLogger.debug({ stop }, "Bogus data from the RATP API: waitingTime < -60");
            return Promise.reject(new Error("The RATP API returned bogus data: waitingTime < -60"));
          }
          let stopTime = new Date(stop.nextStopTime).getTime();
          let minutesUntilStop = Math.ceil((stopTime - now) / 1000 / 60);
          if (minutesUntilStop < -1 || minutesUntilStop > 120) {
            moduleLogger.debug({ stop }, "Bogus data from the RATP API: impossible nextStopTime");
            return Promise.reject(new Error("The RATP API returned bogus data: impossible nextStopTime"));
          }
        });
      let stops = response.nextStopsOnLines[0].nextStops
        .filter((stop) => stop.bStopInStation && stop.nextStopTime
          && stop.waitingTimeRaw !== "Service termine"
          && stop.waitingTimeRaw !== "Train arrete")
        .map((stop) => {
          let stopTime = new Date(stop.nextStopTime).getTime();
          let minutesUntilStop = Math.ceil((stopTime - now) / 1000 / 60);
          let message = minutesUntilStop + " mins";
          if (message === "1 mins") {
            message = "1 min";
          }
          if (stop.waitingTimeRaw === "Train a l'approche"
            || stop.waitingTime === -60
            || message === "0 mins") {
            message = "< 1 min";
          }
          if (stop.waitingTimeRaw === "Train a quai") {
            message = "À quai";
          }
          if (stop.waitingTimeRaw === "Train retarde") {
            message += " (R)";
          }
          return {
            destination: stop.destinationName,
            waitingTime: stop.waitingTime,
            nextStopTime: stop.nextStopTime,
            rawMessage: stop.waitingTimeRaw,
            message,
            delayed: stop.waitingTimeRaw === "Train retarde",
          };
        });
      return stops;
    });
  },

  getIssuesForAll() {
    return co(function* () {
      let events = yield ratpApi.query(`cmd=getTrafficSituation` +
        `&networkType=all&category=all`);
      let issues = [];
      events.events
        .filter((event) => !!event.incidents)
        .forEach((event) => {
          issues = _.union(issues, event.incidents);
        }) || [];
      return issues;
    });
  },

  getIssuesForMetro() {
    return co(function* () {
      let events = yield ratpApi.query(`cmd=getTrafficSituation` +
        `&networkType=groupoflines&network=1`);
      let issues = [];
      events.events
        .filter((event) => !!event.incidents)
        .forEach((event) => {
          issues = _.union(issues, event.incidents);
        }) || [];
      return issues;
    });
  },

  getIssuesForLine(lineCode) {
    return co(function* () {
      let issues = yield ratp.getIssuesForMetro();
      let relevantIssues = issues
        .filter((issue) => {
          let concernsLine = false;
          if (!issue.lines) {
            return false;
          }
          issue.lines.forEach((line) => {
            if (line.name === lineCode) {
              concernsLine = true;
            }
          });
          return concernsLine;
        }) || [];
      return relevantIssues;
    });
  },
};

export default ratp;
<|endoftext|>


<commit_before>!function() {

var gantplanning={
	version:"0.1.0"
};

var Planning=function()
{
	_this=this;

	this.style={
		phasesColor:d3.rgb("#083968"),
		stepWidth:100,
		phasesHeight:35,
		textColor:d3.rgb("#F7F7F7"),
		phasesY:30,
		fontsize:16
	};

	this.params={
		sticky:0.1,
		border:0.3
	}

	var cleanPhase=function(phase) {
		phase.dx=0;
		phase.dy=0;
		return phase;
	}

	this.fill=function(phases) {
		this.phases=phases;
		for(i=0;i<this.phases.length;i++){
			this.phases[i]=cleanPhase(this.phases[i]);
		}
		return this;
	}

	this.addPhase=function(phase) {
		this.phases.push(cleanPhase(phase));
		this.draw()
	}

	this.attachTo=function(element) {
		this.element=element;
		this.mainElement= d3.select(this.element)
			.attr("class","gant-planning");
		return this;
	}

	var detectMoveDirection=function(xleft,width){
		if (Math.abs(xleft)<_this.params.border*_this.style.stepWidth) {
				return "left";
			}
		var xright=xleft-width;

		if (Math.abs(xright)<_this.params.border*_this.style.stepWidth) {
				return "right";
			}
		return "none";
	}

	var drag = d3.behavior.drag()
		.on("dragstart", function(d,i) {
			var type=d3.event.sourceEvent.srcElement.nodeName;
			var xleft=d3.event.sourceEvent.offsetX;
			if (type=="rect")
				xleft-=d.x;
			if (type=="text")
				xleft-=d.x;
			moveDirection=detectMoveDirection(xleft,d.width)
		})
		.on("drag", function(d,i) {
			d.dx+=d3.event.dx;
			//Sticky edges
			var difference=Math.round(timeToCoordinate.invert(d.dx))-timeToCoordinate.invert(d.dx);
			var distance=Math.abs(difference);
			if (distance<_this.params.sticky) {
					d.rx= timeToCoordinate(Math.round(timeToCoordinate.invert(d.dx)));
				}
			else {
					d.rx=d.dx;
				}

			if (moveDirection=="none") {
				_this.mainElement.selectAll(".phase-"+i)
					.attr("transform","translate("+[d.rx,0]+")");
			}
			if (moveDirection=="right") {
				_this.mainElement.selectAll(".phase-"+i)
					.attr("width",function(v){return (v.width+d.rx)+"px"})
			}
			if (moveDirection=="left") {
				_this.mainElement.selectAll(".phase.phase-"+i)
					.attr("x",function(v){return (v.x+d.rx)+"px"})
					.attr("width",function(v){return (v.width-d.rx)+"px"})
			}
		})
		.on("dragend",function(d,i) {
			for(i=0;i<_this.phases.length;i++) {
				var phase=_this.phases[i];
				if(moveDirection=="none") {
					phase.x+=phase.dx;
					var newStart=Math.round(timeToCoordinate.invert(phase.x));
					phase.end=newStart+phase.end-phase.start
					phase.start=newStart
				}
				if (moveDirection=="right")
					phase.end=Math.round(timeToCoordinate.invert(phase.x+phase.width+phase.dx))+1
				if (moveDirection=="left")
					phase.start=Math.round(timeToCoordinate.invert(phase.x+phase.dx))
				phase.dx=0;
			}
			_this.draw();
		});

	var reorderPhases=function() {
		_this.phases.sort(function(a,b){
			var x = a.start+1/100*a.end;
			var y = b.start+1/100*b.end;
			if (x<y) return -1;
			if (x>y) return 1;
			if (a.description<b.description) return -1;
			if (a.description>b.description) return 1;
			return -1;
		});
	}

	var timeToCoordinate = d3.scale.linear()
		.domain([1,2])
		.range([0,_this.style.stepWidth]);

	var calcCoordinates=function() {
		for(i=0;i<_this.phases.length;i++) {
			var phase=_this.phases[i];
			phase.x=timeToCoordinate(phase.start);
			phase.textx=timeToCoordinate((phase.start+phase.end-1)/2);
			phase.width=timeToCoordinate(phase.end-phase.start);
			phase.y=_this.style.phasesY+i*_this.style.phasesHeight;
			phase.texty=_this.style.phasesY+17+i*_this.style.phasesHeight;
			_this.phases[i]=phase;
		}
	}

	var updatePhases=function() {
		var phases=_this.mainElement.selectAll("rect.phase")
			.data(_this.phases);

		phases.enter()
			.append("rect")
			.attr("class",function(v,i){return "phase phase-"+i})
			.attr("fill",_this.style.phasesColor)
			.attr("height",_this.style.phasesHeight+"px")
			.attr("y",function(v){return v.y+"px"})
			.on("mousemove",function(d,i){
				var type=d3.event.srcElement.nodeName;
				var xleft=d3.event.offsetX;
				if (type=="rect")
					xleft-=d.x;
				if (type=="text")
					xleft-=d.x;
				var moveDirection=detectMoveDirection(xleft,d.width)
				d3.select(this)
					.attr("class",function(v){return "phase phase-"+i+" stretch-"+moveDirection})
			});

		phases
			.attr("width",function(v){return v.width+"px"})
			.attr("transform","translate("+[0,0]+")")
			.attr("x",function(v){return v.x+"px"})
			.attr("y",function(v){return v.y+"px"})
			.call(drag);
	}

	var updateDescriptions=function() {
		var descriptions=_this.mainElement.selectAll("text.description")
			.data(_this.phases);

		descriptions.enter()
			.append("text")
			.attr("class",function(v,i){return "description phase-"+i})
			.attr("text-anchor","middle")
			.attr("font-size",_this.style.fontsize)
			.attr("fill",_this.style.textColor)
			.on("mousemove",function(d,i){
				var type=d3.event.srcElement.nodeName;
				var xleft=d3.event.offsetX;
				if (type=="rect")
					xleft-=d.x;
				if (type=="text")
					xleft-=d.x;
				var moveDirection=detectMoveDirection(xleft,d.width)
				d3.select(this)
					.attr("class",function(v){return "description phase-"+i+" stretch-"+moveDirection})
			});

		descriptions
			.attr("transform","translate("+[0,0]+")")
			.attr("x",function(v,i){return v.textx+"px"})
			.attr("y",function(v,i){return v.texty+"px"})
			.text(function(d){return d.description})
			.call(drag);
	}

	this.draw=function() {
		reorderPhases();
		calcCoordinates();
		updatePhases();
		updateDescriptions();
		return this;
	}

	this.phases=[];
}

gantplanning.createPlanning=function(planning) {
	return new Planning();
}

this.gantplanning=gantplanning;
}()
<commit_msg>better cursor handling<commit_after>!function() {

var gantplanning={
	version:"0.1.0"
};

var Planning=function()
{
	_this=this;

	this.style={
		phasesColor:d3.rgb("#083968"),
		stepWidth:100,
		phasesHeight:35,
		textColor:d3.rgb("#F7F7F7"),
		phasesY:30,
		fontsize:16
	};

	this.params={
		sticky:0.1,
		border:0.3
	}

	var cleanPhase=function(phase) {
		phase.dx=0;
		phase.dy=0;
		return phase;
	}

	this.fill=function(phases) {
		this.phases=phases;
		for(i=0;i<this.phases.length;i++){
			this.phases[i]=cleanPhase(this.phases[i]);
		}
		return this;
	}

	this.addPhase=function(phase) {
		this.phases.push(cleanPhase(phase));
		this.draw()
	}

	this.attachTo=function(element) {
		this.element=element;
		this.mainElement= d3.select(this.element)
			.attr("class","gant-planning");
		return this;
	}

	var detectMoveDirection=function(xleft,width){
		if (Math.abs(xleft)<_this.params.border*_this.style.stepWidth) {
				return "left";
			}
		var xright=xleft-width;

		if (Math.abs(xright)<_this.params.border*_this.style.stepWidth) {
				return "right";
			}
		return "none";
	}

	var drag = d3.behavior.drag()
		.on("dragstart", function(d,i) {
			var type=d3.event.sourceEvent.srcElement.nodeName;
			var xleft=d3.event.sourceEvent.offsetX;
			if (type=="rect")
				xleft-=d.x;
			if (type=="text")
				xleft-=d.x;
			currentMoveDirection=detectMoveDirection(xleft,d.width)
		})
		.on("drag", function(d,i) {
			d.dx+=d3.event.dx;
			//Sticky edges
			var difference=Math.round(timeToCoordinate.invert(d.dx))-timeToCoordinate.invert(d.dx);
			var distance=Math.abs(difference);
			if (distance<_this.params.sticky) {
					d.rx= timeToCoordinate(Math.round(timeToCoordinate.invert(d.dx)));
				}
			else {
					d.rx=d.dx;
				}

			if (currentMoveDirection=="none") {
				_this.mainElement.selectAll(".phase-"+i)
					.attr("transform","translate("+[d.rx,0]+")");
			}
			if (currentMoveDirection=="right") {
				_this.mainElement.selectAll(".phase-"+i)
					.attr("width",function(v){return (v.width+d.rx)+"px"})
			}
			if (currentMoveDirection=="left") {
				_this.mainElement.selectAll(".phase.phase-"+i)
					.attr("x",function(v){return (v.x+d.rx)+"px"})
					.attr("width",function(v){return (v.width-d.rx)+"px"})
			}
		})
		.on("dragend",function(d,i) {
			for(i=0;i<_this.phases.length;i++) {
				var phase=_this.phases[i];
				if(currentMoveDirection=="none") {
					phase.x+=phase.dx;
					var newStart=Math.round(timeToCoordinate.invert(phase.x));
					phase.end=newStart+phase.end-phase.start
					phase.start=newStart
				}
				if (currentMoveDirection=="right")
					phase.end=Math.round(timeToCoordinate.invert(phase.x+phase.width+phase.dx))+1
				if (currentMoveDirection=="left")
					phase.start=Math.round(timeToCoordinate.invert(phase.x+phase.dx))
				phase.dx=0;
			}
			_this.draw();
			currentMoveDirection=undefined;
		});

	var reorderPhases=function() {
		_this.phases.sort(function(a,b){
			var x = a.start+1/100*a.end;
			var y = b.start+1/100*b.end;
			if (x<y) return -1;
			if (x>y) return 1;
			if (a.description<b.description) return -1;
			if (a.description>b.description) return 1;
			return -1;
		});
	}

	var timeToCoordinate = d3.scale.linear()
		.domain([1,2])
		.range([0,_this.style.stepWidth]);

	var calcCoordinates=function() {
		for(i=0;i<_this.phases.length;i++) {
			var phase=_this.phases[i];
			phase.x=timeToCoordinate(phase.start);
			phase.textx=timeToCoordinate((phase.start+phase.end-1)/2);
			phase.width=timeToCoordinate(phase.end-phase.start);
			phase.y=_this.style.phasesY+i*_this.style.phasesHeight;
			phase.texty=_this.style.phasesY+17+i*_this.style.phasesHeight;
			_this.phases[i]=phase;
		}
	}

	var updatePhases=function() {
		var phases=_this.mainElement.selectAll("rect.phase")
			.data(_this.phases);

		phases.enter()
			.append("rect")
			.attr("class",function(v,i){return "phase phase-"+i})
			.attr("fill",_this.style.phasesColor)
			.attr("height",_this.style.phasesHeight+"px")
			.attr("y",function(v){return v.y+"px"})
			.on("mousemove",function(d,i){
				var type=d3.event.srcElement.nodeName;
				var xleft=d3.event.offsetX;
				if (type=="rect")
					xleft-=d.x;
				if (type=="text")
					xleft-=d.x;
				if (currentMoveDirection==undefined)
					var moveDirection=detectMoveDirection(xleft,d.width)
				else
					var moveDirection=currentMoveDirection;
				d3.select(this)
					.attr("class",function(v){return "phase phase-"+i+" stretch-"+moveDirection})
			});

		phases
			.attr("width",function(v){return v.width+"px"})
			.attr("transform","translate("+[0,0]+")")
			.attr("x",function(v){return v.x+"px"})
			.attr("y",function(v){return v.y+"px"})
			.call(drag);
	}

	var updateDescriptions=function() {
		var descriptions=_this.mainElement.selectAll("text.description")
			.data(_this.phases);

		descriptions.enter()
			.append("text")
			.attr("class",function(v,i){return "description phase-"+i})
			.attr("text-anchor","middle")
			.attr("font-size",_this.style.fontsize)
			.attr("fill",_this.style.textColor)
			.on("mousemove",function(d,i){
				var type=d3.event.srcElement.nodeName;
				var xleft=d3.event.offsetX;
				if (type=="rect")
					xleft-=d.x;
				if (type=="text")
					xleft-=d.x;
				if (currentMoveDirection==undefined)
					var moveDirection=detectMoveDirection(xleft,d.width)
				else
					var moveDirection=currentMoveDirection;
				d3.select(this)
					.attr("class",function(v){return "description phase-"+i+" stretch-"+moveDirection})
			});

		descriptions
			.attr("transform","translate("+[0,0]+")")
			.attr("x",function(v,i){return v.textx+"px"})
			.attr("y",function(v,i){return v.texty+"px"})
			.text(function(d){return d.description})
			.call(drag);
	}

	this.draw=function() {
		reorderPhases();
		calcCoordinates();
		updatePhases();
		updateDescriptions();
		return this;
	}

	this.phases=[];
}

gantplanning.createPlanning=function(planning) {
	return new Planning();
}

this.gantplanning=gantplanning;
}()
<|endoftext|>


<commit_before>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['es6-object-assign'], function(ObjectAssign) {
            return factory(ObjectAssign.assign);
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('es6-object-assign').assign);
    } else {
        // Browser globals (root is window)
        root.CoinsLogonWidget = root.CoinsLogonWidget || {};
        root.CoinsLogonWidget.Form = factory(root.ObjectAssign.assign);
    }
}(this, function (assign) {
    'use strict';

    function Form(options) {
        options = options || {};

        this.options = assign({}, Form.DEFAULTS, options);
        this.element = this._getFormElements();
    }

    Form.prototype._getFormElements = function() {
        var button = document.createElement('button');
        var classNames = this.options.classNames;
        var form = document.createElement('form');

        form.className = classNames.form;
        button.className = classNames.button + ' ' + classNames.buttonPrimary;
        //TODO: Don't hard code button's text
        button.textContent = 'Log In';
        button.type = 'submit';

        form.appendChild(button);

        return form;
    };

    Form.prototype._getNotificationElement = function() {
        var notification = document.createElement('div');

        notification.className = this.options.classNames.notification;
        notification.setAttribute('role', 'alert');

        return notification;
    };

    Form.prototype.setNotification = function(text, state) {
        //TODO: Can not set multiple notifications. Add this feature.
        this.clearNotification();

        if (!text) {
            return;
        }

        var notification = this._getNotificationElement();
        notification.textContent = text;

        if (state === 'error') {
            notification.classList.add(classNames.notificationError);
        } else if (state === 'success') {
            notification.classList.add(classNames.notificationSuccess);
        }

        //TODO: fix
        this.element.prepend(notification);
    };

    Form.clearNotification = function() {
        var notification = this.element.querySelector(
            '.' + this.options.classNames.notification
        );

        if (notification) {
            notification.parentNode.removeChild(notification);
        }
    };

    Form.DEFAULTS = {
        classNames: {
            button: 'coins-logon-widget-button',
            buttonPrimary: 'coins-logon-widget-button-primary',
            form: 'coins-logon-widget-form',
            notification: 'coins-logon-widget-notification',
            notificationError: 'coins-logon-widget-notification-error',
            notificationSuccess: 'coins-logon-widget-notification-success'
        }
    };

    return Form;
}));
<commit_msg>Fix 'Form' component's bugs.<commit_after>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['es6-object-assign'], function(ObjectAssign) {
            return factory(ObjectAssign.assign);
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('es6-object-assign').assign);
    } else {
        // Browser globals (root is window)
        root.CoinsLogonWidget = root.CoinsLogonWidget || {};
        root.CoinsLogonWidget.Form = factory(root.ObjectAssign.assign);
    }
}(this, function (assign) {
    'use strict';

    function Form(options) {
        options = options || {};

        this.options = assign({}, Form.DEFAULTS, options);
        this.element = this._getFormElements();
    }

    Form.prototype._getFormElements = function() {
        var button = document.createElement('button');
        var classNames = this.options.classNames;
        var form = document.createElement('form');

        form.className = classNames.form;
        button.className = classNames.button + ' ' + classNames.buttonPrimary;
        //TODO: Don't hard code button's text
        button.textContent = 'Log In';
        button.type = 'submit';

        form.appendChild(button);

        return form;
    };

    Form.prototype._getNotificationElement = function() {
        var notification = document.createElement('div');

        notification.className = this.options.classNames.notification;
        notification.setAttribute('role', 'alert');

        return notification;
    };

    Form.prototype.setNotification = function(text, state) {
        //TODO: Can not set multiple notifications. Add this feature.
        this.clearNotification();

        if (!text) {
            return;
        }

        var notification = this._getNotificationElement();
        notification.textContent = text;

        if (state === 'error') {
            notification.classList.add(classNames.notificationError);
        } else if (state === 'success') {
            notification.classList.add(classNames.notificationSuccess);
        }

        this.element.insertBefore(notification, this.element.firstChild);
    };

    Form.prototype.clearNotification = function() {
        var notification = this.element.querySelector(
            '.' + this.options.classNames.notification
        );

        if (notification) {
            notification.parentNode.removeChild(notification);
        }
    };

    Form.DEFAULTS = {
        classNames: {
            button: 'coins-logon-widget-button',
            buttonPrimary: 'coins-logon-widget-button-primary',
            form: 'coins-logon-widget-form',
            notification: 'coins-logon-widget-notification',
            notificationError: 'coins-logon-widget-notification-error',
            notificationSuccess: 'coins-logon-widget-notification-success'
        }
    };

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


<commit_before>'use strict';

var DEFAULT_ARGS = ['lib/', 'src/', 'tasks/'],
    sys = require('sys'),
    fs = require('fs'),
    sh = require('shelljs'),
    Path = require('path'),
    exec = require('child_process').exec;

function run(jsdoc_args) {
  jsdoc_args = jsdoc_args || getDefaultJSDocArgs();

  var filename = "docs.json",
      cmd = "jsdoc " + jsdoc_args.join(' ') + " --explain --recurse";

  var output = sh.exec(cmd, {silent: true}).output;
  var objects = JSON.parse( output );
  var data = prepareMetadata(objects, []);
  fs.writeFileSync(filename, JSON.stringify(data));
  return filename;
}

/** Returns an Array of default arguments for JSDOC. */
function getDefaultJSDocArgs() {
  var results = [];
  DEFAULT_ARGS.forEach(function(dir) {
    if( fs.existsSync(dir) ) results.push(dir);
  });
  return results;
}

// Removes absolute paths from JSON output and inject
// some relevant metadata, e.g. language.
var prepareMetadata = function(objects, inch_args) {
  var cwd = process.cwd();
  return {
    language: 'nodejs',
    args: inch_args,
    objects: objects.map(function(item) {
        return prepareCodeObject(item, cwd);
      })
  };
}

var prepareCodeObject = function(item, cwd) {
  if( item['meta'] ) {
    if( item['comment'] == '' ) {
      var filename = Path.join(item['meta']['path'], item['meta']['filename']);
      var line = item['meta']['lineno'];
      item['comment'] = readCommentsFromFilePrecedingLine(filename, line);
    }
    item['meta']['path'] = item['meta']['path'].replace(cwd, '');
  }
  return item;
}

var readCommentsFromFilePrecedingLine = function(filename, lineno) {
  var contents = fs.readFileSync(filename, 'utf-8'),
      lines = contents.split('\n'),
      result = [];
  for(var i=lineno-2;i>=0;i--) {
    var line = lines[i];
    if( line.match(/^\s*[\/\*]/) ) {
      result.unshift(line);
    } else {
      return result.join('\n');
    }
  }
  return result.join('\n');
}

module.exports = {
  run: run
};
<commit_msg>Add check for type of item metadata<commit_after>'use strict';

var DEFAULT_ARGS = ['lib/', 'src/', 'tasks/'],
    sys = require('sys'),
    fs = require('fs'),
    sh = require('shelljs'),
    Path = require('path'),
    exec = require('child_process').exec;

function run(jsdoc_args) {
  jsdoc_args = jsdoc_args || getDefaultJSDocArgs();

  var filename = "docs.json",
      cmd = "jsdoc " + jsdoc_args.join(' ') + " --explain --recurse";

  var output = sh.exec(cmd, {silent: true}).output;
  var objects = JSON.parse( output );
  var data = prepareMetadata(objects, []);
  fs.writeFileSync(filename, JSON.stringify(data));
  return filename;
}

/** Returns an Array of default arguments for JSDOC. */
function getDefaultJSDocArgs() {
  var results = [];
  DEFAULT_ARGS.forEach(function(dir) {
    if( fs.existsSync(dir) ) results.push(dir);
  });
  return results;
}

// Removes absolute paths from JSON output and inject
// some relevant metadata, e.g. language.
var prepareMetadata = function(objects, inch_args) {
  var cwd = process.cwd();
  return {
    language: 'nodejs',
    args: inch_args,
    objects: objects.map(function(item) {
        return prepareCodeObject(item, cwd);
      })
  };
}

var prepareCodeObject = function(item, cwd) {
  if( item['meta'] && typeof(item['meta']) == "object" ) {
    if( item['comment'] == '' ) {
      var filename = Path.join(item['meta']['path'], item['meta']['filename']);
      var line = item['meta']['lineno'];
      item['comment'] = readCommentsFromFilePrecedingLine(filename, line);
    }
    item['meta']['path'] = item['meta']['path'].replace(cwd, '');
  }
  return item;
}

var readCommentsFromFilePrecedingLine = function(filename, lineno) {
  var contents = fs.readFileSync(filename, 'utf-8'),
      lines = contents.split('\n'),
      result = [];
  for(var i=lineno-2;i>=0;i--) {
    var line = lines[i];
    if( line.match(/^\s*[\/\*]/) ) {
      result.unshift(line);
    } else {
      return result.join('\n');
    }
  }
  return result.join('\n');
}

module.exports = {
  run: run
};
<|endoftext|>


<commit_before>import React from 'react';
import { mount } from 'enzyme'
import { getReduxStore } from '../reduxStore';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { theme } from '../theme';
import ProjectSubmission from './ProjectSubmission';

function renderComponent(ComponentClass, props) {
  return getReduxStore().then(
    (store) => {
      mount(
        <Provider store={store}>
          <ThemeProvider theme={theme}>
            <MuiThemeProvider>
              <ComponentClass {...props}/>
            </MuiThemeProvider>
          </ThemeProvider>
        </Provider>
      );
    });
}

describe('the Submission component', () => {
  let initializedExplorer = testExpected.explorer;
  beforeEach(() => {
    fetch.mockResponseOnce(JSON.stringify(testData), { status: 200 });
    return renderComponent(ProjectSubmission, {viewer: testData.data.viewer});
  });

  it('Update redux store when loading', () => {
    return getReduxStore().then(
      (store) => {
        expect(store.getState().explorer).toEqual(initializedExplorer);
      }
    );
  });

  it('Update redux store when page size changed', () => {
    return getReduxStore().then(
      (store) => {
        fetch.mockResponse(JSON.stringify(testData), { status: 200 });
        return store.dispatch(changePageSize(3, {
          oldPageSize: initializedExplorer.pageSize,
          pagesPerTab: initializedExplorer.pagesPerTab,
          cursors: initializedExplorer.cursors,
          queriedCursors: initializedExplorer.queriedCursors
        })).then(
          () => {
            expect(store.getState().explorer.pageSize).toEqual(3)
          }
        );
      }
    );
  });

  it('Update redux store when current page changed', () => {
    return getReduxStore().then(
      (store) => {
        fetch.mockResponse(JSON.stringify(testData), { status: 200 });
        return store.dispatch(changePage('aligned_reads', 2,
          initializedExplorer.currentPages)).then(
          () => {
            expect(store.getState().explorer.currentPages['aligned_reads']).toEqual(2)
          }
        );
      }
    );
  });
});
<commit_msg>fix(test): remove test case fails<commit_after><|endoftext|>


<commit_before>const Bluebird = require('bluebird');
const csv = require('csvtojson');
const fetch = require('node-fetch');
const find = require('array.prototype.find');
const get = require('lodash.get');
const moment = require('moment');

// Locale needs to be set to en, to be able to read month names in English
moment.locale('en');

const originalCsvURL = process.env.PROD_CSV;
const earliestDate = moment("2017-02-07 12:00");
const latestDate = moment("2017-05-13 12:00");

/*
  CSVs are a bit tricky ones to parse manually. Because of that,
  an external library will handle that for us.
*/
const csvToJsonAsync = content => new Promise((resolve, reject) => {
  var entries = [];
  csv({})
    .fromString(content)
    .on('json', (json) => { entries.push(json); })
    .on('done',(error)=>{
      error ? reject(error) : resolve(entries);
    });
});

/*
  HERE Maps Multi Reverse Geocode API requires HTTP POST content to follow a custom format
  See. https://developer.here.com/rest-apis/documentation/geocoder/topics/resource-multi-reverse-geocode.html
*/
const bodyForGeocodingRequest = entries => entries
  .map(entry => `id=${entry.index}&prox=${entry.latitude},${entry.longitude},50`)
  .join("\n");

/*
  Get district info for the location provided. The Multi Reverse Geocode API support only
  up to 100 locations; because of this, we'll find location only for the map markers.
  There shouldn't be more than 100 of those during the pilgrimage.
*/
const getPlacesAsync = entries => Bluebird.resolve(entries)
  .filter(entry => entry.marker)
  .then(filteredEntries => fetch(
    'https://reverse.geocoder.cit.api.here.com/6.2/multi-reversegeocode.json' +
    '?mode=retrieveAreas&maxresults=1&gen=9' +
    `&app_id=${process.env.HERE_APP_ID}&app_code=${process.env.HERE_APP_CODE}`,
    {
      method: 'POST',
      body: bodyForGeocodingRequest(filteredEntries),
      headers: { 'Content-Type': 'text/plain' }
    }))
  .then(res => res.json())
  .then(res => res && res.Response && res.Response.Item);

/*
  The actual JSON generation
*/

module.exports = function getEntriesAsync() {
  // Enable advanced promise functions in this chain
  return Bluebird.resolve(true)
    // Fetch CSV content
    .then(() => fetch(originalCsvURL))
    .then(res => res.text())
    // Add header row to CSV (to ease parsing of it) and parse it
    .then(body => "date,image,latitude,longitude,shareUrl\r\n" + body)
    .then(csvToJsonAsync)
    // Parse interesting columns (including the IFTTT custom date format)
    .then(entries => entries.map(entry => ({
      moment: moment(entry.date, 'MMMM D, YYYY at hh:mmA'),
      latitude: entry.latitude,
      longitude: entry.longitude
    })))
    // Filter problematic and unwanted entries away
    .then(entries => entries.filter(entry => entry.moment.isValid() &&
      entry.moment.isAfter(earliestDate) && entry.moment.isBefore(latestDate)))
    // Enrich content (eg. which entries need an actual marker)
    .then(entries => entries.map((current, index) => {
      const next = entries[index + 1];
      const isMarker = (index === 0 || !next || next.moment.isAfter(current.moment, 'day'));
      return {
        index: index,
        date: current.moment.format('dddd MMM Do [of] YYYY [at] H:mm'),
        latitude: current.latitude,
        longitude: current.longitude,
        marker: isMarker
      };
    }))
    // Add reverse geocoding data
    .then(entries => Bluebird.join(entries, getPlacesAsync(entries)))
    // Combine entries and place names
    .spread((entries, places) => {
      return entries.map(entry => {
        const place = find(places, item => item.ItemId === entry.index);
        entry.place = get(place, 'Result[0].Location.Address.Label', '');
        return entry;
      });
    });
};
<commit_msg>Use proper dates<commit_after>const Bluebird = require('bluebird');
const csv = require('csvtojson');
const fetch = require('node-fetch');
const find = require('array.prototype.find');
const get = require('lodash.get');
const moment = require('moment');

// Locale needs to be set to en, to be able to read month names in English
moment.locale('en');

const originalCsvURL = process.env.PROD_CSV;
const earliestDate = moment("2017-04-07 12:00");
const latestDate = moment("2017-05-13 12:00");

/*
  CSVs are a bit tricky ones to parse manually. Because of that,
  an external library will handle that for us.
*/
const csvToJsonAsync = content => new Promise((resolve, reject) => {
  var entries = [];
  csv({})
    .fromString(content)
    .on('json', (json) => { entries.push(json); })
    .on('done',(error)=>{
      error ? reject(error) : resolve(entries);
    });
});

/*
  HERE Maps Multi Reverse Geocode API requires HTTP POST content to follow a custom format
  See. https://developer.here.com/rest-apis/documentation/geocoder/topics/resource-multi-reverse-geocode.html
*/
const bodyForGeocodingRequest = entries => entries
  .map(entry => `id=${entry.index}&prox=${entry.latitude},${entry.longitude},50`)
  .join("\n");

/*
  Get district info for the location provided. The Multi Reverse Geocode API support only
  up to 100 locations; because of this, we'll find location only for the map markers.
  There shouldn't be more than 100 of those during the pilgrimage.
*/
const getPlacesAsync = entries => Bluebird.resolve(entries)
  .filter(entry => entry.marker)
  .then(filteredEntries => fetch(
    'https://reverse.geocoder.cit.api.here.com/6.2/multi-reversegeocode.json' +
    '?mode=retrieveAreas&maxresults=1&gen=9' +
    `&app_id=${process.env.HERE_APP_ID}&app_code=${process.env.HERE_APP_CODE}`,
    {
      method: 'POST',
      body: bodyForGeocodingRequest(filteredEntries),
      headers: { 'Content-Type': 'text/plain' }
    }))
  .then(res => res.json())
  .then(res => res && res.Response && res.Response.Item);

/*
  The actual JSON generation
*/

module.exports = function getEntriesAsync() {
  // Enable advanced promise functions in this chain
  return Bluebird.resolve(true)
    // Fetch CSV content
    .then(() => fetch(originalCsvURL))
    .then(res => res.text())
    // Add header row to CSV (to ease parsing of it) and parse it
    .then(body => "date,image,latitude,longitude,shareUrl\r\n" + body)
    .then(csvToJsonAsync)
    // Parse interesting columns (including the IFTTT custom date format)
    .then(entries => entries.map(entry => ({
      moment: moment(entry.date, 'MMMM D, YYYY at hh:mmA'),
      latitude: entry.latitude,
      longitude: entry.longitude
    })))
    // Filter problematic and unwanted entries away
    .then(entries => entries.filter(entry => entry.moment.isValid() &&
      entry.moment.isAfter(earliestDate) && entry.moment.isBefore(latestDate)))
    // Enrich content (eg. which entries need an actual marker)
    .then(entries => entries.map((current, index) => {
      const next = entries[index + 1];
      const isMarker = (index === 0 || !next || next.moment.isAfter(current.moment, 'day'));
      return {
        index: index,
        date: current.moment.format('dddd MMM Do [of] YYYY [at] H:mm'),
        latitude: current.latitude,
        longitude: current.longitude,
        marker: isMarker
      };
    }))
    // Add reverse geocoding data
    .then(entries => Bluebird.join(entries, getPlacesAsync(entries)))
    // Combine entries and place names
    .spread((entries, places) => {
      return entries.map(entry => {
        const place = find(places, item => item.ItemId === entry.index);
        entry.place = get(place, 'Result[0].Location.Address.Label', '');
        return entry;
      });
    });
};
<|endoftext|>


<commit_before>module.exports = {
  plugins: ['gatsby-plugin-react-helmet'],
}
<commit_msg>Add contentful plugin<commit_after>const keys = require('./keys')

module.exports = {
  plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: 'gatsby-source-contentful',
      options: {
        spaceId: keys.contentful.spaceId,
        accessToken: keys.contentful.token,
      },
    },
  ],
}
<|endoftext|>


<commit_before>module.exports = {
  siteMetadata: {
    title: 'Spark Design System',
    description:
      'Spark Design System is a system of patterns and components ' +
      'used to create the user interface for the Quicken Loans family of ' +
      'Fintech products.',
    author: '@sparkQL',
  },
  plugins: [
    {
      resolve: 'gatsby-plugin-manifest',
      options: {
        icon: 'src/images/spark-logo.svg',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/homepage',
        name: 'homepage-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/data/',
        name: 'data',
      },
    },
    {
      resolve: 'gatsby-transformer-json',
      options: {
        path: 'src/data/',
        name: 'data',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/installing-spark',
        name: 'installing-spark-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/installing-spark.mdx',
        name: 'installing-spark-page',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/principles',
        name: 'principles-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/using-spark',
        name: 'using-spark-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/using-spark.mdx',
        name: 'using-spark-page',
      },
    },

    'gatsby-plugin-sass',
    {
      resolve: 'gatsby-symbol-set-fetch',
      options: {
        url:
          'https://www.rockomni.com/mcds/assets/GlobalContent/NonStockImages' +
          '/Icons/spark-icons-v14.svg',
      },
    },
    {
      resolve: 'gatsby-plugin-mdx',
      options: {
        defaultLayouts: {
          'homepage-pages': require.resolve(
            './src/components/layouts/HomepageLayout.js',
          ),
          'installing-spark-pages': require.resolve(
            './src/components/layouts/InstallingSparkLayout.js',
          ),
          'installing-spark-page': require.resolve(
            './src/components/layouts/InstallingSparkLayout.js',
          ),
          'using-spark-pages': require.resolve(
            './src/components/layouts/UsingSparkLayout.js',
          ),
          'using-spark-page': require.resolve(
            './src/components/layouts/UsingSparkLayout.js',
          ),
          'principles-pages': require.resolve(
            './src/components/layouts/PrinciplesLayout.js',
          ),
          default: require.resolve('./src/components/layouts/Layout.js'),
        },
      },
    },
    {
      resolve: 'gatsby-plugin-postcss',
      options: {
        // eslint-disable-next-line global-require
        postCssPlugins: [require('postcss-preset-env')({ stage: 0 })],
      },
    },
    {
      resolve: 'gatsby-plugin-google-analytics',
      options: {
        // The property ID; the tracking code won't be generated without it
        trackingId: 'UA-113915182-1',
        // Defines where to place the tracking
        /// script - `true` in the head and `false` in the body
        head: false,
        // Setting this parameter is also optional
        respectDNT: true,
        // Delays sending pageview hits on route update (in milliseconds)
        pageTransitionDelay: 0,
      },
    },
  ],
};
<commit_msg>try spark assets url<commit_after>module.exports = {
  siteMetadata: {
    title: 'Spark Design System',
    description:
      'Spark Design System is a system of patterns and components ' +
      'used to create the user interface for the Quicken Loans family of ' +
      'Fintech products.',
    author: '@sparkQL',
  },
  plugins: [
    {
      resolve: 'gatsby-plugin-manifest',
      options: {
        icon: 'src/images/spark-logo.svg',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/homepage',
        name: 'homepage-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/data/',
        name: 'data',
      },
    },
    {
      resolve: 'gatsby-transformer-json',
      options: {
        path: 'src/data/',
        name: 'data',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/installing-spark',
        name: 'installing-spark-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/installing-spark.mdx',
        name: 'installing-spark-page',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/principles',
        name: 'principles-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/using-spark',
        name: 'using-spark-pages',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: 'src/pages/using-spark.mdx',
        name: 'using-spark-page',
      },
    },

    'gatsby-plugin-sass',
    {
      resolve: 'gatsby-symbol-set-fetch',
      options: {
        url: 'https://spark-assets.netlify.app/spark-icons-v14.svg',
      },
    },
    {
      resolve: 'gatsby-plugin-mdx',
      options: {
        defaultLayouts: {
          'homepage-pages': require.resolve(
            './src/components/layouts/HomepageLayout.js',
          ),
          'installing-spark-pages': require.resolve(
            './src/components/layouts/InstallingSparkLayout.js',
          ),
          'installing-spark-page': require.resolve(
            './src/components/layouts/InstallingSparkLayout.js',
          ),
          'using-spark-pages': require.resolve(
            './src/components/layouts/UsingSparkLayout.js',
          ),
          'using-spark-page': require.resolve(
            './src/components/layouts/UsingSparkLayout.js',
          ),
          'principles-pages': require.resolve(
            './src/components/layouts/PrinciplesLayout.js',
          ),
          default: require.resolve('./src/components/layouts/Layout.js'),
        },
      },
    },
    {
      resolve: 'gatsby-plugin-postcss',
      options: {
        // eslint-disable-next-line global-require
        postCssPlugins: [require('postcss-preset-env')({ stage: 0 })],
      },
    },
    {
      resolve: 'gatsby-plugin-google-analytics',
      options: {
        // The property ID; the tracking code won't be generated without it
        trackingId: 'UA-113915182-1',
        // Defines where to place the tracking
        /// script - `true` in the head and `false` in the body
        head: false,
        // Setting this parameter is also optional
        respectDNT: true,
        // Delays sending pageview hits on route update (in milliseconds)
        pageTransitionDelay: 0,
      },
    },
  ],
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

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

    var Promise = require("bluebird"),
        EventEmitter = require("eventEmitter"),
        adapter = require("./adapter"),
        util = require("./util");

    /**
     * The LayerManager provides an API for dealing with the layers of the
     * currently active document
     *
     * Right now, this is purely demonstration code. This will need to be elaborated
     * to deal with all documents (or changed to be document specific / not a singleton).
     *
     * Emits "selectionChanged" event whenever the currently selected layer changes
     * with the following parameters
     *    1. @param {String} the selected layer name
     *
     * @extends EventEmitter
     * @constructor
     * @private
     */
    var LayerManager = function () {
        EventEmitter.call(this);

        adapter.on("select", this._handleSelect.bind(this));
        adapter.on("make", this._handleSelect.bind(this));
    };
    util.inherits(LayerManager, EventEmitter);

    /**
     * Holds the currently selected layer indices
     *
     * @type {Array<number>}
     */
    LayerManager.prototype._currentSelection = null;

    /**
     * Event handler for low level select and make events
     * "Make" layer may cause selection to change as well
     * Grabs the current selected layer indices, saves them in _currentSelection
     * and emits a "selectionChanged" event with the current Selection array
     *
     * @private
     */
    LayerManager.prototype._handleSelect = function () {
        this.getSelectedLayerIndices().bind(this).then(function (layers) {
            this._currentSelection = layers;
            this.emit("selectionChanged", this._currentSelection);
        });
    };

    /**
     * Gets the action descriptor of the layer at given index
     *
     * Will check for correct index, if image is only a background layer, will return that
     *
     * @param {number} index Layer index
     * @return {Promise.<Object>} Promise that resolve to the action descriptor of the layer
     */
    LayerManager.prototype.getLayerAtIndex = function (index) {
        return adapter.getProperty("document", "numberOfLayers").then(function (layerCount) {
            if (typeof index === undefined || index < 0 || index > layerCount) {
                throw new Error("Invalid layer index");
            }

            // If only background layer, count is 0, and background layer cannot be unselected
            if (layerCount === 0) {
                return adapter.get({ref: "$Lyr", value: "$Trgt", enum: "$Ordn"});
            } else {
                return adapter.get({ref: "$Lyr", "index": index});
            }
        });
    };

    /**
     * Gets an array of layer references for all selected layers in the current document
     *
     * @return {Promise.<Array.<ref: object, index: number>>} Resolves to an array of
     *      layers with index and ref(:layer) keys
     */
    LayerManager.prototype.getSelectedLayers = function () {
        return adapter.get("document").then(function (document) {
            return document.targetLayers || [];
        });
    };

    /**
     * Gets an array of layer indices for all selected layers in the current document
     *
     * @return {Promise.<Array.<Number>>} Resolves to an array of numbers for each index of layers
     */
    LayerManager.prototype.getSelectedLayerIndices = function () {
        return this.getSelectedLayers().then(function (layers) {
            return layers.map(function (layer) {
                return layer.index;
            });
        });
    };

    /**
     * Gets the action descriptors for all selected layers
     *
     * @return {Promise.<Array.<ActionDescriptor>>} Resolves to an array of
     *     action descriptor for each selected layers
     */
    LayerManager.prototype.getSelectedLayerDescriptors = function () {
        return this.getSelectedLayerIndices().then(function (indices) {
            var layerGetPromises = indices.map(function (index) {
                return adapter.get({ref: "layer", index: index});
            });
            return Promise.all(layerGetPromises);
        });
    };

    /**
     * Creates a new layer in the currently active document.
     * The returned promise resolves/rejects when the operation is complete/has an error
     *
     * @return {Promise.<Object>} Result of the layer creation call
     */
    LayerManager.prototype.createNewLayer = function () {
        return adapter.call("$Mk", {"null" : { "ref" : "$Lyr " }});
    };

    /**
     * Gets all layers in the document with their properties
     *
     * @return {Promise.<Array.<ActionDescriptor>>} Resolves to an array of all array descriptors
     */
    LayerManager.prototype.getAllLayers = function () {
        return adapter.getProperty("document", "numberOfLayers").then(function (layerCount) {
            return adapter.getProperty("document", "hasBackgroundLayer").then(function (hasBackground) {
                var layerGets = [];
                var startIndex = hasBackground ? 0 : 1;

                for (var i = startIndex; i <= layerCount; i++) {
                    layerGets.push(adapter.get({ref: "layer", index: i}));
                }
                return Promise.all(layerGets);
            });
        });
    };

    /**
     * Duplicates layers.
     * If index is supplied, will only duplicate the indexed layer
     *
     * If only one layer is selected, and newLayerName is not null, copy will be renamed
     * Otherwise layers get copied with their original names
     * The returned promise resolves to the dupeArgs object.
     *
     * @param {?number} index Index of the layer to duplicate, default is currently selected layer(s)
     * @param {?string} newLayerName If supplied, renames the new layer, only applies when a single layer is selected
     * @return {Promise.<Object>} Promise that resolves to a reference to currently selected layer
     */
    LayerManager.prototype.duplicateLayer = function (index, newLayerName) {
        var nullArgs = { "ref": "$Lyr" };

        // This way we can either choose the current layer or a layer by index
        if (index !== null) {
            nullArgs.index = index;
        } else {
            nullArgs.enum = "$Ordn";
            nullArgs.value = "$Trgt";
        }

        var dupeArgs = { "null" : nullArgs };
        dupeArgs.$Vrsn = 5;

        if (newLayerName) {
            dupeArgs["$Nm  "] = newLayerName;
        }

        return adapter.call("$Dplc", dupeArgs);
    };

    /**
     * Causes the layer at the given index to be selected in Photoshop.
     *
     * @param {!number} index Index of the layer to select
     * @return {Promise}
     */
    LayerManager.prototype.selectLayerAtIndex = function (index) {
        var selectArgs = {
            "null": {
                "ref": "$Lyr",
                "index": index
            }
        };

        return adapter.call("$slct", selectArgs);
    };

    /**
     * Moves a layer
     * If index is null, will move the selected layer
     *
     * @param {?number} index Index of the layer to move, default is currently selected layer
     * @param {number=} dx Pixel value to move the layer horizontally (Default 0)
     * @param {number=} dy Pixel value to move the layer vertically (Default 0)
     * @return {Promise.<Object>} Promise that resolves to empty object
     */
    LayerManager.prototype.moveLayer = function (index, dx, dy) {
        // If index is given, this ensures that the layer with given index is selected first
        var selectPromise = index !== null ?
            this.selectLayerAtIndex(index) :
            Promise.resolve();

        return selectPromise.then(function () {
            var nullArgs = { "ref": "$Lyr" };

            if (index !== null) {
                nullArgs.index = index;
            } else {
                nullArgs.enum = "$Ordn";
                nullArgs.value = "$Trgt";
            }

            var moveArgs = { "null": nullArgs };

            moveArgs.to = {
                "obj": "Offset",
                "value": {
                    "horizontal": {
                        "unit": "pixelsUnit",
                        "value": dx || 0
                    },
                    "vertical": {
                        "unit": "pixelsUnit",
                        "value": dy || 0
                    }
                }
            };

            return adapter.call("move", moveArgs);
        });
    };

    /**
     * Copies a layer and moves it
     * If index is null, will copy the selected layer
     *
     * @param {?number} index Index of the layer to copy, default is currently selected layer
     * @param {number=} dx Pixel value to move the copied layer horizontally (Default 0)
     * @param {number=} dy Pixel value to move the copied layer vertically (Default 0)
     * @return {Promise.<Object>} Promise that resolves to empty object
     */
    LayerManager.prototype.copyLayer = function (index, dx, dy) {
        // If index is given, this ensures that the layer with given index is selected first
        var selectPromise = index !== null ?
            this.selectLayerAtIndex(index) :
            Promise.resolve();

        return selectPromise.then(function () {
            var nullArgs = { "ref": "$Lyr" };

            if (index !== null) {
                nullArgs.index = index;
            } else {
                nullArgs.enum = "$Ordn";
                nullArgs.value = "$Trgt";
            }

            var copyArgs = { "null": nullArgs };

            copyArgs.to = {
                "obj": "Offset",
                "value": {
                    "horizontal": {
                        "unit": "pixelsUnit",
                        "value": dx || 0
                    },
                    "vertical": {
                        "unit": "pixelsUnit",
                        "value": dy || 0
                    }
                }
            };

            return adapter.call("copyEvent", copyArgs);
        });
    };

    /**
     * Copies a layer multiple times to the given offsets. If the index is null the currently
     * selected layer will be copied.
     *
     * @param {?number} index Index of the layer to copy, default is currently selected layer
     * @param {Array.{dx: number, dy: number}} offsets Offsets at which to copy the layer
     * @return {Promise.<Array.<object>>} Promise that resolves to an array of
     *      ActionDescriptor results
     */
    LayerManager.prototype.copyLayerMultiple = function (index, offsets) {
        var nullArgs = { "ref": "$Lyr" };

        if (index !== null) {
            nullArgs.index = index;
        } else {
            nullArgs.enum = "$Ordn";
            nullArgs.value = "$Trgt";
        }

        var commands = offsets.reduce(function (commands, offset) {
            var selectCommand = {
                name: "$slct",
                descriptor: {
                    "null": nullArgs
                }
            };

            var copyCommand = {
                name: "copyEvent",
                descriptor: {
                    "null": nullArgs,
                    "to": {
                        "obj": "Offset",
                        "value": {
                            "horizontal": {
                                "unit": "pixelsUnit",
                                "value": offset.dx || 0
                            },
                            "vertical": {
                                "unit": "pixelsUnit",
                                "value": offset.dy || 0
                            }
                        }
                    }
                }
            };

            commands.push(selectCommand);
            commands.push(copyCommand);

            return commands;
        }, []);

        return adapter.batchCall(commands);
    };

    /**
    * Given a layer index, adds that layer to the selected layers
    *
    * @param {Number} index Index of layer to be added to selection
    */
    LayerManager.prototype.addLayerToSelection = function (index) {
        return adapter.call("$slct", {
            "null": {
                "ref": "$Lyr",
                "index": index
            },
            "selectionModifier": {
                value: "addToSelection",
                enum: "selectionModifierType"
            },
            "$MkVs": 0
        });
    };

    /**
    * Given an array of layer indices
    * Has Photoshop select those layers.
    *
    * Note: If the layer at a given index is an endSection, it will be a no-op for that layer
    * It is a good idea to not call this function directly as indices are not what they seem on Photoshop UI
    *
    * @param {Array.<Number>} layerIndices Indexes of layers to be selected
    *
    * @returns {Promise.<Array.<Promise>>} Promise that resolves to an array of promises for each layer selection
    */
    LayerManager.prototype.selectLayers = function (layerIndices) {
        if (layerIndices.length === 0) {
            return Promise.resolve([]);
        }

        var firstLayer = this.selectLayerAtIndex(layerIndices.shift());

        return Promise.reduce(layerIndices, function (selectors, layerIndex) {
            selectors.push(this.addLayerToSelection(layerIndex));

            return selectors;
        }.bind(this), [firstLayer]);
    };

    /**
    * Given an array of layer indices, collects them into a new group with
    * given name (if any)
    *
    * @param {Array.<Number>} layerIndices layer indexes that are to be
    * grouped
    * @param {?String} groupName If given, will be used as the new group
    * name, default is what Photoshop assigns
    *
    * @returns {Promise.<ActionDescriptor>} Promise that resolves to the action descriptor
    *     containing the Group command args
    */
    LayerManager.prototype.groupLayers = function (layerIndices, groupName) {
        return this.selectLayers(layerIndices).then(function () {
            var groupArgs = {
                "null": {
                    ref: "layerSection"
                },
                "$From": {
                    ref: "$Lyr",
                    value: "$Trgt",
                    enum: "$Ordn"
                }
            };

            if (groupName) {
                groupArgs.$Usng = {
                    "obj": "layerSection",
                    "value" : {
                        "$Nm" : groupName
                    }
                };
            }

            return adapter.call("$Mk", groupArgs);
        });
    };

    /** @type {LayerManager} The LayerManager singleton */
    var theLayerManager = new LayerManager();

    module.exports = theLayerManager;
});
<commit_msg>Add deselect layers func. back into selectLayers, explain it better, get rid of wrong logic<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

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

    var Promise = require("bluebird"),
        EventEmitter = require("eventEmitter"),
        adapter = require("./adapter"),
        util = require("./util");

    /**
     * The LayerManager provides an API for dealing with the layers of the
     * currently active document
     *
     * Right now, this is purely demonstration code. This will need to be elaborated
     * to deal with all documents (or changed to be document specific / not a singleton).
     *
     * Emits "selectionChanged" event whenever the currently selected layer changes
     * with the following parameters
     *    1. @param {String} the selected layer name
     *
     * @extends EventEmitter
     * @constructor
     * @private
     */
    var LayerManager = function () {
        EventEmitter.call(this);

        adapter.on("select", this._handleSelect.bind(this));
        adapter.on("make", this._handleSelect.bind(this));
    };
    util.inherits(LayerManager, EventEmitter);

    /**
     * Holds the currently selected layer indices
     *
     * @type {Array<number>}
     */
    LayerManager.prototype._currentSelection = null;

    /**
     * Event handler for low level select and make events
     * "Make" layer may cause selection to change as well
     * Grabs the current selected layer indices, saves them in _currentSelection
     * and emits a "selectionChanged" event with the current Selection array
     *
     * @private
     */
    LayerManager.prototype._handleSelect = function () {
        this.getSelectedLayerIndices().bind(this).then(function (layers) {
            this._currentSelection = layers;
            this.emit("selectionChanged", this._currentSelection);
        });
    };

    /**
     * Gets the action descriptor of the layer at given index
     *
     * Will check for correct index, if image is only a background layer, will return that
     *
     * @param {number} index Layer index
     * @return {Promise.<Object>} Promise that resolve to the action descriptor of the layer
     */
    LayerManager.prototype.getLayerAtIndex = function (index) {
        return adapter.getProperty("document", "numberOfLayers").then(function (layerCount) {
            if (typeof index === undefined || index < 0 || index > layerCount) {
                throw new Error("Invalid layer index");
            }

            // If only background layer, count is 0, and background layer cannot be unselected
            if (layerCount === 0) {
                return adapter.get({ref: "$Lyr", value: "$Trgt", enum: "$Ordn"});
            } else {
                return adapter.get({ref: "$Lyr", "index": index});
            }
        });
    };

    /**
     * Gets an array of layer references for all selected layers in the current document
     *
     * @return {Promise.<Array.<ref: object, index: number>>} Resolves to an array of
     *      layers with index and ref(:layer) keys
     */
    LayerManager.prototype.getSelectedLayers = function () {
        return adapter.get("document").then(function (document) {
            return document.targetLayers || [];
        });
    };

    /**
     * Gets an array of layer indices for all selected layers in the current document
     *
     * @return {Promise.<Array.<Number>>} Resolves to an array of numbers for each index of layers
     */
    LayerManager.prototype.getSelectedLayerIndices = function () {
        return this.getSelectedLayers().then(function (layers) {
            return layers.map(function (layer) {
                return layer.index;
            });
        });
    };

    /**
     * Gets the action descriptors for all selected layers
     *
     * @return {Promise.<Array.<ActionDescriptor>>} Resolves to an array of
     *     action descriptor for each selected layers
     */
    LayerManager.prototype.getSelectedLayerDescriptors = function () {
        return this.getSelectedLayerIndices().then(function (indices) {
            var layerGetPromises = indices.map(function (index) {
                return adapter.get({ref: "layer", index: index});
            });
            return Promise.all(layerGetPromises);
        });
    };

    /**
     * Creates a new layer in the currently active document.
     * The returned promise resolves/rejects when the operation is complete/has an error
     *
     * @return {Promise.<Object>} Result of the layer creation call
     */
    LayerManager.prototype.createNewLayer = function () {
        return adapter.call("$Mk", {"null" : { "ref" : "$Lyr " }});
    };

    /**
     * Gets all layers in the document with their properties
     *
     * @return {Promise.<Array.<ActionDescriptor>>} Resolves to an array of all array descriptors
     */
    LayerManager.prototype.getAllLayers = function () {
        return adapter.getProperty("document", "numberOfLayers").then(function (layerCount) {
            return adapter.getProperty("document", "hasBackgroundLayer").then(function (hasBackground) {
                var layerGets = [];
                var startIndex = hasBackground ? 0 : 1;

                for (var i = startIndex; i <= layerCount; i++) {
                    layerGets.push(adapter.get({ref: "layer", index: i}));
                }
                return Promise.all(layerGets);
            });
        });
    };

    /**
     * Duplicates layers.
     * If index is supplied, will only duplicate the indexed layer
     *
     * If only one layer is selected, and newLayerName is not null, copy will be renamed
     * Otherwise layers get copied with their original names
     * The returned promise resolves to the dupeArgs object.
     *
     * @param {?number} index Index of the layer to duplicate, default is currently selected layer(s)
     * @param {?string} newLayerName If supplied, renames the new layer, only applies when a single layer is selected
     * @return {Promise.<Object>} Promise that resolves to a reference to currently selected layer
     */
    LayerManager.prototype.duplicateLayer = function (index, newLayerName) {
        var nullArgs = { "ref": "$Lyr" };

        // This way we can either choose the current layer or a layer by index
        if (index !== null) {
            nullArgs.index = index;
        } else {
            nullArgs.enum = "$Ordn";
            nullArgs.value = "$Trgt";
        }

        var dupeArgs = { "null" : nullArgs };
        dupeArgs.$Vrsn = 5;

        if (newLayerName) {
            dupeArgs["$Nm  "] = newLayerName;
        }

        return adapter.call("$Dplc", dupeArgs);
    };

    /**
     * Causes the layer at the given index to be selected in Photoshop.
     *
     * @param {!number} index Index of the layer to select
     * @return {Promise}
     */
    LayerManager.prototype.selectLayerAtIndex = function (index) {
        var selectArgs = {
            "null": {
                "ref": "$Lyr",
                "index": index
            }
        };

        return adapter.call("$slct", selectArgs);
    };

    /**
     * Moves a layer
     * If index is null, will move the selected layer
     *
     * @param {?number} index Index of the layer to move, default is currently selected layer
     * @param {number=} dx Pixel value to move the layer horizontally (Default 0)
     * @param {number=} dy Pixel value to move the layer vertically (Default 0)
     * @return {Promise.<Object>} Promise that resolves to empty object
     */
    LayerManager.prototype.moveLayer = function (index, dx, dy) {
        // If index is given, this ensures that the layer with given index is selected first
        var selectPromise = index !== null ?
            this.selectLayerAtIndex(index) :
            Promise.resolve();

        return selectPromise.then(function () {
            var nullArgs = { "ref": "$Lyr" };

            if (index !== null) {
                nullArgs.index = index;
            } else {
                nullArgs.enum = "$Ordn";
                nullArgs.value = "$Trgt";
            }

            var moveArgs = { "null": nullArgs };

            moveArgs.to = {
                "obj": "Offset",
                "value": {
                    "horizontal": {
                        "unit": "pixelsUnit",
                        "value": dx || 0
                    },
                    "vertical": {
                        "unit": "pixelsUnit",
                        "value": dy || 0
                    }
                }
            };

            return adapter.call("move", moveArgs);
        });
    };

    /**
     * Copies a layer and moves it
     * If index is null, will copy the selected layer
     *
     * @param {?number} index Index of the layer to copy, default is currently selected layer
     * @param {number=} dx Pixel value to move the copied layer horizontally (Default 0)
     * @param {number=} dy Pixel value to move the copied layer vertically (Default 0)
     * @return {Promise.<Object>} Promise that resolves to empty object
     */
    LayerManager.prototype.copyLayer = function (index, dx, dy) {
        // If index is given, this ensures that the layer with given index is selected first
        var selectPromise = index !== null ?
            this.selectLayerAtIndex(index) :
            Promise.resolve();

        return selectPromise.then(function () {
            var nullArgs = { "ref": "$Lyr" };

            if (index !== null) {
                nullArgs.index = index;
            } else {
                nullArgs.enum = "$Ordn";
                nullArgs.value = "$Trgt";
            }

            var copyArgs = { "null": nullArgs };

            copyArgs.to = {
                "obj": "Offset",
                "value": {
                    "horizontal": {
                        "unit": "pixelsUnit",
                        "value": dx || 0
                    },
                    "vertical": {
                        "unit": "pixelsUnit",
                        "value": dy || 0
                    }
                }
            };

            return adapter.call("copyEvent", copyArgs);
        });
    };

    /**
     * Copies a layer multiple times to the given offsets. If the index is null the currently
     * selected layer will be copied.
     *
     * @param {?number} index Index of the layer to copy, default is currently selected layer
     * @param {Array.{dx: number, dy: number}} offsets Offsets at which to copy the layer
     * @return {Promise.<Array.<object>>} Promise that resolves to an array of
     *      ActionDescriptor results
     */
    LayerManager.prototype.copyLayerMultiple = function (index, offsets) {
        var nullArgs = { "ref": "$Lyr" };

        if (index !== null) {
            nullArgs.index = index;
        } else {
            nullArgs.enum = "$Ordn";
            nullArgs.value = "$Trgt";
        }

        var commands = offsets.reduce(function (commands, offset) {
            var selectCommand = {
                name: "$slct",
                descriptor: {
                    "null": nullArgs
                }
            };

            var copyCommand = {
                name: "copyEvent",
                descriptor: {
                    "null": nullArgs,
                    "to": {
                        "obj": "Offset",
                        "value": {
                            "horizontal": {
                                "unit": "pixelsUnit",
                                "value": offset.dx || 0
                            },
                            "vertical": {
                                "unit": "pixelsUnit",
                                "value": offset.dy || 0
                            }
                        }
                    }
                }
            };

            commands.push(selectCommand);
            commands.push(copyCommand);

            return commands;
        }, []);

        return adapter.batchCall(commands);
    };

    /**
    * Given a layer index, adds that layer to the selected layers
    *
    * @param {Number} index Index of layer to be added to selection
    */
    LayerManager.prototype.addLayerToSelection = function (index) {
        return adapter.call("$slct", {
            "null": {
                "ref": "$Lyr",
                "index": index
            },
            "selectionModifier": {
                value: "addToSelection",
                enum: "selectionModifierType"
            },
            "$MkVs": 0
        });
    };

    /**
    * Given an array of layer indices
    * Has Photoshop select those layers.
    *
    * If array is empty, will deselect all layers
    *
    * Note: If the layer at a given index is an endSection, it will be a no-op for that layer
    * It is a good idea to not call this function directly as indices are not what they seem on Photoshop UI
    *
    * @param {Array.<Number>} layerIndices Indexes of layers to be selected
    *
    * @returns {Promise.<Array.<Promise.<ActionDescriptor>>>} Promise that resolves to an array of promises for each layer selection
    *     If given array was empty, resolves to a single promise for layer deselection ActionDescriptor
    */
    LayerManager.prototype.selectLayers = function (layerIndices) {
        if (layerIndices.length === 0) {
            return adapter.call("selectNoLayers", {
                "null": {
                    ref: "$Lyr",
                    enum: "$Ordn",
                    value: "$Trgt"
                }
            });
        }

        var firstLayer = this.selectLayerAtIndex(layerIndices.shift());

        return Promise.reduce(layerIndices, function (selectors, layerIndex) {
            selectors.push(this.addLayerToSelection(layerIndex));

            return selectors;
        }.bind(this), [firstLayer]);
    };

    /**
    * Given an array of layer indices, collects them into a new group with
    * given name (if any)
    *
    * @param {Array.<Number>} layerIndices layer indexes that are to be
    * grouped
    * @param {?String} groupName If given, will be used as the new group
    * name, default is what Photoshop assigns
    *
    * @returns {Promise.<ActionDescriptor>} Promise that resolves to the action descriptor
    *     containing the Group command args
    */
    LayerManager.prototype.groupLayers = function (layerIndices, groupName) {
        return this.selectLayers(layerIndices).then(function () {
            var groupArgs = {
                "null": {
                    ref: "layerSection"
                },
                "$From": {
                    ref: "$Lyr",
                    value: "$Trgt",
                    enum: "$Ordn"
                }
            };

            if (groupName) {
                groupArgs.$Usng = {
                    "obj": "layerSection",
                    "value" : {
                        "$Nm" : groupName
                    }
                };
            }

            return adapter.call("$Mk", groupArgs);
        });
    };

    /** @type {LayerManager} The LayerManager singleton */
    var theLayerManager = new LayerManager();

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


<commit_before>module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`
      }
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: process.env.GOOGLE_ANALYTICS_TRACKER_ID
      }
    }
  ]
};
<commit_msg>Remove unused siteMetadata<commit_after>module.exports = {
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`
      }
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: process.env.GOOGLE_ANALYTICS_TRACKER_ID
      }
    }
  ]
};
<|endoftext|>


<commit_before>module.exports = {
  siteMetadata: {
    title: `Neon Tsunami`,
    author: `Dwight Watson`,
    description: `A blog on Laravel & Rails.`,
    siteUrl: `https://www.neontsunami.com`,
    social: {
      twitter: `DwightConrad`,
    },
  },
  plugins: [
    `gatsby-plugin-postcss`,
    {
      resolve: `gatsby-plugin-purgecss`,
      options: {
        tailwind: true,
        ignore: ['prismjs/']
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `markdown`,
        path: `${__dirname}/src/markdown`,
      },
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          `gatsby-remark-prismjs`,
        ],
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: 'UA-23727271-4',
      },
    },
    `gatsby-plugin-feed`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Neon Tsunami`,
        short_name: `Neon Tsunami`,
        start_url: `/`,
        background_color: `#ffffff`,
        theme_color: `#4299e1`,
        display: `minimal-ui`,
        icon: `src/images/tsunami.png`,
      },
    },
    `gatsby-plugin-offline`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-netlify`,
  ],
}
<commit_msg>Allow GitHub styles not to be purged<commit_after>module.exports = {
  siteMetadata: {
    title: `Neon Tsunami`,
    author: `Dwight Watson`,
    description: `A blog on Laravel & Rails.`,
    siteUrl: `https://www.neontsunami.com`,
    social: {
      twitter: `DwightConrad`,
    },
  },
  plugins: [
    `gatsby-plugin-postcss`,
    {
      resolve: `gatsby-plugin-purgecss`,
      options: {
        tailwind: true,
        ignore: ['github.css']
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `markdown`,
        path: `${__dirname}/src/markdown`,
      },
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          `gatsby-remark-prismjs`,
        ],
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-google-analytics`,
      options: {
        trackingId: 'UA-23727271-4',
      },
    },
    `gatsby-plugin-feed`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Neon Tsunami`,
        short_name: `Neon Tsunami`,
        start_url: `/`,
        background_color: `#ffffff`,
        theme_color: `#4299e1`,
        display: `minimal-ui`,
        icon: `src/images/tsunami.png`,
      },
    },
    `gatsby-plugin-offline`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-netlify`,
  ],
}
<|endoftext|>