<commit_before>var config = {}

config.current_semester = '1';

config.group_list = {
	'vincent' : {
		'H0N06A' : ['1MscB', '1MscC+Mti'] // Modelling of Complex Systems
	}
};

config.users = {
	'axel' : {
		'51230411-2017-2-1' : [
								'S0B88a', // Genderstudies
								'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
								//'H03F0a', 'H0T45a', // Technisch-wetenschappelijke software
								//'H03F9a', 'H03G0a', // Parallel Computing
								//'H00H1a', 'H02D1a', 'H08M3a', // Genetic Algorithms and Evolutionary Computing
								'H05N0a' // Capita Selecta Computer Science: Artificial Intelligence
							],
		'51230411-2017-1-2' : [
								'H05N0a' // Capita Selecta Computer Science: Artificial Intelligence
							],
		'52364422-2017-2-1' : [
 								'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
  						],
		'51016880-2017-1-1' : [
								'H00I6a', 'H00I7a', 'H02A0a', 'H02K1a', // 	Fundamentals of Artificial Intelligence
								'H00G1A', 'H02B2A', // Cognitive Science
								'H02H5A', // Cybernetics and its Applications in Physiology and Biological Sciences
							],
		'51016880-2017-1-2' : [
								//'H02D5a', // Philosophy of Mind and Artificial Intelligence
								'H00G8a', 'H02C4a', // Artificial Neural Networks
								'H00H3a', 'H02D3a', // Support Vector Machines: Methods and Applications
								'H00G9a', 'H02C8A', // Text based Information Retrieval
							],
		'51016867-2017-2-1' : [
								'H05M9A', 'H05N1A' // Bio-informatica
		],
		'51518550-2017-2-1' : [
								'H03E1a' // System Identification and Modelling
		]
	},
	'luuk' : {
		'51230411-2017-2-1' : [
								'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
                'H04G7A'  // Capita Selecta Computer Science: Distributed Systems
              ],
		'52364422-2017-2-1' : [
 								'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
  						],
		'51230411-2017-2-2' : [
								'H04I0A', 'H04J1A', //internet infrastructure
                'G0K31B', //gedistribueerde software archituren: verdiepende studie
                'H04G7A', // Capita Selecta Computer Science: Distributed Systems
								'H04G4A', 'H04G5A', 'H04G6A' //Security of Network and Computer infrastructure
              ],
    '51230411-2017-1-1' : [
                // 'G0B34A', 'G0B35A', 'H00Y8A', 'H00Y9A', //knowledge and the web
								'G0K32A', //vereisten analyse
                'H00K1A', 'H03K2A' //Engineering Economy
    ]
	},
	'vincent' : {
		'51230411-2017-1-1' : [
						'H01F2A', 'H01F3A', // Bedrijfskunde & Entrepreneurship
						'G0Q57a', 'G0Q58a', // Modellering en simulatie
						'G0B23A', 'G0B24A', 'G0B25A', //Modellering van complexe systemen
						'G0K32A', // Vereisten analyse
						'H00K1A', 'H03K2A' // Engineering Economy
						],
		'52364422-2017-1-1' : [
						'H0N05a', 'H0N06a', 'H0N07a', // Modelling of complex systems
						],
		'51230411-2017-1-2' : [
						'H01L1A', 'H01L2A', // Digitale elektronica en processoren
						// 'H00I0A', 'H02C6A', 'HMI08B',// Data Mining
						'H00I2A', 'H02H4A', 'H08M2A', // Multi-Agent Sytems
						],

		'51230411-2017-2-1' : [
						'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
						'H04G7A'  // Capita Selecta Computer Science: Distributed Systems
												],
		'52364422-2017-2-1' : [
						'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
										],

		'51230411-2017-2-2' : [
						'H04I0A', 'H04J1A', // Internet infrastructure
						'G0K31B', // Gedistribueerde software architecturen: verdiepende studie
						'H04G4A', 'H04G5A', 'H04G6A', //Security of Network and Computer infrastructure
						'H04G7A', // Capita Selecta Computer Science: Distributed Systems
					],
	},
	'emiel' : {
		'51230411-2017-1-1' : [
						'G0B23A', 'G0B24A', 'G0B25A', //Modellering van complexe systemen
						],
		'52364422-2017-1-1' : [
						'H0N05a', 'H0N06a', 'H0N07a', // Modelling of complex systems
						],
		'51230411-2017-1-2' : [
						'H01L1A', 'H01L2A', // Digitale elektronica en processoren

						],

		'51230411-2017-2-1' : [
						'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
						'G0L15A'  // Capita Selecta van de software engineering
												],
		'52364422-2017-2-1' : [
						'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
						'H0A12a', // Geavanceerde methodes voor software architectuur
						'H04K5a','H04K6a' // Development of Secure Software
										],

		'51230411-2017-2-2' : [
						'G0K31B', // Gedistribueerde software architecturen: verdiepende studie
						'H04G4A', 'H04G5A', 'H04G6A', //Security of Network and Computer infrastructure
						'G0L15A', // Capita Selecta van de software engineering
					],
	}
};

module.exports = config
<commit_msg>- fixed spelling<commit_after>var config = {}

config.current_semester = '1';

config.group_list = {
	'vincent' : {
		'H0N06A' : ['1McsB', '1McsC+Mti'] // Modelling of Complex Systems
	}
};

config.users = {
	'axel' : {
		'51230411-2017-2-1' : [
								'S0B88a', // Genderstudies
								'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
								//'H03F0a', 'H0T45a', // Technisch-wetenschappelijke software
								//'H03F9a', 'H03G0a', // Parallel Computing
								//'H00H1a', 'H02D1a', 'H08M3a', // Genetic Algorithms and Evolutionary Computing
								'H05N0a' // Capita Selecta Computer Science: Artificial Intelligence
							],
		'51230411-2017-1-2' : [
								'H05N0a' // Capita Selecta Computer Science: Artificial Intelligence
							],
		'52364422-2017-2-1' : [
 								'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
  						],
		'51016880-2017-1-1' : [
								'H00I6a', 'H00I7a', 'H02A0a', 'H02K1a', // 	Fundamentals of Artificial Intelligence
								'H00G1A', 'H02B2A', // Cognitive Science
								'H02H5A', // Cybernetics and its Applications in Physiology and Biological Sciences
							],
		'51016880-2017-1-2' : [
								//'H02D5a', // Philosophy of Mind and Artificial Intelligence
								'H00G8a', 'H02C4a', // Artificial Neural Networks
								'H00H3a', 'H02D3a', // Support Vector Machines: Methods and Applications
								'H00G9a', 'H02C8A', // Text based Information Retrieval
							],
		'51016867-2017-2-1' : [
								'H05M9A', 'H05N1A' // Bio-informatica
		],
		'51518550-2017-2-1' : [
								'H03E1a' // System Identification and Modelling
		]
	},
	'luuk' : {
		'51230411-2017-2-1' : [
								'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
                'H04G7A'  // Capita Selecta Computer Science: Distributed Systems
              ],
		'52364422-2017-2-1' : [
 								'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
  						],
		'51230411-2017-2-2' : [
								'H04I0A', 'H04J1A', //internet infrastructure
                'G0K31B', //gedistribueerde software archituren: verdiepende studie
                'H04G7A', // Capita Selecta Computer Science: Distributed Systems
								'H04G4A', 'H04G5A', 'H04G6A' //Security of Network and Computer infrastructure
              ],
    '51230411-2017-1-1' : [
                // 'G0B34A', 'G0B35A', 'H00Y8A', 'H00Y9A', //knowledge and the web
								'G0K32A', //vereisten analyse
                'H00K1A', 'H03K2A' //Engineering Economy
    ]
	},
	'vincent' : {
		'51230411-2017-1-1' : [
						'H01F2A', 'H01F3A', // Bedrijfskunde & Entrepreneurship
						'G0Q57a', 'G0Q58a', // Modellering en simulatie
						'G0B23A', 'G0B24A', 'G0B25A', //Modellering van complexe systemen
						'G0K32A', // Vereisten analyse
						'H00K1A', 'H03K2A' // Engineering Economy
						],
		'52364422-2017-1-1' : [
						'H0N05a', 'H0N06a', 'H0N07a', // Modelling of complex systems
						],
		'51230411-2017-1-2' : [
						'H01L1A', 'H01L2A', // Digitale elektronica en processoren
						// 'H00I0A', 'H02C6A', 'HMI08B',// Data Mining
						'H00I2A', 'H02H4A', 'H08M2A', // Multi-Agent Sytems
						],

		'51230411-2017-2-1' : [
						'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
						'H04G7A'  // Capita Selecta Computer Science: Distributed Systems
												],
		'52364422-2017-2-1' : [
						'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
										],

		'51230411-2017-2-2' : [
						'H04I0A', 'H04J1A', // Internet infrastructure
						'G0K31B', // Gedistribueerde software architecturen: verdiepende studie
						'H04G4A', 'H04G5A', 'H04G6A', //Security of Network and Computer infrastructure
						'H04G7A', // Capita Selecta Computer Science: Distributed Systems
					],
	},
	'emiel' : {
		'51230411-2017-1-1' : [
						'G0B23A', 'G0B24A', 'G0B25A', //Modellering van complexe systemen
						],
		'52364422-2017-1-1' : [
						'H0N05a', 'H0N06a', 'H0N07a', // Modelling of complex systems
						],
		'51230411-2017-1-2' : [
						'H01L1A', 'H01L2A', // Digitale elektronica en processoren

						],

		'51230411-2017-2-1' : [
						'H04L5a', 'H04L6a', 'H09P6a', // Vergelijkende studie van imperatieve programmeertalen
						'G0L15A'  // Capita Selecta van de software engineering
												],
		'52364422-2017-2-1' : [
						'H0S01a', 'H0S02a', 'H0S03a', // Comparative Programming Languages
						'H0A12a', // Geavanceerde methodes voor software architectuur
						'H04K5a','H04K6a' // Development of Secure Software
										],

		'51230411-2017-2-2' : [
						'G0K31B', // Gedistribueerde software architecturen: verdiepende studie
						'H04G4A', 'H04G5A', 'H04G6A', //Security of Network and Computer infrastructure
						'G0L15A', // Capita Selecta van de software engineering
					],
	}
};

module.exports = config
<|endoftext|>


<commit_before>var mongoose = require('mongoose');
var Promise = require('bluebird');
mongoose.Promise = Promise;
var Schema = mongoose.Schema;
var streamWorker = require('stream-worker');

