<commit_before>
// Metadata model
var useAsId = ["imdb_id", "yt_id", "filmon_id"];
app.factory('metadata', function() {
	return function metadata(meta) {
		var self = this;
		_.extend(self, meta);

		self.popularities = self.popularities || {};

		// auto-generate id from useAsId properties or generic "id" property
		var usableId = (self.id && self.id.split(":").length == 2) ? self.id : null;
		Object.defineProperty(self, "id", { enumerable: true, get: function() {
			if (self.imdb_id) return self.imdb_id;
			if (usableId) return usableId;
			for (var i=0; i!=useAsId.length; i++) if (self[useAsId[i]]) return useAsId[i]+":"+self[useAsId[i]];
		} });

		// this gets passed to stream.find add-on method
		self.getQuery = function(extra) {
			var query = _.extend({ type: self.type }, _.pick(self, useAsId));
			if (self.type == "series") _.extend(query, { season: 1, episode: 1 });
			if (extra) _.extend(query, { yt_id: extra.id, season: extra.season, episode: extra.number });
			return query;
		};
	};
});
<commit_msg>metadata: better support of generic ids<commit_after>
// Metadata model
var useAsId = ["imdb_id", "yt_id", "filmon_id"];
app.factory('metadata', function() {
	return function metadata(meta) {
		var self = this;
		_.extend(self, meta);

		self.popularities = self.popularities || {};

		// auto-generate id from useAsId properties or generic "id" property
		var usableId = (self.id && self.id.split(":").length == 2) ? self.id : null;
		Object.defineProperty(self, "id", { enumerable: true, get: function() {
			if (self.imdb_id) return self.imdb_id;
			if (usableId) return usableId;
			for (var i=0; i!=useAsId.length; i++) if (self[useAsId[i]]) return useAsId[i]+":"+self[useAsId[i]];
		} });

		var getIdFromStr = function(str) {
			if (str.match("^tt")) return { imdb_id: str };
			if (str.match(":")) return _.object([ str.split(":")[0] ], [ str.split(":")[1] ]); // generic
		}

		// this gets passed to stream.find add-on method
		self.getQuery = function(extra) {
			var query = _.extend({ type: self.type }, getIdFromStr(this.id));
			if (self.type == "series") _.extend(query, { season: 1, episode: 1 });
			if (extra) _.extend(query, { yt_id: extra.id, season: extra.season, episode: extra.number });
			return query;
		};
	};
});
<|endoftext|>


<commit_before>define([
	"dojo/_base/declare", "dgrid/Grid", "dojo/store/JsonRest", "dgrid/extensions/DijitRegistry", "dgrid/extensions/Pagination",
	"dgrid/Keyboard", "dgrid/Selection", "./formatter", "dgrid/extensions/ColumnResizer", "dgrid/extensions/ColumnHider",
	"dgrid/extensions/DnD", "dojo/dnd/Source", "dojo/_base/Deferred", "dojo/aspect", "dojo/_base/lang", "../util/PathJoin"],
function(declare, Grid, Store, DijitRegistry, Pagination,
		 Keyboard, Selection, formatter, ColumnResizer,
		 ColumnHider, DnD, DnDSource,
		 Deferred, aspect, lang, PathJoin){
	return declare([Grid, Pagination, ColumnHider, Keyboard, ColumnResizer, DijitRegistry, Selection], {
		constructor: function(){
			this.dndParams.creator = lang.hitch(this, function(item, hint){
				//console.log("item: ", item, " hint:", hint, "dataType: ", this.dndDataType);
				var avatar = dojo.create("div", {innerHTML: item.organism_name || item.ncbi_taxon_id || item.id});
				avatar.data = item;
				if(hint == 'avatar'){
					// create your avatar if you want
				}
				return {node: avatar, data: item, type: this.dndDataType}
			})
		},
		store: null,
		selectionMode: "extended",
		allowTextSelection: false,
		allowSelectAll: true,
		deselectOnRefresh: false,
		rowsPerPage: 200,
		minRowsPerPage: 25,
		bufferRows: 100,
		maxRowsPerPage: 200,
		pagingDelay: 250,
//		pagingMethod: "throttleDelayed",
		farOffRemoval: 2000,
		keepScrollPosition: true,
		rowHeight: 24,
		loadingMessage: "Loading...",
		primaryKey: "id",
		dndDataType: "genome",
		dndParams: {
			accept: "none",
			selfAccept: false,
			copyOnly: true
		},
		_setApiServer: function(server, token){
			//console.log("_setapiServerAttr: ", server);
			this.apiServer = server;
			var t = token || this.apiToken || "";
			this.set('store', this.createStore(this.dataModel, this.primaryKey, t), this.buildQuery());
		},

		apiToken: "",
		_setTotalRows: function(rows){
			// console.log("this.id:", this.id, "_setTotalRows()");
			if(rows){
				this.totalRows = rows;
				// console.log("Total Rows: ", rows);
				if(this.controlButton){
					//console.log("this.controlButton: ", this.controlButton);
					if(!this._originalTitle){
						this._originalTitle = this.controlButton.get('label');
					}
					this.controlButton.set('label', this._originalTitle + " (" + rows + ")");
					//console.log(this.controlButton);
				}
			}
		},

		startup: function(){
			if(this._started){
				return;
			}
			var _self = this;
			aspect.before(_self, 'renderArray', function(results){
				Deferred.when(results.total, function(x){
					_self.set("totalRows", x);
				});
			});

			if(!this.store && this.dataModel){
				this.store = this.createStore(this.dataModel, this.primaryKey);
			}
			this.inherited(arguments);
			this._started = true;

		},

		createStore: function(dataModel, pk, token){
			// console.log("Create Store for ", dataModel, " at ", this.apiServer, " TOKEN: ", token);
			var store = new Store({
				target: PathJoin((this.apiServer ? (this.apiServer) : ""), dataModel) + "/",
				idProperty: pk,
				headers: {
					"accept": "application/json",
					"content-type": "application/json",
					'X-Requested-With': null,
					'Authorization': token ? token : (window.App.authorizationToken || "")
				}
			});
			//console.log("store: ", store);
			return store;
		}
	});
});
<commit_msg>enable column reordering on all pagegrid derived grids<commit_after>define([
	"dojo/_base/declare", "dgrid/Grid", "dojo/store/JsonRest", "dgrid/extensions/DijitRegistry", "dgrid/extensions/Pagination",
	"dgrid/Keyboard", "dgrid/Selection", "./formatter", "dgrid/extensions/ColumnResizer", "dgrid/extensions/ColumnHider",
	"dgrid/extensions/DnD", "dojo/dnd/Source", "dojo/_base/Deferred", "dojo/aspect", "dojo/_base/lang", "../util/PathJoin",
	"dgrid/extensions/ColumnReorder"
],
function(declare, Grid, Store, DijitRegistry, Pagination,
		 Keyboard, Selection, formatter, ColumnResizer,
		 ColumnHider, DnD, DnDSource,
		 Deferred, aspect, lang, PathJoin,
		 ColumnReorder

){
	return declare([Grid, Pagination, ColumnHider, Keyboard, ColumnResizer,ColumnReorder, DijitRegistry, Selection], {
		constructor: function(){
			this.dndParams.creator = lang.hitch(this, function(item, hint){
				//console.log("item: ", item, " hint:", hint, "dataType: ", this.dndDataType);
				var avatar = dojo.create("div", {innerHTML: item.organism_name || item.ncbi_taxon_id || item.id});
				avatar.data = item;
				if(hint == 'avatar'){
					// create your avatar if you want
				}
				return {node: avatar, data: item, type: this.dndDataType}
			})
		},
		store: null,
		selectionMode: "extended",
		allowTextSelection: false,
		allowSelectAll: true,
		deselectOnRefresh: false,
		rowsPerPage: 200,
		minRowsPerPage: 25,
		bufferRows: 100,
		maxRowsPerPage: 200,
		pagingDelay: 250,
//		pagingMethod: "throttleDelayed",
		farOffRemoval: 2000,
		keepScrollPosition: true,
		rowHeight: 24,
		loadingMessage: "Loading...",
		primaryKey: "id",
		dndDataType: "genome",
		dndParams: {
			accept: "none",
			selfAccept: false,
			copyOnly: true
		},
		_setApiServer: function(server, token){
			//console.log("_setapiServerAttr: ", server);
			this.apiServer = server;
			var t = token || this.apiToken || "";
			this.set('store', this.createStore(this.dataModel, this.primaryKey, t), this.buildQuery());
		},

		apiToken: "",
		_setTotalRows: function(rows){
			// console.log("this.id:", this.id, "_setTotalRows()");
			if(rows){
				this.totalRows = rows;
				// console.log("Total Rows: ", rows);
				if(this.controlButton){
					//console.log("this.controlButton: ", this.controlButton);
					if(!this._originalTitle){
						this._originalTitle = this.controlButton.get('label');
					}
					this.controlButton.set('label', this._originalTitle + " (" + rows + ")");
					//console.log(this.controlButton);
				}
			}
		},

		startup: function(){
			if(this._started){
				return;
			}
			var _self = this;
			aspect.before(_self, 'renderArray', function(results){
				Deferred.when(results.total, function(x){
					_self.set("totalRows", x);
				});
			});

			if(!this.store && this.dataModel){
				this.store = this.createStore(this.dataModel, this.primaryKey);
			}
			this.inherited(arguments);
			this._started = true;

		},

		createStore: function(dataModel, pk, token){
			// console.log("Create Store for ", dataModel, " at ", this.apiServer, " TOKEN: ", token);
			var store = new Store({
				target: PathJoin((this.apiServer ? (this.apiServer) : ""), dataModel) + "/",
				idProperty: pk,
				headers: {
					"accept": "application/json",
					"content-type": "application/json",
					'X-Requested-With': null,
					'Authorization': token ? token : (window.App.authorizationToken || "")
				}
			});
			//console.log("store: ", store);
			return store;
		}
	});
});
<|endoftext|>


<commit_before>'use strict';

var app = app || {};

