<commit_before>/*******************
VISUALIZATION FUNCTIONS
Author: Ethan Gruber
Modification Date: November 2012
Description: Functions used in the Hoard Display and Analyze pipelines
for manipulating forms and rendering tables in the form of highcharts
********************/

$(document).ready(function () {
	$("#tabs").tabs();

	/**
	* Visualize an HTML table using Highcharts. The top (horizontal) header
	* is used for series names, and the left (vertical) header is used
	* for category names. This function is based on jQuery.
	* @param {Object} table The reference to the HTML table to visualize
	* @param {Object} options Highcharts options
	*/
	Highcharts.visualize = function (table, options) {
		// the categories
		options.xAxis.categories =[];
		$('tbody th', table).each(function (i) {
			options.xAxis.categories.push(this.innerHTML);
		});

		// the data series
		options.series =[];
		$('tr', table).each(function (i) {
			var tr = this;
			$('th, td', tr).each(function (j) {
				if (j > 0) {
					// skip first column
					if (i == 0) {
						// get the name and init the series
						options.series[j - 1] = {
							name: this.innerHTML,
							data:[]
						};
					} else {
						// add values
						options.series[j - 1].data.push(this.innerHTML == 'null'? null: parseFloat(this.innerHTML));
					}
				}
			});
		});
		console.log(options.series);

		var chart = new Highcharts.Chart(options);
	}

	/** CREATE HIGHCHARTS BASED ON HTML TABLES **/
	$('.calculate').each(function () {
		var id = $(this).attr('id').split('-')[0];
		var formId = $(this).siblings('form').attr('id');
		var type = $('#' + formId + ' input:radio[name=type]:checked').val();
		var chartType = $('#' + formId + ' input:radio[name=chartType]:checked').val();
		var optionString = $('#options-input').val();
		var formOptions = processOptions(optionString);

		var table = $(this),
		options = {
			chart: {
				renderTo: id + '-container',
				type: chartType
			},
			title: {
				text: $(this).children('caption').text()
			},
			legend: {
				enabled: true
			},
			xAxis: {
				labels: {
					rotation: - 45,
					align: 'right',
					style: {
						fontSize: '11px',
						fontFamily: 'Verdana, sans-serif'
					}
				}
			},
			yAxis: {
				max: (type == 'count'? null: 100),
				min: (type == 'count'? null: 0),
				title: {
					text: (type == 'percentage'? 'Percentage': 'Occurrences')
				}
			},
			tooltip: {
				formatter: function () {
					return this.y + (type == 'percentage'? '%: ': ' coins: ') + this.x;
				}
			},
			plotOptions: {
				series: {
					stacking: (type == 'count'? formOptions.stacking: null)
				}
			}
		};
		Highcharts.visualize(table, options);
	});

	/***** CREATE CHART FOR DATES *****/
	if ($('#dateData').text().length > 0) {
		var data = eval($('#dateData').text());
		var type = $('#date-form input:radio[name=type]:checked').val();
		var chartType = $('#date-form input:radio[name=chartType]:checked').val();
		var chart;
		chart = new Highcharts.Chart({
			chart: {
				renderTo: 'dateChart',
				type: chartType
			},
			title: {
				text: $('#dateData').attr('title')
			},
			xAxis: {
				type: 'datetime',
				labels: {
					rotation: - 45,
					align: 'right',
					formatter: function () {
						var year = this.value;
						if (year < 0) {
							return Math.abs(year) + ' B.C.';
						} else {
							return 'A.D. ' + year;
						}
					}
				}
			},
			yAxis: {
				max: (type == 'count'? null: 100),
				min: (type == 'count'? null: 0),
				title: {
					text: (type == 'count'? 'Occurrences': 'Percentage')
				}
			},
			tooltip: {
				formatter: function () {
					if (this.x < 0) {
						var year = Math.abs(this.x) + ' B.C.';
					} else {
						var year = 'A.D. ' + this.x;
					}
					return this.y + (type == 'count'? ' coins: ': '%: ') + year;
				}
			},
			exporting: {
				enabled: true,
				width: 1200
			},
			series: data
		});
	}

	function processOptions(optionString) {
		var options = {
		};
		var result = optionString.split('|');
		for (i = 0; i < result.length; i++) {
			var key = result[i].split(':')[0];
			var value = result[i].split(':')[1];
			options[key] = value;
		}
		return options;
	}

	//lock bar and column charts for count in date visualization--line, spine, area, and areaspline for percentages
	$('#date-form input[name=type]').change(function () {
		if ($(this).val() == 'count') {
			//enable quantification-based visualizations
			$('#date-form').find('input[value=bar]').attr('disabled', false);
			$('#date-form').find('input[value=column]').attr('disabled', false);
			//disable percentage-based visualizations
			$('#date-form').find('input[value=line]').attr('disabled', true);
			$('#date-form').find('input[value=area]').attr('disabled', true);
			$('#date-form').find('input[value=spline]').attr('disabled', true);
			$('#date-form').find('input[value=areaspline]').attr('disabled', true);

			//set column as default
			$('#date-form').find('input[value=column]').attr('checked', true);
		} else {
			//enable quantification-based visualizations
			$('#date-form').find('input[value=bar]').attr('disabled', true);
			$('#date-form').find('input[value=column]').attr('disabled', true);
			//disable percentage-based visualizations
			$('#date-form').find('input[value=line]').attr('disabled', false);
			$('#date-form').find('input[value=area]').attr('disabled', false);
			$('#date-form').find('input[value=spline]').attr('disabled', false);
			$('#date-form').find('input[value=areaspline]').attr('disabled', false);

			//set line as default
			$('#date-form').find('input[value=line]').attr('checked', true);
		}
	});

	//disable and re-enable other calculate types in data download form when "cumulative" is selected
	$('#csv-form input[name=type]').change(function () {
		if ($(this).val() == 'cumulative') {
			$('#csv-form input[name=calculate][value!=date]').attr('disabled', true);
			//set date as default
			$('#csv-form input[value=date]').attr('checked', true);
		} else {
			$('#csv-form input[name=calculate]').attr('disabled', false);
		}
	});

	//enable cumulative when "date" is checked, otherwise disable
	$('#csv-form input[name=calculate]').change(function () {
		if ($(this).val() == 'date') {
			$('#csv-form input[value=cumulative]').attr('disabled', false);
		} else {
			$('#csv-form input[value=cumulative]').attr('disabled', true);
		}
		//set percentage as default
		$('#csv-form input[value=percentage]').attr('checked', true);
	});

	//set max number of 4 hoards for comparison (5 shown in total)
	$("#visualize-form .compare-select").livequery('change', function (event) {
		if ($("#visualize-form .compare-option:selected").length > 10) {
			$("#submit-vis").attr("disabled", "disabled");
		} else {
			$("#submit-vis").removeAttr('disabled');
		}
	});
	$("#date-form .compare-select").livequery('change', function (event) {
		if ($("#date-form .compare-option:selected").length > 10) {
			$("#submit-date").attr("disabled", "disabled");
		} else {
			$("#submit-date").removeAttr('disabled');
		}
	});

	/***** TOGGLE OPTIONAL SETTINGS *****/
	$('.optional-button').click(function () {
		var formId = $(this).attr('id').split('-')[0] + '-form';
		$('#' + formId + ' .optional-div').toggle('slow');
		return false;
	});

	/***** SUBMIT FORMS *****/
	$('.submit-vis').click(function () {
		var id = $(this).parent('form').attr('id');
		if (id == 'visualize-form') {
			//get calculate facets
			var facets = new Array();
			$('.calculate-checkbox:checked').each(function () {
				facets.push($(this).val());
			});
			var param1 = facets.join(',');
			$(this).siblings('input[name=calculate]').attr('value', param1);

			//get options
			optionsArray = new Array();
			$('.optional-div option:selected').each(function () {
				if($(this).parent().attr('class') != 'certainty-select'){
					optionsArray.push($(this).val());
				}
			});
			var options = optionsArray.join('|');
			$('#options-input').attr('value', options);
		} else {
			$(this).siblings('input[name=calculate]').attr('value', 'date');
		}
		//get compare value
		var hoards = new Array();
		$('#' + id + ' .compare-option:selected').each(function () {
			hoards.push($(this).val());
		});
		var param2 = hoards.join(',');
		$(this).siblings('input[name=compare]').attr('value', param2);

		//get exclude value
		var codes = new Array();
		$('#' + id + ' .exclude-option:selected').each(function () {
			codes.push($(this).val());
		});
		var param3 = codes.join(',');
		$(this).siblings('input[name=exclude]').attr('value', param3);
	});

	$('#submit-csv').click(function () {
		var id = $(this).parent('form').attr('id');
		//get compare value
		var hoards = new Array();
		$('#' + id + ' .compare-option:selected').each(function () {
			hoards.push($(this).val());
		});
		var param2 = hoards.join(',');
		$('#' + id + ' .compare-input').attr('value', param2);

		//get exclude value
		var codes = new Array();
		$('#' + id + ' .exclude-option:selected').each(function () {
			codes.push($(this).val());
		});
		var param3 = codes.join(',');
		$(this).siblings('input[name=exclude]').attr('value', param3);
	});

	/********* FILTERING FUNCTIONS **********/
	$(".showFilter").each(function () {
		var tthis = this;
		$(this).fancybox({
			onStart: function () {
				var formId = tthis.id.split('-')[0] + '-form';
				$('#formId').html(formId);
			}
		});
	});

	// total options for advanced search - used for unique id's on dynamically created elements
	var total_options = 1;

	// the boolean (and/or) items. these are set when a new search criteria option is created
	var gate_items = {
	};

	// focus the text field after selecting the field to search on
	$('.searchItemTemplate select') .change(function () {
		$(this) .siblings('.search_text') .focus();
	})

	// assign the gate/boolean button click handler
	$('.gateTypeBtn') .click(function () {
		gateTypeBtnClick($(this), total_options, gate_items);
	})

	//filter button activation
	$('#advancedSearchForm').submit(function () {
		var formId = $('#formId').text();
		var q = assembleQuery();
		$('#' + formId + ' .filter-div').children('span').html(q);
		$('#' + formId + ' .filter-div').show();
		$.get('get_hoards', {
			q: q
		},
		function (data) {
			$('#' + formId + ' .compare-div').html(data);
		});
		$.fancybox.close();
		return false;
	});

	//remove filter
	$('.removeFilter').click(function () {
		var formId = $('#formId').text();
		$('#' + formId + ' .filter-div').hide();
		$.get('get_hoards', {
			q: '*'
		},
		function (data) {
			$('#' + formId + ' .compare-div').html(data);
		});
		return false;
	})
})<commit_msg>pushing latest changes from CHRR<commit_after>/*******************
VISUALIZATION FUNCTIONS
Author: Ethan Gruber
Modification Date: November 2012
Description: Functions used in the Hoard Display and Analyze pipelines
for manipulating forms and rendering tables in the form of highcharts
********************/