/**
 * @class Tree
 * Tree Behavior for Mongoose
 *
 * Implements the materialized path strategy with cascade child re-parenting
 * on delete for storing a hierarchy of documents with Mongoose
 *
 * @param  {Mongoose.Schema} schema
 * @param  {Object} options
 */
function tree(schema, options) {

  var pathSeparator = options && options.pathSeparator || '#';
  var wrapChildrenTree = options && options.wrapChildrenTree;
  var onDelete = options && options.onDelete || 'DELETE'; //'REPARENT'
  var numWorkers = options && options.numWorkers || 5;
  var idType = options && options.idType || Schema.ObjectId;
  var pathSeparatorRegex = '[' + pathSeparator + ']';

  /**
   * Add parent and path properties
   *
   * @property {ObjectID} parent
   * @property {String} path
   */
  schema.add({
    parent: {
      type: idType,
      set: function (val) {
        return (val instanceof Object && val._id) ? val._id : val;
      },
      index: true
    },
    path: {
      type: String,
      index: true
    }
  });

  /**
   * Pre-save middleware
   * Build or rebuild path when needed
   *
   * @param  {Function} next
   */
  schema.pre('save', function preSave(next) {

    var isParentChange = this.isModified('parent');

    if (this.isNew || isParentChange) {
      if (!this.parent) {
        this.path = this._id.toString();
        return Promise.resolve().asCallback(next);
      }

      var self = this;
      return this.constructor.findOne({ _id: this.parent })
      .then(function (parentDoc) {
        var previousPath = self.path;
        self.path = parentDoc.path + pathSeparator + self._id.toString();
        // When the parent is changed we must rewrite all children paths as well
        if (isParentChange) {
          return Promise.all([
            self.constructor.find({path: {'$regex': '^' + previousPath + pathSeparatorRegex}}),
            previousPath,
            self.path
          ])
        } else {
          return Promise.resolve();
        }
      })
      .spread(function (docs, previousPath, parentPath) {
        if (docs) {
          var promises = [];
          docs.forEach(function (doc) {
            var newPath = parentPath + doc.path.substr(previousPath.length);
            promises.push(self.constructor.findByIdAndUpdate(doc._id, { $set: { path: newPath } }));
          });
          return Promise.all(promises);
        } else {
          return Promise.resolve();
        }
      })
      .then(function (result) {
        return;
      })
      .catch(function (err) {
        throw err;
      })
      .asCallback(next);
    } else {
      return Promise.resolve().asCallback(next);
    }

  });

    /**
     * Pre-remove middleware
     *
     * @param  {Function} next
     */
    schema.pre('remove', function preRemove(next) {

      if (!this.path)
        return next();

      // @TODO REPARENT mode support
      return this.collection.remove({ path: { '$regex': '^' + this.path + pathSeparatorRegex } })
      .then(function () {
        return Promise.resolve().asCallback(next);
      });

    });

    /**
     * @method getChildren
     *
     * @param  {Object}               filters (like for mongo find) (optional)
     * @param  {Object} or {String}   fields  (like for mongo find) (optional)
     * @param  {Object}               options (like for mongo find) (optional)
     * @param  {Boolean}              recursive, default false      (optional)
     * @return {Model}
     */
    schema.methods.getChildren = function getChildren(filters, fields, options, recursive) {

      if ('boolean' === typeof filters) {
        recursive = filters
        filters = {};
        fields = null;
        options = {};
      } else if ('boolean' === typeof fields) {
        recursive = fields;
        fields = null;
        options = {};
      } else if ('boolean' === typeof options) {
        recursive = options;
        options = {};
      }

      filters = filters || {};
      fields = fields || null;
      options = options || {};
      recursive = recursive || false;

      if (recursive) {
        if(filters['$query']){
          filters['$query']['path'] = {$regex: '^' + this.path + pathSeparatorRegex};
        } else {
          filters['path'] = {$regex: '^' + this.path + pathSeparatorRegex};
        }
      } else {
        if(filters['$query']){
          filters['$query']['parent'] = this._id;
        } else {
          filters['parent'] = this._id;
        }
      }

      return this.model(this.constructor.modelName).find(filters, fields, options);
    };

    /**
     * @method getParent
     *
     * @param  {Function} next
     * @return {Model}
     */
    schema.methods.getParent = function getParent() {
      return this.model(this.constructor.modelName).findOne({ _id: this.parent });
    };

    /**
     * @method getAncestors
     *
     * @param  {Object}   args
     * @return {Model}
     */
    schema.methods.getAncestors = function getAncestors(filters, fields, options) {

      filters = filters || {};
      fields = fields || null;
      options = options || {};

      var ids = [];

      if (this.path) {
        ids = this.path.split(pathSeparator);
        ids.pop();
      }

      if(filters['$query']){
        filters['$query']['_id'] = { $in: ids };
      } else {
        filters['_id'] = { $in: ids };
      }

      return this.model(this.constructor.modelName).find(filters, fields, options);
    };

    /**
     * @method getChildrenTree
     *
     * @param  {Document} root (optional)
     * @param  {Object}   args (optional)
     * @param  {Object}        .filters (like for mongo find)
     * @param  {Object} or {String}   .fields  (like for mongo find)
     * @param  {Object}        .options (like for mongo find)
     * @param  {Number}        .minLevel, default 1
     * @param  {Boolean}       .recursive
     * @param  {Boolean}       .allowEmptyChildren
     * @return {Model}
     */
    //schema.statics.getChildrenTree = function getChildrenTree(root, args, next) {
    //
    //    if ("function" === typeof(root))
    //    {
    //        next = root;
    //        root = null;
    //        args = {};
    //    }
    //    else if ("function" === typeof(args)) {
    //        next = args;
    //
    //        if ("model" in root) {
    //            args = {};
    //        }
    //        else
    //        {
    //            args = root;
    //            root = null
    //        }
    //    }
    //
    //    var filters = args.filters || {};
    //    var fields = args.fields || null;
    //    var options = args.options || {};
    //    var minLevel = args.minLevel || 1;
    //    var recursive = args.recursive != undefined ? args.recursive : true;
    //    var allowEmptyChildren = args.allowEmptyChildren != undefined ? args.allowEmptyChildren : true;
    //
    //    if (!next)
    //        throw new Error('no callback defined when calling getChildrenTree');
    //
    //    // filters: Add recursive path filter or not
    //    if (recursive) {
    //        if (root) {
    //            filters.path = { $regex: '^' + root.path + pathSeparatorRegex };
    //        }
    //
    //        if (filters.parent === null) {
    //            delete filters.parent;
    //        }
    //
    //    } else {
    //        if (root) {
    //            filters.parent = root._id;
    //        }
    //        else {
    //            filters.parent = null
    //        }
    //    }
    //
    //    // fields: Add path and parent in the result if not already specified
    //    if (fields) {
    //        if (fields instanceof Object) {
    //            if (!fields.hasOwnProperty('path')) {
    //                fields['path'] = 1;
    //            }
    //            if (!fields.hasOwnProperty('parent')) {
    //                fields['parent'] = 1;
    //            }
    //        }
    //        else {
    //            if (!fields.match(/path/)) {
    //                fields += ' path';
    //            }
    //            if (!fields.match(/parent/)) {
    //                fields += ' parent';
    //            }
    //        }
    //    }
    //
    //    // options:sort , path sort is mandatory
    //    if (!options.sort) {
    //        options.sort = {};
    //    }
    //    options.sort.path = 1;
    //
    //    if (options.lean == null) {
    //        options.lean = !wrapChildrenTree;
    //    }
    //
    //    return this.find(filters, fields, options, function (err, results) {
    //
    //        if (err) {
    //            return next(err);
    //        }
    //
    //        var getLevel = function (path) {
    //
    //            return path ? path.split(pathSeparator).length : 0;
    //        };
    //
    //        var createChildren = function createChildren(arr, node, level) {
    //
    //            if (level == minLevel) {
    //                if (allowEmptyChildren) {
    //                    node.children = [];
    //                }
    //                return arr.push(node);
    //            }
    //
    //            var nextIndex = arr.length - 1;
    //            var myNode = arr[nextIndex];
    //
    //            if (!myNode) {
    //                //console.log("Tree node " + node.name + " filtered out. Level: " + level + " minLevel: " + minLevel);
    //                return []
    //            } else {
    //                createChildren(myNode.children, node, level - 1);
    //            }
    //        };
    //
    //        var finalResults = [];
    //        var rootLevel = 1;
    //
    //        if (root) {
    //            rootLevel = getLevel(root.path) + 1;
    //        }
    //
    //        if (minLevel < rootLevel) {
    //            minLevel = rootLevel
    //        }
    //
    //        for (var r in results) {
    //            var level = getLevel(results[r].path);
    //            createChildren(finalResults, results[r], level);
    //        }
    //
    //        next(err, finalResults);
    //
    //    });
    //};

    // schema.methods.getChildrenTree = function(args) {
    //   this.constructor.getChildrenTree(this, args)
    // };

    /**
     * @property {Number} level <virtual>
     */
    schema.virtual('level').get(function virtualPropLevel() {
      return this.path ? this.path.split(pathSeparator).length : 0;
    });
}

module.exports = tree;
<commit_msg>use native then function to replace spread function<commit_after>var mongoose = require('mongoose');
var Promise = require('bluebird');
mongoose.Promise = Promise;
var Schema = mongoose.Schema;
var streamWorker = require('stream-worker');

/**
 * @class Tree
 * Tree Behavior for Mongoose
 *
 * Implements the materialized path strategy with cascade child re-parenting
 * on delete for storing a hierarchy of documents with Mongoose
 *
 * @param  {Mongoose.Schema} schema
 * @param  {Object} options
 */