(function(module) {

  const mapThings = {};

  mapThings.userLocation;
  mapThings.map;
  mapThings.infoWindow;
  mapThings.geocoder;
  mapThings.markerWindow;

  mapThings.initMap = function initMap() {
    mapThings.geocoder = new google.maps.Geocoder();
    mapThings.map = new google.maps.Map(document.getElementById('google-map'), {
      center: {lat: 47.608, lng: -122.335167},
      zoom: 13
    });
    mapThings.infoWindow = new google.maps.InfoWindow;

    if(navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function(position) {
        var pos = {
          lat: position.coords.latitude,
          lng: position.coords.longitude
        };
        mapThings.userLocation = new google.maps.LatLng(pos.lat, pos.lng);

        mapThings.infoWindow.setPosition(pos);
        mapThings.infoWindow.setContent('Current Location');
        mapThings.infoWindow.open(mapThings.map);
        mapThings.map.setCenter(pos);
      }, function() {
        handleLocationError(true, mapThings.infoWindow, mapThings.map.getCenter());
      });
    } else {
      handleLocationError(false, mapThings.infoWindow, mapThings.map.getCenter());
    }
    google.maps.event.addDomListener(window, 'resize', function () {
      var center = mapThings.map.getCenter();
      google.maps.event.trigger(mapThings.map, 'resize');
      mapThings.map.setCenter(center);
    })
  };

  function handleLocationError(browserHasGeolocation, infoWindow, pos) {
    infoWindow.setPosition(pos);
    infoWindow.setContent(browserHasGeolocation ?
      'Error: The Geolocation service failed.' :
      'Error: Your browser doesn\'t support geolocation.');
    infoWindow.open(mapThings.map);
  }

  mapThings.makeMarkers = function () {
    app.meetings.filtered.forEach((ele, idx) => {
      var marker = new google.maps.Marker({
        map: mapThings.map,
        position: new google.maps.LatLng(ele.lat, ele.lng)
      })
      var infoMarker = new google.maps.InfoWindow({
        content: `<p>Test string</p>`
      })
      marker.addListener('click', function() {
        infoMarker.open(mapThings.map, marker);
      })
    });
  };
  module.mapThings = mapThings;
})(app);
<commit_msg>Directions service function in progress<commit_after>'use strict';

var app = app || {};

(function(module) {

  const mapThings = {};

  mapThings.userLocation;
  mapThings.map;
  mapThings.infoWindow;
  mapThings.geocoder;
  mapThings.markerWindow;
  mapThings.directionsService;
  mapThings.directionsDisplay;

  mapThings.initMap = function initMap() {
    mapThings.directionsService = new google.maps.DirectionsService;
    mapThings.directionsDisplay = new google.maps.DirectionsRenderer;
    mapThings.geocoder = new google.maps.Geocoder();
    mapThings.map = new google.maps.Map(document.getElementById('google-map'), {
      center: {lat: 47.608, lng: -122.335167},
      zoom: 13
    });
    mapThings.directionsDisplay.setMap(mapThings.map);

    mapThings.infoWindow = new google.maps.InfoWindow;

    if(navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function(position) {
        var pos = {
          lat: position.coords.latitude,
          lng: position.coords.longitude
        };
        mapThings.userLocation = new google.maps.LatLng(pos.lat, pos.lng);

        mapThings.infoWindow.setPosition(pos);
        mapThings.infoWindow.setContent('Current Location');
        mapThings.infoWindow.open(mapThings.map);
        mapThings.map.setCenter(pos);
      }, function() {
        handleLocationError(true, mapThings.infoWindow, mapThings.map.getCenter());
      });
    } else {
      handleLocationError(false, mapThings.infoWindow, mapThings.map.getCenter());
    }
    google.maps.event.addDomListener(window, 'resize', function () {
      var center = mapThings.map.getCenter();
      google.maps.event.trigger(mapThings.map, 'resize');
      mapThings.map.setCenter(center);
    })
  };

  function handleLocationError(browserHasGeolocation, infoWindow, pos) {
    infoWindow.setPosition(pos);
    infoWindow.setContent(browserHasGeolocation ?
      'Error: The Geolocation service failed.' :
      'Error: Your browser doesn\'t support geolocation.');
    infoWindow.open(mapThings.map);
  }

  mapThings.makeMarkers = function () {
    app.meetings.filtered.forEach((ele, idx) => {
      var marker = new google.maps.Marker({
        map: mapThings.map,
        position: new google.maps.LatLng(ele.lat, ele.lng)
      })
      var infoMarker = new google.maps.InfoWindow({
        content: `<p>${ele.group_name} at</p> <p>${ele.location_name}</p><p>starts at ${ele.time}.</p><input type="button" id="marker${idx}" onclick="alert('foo')" value="Get Directions">`
      })
      marker.addListener('click', function() {
        infoMarker.open(mapThings.map, marker);
      })
      $(`#marker${idx}`).on('click', (directionsService, directionsDisplay) => {
        console.log('Hi Nathan');
        mapThings.directionsService.route({
          origin: mapThings.userLocation,
          destination: marker.position,
          travelMode: 'DRIVING'
        }, function(response, status) {
          if (status === 'OK') {
            mapThings.directionsDisplay.setDirections(response);
          } else {
            window.alert('Directions request failed due to ' + status);
          }
        })
      });
    })
  };
  module.mapThings = mapThings;
})(app);
<|endoftext|>