$(document).ready(function () {
	$("#tabs").tabs();

	/**
	* Visualize an HTML table using Highcharts. The top (horizontal) header
	* is used for series names, and the left (vertical) header is used
	* for category names. This function is based on jQuery.
	* @param {Object} table The reference to the HTML table to visualize
	* @param {Object} options Highcharts options
	*/
	Highcharts.visualize = function (table, options) {
		// the categories
		options.xAxis.categories =[];
		$('tbody th', table).each(function (i) {
			options.xAxis.categories.push(this.innerHTML);
		});

		// the data series
		options.series =[];
		$('tr', table).each(function (i) {
			var tr = this;
			$('th, td', tr).each(function (j) {
				if (j > 0) {
					// skip first column
					if (i == 0) {
						// get the name and init the series
						options.series[j - 1] = {
							name: this.innerHTML,
							data:[]
						};
					} else {
						// add values
						options.series[j - 1].data.push(this.innerHTML == 'null'? null: parseFloat(this.innerHTML));
					}
				}
			});
		});
		console.log(options.series);

		var chart = new Highcharts.Chart(options);
	}

	/** CREATE HIGHCHARTS BASED ON HTML TABLES **/
	$('.calculate').each(function () {
		var id = $(this).attr('id').split('-')[0];
		var formId = $(this).siblings('form').attr('id');
		var type = $('#' + formId + ' input:radio[name=type]:checked').val();
		var chartType = $('#' + formId + ' input:radio[name=chartType]:checked').val();

		var table = $(this),
		options = {
			chart: {
				renderTo: id + '-container',
				type: chartType
			},
			title: {
				text: $(this).children('caption').text()
			},
			legend: {
				enabled: true
			},
			xAxis: {
				labels: {
					rotation: - 45,
					align: 'right',
					style: {
						fontSize: '11px',
						fontFamily: 'Verdana, sans-serif'
					}
				}
			},
			yAxis: {
				max: (type == 'count'? null: 100),
				min: (type == 'count'? null: 0),
				title: {
					text: (type == 'percentage'? 'Percentage': 'Occurrences')
				}
			},
			tooltip: {
				formatter: function () {
					return this.y + (type == 'percentage'? '%: ': ' coins: ') + this.x;
				}
			}
		};
		Highcharts.visualize(table, options);
	});

	/***** CREATE CHART FOR DATES *****/
	if ($('#dateData').text().length > 0) {
		var data = eval($('#dateData').text());
		var type = $('#date-form input:radio[name=type]:checked').val();
		var chartType = $('#date-form input:radio[name=chartType]:checked').val();
		var chart;
		chart = new Highcharts.Chart({
			chart: {
				renderTo: 'dateChart',
				type: chartType
			},
			title: {
				text: $('#dateData').attr('title')
			},
			xAxis: {
				type: 'datetime',
				labels: {
					rotation: - 45,
					align: 'right',
					formatter: function () {
						var year = this.value;
						if (year < 0) {
							return Math.abs(year) + ' B.C.';
						} else {
							return 'A.D. ' + year;
						}
					}
				}
			},
			yAxis: {
				max: (type == 'count'? null: 100),
				min: (type == 'count'? null: 0),
				title: {
					text: (type == 'count'? 'Occurrences': 'Percentage')
				}
			},
			tooltip: {
				formatter: function () {
					if (this.x < 0) {
						var year = Math.abs(this.x) + ' B.C.';
					} else {
						var year = 'A.D. ' + this.x;
					}
					return this.y + (type == 'count'? ' coins: ': '%: ') + year;
				}
			},
			exporting: {
				enabled: true,
				width: 1200
			},
			series: data
		});
	}

	//lock bar and column charts for count in date visualization--line, spine, area, and areaspline for percentages
	$('#date-form input[name=type]').change(function () {
		if ($(this).val() == 'count') {
			//enable quantification-based visualizations
			$('#date-form').find('input[value=bar]').attr('disabled', false);
			$('#date-form').find('input[value=column]').attr('disabled', false);
			//disable percentage-based visualizations
			$('#date-form').find('input[value=line]').attr('disabled', true);
			$('#date-form').find('input[value=area]').attr('disabled', true);
			$('#date-form').find('input[value=spline]').attr('disabled', true);
			$('#date-form').find('input[value=areaspline]').attr('disabled', true);

			//set column as default
			$('#date-form').find('input[value=column]').attr('checked', true);
		} else {
			//enable quantification-based visualizations
			$('#date-form').find('input[value=bar]').attr('disabled', true);
			$('#date-form').find('input[value=column]').attr('disabled', true);
			//disable percentage-based visualizations
			$('#date-form').find('input[value=line]').attr('disabled', false);
			$('#date-form').find('input[value=area]').attr('disabled', false);
			$('#date-form').find('input[value=spline]').attr('disabled', false);
			$('#date-form').find('input[value=areaspline]').attr('disabled', false);

			//set line as default
			$('#date-form').find('input[value=line]').attr('checked', true);
		}
	});

	//disable and re-enable other calculate types in data download form when "cumulative" is selected
	$('#csv-form input[name=type]').change(function () {
		if ($(this).val() == 'cumulative') {
			$('#csv-form input[name=calculate][value!=date]').attr('disabled', true);
			//set date as default
			$('#csv-form input[value=date]').attr('checked', true);
		} else {
			$('#csv-form input[name=calculate]').attr('disabled', false);
		}
	});

	//enable cumulative when "date" is checked, otherwise disable
	$('#csv-form input[name=calculate]').change(function () {
		if ($(this).val() == 'date') {
			$('#csv-form input[value=cumulative]').attr('disabled', false);
		} else {
			$('#csv-form input[value=cumulative]').attr('disabled', true);
		}
		//set percentage as default
		$('#csv-form input[value=percentage]').attr('checked', true);
	});

	//set max number of 4 hoards for comparison (5 shown in total)
	$("#visualize-form .compare-select").livequery('change', function (event) {
		if ($("#visualize-form .compare-option:selected").length > 6) {
			$("#submit-vis").attr("disabled", "disabled");
		} else {
			$("#submit-vis").removeAttr('disabled');
		}
	});
	$("#date-form .compare-select").livequery('change', function (event) {
		if ($("#date-form .compare-option:selected").length > 6) {
			$("#submit-date").attr("disabled", "disabled");
		} else {
			$("#submit-date").removeAttr('disabled');
		}
	});
	/***** GET HOARDS FOR COMPARISON ON DISPLAY PAGE *****/
	$('.compare-button').click(function () {
		//display the compare multiselect list only if it hasn't already been generated
		var cd = $(this).parent().children('.compare-div');
		if (cd.html().indexOf('<option') < 0) {
			$.get('../get_hoards', {
				q: '*'
			},
			function (data) {
				cd.html(data);
			});
		}
		return false;
	});

	/***** TOGGLE OPTIONAL SETTINGS *****/
	$('.optional-button').click(function(){
		var formId = $(this).attr('id').split('-')[0] + '-form';
		$('#' + formId + ' .optional-div').toggle('slow');
	});

	/***** SUBMIT FORMS *****/
	$('.submit-vis').click(function () {
		var id = $(this).parent('form').attr('id');
		if (id == 'visualize-form') {
			//get calculate facets
			var facets = new Array();
			$('.calculate-checkbox:checked').each(function () {
				facets.push($(this).val());
			});
			var param1 = facets.join(',');
			$(this).siblings('input[name=calculate]').attr('value', param1);
		} else {
			$(this).siblings('input[name=calculate]').attr('value', 'date');
		}
		//get compare value
		var hoards = new Array();
		$('#' + id + ' .compare-option:selected').each(function () {
			hoards.push($(this).val());
		});
		var param2 = hoards.join(',');
		$(this).siblings('input[name=compare]').attr('value', param2);

		//get exclude value
		var codes = new Array();
		$('#' + id + ' .exclude-option:selected').each(function () {
			codes.push($(this).val());
		});
		var param3 = codes.join(',');
		$(this).siblings('input[name=exclude]').attr('value', param3);
	});

	$('#submit-csv').click(function () {
		var id = $(this).parent('form').attr('id');
		//get compare value
		var hoards = new Array();
		$('#' + id + ' .compare-option:selected').each(function () {
			hoards.push($(this).val());
		});
		var param2 = hoards.join(',');
		$('#' + id + ' .compare-input').attr('value', param2);

		//get exclude value
		var codes = new Array();
		$('#' + id + ' .exclude-option:selected').each(function () {
			codes.push($(this).val());
		});
		var param3 = codes.join(',');
		$(this).siblings('input[name=exclude]').attr('value', param3);
	});

	/********* FILTERING FUNCTIONS **********/
	$(".showFilter").each(function () {
		var tthis = this;
		$(this).fancybox({
			onStart: function () {
				var formId = tthis.id.split('-')[0] + '-form';
				alert(formId);
				$('#formId').html(formId);
			}
		});
	});

	// total options for advanced search - used for unique id's on dynamically created elements
	var total_options = 1;

	// the boolean (and/or) items. these are set when a new search criteria option is created
	var gate_items = {
	};

	// focus the text field after selecting the field to search on
	$('.searchItemTemplate select') .change(function () {
		$(this) .siblings('.search_text') .focus();
	})

	// assign the gate/boolean button click handler
	$('.gateTypeBtn') .click(function () {
		gateTypeBtnClick($(this), total_options, gate_items);
	})

	//filter button activation
	$('#advancedSearchForm').submit(function () {
		var formId = $('#formId').text();
		var q = assembleQuery();
		$('#' + formId + ' .filter-div').children('span').html(q);
		$('#' + formId + ' .filter-div').show();
		$.get('get_hoards', {
			q: q
		},
		function (data) {
			$('#' + formId + ' .compare-div').html(data);
		});
		$.fancybox.close();
		return false;
	});

	//remove filter
	$('.removeFilter').click(function () {
		var formId = $('#formId').text();
		$('#' + formId + ' .filter-div').hide();
		$.get('get_hoards', {
			q: '*'
		},
		function (data) {
			$('#' + formId + ' .compare-div').html(data);
		});
		return false;
	})
})<|endoftext|>