function tree(schema, options) {

  var pathSeparator = options && options.pathSeparator || '#';
  var wrapChildrenTree = options && options.wrapChildrenTree;
  var onDelete = options && options.onDelete || 'DELETE'; //'REPARENT'
  var numWorkers = options && options.numWorkers || 5;
  var idType = options && options.idType || Schema.ObjectId;
  var pathSeparatorRegex = '[' + pathSeparator + ']';

  /**
   * Add parent and path properties
   *
   * @property {ObjectID} parent
   * @property {String} path
   */
  schema.add({
    parent: {
      type: idType,
      set: function (val) {
        return (val instanceof Object && val._id) ? val._id : val;
      },
      index: true
    },
    path: {
      type: String,
      index: true
    }
  });

  /**
   * Pre-save middleware
   * Build or rebuild path when needed
   *
   * @param  {Function} next
   */
  schema.pre('save', function preSave(next) {

    var isParentChange = this.isModified('parent');

    if (this.isNew || isParentChange) {
      if (!this.parent) {
        this.path = this._id.toString();
        return Promise.resolve().asCallback(next);
      }

      var self = this;
      return this.constructor.findOne({ _id: this.parent })
      .then(function (parentDoc) {
        var previousPath = self.path;
        self.path = parentDoc.path + pathSeparator + self._id.toString();
        // When the parent is changed we must rewrite all children paths as well
        if (isParentChange) {
          return Promise.all([
            self.constructor.find({path: {'$regex': '^' + previousPath + pathSeparatorRegex}}),
            previousPath,
            self.path
          ])
        } else {
          return Promise.resolve();
        }
      })
      // use then function to replace spread function
      // .spread(function (docs, previousPath, parentPath) {
      .then(function (result) {
        if (result[0]) {
          var promises = [];
          result[0].forEach(function (doc) {
            var newPath = result[2] + doc.path.substr(result[1].length);
            promises.push(self.constructor.findByIdAndUpdate(doc._id, { $set: { path: newPath } }));
          });
          return Promise.all(promises);
        } else {
          return Promise.resolve();
        }
      })
      .then(function (result) {
        return;
      })
      .catch(function (err) {
        throw err;
      })
      .asCallback(next);
    } else {
      return Promise.resolve().asCallback(next);
    }

  });

    /**
     * Pre-remove middleware
     *
     * @param  {Function} next
     */
    schema.pre('remove', function preRemove(next) {

      if (!this.path)
        return next();

      // @TODO REPARENT mode support
      return this.collection.remove({ path: { '$regex': '^' + this.path + pathSeparatorRegex } })
      .then(function () {
        return Promise.resolve().asCallback(next);
      });

    });

    /**
     * @method getChildren
     *
     * @param  {Object}               filters (like for mongo find) (optional)
     * @param  {Object} or {String}   fields  (like for mongo find) (optional)
     * @param  {Object}               options (like for mongo find) (optional)
     * @param  {Boolean}              recursive, default false      (optional)
     * @return {Model}
     */
    schema.methods.getChildren = function getChildren(filters, fields, options, recursive) {

      if ('boolean' === typeof filters) {
        recursive = filters
        filters = {};
        fields = null;
        options = {};
      } else if ('boolean' === typeof fields) {
        recursive = fields;
        fields = null;
        options = {};
      } else if ('boolean' === typeof options) {
        recursive = options;
        options = {};
      }

      filters = filters || {};
      fields = fields || null;
      options = options || {};
      recursive = recursive || false;

      if (recursive) {
        if(filters['$query']){
          filters['$query']['path'] = {$regex: '^' + this.path + pathSeparatorRegex};
        } else {
          filters['path'] = {$regex: '^' + this.path + pathSeparatorRegex};
        }
      } else {
        if(filters['$query']){
          filters['$query']['parent'] = this._id;
        } else {
          filters['parent'] = this._id;
        }
      }

      return this.model(this.constructor.modelName).find(filters, fields, options);
    };

    /**
     * @method getParent
     *
     * @param  {Function} next
     * @return {Model}
     */
    schema.methods.getParent = function getParent() {
      return this.model(this.constructor.modelName).findOne({ _id: this.parent });
    };

    /**
     * @method getAncestors
     *
     * @param  {Object}   args
     * @return {Model}
     */
    schema.methods.getAncestors = function getAncestors(filters, fields, options) {

      filters = filters || {};
      fields = fields || null;
      options = options || {};

      var ids = [];

      if (this.path) {
        ids = this.path.split(pathSeparator);
        ids.pop();
      }

      if(filters['$query']){
        filters['$query']['_id'] = { $in: ids };
      } else {
        filters['_id'] = { $in: ids };
      }

      return this.model(this.constructor.modelName).find(filters, fields, options);
    };

    /**
     * @method getChildrenTree
     *
     * @param  {Document} root (optional)
     * @param  {Object}   args (optional)
     * @param  {Object}        .filters (like for mongo find)
     * @param  {Object} or {String}   .fields  (like for mongo find)
     * @param  {Object}        .options (like for mongo find)
     * @param  {Number}        .minLevel, default 1
     * @param  {Boolean}       .recursive
     * @param  {Boolean}       .allowEmptyChildren
     * @return {Model}
     */
    //schema.statics.getChildrenTree = function getChildrenTree(root, args, next) {
    //
    //    if ("function" === typeof(root))
    //    {
    //        next = root;
    //        root = null;
    //        args = {};
    //    }
    //    else if ("function" === typeof(args)) {
    //        next = args;
    //
    //        if ("model" in root) {
    //            args = {};
    //        }
    //        else
    //        {
    //            args = root;
    //            root = null
    //        }
    //    }
    //
    //    var filters = args.filters || {};
    //    var fields = args.fields || null;
    //    var options = args.options || {};
    //    var minLevel = args.minLevel || 1;
    //    var recursive = args.recursive != undefined ? args.recursive : true;
    //    var allowEmptyChildren = args.allowEmptyChildren != undefined ? args.allowEmptyChildren : true;
    //
    //    if (!next)
    //        throw new Error('no callback defined when calling getChildrenTree');
    //
    //    // filters: Add recursive path filter or not
    //    if (recursive) {
    //        if (root) {
    //            filters.path = { $regex: '^' + root.path + pathSeparatorRegex };
    //        }
    //
    //        if (filters.parent === null) {
    //            delete filters.parent;
    //        }
    //
    //    } else {
    //        if (root) {
    //            filters.parent = root._id;
    //        }
    //        else {
    //            filters.parent = null
    //        }
    //    }
    //
    //    // fields: Add path and parent in the result if not already specified
    //    if (fields) {
    //        if (fields instanceof Object) {
    //            if (!fields.hasOwnProperty('path')) {
    //                fields['path'] = 1;
    //            }
    //            if (!fields.hasOwnProperty('parent')) {
    //                fields['parent'] = 1;
    //            }
    //        }
    //        else {
    //            if (!fields.match(/path/)) {
    //                fields += ' path';
    //            }
    //            if (!fields.match(/parent/)) {
    //                fields += ' parent';
    //            }
    //        }
    //    }
    //
    //    // options:sort , path sort is mandatory
    //    if (!options.sort) {
    //        options.sort = {};
    //    }
    //    options.sort.path = 1;
    //
    //    if (options.lean == null) {
    //        options.lean = !wrapChildrenTree;
    //    }
    //
    //    return this.find(filters, fields, options, function (err, results) {
    //
    //        if (err) {
    //            return next(err);
    //        }
    //
    //        var getLevel = function (path) {
    //
    //            return path ? path.split(pathSeparator).length : 0;
    //        };
    //
    //        var createChildren = function createChildren(arr, node, level) {
    //
    //            if (level == minLevel) {
    //                if (allowEmptyChildren) {
    //                    node.children = [];
    //                }
    //                return arr.push(node);
    //            }
    //
    //            var nextIndex = arr.length - 1;
    //            var myNode = arr[nextIndex];
    //
    //            if (!myNode) {
    //                //console.log("Tree node " + node.name + " filtered out. Level: " + level + " minLevel: " + minLevel);
    //                return []
    //            } else {
    //                createChildren(myNode.children, node, level - 1);
    //            }
    //        };
    //
    //        var finalResults = [];
    //        var rootLevel = 1;
    //
    //        if (root) {
    //            rootLevel = getLevel(root.path) + 1;
    //        }
    //
    //        if (minLevel < rootLevel) {
    //            minLevel = rootLevel
    //        }
    //
    //        for (var r in results) {
    //            var level = getLevel(results[r].path);
    //            createChildren(finalResults, results[r], level);
    //        }
    //
    //        next(err, finalResults);
    //
    //    });
    //};

    // schema.methods.getChildrenTree = function(args) {
    //   this.constructor.getChildrenTree(this, args)
    // };

    /**
     * @property {Number} level <virtual>
     */
    schema.virtual('level').get(function virtualPropLevel() {
      return this.path ? this.path.split(pathSeparator).length : 0;
    });
}