<commit_before>$(document).ready(function(){

	/* begin validation*/
	$("#contactForm").validate({

		// setup handling of form errors
		debug: true,
		errorClass: "alert alert-danger",
		errorLabelContainer: "#output-area",
		errorElement: "div",

		// rules define what is good/bad input
		// each rule starts with the form input element's NAME attribute
		rules: {
			name: {
				required: true
			},
			email: {
				email: true,
				required: true
			},
			// phone: {
				//phoneUs: true,
				//required: true
			//},
			message: {
				required: true,
				maxlength: 2000
			}
		},

		// error messages to display to the end user
		messages: {
			name: {
				required: "Please enter your name."
			},
			email: {
				email: "Please enter a valid email address.",
				required: "Please enter a valid email address."
			},
			phone: {
				phoneUs: "Please enter a valid phone number."
			//},
			message: {
				required: "Please enter a message.",
				maxlength: "2000 characters max."
			}
		},

		submitHandler: function(form) {
			$("#contactForm").ajaxSubmit({
				type: "POST",
				url: $("#contactForm").attr("action"),

				success: function(ajaxOutput) {
					// clear the output area's formatting
					$("#output-area").css("display", "");

					// write the server's reply to the output area
					$("#output-area").html(ajaxOutput);

					// reset the form if it was successful
					if($(".alert-success").length >= 1) {
						$("#contactForm")[0].reset();
					}
				}
			})
		}

	});/* end validate function */

});/*end document.ready()*/<commit_msg>Rochelle fixed the stupid form-validate brokenness.<commit_after>$(document).ready(function(){

	/* begin validation*/
	$("#contactForm").validate({

		// setup handling of form errors
		debug: true,
		errorClass: "alert alert-danger",
		errorLabelContainer: "#output-area",
		errorElement: "div",

		// rules define what is good/bad input
		// each rule starts with the form input element's NAME attribute
		rules: {
			name: {
				required: true
			},
			email: {
				email: true,
				required: true
			},
			// phone: {
				//phoneUs: true,
				//required: true
			//},
			message: {
				required: true,
				maxlength: 2000
			}
		},

		// error messages to display to the end user
		messages: {
			name: {
				required: "Please enter your name."
			},
			email: {
				email: "Please enter a valid email address.",
				required: "Please enter a valid email address."
			},
			phone: {
				phoneUs: "Please enter a valid phone number."
			},
			message: {
				required: "Please enter a message.",
				maxlength: "2000 characters max."
			}
		},

		submitHandler: function(form) {
			$("#contactForm").ajaxSubmit({
				type: "POST",
				url: $("#contactForm").attr("action"),

				success: function(ajaxOutput) {
					// clear the output area's formatting
					$("#output-area").css("display", "");

					// write the server's reply to the output area
					$("#output-area").html(ajaxOutput);

					// reset the form if it was successful
					if($(".alert-success").length >= 1) {
						$("#contactForm")[0].reset();
					}
				}
			})
		}

	});/* end validate function */

});/*end document.ready()*/<|endoftext|>


<commit_before>/*
 * Daniel Savaria
 * Released under the MIT license
 */

var Solver = function (puzzle) {
    this.puzzle = puzzle;
};

Solver.prototype.solve = function () {
    this.rowPossibilities = [];
    for (var row = 0; row < this.puzzle.getRowCount(); row++) {
        this.rowPossibilities.push(assemblePossibilities(
                this.puzzle.getRow(row), this.puzzle.getWidth()));
    }

    this.columnPossibilities = [];
    for (var col = 0; col < this.puzzle.getColumnCount(); col++) {
        this.columnPossibilities.push(assemblePossibilities(
                this.puzzle.getColumn(col), this.puzzle.getHeight()));
    }

    this.fillSolutionFromPossibilities();

    //@TODO...
};

Solver.prototype.fillSolutionFromPossibilities = function () {
    for (var col = 0; col < this.puzzle.getColumnCount(); col++) {
        for (var row = 0; row < this.puzzle.getRowCount(); row++) {
            if (this.puzzle.getSolutionAt(col, row) === Puzzle.UNKNOWN) {
                var answer = findCommonPossibility(this.columnPossibilities[col], row);
                if (answer !== Puzzle.UNKNOWN) {
                    this.puzzle.setSolutionAt(col, row, answer);
                }
            }
            if (this.puzzle.getSolutionAt(col, row) === Puzzle.UNKNOWN) {
                var answer = findCommonPossibility(this.rowPossibilities[row], col);
                if (answer !== Puzzle.UNKNOWN) {
                    this.puzzle.setSolutionAt(col, row, answer);
                }
            }
        }
    }
};

function findCommonPossibility(possibilities, index) {
    var toReturn = possibilities[0][index];
    for (var i = 0; i < possibilities.length; i++) {
        if (possibilities[i][index] !== toReturn) {
            toReturn = Puzzle.UNKNOWN;
            break;
        }
    }
    return toReturn;
}

/**
 * Creates all of the possible solutions for a given row or column.
 *
 * @param {type} data
 * @param {number} totalLength
 * @returns {Array|createPossibilities.toReturn}
 */
function assemblePossibilities(data, totalLength) {
    var filledSegments = data.length;
    var filled = data.reduce(function (a, b) {
        return a + b;
    });
    var emptySegments = filledSegments + 1;
    var empty = totalLength - filled;

    //empty is the number of empty squares that must exist in this row or
    //column. However, there must be at least one empty square between each
    //segment of filled squares. So variableEmpty is the number of empty squares
    //left over after we take into account the one required empty between each
    //filled segment.
    var variableEmpty = empty;
    if (emptySegments > 2) {
        variableEmpty -= emptySegments - 2;
    }

    //figure out the different ways to distribute the empty squares.
    var e = setUpEmpty(variableEmpty, 0, emptySegments);

    //setUpEmpty only distributes the variable empty squares, this pusts the
    //required squares back in to the count.
    for (var i = 0; i < e.length; i++) {
        for (var j = 1; j < e[i].length - 1; j++) {
            e[i][j]++;
        }
    }

    return createPossibilities(data, e);
}

/**
 * Given a row or column and the possible empty segments, put together all the
 * possible solutions.
 *
 * @param {type} data
 * @param {Array} empties
 * @returns {Array|createPossibilities.toReturn}
 */
function createPossibilities(data, empties) {
    var toReturn = [];
    empties.forEach(function (empty) {
        var possibility = [];
        toReturn.push(possibility);
        var copy = data.slice(0);
        for (var i = 0; i < empty.length; i++) {
            while (empty[i] > 0) {
                possibility.push(Puzzle.EMPTY);
                empty[i]--;
            }
            if (i < copy.length) {
                while (copy[i] > 0) {
                    possibility.push(Puzzle.FILLED);
                    copy[i]--;
                }
            }
        }
    });
    return toReturn;
}

/**
 * This recursive function takes the  number of empty squares to distribute, the
 * depth of the calculation, and the number of segments to distribute the empty
 * elements to.
 *
 * @param {number} count
 * @param {number} start
 * @param {number} segmentCount
 * @returns {Array|setUpEmpty.toReturn}
 */
function setUpEmpty(count, start, segmentCount) {
    var toReturn = [];
    if (start === segmentCount - 1) {
        toReturn[0] = [count];
    } else {
        for (var i = 0; i <= count; i++) {
            var nextPart = setUpEmpty(count - i, start + 1, segmentCount);
            for (var j = 0; j < nextPart.length; j++) {
                var temp = [];
                temp[0] = i;
                temp = temp.concat(nextPart[j]);
                toReturn.push(temp);
            }
        }
    }
    return toReturn;
}

<commit_msg>Finished the basic solving algorithm<commit_after>/*
 * Daniel Savaria
 * Released under the MIT license
 */

/**
 *
 * @param {Puzzle} puzzle
 * @returns {Solver}
 */
var Solver = function (puzzle) {
    this.puzzle = puzzle;
    this.rowPossibilities = [];
    this.columnPossibilities = [];
};

Solver.prototype.solve = function () {
    this.rowPossibilities = [];
    for (var row = 0; row < this.puzzle.getRowCount(); row++) {
        this.rowPossibilities.push(assemblePossibilities(
                this.puzzle.getRow(row), this.puzzle.getWidth()));
    }

    this.columnPossibilities = [];
    for (var col = 0; col < this.puzzle.getColumnCount(); col++) {
        this.columnPossibilities.push(assemblePossibilities(
                this.puzzle.getColumn(col), this.puzzle.getHeight()));
    }

    var keepGoing = true;
    while (keepGoing) {
        keepGoing = this.fillSolutionFromPossibilities();
        this.removeImpossibilities();
    }
};

Solver.prototype.removeImpossibilities = function () {
    for (var col = 0; col < this.puzzle.getColumnCount(); col++) {
        for (var row = 0; row < this.puzzle.getRowCount(); row++) {
            var answer = this.puzzle.getSolutionAt(col, row);
            if (answer !== Puzzle.UNKNOWN) {
                var possSet = this.columnPossibilities[col];
                for (var poss = possSet.length - 1; poss >= 0; poss--) {
                    if (answer !== possSet[poss][row]) {
                        possSet.splice(poss, 1);
                    }
                }

                possSet = this.rowPossibilities[row];
                for (var poss = possSet.length - 1; poss >= 0; poss--) {
                    if (answer !== possSet[poss][col]) {
                        possSet.splice(poss, 1);
                    }
                }
            }
        }
    }
};

Solver.prototype.fillSolutionFromPossibilities = function () {
    var updated = false;
    for (var col = 0; col < this.puzzle.getColumnCount(); col++) {
        for (var row = 0; row < this.puzzle.getRowCount(); row++) {
            if (this.puzzle.getSolutionAt(col, row) === Puzzle.UNKNOWN) {
                var answer = findCommonPossibility(this.columnPossibilities[col], row);
                if (answer !== Puzzle.UNKNOWN) {
                    this.puzzle.setSolutionAt(col, row, answer);
                    updated = true;
                }
            }
            if (this.puzzle.getSolutionAt(col, row) === Puzzle.UNKNOWN) {
                var answer = findCommonPossibility(this.rowPossibilities[row], col);
                if (answer !== Puzzle.UNKNOWN) {
                    this.puzzle.setSolutionAt(col, row, answer);
                    updated = true;
                }
            }
        }
    }
    return updated;
};

function findCommonPossibility(possibilities, index) {
    var toReturn = possibilities[0][index];
    for (var i = 0; i < possibilities.length; i++) {
        if (possibilities[i][index] !== toReturn) {
            toReturn = Puzzle.UNKNOWN;
            break;
        }
    }
    return toReturn;
}

/**
 * Creates all of the possible solutions for a given row or column.
 *
 * @param {type} data
 * @param {number} totalLength
 * @returns {Array|createPossibilities.toReturn}
 */
function assemblePossibilities(data, totalLength) {
    var filledSegments = data.length;
    var filled = data.reduce(function (a, b) {
        return a + b;
    });
    var emptySegments = filledSegments + 1;
    var empty = totalLength - filled;

    //empty is the number of empty squares that must exist in this row or
    //column. However, there must be at least one empty square between each
    //segment of filled squares. So variableEmpty is the number of empty squares
    //left over after we take into account the one required empty between each
    //filled segment.
    var variableEmpty;
    if (emptySegments > 2) {
        variableEmpty = empty - (emptySegments - 2);
    } else {
        variableEmpty = empty;
    }

    //figure out the different ways to distribute the empty squares.
    var e = setUpEmpty(variableEmpty, 0, emptySegments);

    //setUpEmpty only distributes the variable empty squares, this pusts the
    //required squares back in to the count.
    for (var i = 0; i < e.length; i++) {
        for (var j = 1; j < e[i].length - 1; j++) {
            e[i][j]++;
        }
    }

    return createPossibilities(data, e);
}

/**
 * Given a row or column and the possible empty segments, put together all the
 * possible solutions.
 *
 * @param {type} data
 * @param {Array} empties
 * @returns {Array|createPossibilities.toReturn}
 */
function createPossibilities(data, empties) {
    var toReturn = [];
    empties.forEach(function (empty) {
        var possibility = [];
        toReturn.push(possibility);
        var copy = data.slice(0);
        for (var i = 0; i < empty.length; i++) {
            while (empty[i] > 0) {
                possibility.push(Puzzle.EMPTY);
                empty[i]--;
            }
            if (i < copy.length) {
                while (copy[i] > 0) {
                    possibility.push(Puzzle.FILLED);
                    copy[i]--;
                }
            }
        }
    });
    return toReturn;
}

/**
 * This recursive function takes the  number of empty squares to distribute, the
 * depth of the calculation, and the number of segments to distribute the empty
 * elements to.
 *
 * @param {number} count
 * @param {number} start
 * @param {number} segmentCount
 * @returns {Array|setUpEmpty.toReturn}
 */
function setUpEmpty(count, start, segmentCount) {
    var toReturn = [];
    if (start === segmentCount - 1) {
        toReturn[0] = [count];
    } else {
        for (var i = 0; i <= count; i++) {
            var nextPart = setUpEmpty(count - i, start + 1, segmentCount);
            for (var j = 0; j < nextPart.length; j++) {
                var temp = [];
                temp[0] = i;
                temp = temp.concat(nextPart[j]);
                toReturn.push(temp);
            }
        }
    }
    return toReturn;
}

<|endoftext|>


<commit_before>const get = key => JSON.parse(localStorage.getItem(key))
const save = (key, data) => localStorage.setItem(key, JSON.stringify(data))

export default {
    get,
    save
}
<commit_msg>localStoage fallback<commit_after>/**
 * localStorage
 */
const __localStorage = window.localStorage

/**
 * 检测是否支持 localStorage
 */
const isSupported = () => {
    const key = '__EPM_TEST_SUPPORTED__'
    try {
        __localStorage.setItem(key, Date.now())
        __localStorage.removeItem(key)
        return true
    } catch (error) {
        return false
    }
}

/**
 * 封装 browserStorage
 */
const browserStorage = () => {
    const storage = __localStorage
    return {
        get: key => JSON.parse(storage.getItem(key)),
        save: (key, data) => storage.setItem(key, JSON.stringify(data))
    }
}

/**
 * ramStorage
 */
const ramStorage = () => {
    const storage = {}
    return {
        get: key => storage[key],
        save: (key, data) => {
            storage[key] = data
        },
    }
}

/**
 * 不支持 localStorage 时，回退 ramStorage
 */
const cacheStore = isSupported() ? browserStorage() : ramStorage()
export default cacheStore
<|endoftext|>


<commit_before>// member_peer.js
//  handling PeerConnections for MCU Members, with N to N
//
//  Browser MCU Server/Service

const useTrickleICE = true;
let _localStream = null;
//let _peerConnection = null;
let _Connections = [];

let _addRemoteVideoFunc = null;
let _removeRemoteVideoFunc = null;
let _disconnectFunc = null; // function to handle ice disconnect event
let _updateUIFunc = null; // update UI callback

  // --- outer objenct and functions ---
  function setLocalStream(stream) {
    _localStream = stream;
  }

  function getLocalStream() {
    return _localStream
  }

  //function setPeerConnection(peer) {
  //  _peerConnection = peer;
  //}

  //function getPeerConnection() {
  //  return _peerConnection;
  //}

  function setRemoteVideoFunc(addFunc, removeFunc) {
    _addRemoteVideoFunc = addFunc;
    _removeRemoteVideoFunc = removeFunc;
  }

  function setDisconnectFunc(func) {
    _disconnectFunc = func;
  }

  function setUpdateUIFunc(func) {
    _updateUIFunc = func;
  }

  // --- log state
  function _logState(text) {
    console.log(text);
  }

  function _logStream(msg, stream) {
    console.log(msg + ': id=' + stream.id);

    let videoTracks = stream.getVideoTracks();
    if (videoTracks) {
    console.log('videoTracks.length=' + videoTracks.length);
    videoTracks.forEach(function(track) {
      console.log(' track.id=' + track.id);
    });
    }

    let audioTracks = stream.getAudioTracks();
    if (audioTracks) {
    console.log('audioTracks.length=' + audioTracks.length);
    audioTracks.forEach(function(track) {
      console.log(' track.id=' + track.id);
    });
    }
  }

  // -- mcu connection management ---
  //
  //

  function getConnection(id) {
    let peer = _Connections[id];
    if (! peer) {
      console.log('Peer not exist for id:' + id);
    }
    return peer;
  }

  function isConnected(id) {
    const peer = _Connections[id];
    if (peer) {
      return true;
    }
    else {
      return false;
    }
  }

  function addConnection(id, peer) {
    if (isConnected(id)) {
      console.error('ALREADY CONNECTED to id:' + id);
      return;
    }

    _Connections[id] = peer;
  }

  function removeConnection(id) {
    if (! isConnected(id)) {
      console.warn('NOT CONNECTED to id:' + id);
      return;
    }

    let peer = _Connections[id];
    peer.close();
    peer = null;
    delete _Connections[id];
  }

  function getRemoteStream(id) {
    let peer = getConnection(id);
    if (peer) {
      let stream = peer.getRemoteStreams()[0];
      return stream;
    }
    else {
      console.warn('NOT CONNECTED to id:' + id);
      return null;
    }
  }

  function closeAllConnections() {
    for (let id in _Connections) {
      let peer = _Connections[id];
      peer.close();
      peer = null;
      delete _Connections[id];
    }
  }

  function getConnectionCount() {
    //let l1 = _Connections.length;
    //let l2 = Object.keys(_Connections).length;
    //console.log('getConnectionCount() l1=' + l1 + '  l2=' + l2 );
    return  Object.keys(_Connections).length;
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection(id) {
    let pc_config = _PeerConnectionConfig;
    let peer = new RTCPeerConnection(pc_config);
    // --- on get remote stream ---
    if ('onaddstream' in peer) {
      peer.onaddstream = function(event) {
        console.log('-- peer.onaddstream()');
        let stream = event.stream;
        _logStream('remotestream of onaddstream()', stream);

        _addRemoteVideoFunc(stream.id, stream);
      };
    }
    else if ('ontrack' in peer) {
      peer.ontrack = function(event) {
        console.log('-- peer.ontrack()');
        let stream = event.streams[0];
        _logStream('remotestream of ontrack()', stream);
        if ( (stream.getVideoTracks().length > 0) && (stream.getAudioTracks().length > 0) ) {
          _addRemoteVideoFunc(stream.id, stream);
        }

      };
    }
    else {
      console.error('NOT remoteStream handler');
    }
    // --- on get local ICE candidate
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);
        if (useTrickleICE) {
          // Trickle ICE の場合は、ICE candidateを相手に送る
          // send ICE candidate when using Trickle ICE
          sendIceCandidate(id, evt.candidate);
        }
        else {
          // Vanilla ICE の場合には、何もしない
          // do NOTHING for Vanilla ICE
        }
      } else {
        console.log('empty ice event');
        if (useTrickleICE) {
          // Trickle ICE の場合は、何もしない
          // do NOTHING for Trickle ICE
        }
        else {
          // Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る
          // send SDP with ICE candidtes when using Vanilla ICE
          sendSdp(id, peer.localDescription);
        }
      }
    };
    // --- when need to exchange SDP ---
    peer.onnegotiationneeded = function(evt) {
      console.log('-- onnegotiationneeded() ---');
      console.warn('--- IGNORE ---');
    };
    // --- other events ----
    peer.onicecandidateerror = function (evt) {
      console.error('ICE candidate ERROR:', evt);
    };
    peer.onsignalingstatechange = function() {
      console.log('== signaling state=' + peer.signalingState);
    };
    peer.oniceconnectionstatechange = function() {
      console.log('== ice connection state=' + peer.iceConnectionState);
      //showState('ice connection state=' + peer.iceConnectionState);
      _logState('ice connection state=' + peer.iceConnectionState);
      if (peer.iceConnectionState === 'disconnected') {
        console.log('-- disconnected, but wait for re-connect --');
      }
      else if (peer.iceConnectionState === 'failed') {
        console.log('-- failed, so give up --');
        if (_disconnectFunc) {
          _disconnectFunc(id);
        }
      }
    };
    peer.onicegatheringstatechange = function() {
      console.log('==***== ice gathering state=' + peer.iceGatheringState);
    };

    peer.onconnectionstatechange = function() {
      console.log('==***== connection state=' + peer.connectionState);
    };
    peer.onremovestream = function(event) {
      console.log('-- peer.onremovestream()');
      let stream = event.stream;
      _removeRemoteVideoFunc(stream.id, stream);
    };

    // -- add local stream --
    let localStream = getLocalStream();
    if (localStream) {
      console.log('Adding local stream...');
      if ('addStream' in peer) {
        console.log('use addStream()');
        peer.addStream(localStream);
      }
      else if ('addTrack' in peer) {
        console.log('use addTrack()');
        let tracks = localStream.getTracks();
        for (let track of tracks) {
          let sender = peer.addTrack(track, localStream);
        }
      }
      else {
        console.error('NO method to add localStream');
      }
    }
    else {
      console.warn('no local stream, but continue.');
    }
    return peer;
  }

  function setOffer(id, sessionDescription) {
    let peerConnection = getConnection(id);

    if (peerConnection) {
      console.log('peerConnection alreay exist, reuse it');
    }
    else {
      console.log('prepare new PeerConnection');
      peerConnection = prepareNewConnection(id);
      //setPeerConnection(peerConnection);
      addConnection(id, peerConnection);
    }
    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(offer) succsess in promise');
      makeAnswer(id);
      if (_updateUIFunc) {
        _updateUIFunc();
      }
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }

  function setAnswer(id, sessionDescription) {
    let peerConnection = getConnection(id);

    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(offer) succsess in promise');
      if (_updateUIFunc) {
        _updateUIFunc();
      }
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }

  function makeOffer(id) {
    console.log('sending Offer. Creating session description...' );
    let peerConnection = getConnection(id);

    if (peerConnection) {
      console.log('peerConnection alreay exist, reuse it');
    }
    else {
      console.log('prepare new PeerConnection');
      peerConnection = prepareNewConnection(id);
      //setPeerConnection(peerConnection);
      addConnection(id, peerConnection);
    }

    peerConnection.createOffer()
    .then(function (sessionDescription) {
      console.log('createOffer() succsess in promise');

      // -- limit bandwidth --
      const audioBand = 64; // kbps
      const videoBand = 512; // kbps
      let sdpLimit = setBandwidth(sessionDescription.sdp, audioBand, videoBand);
      sessionDescription.sdp = sdpLimit;

      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');
      if (useTrickleICE) {
        // -- Trickle ICE の場合は、初期SDPを相手に送る --
        // send initial SDP when using Trickle ICE
        sendSdp(id, peerConnection.localDescription);
      }
      else {
        // -- Vanilla ICE の場合には、まだSDPは送らない --
        // wait for ICE candidates for Vanilla ICE
        //sendSdp(id, peerConnection.localDescription);
      }
    }).catch(function(err) {
      console.error(err);
    });
  }

  function makeAnswer(id) {
    console.log('sending Answer. Creating session description...' );
    let peerConnection = getConnection(id);
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    peerConnection.createAnswer()
    .then(function (sessionDescription) {
      console.log('createAnswer() succsess in promise');

      // -- limit bandwidth --
      const audioBand = 64; // kbps
      const videoBand = 512; // kbps
      let sdpLimit = setBandwidth(sessionDescription.sdp, audioBand, videoBand);
      sessionDescription.sdp = sdpLimit;

      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');
      if (useTrickleICE) {
        // -- Trickle ICE の場合は、初期SDPを相手に送る --
        // send initial SDP when using Trickle ICE
        sendSdp(id, peerConnection.localDescription);
      }
      else {
        // -- Vanilla ICE の場合には、まだSDPは送らない --
        // wait for ICE candidates for Vanilla ICE
        //sendSdp(id, peerConnection.localDescription);
      }
    }).catch(function(err) {
      console.error(err);
    });
  }

  function addIceCandidate(id, candidate) {
    let peerConnection = getConnection(id);

    if (peerConnection) {
      peerConnection.addIceCandidate(candidate);
    }
    else {
      console.error('PeerConnection not exist!');
      return;
    }
  }

  // ----- band width -----
  // for chrome
  //audioBandwidth = 50; // kbps
  //videoBandwidth = 256; // kbps
  function setBandwidth(sdp, audioBandwidth, videoBandwidth) {
    let sdpNew = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n');
    sdpNew = sdpNew.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n');

    //// -- trial for firefox, but not work between chrome - firefox --
    //let sdpNew = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n' + 'b=TIAS:' + audioBandwidth*1000 + '\r\n');
    //sdpNew = sdpNew.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n' + 'b=TIAS:' + videoBandwidth*1000 + '\r\n');
    //// -- trial for firefox, but not work between chrome - firefox --

    return sdpNew;
  }

  // ---- send signaling info ----
  let _sendJsonFunc = null;

  function setSendJsonFunc(func) {
    _sendJsonFunc = func;
  }

  function sendSdp(id, sessionDescription) {
    console.log('---sending sdp ---');
    const jsonSDP = sessionDescription.toJSON();
    console.log('sending to:' + id + '  SDP:', jsonSDP);

    //sendJson(jsonSDP);
    _sendJsonFunc(id, jsonSDP);
  }

  function sendIceCandidate(id, candidate) {
    console.log('---sending ICE candidate ---');
    const obj = { type: 'candidate', ice: candidate };
    //sendJson(obj);
    _sendJsonFunc(id, obj);
  }