<commit_before>var urlModule = require('url');
var _ = require('lodash');
var crypto = require('crypto');
var ContentSecurityPolicy = require('../assets/ContentSecurityPolicy');

var directiveByRelationType = {
    HtmlStyle: 'styleSrc',
    CssImport: 'styleSrc',
    HtmlScript: 'scriptSrc',
    CssFontFaceSrc: 'fontSrc',
    HtmlObject: 'objectSrc',
    HtmlApplet: 'objectSrc',
    HtmlImage: 'imgSrc',
    CssImage: 'imgSrc',
    HtmlShortcutIcon: 'imgSrc',
    HtmlPictureSource: 'imgSrc',
    HtmlFluidIcon: 'imgSrc',
    SrcSetEntry: 'imgSrc',
    HtmlLogo: 'imgSrc',
    HtmlAppleTouchStartupImage: 'imgSrc',
    HtmlVideoPoster: 'imgSrc',
    HtmlVideo: 'mediaSrc',
    HtmlAudio: 'mediaSrc',
    HtmlFrame: 'frameSrc',
    HtmlIFrame: 'frameSrc',
    HtmlApplicationManifest: 'manifestSrc',
    JavaScriptFetch: 'connectSrc'
};

function toCamelCase(str) {
    return str.replace(/-([a-z])/g, function ($0, ch) {
        return ch.toUpperCase();
    });
}

function fromCamelCase(str) {
    return str.replace(/[A-Z]/g, function ($0) {
        return '-' + $0.toLowerCase();
    });
}

function isNonceOrHash(sourceExpression) {
    return /^'nonce-|^'sha\d+-/.test(sourceExpression);
}

module.exports = function (queryObj, options) {
    options = options || {};
    return function reviewContentSecurityPolicy(assetGraph) {
        var includePathByDirective = {};
        if (options.includePath) {
            _.flatten(options.includePath).forEach(function (directive) {
                includePathByDirective[toCamelCase(directive)] = true;
            });
        }

        function originFromUrl(url, directive, isInProtocolRelativeContext) {
            if (assetGraph.root && url.indexOf(assetGraph.root) === 0) {
                return '\'self\'';
            } else if (options.includePath === true || includePathByDirective[directive]) {
                if (isInProtocolRelativeContext) {
                    return url.replace(/^https?:\/\//, '');
                } else {
                    return url;
                }
            } else {
                var urlObj = urlModule.parse(url);
                var host = urlObj.hostname;
                if (urlObj.port && parseInt(urlObj.port, 10) !== {'https:': 443, 'http:': 80}[urlObj.protocol]) {
                    host += ':' + urlObj.port;
                }

                if (isInProtocolRelativeContext) {
                    return host;
                } else {
                    return urlObj.protocol + '//' + host;
                }
            }
        }

        assetGraph.findAssets(queryObj || { type: 'Html', isInline: false, isFragment: false, isLoaded: true }).forEach(function (htmlAsset) {
            var htmlContentSecurityPolicies = assetGraph.findRelations({from: htmlAsset, type: 'HtmlContentSecurityPolicy'});
            htmlContentSecurityPolicies.forEach(function (htmlContentSecurityPolicy) {
                var contentSecurityPolicy = htmlContentSecurityPolicy.to;
                var defaultSrc = contentSecurityPolicy.parseTree.defaultSrc;

                function supportsUnsafeInline(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].indexOf('\'unsafe-inline\'') !== -1 :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.indexOf('\'unsafe-inline\'') !== -1
                    );
                }

                function hasNonceOrHash(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].some(isNonceOrHash) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.some(isNonceOrHash)
                    );
                }

                var disallowedRelationsByDirectiveAndOrigin = {};
                var seenNoncesByDirective = {};

                function noteAsset(asset, incomingRelation, isInProtocolRelativeContext, directive) {
                    directive = directive || (incomingRelation && directiveByRelationType[incomingRelation.type]);
                    if (directive) {
                        if (asset.isInline) {
                            if ((directive === 'styleSrc' || directive === 'scriptSrc') && (!supportsUnsafeInline(directive) || hasNonceOrHash(directive))) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                var hashSource = '\'sha256-' + crypto.createHash('sha256').update(asset.rawSrc).digest('base64') + '\'';
                                (disallowedRelationsByDirectiveAndOrigin[directive][hashSource] = disallowedRelationsByDirectiveAndOrigin[directive][hashSource] || []).push(incomingRelation);
                            } else if (/^data:/.test(incomingRelation.href)) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                (disallowedRelationsByDirectiveAndOrigin[directive]['data:'] = disallowedRelationsByDirectiveAndOrigin[directive]['data:'] || []).push(incomingRelation);
                            }
                        } else if (!contentSecurityPolicy.allows(directive, asset.url)) {
                            disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                            var origin = originFromUrl(asset.url, directive, isInProtocolRelativeContext);
                            (disallowedRelationsByDirectiveAndOrigin[directive][origin] = disallowedRelationsByDirectiveAndOrigin[directive][origin] || []).push(incomingRelation);
                        }
                        if (incomingRelation.from.type === 'Html') {
                            var nonce = incomingRelation.node.getAttribute('nonce');
                            if (nonce) {
                                (seenNoncesByDirective[directive] = seenNoncesByDirective[directive] || []).push('\'nonce-' + nonce + '\'');
                                if (options.update) {
                                    incomingRelation.node.removeAttribute('nonce');
                                    incomingRelation.from.markDirty();
                                }
                            }
                        }
                    }
                }

                var isSeenByAssetId = {};
                isSeenByAssetId[htmlAsset.id] = true;
                var protocolRelativeStack = [false];
                assetGraph._traverse(htmlAsset, {type: assetGraph.query.not(['HtmlAnchor', 'HtmlMetaRefresh', 'JavaScriptSourceUrl', 'JavaScriptSourceMappingUrl', 'CssSourceUrl', 'CssSourceMappingUrl'])}, function (asset, incomingRelation) {
                    var isInProtocolRelativeContext = protocolRelativeStack[protocolRelativeStack.length - 1] || (incomingRelation && incomingRelation.hrefType === 'protocolRelative');
                    protocolRelativeStack.push(isInProtocolRelativeContext);
                    isSeenByAssetId[asset.id] = true;
                    if (incomingRelation && incomingRelation.type === 'HttpRedirect') {
                        // Work backwards through all the seen assets to find all relevant paths through this redirect:
                        var isSeenByRelationId = {};
                        var queue = [].concat(incomingRelation.from.incomingRelations.filter(function (relation) {
                            return isSeenByAssetId[relation.from.id];
                        }));
                        while (queue.length > 0) {
                            var relation = queue.shift();
                            if (!isSeenByRelationId[relation.id]) {
                                isSeenByRelationId[relation.id] = true;
                                if (relation.type === 'HttpRedirect') {
                                    Array.prototype.push.apply(queue, relation.from.incomingRelations.filter(function (relation) {
                                        return isSeenByAssetId[relation.from.id] && !isSeenByRelationId[relation.id];
                                    }));
                                } else {
                                    noteAsset(asset, incomingRelation, isInProtocolRelativeContext, directiveByRelationType[relation.type]);
                                }
                            }
                        }
                    } else {
                        noteAsset(asset, incomingRelation, isInProtocolRelativeContext);
                    }
                }, function () {
                    protocolRelativeStack.pop();
                });

                var directives = _.uniq(Object.keys(disallowedRelationsByDirectiveAndOrigin).concat(Object.keys(seenNoncesByDirective)).concat(Object.keys(contentSecurityPolicy.parseTree)));

                directives.forEach(function (directive) {
                    var origins = Object.keys(disallowedRelationsByDirectiveAndOrigin[directive] || {});
                    if (options.update) {
                        if (contentSecurityPolicy.parseTree[directive]) {
                            origins = _.uniq(origins.concat(contentSecurityPolicy.parseTree[directive]));
                        } else if (ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && contentSecurityPolicy.parseTree.defaultSrc) {
                            origins = _.uniq(origins.concat(contentSecurityPolicy.parseTree.defaultSrc));
                        }
                        var noncesWereRemoved = false;
                        var originsWithNoncesSubtracted = _.difference(origins, ["'nonce-developmentonly'"].concat(seenNoncesByDirective[directive]));
                        if (originsWithNoncesSubtracted.length < origins.length) {
                            noncesWereRemoved = true;
                        }
                        origins = originsWithNoncesSubtracted;

                        // If we're removing 'nonce-developmentonly' when 'unsafe-inline' is in effect and there are no other
                        // nonces or hashes whitelisted, add the sha256 hash of the empty string to the list of allowed origins.
                        // This prevents 'unsafe-inline' from taking effect in CSP2+ compliant browsers that would otherwise
                        // ignore 'unsafe-inline' when at least one nonce or hash is present for the directive.
                        // https://www.w3.org/TR/CSP3/#match-element-to-source-list
                        if (noncesWereRemoved && origins.indexOf("'unsafe-inline'") !== -1 && !origins.some(isNonceOrHash)) {
                            origins.push("'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='");
                        } else if (origins.some(isNonceOrHash) && origins.indexOf("'unsafe-inline'") === -1) {
                            origins.push("'unsafe-inline'");
                        }
                        if (origins.length > 1) {
                            var indexOfNoneToken = origins.indexOf('\'none\'');
                            if (indexOfNoneToken !== -1) {
                                origins.splice(indexOfNoneToken, 1);
                            }
                        }
                        if (directive !== 'defaultSrc' && defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && _.difference(defaultSrc, origins).length === 0 && _.difference(origins, defaultSrc).length === 0) {
                            delete contentSecurityPolicy.parseTree[directive];
                        } else {
                            contentSecurityPolicy.parseTree[directive] = origins.sort();
                        }
                        contentSecurityPolicy.markDirty();
                    } else {
                        var allowedOrigins = contentSecurityPolicy.parseTree[directive];
                        var directiveInEffect = directive;
                        if (!allowedOrigins) {
                            if (directive !== 'frameAncestors' && contentSecurityPolicy.parseTree.defaultSrc) {
                                directiveInEffect = 'defaultSrc';
                                allowedOrigins = contentSecurityPolicy.parseTree.defaultSrc;
                            } else {
                                allowedOrigins = ['\'none\''];
                            }
                        }
                        origins.forEach(function (nonWhitelistedOrigin) {
                            var relations = disallowedRelationsByDirectiveAndOrigin[directive][nonWhitelistedOrigin];
                            if (relations) {
                                relations = relations.filter(function (relation) {
                                    var nonce;
                                    if (relation.from.type === 'Html') {
                                        nonce = relation.node.getAttribute('nonce');
                                    }
                                    return !nonce || allowedOrigins.indexOf('\'nonce-' + nonce + '\'') === -1;
                                });
                                if (relations.length > 0) {
                                    assetGraph.emit(
                                        'warn',
                                        new Error(
                                            htmlAsset.urlOrDescription + ': ' + (relations.length === 1 ? 'An asset violates' : relations.length + ' relations violate') + ' the ' + fromCamelCase(directiveInEffect) + ' ' + allowedOrigins.join(' ') + ' Content-Security-Policy directive:\n  ' +
                                            relations.map(function (asset) {
                                                return asset.to.urlOrDescription;
                                            }).join('\n  ')
                                        )
                                    );
                                }
                            }
                        });
                    }
                });
            });
        });
    };
};
<commit_msg>reviewContentSecurityPolicy: Add the ability to retrieve the contents of disallowedRelationsByDirectiveAndOrigin via an infoObject.<commit_after>var urlModule = require('url');
var _ = require('lodash');
var crypto = require('crypto');
var ContentSecurityPolicy = require('../assets/ContentSecurityPolicy');