module.exports = tree;
<|endoftext|>


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

  // ----------- MAPS LAYERS -----------
  // var mappos = L.Permalink.getMapLocation();
  // var map = L.map('map', {
  //   center: mappos.center,
  //   zoom: mappos.zoom
  // });

  var map = L.map('map', {
    center: [42.3469,13.39817],
    zoom: 14
  });

  var mapy_winter = L.tileLayer('https://mapserver.mapy.cz/winter-m/{z}-{x}-{y}', {
    maxZoom: 16,
    maxNativeZoom: 18,
    minNativeZoom: 3,
    minZoom: 10,
    attribution: '© Mapy'
  }).addTo(map);

  var baselayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 17,
    minNativeZoom: 5,
    minZoom: 10,
    attribution: '© OpenTopoMap'
  });

  var baselayer2 = L.tileLayer('https://4umaps.com/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 15,
    minNativeZoom: 2,
    minZoom: 10,
    attribution: '© 4UMaps'
  });

  var googleHybrid = L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',{
    maxZoom: 16,
    maxNativeZoom: 20,
    minNativeZoom: 3,
    minZoom: 10,
    subdomains:['mt0','mt1','mt2','mt3'],
    attribution: '© Google'
  });

  // var igm25k_min = L.tileLayer('https://www.meteoaquilano.it/abruzzo/igm25k_min_abr/{z}/{x}/{y}.png', {
  //   //transparent: true,
  //   maxZoom: 16,
  //   maxNativeZoom: 16,
  //   minNativeZoom: 12,
  //   minZoom: 10,
  //   tms: true,
  //   attribution: '© Geoportale Nazionale'
  // });
  //
  // var igm25k_reg = L.tileLayer('https://www.meteoaquilano.it/abruzzo/igm25k_reg_abr/{z}/{x}/{y}.png', {
  //   //transparent: true,
  //   maxZoom: 16,
  //   maxNativeZoom: 16,
  //   minNativeZoom: 10,
  //   minZoom: 10,
  //   tms: true,
  //   attribution: '© Geoportale Regione Abruzzo'
  // });

  // var igm25k_reg = L.tileLayer.wms('http://geocatalogo.regione.abruzzo.it/erdas-iws/ogc/wms/?', {
  //   layers: 'Mosaici_UTM-WGS84_IGM25k_WGS84.ecw',
  //   maxZoom: 16,
  //   minZoom: 10,
  //   attribution: '© Geoportale Regione Abruzzo'
  // }).addTo(map);

  var tcdlayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/tcd_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.3,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Copernicus'
  }).addTo(map);

  var hillshlayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/ingv_dem10m_hillshade_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.2,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Tinitaly'
  }).addTo(map);

  var slopelayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/ingv_dem10m_slope_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.4,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Tinitaly'
  }).addTo(map);

  var pistelayer = L.tileLayer('https://www.opensnowmap.org/tiles-pistes/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 18,
    minNativeZoom: 3,
    minZoom: 10,
    attribution: '© OpenSnowMap'
  }).addTo(map);

  var userFeatures = L.featureGroup().addTo(map);
  var gpsFeatures = L.featureGroup().addTo(map);

  // --------- HASHTAG ---------
  //var allMapLayers = {'mpw':mapy_winter, '4um':baselayer2, 'otm':baselayer, 'igm1':igm25k_min, 'igm2':igm25k_reg, 'gh':googleHybrid, 'tc':tcdlayer, 'hs':hillshlayer, 'sc':slopelayer, 'sp':pistelayer, 'uf':userFeatures};
  var allMapLayers = {'lf':gpsFeatures, 'mpw':mapy_winter, '4um':baselayer2, 'otm':baselayer, 'gh':googleHybrid, 'tc':tcdlayer, 'hs':hillshlayer, 'sc':slopelayer, 'sp':pistelayer, 'uf':userFeatures};
  L.hash(map, allMapLayers);
  // L.Permalink.setup(map);

  //----------- INFO -----------
  L.easyButton({
    states: [{
      icon: '',
      title: 'Go to description page',
      onClick: function(){
        //var export = document.createElement('export');
        // Extract GeoJson from featureGroup
        window.open('https://edrap.github.io/abruzzomap', '_blank');
      }
    }]
  }).addTo(map);

  //// ----------- PRINT MAP -----------
  //L.control.browserPrint().addTo(map);

  // ----------- LOAD FEATURES -----------
  var load = L.Control.fileLayerLoad({
    // Allows you to use a customized version of L.geoJson.
    // For example if you are using the Proj4Leaflet leaflet plugin,
    // you can pass L.Proj.geoJson and load the files into the
    // L.Proj.GeoJson instead of the L.geoJson.
    layer: L.geoJson,
    // See http://leafletjs.com/reference.html#geojson-options
    //layerOptions: {style: {color:'red'}},
    layerOptions: {style: {color:"red",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2}},
    // Add to map after loading (default: true) ?
    addToMap: true,
    // File size limit in kb (default: 1024) ?
    fileSizeLimit: 1024,
    // Restrict accepted file formats (default: .geojson, .kml, and .gpx) ?
    formats: [
      '.geojson',
      '.gpx',
      '.kml'
    ]
  }).addTo(map);
  load.loader.on('data:loaded', function (e) {
    // Add to map layer switcher
    userFeatures.addLayer(e.layer);
  });

  // ----------- DRAW FEATURES -----------
  var drawControl = new L.Control.Draw({
    edit: {
      featureGroup: userFeatures
    }
  }).addTo(map);
  map.on('draw:created', function(e) {
    // Each time a feaute is created, it's added to the over arching feature group
    userFeatures.addLayer(e.layer);
  });

  // ----------- DOWNLOAD FEATURES -----------
  L.easyButton({
    states: [{
      icon: '',
      title: 'Export features to GPX',
      onClick: function(e) {
        // Extract GeoJson from featureGroup
        var data = userFeatures.toGeoJSON();
        // var kml = tokml(data);
        var gpx = togpx(data);
        // Stringify GPX, KML, GeoJson
        var convertedData = 'application/gpx+xml;charset=utf-8,' + encodeURIComponent(gpx);
        // var convertedData = 'application/xml;charset=utf-8,' + encodeURIComponent(kml);
        // var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));
        // Create export
        var exp_feat = document.createElement('a');
        exp_feat.setAttribute('href', 'data:' + convertedData);
        // exp_feat.setAttribute('download', 'data.geojson');
        exp_feat.setAttribute('download', 'data.gpx');
        exp_feat.style.display = 'none';
        document.body.appendChild(exp_feat);
        exp_feat.click();
        document.body.removeChild(exp_feat);
      }
    }]
  }).addTo(map);

  // ----------- FULLSCREEN -----------
  L.control.fullscreen({position: 'topright'}).addTo(map);

  //----------- GET GPS LOCATION -----------
  L.control.locate({
    keepCurrentZoomLevel: 'false',
    position: 'topright',
    layer: gpsFeatures,
    setView: 'always'
  }).addTo(map);

  // ----------- SEARCH BOX -----------
  var geocoder = new L.Control.geocoder({position: "topright", collapsed: true, showResultIcons: false});
  map.addControl(geocoder);
  geocoder.markGeocode = function(result) {
    map.fitBounds(result.bbox);
  };

  // ----------- LAYERS ----------
  // var baseMaps = {'Mapy Winter':mapy_winter, '4UMaps':baselayer2, 'OpenTopoMap':baselayer, 'Igm25k Min':igm25k_min, 'Igm25k Reg':igm25k_reg, 'Google Hybrid':googleHybrid};
  var baseMaps = {'Mapy Winter':mapy_winter, '4UMaps':baselayer2, 'OpenTopoMap':baselayer, 'Google Hybrid':googleHybrid};
  var overlayMaps = {'Tree cover':tcdlayer, 'Hillshade':hillshlayer, 'Slope class':slopelayer, 'Ski Piste':pistelayer, 'User features':userFeatures};
  L.control.layers(baseMaps, overlayMaps, {position: 'topright'}).addTo(map);

  // ----------- SCALE BAR -----------
  L.control.scale({imperial: false, position: 'bottomright'}).addTo(map);

  // ----------- LEGEND -----------
  function getColor(d) {
    return d > 45   ? '#b546fc' :
    d > 40   ? '#fb322f' :
    d > 35   ? '#fdac2b' :
    d > 30   ? '#efe72e' :
    '#FFEDA0';
  }
  function getPercent(d) {
    return d > 80   ? '#246627' :
    d > 60   ? '#3a8240' :
    d > 40   ? '#55a05a' :
    d > 20   ? '#78c376' :
    d > 1    ? '#a3ec9a' :
    '#FFEDA0';
  }
  var legend = L.control({position: 'bottomright'});
  legend.onAdd = function (map) {
    var div = L.DomUtil.create('div', 'info legend'),
    grades = [30, 35, 40, 45];
    labels = [' Slope class '];
    // loop through our density intervals and generate a label with a colored square for each interval
    div.innerHTML = labels + '
';
    for (var i = 0; i < grades.length; i++) {
      div.innerHTML +=
      ' ' +
      grades[i] + '°' + (grades[i + 1] ? '–' + grades[i + 1] + '°' + '
' : '+');
    }
    // percent = [1, 20, 40, 60, 80];
    // labels = [' Tree density '];
    //   // loop through our density intervals and generate a label with a colored square for each interval
    //   div.innerHTML += '
' + '
' + labels + '
';
    //   for (var i = 0; i < percent.length; i++) {
    //     div.innerHTML +=
    //     ' ' +
    //     percent[i] + '%' + (percent[i + 1] ? '–' + percent[i + 1] + '%' + '
' : '+');
    //   }
    return div;
  };
  legend.addTo(map);

})
<commit_msg>Update abruzzomap.js<commit_after>$(document).ready(function () {

  // ----------- MAPS LAYERS -----------
  // var mappos = L.Permalink.getMapLocation();
  // var map = L.map('map', {
  //   center: mappos.center,
  //   zoom: mappos.zoom
  // });

  var map = L.map('map', {
    center: [42.3469,13.39817],
    zoom: 14
  });

  var mapy_winter = L.tileLayer('https://mapserver.mapy.cz/winter-m/{z}-{x}-{y}', {
    maxZoom: 16,
    maxNativeZoom: 18,
    minNativeZoom: 3,
    minZoom: 10,
    attribution: '© Mapy'
  }).addTo(map);

  var baselayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 17,
    minNativeZoom: 5,
    minZoom: 10,
    attribution: '© OpenTopoMap'
  });

  var baselayer2 = L.tileLayer('https://4umaps.com/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 15,
    minNativeZoom: 2,
    minZoom: 10,
    attribution: '© 4UMaps'
  });

  var googleHybrid = L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}',{
    maxZoom: 16,
    maxNativeZoom: 20,
    minNativeZoom: 3,
    minZoom: 10,
    subdomains:['mt0','mt1','mt2','mt3'],
    attribution: '© Google'
  });

  // var igm25k_min = L.tileLayer('https://www.meteoaquilano.it/abruzzo/igm25k_min_abr/{z}/{x}/{y}.png', {
  //   //transparent: true,
  //   maxZoom: 16,
  //   maxNativeZoom: 16,
  //   minNativeZoom: 12,
  //   minZoom: 10,
  //   tms: true,
  //   attribution: '© Geoportale Nazionale'
  // });
  //
  // var igm25k_reg = L.tileLayer('https://www.meteoaquilano.it/abruzzo/igm25k_reg_abr/{z}/{x}/{y}.png', {
  //   //transparent: true,
  //   maxZoom: 16,
  //   maxNativeZoom: 16,
  //   minNativeZoom: 10,
  //   minZoom: 10,
  //   tms: true,
  //   attribution: '© Geoportale Regione Abruzzo'
  // });

  // var igm25k_reg = L.tileLayer.wms('http://geocatalogo.regione.abruzzo.it/erdas-iws/ogc/wms/?', {
  //   layers: 'Mosaici_UTM-WGS84_IGM25k_WGS84.ecw',
  //   maxZoom: 16,
  //   minZoom: 10,
  //   attribution: '© Geoportale Regione Abruzzo'
  // }).addTo(map);

  var tcdlayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/tcd_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.3,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Copernicus'
  }).addTo(map);

  var hillshlayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/ingv_dem10m_hillshade_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.2,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Tinitaly'
  }).addTo(map);

  var slopelayer = L.tileLayer('https://www.meteoaquilano.it/abruzzo/ingv_dem10m_slope_abr/{z}/{x}/{y}.png', {
    //transparent: true,
    opacity: 0.4,
    maxZoom: 16,
    maxNativeZoom: 16,
    minNativeZoom: 10,
    minZoom: 10,
    tms: true,
    attribution: '© Tinitaly'
  }).addTo(map);

  var avalanches = L.featureGroup();
  // Load kml file
  fetch('https://edrap.github.io/leaflet/shapefiles/Mappa storica delle valanghe dal 1957 al 2013.kml')
      .then(res => res.text())
      .then(kmltext => {
          // Create new kml overlay
          const parser = new DOMParser();
          const kml = parser.parseFromString(kmltext, 'text/xml');
          const track = new L.KML(kml);
          avalanches.addLayer(track);

          // Adjust map to show the kml
          //const bounds = track.getBounds();
          //map.fitBounds(bounds);
      });
  avalanches.getAttribution = function() { return '© Opendata Regione Abruzzo'; };
  avalanches.addTo(map);

  var pistelayer = L.tileLayer('https://www.opensnowmap.org/tiles-pistes/{z}/{x}/{y}.png', {
    maxZoom: 16,
    maxNativeZoom: 18,
    minNativeZoom: 3,
    minZoom: 10,
    attribution: '© OpenSnowMap'
  }).addTo(map);

  var userFeatures = L.featureGroup().addTo(map);
  var gpsFeatures = L.featureGroup().addTo(map);

  // --------- HASHTAG ---------
  //var allMapLayers = {'mpw':mapy_winter, '4um':baselayer2, 'otm':baselayer, 'igm1':igm25k_min, 'igm2':igm25k_reg, 'gh':googleHybrid, 'tc':tcdlayer, 'hs':hillshlayer, 'sc':slopelayer, 'sp':pistelayer, 'uf':userFeatures};
  var allMapLayers = {'lf':gpsFeatures, 'mpw':mapy_winter, '4um':baselayer2, 'otm':baselayer, 'gh':googleHybrid, 'tc':tcdlayer, 'hs':hillshlayer, 'sc':slopelayer, 'av':avalanches, 'sp':pistelayer, 'uf':userFeatures};
  L.hash(map, allMapLayers);
  // L.Permalink.setup(map);

  //----------- INFO -----------
  L.easyButton({
    states: [{
      icon: '',
      title: 'Go to description page',
      onClick: function(){
        //var export = document.createElement('export');
        // Extract GeoJson from featureGroup
        window.open('https://edrap.github.io/abruzzomap', '_blank');
      }
    }]
  }).addTo(map);

  //// ----------- PRINT MAP -----------
  //L.control.browserPrint().addTo(map);

  // ----------- LOAD FEATURES -----------
  var load = L.Control.fileLayerLoad({
    // Allows you to use a customized version of L.geoJson.
    // For example if you are using the Proj4Leaflet leaflet plugin,
    // you can pass L.Proj.geoJson and load the files into the
    // L.Proj.GeoJson instead of the L.geoJson.
    layer: L.geoJson,
    // See http://leafletjs.com/reference.html#geojson-options
    //layerOptions: {style: {color:'red'}},
    layerOptions: {style: {color:"red",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2}},
    // Add to map after loading (default: true) ?
    addToMap: true,
    // File size limit in kb (default: 1024) ?
    fileSizeLimit: 1024,
    // Restrict accepted file formats (default: .geojson, .kml, and .gpx) ?
    formats: [
      '.geojson',
      '.gpx',
      '.kml'
    ]
  }).addTo(map);
  load.loader.on('data:loaded', function (e) {
    // Add to map layer switcher
    userFeatures.addLayer(e.layer);
  });

  // ----------- DRAW FEATURES -----------
  var drawControl = new L.Control.Draw({
    edit: {
      featureGroup: userFeatures
    }
  }).addTo(map);
  map.on('draw:created', function(e) {
    // Each time a feaute is created, it's added to the over arching feature group
    userFeatures.addLayer(e.layer);
  });

  // ----------- DOWNLOAD FEATURES -----------
  L.easyButton({
    states: [{
      icon: '',
      title: 'Export features to GPX',
      onClick: function(e) {
        // Extract GeoJson from featureGroup
        var data = userFeatures.toGeoJSON();
        // var kml = tokml(data);
        var gpx = togpx(data);
        // Stringify GPX, KML, GeoJson
        var convertedData = 'application/gpx+xml;charset=utf-8,' + encodeURIComponent(gpx);
        // var convertedData = 'application/xml;charset=utf-8,' + encodeURIComponent(kml);
        // var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));
        // Create export
        var exp_feat = document.createElement('a');
        exp_feat.setAttribute('href', 'data:' + convertedData);
        // exp_feat.setAttribute('download', 'data.geojson');
        exp_feat.setAttribute('download', 'data.gpx');
        exp_feat.style.display = 'none';
        document.body.appendChild(exp_feat);
        exp_feat.click();
        document.body.removeChild(exp_feat);
      }
    }]
  }).addTo(map);

  // ----------- FULLSCREEN -----------
  L.control.fullscreen({position: 'topright'}).addTo(map);

  //----------- GET GPS LOCATION -----------
  L.control.locate({
    keepCurrentZoomLevel: 'false',
    position: 'topright',
    layer: gpsFeatures,
    setView: 'always'
  }).addTo(map);

  // ----------- SEARCH BOX -----------
  var geocoder = new L.Control.geocoder({position: "topright", collapsed: true, showResultIcons: false});
  map.addControl(geocoder);
  geocoder.markGeocode = function(result) {
    map.fitBounds(result.bbox);
  };

  // ----------- LAYERS ----------
  // var baseMaps = {'Mapy Winter':mapy_winter, '4UMaps':baselayer2, 'OpenTopoMap':baselayer, 'Igm25k Min':igm25k_min, 'Igm25k Reg':igm25k_reg, 'Google Hybrid':googleHybrid};
  var baseMaps = {'Mapy Winter':mapy_winter, '4UMaps':baselayer2, 'OpenTopoMap':baselayer, 'Google Hybrid':googleHybrid};
  var overlayMaps = {'Tree cover':tcdlayer, 'Hillshade':hillshlayer, 'Slope class':slopelayer, 'Avalanches':avalanches, 'Ski Piste':pistelayer, 'User features':userFeatures};
  L.control.layers(baseMaps, overlayMaps, {position: 'topright'}).addTo(map);

  // ----------- SCALE BAR -----------
  L.control.scale({imperial: false, position: 'bottomright'}).addTo(map);

  // ----------- LEGEND -----------
  function getColor(d) {
    return d > 45   ? '#b546fc' :
    d > 40   ? '#fb322f' :
    d > 35   ? '#fdac2b' :
    d > 30   ? '#efe72e' :
    '#FFEDA0';
  }
  function getPercent(d) {
    return d > 80   ? '#246627' :
    d > 60   ? '#3a8240' :
    d > 40   ? '#55a05a' :
    d > 20   ? '#78c376' :
    d > 1    ? '#a3ec9a' :
    '#FFEDA0';
  }
  var legend = L.control({position: 'bottomright'});
  legend.onAdd = function (map) {
    var div = L.DomUtil.create('div', 'info legend'),
    grades = [30, 35, 40, 45];
    labels = [' Slope class '];
    // loop through our density intervals and generate a label with a colored square for each interval
    div.innerHTML = labels + '
';
    for (var i = 0; i < grades.length; i++) {
      div.innerHTML +=
      ' ' +
      grades[i] + '°' + (grades[i + 1] ? '–' + grades[i + 1] + '°' + '
' : '+');
    }
    // percent = [1, 20, 40, 60, 80];
    // labels = [' Tree density '];
    //   // loop through our density intervals and generate a label with a colored square for each interval
    //   div.innerHTML += '
' + '
' + labels + '
';
    //   for (var i = 0; i < percent.length; i++) {
    //     div.innerHTML +=
    //     ' ' +
    //     percent[i] + '%' + (percent[i + 1] ? '–' + percent[i + 1] + '%' + '
' : '+');
    //   }
    return div;
  };
  legend.addTo(map);

})
<|endoftext|>