<commit_msg>support one way<commit_after>// member_peer.js
//  handling PeerConnections for MCU Members, with N to N
//
//  Browser MCU Server/Service

const useTrickleICE = true;
let _localStream = null;
//let _peerConnection = null;
let _Connections = [];

let _addRemoteVideoFunc = null;
let _removeRemoteVideoFunc = null;
let _disconnectFunc = null; // function to handle ice disconnect event
let _updateUIFunc = null; // update UI callback

  // --- outer objenct and functions ---
  function setLocalStream(stream) {
    _localStream = stream;
  }

  function getLocalStream() {
    return _localStream
  }

  //function setPeerConnection(peer) {
  //  _peerConnection = peer;
  //}

  //function getPeerConnection() {
  //  return _peerConnection;
  //}

  function setRemoteVideoFunc(addFunc, removeFunc) {
    _addRemoteVideoFunc = addFunc;
    _removeRemoteVideoFunc = removeFunc;
  }

  function setDisconnectFunc(func) {
    _disconnectFunc = func;
  }

  function setUpdateUIFunc(func) {
    _updateUIFunc = func;
  }

  // --- log state
  function _logState(text) {
    console.log(text);
  }

  function _logStream(msg, stream) {
    console.log(msg + ': id=' + stream.id);

    let videoTracks = stream.getVideoTracks();
    if (videoTracks) {
    console.log('videoTracks.length=' + videoTracks.length);
    videoTracks.forEach(function(track) {
      console.log(' track.id=' + track.id);
    });
    }

    let audioTracks = stream.getAudioTracks();
    if (audioTracks) {
    console.log('audioTracks.length=' + audioTracks.length);
    audioTracks.forEach(function(track) {
      console.log(' track.id=' + track.id);
    });
    }
  }

  // -- mcu connection management ---
  //
  //

  function getConnection(id) {
    let peer = _Connections[id];
    if (! peer) {
      console.log('Peer not exist for id:' + id);
    }
    return peer;
  }

  function isConnected(id) {
    const peer = _Connections[id];
    if (peer) {
      return true;
    }
    else {
      return false;
    }
  }

  function addConnection(id, peer) {
    if (isConnected(id)) {
      console.error('ALREADY CONNECTED to id:' + id);
      return;
    }

    _Connections[id] = peer;
  }

  function removeConnection(id) {
    if (! isConnected(id)) {
      console.warn('NOT CONNECTED to id:' + id);
      return;
    }

    let peer = _Connections[id];
    peer.close();
    peer = null;
    delete _Connections[id];
  }

  function getRemoteStream(id) {
    let peer = getConnection(id);
    if (peer) {
      let stream = peer.getRemoteStreams()[0];
      return stream;
    }
    else {
      console.warn('NOT CONNECTED to id:' + id);
      return null;
    }
  }

  function closeAllConnections() {
    for (let id in _Connections) {
      let peer = _Connections[id];
      peer.close();
      peer = null;
      delete _Connections[id];
    }
  }

  function getConnectionCount() {
    //let l1 = _Connections.length;
    //let l2 = Object.keys(_Connections).length;
    //console.log('getConnectionCount() l1=' + l1 + '  l2=' + l2 );
    return  Object.keys(_Connections).length;
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection(id) {
    let pc_config = _PeerConnectionConfig;
    let peer = new RTCPeerConnection(pc_config);
    // --- on get remote stream ---
    if ('onaddstream' in peer) {
      peer.onaddstream = function(event) {
        console.log('-- peer.onaddstream()');
        let stream = event.stream;
        _logStream('remotestream of onaddstream()', stream);

        if (_addRemoteVideoFunc) {
          _addRemoteVideoFunc(stream.id, stream);
        }
        else {
          console.warn('NO Func to handle onaddstream()');
        }
      };
    }
    else if ('ontrack' in peer) {
      peer.ontrack = function(event) {
        console.log('-- peer.ontrack()');
        let stream = event.streams[0];
        _logStream('remotestream of ontrack()', stream);
        if ( (stream.getVideoTracks().length > 0) && (stream.getAudioTracks().length > 0) ) {
          if (_addRemoteVideoFunc) {
            _addRemoteVideoFunc(stream.id, stream);
          }
          else {
            console.warn('NO Func to handle ontrack()');
          }
        }
      };
    }
    else {
      console.error('NOT remoteStream handler');
    }
    // --- on get local ICE candidate
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);
        if (useTrickleICE) {
          // Trickle ICE の場合は、ICE candidateを相手に送る
          // send ICE candidate when using Trickle ICE
          sendIceCandidate(id, evt.candidate);
        }
        else {
          // Vanilla ICE の場合には、何もしない
          // do NOTHING for Vanilla ICE
        }
      } else {
        console.log('empty ice event');
        if (useTrickleICE) {
          // Trickle ICE の場合は、何もしない
          // do NOTHING for Trickle ICE
        }
        else {
          // Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る
          // send SDP with ICE candidtes when using Vanilla ICE
          sendSdp(id, peer.localDescription);
        }
      }
    };
    // --- when need to exchange SDP ---
    peer.onnegotiationneeded = function(evt) {
      console.log('-- onnegotiationneeded() ---');
      console.warn('--- IGNORE ---');
    };
    // --- other events ----
    peer.onicecandidateerror = function (evt) {
      console.error('ICE candidate ERROR:', evt);
    };
    peer.onsignalingstatechange = function() {
      console.log('== signaling state=' + peer.signalingState);
    };
    peer.oniceconnectionstatechange = function() {
      console.log('== ice connection state=' + peer.iceConnectionState);
      //showState('ice connection state=' + peer.iceConnectionState);
      _logState('ice connection state=' + peer.iceConnectionState);
      if (peer.iceConnectionState === 'disconnected') {
        console.log('-- disconnected, but wait for re-connect --');
      }
      else if (peer.iceConnectionState === 'failed') {
        console.log('-- failed, so give up --');
        if (_disconnectFunc) {
          _disconnectFunc(id);
        }
      }
    };
    peer.onicegatheringstatechange = function() {
      console.log('==***== ice gathering state=' + peer.iceGatheringState);
    };

    peer.onconnectionstatechange = function() {
      console.log('==***== connection state=' + peer.connectionState);
    };
    peer.onremovestream = function(event) {
      console.log('-- peer.onremovestream()');
      let stream = event.stream;
      if (_removeRemoteVideoFunc) {
        _removeRemoteVideoFunc(stream.id, stream);
      }
      else {
        console.warn('NO Func to handle onremovestream()');
      }
    };

    // -- add local stream --
    let localStream = getLocalStream();
    if (localStream) {
      console.log('Adding local stream...');
      if ('addStream' in peer) {
        console.log('use addStream()');
        peer.addStream(localStream);
      }
      else if ('addTrack' in peer) {
        console.log('use addTrack()');
        let tracks = localStream.getTracks();
        for (let track of tracks) {
          let sender = peer.addTrack(track, localStream);
        }
      }
      else {
        console.error('NO method to add localStream');
      }
    }
    else {
      console.warn('no local stream, but continue.');
    }
    return peer;
  }

  function setOffer(id, sessionDescription) {
    let peerConnection = getConnection(id);

    if (peerConnection) {
      console.log('peerConnection alreay exist, reuse it');
    }
    else {
      console.log('prepare new PeerConnection');
      peerConnection = prepareNewConnection(id);
      //setPeerConnection(peerConnection);
      addConnection(id, peerConnection);
    }
    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(offer) succsess in promise');
      makeAnswer(id);
      if (_updateUIFunc) {
        _updateUIFunc();
      }
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }

  function setAnswer(id, sessionDescription) {
    let peerConnection = getConnection(id);

    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(offer) succsess in promise');
      if (_updateUIFunc) {
        _updateUIFunc();
      }
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }

  function makeOffer(id) {
    console.log('sending Offer. Creating session description...' );
    let peerConnection = getConnection(id);

    if (peerConnection) {
      console.log('peerConnection alreay exist, reuse it');
    }
    else {
      console.log('prepare new PeerConnection');
      peerConnection = prepareNewConnection(id);
      //setPeerConnection(peerConnection);
      addConnection(id, peerConnection);
    }

    // for receive only,  send only too
    let options = {};
    if (isSendOnly()) {
      options = { offerToReceiveAudio: false, offerToReceiveVideo: false };
    }
    else if (isRecvOnly()) {
      options = { offerToReceiveAudio: true, offerToReceiveVideo: true };

      // -- for safari --
      if ('addTransceiver' in peerConnection) {
        console.log('-- use addTransceiver() for recvonly --');
        peerConnection.addTransceiver('video').setDirection('recvonly');
        peerConnection.addTransceiver('audio').setDirection('recvonly');
      }
    }

    peerConnection.createOffer(options)
    .then(function (sessionDescription) {
      console.log('createOffer() succsess in promise');

      // -- limit bandwidth --
      const audioBand = 64; // kbps
      const videoBand = 512; // kbps
      let sdpLimit = setBandwidth(sessionDescription.sdp, audioBand, videoBand);
      sessionDescription.sdp = sdpLimit;

      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');
      if (useTrickleICE) {
        // -- Trickle ICE の場合は、初期SDPを相手に送る --
        // send initial SDP when using Trickle ICE
        sendSdp(id, peerConnection.localDescription);
      }
      else {
        // -- Vanilla ICE の場合には、まだSDPは送らない --
        // wait for ICE candidates for Vanilla ICE
        //sendSdp(id, peerConnection.localDescription);
      }
    }).catch(function(err) {
      console.error(err);
    });
  }

  function makeAnswer(id) {
    console.log('sending Answer. Creating session description...' );
    let peerConnection = getConnection(id);
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    let options = {};
    if (! isRecvOnly()) {
      //options = { offerToReceiveAudio: true, offerToReceiveVideo: true }

      if ('addTransceiver' in peerConnection) {
        console.log('-- use addTransceiver() for recvonly --');
        peerConnection.addTransceiver('video').setDirection('recvonly');
        peerConnection.addTransceiver('audio').setDirection('recvonly');
      }
    }

    peerConnection.createAnswer()
    .then(function (sessionDescription) {
      console.log('createAnswer() succsess in promise');

      // -- limit bandwidth --
      const audioBand = 64; // kbps
      const videoBand = 512; // kbps
      let sdpLimit = setBandwidth(sessionDescription.sdp, audioBand, videoBand);
      sessionDescription.sdp = sdpLimit;

      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');
      if (useTrickleICE) {
        // -- Trickle ICE の場合は、初期SDPを相手に送る --
        // send initial SDP when using Trickle ICE
        sendSdp(id, peerConnection.localDescription);
      }
      else {
        // -- Vanilla ICE の場合には、まだSDPは送らない --
        // wait for ICE candidates for Vanilla ICE
        //sendSdp(id, peerConnection.localDescription);
      }
    }).catch(function(err) {
      console.error(err);
    });
  }

  function addIceCandidate(id, candidate) {
    let peerConnection = getConnection(id);

    if (peerConnection) {
      peerConnection.addIceCandidate(candidate);
    }
    else {
      console.error('PeerConnection not exist!');
      return;
    }
  }

  function isSendOnly() {
    if (! _addRemoteVideoFunc) {
      return true;
    }
    else {
      return false;
    }
  }

  function isRecvOnly() {
    if (! getLocalStream()) {
      return true;
    }
    else {
      return false;
    }
  }

  // ----- band width -----
  // for chrome
  //audioBandwidth = 50; // kbps
  //videoBandwidth = 256; // kbps
  function setBandwidth(sdp, audioBandwidth, videoBandwidth) {
    let sdpNew = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n');
    sdpNew = sdpNew.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n');

    //// -- trial for firefox, but not work between chrome - firefox --
    //let sdpNew = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n' + 'b=TIAS:' + audioBandwidth*1000 + '\r\n');
    //sdpNew = sdpNew.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n' + 'b=TIAS:' + videoBandwidth*1000 + '\r\n');
    //// -- trial for firefox, but not work between chrome - firefox --

    return sdpNew;
  }

  // ---- send signaling info ----
  let _sendJsonFunc = null;

  function setSendJsonFunc(func) {
    _sendJsonFunc = func;
  }

  function sendSdp(id, sessionDescription) {
    console.log('---sending sdp ---');
    const jsonSDP = sessionDescription.toJSON();
    console.log('sending to:' + id + '  SDP:', jsonSDP);

    //sendJson(jsonSDP);
    _sendJsonFunc(id, jsonSDP);
  }

  function sendIceCandidate(id, candidate) {
    console.log('---sending ICE candidate ---');
    const obj = { type: 'candidate', ice: candidate };
    //sendJson(obj);
    _sendJsonFunc(id, obj);
  }