var directiveByRelationType = {
    HtmlStyle: 'styleSrc',
    CssImport: 'styleSrc',
    HtmlScript: 'scriptSrc',
    CssFontFaceSrc: 'fontSrc',
    HtmlObject: 'objectSrc',
    HtmlApplet: 'objectSrc',
    HtmlImage: 'imgSrc',
    CssImage: 'imgSrc',
    HtmlShortcutIcon: 'imgSrc',
    HtmlPictureSource: 'imgSrc',
    HtmlFluidIcon: 'imgSrc',
    SrcSetEntry: 'imgSrc',
    HtmlLogo: 'imgSrc',
    HtmlAppleTouchStartupImage: 'imgSrc',
    HtmlVideoPoster: 'imgSrc',
    HtmlVideo: 'mediaSrc',
    HtmlAudio: 'mediaSrc',
    HtmlFrame: 'frameSrc',
    HtmlIFrame: 'frameSrc',
    HtmlApplicationManifest: 'manifestSrc',
    JavaScriptFetch: 'connectSrc'
};

function toCamelCase(str) {
    return str.replace(/-([a-z])/g, function ($0, ch) {
        return ch.toUpperCase();
    });
}

function fromCamelCase(str) {
    return str.replace(/[A-Z]/g, function ($0) {
        return '-' + $0.toLowerCase();
    });
}

function isNonceOrHash(sourceExpression) {
    return /^'nonce-|^'sha\d+-/.test(sourceExpression);
}

module.exports = function (queryObj, options) {
    options = options || {};
    return function reviewContentSecurityPolicy(assetGraph) {
        var includePathByDirective = {};
        if (options.includePath) {
            _.flatten(options.includePath).forEach(function (directive) {
                includePathByDirective[toCamelCase(directive)] = true;
            });
        }

        function originFromUrl(url, directive, isInProtocolRelativeContext) {
            if (assetGraph.root && url.indexOf(assetGraph.root) === 0) {
                return '\'self\'';
            } else if (options.includePath === true || includePathByDirective[directive]) {
                if (isInProtocolRelativeContext) {
                    return url.replace(/^https?:\/\//, '');
                } else {
                    return url;
                }
            } else {
                var urlObj = urlModule.parse(url);
                var host = urlObj.hostname;
                if (urlObj.port && parseInt(urlObj.port, 10) !== {'https:': 443, 'http:': 80}[urlObj.protocol]) {
                    host += ':' + urlObj.port;
                }

                if (isInProtocolRelativeContext) {
                    return host;
                } else {
                    return urlObj.protocol + '//' + host;
                }
            }
        }

        assetGraph.findAssets(queryObj || { type: 'Html', isInline: false, isFragment: false, isLoaded: true }).forEach(function (htmlAsset) {
            var htmlContentSecurityPolicies = assetGraph.findRelations({from: htmlAsset, type: 'HtmlContentSecurityPolicy'});
            htmlContentSecurityPolicies.forEach(function (htmlContentSecurityPolicy) {
                var contentSecurityPolicy = htmlContentSecurityPolicy.to;
                var defaultSrc = contentSecurityPolicy.parseTree.defaultSrc;

                function supportsUnsafeInline(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].indexOf('\'unsafe-inline\'') !== -1 :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.indexOf('\'unsafe-inline\'') !== -1
                    );
                }

                function hasNonceOrHash(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].some(isNonceOrHash) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.some(isNonceOrHash)
                    );
                }

                var disallowedRelationsByDirectiveAndOrigin = {};
                var seenNoncesByDirective = {};

                function noteAsset(asset, incomingRelation, isInProtocolRelativeContext, directive) {
                    directive = directive || (incomingRelation && directiveByRelationType[incomingRelation.type]);
                    if (directive) {
                        if (asset.isInline) {
                            if ((directive === 'styleSrc' || directive === 'scriptSrc') && (!supportsUnsafeInline(directive) || hasNonceOrHash(directive))) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                var hashSource = '\'sha256-' + crypto.createHash('sha256').update(asset.rawSrc).digest('base64') + '\'';
                                (disallowedRelationsByDirectiveAndOrigin[directive][hashSource] = disallowedRelationsByDirectiveAndOrigin[directive][hashSource] || []).push(incomingRelation);
                            } else if (/^data:/.test(incomingRelation.href)) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                (disallowedRelationsByDirectiveAndOrigin[directive]['data:'] = disallowedRelationsByDirectiveAndOrigin[directive]['data:'] || []).push(incomingRelation);
                            }
                        } else if (!contentSecurityPolicy.allows(directive, asset.url)) {
                            disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                            var origin = originFromUrl(asset.url, directive, isInProtocolRelativeContext);
                            (disallowedRelationsByDirectiveAndOrigin[directive][origin] = disallowedRelationsByDirectiveAndOrigin[directive][origin] || []).push(incomingRelation);
                        }
                        if (incomingRelation.from.type === 'Html') {
                            var nonce = incomingRelation.node.getAttribute('nonce');
                            if (nonce) {
                                (seenNoncesByDirective[directive] = seenNoncesByDirective[directive] || []).push('\'nonce-' + nonce + '\'');
                                if (options.update) {
                                    incomingRelation.node.removeAttribute('nonce');
                                    incomingRelation.from.markDirty();
                                }
                            }
                        }
                    }
                }

                var isSeenByAssetId = {};
                isSeenByAssetId[htmlAsset.id] = true;
                var protocolRelativeStack = [false];
                assetGraph._traverse(htmlAsset, {type: assetGraph.query.not(['HtmlAnchor', 'HtmlMetaRefresh', 'JavaScriptSourceUrl', 'JavaScriptSourceMappingUrl', 'CssSourceUrl', 'CssSourceMappingUrl'])}, function (asset, incomingRelation) {
                    var isInProtocolRelativeContext = protocolRelativeStack[protocolRelativeStack.length - 1] || (incomingRelation && incomingRelation.hrefType === 'protocolRelative');
                    protocolRelativeStack.push(isInProtocolRelativeContext);
                    isSeenByAssetId[asset.id] = true;
                    if (incomingRelation && incomingRelation.type === 'HttpRedirect') {
                        // Work backwards through all the seen assets to find all relevant paths through this redirect:
                        var isSeenByRelationId = {};
                        var queue = [].concat(incomingRelation.from.incomingRelations.filter(function (relation) {
                            return isSeenByAssetId[relation.from.id];
                        }));
                        while (queue.length > 0) {
                            var relation = queue.shift();
                            if (!isSeenByRelationId[relation.id]) {
                                isSeenByRelationId[relation.id] = true;
                                if (relation.type === 'HttpRedirect') {
                                    Array.prototype.push.apply(queue, relation.from.incomingRelations.filter(function (relation) {
                                        return isSeenByAssetId[relation.from.id] && !isSeenByRelationId[relation.id];
                                    }));
                                } else {
                                    noteAsset(asset, incomingRelation, isInProtocolRelativeContext, directiveByRelationType[relation.type]);
                                }
                            }
                        }
                    } else {
                        noteAsset(asset, incomingRelation, isInProtocolRelativeContext);
                    }
                }, function () {
                    protocolRelativeStack.pop();
                });

                var directives = _.uniq(Object.keys(disallowedRelationsByDirectiveAndOrigin).concat(Object.keys(seenNoncesByDirective)).concat(Object.keys(contentSecurityPolicy.parseTree)));

                if (options.infoObject) {
                    options.infoObject[contentSecurityPolicy.id] = {
                        additions: disallowedRelationsByDirectiveAndOrigin
                    };
                }

                directives.forEach(function (directive) {
                    var origins = Object.keys(disallowedRelationsByDirectiveAndOrigin[directive] || {});
                    if (options.update) {
                        if (contentSecurityPolicy.parseTree[directive]) {
                            origins = _.uniq(origins.concat(contentSecurityPolicy.parseTree[directive]));
                        } else if (ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && contentSecurityPolicy.parseTree.defaultSrc) {
                            origins = _.uniq(origins.concat(contentSecurityPolicy.parseTree.defaultSrc));
                        }
                        var noncesWereRemoved = false;
                        var originsWithNoncesSubtracted = _.difference(origins, ["'nonce-developmentonly'"].concat(seenNoncesByDirective[directive]));
                        if (originsWithNoncesSubtracted.length < origins.length) {
                            noncesWereRemoved = true;
                        }
                        origins = originsWithNoncesSubtracted;

                        // If we're removing 'nonce-developmentonly' when 'unsafe-inline' is in effect and there are no other
                        // nonces or hashes whitelisted, add the sha256 hash of the empty string to the list of allowed origins.
                        // This prevents 'unsafe-inline' from taking effect in CSP2+ compliant browsers that would otherwise
                        // ignore 'unsafe-inline' when at least one nonce or hash is present for the directive.
                        // https://www.w3.org/TR/CSP3/#match-element-to-source-list
                        if (noncesWereRemoved && origins.indexOf("'unsafe-inline'") !== -1 && !origins.some(isNonceOrHash)) {
                            origins.push("'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='");
                        } else if (origins.some(isNonceOrHash) && origins.indexOf("'unsafe-inline'") === -1) {
                            origins.push("'unsafe-inline'");
                        }
                        if (origins.length > 1) {
                            var indexOfNoneToken = origins.indexOf('\'none\'');
                            if (indexOfNoneToken !== -1) {
                                origins.splice(indexOfNoneToken, 1);
                            }
                        }
                        if (directive !== 'defaultSrc' && defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && _.difference(defaultSrc, origins).length === 0 && _.difference(origins, defaultSrc).length === 0) {
                            delete contentSecurityPolicy.parseTree[directive];
                        } else {
                            contentSecurityPolicy.parseTree[directive] = origins.sort();
                        }
                        contentSecurityPolicy.markDirty();
                    } else {
                        var allowedOrigins = contentSecurityPolicy.parseTree[directive];
                        var directiveInEffect = directive;
                        if (!allowedOrigins) {
                            if (directive !== 'frameAncestors' && contentSecurityPolicy.parseTree.defaultSrc) {
                                directiveInEffect = 'defaultSrc';
                                allowedOrigins = contentSecurityPolicy.parseTree.defaultSrc;
                            } else {
                                allowedOrigins = ['\'none\''];
                            }
                        }
                        origins.forEach(function (nonWhitelistedOrigin) {
                            var relations = disallowedRelationsByDirectiveAndOrigin[directive][nonWhitelistedOrigin];
                            if (relations) {
                                relations = relations.filter(function (relation) {
                                    var nonce;
                                    if (relation.from.type === 'Html') {
                                        nonce = relation.node.getAttribute('nonce');
                                    }
                                    return !nonce || allowedOrigins.indexOf('\'nonce-' + nonce + '\'') === -1;
                                });
                                if (relations.length > 0) {
                                    assetGraph.emit(
                                        'warn',
                                        new Error(
                                            htmlAsset.urlOrDescription + ': ' + (relations.length === 1 ? 'An asset violates' : relations.length + ' relations violate') + ' the ' + fromCamelCase(directiveInEffect) + ' ' + allowedOrigins.join(' ') + ' Content-Security-Policy directive:\n  ' +
                                            relations.map(function (asset) {
                                                return asset.to.urlOrDescription;
                                            }).join('\n  ')
                                        )
                                    );
                                }
                            }
                        });
                    }
                });
            });
        });
    };
};
<|endoftext|>


