<commit_before>// ==UserScript==
// @name        bloater
// @author      Christopher Hunter
// @namespace   http://github.com/chuntr/
// @source      https://github.com/chuntr/pagebloat
// @version     0.3.04
// @updateURL   https://raw.githubusercontent.com/chuntr/pagebloat/master/bloater.js
// @description Tampermonkey script that resizes the default column widths for some popular sites to take advantage of wider monitors
// @match       http://github.com/*
// @match       http://git.corp.*.*/*
// @match       http://*.readthedocs.org/*
// @match       http://stackexchange.com/*
// @match       http://*.stackexchange.com/*
// @match       http://stackoverflow.com/*
// @match       http://serverfault.com/*
// @require     http://code.jquery.com/jquery-latest.js
// @grant       none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function jQsetWidthByClassName(width,className){
    jQuery(className).css('width', width);
}
function jQsetMaxWidthByClassName(pxWidth,className){
    jQuery(className).css('max-width', pxWidth);
}
function setWidthByClassName(width,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","width:"+width+";");
        elements[i].style.width = width;
    }
}
function setHeightByClassName(height,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","height:"+height+";");
        elements[i].style.height = height;
    }
}
function setWidthById(width,id){
    var element = document.getElementById(id);
    element.setAttribute("style","width:"+width+";");
    element.style.width = width;
}

// readthedocs.org
jQsetWidthByClassName(1024,"div.rst-content");
jQsetMaxWidthByClassName("1128px","div.wy-nav-content");

// stackexchange
setWidthById("1400px","content");
setWidthById("1100px","mainbar");
setWidthById("1100px","question");
setWidthByClassName("1000px","post-text");
setWidthById("1000px","answers");

// github/git.corp
setHeightByClassName("1000","main-content");
setHeightByClassName("509","ace_editor");
setHeightByClassName("509","ace-github-light");
setHeightByClassName("509","ace_content");
<commit_msg>Catching up and resolving merge conflict<commit_after>// ==UserScript==
// @name        bloater
// @author      Christopher Hunter
// @namespace   http://github.com/chuntr/
// @source      https://github.com/chuntr/pagebloat
// @version     0.3.04
// @updateURL   https://raw.githubusercontent.com/chuntr/pagebloat/master/bloater.js
// @description Tampermonkey script that resizes the default column widths for some popular sites to take advantage of wider monitors
// @match       http://github.com/*
// @match       http://git.corp.*.*/*
// @match       http://*.readthedocs.org/*
// @match       http://stackexchange.com/*
// @match       http://*.stackexchange.com/*
// @match       http://stackoverflow.com/*
// @match       http://serverfault.com/*
// @require     http://code.jquery.com/jquery-latest.js
// @match       https://jira.bigfish.lan/*
// @grant       none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function jQsetWidthByClassName(width,className){
    jQuery(className).css('width', width);
}
function jQsetMaxWidthByClassName(pxWidth,className){
    jQuery(className).css('max-width', pxWidth);
}
function setWidthByClassName(width,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","width:"+width+";");
        elements[i].style.width = width;
    }
}
function setHeightByClassName(height,className){
    var elements = document.getElementsByClassName(className);
    for (var i=0; i<elements.length; i++){
        elements[i].setAttribute("style","height:"+height+";");
        elements[i].style.height = height;
    }
}
function setWidthById(width,id){
    var element = document.getElementById(id);
    element.setAttribute("style","width:"+width+";");
    element.style.width = width;
}
function setFontSizeById(newSize,id){
    var element = document.getElementById(id);
    element.setAttribute("style","font-size:"+newSize+";");
}

// readthedocs.org
jQsetWidthByClassName(1024,"div.rst-content");
jQsetMaxWidthByClassName("1128px","div.wy-nav-content");

// stackexchange
setWidthById("1100px","content");
setWidthById("1100px","mainbar");
setWidthById("1100px","question");
setWidthByClassName("1000px","post-text");
setWidthById("1000px","answers");

// github/git.corp
setHeightByClassName("1000","main-content");
setHeightByClassName("509","ace_editor");
setHeightByClassName("509","ace-github-light");
setHeightByClassName("509","ace_content");
setWidthByClassName("1100px","post-cell");
setWidthById("1100px","answers");

// jira
setWidthById("15%","viewissuesidebar");
//setFontSizeById("70%","viewissuesidebar");
<|endoftext|>


<commit_before>'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
var mediaRecorder;
var recordedBlobs;
var sourceBuffer;

var constraints = {
  audio: true,
  video: true
};

var gumVideo = document.querySelector('video#gum');
var recordedVideo = document.querySelector('video#recorded');

var recordButton = document.querySelector('button#record');
var playButton = document.querySelector('button#play');
var downloadButton = document.querySelector('button#download');
recordButton.onclick = toggleRecording;
playButton.onclick = play;
downloadButton.onclick = download;

// window.isSecureContext could be used for Chrome
var isSecureOrigin = location.protocol === 'https:' ||
location.host === 'localhost';
if (!isSecureOrigin) {
  alert('getUserMedia() must be run from a secure origin: HTTPS or localhost.' +
    '\n\nChanging protocol to HTTPS');
  location.protocol = 'HTTPS';
}

navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
  console.log('getUserMedia() got stream: ', stream);
  window.stream = stream; // make available to browser console
  if (window.URL) {
    gumVideo.src = window.URL.createObjectURL(stream);
  } else {
    gumVideo.src = stream;
  }
}).catch(function(error) {
  console.log('navigator.getUserMedia error: ', error);
});

function handleSourceOpen(event) {
  console.log('MediaSource opened');
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
  console.log('Source buffer: ', sourceBuffer);
}

function handleDataAvailable(event) {
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
    console.assert(mediaRecorder.state === 'recording',
      'State should be "recording"');
  } else {
    console.assert(mediaRecorder.state === 'inactive',
      'State should be "inactive"');
  }
}

function handleStop(event) {
  console.log('Recorder stopped: ', event);
}

function toggleRecording() {
  if (recordButton.textContent === 'Start Recording') {
    startRecording();
  } else {
    stopRecording();
    recordButton.textContent = 'Start Recording';
    playButton.disabled = false;
    downloadButton.disabled = false;
  }
}

function startRecording() {
  var options = {mimeType: 'video/vp9'};
  try {
    recordedBlobs = [];
    mediaRecorder = new MediaRecorder(window.stream, options);
  } catch (e) {
    try {
      options = 'video/vp8'; // Chrome 47
      mediaRecorder = new MediaRecorder(window.stream, options);
    } catch (event) {
      alert('MediaRecorder is not supported by this browser.\n\n' +
        'Try Firefox 29 or later, or Chrome 47 or later, with Enable experimental Web Platform features enabled from chrome://flags.');
      console.error('Exception while creating MediaRecorder:', event);
      return;
    }
  }
  console.log('Created MediaRecorder', mediaRecorder, ' with options', options);
  console.assert(mediaRecorder.state === 'inactive');
  recordButton.textContent = 'Stop Recording';
  playButton.disabled = true;
  downloadButton.disabled = true;
  mediaRecorder.onstop = handleStop;
  mediaRecorder.ondataavailable = handleDataAvailable;
  mediaRecorder.start(10); // collect 10ms of data
  console.log('MediaRecorder started', mediaRecorder);
  console.assert(mediaRecorder.state === 'recording');
}

function stopRecording() {
  mediaRecorder.stop();
  console.log('Recorded Blobs: ', recordedBlobs);
  recordedVideo.controls = true;
}

function play() {
  var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  recordedVideo.src = window.URL.createObjectURL(superBuffer);
}

function download() {
  var blob = new Blob(recordedBlobs, {type: 'video/webm'});
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}
<commit_msg>Fixed options setting in MediaRecorder constructor<commit_after>'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
var mediaRecorder;
var recordedBlobs;
var sourceBuffer;

var constraints = {
  audio: true,
  video: true
};

var gumVideo = document.querySelector('video#gum');
var recordedVideo = document.querySelector('video#recorded');

var recordButton = document.querySelector('button#record');
var playButton = document.querySelector('button#play');
var downloadButton = document.querySelector('button#download');
recordButton.onclick = toggleRecording;
playButton.onclick = play;
downloadButton.onclick = download;

// window.isSecureContext could be used for Chrome
var isSecureOrigin = location.protocol === 'https:' ||
location.host === 'localhost';
if (!isSecureOrigin) {
  alert('getUserMedia() must be run from a secure origin: HTTPS or localhost.' +
    '\n\nChanging protocol to HTTPS');
  location.protocol = 'HTTPS';
}

navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
  console.log('getUserMedia() got stream: ', stream);
  window.stream = stream; // make available to browser console
  if (window.URL) {
    gumVideo.src = window.URL.createObjectURL(stream);
  } else {
    gumVideo.src = stream;
  }
}).catch(function(error) {
  console.log('navigator.getUserMedia error: ', error);
});

function handleSourceOpen(event) {
  console.log('MediaSource opened');
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
  console.log('Source buffer: ', sourceBuffer);
}

function handleDataAvailable(event) {
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
    console.assert(mediaRecorder.state === 'recording',
      'State should be "recording"');
  } else {
    console.assert(mediaRecorder.state === 'inactive',
      'State should be "inactive"');
  }
}