<|endoftext|>


<commit_before>import { capitalize, isArray, isString, isFunction } from 'lodash'
import { supportedAuthProviders, actionTypes } from '../constants'

/**
 * @description Get correct login method and params order based on provided credentials
 * @param {Object} firebase - Internal firebase object
 * @param {String} providerName - Name of Auth Provider (i.e. google, github, facebook, twitter)
 * @param {Array|String} scopes - List of scopes to add to auth provider
 * @return {firebase.auth.AuthCredential} provider - Auth Provider
 * @private
 */
const createAuthProvider = (firebase, providerName, scopes) => {
  // TODO: Verify scopes are valid before adding
  // TODO: Validate parameter inputs
  const capitalProviderName = `${capitalize(providerName)}AuthProvider`
  const provider = new firebase.auth[capitalProviderName]()

  // Custom Auth Parameters
  const { customAuthParameters } = firebase._.config
  if (customAuthParameters && customAuthParameters[providerName]) {
    provider.setCustomParameters(customAuthParameters[providerName])
  }

  // Handle providers without scopes
  if (
    providerName.toLowerCase() === 'twitter' ||
    !isFunction(provider.addScope)
  ) {
    return provider
  }

  provider.addScope('email')

  if (scopes) {
    if (isArray(scopes)) {
      scopes.forEach(scope => {
        provider.addScope(scope)
      })
    }
    if (isString(scopes)) {
      provider.addScope(scopes)
    }
  }

  return provider
}