<commit_before>import {Button} from 'widget/button'
import {ButtonGroup} from 'widget/buttonGroup'
import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {msg} from 'translate'
import React from 'react'
import Tagline from './tagline'
import Title from './title'
import image_01 from './intro/bhutan.jpg'
import image_02 from './intro/senegal.jpg'
import image_03 from './intro/namibia.jpg'
import image_04 from './intro/terminal.jpg'
import image_gee from './intro/tools/gee.png'
import image_jupyter from './intro/tools/jupyter.png'
import image_rstudio from './intro/tools/rstudio.png'
import image_shiny from './intro/tools/shiny.jpg'
import partner_ec from './intro/partners/ec.png'
import partner_esa from './intro/partners/esa.png'
import partner_eth from './intro/partners/eth.png'
import partner_formin from './intro/partners/formin.png'
import partner_germany from './intro/partners/germany.png'
import partner_gfoi from './intro/partners/gfoi.png'
import partner_google from './intro/partners/google.png'
import partner_jaxa from './intro/partners/jaxa.png'
import partner_jica from './intro/partners/jica.png'
import partner_kfw from './intro/partners/kfw.png'
import partner_nasa from './intro/partners/nasa.png'
import partner_nicfi from './intro/partners/nicfi.png'
import partner_openforis from './intro/partners/openforis.png'
import partner_sc from './intro/partners/sc.png'
import partner_servir from './intro/partners/servir.png'
import partner_sig from './intro/partners/sig.png'
import partner_wageningen from './intro/partners/wageningen.png'
import styles from './intro.module.css'

const signupUrl = 'https://docs.google.com/forms/d/e/1FAIpQLSci4hopXNtMOQKJzsUybaJETrAPQp8j6TCqycSBQ0XO37jBwA/viewform?c=0&w=1'

const Intro = ({onLaunch}) =>
    <ScrollableContainer>
        <Scrollable>

                    {/*
 */}
                    <Title className={styles.title}/>
                    <Tagline className={styles.tagline}/>
                    <ButtonGroup layout='horizontal-nowrap-loose'>
                        <Button
                            look='default'
                            size='x-large'
                            air='more'
                            label={msg('landing.signup')}
                            tabIndex={1}
                            linkUrl={signupUrl}
                            linkTarget='_self'
                            additionalClassName={styles.button}
                        />
                        <Button
                            type='submit'
                            look='apply'
                            size='x-large'
                            air='more'
                            label={msg('landing.launch')}
                            tabIndex={2}
                            onClick={onLaunch}
                            additionalClassName={styles.button}
                        />
                    </ButtonGroup>

EASE OF USE

Allows users to query and process satellite data quickly and efficiently, tailor their products for local needs, and produce sophisticated and relevant geospatial analyses quickly.

COMPUTING POWER

                            Harness high performance cloud-based computing and modern
                            geospatial data infrastructures.

GOOGLE EARTH ENGINE

                            Get access to Earth Engine's multi-petabyte catalog of satellite
                            imagery and use their planetary-scale analysis capabilities. All
                            without writing a single line of code. Just connect your Google
                            account to SEPAL.

JUPYTER NOTEBOOK

                            Run any of the geospatial processing notebooks in SEPAL's
                            catalogue, or develop your own. The hosted Jupyter server
                            comes with Python 3, R, and JavaScript kernels.

SHINY SERVER

                            Perform stratified area estimation, time-series analysis
                            with BFAST, and other geospatial processing through the R Shiny
                            apps hosted in SEPAL.

RSTUDIO SERVER

                            Develop your R scripts with RStudio, directly inside SEPAL. Use
                            any of the many useful R packages already installed, and install
                            your own when you need to.

INTEGRATIONS

                            SEPAL doesn't want to reinvent the wheel. We rather use and integrate with
                            existing solutions, such as Open Foris Collect Earth Online, for visual
                            interpretation of satellite imagery.

POWER USERS

                            Get access to dedicated Linux instances, with up to 128 CPU cores, 2TB of RAM
                            and a host of development and geospatial tools installed.
                            Access it directly from within the browser, or an SSH client.
                            Transfer files to and from the instance with rsync, scp, or your favorite
                            FTP client.

ABOUT

SEPAL is an opensource project by the Open Foris team in Forestry Department of the United Nations Food and Agriculture Organization (FAO), funded by the Government of Norway.

PARTNERS

                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='http://www.openforis.org'
                        linkTarget='openforis'
                        label='Openforis'>
                    </Button>
                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='https://github.com/openforis/sepal'
                        linkTarget='github-project'
                        label='GitHub'>
                    </Button>
                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='/privacy-policy'
                        linkTarget='privacy-policy'
                        label={msg('landing.privacyPolicy')}>
                    </Button>

        </Scrollable>
    </ScrollableContainer>