<commit_before>/* eslint-disable */
/**
 * Mnemonist KDTree
 * =================
 *
 * Low-level JavaScript implementation of a k-dimensional tree.
 */
var forEach = require('obliterator/foreach');
var typed = require('./utils/typed-arrays.js');

function buildTree(dimensions, data) {
  var axes = new Array(dimensions),
      sortedAxes = new Array(dimensions),
      values = new Array(l),
      axis,
      sortedAxis;

  var l = data.length;

  var PointerArray = typed.getPointerArray(l);

  var d, i, row;

  var f = true;

  for (d = 0; d < dimensions; d++) {
    axis = new Float64Array(l);
    sortedAxis = new PointerArray(l);

    for (i = 0; i < l; i++) {
      row = data[i];
      axis[i] = row[1][d];
      sortedAxis[i] = i;

      if (f)
        values[i] = row[0];
    }

    f = false;
    axes[d] = axis;
    sortedAxes[d] = sortedAxis.sort(function(a, b) {
      a = axis[a];
      b = axis[b];

      if (a < b)
        return -1;

      if (a > b)
        return 1;

      return 0;
    });
  }

  return {axes: axes, sortedAxes: sortedAxes, values: values};
}

/**
 * KDTree.
 *
 * @constructor
 */
function KDTree(dimensions, data) {
  var result = buildTree(dimensions, data);

  this.dimensions = dimensions;

  this.axes = result.axes;
  this.sortedAxes = result.sortedAxes;
  this.values = result.values;

  this.size = this.values.length;
}

KDTree.prototype.nearestNeighbor = function(point) {
  var bestDistance = -Infinity,
      best = null;

  var visited = 0;

  var stack = [[0, 0, this.size - 1]],
      step,
      depth,
      start,
      stop,
      length,
      half,
      median,
      pivot,
      d;

  while (stack.length !== 0) {
    visited++;

    step = stack.pop();
    depth = step[0];
    start = step[1];
    stop = step[2];

    d = depth % this.dimensions;

    length = stop - start + 1;
    half = (length / 2) | 0;
    median = start + half;
    pivot = this.sortedAxes[d][median];

    console.log(this.values[pivot]);
  }
};

// if (!root) return;
// d = dist(root, nd, dim);
// dx = root->x[i] - nd->x[i];
// dx2 = dx * dx;

// visited ++;

// if (!*best || d < *best_dist) {
//     *best_dist = d;
//     *best = root;
// }

// /* if chance of exact match is high */
// if (!*best_dist) return;

// if (++i >= dim) i = 0;

// nearest(dx > 0 ? root->left : root->right, nd, i, dim, best, best_dist);
// if (dx2 >= *best_dist) return;
// nearest(dx > 0 ? root->right : root->left, nd, i, dim, best, best_dist);

/**
 * Convenience known methods.
 */
KDTree.prototype.inspect = function() {
  var dummy = new Map();

  dummy.dimensions = this.dimensions;

  Object.defineProperty(dummy, 'constructor', {
    value: KDTree,
    enumerable: false
  });

  return dummy;
};

if (typeof Symbol !== 'undefined')
  KDTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = KDTree.prototype.inspect;