function handleStop(event) {
  console.log('Recorder stopped: ', event);
}

function toggleRecording() {
  if (recordButton.textContent === 'Start Recording') {
    startRecording();
  } else {
    stopRecording();
    recordButton.textContent = 'Start Recording';
    playButton.disabled = false;
    downloadButton.disabled = false;
  }
}

function startRecording() {
  var options = {mimeType: 'video/vp9'};
  try {
    recordedBlobs = [];
    mediaRecorder = new MediaRecorder(window.stream, options);
  } catch (e) {
    try {
      options = 'video/vp8'; // Chrome 47
      mediaRecorder = new MediaRecorder(window.stream, options);
    } catch (event) {
      alert('MediaRecorder is not supported by this browser.\n\n' +
        'Try Firefox 29 or later, or Chrome 47 or later, with Enable experimental Web Platform features enabled from chrome://flags.');
      console.error('Exception while creating MediaRecorder:', event);
      return;
    }
  }
  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
  console.assert(mediaRecorder.state === 'inactive');
  recordButton.textContent = 'Stop Recording';
  playButton.disabled = true;
  downloadButton.disabled = true;
  mediaRecorder.onstop = handleStop;
  mediaRecorder.ondataavailable = handleDataAvailable;
  mediaRecorder.start(10); // collect 10ms of data
  console.log('MediaRecorder started', mediaRecorder);
  console.assert(mediaRecorder.state === 'recording');
}

function stopRecording() {
  mediaRecorder.stop();
  console.log('Recorded Blobs: ', recordedBlobs);
  recordedVideo.controls = true;
}

function play() {
  var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  recordedVideo.src = window.URL.createObjectURL(superBuffer);
}

function download() {
  var blob = new Blob(recordedBlobs, {type: 'video/webm'});
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}
<|endoftext|>


<commit_before>/*!
 * Annotations Support for Patterns - v0.3
 *
 * Copyright (c) 2013-2014 Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 *
 */