export default Intro
<commit_msg>Cleanup.<commit_after>import {Button} from 'widget/button'
import {ButtonGroup} from 'widget/buttonGroup'
import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {msg} from 'translate'
import React from 'react'
import Tagline from './tagline'
import Title from './title'
import image_01 from './intro/bhutan.jpg'
import image_02 from './intro/senegal.jpg'
import image_03 from './intro/namibia.jpg'
import image_04 from './intro/terminal.jpg'
import image_gee from './intro/tools/gee.png'
import image_jupyter from './intro/tools/jupyter.png'
import image_rstudio from './intro/tools/rstudio.png'
import image_shiny from './intro/tools/shiny.jpg'
import partner_ec from './intro/partners/ec.png'
import partner_esa from './intro/partners/esa.png'
import partner_eth from './intro/partners/eth.png'
import partner_formin from './intro/partners/formin.png'
import partner_germany from './intro/partners/germany.png'
import partner_gfoi from './intro/partners/gfoi.png'
import partner_google from './intro/partners/google.png'
import partner_jaxa from './intro/partners/jaxa.png'
import partner_jica from './intro/partners/jica.png'
import partner_kfw from './intro/partners/kfw.png'
import partner_nasa from './intro/partners/nasa.png'
import partner_nicfi from './intro/partners/nicfi.png'
import partner_openforis from './intro/partners/openforis.png'
import partner_sc from './intro/partners/sc.png'
import partner_servir from './intro/partners/servir.png'
import partner_sig from './intro/partners/sig.png'
import partner_wageningen from './intro/partners/wageningen.png'
import styles from './intro.module.css'

const signupUrl = 'https://docs.google.com/forms/d/e/1FAIpQLSci4hopXNtMOQKJzsUybaJETrAPQp8j6TCqycSBQ0XO37jBwA/viewform?c=0&w=1'

const Intro = ({onLaunch}) =>
    <ScrollableContainer>
        <Scrollable>

                    <Title className={styles.title}/>
                    <Tagline className={styles.tagline}/>
                    <ButtonGroup layout='horizontal-nowrap-loose'>
                        <Button
                            look='default'
                            size='x-large'
                            air='more'
                            label={msg('landing.signup')}
                            tabIndex={1}
                            linkUrl={signupUrl}
                            linkTarget='_self'
                            additionalClassName={styles.button}
                        />
                        <Button
                            type='submit'
                            look='apply'
                            size='x-large'
                            air='more'
                            label={msg('landing.launch')}
                            tabIndex={2}
                            onClick={onLaunch}
                            additionalClassName={styles.button}
                        />
                    </ButtonGroup>

EASE OF USE

Allows users to query and process satellite data quickly and efficiently, tailor their products for local needs, and produce sophisticated and relevant geospatial analyses quickly.

COMPUTING POWER

                            Harness high performance cloud-based computing and modern
                            geospatial data infrastructures.

GOOGLE EARTH ENGINE

                            Get access to Earth Engine's multi-petabyte catalog of satellite
                            imagery and use their planetary-scale analysis capabilities. All
                            without writing a single line of code. Just connect your Google
                            account to SEPAL.

JUPYTER NOTEBOOK

                            Run any of the geospatial processing notebooks in SEPAL's
                            catalogue, or develop your own. The hosted Jupyter server
                            comes with Python 3, R, and JavaScript kernels.

SHINY SERVER

                            Perform stratified area estimation, time-series analysis
                            with BFAST, and other geospatial processing through the R Shiny
                            apps hosted in SEPAL.

RSTUDIO SERVER

                            Develop your R scripts with RStudio, directly inside SEPAL. Use
                            any of the many useful R packages already installed, and install
                            your own when you need to.

INTEGRATIONS

                            SEPAL doesn't want to reinvent the wheel. We rather use and integrate with
                            existing solutions, such as Open Foris Collect Earth Online, for visual
                            interpretation of satellite imagery.

POWER USERS

                            Get access to dedicated Linux instances, with up to 128 CPU cores, 2TB of RAM
                            and a host of development and geospatial tools installed.
                            Access it directly from within the browser, or an SSH client.
                            Transfer files to and from the instance with rsync, scp, or your favorite
                            FTP client.

ABOUT

SEPAL is an opensource project by the Open Foris team in Forestry Department of the United Nations Food and Agriculture Organization (FAO), funded by the Government of Norway.

PARTNERS

                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='http://www.openforis.org'
                        linkTarget='openforis'
                        label='Openforis'>
                    </Button>
                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='https://github.com/openforis/sepal'
                        linkTarget='github-project'
                        label='GitHub'>
                    </Button>
                    <Button
                        chromeless
                        look='transparent'
                        shape='pill'
                        size='x-large'
                        linkUrl='/privacy-policy'
                        linkTarget='privacy-policy'
                        label={msg('landing.privacyPolicy')}>
                    </Button>

        </Scrollable>
    </ScrollableContainer>

export default Intro
<|endoftext|>