/**
 * Static @.from function taking an abitrary iterable & converting it into
 * a structure.
 *
 * @param  {Iterable} iterable - Target iterable.
 * @return {KDTree}
 */
KDTree.from = function(iterable) {

};

/**
 * Exporting.
 */
module.exports = KDTree;

if (require.main === module) {
  var D = [
    ['zero', [2, 3]],
    ['one', [5, 4]],
    ['two', [9, 6]],
    ['three', [4, 7]],
    ['four', [8, 1]],
    ['five', [7, 2]]
  ];

  var tree = new KDTree(2, D);

  console.log(tree);
  console.log(tree.nearestNeighbor([3, 4]));
}
<commit_msg>Amending<commit_after>/* eslint-disable */
/**
 * Mnemonist KDTree
 * =================
 *
 * Low-level JavaScript implementation of a k-dimensional tree.
 */
var forEach = require('obliterator/foreach');
var typed = require('./utils/typed-arrays.js');

function buildTree(dimensions, data) {
  var axes = new Array(dimensions),
      sortedAxes = new Array(dimensions),
      values = new Array(l),
      axis,
      sortedAxis;

  var l = data.length;

  var PointerArray = typed.getPointerArray(l);

  var d, i, row;

  var f = true;

  for (d = 0; d < dimensions; d++) {
    axis = new Float64Array(l);
    sortedAxis = new PointerArray(l);

    for (i = 0; i < l; i++) {
      row = data[i];
      axis[i] = row[1][d];
      sortedAxis[i] = i;

      if (f)
        values[i] = row[0];
    }

    f = false;
    axes[d] = axis;
    sortedAxes[d] = sortedAxis.sort(function(a, b) {
      a = axis[a];
      b = axis[b];

      if (a < b)
        return -1;

      if (a > b)
        return 1;

      return 0;
    });
  }

  return {axes: axes, sortedAxes: sortedAxes, values: values};
}

/**
 * KDTree.
 *
 * @constructor
 */
function KDTree(dimensions, data) {
  var result = buildTree(dimensions, data);

  this.dimensions = dimensions;

  this.axes = result.axes;
  this.sortedAxes = result.sortedAxes;
  this.values = result.values;

  this.size = this.values.length;
}

KDTree.prototype.nearestNeighbor = function(point) {
  var bestDistance = -Infinity,
      best = null;

  var visited = 0;

  var stack = [[0, 0, this.size - 1]],
      step,
      depth,
      start,
      stop,
      length,
      half,
      median,
      pivot,
      d;

  while (stack.length !== 0) {
    visited++;

    step = stack.pop();
    depth = step[0];
    start = step[1];
    stop = step[2];

    d = depth % this.dimensions;

    length = stop - start + 1;

    // TODO: bitwise opt.
    half = (length / 2) | 0;
    median = start + half;
    pivot = this.sortedAxes[d][median];

    console.log(this.values[pivot]);
  }
};

/**
 * Convenience known methods.
 */
KDTree.prototype.inspect = function() {
  var dummy = new Map();

  dummy.dimensions = this.dimensions;

  Object.defineProperty(dummy, 'constructor', {
    value: KDTree,
    enumerable: false
  });

  return dummy;
};

if (typeof Symbol !== 'undefined')
  KDTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = KDTree.prototype.inspect;

/**
 * Static @.from function taking an abitrary iterable & converting it into
 * a structure.
 *
 * @param  {Iterable} iterable - Target iterable.
 * @return {KDTree}
 */
KDTree.from = function(iterable) {

};

/**
 * Exporting.
 */
module.exports = KDTree;

if (require.main === module) {
  var D = [
    ['zero', [2, 3]],
    ['one', [5, 4]],
    ['two', [9, 6]],
    ['three', [4, 7]],
    ['four', [8, 1]],
    ['five', [7, 2]]
  ];

  var tree = new KDTree(2, D);

  console.log(tree);
  console.log(tree.nearestNeighbor([3, 4]));
}
<|endoftext|>


<commit_before>var fbgraph = require('fbgraph'),
    async   = require('async');

var removeMilliseconds = function(timestampNum) {
    var timestamp = timestampNum.toString();
    var charsToReturn = timestamp.length - 3;

    return timestamp.substr(0, charsToReturn);
};

var parseFacebookData = function(data, keyword, callback) {
    var parsedData = [];

    var processData = function(postObj, cb) {
        var userPostIds = postObj.id.split('_'),
            userId = userPostIds[0],
            postId = userPostIds[1];

        fbgraph.get(userId, {fields: 'username'}, function(err, reply){
            if(!err) {
                parsedData.push({
                    userId:     userId,
                    userName:   reply.username,
                    postId:     postId,
                    link:      'http://www.facebook.com/' + reply.username + '/posts/' + postId,
                    time:       Date.parse(postObj.created_time),
                    keyword:    keyword,
                    type:       'facebook'
                });
            }
            cb();
        });

    };

    async.each(data, processData, function(err) {
        callback(parsedData);
    });
};

function FacebookSearch(keywords, fbKeys) {
    this.keywords = keywords;
    this.accessToken = fbKeys.appId + '|' + fbKeys.appSecret;
}

FacebookSearch.prototype.getFbResults = function(addLinksCb, saveKeywordsCb, numPosts) {
    var self = this;

    var fetchPosts = function(keywordObj, cb) {
        var searchParams = {
            q:              keywordObj.keyword,
            type:           'post',
            fields:         'id, created_time',
            since:          removeMilliseconds(keywordObj.lastChecked),
            access_token:   self.accessToken
        };

        if(numPosts) {
            searchParams.limit = numPosts;
        }

        fbgraph.search(searchParams, function(err, reply) {
            if(err) {
                addLinksCb(err);
                cb();
                return;
            }

            parseFacebookData(reply.data, keywordObj.keyword, function(links){
                keywordObj.lastChecked = Date.now();
                addLinksCb(null, links);
                cb();
            });
        });
    };

    async.each(this.keywords, fetchPosts, function(err) {
        saveKeywordsCb('facebook', self.keywords);
    });
};

module.exports = FacebookSearch;<commit_msg>undefined check for fb username<commit_after>var fbgraph = require('fbgraph'),
    async   = require('async');

var removeMilliseconds = function(timestampNum) {
    var timestamp = timestampNum.toString();
    var charsToReturn = timestamp.length - 3;

    return timestamp.substr(0, charsToReturn);
};

var parseFacebookData = function(data, keyword, callback) {
    var parsedData = [];

    var processData = function(postObj, cb) {
        var userPostIds = postObj.id.split('_'),
            userId = userPostIds[0],
            postId = userPostIds[1];

        fbgraph.get(userId, {fields: 'username'}, function(err, reply){
            if(!err) {
                parsedData.push({
                    userId:     userId,
                    userName:   reply.username ? reply.username : "undefined",
                    postId:     postId,
                    link:      'http://www.facebook.com/' + reply.username + '/posts/' + postId,
                    time:       Date.parse(postObj.created_time),
                    keyword:    keyword,
                    type:       'facebook'
                });
            }
            cb();
        });

    };

    async.each(data, processData, function(err) {
        callback(parsedData);
    });
};

function FacebookSearch(keywords, fbKeys) {
    this.keywords = keywords;
    this.accessToken = fbKeys.appId + '|' + fbKeys.appSecret;
}

FacebookSearch.prototype.getFbResults = function(addLinksCb, saveKeywordsCb, numPosts) {
    var self = this;

    var fetchPosts = function(keywordObj, cb) {
        var searchParams = {
            q:              keywordObj.keyword,
            type:           'post',
            fields:         'id, created_time',
            since:          removeMilliseconds(keywordObj.lastChecked),
            access_token:   self.accessToken
        };

        if(numPosts) {
            searchParams.limit = numPosts;
        }

        fbgraph.search(searchParams, function(err, reply) {
            if(err) {
                addLinksCb(err);
                cb();
                return;
            }

            parseFacebookData(reply.data, keywordObj.keyword, function(links){
                keywordObj.lastChecked = Date.now();
                addLinksCb(null, links);
                cb();
            });
        });
    };

    async.each(this.keywords, fetchPosts, function(err) {
        saveKeywordsCb('facebook', self.keywords);
    });
};

module.exports = FacebookSearch;<|endoftext|>


<commit_before>/*

Passport-Live is a modern web app for schools that helps them manage passes.
    Copyright (C) 2017  Joseph Hassell

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

email: hi@josephhassell.com

*/
let polymer = require("@polymer/polymer/polymer-element");
let view = require("./pass-list.template.html");

/** COMPONENTS **/
require("@polymer/paper-listbox/paper-listbox.js");
require("../pass/pass.js");
/**
 * Polymer Element that displays and fetches a list of <passport-pass> elements.
 * @class
 * @property {Object[]} [passes] - The list of raw pass objects
 * @property {Boolean} [fetchPasses=true] - if false, the element will NOT fetch the passes from the server automatically
 * @property {String} [userId] - The ID of the
 * @example
 * <passport-pass-list fetchPasses></passport-pass-list>
 */
class PassportPassList extends polymer.Element {
    static get template() {
        return view;
    }
    constructor() {
        super();
    }
    static get properties() {
        return {
            //Raw pass object array
            passes: {
                type: Array,
                notify: true
            },
            fetchPasses: {
                type: Boolean,
                value: true
            },
            userId: {
                type: String,
                notify: true
            },
            filter: {
                type: Object
            }
        };
    }

}

module.exports = PassportPassList;
customElements.define("passport-pass-list", module.exports);<commit_msg>Changed var name <commit_after>/*

Passport-Live is a modern web app for schools that helps them manage passes.
    Copyright (C) 2017  Joseph Hassell

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

email: hi@josephhassell.com

*/
let polymer = require("@polymer/polymer/polymer-element");
let view = require("./pass-list.template.html");

/** COMPONENTS **/
require("@polymer/paper-listbox/paper-listbox.js");
require("../pass/pass.js");
/**
 * Polymer Element that displays and fetches a list of <passport-pass> elements.
 * @class
 * @property {Object[]} [passes] - The list of raw pass objects
 * @property {Boolean} [noAutoFetch=false] - if true, the element will NOT fetch the passes from the server automatically
 * @property {String} [userId] - The ID of the
 * @example
 * <passport-pass-list noAutoFetch></passport-pass-list>
 */
class PassportPassList extends polymer.Element {
    static get template() {
        return view;
    }
    constructor() {
        super();
    }
    static get properties() {
        return {
            //Raw pass object array
            passes: {
                type: Array,
                notify: true
            },
            noAutoFetch: {
                type: Boolean,
                value: false
            },
            userId: {
                type: String,
                notify: true
            },
            filter: {
                type: Object
            }
        };
    }

}