/**
 * @description Get correct login method and params order based on provided
 * credentials
 * @param {Object} firebase - Internal firebase object
 * @param {Object} credentials - Login credentials
 * @param {String} credentials.email - Email to login with (only needed for
 * email login)
 * @param {String} credentials.password - Password to login with (only needed
 * for email login)
 * @param {String} credentials.provider - Provider name such as google, twitter
 * (only needed for 3rd party provider login)
 * @param {String} credentials.type - Popup or redirect (only needed for 3rd
 * party provider login)
 * @param {String} credentials.token - Custom or provider token
 * @param {firebase.auth.AuthCredential} credentials.credential - Custom or
 * provider token
 * @param {Array|String} credentials.scopes - Scopes to add to provider
 * (i.e. email)
 * @private
 */
export const getLoginMethodAndParams = (firebase, creds) => {
  const {
    email,
    password,
    provider,
    type,
    token,
    scopes,
    phoneNumber,
    applicationVerifier,
    credential
  } = creds
  if (credential) {
    const credentialAuth = firebase.auth().signInAndRetrieveDataWithCredential

    if (credentialAuth) {
      return {
        method: 'signInAndRetrieveDataWithCredential',
        params: [credential]
      }
    }
    return { method: 'signInWithCredential', params: [credential] }
  }
  if (provider) {
    // Verify providerName is valid
    if (supportedAuthProviders.indexOf(provider.toLowerCase()) === -1) {
      throw new Error(`${provider} is not a valid Auth Provider`)
    }
    if (token) {
      throw new Error(
        'provider with token no longer supported, use credential parameter instead'
      )
    }
    const authProvider = createAuthProvider(firebase, provider, scopes)
    if (type === 'popup') {
      return { method: 'signInWithPopup', params: [authProvider] }
    }
    return { method: 'signInWithRedirect', params: [authProvider] }
  }
  if (token) {
    const tokenAuth = firebase.auth().signInAndRetrieveDataWithCustomToken

    if (tokenAuth) {
      return { method: 'signInAndRetrieveDataWithCustomToken', params: [token] }
    }

    return { method: 'signInWithCustomToken', params: [token] }
  }
  if (phoneNumber) {
    if (!applicationVerifier) {
      throw new Error(
        'Application verifier is required for phone authentication'
      )
    }
    return {
      method: 'signInWithPhoneNumber',
      params: [phoneNumber, applicationVerifier]
    }
  }

  const emailPasswordAuth = firebase.auth()
    .signInAndRetrieveDataWithEmailAndPassword

  if (emailPasswordAuth) {
    return {
      method: 'signInAndRetrieveDataWithEmailAndPassword',
      params: [email, password]
    }
  }

  return { method: 'signInWithEmailAndPassword', params: [email, password] }
}

/**
 * Returns a promise that completes when Firebase Auth is ready in the given
 * store using react-redux-firebase.
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {string} [stateName='firebase'] - The attribute name of the
 * react-redux-firebase reducer when using multiple combined reducers.
 * 'firebase' by default. Set this to `null` to indicate that the
 * react-redux-firebase reducer is not in a combined reducer.
 * @return {Promise} Resolves when Firebase auth is ready in the store.
 */
const isAuthReady = (store, stateName) => {
  const state = store.getState()
  const firebaseState = stateName ? state[stateName] : state
  const firebaseAuthState = firebaseState && firebaseState.auth
  if (!firebaseAuthState) {
    throw new Error(
      `The Firebase auth state could not be found in the store under the attribute '${
        stateName ? `${stateName}.` : ''
      }auth'. Make sure your react-redux-firebase reducer is correctly set in the store`
    )
  }
  return firebaseState.auth.isLoaded
}

/**
 * Returns a promise that completes when Firebase Auth is ready in the given
 * store using react-redux-firebase.
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {string} [stateName='firebase'] - The attribute name of the react-redux-firebase
 * reducer when using multiple combined reducers. 'firebase' by default. Set
 * this to `null` to indicate that the react-redux-firebase reducer is not in a
 * combined reducer.
 * @return {Promise} Resolve when Firebase auth is ready in the store.
 */
export const authIsReady = (store, stateName = 'firebase') =>
  new Promise(resolve => {
    if (isAuthReady(store, stateName)) {
      resolve()
    } else {
      const unsubscribe = store.subscribe(() => {
        if (isAuthReady(store, stateName)) {
          unsubscribe()
          resolve()
        }
      })
    }
  })

/**
 * Function that creates and authIsReady promise
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {Object} config - Config options for authIsReady
 * @param {string} config.authIsReady - Config options for authIsReady
 * @param {string} config.firebaseStateName - Config options for authIsReady
 * @return {Promise} Resolves when Firebase auth is ready in the store.
 */
export const createAuthIsReady = (store, config) => {
  return isFunction(config.authIsReady)
    ? config.authIsReady(store, config)
    : authIsReady(store, config.firebaseStateName)
}

/**
 * Update profile data on Firebase Real Time Database
 * @param  {Object} firebase - internal firebase object
 * @param  {Object} profileUpdate - Updates to profile object
 * @return {Promise} Resolves with results of profile get
 */
export const updateProfileOnRTDB = (firebase, profileUpdate) => {
  const { database, _: { config, authUid } } = firebase
  const profileRef = database().ref(`${config.userProfile}/${authUid}`)
  return profileRef.update(profileUpdate).then(() => profileRef.once('value'))
}

/**
 * Update profile data on Firestore by calling set (with merge: true) on
 * the profile.
 * @param  {Object} firebase - internal firebase object
 * @param  {Object} profileUpdate - Updates to profile object
 * @param  {Object} options - Options object for configuring how profile
 * update occurs
 * @param  {Boolean} [options.useSet=true] - Use set with merge instead of
 * update. Setting to `false` uses update (can cause issue of profile document
 * does not exist).
 * @param  {Boolean} [options.merge=true] - Whether or not to use merge when
 * setting profile
 * @return {Promise} Resolves with results of profile get
 */
export const updateProfileOnFirestore = (
  firebase,
  profileUpdate,
  options = {}
) => {
  const { useSet = true, merge = true } = options
  const { firestore, _: { config, authUid } } = firebase
  const profileRef = firestore().doc(`${config.userProfile}/${authUid}`)
  // Use set with merge (to prevent "No document to update") unless otherwise
  // specificed through options
  const profileUpdatePromise = useSet
    ? profileRef.set(profileUpdate, { merge })
    : profileRef.update(profileUpdate)
  return profileUpdatePromise.then(() => profileRef.get())
}

/**
 * @description Start presence management for a specificed user uid.
 * Presence collection contains a list of users that are online currently.
 * Sessions collection contains a record of all user sessions.
 * This function is called within login functions if enablePresence: true.
 * @param {Function} dispatch - Action dispatch function
 * @param {Object} firebase - Internal firebase object
 * @return {Promise}
 * @private
 */
export function setupPresence(dispatch, firebase) {
  // exit if database does not exist on firebase instance
  if (!firebase.database || !firebase.database.ServerValue) {
    return
  }
  const ref = firebase.database().ref()
  const { config: { presence, sessions }, authUid } = firebase._
  const amOnline = ref.child('.info/connected')
  const onlineRef = ref
    .child(
      isFunction(presence)
        ? presence(firebase.auth().currentUser, firebase)
        : presence
    )
    .child(authUid)
  let sessionsRef = isFunction(sessions)
    ? sessions(firebase.auth().currentUser, firebase)
    : sessions
  if (sessionsRef) {
    sessionsRef = ref.child(sessions)
  }
  amOnline.on('value', snapShot => {
    if (!snapShot.val()) return
    // user is online
    if (sessionsRef) {
      // add session and set disconnect
      dispatch({ type: actionTypes.SESSION_START, payload: authUid })
      // add new session to sessions collection
      const session = sessionsRef.push({
        startedAt: firebase.database.ServerValue.TIMESTAMP,
        user: authUid
      })
      // Support versions of react-native-firebase that do not have setPriority
      // on firebase.database.ThenableReference
      if (isFunction(session.setPriority)) {
        // set authUid as priority for easy sorting
        session.setPriority(authUid)
      }
      session
        .child('endedAt')
        .onDisconnect()
        .set(firebase.database.ServerValue.TIMESTAMP, () => {
          dispatch({ type: actionTypes.SESSION_END })
        })
    }
    // add correct session id to user
    // remove from presence list
    onlineRef.set(true)
    onlineRef.onDisconnect().remove()
  })
}
<commit_msg>Added comments pointing to issue numbers in getLoginMethodAndParams<commit_after>import { capitalize, isArray, isString, isFunction } from 'lodash'
import { supportedAuthProviders, actionTypes } from '../constants'

/**
 * @description Get correct login method and params order based on provided credentials
 * @param {Object} firebase - Internal firebase object
 * @param {String} providerName - Name of Auth Provider (i.e. google, github, facebook, twitter)
 * @param {Array|String} scopes - List of scopes to add to auth provider
 * @return {firebase.auth.AuthCredential} provider - Auth Provider
 * @private
 */
const createAuthProvider = (firebase, providerName, scopes) => {
  // TODO: Verify scopes are valid before adding
  // TODO: Validate parameter inputs
  const capitalProviderName = `${capitalize(providerName)}AuthProvider`
  const provider = new firebase.auth[capitalProviderName]()

  // Custom Auth Parameters
  const { customAuthParameters } = firebase._.config
  if (customAuthParameters && customAuthParameters[providerName]) {
    provider.setCustomParameters(customAuthParameters[providerName])
  }

  // Handle providers without scopes
  if (
    providerName.toLowerCase() === 'twitter' ||
    !isFunction(provider.addScope)
  ) {
    return provider
  }

  provider.addScope('email')

  if (scopes) {
    if (isArray(scopes)) {
      scopes.forEach(scope => {
        provider.addScope(scope)
      })
    }
    if (isString(scopes)) {
      provider.addScope(scopes)
    }
  }

  return provider
}

/**
 * @description Get correct login method and params order based on provided
 * credentials
 * @param {Object} firebase - Internal firebase object
 * @param {Object} credentials - Login credentials
 * @param {String} credentials.email - Email to login with (only needed for
 * email login)
 * @param {String} credentials.password - Password to login with (only needed
 * for email login)
 * @param {String} credentials.provider - Provider name such as google, twitter
 * (only needed for 3rd party provider login)
 * @param {String} credentials.type - Popup or redirect (only needed for 3rd
 * party provider login)
 * @param {String} credentials.token - Custom or provider token
 * @param {firebase.auth.AuthCredential} credentials.credential - Custom or
 * provider token
 * @param {Array|String} credentials.scopes - Scopes to add to provider
 * (i.e. email)
 * @private
 */