<commit_before>//TODO move into own file, think about the name
Ext.namespace('Vpc.Form.Fields');
Vpc.Form.Fields.vpsField = function(fieldEl) {
    Vpc.Form.Fields.vpsField.superclass.constructor.call(this);
    this.el = fieldEl;
    this.el.enableDisplayMode();
    this.initField();
    this.addEvents('change');
};
Ext.extend(Vpc.Form.Fields.vpsField, Ext.util.Observable, {
    initField: function() {
        var inp = this.el.child('input');
        if (inp) {
            inp.on('change', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }
    },
    getFieldName: function() {
        var inp = this.el.child('input');
        if (!inp) return null;
        return inp.dom.name;
    },
    getValue: function() {
        var inp = this.el.child('input');
        if (!inp) return null;
        return inp.dom.value;
    },
    hide: function() {
        this.el.hide();
    },
    show: function() {
        this.el.show();
    }
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldRadio = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('input').each(function(input) {
            input.on('click', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getValue: function() {
        var ret = null;
        this.el.select('input').each(function(input) {
            if (input.dom.checked) {
                ret = input.dom.value;
            }
        }, this);
        return ret;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldSelect = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('select').each(function(input) {
            input.on('click', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getFieldName: function() {
        return this.el.child('select').dom.name;
    },
    getValue: function() {
        return this.el.child('select').dom.value;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldTextArea = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('textarea').each(function(input) {
            input.on('keypress', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getFieldName: function() {
        return this.el.child('textarea').dom.name;
    },
    getValue: function() {
        return this.el.child('textarea').dom.value;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldStatic = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
    },
    getValue: function() {
        return null;
    },
    getFieldName: function() {
        return null; //TODO?
    }
});

Vps.onContentReady(function() {
    Ext.select('.vpcForm', true).each(function(form) {
        var config = form.parent().down('.config', true);
        if (!config) return;
        config = Ext.decode(config.value);
        if (!config) return;

        //TODO there should be a proper Form class and object
        //but for now this should do...
        //TODO move into own file, think about the name
        var fields = [];
        form.select('.vpsField', true).each(function(fieldEl) {
            var classes = fieldEl.dom.className.split(' ');
            var fieldConstructor = false;
            classes.each(function (c) {
                if (Vpc.Form.Fields[c]) {
                    fieldConstructor = Vpc.Form.Fields[c];
                }
            }, this);
            if (fieldConstructor) {
                var field = new fieldConstructor(fieldEl);
                fields.push(field);
            }
        }, this);
        var findField = function(fieldName) {
            var ret = null;
            fields.each(function(f) {
                if (f.getFieldName() == fieldName) {
                    ret = f;
                    return true;
                }
            }, this);
            return ret;
        };
        if (config.hideForValue) {
            config.hideForValue.each(function(hideForValue) {
                var field = findField(hideForValue.field);
                field.on('change', function(v, f) {
                    if (v == hideForValue.value) {
                        findField(hideForValue.hide).hide();
                    } else {
                        findField(hideForValue.hide).show();
                    }
                }, this);

                //initialize on startup
                if (field.getValue() == hideForValue.value) {
                    findField(hideForValue.hide).hide();
                }
            }, this);
        }

        form.child('form button.submit').on('click', function(e) {
            e.stopEvent();

            var button = formDiv.child('.button');
            button.down('.saving').show();
            button.down('.submit').hide();

            Ext.Ajax.request({
                url: config.controllerUrl + '/json-save',
                params: {
                    componentId: config.componentId
                },
                form: formDiv.down('form'),
                success: function(response, options, r) {

                    button.down('.saving').hide();
                    button.down('.submit').show();

                    // remove and set error classes for fields
                    Ext.each(formDiv.query('.vpsField'), function(field) {
                        Ext.fly(field).removeClass('vpsFieldError');
                    });
                    if (r.errorFields && r.errorFields.length) {
                        for (var i=0; i<r.errorFields.length; i++) {
                            var field = formDiv.child('.' + r.errorFields[i]);
                            if (field) field.addClass('vpsFieldError');
                        }
                    }

                    // remove and add error messages
                    var error = formDiv.parent().down('.webFormError');
                    if (error) error.remove();

                    if (r.errorMessages && r.errorMessages.length) {
                        var html = '
';
                        html += '
' + r.errorPlaceholder + ':
';
                        html += '
';
                        for (var i=0; i<r.errorMessages.length; i++) {
                            html += '
' + r.errorMessages[i] + '
';
                        }
                        html += '
';
                        html += '
';
                        formDiv.parent().createChild(html, formDiv);
                    }

                    // show success content
                    if (r.successContent) {
                        formDiv.parent().createChild(r.successContent);
                        formDiv.remove();
                    }
                },
                scope: this
            });

        });
    });
});
<commit_msg>fix submit<commit_after>//TODO move into own file, think about the name
Ext.namespace('Vpc.Form.Fields');
Vpc.Form.Fields.vpsField = function(fieldEl) {
    Vpc.Form.Fields.vpsField.superclass.constructor.call(this);
    this.el = fieldEl;
    this.el.enableDisplayMode();
    this.initField();
    this.addEvents('change');
};
Ext.extend(Vpc.Form.Fields.vpsField, Ext.util.Observable, {
    initField: function() {
        var inp = this.el.child('input');
        if (inp) {
            inp.on('change', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }
    },
    getFieldName: function() {
        var inp = this.el.child('input');
        if (!inp) return null;
        return inp.dom.name;
    },
    getValue: function() {
        var inp = this.el.child('input');
        if (!inp) return null;
        return inp.dom.value;
    },
    hide: function() {
        this.el.hide();
    },
    show: function() {
        this.el.show();
    }
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldRadio = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('input').each(function(input) {
            input.on('click', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getValue: function() {
        var ret = null;
        this.el.select('input').each(function(input) {
            if (input.dom.checked) {
                ret = input.dom.value;
            }
        }, this);
        return ret;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldSelect = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('select').each(function(input) {
            input.on('click', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getFieldName: function() {
        return this.el.child('select').dom.name;
    },
    getValue: function() {
        return this.el.child('select').dom.value;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldTextArea = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
        this.el.select('textarea').each(function(input) {
            input.on('keypress', function() {
                this.fireEvent('change', this.getValue());
            }, this);
        }, this);
    },
    getFieldName: function() {
        return this.el.child('textarea').dom.name;
    },
    getValue: function() {
        return this.el.child('textarea').dom.value;
    },
});

//TODO move into own file, think about the name
Vpc.Form.Fields.vpsFormFieldStatic = Ext.extend(Vpc.Form.Fields.vpsField, {
    initField: function() {
    },
    getValue: function() {
        return null;
    },
    getFieldName: function() {
        return null; //TODO?
    }
});

Vps.onContentReady(function() {
    Ext.select('.vpcForm', true).each(function(form) {
        var config = form.parent().down('.config', true);
        if (!config) return;
        config = Ext.decode(config.value);
        if (!config) return;

        //TODO there should be a proper Form class and object
        //but for now this should do...
        //TODO move into own file, think about the name
        var fields = [];
        form.select('.vpsField', true).each(function(fieldEl) {
            var classes = fieldEl.dom.className.split(' ');
            var fieldConstructor = false;
            classes.each(function (c) {
                if (Vpc.Form.Fields[c]) {
                    fieldConstructor = Vpc.Form.Fields[c];
                }
            }, this);
            if (fieldConstructor) {
                var field = new fieldConstructor(fieldEl);
                fields.push(field);
            }
        }, this);
        var findField = function(fieldName) {
            var ret = null;
            fields.each(function(f) {
                if (f.getFieldName() == fieldName) {
                    ret = f;
                    return true;
                }
            }, this);
            return ret;
        };
        if (config.hideForValue) {
            config.hideForValue.each(function(hideForValue) {
                var field = findField(hideForValue.field);
                field.on('change', function(v, f) {
                    if (v == hideForValue.value) {
                        findField(hideForValue.hide).hide();
                    } else {
                        findField(hideForValue.hide).show();
                    }
                }, this);

                //initialize on startup
                if (field.getValue() == hideForValue.value) {
                    findField(hideForValue.hide).hide();
                }
            }, this);
        }

        form.child('form button.submit').on('click', function(e) {
            e.stopEvent();

            var button = form.child('.button');
            button.down('.saving').show();
            button.down('.submit').hide();

            Ext.Ajax.request({
                url: config.controllerUrl + '/json-save',
                params: {
                    componentId: config.componentId
                },
                form: form.down('form'),
                success: function(response, options, r) {

                    button.down('.saving').hide();
                    button.down('.submit').show();

                    // remove and set error classes for fields
                    Ext.each(form.query('.vpsField'), function(field) {
                        Ext.fly(field).removeClass('vpsFieldError');
                    });
                    if (r.errorFields && r.errorFields.length) {
                        for (var i=0; i<r.errorFields.length; i++) {
                            var field = form.child('.' + r.errorFields[i]);
                            if (field) field.addClass('vpsFieldError');
                        }
                    }

                    // remove and add error messages
                    var error = form.parent().down('.webFormError');
                    if (error) error.remove();

                    if (r.errorMessages && r.errorMessages.length) {
                        var html = '
';
                        html += '
' + r.errorPlaceholder + ':
';
                        html += '
';
                        for (var i=0; i<r.errorMessages.length; i++) {
                            html += '
' + r.errorMessages[i] + '
';
                        }
                        html += '
';
                        html += '
';
                        form.parent().createChild(html, form);
                    }

                    // show success content
                    if (r.successContent) {
                        form.parent().createChild(r.successContent);
                        form.remove();
                    }
                },
                scope: this
            });

        });
    });
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

// Components
import WidgetsTable from 'components/admin/widgets/table/WidgetsTable';

export default function WidgetsIndex({ user = {} }) {
  const { user } = props;

  return (

      <WidgetsTable
        dataset={props.dataset}
        authorization={user.token}
      />

  );
}

WidgetsIndex.propTypes = {
  user: PropTypes.object.isRequired,
  dataset: PropTypes.string
};
<commit_msg>minor fix to WidgetIndex component<commit_after>import React from 'react';
import PropTypes from 'prop-types';

// Components
import WidgetsTable from 'components/admin/widgets/table/WidgetsTable';

export default function WidgetsIndex(props) {
  const { user } = props;

  return (

      <WidgetsTable
        dataset={props.dataset}
        authorization={user.token}
      />

  );
}

WidgetsIndex.defaultProps = { dataset: null };

WidgetsIndex.propTypes = {
  user: PropTypes.object.isRequired,
  dataset: PropTypes.string
};
<|endoftext|>


<commit_before>/**
 * Created by edgilmore on 12/17/2015.
 * Instructions: Compare two arrays and return a new array with any items only found in one of the original arrays.
 */
'use strict';
function filterByValue(arr){
    for(var i = 0; i < arr.length; i++){
        if(arr[i] === value){
            return true;
        }
    }
}
function diff(arr1, arr2) {
    var newArr = [];
    // Same, same; but different.
    for(var i = 0; i < arr1.length; i++){
        for (var j = 0; j <arr2.length; j++){
            if (arr1[i] === arr2[j]) {
                newArr.push(arr1[i]);
            }
        }
    }
    return newArr;
}

diff([1, 2, 3, 5], [1, 2, 3, 4, 5]);<commit_msg>Added solution for DiffTwoArrays<commit_after>/**
 * Created by edgilmore on 12/17/2015.
 * Instructions: Compare two arrays and return a new array with any items only found in one of the original arrays.
 */
'use strict';
function diff(arr1, arr2) {
    var newArr = [];
    for(var i = 0; i < arr1.length; i++){
        if(arr2.indexOf(arr1[i]) === -1) {
            if(newArr.indexOf(arr1[i]) === -1){
                newArr.push(arr1[i]);
            }
        }
    }
    for(var j = 0; j < arr2.length; j++){
        if(arr1.indexOf(arr2[j]) === -1){
            if(newArr.indexOf(arr2[j]) === -1){
                newArr.push(arr2[j]);
            }
        }
    }
    return newArr;
}

diff([1, 2, 3, 5], [1, 2, 3, 4, 5]);
diff(["diorite", "andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"]);
diff(["andesite", "grass", "dirt", "pink wool", "dead shrub"], ["diorite", "andesite", "grass", "dirt", "dead shrub"]);
diff(["andesite", "grass", "dirt", "dead shrub"], ["andesite", "grass", "dirt", "dead shrub"]);
diff([1, "calf", 3, "piglet"], [1, "calf", 3, 4]);
diff([], ["snuffleupagus", "cookie monster", "elmo"]);<|endoftext|>


<commit_before>define('component-ajax', ['./create-element.js', './secureajax.js'], (createElement, ajax) => {
    const componentSupport = {
        countryData: null,
        currentUser: null,
        currencies: null,
        iddPrefixes: [],

        // TODO: remove this and update all web components to use ajax.execute (can be passed as dependency)
        request: ajax.execute,

        init(req) {
            // if present, override .request with param passed to init
            componentSupport.request = req || componentSupport.request;

            // get country data from html attribute
            const countriesUrl = root.getAttribute('data-countries-url') || '';

            if (countriesUrl !== '') {
                ajax.execute({ url: countriesUrl, dataType: 'json' })
                    .then(data => {
                        componentSupport.countryData = data;
                    });
            }

            // get country data from html attribute
            const feedbackSchemaUrl = root.getAttribute('data-feedback-schema-url') || '';

            if (feedbackSchemaUrl !== '') {
                setTimeout(() => {
                    createElement(document.body, 'ca-feedback', {
                        dialogTitle: 'Feedback',
                        tip: 'Give us your feedback',
                        description: "We'd love to know what you think! Your feedback (good & bad) will help us improve the application.",
                        src: feedbackSchemaUrl
                    });
                }, 2000);
            }
        },

        getCurrencyMap() {
            if (!componentSupport.currencies && componentSupport.countryData) {
                componentSupport.currencies = {};
                componentSupport.countryData.forEach(country => {
                    componentSupport.currencies[country.iso] = country.currency;
                });
            }
            return componentSupport.currencies;
        },

        getUserLanguage() {
            // TODO FIX... or delete. This function is way too complicated.
            // TODO read the user language from the server 'me' record, provide a switcher client side.
            const userCountry = componentSupport.currentUser &&
                componentSupport.currentUser.properties.country &&
                componentSupport.countryData &&
                componentSupport.countryData.find(country => country.iso === componentSupport.currentUser.properties.country);
            let language = ((userCountry && userCountry.languages && userCountry.languages[0]) || (navigator.languages && (navigator.languages[0] || navigator.language)));
            if (userCountry) {
                if (language.indexOf('-') >= 0) {
                    language = language.substring(0, language.indexOf('-'));
                }
                language += `-${userCountry.iso}`;
            }
            return language;
        },

        getUserCurrency() {
            const userCountryCode = componentSupport.currentUser && (componentSupport.currentUser.properties.country || 'GB');
            const currencyMap = (componentSupport.getCurrencyMap() || {})[userCountryCode];
            // eslint-disable-next-line no-restricted-syntax
            for (const code in currencyMap) {
                if (Object.prototype.hasOwnProperty.call(currencyMap, code)) {
                    // Once only...
                    return {
                        code,
                        symbol: currencyMap[code].symbol
                    };
                }
            }
            return { code: 'XXX', symbol: 'XXX' }; // ISO standard for no currency.
        },

        /**
         * Go through schema properties resolving JSON pointers to external schemas (inlining external schema definitions)
         * @param {object} schema - schema to be parsed
         * @returns {promise} resolved state of schema
         */
        resolveExternalSchemaDefinitions(schema) {
            // get the properties section
            const properties = schema.properties;

            return new Promise((resolve, reject) => {
                const promises = [];

                // build an array of promises for each $ref
                // eslint-disable-next-line no-restricted-syntax
                for (const key in properties) {
                    if (Object.prototype.hasOwnProperty.call(properties, key)) {
                        const prop = properties[key];

                        if (prop.items &&
                            prop.items.extends &&
                            prop.items.extends[0] &&
                            prop.items.extends[0].$ref) {

                            promises.push(ajax.execute({ url: prop.items.extends[0].$ref, dataType: 'json' }));
                        }
                    }
                }

                if (promises.length > 0) {

                    // fetch each external schema definition and inline them
                    Promise.all(promises).then(results => {

                        // 1. find matching ref
                        // 2. replace extends ref with actual schema

                        results.forEach(item => {
                            // eslint-disable-next-line no-restricted-syntax
                            for (const key in properties) {
                                if (Object.prototype.hasOwnProperty.call(properties, key)) {
                                    const prop = properties[key];

                                    if (prop.items &&
                                        prop.items.extends &&
                                        prop.items.extends[0] &&
                                        prop.items.extends[0].$ref === item.id) {

                                        // replace extends ref with actual schema
                                        prop.items.extends[0] = item;
                                    }
                                }
                            }
                        });

                        resolve(schema);
                    })
                    .catch(err => {
                        reject(err);
                    });
                } else {
                    // no $ref to process, so just resolve
                    resolve(schema);
                }
            });
        },

        /**
         * Returns a list of international dialing codes to be consumsed by ca-tel components
         * @returns {array} sorted indexs
         */
        getTelephoneCodes() {
            if (componentSupport.iddPrefixes.length <= 0) {
                // keep a copy of what we've alrady added (to avoid duplicates)
                const existance = {};

                // add empty option
                componentSupport.iddPrefixes.push({ value: '', label: '', countryCode: '' });

                // filter app country data to return country codes
                componentSupport.countryData.forEach(item => {
                    const telCode = parseInt(item.code[0] || '-1', 10);
                    const population = item.population;

                    if (telCode > -1) {

                        // if we've already use this country code but we have a new one with a greater population, remove the old one
                        if (existance[telCode] || population > existance[telCode]) {
                            componentSupport.iddPrefixes = componentSupport.iddPrefixes.filter(iid => (iid.value !== telCode));
                        }

                        componentSupport.iddPrefixes.push({
                            value: telCode,
                            label: telCode,
                            countryCode: item.iso
                        });

                        // store a copy of this value as a property (makes existance testing faster)
                        existance[telCode] = population;
                    }
                });

                // TODO: FIX: nested-ternary are very unreadable. Saving lines for readbility is insane.
                // store the sorted list of international dialing codes on app object to we only have to done this once
                // eslint-disable-next-line no-confusing-arrow
                componentSupport.iddPrefixes = componentSupport.iddPrefixes.sort((a, b) =>
                    // eslint-disable-next-line no-nested-ternary
                    a.value > b.value ? 1 : a.value < b.value ? -1 : 0
                );
            }

            // return sorted array
            return componentSupport.iddPrefixes;
        }
    };

    return componentSupport;
});
<commit_msg>fixing module name<commit_after>define('component-support', ['./create-element.js', './secureajax.js'], (createElement, ajax) => {
    const componentSupport = {
        countryData: null,
        currentUser: null,
        currencies: null,
        iddPrefixes: [],

        // TODO: remove this and update all web components to use ajax.execute (can be passed as dependency)
        request: ajax.execute,

        init(req) {
            // if present, override .request with param passed to init
            componentSupport.request = req || componentSupport.request;

            // get country data from html attribute
            const countriesUrl = root.getAttribute('data-countries-url') || '';

            if (countriesUrl !== '') {
                ajax.execute({ url: countriesUrl, dataType: 'json' })
                    .then(data => {
                        componentSupport.countryData = data;
                    });
            }

            // get country data from html attribute
            const feedbackSchemaUrl = root.getAttribute('data-feedback-schema-url') || '';

            if (feedbackSchemaUrl !== '') {
                setTimeout(() => {
                    createElement(document.body, 'ca-feedback', {
                        dialogTitle: 'Feedback',
                        tip: 'Give us your feedback',
                        description: "We'd love to know what you think! Your feedback (good & bad) will help us improve the application.",
                        src: feedbackSchemaUrl
                    });
                }, 2000);
            }
        },

        getCurrencyMap() {
            if (!componentSupport.currencies && componentSupport.countryData) {
                componentSupport.currencies = {};
                componentSupport.countryData.forEach(country => {
                    componentSupport.currencies[country.iso] = country.currency;
                });
            }
            return componentSupport.currencies;
        },

        getUserLanguage() {
            // TODO FIX... or delete. This function is way too complicated.
            // TODO read the user language from the server 'me' record, provide a switcher client side.
            const userCountry = componentSupport.currentUser &&
                componentSupport.currentUser.properties.country &&
                componentSupport.countryData &&
                componentSupport.countryData.find(country => country.iso === componentSupport.currentUser.properties.country);
            let language = ((userCountry && userCountry.languages && userCountry.languages[0]) || (navigator.languages && (navigator.languages[0] || navigator.language)));
            if (userCountry) {
                if (language.indexOf('-') >= 0) {
                    language = language.substring(0, language.indexOf('-'));
                }
                language += `-${userCountry.iso}`;
            }
            return language;
        },

        getUserCurrency() {
            const userCountryCode = componentSupport.currentUser && (componentSupport.currentUser.properties.country || 'GB');
            const currencyMap = (componentSupport.getCurrencyMap() || {})[userCountryCode];
            // eslint-disable-next-line no-restricted-syntax
            for (const code in currencyMap) {
                if (Object.prototype.hasOwnProperty.call(currencyMap, code)) {
                    // Once only...
                    return {
                        code,
                        symbol: currencyMap[code].symbol
                    };
                }
            }
            return { code: 'XXX', symbol: 'XXX' }; // ISO standard for no currency.
        },

        /**
         * Go through schema properties resolving JSON pointers to external schemas (inlining external schema definitions)
         * @param {object} schema - schema to be parsed
         * @returns {promise} resolved state of schema
         */
        resolveExternalSchemaDefinitions(schema) {
            // get the properties section
            const properties = schema.properties;

            return new Promise((resolve, reject) => {
                const promises = [];

                // build an array of promises for each $ref
                // eslint-disable-next-line no-restricted-syntax
                for (const key in properties) {
                    if (Object.prototype.hasOwnProperty.call(properties, key)) {
                        const prop = properties[key];

                        if (prop.items &&
                            prop.items.extends &&
                            prop.items.extends[0] &&
                            prop.items.extends[0].$ref) {

                            promises.push(ajax.execute({ url: prop.items.extends[0].$ref, dataType: 'json' }));
                        }
                    }
                }

                if (promises.length > 0) {

                    // fetch each external schema definition and inline them
                    Promise.all(promises).then(results => {

                        // 1. find matching ref
                        // 2. replace extends ref with actual schema

                        results.forEach(item => {
                            // eslint-disable-next-line no-restricted-syntax
                            for (const key in properties) {
                                if (Object.prototype.hasOwnProperty.call(properties, key)) {
                                    const prop = properties[key];

                                    if (prop.items &&
                                        prop.items.extends &&
                                        prop.items.extends[0] &&
                                        prop.items.extends[0].$ref === item.id) {

                                        // replace extends ref with actual schema
                                        prop.items.extends[0] = item;
                                    }
                                }
                            }
                        });

                        resolve(schema);
                    })
                    .catch(err => {
                        reject(err);
                    });
                } else {
                    // no $ref to process, so just resolve
                    resolve(schema);
                }
            });
        },

        /**
         * Returns a list of international dialing codes to be consumsed by ca-tel components
         * @returns {array} sorted indexs
         */
        getTelephoneCodes() {
            if (componentSupport.iddPrefixes.length <= 0) {
                // keep a copy of what we've alrady added (to avoid duplicates)
                const existance = {};

                // add empty option
                componentSupport.iddPrefixes.push({ value: '', label: '', countryCode: '' });

                // filter app country data to return country codes
                componentSupport.countryData.forEach(item => {
                    const telCode = parseInt(item.code[0] || '-1', 10);
                    const population = item.population;

                    if (telCode > -1) {

                        // if we've already use this country code but we have a new one with a greater population, remove the old one
                        if (existance[telCode] || population > existance[telCode]) {
                            componentSupport.iddPrefixes = componentSupport.iddPrefixes.filter(iid => (iid.value !== telCode));
                        }

                        componentSupport.iddPrefixes.push({
                            value: telCode,
                            label: telCode,
                            countryCode: item.iso
                        });

                        // store a copy of this value as a property (makes existance testing faster)
                        existance[telCode] = population;
                    }
                });

                // TODO: FIX: nested-ternary are very unreadable. Saving lines for readbility is insane.
                // store the sorted list of international dialing codes on app object to we only have to done this once
                // eslint-disable-next-line no-confusing-arrow
                componentSupport.iddPrefixes = componentSupport.iddPrefixes.sort((a, b) =>
                    // eslint-disable-next-line no-nested-ternary
                    a.value > b.value ? 1 : a.value < b.value ? -1 : 0
                );
            }

            // return sorted array
            return componentSupport.iddPrefixes;
        }
    };

    return componentSupport;
});
<|endoftext|>