var annotationsPattern = {

	commentsOverlayActive:  false,
	commentsOverlay:        false,
	commentsEmbeddedActive: false,
	commentsEmbedded:       false,
	commentsGathered:       { "commentOverlay": "on", "comments": { } },
	trackedElements:        [ ],

	/**
	* record which annotations are related to this pattern so they can be sent to the viewer when called
	*/
	gatherComments: function() {

		// make sure this only added when we're on a pattern specific view
		if (document.getElementById("sg-patterns") === null) {

			var count = 0;

			for (comment in comments.comments) {
				var item = comments.comments[comment];
				var els  = document.querySelectorAll(item.el);
				if (els.length > 0) {

					count++;
					item.displaynumber = count;

					for (var i = 0; i < els.length; ++i) {
						els[i].onclick = (function(item) {
							return function(e) {

								e.preventDefault();
								e.stopPropagation();
								var obj = {};

								// if an element was clicked on while the overlay was already on swap it
								obj = { "displaynumber": item.displaynumber, "el": item.el, "title": item.title, "comment": item.comment };

								var targetOrigin = (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host;
								parent.postMessage(obj,targetOrigin);

							}
						})(item);
					}
				}

			}

		} else {

			var obj = { "commentOverlay": "off" };
			var targetOrigin = (window.location.protocol === "file:") ? "*" : window.location.protocol+"//"+window.location.host;
			parent.postMessage(obj,targetOrigin);

		}

	},

	/**
	* embed a comment by building the sg-annotations div (if necessary) and building an sg-annotation div
	* @param  {Object}      element to check the parent node of
	* @param  {String}      the title of the comment
	* @param  {String}      the comment HTML
	*/
	embedComments: function (el,title,comment) {

		// build the annotation div and add the content to it
		var annotationDiv = document.createElement("div");
		annotationDiv.classList.add("sg-annotation");

		var h3       = document.createElement("h3");
		var p        = document.createElement("p");
		h3.innerHTML = title;
		p.innerHTML  = comment;

		annotationDiv.appendChild(h3);
		annotationDiv.appendChild(p);

		// find the parent element to attach things to
		var parentEl = annotationsPattern.findParent(el);

		// see if a child with the class annotations exists
		var els = parentEl.getElementsByClassName("sg-annotations");
		if (els.length > 0) {
			els[0].appendChild(annotationDiv);
		} else {
			var annotationsDiv = document.createElement("div");
			annotationsDiv.classList.add("sg-annotations");
			annotationsDiv.appendChild(annotationDiv);
			parentEl.appendChild(annotationsDiv);
		}

	},

	/**
	* recursively find the parent of an element to see if it contains the sg-pattern class
	* @param  {Object}      element to check the parent node of
	*/
	findParent: function(el) {

		var parentEl;

		if (el.parentNode.classList.contains("sg-pattern")) {
			return el.parentNode;
		} else {
			parentEl = annotationsPattern.findParent(el.parentNode);
		}

		return parentEl;

	},

	/**
	* toggle the annotation feature on/off
	* based on the great MDN docs at https://developer.mozilla.org/en-US/docs/Web/API/window.postMessage
	* @param  {Object}      event info
	*/
	receiveIframeMessage: function(event) {

		// does the origin sending the message match the current host? if not dev/null the request
		if ((window.location.protocol != "file:") && (event.origin !== window.location.protocol+"//"+window.location.host)) {
			return;
		}

		var targetOrigin = (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host;

		if ((event.data.resize !== undefined) && (annotationsPattern.commentsOverlayActive)) {

			for (var i = 0; i < annotationsPattern.trackedElements.length; ++i) {
				var el = annotationsPattern.trackedElements[i];
				if (window.getComputedStyle(el.element,null).getPropertyValue("max-height") == "0px") {
					el.element.firstChild.style.display = "none";
					parent.postMessage({"annotationState": false, "displayNumber": el.displayNumber },targetOrigin);
				} else {
					el.element.firstChild.style.display = "block";
					parent.postMessage({"annotationState": true, "displayNumber": el.displayNumber },targetOrigin);
				}
			}

		} else if (event.data.commentToggle !== undefined) {

			var i, els, item, displayNum;

			// if this is an overlay make sure it's active for the onclick event
			annotationsPattern.commentsOverlayActive  = false;
			annotationsPattern.commentsEmbeddedActive = false;

			// see which flag to toggle based on if this is a styleguide or view-all page
			if ((event.data.commentToggle === "on") && (document.getElementById("sg-patterns") !== null)) {
				annotationsPattern.commentsEmbeddedActive = true;
			} else if (event.data.commentToggle === "on") {
				annotationsPattern.commentsOverlayActive  = true;
			}

			// if comments overlay is turned off make sure to remove the has-annotation class and pointer
			if (!annotationsPattern.commentsOverlayActive) {
				els = document.querySelectorAll(".has-annotation");
				for (i = 0; i < els.length; i++) {
					els[i].classList.remove("has-annotation");
				}
				els = document.querySelectorAll(".annotation-tip");
				for (i = 0; i < els.length; i++) {
					els[i].style.display = "none";
				}
			}

			// if comments embedding is turned off make sure to hide the annotations div
			if (!annotationsPattern.commentsEmbeddedActive) {
				els = document.getElementsByClassName("sg-annotations");
				for (i = 0; i < els.length; i++) {
					els[i].style.display = "none";
				}
			}

			// if comments overlay is turned on add the has-annotation class and pointer
			if (annotationsPattern.commentsOverlayActive) {

				var count = 0;

				for (i = 0; i < comments.comments.length; i++) {
					item = comments.comments[i];
					els  = document.querySelectorAll(item.el);

					var state = true;

					if (els.length) {

						count++;

						//Loop through all items with annotations
						for (k = 0; k < els.length; k++) {

							els[k].classList.add("has-annotation");

							var span              = document.createElement("span");
							span.innerHTML        = count;
							span.classList.add("annotation-tip");

							if (window.getComputedStyle(els[k],null).getPropertyValue("max-height") == "0px") {
								span.style.display = "none";
								state = false;
							}

							annotationsPattern.trackedElements.push({ "itemel": item.el, "element": els[k], "displayNumber": count, "state": state });

							els[k].insertBefore(span,els[k].firstChild);

						}

					}

				}

				// count elements so it can be used when displaying the results in the viewer
				var count = 0;

				// iterate over the comments in annotations.js
				for(i = 0; i < comments.comments.length; i++) {

					var state = true;

					var item = comments.comments[i];
					var els  = document.querySelectorAll(item.el);

					// if an element is found in the given pattern add it to the overall object so it can be passed when the overlay is turned on
					if (els.length > 0) {
						count++;
						for (k = 0; k < els.length; k++) {
							if (window.getComputedStyle(els[k],null).getPropertyValue("max-height") == "0px") {
								state = false;
							}
						}
						annotationsPattern.commentsGathered.comments[count] = { "el": item.el, "title": item.title, "comment": item.comment, "number": count, "state": state };
					}

				}

				// send the list of annotations for the page back to the parent
				parent.postMessage(annotationsPattern.commentsGathered,targetOrigin);

			} else if (annotationsPattern.commentsEmbeddedActive && !annotationsPattern.commentsEmbedded) {

				// if comment embedding is turned on and comments haven't been embedded yet do it
				for (i = 0; i < comments.comments.length; i++)  {
					item = comments.comments[i];
					els  = document.querySelectorAll(item.el);
					if (els.length > 0) {
						annotationsPattern.embedComments(els[0],item.title,item.comment); //Embed the comment
					}
					annotationsPattern.commentsEmbedded = true;
				}

			} else if (annotationsPattern.commentsEmbeddedActive && annotationsPattern.commentsEmbedded) {

				// if comment embedding is turned on and comments have been embedded simply display them
				els = document.getElementsByClassName("sg-annotations");
				for (i = 0; i < els.length; ++i) {
					els[i].style.display = "block";
				}

			}

		}

	}

};

// add the onclick handlers to the elements that have an annotations
annotationsPattern.gatherComments();
window.addEventListener("message", annotationsPattern.receiveIframeMessage, false);

// before unloading the iframe make sure any active overlay is turned off/closed
window.onbeforeunload = function() {
	var obj = { "commentOverlay": "off" };
	var targetOrigin = (window.location.protocol === "file:") ? "*" : window.location.protocol+"//"+window.location.host;
	parent.postMessage(obj,targetOrigin);
};
<commit_msg>making sure linked items still fire when the annotation overlay is off<commit_after>/*!
 * Annotations Support for Patterns - v0.3
 *
 * Copyright (c) 2013-2014 Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 *
 */

var annotationsPattern = {

	commentsOverlayActive:  false,
	commentsOverlay:        false,
	commentsEmbeddedActive: false,
	commentsEmbedded:       false,
	commentsGathered:       { "commentOverlay": "on", "comments": { } },
	trackedElements:        [ ],

	/**
	* record which annotations are related to this pattern so they can be sent to the viewer when called
	*/
	gatherComments: function() {

		// make sure this only added when we're on a pattern specific view
		if (document.getElementById("sg-patterns") === null) {

			var count = 0;

			for (comment in comments.comments) {
				var item = comments.comments[comment];
				var els  = document.querySelectorAll(item.el);
				if (els.length > 0) {

					count++;
					item.displaynumber = count;

					for (var i = 0; i < els.length; ++i) {
						els[i].onclick = (function(item) {
							return function(e) {

								if (annotationsPattern.commentsOverlayActive) {

									e.preventDefault();
									e.stopPropagation();
									var obj = {};

									// if an element was clicked on while the overlay was already on swap it
									obj = { "displaynumber": item.displaynumber, "el": item.el, "title": item.title, "comment": item.comment };

									var targetOrigin = (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host;
									parent.postMessage(obj,targetOrigin);

								}

							}
						})(item);
					}
				}

			}

		} else {

			var obj = { "commentOverlay": "off" };
			var targetOrigin = (window.location.protocol === "file:") ? "*" : window.location.protocol+"//"+window.location.host;
			parent.postMessage(obj,targetOrigin);

		}

	},

	/**
	* embed a comment by building the sg-annotations div (if necessary) and building an sg-annotation div
	* @param  {Object}      element to check the parent node of
	* @param  {String}      the title of the comment
	* @param  {String}      the comment HTML
	*/
	embedComments: function (el,title,comment) {

		// build the annotation div and add the content to it
		var annotationDiv = document.createElement("div");
		annotationDiv.classList.add("sg-annotation");

		var h3       = document.createElement("h3");
		var p        = document.createElement("p");
		h3.innerHTML = title;
		p.innerHTML  = comment;

		annotationDiv.appendChild(h3);
		annotationDiv.appendChild(p);

		// find the parent element to attach things to
		var parentEl = annotationsPattern.findParent(el);

		// see if a child with the class annotations exists
		var els = parentEl.getElementsByClassName("sg-annotations");
		if (els.length > 0) {
			els[0].appendChild(annotationDiv);
		} else {
			var annotationsDiv = document.createElement("div");
			annotationsDiv.classList.add("sg-annotations");
			annotationsDiv.appendChild(annotationDiv);
			parentEl.appendChild(annotationsDiv);
		}

	},

	/**
	* recursively find the parent of an element to see if it contains the sg-pattern class
	* @param  {Object}      element to check the parent node of
	*/
	findParent: function(el) {

		var parentEl;

		if (el.parentNode.classList.contains("sg-pattern")) {
			return el.parentNode;
		} else {
			parentEl = annotationsPattern.findParent(el.parentNode);
		}

		return parentEl;

	},

	/**
	* toggle the annotation feature on/off
	* based on the great MDN docs at https://developer.mozilla.org/en-US/docs/Web/API/window.postMessage
	* @param  {Object}      event info
	*/
	receiveIframeMessage: function(event) {

		// does the origin sending the message match the current host? if not dev/null the request
		if ((window.location.protocol != "file:") && (event.origin !== window.location.protocol+"//"+window.location.host)) {
			return;
		}

		var targetOrigin = (window.location.protocol == "file:") ? "*" : window.location.protocol+"//"+window.location.host;

		if ((event.data.resize !== undefined) && (annotationsPattern.commentsOverlayActive)) {

			for (var i = 0; i < annotationsPattern.trackedElements.length; ++i) {
				var el = annotationsPattern.trackedElements[i];
				if (window.getComputedStyle(el.element,null).getPropertyValue("max-height") == "0px") {
					el.element.firstChild.style.display = "none";
					parent.postMessage({"annotationState": false, "displayNumber": el.displayNumber },targetOrigin);
				} else {
					el.element.firstChild.style.display = "block";
					parent.postMessage({"annotationState": true, "displayNumber": el.displayNumber },targetOrigin);
				}
			}

		} else if (event.data.commentToggle !== undefined) {

			var i, els, item, displayNum;

			// if this is an overlay make sure it's active for the onclick event
			annotationsPattern.commentsOverlayActive  = false;
			annotationsPattern.commentsEmbeddedActive = false;

			// see which flag to toggle based on if this is a styleguide or view-all page
			if ((event.data.commentToggle === "on") && (document.getElementById("sg-patterns") !== null)) {
				annotationsPattern.commentsEmbeddedActive = true;
			} else if (event.data.commentToggle === "on") {
				annotationsPattern.commentsOverlayActive  = true;
			}

			// if comments overlay is turned off make sure to remove the has-annotation class and pointer
			if (!annotationsPattern.commentsOverlayActive) {
				els = document.querySelectorAll(".has-annotation");
				for (i = 0; i < els.length; i++) {
					els[i].classList.remove("has-annotation");
				}
				els = document.querySelectorAll(".annotation-tip");
				for (i = 0; i < els.length; i++) {
					els[i].style.display = "none";
				}
			}

			// if comments embedding is turned off make sure to hide the annotations div
			if (!annotationsPattern.commentsEmbeddedActive) {
				els = document.getElementsByClassName("sg-annotations");
				for (i = 0; i < els.length; i++) {
					els[i].style.display = "none";
				}
			}

			// if comments overlay is turned on add the has-annotation class and pointer
			if (annotationsPattern.commentsOverlayActive) {

				var count = 0;

				for (i = 0; i < comments.comments.length; i++) {
					item = comments.comments[i];
					els  = document.querySelectorAll(item.el);

					var state = true;

					if (els.length) {

						count++;

						//Loop through all items with annotations
						for (k = 0; k < els.length; k++) {

							els[k].classList.add("has-annotation");

							var span              = document.createElement("span");
							span.innerHTML        = count;
							span.classList.add("annotation-tip");

							if (window.getComputedStyle(els[k],null).getPropertyValue("max-height") == "0px") {
								span.style.display = "none";
								state = false;
							}

							annotationsPattern.trackedElements.push({ "itemel": item.el, "element": els[k], "displayNumber": count, "state": state });

							els[k].insertBefore(span,els[k].firstChild);

						}

					}

				}

				// count elements so it can be used when displaying the results in the viewer
				var count = 0;

				// iterate over the comments in annotations.js
				for(i = 0; i < comments.comments.length; i++) {

					var state = true;

					var item = comments.comments[i];
					var els  = document.querySelectorAll(item.el);

					// if an element is found in the given pattern add it to the overall object so it can be passed when the overlay is turned on
					if (els.length > 0) {
						count++;
						for (k = 0; k < els.length; k++) {
							if (window.getComputedStyle(els[k],null).getPropertyValue("max-height") == "0px") {
								state = false;
							}
						}
						annotationsPattern.commentsGathered.comments[count] = { "el": item.el, "title": item.title, "comment": item.comment, "number": count, "state": state };
					}

				}

				// send the list of annotations for the page back to the parent
				parent.postMessage(annotationsPattern.commentsGathered,targetOrigin);

			} else if (annotationsPattern.commentsEmbeddedActive && !annotationsPattern.commentsEmbedded) {

				// if comment embedding is turned on and comments haven't been embedded yet do it
				for (i = 0; i < comments.comments.length; i++)  {
					item = comments.comments[i];
					els  = document.querySelectorAll(item.el);
					if (els.length > 0) {
						annotationsPattern.embedComments(els[0],item.title,item.comment); //Embed the comment
					}
					annotationsPattern.commentsEmbedded = true;
				}

			} else if (annotationsPattern.commentsEmbeddedActive && annotationsPattern.commentsEmbedded) {

				// if comment embedding is turned on and comments have been embedded simply display them
				els = document.getElementsByClassName("sg-annotations");
				for (i = 0; i < els.length; ++i) {
					els[i].style.display = "block";
				}

			}

		}

	}

};

// add the onclick handlers to the elements that have an annotations
annotationsPattern.gatherComments();
window.addEventListener("message", annotationsPattern.receiveIframeMessage, false);

// before unloading the iframe make sure any active overlay is turned off/closed
window.onbeforeunload = function() {
	var obj = { "commentOverlay": "off" };
	var targetOrigin = (window.location.protocol === "file:") ? "*" : window.location.protocol+"//"+window.location.host;
	parent.postMessage(obj,targetOrigin);
};
<|endoftext|>


<commit_before>Dionysus.module('DionysusApp.AdminHeader', function(Header, Dionysus, Backbone, Marionette, $) {
  var HeaderModel = Backbone.Model.extend({
    initialize: function() {
      Backbone.Select.Me.applyTo( this );
    }
  });

  var HeaderCollection = Backbone.Collection.extend({
    model: HeaderModel,
    initialize: function(models, options) {
      Backbone.Select.One.applyTo( this, models, options );
    }
  });

  var headers = new HeaderCollection([
    { name: "Home", href: "/admin", icon: "home" },
    { name: "Articles", href: "/admin/articles", icon: "edit" },
    { name: "Categories", href: "/admin/categories", icon: "inbox" },
    { name: "Tests", href: "/admin/tests", icon: "lab" }
  ]);

  var LinkView = Marionette.ItemView.extend({
    template: "#admin-header-tpl",
    className: "item",
    tagName: 'a',
    attributes: function() {
      return {
        href: this.model.get('href')
      };
    },
    modelEvents: {
      "selected": "onRender",
      "deselected": "onRender"
    },
    events: {
      "click" : "navigateMenu"
    },
    navigateMenu: function(e) {
      this.model.select();
    },
    onRender: function() {
      this.$el.toggleClass('active', !!this.model.selected);
    }
  });

  var HeaderView = Marionette.CollectionView.extend({
    childView: LinkView,
    className: 'ui menu',
    tagName: 'nav'
  });

  Dionysus.addInitializer(function() {
    Dionysus.headerRegion.show(new HeaderView({collection: headers}));
  });
});

Dionysus.start();<commit_msg>rename header to link<commit_after>Dionysus.module('DionysusApp.AdminHeader', function(Header, Dionysus, Backbone, Marionette, $) {
  var LinkModel = Backbone.Model.extend({
    initialize: function() {
      Backbone.Select.Me.applyTo( this );
    }
  });

  var LinkCollection = Backbone.Collection.extend({
    model: LinkModel,
    initialize: function(models, options) {
      Backbone.Select.One.applyTo( this, models, options );
    }
  });

  var links = new LinkCollection([
    { name: "Home",       href: "/admin",             icon: "home"  },
    { name: "Articles",   href: "/admin/articles",    icon: "edit"  },
    { name: "Categories", href: "/admin/categories",  icon: "inbox" },
    { name: "Tests",      href: "/admin/tests",       icon: "lab"   }
  ]);

  var LinkView = Marionette.ItemView.extend({
    template: "#admin-header-tpl",
    className: "item",
    tagName: 'a',
    attributes: function() {
      return { href: this.model.get('href') };
    },
    modelEvents: {
      "selected": "onRender",
      "deselected": "onRender"
    },
    events: {
      "click" : "navigateMenu"
    },
    navigateMenu: function(e) {
      this.model.select();
    },
    onRender: function() {
      this.$el.toggleClass('active', !!this.model.selected);
    }
  });

  var HeaderView = Marionette.CollectionView.extend({
    childView: LinkView,
    className: 'ui menu',
    tagName: 'nav'
  });

  Dionysus.addInitializer(function() {
    Dionysus.headerRegion.show(new HeaderView({collection: links}));
  });
});

Dionysus.start();<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////////
/**
 * @module ogs.vgl
 */

/*jslint devel: true, forin: true, newcap: true, plusplus: true*/
/*jslint white: true, continue:true, indent: 2*/

/*global vglModule, ogs, vec4, inherit, $*/
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class viewer
 *
 * @param canvas
 * @returns {vglModule.viewer}
 */
//////////////////////////////////////////////////////////////////////////////
vglModule.viewer = function(canvas) {
  'use strict';

  if (!(this instanceof vglModule.viewer)) {
    return new vglModule.viewer(canvas);
  }

  vglModule.object.call(this);

  var m_that = this,
      m_canvas = canvas,
      m_ready = false,
      m_interactorStyle = null,
      m_renderer = vglModule.renderer(),
      m_renderWindow = vglModule.renderWindow(m_canvas);

  m_renderWindow.addRenderer(m_renderer);

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get canvas of the viewer
   *
   * @returns {*}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.canvas = function() {
    return m_canvas;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return render window of the viewer
   *
   * @returns {vglModule.renderWindow}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.renderWindow = function() {
    return m_renderWindow;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize the viewer
   *
   * This is a must call or otherwise render context may not initialized
   * properly.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.init = function() {
    if (m_renderWindow !== null) {
      m_renderWindow.createWindow();
      m_ready = true;
    }
    else {
      console.log("[ERROR] No render window attached");
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get interactor style of the viewer
   *
   * @returns {vglModule.interactorStyle}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.interactorStyle = function() {
    return m_interactorStyle;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set interactor style to be used by the viewer
   *
   * @param {vglModule.interactorStyle} style
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setInteractorStyle = function(style) {
    if (style !== m_interactorStyle) {
      m_interactorStyle = style;
      m_interactorStyle.setViewer(this);
      this.modified();
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse down event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseDown = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      // Only prevent default action for right mouse button
      if (event.button == 2)
        fixedEvent.preventDefault();
      fixedEvent.state = 'down';
      fixedEvent.type = vglModule.command.mousePressEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse up event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseUp = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.state = 'up';
      fixedEvent.type = vglModule.command.mouseReleaseEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse move event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseMove = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.mouseMoveEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle key press event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleKeyPress = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.keyPressEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle context menu event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleContextMenu = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.contextMenuEvent;
      $(m_that).trigger(fixedEvent);
    }

    return false;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get mouse coodinates related to canvas
   *
   * @param event
   * @returns {{x: number, y: number}}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.relMouseCoords = function(event) {
    var totalOffsetX = 0,
        totalOffsetY = 0,
        canvasX = 0,
        canvasY = 0,
        currentElement = m_canvas;

    do {
      totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
      totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
    } while (currentElement = currentElement.offsetParent);

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    return {
      x: canvasX,
      y: canvasY
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Render
   */
  ////////////////////////////////////////////////////////////////////////////
  this.render = function() {
    m_renderWindow.render();
  };

  return this;
};

inherit(vglModule.viewer, vglModule.object);
<commit_msg>Fixed viewer<commit_after>//////////////////////////////////////////////////////////////////////////////
/**
 * @module ogs.vgl
 */

/*jslint devel: true, forin: true, newcap: true, plusplus: true*/
/*jslint white: true, continue:true, indent: 2*/

/*global window, vglModule, ogs, vec4, inherit, $*/
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class viewer
 *
 * @param canvas
 * @returns {vglModule.viewer}
 */
//////////////////////////////////////////////////////////////////////////////
vglModule.viewer = function(canvas) {
  'use strict';

  if (!(this instanceof vglModule.viewer)) {
    return new vglModule.viewer(canvas);
  }

  vglModule.object.call(this);

  var m_that = this,
      m_canvas = canvas,
      m_ready = false,
      m_interactorStyle = null,
      m_renderer = vglModule.renderer(),
      m_renderWindow = vglModule.renderWindow(m_canvas);

  m_renderWindow.addRenderer(m_renderer);

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get canvas of the viewer
   *
   * @returns {*}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.canvas = function() {
    return m_canvas;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Return render window of the viewer
   *
   * @returns {vglModule.renderWindow}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.renderWindow = function() {
    return m_renderWindow;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize the viewer
   *
   * This is a must call or otherwise render context may not initialized
   * properly.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.init = function() {
    if (m_renderWindow !== null) {
      m_renderWindow.createWindow();
      m_ready = true;
    }
    else {
      console.log("[ERROR] No render window attached");
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get interactor style of the viewer
   *
   * @returns {vglModule.interactorStyle}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.interactorStyle = function() {
    return m_interactorStyle;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Set interactor style to be used by the viewer
   *
   * @param {vglModule.interactorStyle} style
   */
  ////////////////////////////////////////////////////////////////////////////
  this.setInteractorStyle = function(style) {
    if (style !== m_interactorStyle) {
      m_interactorStyle = style;
      m_interactorStyle.setViewer(this);
      this.modified();
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse down event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseDown = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      // Only prevent default action for right mouse button
      if (event.button === 2) {
        fixedEvent.preventDefault();
      }
      fixedEvent.state = 'down';
      fixedEvent.type = vglModule.command.mousePressEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse up event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseUp = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.state = 'up';
      fixedEvent.type = vglModule.command.mouseReleaseEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle mouse move event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleMouseMove = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.mouseMoveEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle key press event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleKeyPress = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.keyPressEvent;
      $(m_that).trigger(fixedEvent);
    }

    return true;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle context menu event
   *
   * @param event
   * @returns {boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.handleContextMenu = function(event) {
    if (m_ready === true) {
      var fixedEvent = $.event.fix(event || window.event);
      fixedEvent.preventDefault();
      fixedEvent.type = vglModule.command.contextMenuEvent;
      $(m_that).trigger(fixedEvent);
    }

    return false;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get mouse coodinates related to canvas
   *
   * @param event
   * @returns {{x: number, y: number}}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.relMouseCoords = function(event) {
    var totalOffsetX = 0,
        totalOffsetY = 0,
        canvasX = 0,
        canvasY = 0,
        currentElement = m_canvas;

    do {
      totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
      totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
    } while (currentElement === currentElement.offsetParent);

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    return {
      x: canvasX,
      y: canvasY
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Render
   */
  ////////////////////////////////////////////////////////////////////////////
  this.render = function() {
    m_renderWindow.render();
  };

  return this;
};

inherit(vglModule.viewer, vglModule.object);
<|endoftext|>


<commit_before>var tilelive = require('tilelive');
var grainstore = require('grainstore');
var step = require('step');
var _ = require('underscore');
var assert = require('assert');
var GeojsonRenderer = require('./geojson_renderer');
var CartoPSQL = require('cartodb-psql');
var RendererParams = require('../renderer_params');
var layersFilter = require('../../utils/layer_filter');

var MapnikAdaptor = require('./adaptor');
var BaseAdaptor = require('../base_adaptor');
var DefaultQueryRewriter = require('../../utils/default_query_rewriter');

require('tilelive-mapnik').registerProtocols(tilelive);
require('tilelive-bridge').registerProtocols(tilelive);

var COLUMN_TYPE_GEOMETRY = 'geometry';
var COLUMN_TYPE_RASTER = 'raster';

var COLUMN_TYPE_DEFAULT = COLUMN_TYPE_GEOMETRY;

var DEFAULT_TILE_SIZE = 256;

var FORMAT_MVT = 'mvt';

function MapnikFactory(options) {
    options.grainstore = options.grainstore || {};

    this.supportedFormats = {
        'png': true,
        'png32': true,
        'grid.json': true,
        'geojson': true
    };

    this.supportedFormats[FORMAT_MVT] = true;

    this._mmlStore = new grainstore.MMLStore(options.grainstore);
    this._options = options;

    // Set default mapnik options
    this._mapnik_opts = _.defaults(options.mapnik || {}, {

        geometry_field: 'the_geom_webmercator',

        // Metatile is the number of tiles-per-side that are going
        // to be rendered at once. If all of them will be requested
        // we'd have saved time. If only one will be used, we'd have
        // wasted time.
        //
        // Defaults to 2 as of tilelive-mapnik@0.3.2
        //
        // We'll assume an average of a 4x4 viewport
        metatile: 4,

        // tilelive-mapnik uses an internal cache to store tiles/grids
        // generated when using metatile. This options allow to tune
        // the behaviour for that internal cache.
        metatileCache: {
            // Time an object must stay in the cache until is removed
            ttl: 0,
            // Whether an object must be removed after the first hit
            // Usually you want to use `true` here when ttl>0.
            deleteOnHit: false
        },

        // Override metatile behaviour depending on the format
        formatMetatile: {},

        // Buffer size is the tickness in pixel of a buffer
        // around the rendered (meta?)tile.
        //
        // This is important for labels and other marker that overlap tile boundaries.
        // Setting to 128 ensures no render artifacts.
        // 64 may have artifacts but is faster.
        // Less important if we can turn metatiling on.
        //
        // defaults to 128 as of tilelive-mapnik@0.3.2
        //
        bufferSize: 64,

        // Buffer size behaviour depending on the format
        formatBufferSize: {},

        // retina support, which scale factors are supported
        scale_factors: [1, 2],

        limits: {
            // Time in milliseconds a render request can take before it fails, some notes:
            //  - 0 means no render limit
            //  - it considers metatiling, it naive implementation: (render timeout) * (number of tiles in metatile)
            render: 0,
            // As the render request will finish even if timed out, whether it should be placed in the internal
            // cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
            // the same tile will result in an immediate response, however that will use a lot of more application
            // memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
            // internal cache.
            cacheOnTimeout: true
        },

        // A query-rewriter can be passed to preprocess SQL queries
        // before passing them to Mapnik.
        // The query rewriter should contain one function:
        // - `query(sql, data)` to transform queries using the optional data provided
        // The data passed to this function can be provided for eaach layer
        // through a `query_rewrite_data` entry in the layer options.
        // By default a dummy query rewriter which doesn't alter queries is used.
        queryRewriter: new DefaultQueryRewriter()
    });

    this.tile_scale_factors = this._mapnik_opts.scale_factors.reduce(function(previousValue, currentValue) {
        previousValue[currentValue] = DEFAULT_TILE_SIZE * currentValue;
        return previousValue;
    }, {});

}

module.exports = MapnikFactory;
const NAME = 'mapnik';
module.exports.NAME = NAME;

MapnikFactory.prototype.getName = function() {
    return NAME;
};

MapnikFactory.prototype.supportsFormat = function(format) {
    return !!this.supportedFormats[format];
};

MapnikFactory.prototype.getAdaptor = function(renderer, format, onTileErrorStrategy) {
    if (format === FORMAT_MVT || format === 'geojson') {
        return new BaseAdaptor(renderer, format, onTileErrorStrategy);
    }
    return new MapnikAdaptor(renderer, format, onTileErrorStrategy);
};

MapnikFactory.prototype.getRenderer = function (mapConfig, format, options, callback) {
    if (format === 'geojson') {
        return this.getGeoJsonRenderer(mapConfig, format, options, callback);
    }
    return this.getMapnikRenderer(mapConfig, format, options, callback);
};

MapnikFactory.prototype.getGeoJsonRenderer = function(mapConfig, format, options, callback) {
    var requestDbParams = RendererParams.dbParamsFromReqParams(options.params);
    var datasource = mapConfig.getLayerDatasource(options.layer);
    var dbParams = _.extend({}, requestDbParams, datasource);
    var dbPoolParams = this._mapnik_opts.geojson.dbPoolParams;
    var cartoPSQL = new CartoPSQL(dbParams, dbPoolParams);

    var layerFilter = options.layer;
    var rendererOptions = this._mapnik_opts.geojson || {};
    var bufferSize = this.getBufferSize(mapConfig, format, rendererOptions);
    rendererOptions.bufferSize = bufferSize;
    var layers = [];

    if (typeof layerFilter === 'number') {
        layers = [ mapConfig.getLayer(layerFilter) ];
    } else if (layerFilter && layerFilter !== 'mapnik') {
        var filteredLayers;

        try {
            filteredLayers = layersFilter(mapConfig, layerFilter);
        } catch (err) {
            return callback(err);
        }

        layers = filteredLayers.map(function (filteredLayer) {
            return mapConfig.getLayer(+filteredLayer);
        });
    } else {
        layers = mapConfig.getLayers();
    }

    callback(null, new GeojsonRenderer(cartoPSQL, layers, rendererOptions));
};

MapnikFactory.prototype.getMapnikRenderer = function(mapConfig, format, options, callback) {
    var self = this;
    var limits = _.defaults({}, options.limits, this._mapnik_opts.limits);
    var mmlBuilderConfig = this.mapConfigToMMLBuilderConfig(mapConfig, this._mapnik_opts.queryRewriter, options.layer);
    var params = _.defaults(mmlBuilderConfig, options.params);

    // fix layer index
    // see https://github.com/CartoDB/Windshaft/blob/0.43.0/lib/windshaft/backends/map_validator.js#L69-L81
    if (!!params.layer) {
        params.layer = mapConfig.getLayerIndexByType('mapnik', params.layer);
    }

    var scaleFactor = _.isUndefined(params.scale_factor) ? 1 : +params.scale_factor,
        tileSize = this.tile_scale_factors[scaleFactor];

    if (!tileSize) {
        var err = new Error('Tile with specified resolution not found');
        err.http_status = 404;
        return callback(err);
    }

    step(
        function initBuilder() {
            var mmlBuilderOptions = {};
            if (format === 'png32') {
                mmlBuilderOptions.mapnik_tile_format = 'png';
            }

            self._mmlStore.mml_builder(params, mmlBuilderOptions).toXML(this);
        },
        function loadMapnik(err, xml) {
            assert.ifError(err);

            var query = {
                metatile: self.getMetatile(format),
                metatileCache: self._mapnik_opts.metatileCache,
                bufferSize: self.getBufferSize(mapConfig, format),
                poolSize: self._mapnik_opts.poolSize,
                scale: scaleFactor,
                tileSize: tileSize,
                autoLoadFonts: false,
                internal_cache: false,
                limits: limits
            };

            var isMvt = format === FORMAT_MVT;

            // build full document to pass to tilelive
            var uri = {
                pathname: '', // to avoid TypeError: https://nodejs.org/api/path.html#path_path_dirname_path
                query: query,
                protocol: isMvt ? 'bridge:' : 'mapnik:',
                xml: xml,
                strict: !!params.strict
            };

            if (isMvt) {
                uri.gzip = false;
            }

            // hand off to tilelive to create a renderer
            tilelive.load(uri, this);
        },
        function returnCallback(err, source) {
            callback(err, source);
        }
    );
};

MapnikFactory.prototype.getMetatile = function(format) {
    var metatile = this._mapnik_opts.metatile;
    if (Number.isFinite(this._mapnik_opts.formatMetatile[format])) {
        metatile = this._mapnik_opts.formatMetatile[format];
    }
    return metatile;
};

MapnikFactory.prototype.getBufferSizeFromOptions = function(format, options) {
    return options && options.formatBufferSize && Number.isFinite(options.formatBufferSize[format]) ?
        options.formatBufferSize[format] :
        options.bufferSize;
};

MapnikFactory.prototype.getBufferSize = function(mapConfig, format, options) {
    options = options || this._mapnik_opts;

    if (Number.isFinite(mapConfig.getBufferSize(format))) {
        return mapConfig.getBufferSize(format);
    }

    return this.getBufferSizeFromOptions(format, options);
};

// TODO: fix this
MapnikFactory.prototype.mapConfigToMMLBuilderConfig = function(mapConfig, queryRewriter, layerFilter) {
    // jshint maxcomplexity: 8
    var self = this;
    var options = {
        ids: [],
        sql: [],
        originalSql: [],
        style: [],
        style_version: [],
        interactivity: [],
        ttl: 0,
        datasource_extend: [],
        extra_ds_opts: [],
        gcols: []
    };

    var filteredLayerIndexes = layersFilter(mapConfig, layerFilter);
    filteredLayerIndexes.reduce(function (options, layerIndex) {
        var layer = mapConfig.getLayer(layerIndex);

        validateLayer(layer, layerIndex);

        options.ids.push(mapConfig.getLayerId(layerIndex));
        var lyropt = layer.options;

        options.style.push(lyropt.cartocss);
        options.style_version.push(lyropt.cartocss_version);

        var query = queryRewriter.query(lyropt.sql, lyropt.query_rewrite_data);
        var queryOptions = prepareQuery(query, lyropt.geom_column, lyropt.geom_type, self._mapnik_opts);

        options.sql.push(queryOptions.sql);
        options.originalSql.push(query);

        options.gcols.push({
            type: queryOptions.geomColumnType, // grainstore allows undefined here
            name: queryOptions.geomColumnName
        });

        var extra_opt = {};
        if ( lyropt.hasOwnProperty('raster_band') ) {
            extra_opt.band = lyropt.raster_band;
        }
        options.datasource_extend.push(mapConfig.getLayerDatasource(layerIndex));
        options.extra_ds_opts.push( extra_opt );

        return options;
    }, options);

    if (!options.sql.length) {
        throw new Error("No 'mapnik' layers in MapConfig");
    }

    if (!options.gcols.length) {
        options.gcols = undefined;
    }

    // Grainstore limits interactivity to one layer. If there are more than one layer in layer filter then interactivity
    // won't be passed to grainstore (due to format requested is png, only one layer is allowed for grid.json format)
    if (filteredLayerIndexes.length === 1) {
        var lyrInteractivity = mapConfig.getLayer(filteredLayerIndexes[0]);
        var lyropt = lyrInteractivity.options;

        if (lyropt.interactivity) {
            // NOTE: interactivity used to be a string as of version 1.0.0
            if (_.isArray(lyropt.interactivity)) {
                lyropt.interactivity = lyropt.interactivity.join(',');
            }

            options.interactivity.push(lyropt.interactivity);
            // grainstore needs to know the layer index to take the interactivity, forced to be 0.
            options.layer = 0;
        }
    }

    return options;
};

function validateLayer(layer, layerIdx) {
    var layerOptions = layer.options;

    if ( ! layerOptions.hasOwnProperty('cartocss') ) {
        throw new Error("Missing cartocss for layer " + layerIdx + " options");
    }
    if ( ! layerOptions.hasOwnProperty('cartocss_version') ) {
        throw new Error("Missing cartocss_version for layer " + layerIdx + " options");
    }
    if ( ! layerOptions.hasOwnProperty('sql') ) {
        throw new Error("Missing sql for layer " + layerIdx + " options");
    }

    // It doesn't make sense to have interactivity with raster layers so we raise
    // an error in case someone tries to instantiate a raster layer with interactivity.
    if (isRasterColumnType(layerOptions.geom_type) && !_.isUndefined(layerOptions.interactivity)) {
        throw new Error("Mapnik raster layers do not support interactivity");
    }
}

// Wrap SQL requests in mapnik format if sent
function prepareQuery(userSql, geomColumnName, geomColumnType, options) {

    // remove trailing ';'
    userSql = userSql.replace(/;\s*$/, '');

    geomColumnName = geomColumnName || options.geometry_field;
    geomColumnType = geomColumnType || COLUMN_TYPE_DEFAULT;

    return {
        sql: '(' + userSql + ') as cdbq',
        geomColumnName: geomColumnName,
        geomColumnType: geomColumnType
    };
}

function isRasterColumnType(geomColumnType) {
    return geomColumnType === COLUMN_TYPE_RASTER;
}
<commit_msg>Keep the mapnik filter to not rely on outer filtering<commit_after>var tilelive = require('tilelive');
var grainstore = require('grainstore');
var step = require('step');
var _ = require('underscore');
var assert = require('assert');
var GeojsonRenderer = require('./geojson_renderer');
var CartoPSQL = require('cartodb-psql');
var RendererParams = require('../renderer_params');
var layersFilter = require('../../utils/layer_filter');

var MapnikAdaptor = require('./adaptor');
var BaseAdaptor = require('../base_adaptor');
var DefaultQueryRewriter = require('../../utils/default_query_rewriter');

require('tilelive-mapnik').registerProtocols(tilelive);
require('tilelive-bridge').registerProtocols(tilelive);

var COLUMN_TYPE_GEOMETRY = 'geometry';
var COLUMN_TYPE_RASTER = 'raster';

var COLUMN_TYPE_DEFAULT = COLUMN_TYPE_GEOMETRY;

var DEFAULT_TILE_SIZE = 256;

var FORMAT_MVT = 'mvt';

function MapnikFactory(options) {
    options.grainstore = options.grainstore || {};

    this.supportedFormats = {
        'png': true,
        'png32': true,
        'grid.json': true,
        'geojson': true
    };

    this.supportedFormats[FORMAT_MVT] = true;

    this._mmlStore = new grainstore.MMLStore(options.grainstore);
    this._options = options;

    // Set default mapnik options
    this._mapnik_opts = _.defaults(options.mapnik || {}, {

        geometry_field: 'the_geom_webmercator',

        // Metatile is the number of tiles-per-side that are going
        // to be rendered at once. If all of them will be requested
        // we'd have saved time. If only one will be used, we'd have
        // wasted time.
        //
        // Defaults to 2 as of tilelive-mapnik@0.3.2
        //
        // We'll assume an average of a 4x4 viewport
        metatile: 4,

        // tilelive-mapnik uses an internal cache to store tiles/grids
        // generated when using metatile. This options allow to tune
        // the behaviour for that internal cache.
        metatileCache: {
            // Time an object must stay in the cache until is removed
            ttl: 0,
            // Whether an object must be removed after the first hit
            // Usually you want to use `true` here when ttl>0.
            deleteOnHit: false
        },

        // Override metatile behaviour depending on the format
        formatMetatile: {},

        // Buffer size is the tickness in pixel of a buffer
        // around the rendered (meta?)tile.
        //
        // This is important for labels and other marker that overlap tile boundaries.
        // Setting to 128 ensures no render artifacts.
        // 64 may have artifacts but is faster.
        // Less important if we can turn metatiling on.
        //
        // defaults to 128 as of tilelive-mapnik@0.3.2
        //
        bufferSize: 64,

        // Buffer size behaviour depending on the format
        formatBufferSize: {},

        // retina support, which scale factors are supported
        scale_factors: [1, 2],

        limits: {
            // Time in milliseconds a render request can take before it fails, some notes:
            //  - 0 means no render limit
            //  - it considers metatiling, it naive implementation: (render timeout) * (number of tiles in metatile)
            render: 0,
            // As the render request will finish even if timed out, whether it should be placed in the internal
            // cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
            // the same tile will result in an immediate response, however that will use a lot of more application
            // memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
            // internal cache.
            cacheOnTimeout: true
        },

        // A query-rewriter can be passed to preprocess SQL queries
        // before passing them to Mapnik.
        // The query rewriter should contain one function:
        // - `query(sql, data)` to transform queries using the optional data provided
        // The data passed to this function can be provided for eaach layer
        // through a `query_rewrite_data` entry in the layer options.
        // By default a dummy query rewriter which doesn't alter queries is used.
        queryRewriter: new DefaultQueryRewriter()
    });

    this.tile_scale_factors = this._mapnik_opts.scale_factors.reduce(function(previousValue, currentValue) {
        previousValue[currentValue] = DEFAULT_TILE_SIZE * currentValue;
        return previousValue;
    }, {});

}

module.exports = MapnikFactory;
const NAME = 'mapnik';
module.exports.NAME = NAME;

MapnikFactory.prototype.getName = function() {
    return NAME;
};

MapnikFactory.prototype.supportsFormat = function(format) {
    return !!this.supportedFormats[format];
};

MapnikFactory.prototype.getAdaptor = function(renderer, format, onTileErrorStrategy) {
    if (format === FORMAT_MVT || format === 'geojson') {
        return new BaseAdaptor(renderer, format, onTileErrorStrategy);
    }
    return new MapnikAdaptor(renderer, format, onTileErrorStrategy);
};

MapnikFactory.prototype.getRenderer = function (mapConfig, format, options, callback) {
    if (format === 'geojson') {
        return this.getGeoJsonRenderer(mapConfig, format, options, callback);
    }
    return this.getMapnikRenderer(mapConfig, format, options, callback);
};

MapnikFactory.prototype.getGeoJsonRenderer = function(mapConfig, format, options, callback) {
    var requestDbParams = RendererParams.dbParamsFromReqParams(options.params);
    var datasource = mapConfig.getLayerDatasource(options.layer);
    var dbParams = _.extend({}, requestDbParams, datasource);
    var dbPoolParams = this._mapnik_opts.geojson.dbPoolParams;
    var cartoPSQL = new CartoPSQL(dbParams, dbPoolParams);

    var layerFilter = options.layer;
    var rendererOptions = this._mapnik_opts.geojson || {};
    var bufferSize = this.getBufferSize(mapConfig, format, rendererOptions);
    rendererOptions.bufferSize = bufferSize;
    var layers = [];

    if (typeof layerFilter === 'number') {
        layers = [ mapConfig.getLayer(layerFilter) ];
    } else if (layerFilter && layerFilter !== 'mapnik') {
        var filteredLayers;

        try {
            filteredLayers = layersFilter(mapConfig, layerFilter);
        } catch (err) {
            return callback(err);
        }

        layers = filteredLayers.map(function (filteredLayer) {
            return mapConfig.getLayer(+filteredLayer);
        });
    } else {
        layers = mapConfig.getLayers();
    }

    callback(null, new GeojsonRenderer(cartoPSQL, layers, rendererOptions));
};

MapnikFactory.prototype.getMapnikRenderer = function(mapConfig, format, options, callback) {
    var self = this;
    var limits = _.defaults({}, options.limits, this._mapnik_opts.limits);
    var mmlBuilderConfig = this.mapConfigToMMLBuilderConfig(mapConfig, this._mapnik_opts.queryRewriter, options.layer);
    var params = _.defaults(mmlBuilderConfig, options.params);

    // fix layer index
    // see https://github.com/CartoDB/Windshaft/blob/0.43.0/lib/windshaft/backends/map_validator.js#L69-L81
    if (!!params.layer) {
        params.layer = mapConfig.getLayerIndexByType('mapnik', params.layer);
    }

    var scaleFactor = _.isUndefined(params.scale_factor) ? 1 : +params.scale_factor,
        tileSize = this.tile_scale_factors[scaleFactor];

    if (!tileSize) {
        var err = new Error('Tile with specified resolution not found');
        err.http_status = 404;
        return callback(err);
    }

    step(
        function initBuilder() {
            var mmlBuilderOptions = {};
            if (format === 'png32') {
                mmlBuilderOptions.mapnik_tile_format = 'png';
            }

            self._mmlStore.mml_builder(params, mmlBuilderOptions).toXML(this);
        },
        function loadMapnik(err, xml) {
            assert.ifError(err);

            var query = {
                metatile: self.getMetatile(format),
                metatileCache: self._mapnik_opts.metatileCache,
                bufferSize: self.getBufferSize(mapConfig, format),
                poolSize: self._mapnik_opts.poolSize,
                scale: scaleFactor,
                tileSize: tileSize,
                autoLoadFonts: false,
                internal_cache: false,
                limits: limits
            };

            var isMvt = format === FORMAT_MVT;

            // build full document to pass to tilelive
            var uri = {
                pathname: '', // to avoid TypeError: https://nodejs.org/api/path.html#path_path_dirname_path
                query: query,
                protocol: isMvt ? 'bridge:' : 'mapnik:',
                xml: xml,
                strict: !!params.strict
            };

            if (isMvt) {
                uri.gzip = false;
            }

            // hand off to tilelive to create a renderer
            tilelive.load(uri, this);
        },
        function returnCallback(err, source) {
            callback(err, source);
        }
    );
};

MapnikFactory.prototype.getMetatile = function(format) {
    var metatile = this._mapnik_opts.metatile;
    if (Number.isFinite(this._mapnik_opts.formatMetatile[format])) {
        metatile = this._mapnik_opts.formatMetatile[format];
    }
    return metatile;
};

MapnikFactory.prototype.getBufferSizeFromOptions = function(format, options) {
    return options && options.formatBufferSize && Number.isFinite(options.formatBufferSize[format]) ?
        options.formatBufferSize[format] :
        options.bufferSize;
};

MapnikFactory.prototype.getBufferSize = function(mapConfig, format, options) {
    options = options || this._mapnik_opts;

    if (Number.isFinite(mapConfig.getBufferSize(format))) {
        return mapConfig.getBufferSize(format);
    }

    return this.getBufferSizeFromOptions(format, options);
};

// TODO: fix this
MapnikFactory.prototype.mapConfigToMMLBuilderConfig = function(mapConfig, queryRewriter, layerFilter) {
    // jshint maxcomplexity: 8
    var self = this;
    var options = {
        ids: [],
        sql: [],
        originalSql: [],
        style: [],
        style_version: [],
        interactivity: [],
        ttl: 0,
        datasource_extend: [],
        extra_ds_opts: [],
        gcols: []
    };

    var filteredLayerIndexes = layersFilter(mapConfig, layerFilter);
    filteredLayerIndexes.filter(layerIndex => {
        return mapConfig.layerType(layerIndex) === 'mapnik';
    })
    .reduce(function (options, layerIndex) {
        var layer = mapConfig.getLayer(layerIndex);

        validateLayer(layer, layerIndex);

        options.ids.push(mapConfig.getLayerId(layerIndex));
        var lyropt = layer.options;

        options.style.push(lyropt.cartocss);
        options.style_version.push(lyropt.cartocss_version);

        var query = queryRewriter.query(lyropt.sql, lyropt.query_rewrite_data);
        var queryOptions = prepareQuery(query, lyropt.geom_column, lyropt.geom_type, self._mapnik_opts);

        options.sql.push(queryOptions.sql);
        options.originalSql.push(query);

        options.gcols.push({
            type: queryOptions.geomColumnType, // grainstore allows undefined here
            name: queryOptions.geomColumnName
        });

        var extra_opt = {};
        if ( lyropt.hasOwnProperty('raster_band') ) {
            extra_opt.band = lyropt.raster_band;
        }
        options.datasource_extend.push(mapConfig.getLayerDatasource(layerIndex));
        options.extra_ds_opts.push( extra_opt );

        return options;
    }, options);

    if (!options.sql.length) {
        throw new Error("No 'mapnik' layers in MapConfig");
    }

    if (!options.gcols.length) {
        options.gcols = undefined;
    }

    // Grainstore limits interactivity to one layer. If there are more than one layer in layer filter then interactivity
    // won't be passed to grainstore (due to format requested is png, only one layer is allowed for grid.json format)
    if (filteredLayerIndexes.length === 1) {
        var lyrInteractivity = mapConfig.getLayer(filteredLayerIndexes[0]);
        var lyropt = lyrInteractivity.options;

        if (lyropt.interactivity) {
            // NOTE: interactivity used to be a string as of version 1.0.0
            if (_.isArray(lyropt.interactivity)) {
                lyropt.interactivity = lyropt.interactivity.join(',');
            }

            options.interactivity.push(lyropt.interactivity);
            // grainstore needs to know the layer index to take the interactivity, forced to be 0.
            options.layer = 0;
        }
    }

    return options;
};

function validateLayer(layer, layerIdx) {
    var layerOptions = layer.options;

    if ( ! layerOptions.hasOwnProperty('cartocss') ) {
        throw new Error("Missing cartocss for layer " + layerIdx + " options");
    }
    if ( ! layerOptions.hasOwnProperty('cartocss_version') ) {
        throw new Error("Missing cartocss_version for layer " + layerIdx + " options");
    }
    if ( ! layerOptions.hasOwnProperty('sql') ) {
        throw new Error("Missing sql for layer " + layerIdx + " options");
    }

    // It doesn't make sense to have interactivity with raster layers so we raise
    // an error in case someone tries to instantiate a raster layer with interactivity.
    if (isRasterColumnType(layerOptions.geom_type) && !_.isUndefined(layerOptions.interactivity)) {
        throw new Error("Mapnik raster layers do not support interactivity");
    }
}

// Wrap SQL requests in mapnik format if sent
function prepareQuery(userSql, geomColumnName, geomColumnType, options) {

    // remove trailing ';'
    userSql = userSql.replace(/;\s*$/, '');

    geomColumnName = geomColumnName || options.geometry_field;
    geomColumnType = geomColumnType || COLUMN_TYPE_DEFAULT;

    return {
        sql: '(' + userSql + ') as cdbq',
        geomColumnName: geomColumnName,
        geomColumnType: geomColumnType
    };
}

function isRasterColumnType(geomColumnType) {
    return geomColumnType === COLUMN_TYPE_RASTER;
}
<|endoftext|>


<commit_before>$(document).ready( function() {
  fixButtons();
  bindFileInput($("#icon_files"));
});

function fixButtons() {
  $(".icon-row-add").hide().unbind();
  $(".icon-row-add").last().show();
  $(".icon-row-rem").show();
  $(".icon-row-rem").first().hide();
  bindAdd();
  bindRem();
  $("#icon-table tr.icon-row:odd td").removeClass('even').addClass("odd");
  $("#icon-table tr.icon-row:even td").removeClass('odd').addClass("even");
};

function bindAdd() {
  $(".icon-row-add").click(function () {
    addNewRow();
    fixButtons();
  });
};

function addNewRow() {
  var new_row = $(".icon-row:last").clone();
  var index = new_row.data('index') + 1;
  new_row.attr('data-index', index);

  // clear all input values in the clone
  var inputs = new_row.find('input');
  inputs.val('');

  // handle the URL field specially
  // because uploads have special UI
  var urlField = inputs.first();
  new_row.find('.conf').remove();
  urlField.show();
  urlField.attr('id', 'icons_'+index+'_url');

  new_row.insertBefore($(".submit-row"));
  return index;
};

function bindRem() {
  $(".icon-row-rem").click(function () {
    var rem_row = $(this).parent().parent();
    rem_row.remove();
    fixButtons();
  });
};

function bindFileInput(fileInput) {
    var form         = $('form.icon-upload');
    var submitButton = form.find('input[type="submit"]');
    var formData     = form.data('form-data');

    fileInput.fileupload({
      fileInput:       fileInput,
      url:             form.data('url'),
      type:            'POST',
      autoUpload:       true,
      formData:         formData,
      paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
      dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
      replaceFileInput: false,

      add: function (e, data) {
        var fileType = data.files[0].type;
        if (!fileType.startsWith('image/')) {
          alert("You must upload files with an image filetype such as .png or .jpg - please retry with a valid file.");
          return;
        } else if (fileType === 'image/tiff') {
          alert("Unfortunately, .tiff files are only supported by Safari - please retry with a valid file.");
          return
        }

        formData["Content-Type"] = fileType;
        data.formData = formData;
        data.submit();
        fileInput.val('');
      },
      start: function (e) {
        submitButton.prop('disabled', true);
      },
      done: function(e, data) {
        submitButton.prop('disabled', false);

        // extract key and generate URL from response
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = 'https://d1anwqy6ci9o1i.cloudfront.net/' + key;

        // create hidden field
        var iconIndex = addNewRow();
        var urlInput = $("#icons_"+iconIndex+"_url");
        var urlCell = $(urlInput.parents('td:first'));
        urlInput.hide().val(url);
        urlCell.find('.conf').remove();
        var uploaded = ' Uploaded ' + data.files[0].name;
        urlCell.append(""+uploaded+"");
        cleanUpRows();
      },
      fail: function(e, data) {
        var iconIndex = data.iconIndex;
        submitButton.prop('disabled', false);
        var response = data.response().jqXHR
        var policyExpired = response.responseText.includes("Invalid according to Policy: Policy expired.");
        if (!policyExpired) { policyExpired = response.responseText.includes("Idle connections will be closed."); }
        var badFiletype = response.responseText.includes("Policy Condition failed") && response.responseText.includes('"$Content-Type", "image/"');
        var bugsData = {
          'response_status': response.status,
          'response_body': response.responseText,
          'response_text': response.statusText,
          'file_name': data.files[0].name,
          'file_type': data.files[0].type,
        };
        if (policyExpired) {
          alert("Your upload permissions appear to have expired. Please refresh the page and try again.");
        } else if (badFiletype) {
          alert("You must upload files with an image filetype such as .png or .jpg - please retry with a valid file.");
        } else {
          $.post('/bugs', bugsData);
          alert("Upload of " + data.files[0].name + " failed, Marri has been notified.");
        }
      },
    });
};

function cleanUpRows() {
  $(".icon-row").each(function (index) {
    var anySet = false;
    if ($(this).find('.conf').length > 0) { return true; }
    $(this).find('input').each(function() {
      if($(this).val() != '') {
        anySet = true;
        return false;
      }
    });
    if (!anySet) { $(this).remove(); }
  });
  $(".icon-row").each(function (index) {
    $(this).attr('data-index', index);
    $(this).find('input').first().attr('id', 'icons_'+index+'_url');
  });
  fixButtons();
};
<commit_msg>Make icon uploads set default keyword to file name<commit_after>$(document).ready( function() {
  fixButtons();
  bindFileInput($("#icon_files"));
});

function fixButtons() {
  $(".icon-row-add").hide().unbind();
  $(".icon-row-add").last().show();
  $(".icon-row-rem").show();
  $(".icon-row-rem").first().hide();
  bindAdd();
  bindRem();
  $("#icon-table tr.icon-row:odd td").removeClass('even').addClass("odd");
  $("#icon-table tr.icon-row:even td").removeClass('odd').addClass("even");
};

function bindAdd() {
  $(".icon-row-add").click(function () {
    addNewRow();
    fixButtons();
  });
};

function addNewRow() {
  var new_row = $(".icon-row:last").clone();
  var index = new_row.data('index') + 1;
  new_row.attr('data-index', index);

  // clear all input values in the clone
  var inputs = new_row.find('input');
  inputs.val('');

  // handle the URL field specially
  // because uploads have special UI
  var urlField = inputs.first();
  new_row.find('.conf').remove();
  urlField.show();
  urlField.attr('id', 'icons_'+index+'_url');

  new_row.insertBefore($(".submit-row"));
  return index;
};

function bindRem() {
  $(".icon-row-rem").click(function () {
    var rem_row = $(this).parent().parent();
    rem_row.remove();
    fixButtons();
  });
};

function bindFileInput(fileInput) {
    var form         = $('form.icon-upload');
    var submitButton = form.find('input[type="submit"]');
    var formData     = form.data('form-data');

    fileInput.fileupload({
      fileInput:       fileInput,
      url:             form.data('url'),
      type:            'POST',
      autoUpload:       true,
      formData:         formData,
      paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
      dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
      replaceFileInput: false,

      add: function (e, data) {
        var fileType = data.files[0].type;
        if (!fileType.startsWith('image/')) {
          alert("You must upload files with an image filetype such as .png or .jpg - please retry with a valid file.");
          return;
        } else if (fileType === 'image/tiff') {
          alert("Unfortunately, .tiff files are only supported by Safari - please retry with a valid file.");
          return;
        }

        formData["Content-Type"] = fileType;
        data.formData = formData;
        data.submit();
        fileInput.val('');
      },
      start: function (e) {
        submitButton.prop('disabled', true);
      },
      done: function(e, data) {
        submitButton.prop('disabled', false);

        // extract key and generate URL from response
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = 'https://d1anwqy6ci9o1i.cloudfront.net/' + key;

        // create hidden field
        var iconIndex = addNewRow();
        var urlInput = $("#icons_"+iconIndex+"_url");
        var urlCell = $(urlInput.parents('td:first'));
        urlInput.hide().val(url);
        urlCell.find('.conf').remove();

        // update keyword box with data.files[0].name minus file extension
        var keyword = data.files[0].name;
        var fileExt = keyword.split('.').slice(-1)[0];
        if (fileExt != keyword)
          keyword = keyword.replace('.'+fileExt, '');
        $(".icon-row[data-index='" + iconIndex + "'] input[id$='_keyword']").val(keyword);

        var uploaded = ' Uploaded ' + data.files[0].name;
        urlCell.append(""+uploaded+"");
        cleanUpRows();
      },
      fail: function(e, data) {
        var iconIndex = data.iconIndex;
        submitButton.prop('disabled', false);
        var response = data.response().jqXHR
        var policyExpired = response.responseText.includes("Invalid according to Policy: Policy expired.");
        if (!policyExpired) { policyExpired = response.responseText.includes("Idle connections will be closed."); }
        var badFiletype = response.responseText.includes("Policy Condition failed") && response.responseText.includes('"$Content-Type", "image/"');
        var bugsData = {
          'response_status': response.status,
          'response_body': response.responseText,
          'response_text': response.statusText,
          'file_name': data.files[0].name,
          'file_type': data.files[0].type,
        };
        if (policyExpired) {
          alert("Your upload permissions appear to have expired. Please refresh the page and try again.");
        } else if (badFiletype) {
          alert("You must upload files with an image filetype such as .png or .jpg - please retry with a valid file.");
        } else {
          $.post('/bugs', bugsData);
          alert("Upload of " + data.files[0].name + " failed, Marri has been notified.");
        }
      },
    });
};

function cleanUpRows() {
  $(".icon-row").each(function (index) {
    var anySet = false;
    if ($(this).find('.conf').length > 0) { return true; }
    $(this).find('input').each(function() {
      if($(this).val() != '') {
        anySet = true;
        return false;
      }
    });
    if (!anySet) { $(this).remove(); }
  });
  $(".icon-row").each(function (index) {
    $(this).attr('data-index', index);
    $(this).find('input').first().attr('id', 'icons_'+index+'_url');
  });
  fixButtons();
};
<|endoftext|>