module.exports = PassportPassList;
customElements.define("passport-pass-list", module.exports);<|endoftext|>


<commit_before>// @flow

import type { ThreadStore } from 'lib/types/thread-types';
import type { EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { NavInfo } from './navigation-setup';
import type { PersistState } from 'redux-persist/src/types';
import {
  type NotifPermissionAlertInfo,
  defaultNotifPermissionAlertInfo,
  recordNotifPermissionAlertActionType,
} from './push/alerts';
import type { NavigationStateRoute, NavigationRoute } from 'react-navigation';
import {
  type PingTimestamps,
  defaultPingTimestamps,
} from 'lib/types/ping-types';
import type { ServerRequest } from 'lib/types/request-types';
import {
  type CalendarFilter,
  defaultCalendarFilters,
} from 'lib/types/filter-types';

import React from 'react';
import invariant from 'invariant';
import thunk from 'redux-thunk';
import { createStore as defaultCreateStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistStore, persistReducer } from 'redux-persist';
import PropTypes from 'prop-types';
import { NavigationActions, StackActions } from 'react-navigation';
import {
  createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import { AppState as NativeAppState } from 'react-native';

import baseReducer from 'lib/reducers/master-reducer';
import { newSessionID } from 'lib/selectors/session-selectors';
import { notificationPressActionType } from 'lib/shared/notif-utils';
import { sendMessageActionTypes } from 'lib/actions/message-actions';
import { pingActionTypes } from 'lib/actions/ping-actions';
import { reduxLoggerMiddleware } from 'lib/utils/redux-logger';

import { MessageListRouteName } from './chat/message-list.react';
import { activeThreadSelector } from './selectors/nav-selectors';
import {
  handleURLActionType,
  navigateToAppActionType,
  defaultNavInfo,
  reduceNavInfo,
  removeScreensFromStack,
} from './navigation-setup';
import {
  recordAndroidNotificationActionType,
  clearAndroidNotificationActionType,
  reduceThreadIDsToNotifIDs,
} from './push/android';
import { persistConfig, setPersistor } from './persist';
import {
  defaultURLPrefix,
  natServer,
  setCustomServer,
} from './utils/url-utils';
import {
  assertNavigationRouteNotLeafNode,
  currentLeafRoute,
  findRouteIndexWithKey,
} from './utils/navigation-utils';
import { ComposeThreadRouteName } from './chat/compose-thread.react';
import reactotron from './reactotron';

const createStore = reactotron
  ? reactotron.createStore
  : defaultCreateStore;

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  sessionID: string,
  entryStore: EntryStore,
  lastUserInteraction: {[section: string]: number},
  threadStore: ThreadStore,
  userInfos: {[id: string]: UserInfo},
  messageStore: MessageStore,
  drafts: {[key: string]: string},
  updatesCurrentAsOf: number,
  loadingStatuses: {[key: string]: {[idx: number]: LoadingStatus}},
  pingTimestamps: PingTimestamps,
  activeServerRequests: $ReadOnlyArray<ServerRequest>,
  calendarFilters: $ReadOnlyArray<CalendarFilter>,
  cookie: ?string,
  deviceToken: ?string,
  urlPrefix: string,
  customServer: ?string,
  threadIDsToNotifIDs: {[threadID: string]: string[]},
  notifPermissionAlertInfo: NotifPermissionAlertInfo,
  messageSentFromRoute: $ReadOnlyArray<string>,
  _persist: ?PersistState,
|};

const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  sessionID: newSessionID(),
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
  },
  lastUserInteraction: { sessionReset: Date.now() },
  threadStore: {
    threadInfos: {},
    inconsistencyResponses: [],
  },
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
    currentAsOf: 0,
  },
  drafts: {},
  updatesCurrentAsOf: 0,
  loadingStatuses: {},
  pingTimestamps: defaultPingTimestamps,
  activeServerRequests: [],
  calendarFilters: defaultCalendarFilters,
  cookie: null,
  deviceToken: null,
  urlPrefix: defaultURLPrefix(),
  customServer: natServer,
  threadIDsToNotifIDs: {},
  notifPermissionAlertInfo: defaultNotifPermissionAlertInfo,
  messageSentFromRoute: [],
  _persist: null,
}: AppState);

function chatRouteFromNavInfo(navInfo: NavInfo): NavigationStateRoute {
  const navState = navInfo.navigationState;
  const appRoute = assertNavigationRouteNotLeafNode(navState.routes[0]);
  return assertNavigationRouteNotLeafNode(appRoute.routes[1]);
}

function reducer(state: AppState = defaultState, action: *) {
  const oldState = state;
  let navInfo = reduceNavInfo(state, action);

  if (navInfo && navInfo !== state.navInfo) {
    const chatRoute = chatRouteFromNavInfo(navInfo);
    const currentChatSubroute = currentLeafRoute(chatRoute);
    if (currentChatSubroute.routeName === ComposeThreadRouteName) {
      const oldChatRoute = chatRouteFromNavInfo(state.navInfo);
      const oldRouteIndex = findRouteIndexWithKey(
        oldChatRoute,
        currentChatSubroute.key,
      );
      const oldNextRoute = oldChatRoute.routes[oldRouteIndex + 1];
      if (
        oldNextRoute &&
        state.messageSentFromRoute.includes(oldNextRoute.key)
      ) {
        // This indicates that the user went to the compose thread screen, then
        // saw that a thread already existed for the people they wanted to
        // contact, and sent a message to that thread. We are now about to
        // navigate back to that compose thread screen, but instead, since the
        // user's intent has ostensibly already been satisfied, we will pop up
        // to the screen right before that one.
        const newChatRoute = removeScreensFromStack(
          chatRoute,
          (route: NavigationRoute) => route.key === currentChatSubroute.key
            ? "remove"
            : "keep",
        );

        const appRoute =
          assertNavigationRouteNotLeafNode(navInfo.navigationState.routes[0]);
        const newAppSubRoutes = [ ...appRoute.routes ];
        newAppSubRoutes[1] = newChatRoute;
        const newRootSubRoutes = [ ...navInfo.navigationState.routes ];
        newRootSubRoutes[0] = { ...appRoute, routes: newAppSubRoutes };
        navInfo = {
          startDate: navInfo.startDate,
          endDate: navInfo.endDate,
          navigationState: {
            ...navInfo.navigationState,
            routes: newRootSubRoutes,
          },
        };
      }
    }

    state = {
      navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }
  if (
    action.type === recordAndroidNotificationActionType ||
    action.type === clearAndroidNotificationActionType
  ) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: reduceThreadIDsToNotifIDs(
        state.threadIDsToNotifIDs,
        action,
      ),
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  } else if (action.type === setCustomServer) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: action.payload,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  } else if (action.type === recordNotifPermissionAlertActionType) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: {
        totalAlerts: state.notifPermissionAlertInfo.totalAlerts + 1,
        lastAlertTime: action.payload.time,
      },
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }
  // These action type are handled by reduceNavInfo above
  if (
    action.type === handleURLActionType ||
      action.type === navigateToAppActionType ||
      action.type === notificationPressActionType ||
      action.type === NavigationActions.INIT ||
      action.type === NavigationActions.NAVIGATE ||
      action.type === NavigationActions.BACK ||
      action.type === NavigationActions.SET_PARAMS ||
      action.type === StackActions.POP ||
      action.type === StackActions.POP_TO_TOP ||
      action.type === StackActions.PUSH ||
      action.type === StackActions.RESET ||
      action.type === StackActions.REPLACE ||
      action.type === StackActions.COMPLETE_TRANSITION
  ) {
    return validateState(oldState, state);
  }
  if (action.type === sendMessageActionTypes.started) {
    const chatRoute = chatRouteFromNavInfo(state.navInfo);
    const currentChatSubroute = currentLeafRoute(chatRoute);
    const messageSentFromRoute =
      state.messageSentFromRoute.includes(currentChatSubroute.key)
        ? state.messageSentFromRoute
        : [ ...state.messageSentFromRoute, currentChatSubroute.key];
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute,
      _persist: state._persist,
    };
  }
  return validateState(oldState, baseReducer(state, action));
}

function validateState(oldState: AppState, state: AppState): AppState {
  const activeThread = activeThreadSelector(state);
  if (
    activeThread &&
    NativeAppState.currentState === "active" &&
    state.threadStore.threadInfos[activeThread].currentUser.unread
  ) {
    // Makes sure a currently focused thread is never unread
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: {
        threadInfos: {
          ...state.threadStore.threadInfos,
          [activeThread]: {
            ...state.threadStore.threadInfos[activeThread],
            currentUser: {
              ...state.threadStore.threadInfos[activeThread].currentUser,
              unread: false,
            },
          },
        },
        inconsistencyResponses: state.threadStore.inconsistencyResponses,
      },
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }

  const oldActiveThread = activeThreadSelector(oldState);
  if (
    activeThread &&
    oldActiveThread !== activeThread &&
    state.messageStore.threads[activeThread]
  ) {
    // Update messageStore.threads[activeThread].lastNavigatedTo
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: {
        messages: state.messageStore.messages,
        threads: {
          ...state.messageStore.threads,
          [activeThread]: {
            ...state.messageStore.threads[activeThread],
            lastNavigatedTo: Date.now(),
          },
        },
        currentAsOf: state.messageStore.currentAsOf,
      },
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }

  const chatRoute = chatRouteFromNavInfo(state.navInfo);
  const chatSubrouteKeys = new Set(chatRoute.routes.map(route => route.key));
  const messageSentFromRoute = state.messageSentFromRoute.filter(
    key => chatSubrouteKeys.has(key),
  );
  if (messageSentFromRoute.length !== state.messageSentFromRoute.length) {
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute,
      _persist: state._persist,
    };
  }

  return state;
}

const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
  "root",
  (state: AppState) => state.navInfo.navigationState,
);
const store = createStore(
  persistReducer(persistConfig, reducer),
  defaultState,
  composeWithDevTools(
    applyMiddleware(thunk, reactNavigationMiddleware, reduxLoggerMiddleware),
  ),
);
const persistor = persistStore(store);
setPersistor(persistor);

export {
  store,
};
<commit_msg>[native] Use ... instead of manually listing every property in redux-setup<commit_after>// @flow