export const getLoginMethodAndParams = (firebase, creds) => {
  const {
    email,
    password,
    provider,
    type,
    token,
    scopes,
    phoneNumber,
    applicationVerifier,
    credential
  } = creds
  // Credential Auth
  if (credential) {
    // Attempt to use signInAndRetrieveDataWithCredential if it exists (see #467 for more info)
    const credentialAuth = firebase.auth().signInAndRetrieveDataWithCredential

    if (credentialAuth) {
      return {
        method: 'signInAndRetrieveDataWithCredential',
        params: [credential]
      }
    }
    return { method: 'signInWithCredential', params: [credential] }
  }

  // Provider Auth
  if (provider) {
    // Verify providerName is valid
    if (supportedAuthProviders.indexOf(provider.toLowerCase()) === -1) {
      throw new Error(`${provider} is not a valid Auth Provider`)
    }
    if (token) {
      throw new Error(
        'provider with token no longer supported, use credential parameter instead'
      )
    }
    const authProvider = createAuthProvider(firebase, provider, scopes)
    if (type === 'popup') {
      return { method: 'signInWithPopup', params: [authProvider] }
    }
    return { method: 'signInWithRedirect', params: [authProvider] }
  }

  // Token Auth
  if (token) {
    // Check for new sign in method (see #484 for more info)
    const tokenAuth = firebase.auth().signInAndRetrieveDataWithCustomToken

    if (tokenAuth) {
      return { method: 'signInAndRetrieveDataWithCustomToken', params: [token] }
    }

    return { method: 'signInWithCustomToken', params: [token] }
  }

  // Phone Number Auth
  if (phoneNumber) {
    if (!applicationVerifier) {
      throw new Error(
        'Application verifier is required for phone authentication'
      )
    }
    return {
      method: 'signInWithPhoneNumber',
      params: [phoneNumber, applicationVerifier]
    }
  }

  // Email/Password Auth
  // Check for new sign in method (see #484 for more info)
  const emailPasswordAuth = firebase.auth()
    .signInAndRetrieveDataWithEmailAndPassword
  if (emailPasswordAuth) {
    return {
      method: 'signInAndRetrieveDataWithEmailAndPassword',
      params: [email, password]
    }
  }

  return { method: 'signInWithEmailAndPassword', params: [email, password] }
}

/**
 * Returns a promise that completes when Firebase Auth is ready in the given
 * store using react-redux-firebase.
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {string} [stateName='firebase'] - The attribute name of the
 * react-redux-firebase reducer when using multiple combined reducers.
 * 'firebase' by default. Set this to `null` to indicate that the
 * react-redux-firebase reducer is not in a combined reducer.
 * @return {Promise} Resolves when Firebase auth is ready in the store.
 */
const isAuthReady = (store, stateName) => {
  const state = store.getState()
  const firebaseState = stateName ? state[stateName] : state
  const firebaseAuthState = firebaseState && firebaseState.auth
  if (!firebaseAuthState) {
    throw new Error(
      `The Firebase auth state could not be found in the store under the attribute '${
        stateName ? `${stateName}.` : ''
      }auth'. Make sure your react-redux-firebase reducer is correctly set in the store`
    )
  }
  return firebaseState.auth.isLoaded
}

/**
 * Returns a promise that completes when Firebase Auth is ready in the given
 * store using react-redux-firebase.
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {string} [stateName='firebase'] - The attribute name of the react-redux-firebase
 * reducer when using multiple combined reducers. 'firebase' by default. Set
 * this to `null` to indicate that the react-redux-firebase reducer is not in a
 * combined reducer.
 * @return {Promise} Resolve when Firebase auth is ready in the store.
 */
export const authIsReady = (store, stateName = 'firebase') =>
  new Promise(resolve => {
    if (isAuthReady(store, stateName)) {
      resolve()
    } else {
      const unsubscribe = store.subscribe(() => {
        if (isAuthReady(store, stateName)) {
          unsubscribe()
          resolve()
        }
      })
    }
  })

/**
 * Function that creates and authIsReady promise
 * @param {Object} store - The Redux store on which we want to detect if
 * Firebase auth is ready.
 * @param {Object} config - Config options for authIsReady
 * @param {string} config.authIsReady - Config options for authIsReady
 * @param {string} config.firebaseStateName - Config options for authIsReady
 * @return {Promise} Resolves when Firebase auth is ready in the store.
 */
export const createAuthIsReady = (store, config) => {
  return isFunction(config.authIsReady)
    ? config.authIsReady(store, config)
    : authIsReady(store, config.firebaseStateName)
}

/**
 * Update profile data on Firebase Real Time Database
 * @param  {Object} firebase - internal firebase object
 * @param  {Object} profileUpdate - Updates to profile object
 * @return {Promise} Resolves with results of profile get
 */
export const updateProfileOnRTDB = (firebase, profileUpdate) => {
  const { database, _: { config, authUid } } = firebase
  const profileRef = database().ref(`${config.userProfile}/${authUid}`)
  return profileRef.update(profileUpdate).then(() => profileRef.once('value'))
}

/**
 * Update profile data on Firestore by calling set (with merge: true) on
 * the profile.
 * @param  {Object} firebase - internal firebase object
 * @param  {Object} profileUpdate - Updates to profile object
 * @param  {Object} options - Options object for configuring how profile
 * update occurs
 * @param  {Boolean} [options.useSet=true] - Use set with merge instead of
 * update. Setting to `false` uses update (can cause issue of profile document
 * does not exist).
 * @param  {Boolean} [options.merge=true] - Whether or not to use merge when
 * setting profile
 * @return {Promise} Resolves with results of profile get
 */
export const updateProfileOnFirestore = (
  firebase,
  profileUpdate,
  options = {}
) => {
  const { useSet = true, merge = true } = options
  const { firestore, _: { config, authUid } } = firebase
  const profileRef = firestore().doc(`${config.userProfile}/${authUid}`)
  // Use set with merge (to prevent "No document to update") unless otherwise
  // specificed through options
  const profileUpdatePromise = useSet
    ? profileRef.set(profileUpdate, { merge })
    : profileRef.update(profileUpdate)
  return profileUpdatePromise.then(() => profileRef.get())
}

/**
 * @description Start presence management for a specificed user uid.
 * Presence collection contains a list of users that are online currently.
 * Sessions collection contains a record of all user sessions.
 * This function is called within login functions if enablePresence: true.
 * @param {Function} dispatch - Action dispatch function
 * @param {Object} firebase - Internal firebase object
 * @return {Promise}
 * @private
 */
export function setupPresence(dispatch, firebase) {
  // exit if database does not exist on firebase instance
  if (!firebase.database || !firebase.database.ServerValue) {
    return
  }
  const ref = firebase.database().ref()
  const { config: { presence, sessions }, authUid } = firebase._
  const amOnline = ref.child('.info/connected')
  const onlineRef = ref
    .child(
      isFunction(presence)
        ? presence(firebase.auth().currentUser, firebase)
        : presence
    )
    .child(authUid)
  let sessionsRef = isFunction(sessions)
    ? sessions(firebase.auth().currentUser, firebase)
    : sessions
  if (sessionsRef) {
    sessionsRef = ref.child(sessions)
  }
  amOnline.on('value', snapShot => {
    if (!snapShot.val()) return
    // user is online
    if (sessionsRef) {
      // add session and set disconnect
      dispatch({ type: actionTypes.SESSION_START, payload: authUid })
      // add new session to sessions collection
      const session = sessionsRef.push({
        startedAt: firebase.database.ServerValue.TIMESTAMP,
        user: authUid
      })
      // Support versions of react-native-firebase that do not have setPriority
      // on firebase.database.ThenableReference
      if (isFunction(session.setPriority)) {
        // set authUid as priority for easy sorting
        session.setPriority(authUid)
      }
      session
        .child('endedAt')
        .onDisconnect()
        .set(firebase.database.ServerValue.TIMESTAMP, () => {
          dispatch({ type: actionTypes.SESSION_END })
        })
    }
    // add correct session id to user
    // remove from presence list
    onlineRef.set(true)
    onlineRef.onDisconnect().remove()
  })
}
<|endoftext|>


<commit_before>import d3 from 'd3';

let defaultOrient = "bottom";
let axisOrients = {top: 1, right: 1, bottom: 1, left: 1};
var ε = 1e-6;

export var axis = function() {
  var scale = d3.scale.linear(),
      orient = defaultOrient,
      innerTickSize = 6,
      outerTickSize = 6,
      tickPadding = 3,
      tickArguments_ = [10],
      tickValues = null,
      tickFormat_;

  function axis(g) {
    g.each(function() {
      var g = d3.select(this);

      // Stash a snapshot of the new scale, and retrieve the old snapshot.
      var scale0 = this.__chart__ || scale,
          scale1 = this.__chart__ = scale.copy();

      // Ticks, or domain values for ordinal scales.
      var ticks = tickValues == null ? (scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain()) : tickValues,
          tickFormat = tickFormat_ == null ? (scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity) : tickFormat_,
          tick = g.selectAll(".tick").data(ticks, scale1),
          tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε),
          tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(),
          tickUpdate = d3.transition(tick.order()).style("opacity", 1),
          tickSpacing = Math.max(innerTickSize, 0) + tickPadding,
          tickTransform;

      // Domain.
      var range = scaleRange(scale1),
          path = g.selectAll(".domain").data([0]),
          pathUpdate = (path.enter().append("path").attr("class", "domain"), d3.transition(path));

      tickEnter.append("line");
      tickEnter.append("text");

      var lineEnter = tickEnter.select("line"),
          lineUpdate = tickUpdate.select("line"),
          text = tick.select("text").text(tickFormat),
          textEnter = tickEnter.select("text"),
          textUpdate = tickUpdate.select("text"),
          sign = orient === "top" || orient === "left" ? -1 : 1,
          x1, x2, y1, y2;

      if (orient === "bottom" || orient === "top") {
        tickTransform = axisX;
        x1 = "x";
        y1 = "y";
        x2 = "x2";
        y2 = "y2";

        text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
        pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);

      } else {
        tickTransform = axisY;
        x1 = "y";
        y1 = "x";
        x2 = "y2";
        y2 = "x2";

        text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
        pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
      }

      lineEnter.attr(y2, sign * innerTickSize);
      textEnter.attr(y1, sign * tickSpacing);
      lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
      textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);

      // If either the new or old scale is ordinal,
      // entering ticks are undefined in the old scale,
      // and so can fade-in in the new scale’s position.
      // Exiting ticks are likewise undefined in the new scale,
      // and so can fade-out in the old scale’s position.
      if (scale1.rangeBand) {
        var x = scale1, dx = x.rangeBand() / 2;
        scale0 = scale1 = function(d) { return x(d) + dx; };
      } else if (scale0.rangeBand) {
        scale0 = scale1;
      } else {
        tickExit.call(tickTransform, scale1, scale0);
      }

      tickEnter.call(tickTransform, scale0, scale1);
      tickUpdate.call(tickTransform, scale1, scale1);
    });
  }

  axis.scale = function(x) {
    if (!arguments.length) {
      return scale;
    }
    scale = x;
    return axis;
  };

  axis.orient = function(x) {
    if (!arguments.length) {
      return orient;
    }
    orient = x in axisOrients ? x + "" : defaultOrient;
    return axis;
  };

  axis.ticks = function() {
    if (!arguments.length) {
      return tickArguments_;
    }
    tickArguments_ = arguments;
    return axis;
  };

  axis.tickValues = function(x) {
    if (!arguments.length) {
      return tickValues;
    }
    tickValues = x;
    return axis;
  };

  axis.tickFormat = function(x) {
    if (!arguments.length) {
      return tickFormat_;
    }
    tickFormat_ = x;
    return axis;
  };

  axis.tickSize = function(x) {
    var n = arguments.length;
    if (!n) {
      return innerTickSize;
    }
    innerTickSize = +x;
    outerTickSize = +arguments[n - 1];
    return axis;
  };

  axis.innerTickSize = function(x) {
    if (!arguments.length) {
      return innerTickSize;
    }
    innerTickSize = +x;
    return axis;
  };

  axis.outerTickSize = function(x) {
    if (!arguments.length) {
      return outerTickSize;
    }
    outerTickSize = +x;
    return axis;
  };

  axis.tickPadding = function(x) {
    if (!arguments.length) {
      return tickPadding;
    }
    tickPadding = +x;
    return axis;
  };

  axis.tickSubdivide = function() {
    return arguments.length && axis;
  };

  return axis;
};