import type { ThreadStore } from 'lib/types/thread-types';
import type { EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { NavInfo } from './navigation-setup';
import type { PersistState } from 'redux-persist/src/types';
import {
  type NotifPermissionAlertInfo,
  defaultNotifPermissionAlertInfo,
  recordNotifPermissionAlertActionType,
} from './push/alerts';
import type { NavigationStateRoute, NavigationRoute } from 'react-navigation';
import {
  type PingTimestamps,
  defaultPingTimestamps,
} from 'lib/types/ping-types';
import type { ServerRequest } from 'lib/types/request-types';
import {
  type CalendarFilter,
  defaultCalendarFilters,
} from 'lib/types/filter-types';

import React from 'react';
import invariant from 'invariant';
import thunk from 'redux-thunk';
import { createStore as defaultCreateStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistStore, persistReducer } from 'redux-persist';
import PropTypes from 'prop-types';
import { NavigationActions, StackActions } from 'react-navigation';
import {
  createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import { AppState as NativeAppState } from 'react-native';

import baseReducer from 'lib/reducers/master-reducer';
import { newSessionID } from 'lib/selectors/session-selectors';
import { notificationPressActionType } from 'lib/shared/notif-utils';
import { sendMessageActionTypes } from 'lib/actions/message-actions';
import { pingActionTypes } from 'lib/actions/ping-actions';
import { reduxLoggerMiddleware } from 'lib/utils/redux-logger';

import { MessageListRouteName } from './chat/message-list.react';
import { activeThreadSelector } from './selectors/nav-selectors';
import {
  handleURLActionType,
  navigateToAppActionType,
  defaultNavInfo,
  reduceNavInfo,
  removeScreensFromStack,
} from './navigation-setup';
import {
  recordAndroidNotificationActionType,
  clearAndroidNotificationActionType,
  reduceThreadIDsToNotifIDs,
} from './push/android';
import { persistConfig, setPersistor } from './persist';
import {
  defaultURLPrefix,
  natServer,
  setCustomServer,
} from './utils/url-utils';
import {
  assertNavigationRouteNotLeafNode,
  currentLeafRoute,
  findRouteIndexWithKey,
} from './utils/navigation-utils';
import { ComposeThreadRouteName } from './chat/compose-thread.react';
import reactotron from './reactotron';

const createStore = reactotron
  ? reactotron.createStore
  : defaultCreateStore;

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  sessionID: string,
  entryStore: EntryStore,
  lastUserInteraction: {[section: string]: number},
  threadStore: ThreadStore,
  userInfos: {[id: string]: UserInfo},
  messageStore: MessageStore,
  drafts: {[key: string]: string},
  updatesCurrentAsOf: number,
  loadingStatuses: {[key: string]: {[idx: number]: LoadingStatus}},
  pingTimestamps: PingTimestamps,
  activeServerRequests: $ReadOnlyArray<ServerRequest>,
  calendarFilters: $ReadOnlyArray<CalendarFilter>,
  cookie: ?string,
  deviceToken: ?string,
  urlPrefix: string,
  customServer: ?string,
  threadIDsToNotifIDs: {[threadID: string]: string[]},
  notifPermissionAlertInfo: NotifPermissionAlertInfo,
  messageSentFromRoute: $ReadOnlyArray<string>,
  _persist: ?PersistState,
|};

const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  sessionID: newSessionID(),
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
  },
  lastUserInteraction: { sessionReset: Date.now() },
  threadStore: {
    threadInfos: {},
    inconsistencyResponses: [],
  },
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
    currentAsOf: 0,
  },
  drafts: {},
  updatesCurrentAsOf: 0,
  loadingStatuses: {},
  pingTimestamps: defaultPingTimestamps,
  activeServerRequests: [],
  calendarFilters: defaultCalendarFilters,
  cookie: null,
  deviceToken: null,
  urlPrefix: defaultURLPrefix(),
  customServer: natServer,
  threadIDsToNotifIDs: {},
  notifPermissionAlertInfo: defaultNotifPermissionAlertInfo,
  messageSentFromRoute: [],
  _persist: null,
}: AppState);

function chatRouteFromNavInfo(navInfo: NavInfo): NavigationStateRoute {
  const navState = navInfo.navigationState;
  const appRoute = assertNavigationRouteNotLeafNode(navState.routes[0]);
  return assertNavigationRouteNotLeafNode(appRoute.routes[1]);
}

function reducer(state: AppState = defaultState, action: *) {
  const oldState = state;
  let navInfo = reduceNavInfo(state, action);

  if (navInfo && navInfo !== state.navInfo) {
    const chatRoute = chatRouteFromNavInfo(navInfo);
    const currentChatSubroute = currentLeafRoute(chatRoute);
    if (currentChatSubroute.routeName === ComposeThreadRouteName) {
      const oldChatRoute = chatRouteFromNavInfo(state.navInfo);
      const oldRouteIndex = findRouteIndexWithKey(
        oldChatRoute,
        currentChatSubroute.key,
      );
      const oldNextRoute = oldChatRoute.routes[oldRouteIndex + 1];
      if (
        oldNextRoute &&
        state.messageSentFromRoute.includes(oldNextRoute.key)
      ) {
        // This indicates that the user went to the compose thread screen, then
        // saw that a thread already existed for the people they wanted to
        // contact, and sent a message to that thread. We are now about to
        // navigate back to that compose thread screen, but instead, since the
        // user's intent has ostensibly already been satisfied, we will pop up
        // to the screen right before that one.
        const newChatRoute = removeScreensFromStack(
          chatRoute,
          (route: NavigationRoute) => route.key === currentChatSubroute.key
            ? "remove"
            : "keep",
        );

        const appRoute =
          assertNavigationRouteNotLeafNode(navInfo.navigationState.routes[0]);
        const newAppSubRoutes = [ ...appRoute.routes ];
        newAppSubRoutes[1] = newChatRoute;
        const newRootSubRoutes = [ ...navInfo.navigationState.routes ];
        newRootSubRoutes[0] = { ...appRoute, routes: newAppSubRoutes };
        navInfo = {
          startDate: navInfo.startDate,
          endDate: navInfo.endDate,
          navigationState: {
            ...navInfo.navigationState,
            routes: newRootSubRoutes,
          },
        };
      }
    }

    state = {
      navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }
  if (
    action.type === recordAndroidNotificationActionType ||
    action.type === clearAndroidNotificationActionType
  ) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: reduceThreadIDsToNotifIDs(
        state.threadIDsToNotifIDs,
        action,
      ),
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  } else if (action.type === setCustomServer) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: action.payload,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  } else if (action.type === recordNotifPermissionAlertActionType) {
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: {
        totalAlerts: state.notifPermissionAlertInfo.totalAlerts + 1,
        lastAlertTime: action.payload.time,
      },
      messageSentFromRoute: state.messageSentFromRoute,
      _persist: state._persist,
    };
  }
  // These action type are handled by reduceNavInfo above
  if (
    action.type === handleURLActionType ||
      action.type === navigateToAppActionType ||
      action.type === notificationPressActionType ||
      action.type === NavigationActions.INIT ||
      action.type === NavigationActions.NAVIGATE ||
      action.type === NavigationActions.BACK ||
      action.type === NavigationActions.SET_PARAMS ||
      action.type === StackActions.POP ||
      action.type === StackActions.POP_TO_TOP ||
      action.type === StackActions.PUSH ||
      action.type === StackActions.RESET ||
      action.type === StackActions.REPLACE ||
      action.type === StackActions.COMPLETE_TRANSITION
  ) {
    return validateState(oldState, state);
  }
  if (action.type === sendMessageActionTypes.started) {
    const chatRoute = chatRouteFromNavInfo(state.navInfo);
    const currentChatSubroute = currentLeafRoute(chatRoute);
    const messageSentFromRoute =
      state.messageSentFromRoute.includes(currentChatSubroute.key)
        ? state.messageSentFromRoute
        : [ ...state.messageSentFromRoute, currentChatSubroute.key];
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadStore: state.threadStore,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      updatesCurrentAsOf: state.updatesCurrentAsOf,
      loadingStatuses: state.loadingStatuses,
      pingTimestamps: state.pingTimestamps,
      activeServerRequests: state.activeServerRequests,
      calendarFilters: state.calendarFilters,
      cookie: state.cookie,
      deviceToken: state.deviceToken,
      urlPrefix: state.urlPrefix,
      customServer: state.customServer,
      threadIDsToNotifIDs: state.threadIDsToNotifIDs,
      notifPermissionAlertInfo: state.notifPermissionAlertInfo,
      messageSentFromRoute,
      _persist: state._persist,
    };
  }
  return validateState(oldState, baseReducer(state, action));
}

function validateState(oldState: AppState, state: AppState): AppState {
  const activeThread = activeThreadSelector(state);
  if (
    activeThread &&
    NativeAppState.currentState === "active" &&
    state.threadStore.threadInfos[activeThread].currentUser.unread
  ) {
    // Makes sure a currently focused thread is never unread
    state = {
      ...state,
      threadStore: {
        ...state.threadStore,
        threadInfos: {
          ...state.threadStore.threadInfos,
          [activeThread]: {
            ...state.threadStore.threadInfos[activeThread],
            currentUser: {
              ...state.threadStore.threadInfos[activeThread].currentUser,
              unread: false,
            },
          },
        },
      },
    };
  }

  const oldActiveThread = activeThreadSelector(oldState);
  if (
    activeThread &&
    oldActiveThread !== activeThread &&
    state.messageStore.threads[activeThread]
  ) {
    // Update messageStore.threads[activeThread].lastNavigatedTo
    state = {
      ...state,
      messageStore: {
        ...state.messageStore,
        threads: {
          ...state.messageStore.threads,
          [activeThread]: {
            ...state.messageStore.threads[activeThread],
            lastNavigatedTo: Date.now(),
          },
        },
      },
    };
  }

  const chatRoute = chatRouteFromNavInfo(state.navInfo);
  const chatSubrouteKeys = new Set(chatRoute.routes.map(route => route.key));
  const messageSentFromRoute = state.messageSentFromRoute.filter(
    key => chatSubrouteKeys.has(key),
  );
  if (messageSentFromRoute.length !== state.messageSentFromRoute.length) {
    state = {
      ...state,
      messageSentFromRoute,
    };
  }

  return state;
}

const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
  "root",
  (state: AppState) => state.navInfo.navigationState,
);
const store = createStore(
  persistReducer(persistConfig, reducer),
  defaultState,
  composeWithDevTools(
    applyMiddleware(thunk, reactNavigationMiddleware, reduxLoggerMiddleware),
  ),
);
const persistor = persistStore(store);
setPersistor(persistor);

export {
  store,
};
<|endoftext|>