function scaleExtent(domain) {
  var start = domain[0], stop = domain[domain.length - 1];
  return start < stop ? [start, stop] : [stop, start];
}

function scaleRange(scale) {
  return scale.rangeExtent ? scale.rangeExtent() : scaleExtent(scale.range());
}

function axisX(selection, x0, x1) {
  selection.attr("transform", function(d) { var v0 = x0(d); return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; });
}

function axisY(selection, y0, y1) {
  selection.attr("transform", function(d) { var v0 = y0(d); return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; });
}
<commit_msg>reformatting variables<commit_after>import d3 from 'd3';

let defaultOrient = "bottom";
let axisOrients = {top: 1, right: 1, bottom: 1, left: 1};
var ε = 1e-6;

export var axis = function() {
  let scale = d3.scale.linear();
  let orient = defaultOrient;
  let innerTickSize = 6;
  let outerTickSize = 6;
  let tickPadding = 3;
  let tickArguments_ = [10];
  let tickValues = null;
  let tickFormat_;

  function axis(g) {
    g.each(function() {
      var g = d3.select(this);

      // Stash a snapshot of the new scale, and retrieve the old snapshot.
      let scale0 = this.__chart__ || scale;
      let scale1 = this.__chart__ = scale.copy();

      // Ticks, or domain values for ordinal scales.
      var ticks = tickValues == null ? (scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain()) : tickValues,
          tickFormat = tickFormat_ == null ? (scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : function (d) { return d; }) : tickFormat_,
          tick = g.selectAll(".tick").data(ticks, scale1),
          tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε),
          tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(),
          tickUpdate = d3.transition(tick.order()).style("opacity", 1),
          tickSpacing = Math.max(innerTickSize, 0) + tickPadding,
          tickTransform;

      // Domain.
      var range = scaleRange(scale1),
          path = g.selectAll(".domain").data([0]),
          pathUpdate = (path.enter().append("path").attr("class", "domain"), d3.transition(path));

      tickEnter.append("line");
      tickEnter.append("text");

      var lineEnter = tickEnter.select("line"),
          lineUpdate = tickUpdate.select("line"),
          text = tick.select("text").text(tickFormat),
          textEnter = tickEnter.select("text"),
          textUpdate = tickUpdate.select("text"),
          sign = orient === "top" || orient === "left" ? -1 : 1,
          x1, x2, y1, y2;

      if (orient === "bottom" || orient === "top") {
        tickTransform = axisX;
        x1 = "x";
        y1 = "y";
        x2 = "x2";
        y2 = "y2";

        text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
        pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);

      } else {
        tickTransform = axisY;
        x1 = "y";
        y1 = "x";
        x2 = "y2";
        y2 = "x2";

        text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
        pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
      }

      lineEnter.attr(y2, sign * innerTickSize);
      textEnter.attr(y1, sign * tickSpacing);
      lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
      textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);

      // If either the new or old scale is ordinal,
      // entering ticks are undefined in the old scale,
      // and so can fade-in in the new scale’s position.
      // Exiting ticks are likewise undefined in the new scale,
      // and so can fade-out in the old scale’s position.
      if (scale1.rangeBand) {
        var x = scale1, dx = x.rangeBand() / 2;
        scale0 = scale1 = function(d) { return x(d) + dx; };
      } else if (scale0.rangeBand) {
        scale0 = scale1;
      } else {
        tickExit.call(tickTransform, scale1, scale0);
      }

      tickEnter.call(tickTransform, scale0, scale1);
      tickUpdate.call(tickTransform, scale1, scale1);
    });
  }

  axis.scale = function(x) {
    if (!arguments.length) {
      return scale;
    }
    scale = x;
    return axis;
  };

  axis.orient = function(x) {
    if (!arguments.length) {
      return orient;
    }
    orient = x in axisOrients ? x + "" : defaultOrient;
    return axis;
  };

  axis.ticks = function() {
    if (!arguments.length) {
      return tickArguments_;
    }
    tickArguments_ = arguments;
    return axis;
  };

  axis.tickValues = function(x) {
    if (!arguments.length) {
      return tickValues;
    }
    tickValues = x;
    return axis;
  };

  axis.tickFormat = function(x) {
    if (!arguments.length) {
      return tickFormat_;
    }
    tickFormat_ = x;
    return axis;
  };

  axis.tickSize = function(x) {
    var n = arguments.length;
    if (!n) {
      return innerTickSize;
    }
    innerTickSize = +x;
    outerTickSize = +arguments[n - 1];
    return axis;
  };

  axis.innerTickSize = function(x) {
    if (!arguments.length) {
      return innerTickSize;
    }
    innerTickSize = +x;
    return axis;
  };

  axis.outerTickSize = function(x) {
    if (!arguments.length) {
      return outerTickSize;
    }
    outerTickSize = +x;
    return axis;
  };

  axis.tickPadding = function(x) {
    if (!arguments.length) {
      return tickPadding;
    }
    tickPadding = +x;
    return axis;
  };

  axis.tickSubdivide = function() {
    return arguments.length && axis;
  };

  return axis;
};

function scaleExtent(domain) {
  var start = domain[0], stop = domain[domain.length - 1];
  return start < stop ? [start, stop] : [stop, start];
}

function scaleRange(scale) {
  return scale.rangeExtent ? scale.rangeExtent() : scaleExtent(scale.range());
}

function axisX(selection, x0, x1) {
  selection.attr("transform", function(d) { var v0 = x0(d); return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; });
}

function axisY(selection, y0, y1) {
  selection.attr("transform", function(d) { var v0 = y0(d); return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; });
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import type { CallNodePath } from '../types/profile-derived';

export function arePathsEqual(a: CallNodePath, b: CallNodePath): boolean {
  if (a === b) {
    return true;
  }

  if (a.length !== b.length) {
    return false;
  }

  // Iterating from the end because this will likely fail faster
  for (let i = a.length - 1; i >= 0; i--) {
    if (a[i] !== b[i]) {
      return false;
    }
  }

  return true;
}

// We take the easy path by converting the path to a string that will be unique.
// In the future and if necessary we might want to have an integer-based hash so
// that it's faster. In that case we might want to use only the n last values
// that should be more discriminate and keep it fast.
export function hashPath(a: CallNodePath): string {
  return a.join('-');
}

export class PathSet implements Iterable<CallNodePath> {
  _table: Map<string, CallNodePath>;

  constructor(iterable?: Iterable<CallNodePath>) {
    this._table = new Map();

    if (!iterable) {
      return;
    }

    for (const path of iterable) {
      this._table.set(hashPath(path), path);
    }
  }

  add(path: CallNodePath) {
    this._table.set(hashPath(path), path);
    return this;
  }

  clear() {
    this._table.clear();
  }

  delete(path: CallNodePath) {
    return this._table.delete(hashPath(path));
  }

  *values(): Iterator<CallNodePath> {
    yield* this._table.values();
  }

  *entries(): Iterator<[CallNodePath, CallNodePath]> {
    for (const entry of this) {
      yield [entry, entry];
    }
  }

  forEach(func: (CallNodePath, CallNodePath, PathSet) => void, thisArg?: any) {
    for (const entry of this) {
      func.call(thisArg, entry, entry, this);
    }
  }

  has(path: CallNodePath): boolean {
    return this._table.has(hashPath(path));
  }

  get size(): number {
    return this._table.size;
  }

  // https://github.com/facebook/flow/issues/3258
  // $FlowFixMe
  *[Symbol.iterator]() {
    yield* this._table.values();
  }

  // This is a hack for Flow's Iterable support,
  // see https://stackoverflow.com/questions/48491307/iterable-class-in-flow
  /*::
  @@iterator(): * {
    // $FlowFixMe
    return this[Symbol.iterator]()
  }
  */
}
<commit_msg>Implement a shortcut when creating a PathSet out of another PathSet<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import type { CallNodePath } from '../types/profile-derived';

export function arePathsEqual(a: CallNodePath, b: CallNodePath): boolean {
  if (a === b) {
    return true;
  }

  if (a.length !== b.length) {
    return false;
  }

  // Iterating from the end because this will likely fail faster
  for (let i = a.length - 1; i >= 0; i--) {
    if (a[i] !== b[i]) {
      return false;
    }
  }

  return true;
}

// We take the easy path by converting the path to a string that will be unique.
// In the future and if necessary we might want to have an integer-based hash so
// that it's faster. In that case we might want to use only the n last values
// that should be more discriminate and keep it fast.
export function hashPath(a: CallNodePath): string {
  return a.join('-');
}

export class PathSet implements Iterable<CallNodePath> {
  _table: Map<string, CallNodePath>;

  constructor(iterable?: Iterable<CallNodePath>) {
    if (iterable instanceof PathSet) {
      // This shortcut avoids the call to `hashPath` by taking advantage of
      // knowing some inner workings.
      this._table = new Map(iterable._table);
      return;
    }

    this._table = new Map();

    if (!iterable) {
      return;
    }

    for (const path of iterable) {
      this._table.set(hashPath(path), path);
    }
  }

  add(path: CallNodePath) {
    this._table.set(hashPath(path), path);
    return this;
  }

  clear() {
    this._table.clear();
  }

  delete(path: CallNodePath) {
    return this._table.delete(hashPath(path));
  }

  *values(): Iterator<CallNodePath> {
    yield* this._table.values();
  }

  *entries(): Iterator<[CallNodePath, CallNodePath]> {
    for (const entry of this) {
      yield [entry, entry];
    }
  }

  forEach(func: (CallNodePath, CallNodePath, PathSet) => void, thisArg?: any) {
    for (const entry of this) {
      func.call(thisArg, entry, entry, this);
    }
  }

  has(path: CallNodePath): boolean {
    return this._table.has(hashPath(path));
  }

  get size(): number {
    return this._table.size;
  }

  // https://github.com/facebook/flow/issues/3258
  // $FlowFixMe
  *[Symbol.iterator]() {
    yield* this._table.values();
  }

  // This is a hack for Flow's Iterable support,
  // see https://stackoverflow.com/questions/48491307/iterable-class-in-flow
  /*::
  @@iterator(): * {
    // $FlowFixMe
    return this[Symbol.iterator]()
  }
  */
}
<|endoftext|>