<commit_before>/*
 * Copyright (c) 2014 gm9
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */

(function(){
    // import
    var igt = gm9.IngressGlyphTools;
    // レベル毎のグリフシーケンス
    // 小文字は読み方が曖昧な(自信がない)グリフ
    var LEVEL_SEQUENCES = [
        //L0
        [
        ],
        //L1
        [
        ],
        //L2
        [
        ],
        //L3
        [
        ],
        //L4
        [
        ],
        //L5
        [
            ["ACCEPT", "HUMAN", "WEAK"],
            ["ADVANCE", "PURE", "TRUTH"],
            ["ALL", "CIVILIZATION", "CHAOS"],
            ["ATTACK", "SHAPERS", "CHAOS"],
            ["AVOID", "DESTINY", "LIE"],
            ["COURAGE", "DESTINY", "REBEL"],
            ["DESTROY", "IMPURE", "TRUTH"],
            ["DISCOVER", "RESISTANCE", "TRUTH"],
            ["DISCOVER", "SHAPERS", "ENLIGHTENMENT"],
            ["DISCOVER", "SHAPERS", "MESSAGE"],
            ["ESCAPE", "IMPURE", "EVOLUTION"],
            ["FOLLOW", "PURE", "JOURNEY"],
            ["HUMAN", "GAIN", "SAFETY"],
            ["IMPROVE", "HUMAN", "SHAPERS"],
            ["INSIDE", "XM", "TRUTH"],
            ["JOURNEY", "INSIDE", "SOUL"],
            ["LIBERATE", "HUMAN", "FUTURE"],
            ["PEACE", "SIMPLE", "JOURNEY"],
            ["PERFECTION", "PATH", "PEACE"],
            ["PURSUE", "PURE", "BODY"],
            ["QUESTION", "HIDE", "TRUTH"],
            ["RETREAT", "SEARCH", "SAFETY"],
            ["WAR", "DESTROY", "FUTURE"],
            ["XM", "NOURISH", "CIVILIZATION"]
        ],
        //L6
        [
        ],
        //L7
        [
            ["ALL", "CHAOS", "INSIDE", "BODY"],
            ["ATTACK", "WEAK", "SHAPERS", "LIE"],
            ["BREATHE", "AGAIN", "JOURNEY", "AGAIN"],
            ["CAPTURE", "FEAR", "DISCOVER", "COURAGE"],
            ["CHANGE", "FUTURE", "CAPTURE", "DESTINY"],
            ["CHANGE", "HUMAN", "POTENTIAL", "USE"],
            ["CHAOS", "barrier", "SHAPERS", "PORTAL"],
            ["CLEAR", "MIND", "OPEN", "MIND"],
            ["CLEARALL", "OPENALL", "DISCOVER", "TRUTH"],
            ["CONTEMPLATE", "COMPLEX", "SHAPERS", "CIVILIZATION"],
            ["COURAGE", "attack", "shapers", "FUTURE"],
            ["CREATE", "DISTANCE", "IMPURE", "PATH"],
            ["CREATE", "FUTURE", "NOT", "WAR"],
            ["DEFEND", "MESSAGE", "ANSWER", "IDEA"],
            ["DESTROY", "DESTINY", "HUMAN", "LIE"],
            ["DETERIORATE", "HUMAN", "WEAK", "REBEL"],
            ["ESCAPE", "SIMPLE", "HUMAN", "FUTURE"],
            ["FOLLOW", "SHAPERS", "PORTAL", "MESSAGE"],
            ["FORGET", "CONFLICT", "ACCEPT", "WAR"],
            ["HARMONY", "PATH", "NOURISH", "PRESENT"],
            ["HELP", "GAIN", "CREATE", "PURSUE"],
            ["HIDE", "IMPURE", "HUMAN", "THOUGHT"],
            ["HUMAN", "HAVE", "IMPURE", "CIVILIZATION"],
            ["HUMAN", "PAST", "PRESENT", "FUTURE"],
            ["HUMAN", "SOUL", "STRONG", "PURE"],
            ["IGNORE", "HUMAN", "CHAOS", "LIE"],
            ["IMPROVE", "BODY", "MIND", "SOUL"],
            ["IMPROVE", "BODY", "PURSUE", "JOURNEY"],
            ["INSIDE", "MIND", "JOURNEY", "PERFECTION"],
            ["JOURNEY", "not", "IMPROVE", "SOUL"],
            ["LEAD", "PURSUE", "REACT", "DEFEND"],
            ["LESS", "SOUL", "MORE", "MIND"],
            ["LESS", "TRUTH", "MORE", "CHAOS"],
            ["LIBERATE", "XM", "PORTAL", "TOGETHER"],
            ["LOSE", "DANGER", "GAIN", "SAFETY"],
            ["PAST", "AGAIN", "PRESENT", "AGAIN"],
            ["PATH", "RESTRAINT", "STRONG", "SAFETY"],
            ["PORTAL", "CHANGE", "CIVILIZATION", "END"],
            ["PORTAL", "DIE", "CIVILIZATION", "DIE"],
            ["QUESTION", "TRUTH", "GAIN", "FUTURE"],
            ["RESTRAINT", "FEAR", "AVOID", "DANGER"],
            ["RESTRAINT", "PATH", "GAIN", "HARMONY"],
            ["SEARCH", "DATA", "DISCOVER", "PATH"],
            ["SEARCH", "TRUTH", "SAVE", "CIVILIZATION"],
            ["SEARCH", "XM", "SAVE", "PORTAL"],
            ["SEE", "TRUTH", "SEE", "FUTURE"],
            ["SEPARATE", "WEAK", "IGNORE", "TRUTH"],
            ["SHAPERS", "CHAOS", "PURE", "HARM"],
            ["SHAPERS", "MESSAGE", "END", "CIVILIZATION"],
            ["SHAPERS", "MIND", "COMPLEX", "HARMONY"],
            ["SHAPERS", "PAST", "PRESENT", "FUTURE"],
            ["SHAPERS", "SEE", "POTENTIAL", "EVOLUTION"],
            ["SIMPLE", "CIVILIZATION", "IMPURE", "WEAK"],
            ["SIMPLE", "MESSAGE", "COMPLEX", "IDEA"],
            ["SIMPLE", "TRUTH", "BREATHE", "NATURE"],
            ["SOUL", "REBEL", "HUMAN", "DIE"],
            ["STAY", "TOGETHER", "DEFEND", "TRUTH"],
            ["STRONG", "IDEA", "PURSUE", "TRUTH"],
            ["STRUGGLE", "DEFEND", "SHAPERS", "DANGER"],
            ["STRUGGLE", "IMPROVE", "HUMAN", "SOUL"],
            ["TOGETHER", "DISCOVER", "HARMONY", "EQUAL"],
            ["TRUTH", "IDEA", "DISCOVER", "XM"],
            ["XM", "DIE", "CHAOS", "LIVE"]
        ],
        //L8
        [
            ["advance", "civilization", "pursue", "shapers", "PATH"],
            ["ANSWER", "QUESTION", "DISCOVER", "DIFFICULT", "TRUTH"],
            ["AVOID", "PERFECTION", "STAY", "HUMAN", "SELF"],
            ["avoid", "chaos", "avoid", "shapers", "LIE"],
            ["avoid", "chaos", "repair", "potential", "WAR"],
            ["breathe", "inside", "XM", "lose", "SELF"],
            ["capture", "portal", "defend", "portal", "COURAGE"],
            ["CHAOS", "WAR", "CONFLICT", "DISCOVER", "PEACE"],
            ["civilization", "die", "new", "civilization", "BEGIN"],
            ["clear", "mind", "liberate", "barrier", "BODY"],
            ["clearall", "idea", "past", "present", "FUTURE"],
            ["CONTEMPLATE", "FUTURE", "NOT", "SHAPERS", "PATH"],
            ["contemplate", "restraint", "discover", "more", "COURAGE"],
            ["courage", "attack", "shapers", "portal", "TOGETHER"],
            ["create", "pure", "future", "human", "civilization"],
            ["create", "pure", "future", "not", "WAR"],
            ["create", "separate", "path", "end", "journey"],
            ["defend", "destiny", "defend", "human", "CIVILIZATION"],
            ["defend", "human", "civilization", "XM", "MESSAGE"],
            ["defend", "human", "civilization", "portal", "DATA"],
            ["defend", "human", "civilization", "shapers", "LIE"],
            ["defend", "human", "civilization", "shapers", "PORTAL"],
            ["DESTROY", "CIVILIZATION", "END", "CONFLICT", "WAR"],
            ["destroy", "lie", "not", "gain", "SOUL"],
            ["distance", "self", "avoid", "human", "LIE"],
            ["easy", "path", "future", "follow", "SHAPERS"],
            ["end", "old", "civilization", "create", "NEW"],
            ["escape", "body", "journey", "distance", "PRESENT"],
            ["escape", "body", "mind", "self", "WANT"],
            ["forget", "attack", "see", "outside", "harmony"],
            ["forget", "past", "see", "present", "DANGER"],
            ["gain", "truth", "open", "human", "SOUL"],
            ["HARM", "PROGRESS", "PURSUE", "MORE", "WAR"],
            ["help", "enlightened", "capture", "all", "portal"],
            ["HELP", "HUMAN", "CIVILIZATION", "PURSUE", "DESTINY"],
            ["help", "resistance", "capture", "all", "PORTAL"],
            ["hide", "struggle", "advance", "strong", "TOGETHER"],
            ["human", "not", "together", "civilization", "deteriorate"],
            ["human", "shapers", "together", "create", "DESTINY"],
            ["imperfect", "TRUTH", "open", "complex", "ANSWER"],
            ["imperfect", "XM", "message", "human", "CHAOS"],
            ["inside", "mind", "inside", "soul", "HARMONY"],
            ["liberate", "portal", "liberate", "human", "MIND"],
            ["liberate", "self", "liberate", "human", "CIVILIZATION"],
            ["lose", "shapers", "message", "gain", "CHAOS"],
            ["MIND", "BODY", "SOUL", "PURE", "HUMAN"],
            ["more", "data", "gain", "portal", "ADVANCE"],
            ["old", "nature", "less", "strong", "present"],
            ["past", "chaos", "create", "future", "HARMONY"],
            ["past", "path", "create", "future", "JOURNEY"],
            ["portal", "barrier", "defend", "human", "SHAPERS"],
            ["portal", "create", "danger", "pursue", "SAFETY"],
            ["portal", "improve", "human", "future", "CIVILIZATION"],
            ["portal", "potential", "help", "human", "future"],
            ["present", "chaos", "create", "future", "CIVILIZATION"],
            ["PURE", "HUMAN", "FAILURE", "NOW", "CHAOS"],
            ["PURSUE", "CONFLICT", "WAR", "ADVANCE", "CHAOS"],
            ["pursue", "path", "distance", "shapers", "LIE"],
            ["QUESTION", "LESS", "FORGET", "ALL", "LIE"],
            ["REACT", "REBEL", "QUESTION", "SHAPERS", "LIE"],
            ["REBEL", "THOUGHT", "EVOLUTION", "DESTINY", "NOW"],
            ["REPAIR", "PRESENT", "REPAIR", "HUMAN", "SOUL"],
            ["repair", "soul", "less", "human", "HARM"],
            ["save", "human", "civilization", "destroy", "PORTAL"],
            ["search", "destiny", "create", "pure", "future"],
            ["separate", "mind", "body", "discover", "ENLIGHTENMENT"],
            ["separate", "truth", "lie", "shapers", "future"],
            ["SHAPERS", "LEAD", "HUMAN", "COMPLEX", "JOURNEY"],
            ["shapers", "portal", "data", "create", "chaos"],
            ["shapers", "portal", "message", "destroy", "CIVILIZATION"],
            ["shapers", "see", "complex", "path", "DESTINY"],
            ["shapers", "want", "human", "mind", "FUTURE"],
            ["simple", "old", "truth", "journey", "INSIDE"],
            ["simple", "truth", "forget", "easy", "SUCCESS"],
            ["simple", "truth", "shapers", "destroy", "civilization"],
            ["STAY", "STRONG", "TOGETHER", "DEFEND", "RESISTANCE"],
            ["strong", "together", "attack", "together", "CHAOS"],
            ["STRONG", "TOGETHER", "WAR", "TOGETHER", "DESTINY"],
            ["use", "mind", "use", "courage", "CHANGE"],
            ["USE", "RESTRAINT", "FOLLOW", "EASY", "PATH"],
            ["want", "truth", "pursue", "difficult", "PATH"],
            ["weak", "human", "destiny", "destroy", "CIVILIZATION"],
            ["XM", "create", "complex", "human", "DESTINY"],
            //["XM", "create", "complex", "shapers", "DESTINY"],
            ["XM", "PATH", "FUTURE", "DESTINY", "HARMONY"]
        ]
    ];

    igt.sequenceDic = {
        getSequences: function(lv){ return LEVEL_SEQUENCES[lv];},
        getSequenceRandom: function(lv){
            var seqs = LEVEL_SEQUENCES[lv];
            return seqs && seqs.length > 0 ? seqs[Math.floor(Math.random() * seqs.length)] : null;
        }
    };
})();
<commit_msg>L5グリフシーケンスを五つ追加した。<commit_after>/*
 * Copyright (c) 2014 gm9
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */

(function(){
    // import
    var igt = gm9.IngressGlyphTools;
    // レベル毎のグリフシーケンス
    // 小文字は読み方が曖昧な(自信がない)グリフ
    var LEVEL_SEQUENCES = [
        //L0
        [
        ],
        //L1
        [
        ],
        //L2
        [
        ],
        //L3
        [
        ],
        //L4
        [
        ],
        //L5
        [
            ["ACCEPT", "HUMAN", "WEAK"],
            ["ADVANCE", "PURE", "TRUTH"],
            ["ALL", "CIVILIZATION", "CHAOS"],
            ["ATTACK", "SHAPERS", "CHAOS"],
            ["AVOID", "CHAOS", "SOUL"],
            ["AVOID", "DESTINY", "LIE"],
            ["AVOID", "PURE", "CHAOS"],
            ["COURAGE", "DESTINY", "REBEL"],
            ["DESTROY", "IMPURE", "TRUTH"],
            ["DISCOVER", "RESISTANCE", "TRUTH"],
            ["DISCOVER", "SHAPERS", "ENLIGHTENMENT"],
            ["DISCOVER", "SHAPERS", "MESSAGE"],
            ["ESCAPE", "IMPURE", "EVOLUTION"],
            ["FOLLOW", "PURE", "JOURNEY"],
            ["HUMAN", "GAIN", "SAFETY"],
            ["IMPROVE", "ADVANCE", "PRESENT"],
            ["IMPROVE", "HUMAN", "SHAPERS"],
            ["INSIDE", "XM", "TRUTH"],
            ["JOURNEY", "INSIDE", "SOUL"],
            ["LEAD", "ENLIGHTENMENT", "CIVILIZATION"],
            ["LIBERATE", "HUMAN", "FUTURE"],
            ["PEACE", "SIMPLE", "JOURNEY"],
            ["PERFECTION", "PATH", "PEACE"],
            ["PURSUE", "PURE", "BODY"],
            ["QUESTION", "HIDE", "TRUTH"],
            ["REACT", "IMPURE", "CIVILIZATION"],
            ["RETREAT", "SEARCH", "SAFETY"],
            ["WAR", "DESTROY", "FUTURE"],
            ["XM", "NOURISH", "CIVILIZATION"]
        ],
        //L6
        [
        ],
        //L7
        [
            ["ALL", "CHAOS", "INSIDE", "BODY"],
            ["ATTACK", "WEAK", "SHAPERS", "LIE"],
            ["BREATHE", "AGAIN", "JOURNEY", "AGAIN"],
            ["CAPTURE", "FEAR", "DISCOVER", "COURAGE"],
            ["CHANGE", "FUTURE", "CAPTURE", "DESTINY"],
            ["CHANGE", "HUMAN", "POTENTIAL", "USE"],
            ["CHAOS", "barrier", "SHAPERS", "PORTAL"],
            ["CLEAR", "MIND", "OPEN", "MIND"],
            ["CLEARALL", "OPENALL", "DISCOVER", "TRUTH"],
            ["CONTEMPLATE", "COMPLEX", "SHAPERS", "CIVILIZATION"],
            ["COURAGE", "attack", "shapers", "FUTURE"],
            ["CREATE", "DISTANCE", "IMPURE", "PATH"],
            ["CREATE", "FUTURE", "NOT", "WAR"],
            ["DEFEND", "MESSAGE", "ANSWER", "IDEA"],
            ["DESTROY", "DESTINY", "HUMAN", "LIE"],
            ["DETERIORATE", "HUMAN", "WEAK", "REBEL"],
            ["ESCAPE", "SIMPLE", "HUMAN", "FUTURE"],
            ["FOLLOW", "SHAPERS", "PORTAL", "MESSAGE"],
            ["FORGET", "CONFLICT", "ACCEPT", "WAR"],
            ["HARMONY", "PATH", "NOURISH", "PRESENT"],
            ["HELP", "GAIN", "CREATE", "PURSUE"],
            ["HIDE", "IMPURE", "HUMAN", "THOUGHT"],
            ["HUMAN", "HAVE", "IMPURE", "CIVILIZATION"],
            ["HUMAN", "PAST", "PRESENT", "FUTURE"],
            ["HUMAN", "SOUL", "STRONG", "PURE"],
            ["IGNORE", "HUMAN", "CHAOS", "LIE"],
            ["IMPROVE", "BODY", "MIND", "SOUL"],
            ["IMPROVE", "BODY", "PURSUE", "JOURNEY"],
            ["INSIDE", "MIND", "JOURNEY", "PERFECTION"],
            ["JOURNEY", "not", "IMPROVE", "SOUL"],
            ["LEAD", "PURSUE", "REACT", "DEFEND"],
            ["LESS", "SOUL", "MORE", "MIND"],
            ["LESS", "TRUTH", "MORE", "CHAOS"],
            ["LIBERATE", "XM", "PORTAL", "TOGETHER"],
            ["LOSE", "DANGER", "GAIN", "SAFETY"],
            ["PAST", "AGAIN", "PRESENT", "AGAIN"],
            ["PATH", "RESTRAINT", "STRONG", "SAFETY"],
            ["PORTAL", "CHANGE", "CIVILIZATION", "END"],
            ["PORTAL", "DIE", "CIVILIZATION", "DIE"],
            ["QUESTION", "TRUTH", "GAIN", "FUTURE"],
            ["RESTRAINT", "FEAR", "AVOID", "DANGER"],
            ["RESTRAINT", "PATH", "GAIN", "HARMONY"],
            ["SEARCH", "DATA", "DISCOVER", "PATH"],
            ["SEARCH", "TRUTH", "SAVE", "CIVILIZATION"],
            ["SEARCH", "XM", "SAVE", "PORTAL"],
            ["SEE", "TRUTH", "SEE", "FUTURE"],
            ["SEPARATE", "WEAK", "IGNORE", "TRUTH"],
            ["SHAPERS", "CHAOS", "PURE", "HARM"],
            ["SHAPERS", "MESSAGE", "END", "CIVILIZATION"],
            ["SHAPERS", "MIND", "COMPLEX", "HARMONY"],
            ["SHAPERS", "PAST", "PRESENT", "FUTURE"],
            ["SHAPERS", "SEE", "POTENTIAL", "EVOLUTION"],
            ["SIMPLE", "CIVILIZATION", "IMPURE", "WEAK"],
            ["SIMPLE", "MESSAGE", "COMPLEX", "IDEA"],
            ["SIMPLE", "TRUTH", "BREATHE", "NATURE"],
            ["SOUL", "REBEL", "HUMAN", "DIE"],
            ["STAY", "TOGETHER", "DEFEND", "TRUTH"],
            ["STRONG", "IDEA", "PURSUE", "TRUTH"],
            ["STRUGGLE", "DEFEND", "SHAPERS", "DANGER"],
            ["STRUGGLE", "IMPROVE", "HUMAN", "SOUL"],
            ["TOGETHER", "DISCOVER", "HARMONY", "EQUAL"],
            ["TRUTH", "IDEA", "DISCOVER", "XM"],
            ["XM", "DIE", "CHAOS", "LIVE"]
        ],
        //L8
        [
            ["advance", "civilization", "pursue", "shapers", "PATH"],
            ["ANSWER", "QUESTION", "DISCOVER", "DIFFICULT", "TRUTH"],
            ["AVOID", "PERFECTION", "STAY", "HUMAN", "SELF"],
            ["avoid", "chaos", "avoid", "shapers", "LIE"],
            ["avoid", "chaos", "repair", "potential", "WAR"],
            ["breathe", "inside", "XM", "lose", "SELF"],
            ["capture", "portal", "defend", "portal", "COURAGE"],
            ["CHAOS", "WAR", "CONFLICT", "DISCOVER", "PEACE"],
            ["civilization", "die", "new", "civilization", "BEGIN"],
            ["clear", "mind", "liberate", "barrier", "BODY"],
            ["clearall", "idea", "past", "present", "FUTURE"],
            ["CONTEMPLATE", "FUTURE", "NOT", "SHAPERS", "PATH"],
            ["contemplate", "restraint", "discover", "more", "COURAGE"],
            ["courage", "attack", "shapers", "portal", "TOGETHER"],
            ["create", "pure", "future", "human", "civilization"],
            ["create", "pure", "future", "not", "WAR"],
            ["create", "separate", "path", "end", "journey"],
            ["defend", "destiny", "defend", "human", "CIVILIZATION"],
            ["defend", "human", "civilization", "XM", "MESSAGE"],
            ["defend", "human", "civilization", "portal", "DATA"],
            ["defend", "human", "civilization", "shapers", "LIE"],
            ["defend", "human", "civilization", "shapers", "PORTAL"],
            ["DESTROY", "CIVILIZATION", "END", "CONFLICT", "WAR"],
            ["destroy", "lie", "not", "gain", "SOUL"],
            ["distance", "self", "avoid", "human", "LIE"],
            ["easy", "path", "future", "follow", "SHAPERS"],
            ["end", "old", "civilization", "create", "NEW"],
            ["escape", "body", "journey", "distance", "PRESENT"],
            ["escape", "body", "mind", "self", "WANT"],
            ["forget", "attack", "see", "outside", "harmony"],
            ["forget", "past", "see", "present", "DANGER"],
            ["gain", "truth", "open", "human", "SOUL"],
            ["HARM", "PROGRESS", "PURSUE", "MORE", "WAR"],
            ["help", "enlightened", "capture", "all", "portal"],
            ["HELP", "HUMAN", "CIVILIZATION", "PURSUE", "DESTINY"],
            ["help", "resistance", "capture", "all", "PORTAL"],
            ["hide", "struggle", "advance", "strong", "TOGETHER"],
            ["human", "not", "together", "civilization", "deteriorate"],
            ["human", "shapers", "together", "create", "DESTINY"],
            ["imperfect", "TRUTH", "open", "complex", "ANSWER"],
            ["imperfect", "XM", "message", "human", "CHAOS"],
            ["inside", "mind", "inside", "soul", "HARMONY"],
            ["liberate", "portal", "liberate", "human", "MIND"],
            ["liberate", "self", "liberate", "human", "CIVILIZATION"],
            ["lose", "shapers", "message", "gain", "CHAOS"],
            ["MIND", "BODY", "SOUL", "PURE", "HUMAN"],
            ["more", "data", "gain", "portal", "ADVANCE"],
            ["old", "nature", "less", "strong", "present"],
            ["past", "chaos", "create", "future", "HARMONY"],
            ["past", "path", "create", "future", "JOURNEY"],
            ["portal", "barrier", "defend", "human", "SHAPERS"],
            ["portal", "create", "danger", "pursue", "SAFETY"],
            ["portal", "improve", "human", "future", "CIVILIZATION"],
            ["portal", "potential", "help", "human", "future"],
            ["present", "chaos", "create", "future", "CIVILIZATION"],
            ["PURE", "HUMAN", "FAILURE", "NOW", "CHAOS"],
            ["PURSUE", "CONFLICT", "WAR", "ADVANCE", "CHAOS"],
            ["pursue", "path", "distance", "shapers", "LIE"],
            ["QUESTION", "LESS", "FORGET", "ALL", "LIE"],
            ["REACT", "REBEL", "QUESTION", "SHAPERS", "LIE"],
            ["REBEL", "THOUGHT", "EVOLUTION", "DESTINY", "NOW"],
            ["REPAIR", "PRESENT", "REPAIR", "HUMAN", "SOUL"],
            ["repair", "soul", "less", "human", "HARM"],
            ["save", "human", "civilization", "destroy", "PORTAL"],
            ["search", "destiny", "create", "pure", "future"],
            ["separate", "mind", "body", "discover", "ENLIGHTENMENT"],
            ["separate", "truth", "lie", "shapers", "future"],
            ["SHAPERS", "LEAD", "HUMAN", "COMPLEX", "JOURNEY"],
            ["shapers", "portal", "data", "create", "chaos"],
            ["shapers", "portal", "message", "destroy", "CIVILIZATION"],
            ["shapers", "see", "complex", "path", "DESTINY"],
            ["shapers", "want", "human", "mind", "FUTURE"],
            ["simple", "old", "truth", "journey", "INSIDE"],
            ["simple", "truth", "forget", "easy", "SUCCESS"],
            ["simple", "truth", "shapers", "destroy", "civilization"],
            ["STAY", "STRONG", "TOGETHER", "DEFEND", "RESISTANCE"],
            ["strong", "together", "attack", "together", "CHAOS"],
            ["STRONG", "TOGETHER", "WAR", "TOGETHER", "DESTINY"],
            ["use", "mind", "use", "courage", "CHANGE"],
            ["USE", "RESTRAINT", "FOLLOW", "EASY", "PATH"],
            ["want", "truth", "pursue", "difficult", "PATH"],
            ["weak", "human", "destiny", "destroy", "CIVILIZATION"],
            ["XM", "create", "complex", "human", "DESTINY"],
            //["XM", "create", "complex", "shapers", "DESTINY"],
            ["XM", "PATH", "FUTURE", "DESTINY", "HARMONY"]
        ]
    ];

    igt.sequenceDic = {
        getSequences: function(lv){ return LEVEL_SEQUENCES[lv];},
        getSequenceRandom: function(lv){
            var seqs = LEVEL_SEQUENCES[lv];
            return seqs && seqs.length > 0 ? seqs[Math.floor(Math.random() * seqs.length)] : null;
        }
    };
})();
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isArray = require( '@stdlib/assert/is-array' );
var isArrayLike = require( '@stdlib/assert/is-array-like' );
var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
var isInteger = require( '@stdlib/assert/is-integer' );
var prependArray = require( './prepend_array.js' );
var prependObject = require( './prepend_object.js' );
var prependTypedArray = require( './prepend_typed_array.js' );

// MAIN //

/**
* Adds elements from one collection to the beginning of another collection.
*
* @param {(Array|TypedArray|Object)} collection1 - collection
* @param {ArrayLike} collection2 - collection containing elements to add
* @throws {TypeError} first argument must be either an array, typed array, or an array-like object
* @throws {TypeError} second argument must be an array-like object
* @returns {(Array|TypedArray|Object)} updated collection
*
* @example
* var arr = [ 1.0, 2.0, 3.0, 4.0, 5.0 ];
*
* arr = prepend( arr, [ 6.0, 7.0 ] );
* // returns [ 6.0, 7.0, 1.0, 2.0, 3.0, 4.0, 5.0 ]
*
* @example
* var arr = new Float64Array( [ 1.0, 2.0, 3.0, 4.0, 5.0 ] );
* // returns <Float64Array>[ 1.0, 2.0, 3.0, 4.0, 5.0 ]
*
* arr = prepend( arr, [ 6.0, 7.0 ] );
* // returns <Float64Array>[ 6.0, 7.0, 1.0, 2.0, 3.0, 4.0, 5.0 ]
*/
function prepend( collection1, collection2 ) {
	if ( !isArrayLike( collection2 ) ) {
		throw new TypeError( 'invalid input argument. Second argument must be an array-like object. Value: `'+collection2+'`.' );
	}
	if ( isArray( collection1 ) ) {
		return prependArray( collection1, collection2 );
	}
	// Check for a typed-array-like object, as verifying actual typed arrays is expensive...
	if ( isTypedArrayLike( collection1 ) ) {
		return prependTypedArray( collection1, collection2 );
	}
	// Check for an array-like object...
	if (
		collection1 !== null &&
		typeof collection1 === 'object' &&
		typeof collection1.length === 'number' &&
		isInteger( collection1.length ) &&
		collection1.length >= 0
	) {
		return prependObject( collection1, collection2 );
	}
	throw new TypeError( 'invalid input argument. First argument must be either an Array, Typed Array, or an array-like Object. Value: `'+collection1+'`.' );
}

// EXPORTS //

module.exports = prepend;
<commit_msg>Use less restrictive assertion<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isArray = require( '@stdlib/assert/is-array' );
var isCollection = require( '@stdlib/assert/is-collection' );
var isTypedArrayLike = require( '@stdlib/assert/is-typed-array-like' );
var isInteger = require( '@stdlib/assert/is-integer' );
var prependArray = require( './prepend_array.js' );
var prependObject = require( './prepend_object.js' );
var prependTypedArray = require( './prepend_typed_array.js' );

// MAIN //

/**
* Adds elements from one collection to the beginning of another collection.
*
* @param {(Array|TypedArray|Object)} collection1 - collection
* @param {ArrayLike} collection2 - collection containing elements to add
* @throws {TypeError} first argument must be either an array, typed array, or an array-like object
* @throws {TypeError} second argument must be an array-like object
* @returns {(Array|TypedArray|Object)} updated collection
*
* @example
* var arr = [ 1.0, 2.0, 3.0, 4.0, 5.0 ];
*
* arr = prepend( arr, [ 6.0, 7.0 ] );
* // returns [ 6.0, 7.0, 1.0, 2.0, 3.0, 4.0, 5.0 ]
*
* @example
* var arr = new Float64Array( [ 1.0, 2.0, 3.0, 4.0, 5.0 ] );
* // returns <Float64Array>[ 1.0, 2.0, 3.0, 4.0, 5.0 ]
*
* arr = prepend( arr, [ 6.0, 7.0 ] );
* // returns <Float64Array>[ 6.0, 7.0, 1.0, 2.0, 3.0, 4.0, 5.0 ]
*/
function prepend( collection1, collection2 ) {
	if ( !isCollection( collection2 ) ) {
		throw new TypeError( 'invalid input argument. Second argument must be an array-like object. Value: `'+collection2+'`.' );
	}
	if ( isArray( collection1 ) ) {
		return prependArray( collection1, collection2 );
	}
	// Check for a typed-array-like object, as verifying actual typed arrays is expensive...
	if ( isTypedArrayLike( collection1 ) ) {
		return prependTypedArray( collection1, collection2 );
	}
	// Check for an array-like object...
	if (
		collection1 !== null &&
		typeof collection1 === 'object' &&
		typeof collection1.length === 'number' &&
		isInteger( collection1.length ) &&
		collection1.length >= 0
	) {
		return prependObject( collection1, collection2 );
	}
	throw new TypeError( 'invalid input argument. First argument must be either an Array, Typed Array, or an array-like Object. Value: `'+collection1+'`.' );
}

// EXPORTS //

module.exports = prepend;
<|endoftext|>


<commit_before>import _extend from 'lodash-es/extend';
import _find from 'lodash-es/find';
import _forEach from 'lodash-es/forEach';

import rbush from 'rbush';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';

import { geoExtent, geoVecAdd } from '../geo';
import { qaError } from '../osm';
import { services } from './index';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';

var tiler = utilTiler();
var dispatch = d3_dispatch('loaded');

var _erCache;
var _erZoom = 14;

var _impOsmUrls = {
    ow: 'http://directionofflow.skobbler.net/directionOfFlowService',
    mr: 'http://missingroads.skobbler.net/missingGeoService',
    tr: 'http://turnrestrictionservice.skobbler.net/turnRestrictionService'
};

function abortRequest(i) {
    _forEach(i, function(v) {
        if (v) {
            v.abort();
        }
    });
}

function abortUnwantedRequests(cache, tiles) {
    _forEach(cache.inflightTile, function(v, k) {
        var wanted = _find(tiles, function(tile) {
            return k === tile.id;
        });
        if (!wanted) {
            abortRequest(v);
            delete cache.inflightTile[k];
        }
    });
}

function encodeErrorRtree(d) {
    return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
}

// replace or remove error from rtree
function updateRtree(item, replace) {
    _erCache.rtree.remove(item, function isEql(a, b) {
        return a.data.id === b.data.id;
    });

    if (replace) {
        _erCache.rtree.insert(item);
    }
}

function linkErrorObject(d) {
    return '' + d + '';
}

function linkEntity(d) {
    return '' + d + '';
}

function pointAverage(points) {
    var x = 0;
    var y = 0;

    _forEach(points, function(v) {
        x += v.lon;
        y += v.lat;
    });

    x /= points.length;
    y /= points.length;

    return [x, y];
}

function relativeBearing(p1, p2) {
    var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
    if (angle < 0) {
        angle += 2 * Math.PI;
    }

    // Return degrees
    return angle * 180 / Math.PI;
}

// Assuming range [0,360)
function cardinalDirection(bearing) {
    var dir = 45 * Math.round(bearing / 45);
    var compass = {
        0: 'north',
        45: 'northeast',
        90: 'east',
        135: 'southeast',
        180: 'south',
        225: 'southwest',
        270: 'west',
        315: 'northwest',
        360: 'north'
    };

    return t('QA.improveOSM.directions.' + compass[dir]);
}

// Errors shouldn't obscure eachother
function preventCoincident(loc, bumpUp) {
    var coincident = false;
    do {
        // first time, move marker up. after that, move marker right.
        var delta = coincident ? [0.00001, 0] : (bumpUp ? [0, 0.00001] : [0, 0]);
        loc = geoVecAdd(loc, delta);
        var bbox = geoExtent(loc).bbox();
        coincident = _erCache.rtree.search(bbox).length;
    } while (coincident);

    return loc;
}

export default {
    init: function() {
        if (!_erCache) {
            this.reset();
        }

        this.event = utilRebind(this, dispatch, 'on');
    },

    reset: function() {
        if (_erCache) {
            _forEach(_erCache.inflightTile, abortRequest);
        }
        _erCache = {
            data: {},
            loadedTile: {},
            inflightTile: {},
            inflightPost: {},
            closed: {},
            rtree: rbush()
        };
    },

    loadErrors: function(projection) {
        var options = {
            client: 'iD',
            status: 'OPEN',
            zoom: '19' // Use a high zoom so that clusters aren't returned
        };

        // determine the needed tiles to cover the view
        var tiles = tiler
            .zoomExtent([_erZoom, _erZoom])
            .getTiles(projection);

        // abort inflight requests that are no longer needed
        abortUnwantedRequests(_erCache, tiles);

        // issue new requests..
        tiles.forEach(function(tile) {
            if (_erCache.loadedTile[tile.id] || _erCache.inflightTile[tile.id]) return;

            var rect = tile.extent.rectangle();
            var params = _extend({}, options, { east: rect[0], south: rect[3], west: rect[2], north: rect[1] });

            // 3 separate requests to store for each tile
            var requests = {};

            _forEach(_impOsmUrls, function(v, k) {
                // We exclude WATER from missing geometry as it doesn't seem useful
                // We use most confident one-way and turn restrictions only, still have false positives
                var kParams = _extend({}, params, (k === 'mr') ? { type: 'PARKING,ROAD,BOTH,PATH' } : { confidenceLevel: 'C1' });
                var url = v + '/search?' + utilQsString(kParams);

                requests[k] = d3_json(url,
                    function(err, data) {
                        delete _erCache.inflightTile[tile.id];

                        if (err) return;
                        _erCache.loadedTile[tile.id] = true;

                        // Road segments at high zoom == oneways
                        if (data.roadSegments) {
                            data.roadSegments.forEach(function(feature) {
                                // Position error at the approximate middle of the segment
                                var points = feature.points;
                                var mid = points.length / 2;
                                var loc;

                                // Even number of points, find midpoint of the middle two
                                // Odd number of points, use position of very middle point
                                if (mid % 1 === 0) {
                                    loc = pointAverage([points[mid - 1], points[mid]]);
                                } else {
                                    mid = points[Math.floor(mid)];
                                    loc = [mid.lon, mid.lat];
                                }

                                // One-ways can land on same segment in opposite direction
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { // this is used to post changes to the error
                                        wayId: feature.wayId,
                                        fromNodeId: feature.fromNodeId,
                                        toNodeId: feature.toNodeId
                                    },
                                    object_id: feature.wayId,
                                    object_type: 'way',
                                    status: feature.status
                                });

                                // Variables used in the description
                                d.replacements = {
                                    percentage: feature.percentOfTrips,
                                    num_trips: feature.numberOfTrips,
                                    highway: linkErrorObject(t('QA.keepRight.error_parts.highway')),
                                    from_node: linkEntity('n' + feature.fromNodeId),
                                    to_node: linkEntity('n' + feature.toNodeId)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Tiles at high zoom == missing roads
                        if (data.tiles) {
                            data.tiles.forEach(function(feature) {
                                var geoType = feature.type.toLowerCase();

                                // Average of recorded points should land on the missing geometry
                                // Missing geometry could happen to land on another error
                                var loc = pointAverage(feature.points);
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k + '-' + geoType,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { x: feature.x, y: feature.y },
                                    status: feature.status
                                });

                                d.replacements = {
                                    num_trips: feature.numberOfTrips,
                                    geometry_type: t('QA.improveOSM.geometry_types.' + geoType)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Entities at high zoom == turn restrictions
                        if (data.entities) {
                            data.entities.forEach(function(feature) {
                                // Turn restrictions could be missing at same junction
                                // We also want to bump the error up so node is accessible
                                var loc = feature.point;
                                loc = preventCoincident([loc.lon, loc.lat], true);

                                // Elements are presented in a strange way
                                var ids = feature.id.split(',');
                                var from_way = ids[0];
                                var via_node = ids[3];
                                var to_way = ids[2].split(':')[1];

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: feature.id,
                                    object_id: via_node,
                                    object_type: 'node',
                                    status: feature.status
                                });

                                // Travel direction along from_way clarifies the turn restriction
                                var p1 = feature.segments[0].points[0];
                                var p2 = feature.segments[0].points[1];

                                var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));

                                // Variables used in the description
                                d.replacements = {
                                    num_passed: feature.numberOfPasses,
                                    num_trips: feature.segments[0].numberOfTrips,
                                    turn_restriction: feature.turnType.toLowerCase(),
                                    from_way: linkEntity('w' + from_way),
                                    to_way: linkEntity('w' + to_way),
                                    travel_direction: dir_of_travel,
                                    junction: linkErrorObject(t('QA.keepRight.error_parts.this_node'))
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }
                    }
                );
            });

            _erCache.inflightTile[tile.id] = requests;
            dispatch.call('loaded');
        });
    },

    getComments: function(d, callback) {
        var key = d.error_key;
        var qParams = {};

        if (key === 'ow') {
            qParams = d.identifier;
        } else if (key === 'mr') {
            qParams.tileX = d.identifier.x;
            qParams.tileY = d.identifier.y;
        } else if (key === 'tr') {
            qParams.targetId = d.identifier;
        }

        var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams);

        d3_json(url, function(err, data) {
            d.comments = data.comments.reverse(); // comments served newest to oldest
            return callback(err, d);
        });
    },

    postUpdate: function(d, callback) {
        if (!services.osm.authenticated()) { // Username required in payload
            return callback({ message: 'Not Authenticated', status: -3}, d);
        }
        if (_erCache.inflightPost[d.id]) {
            return callback({ message: 'Error update already inflight', status: -2 }, d);
        }

        var that = this;

        // Payload can only be sent once username is established
        services.osm.userDetails(sendPayload);

        function sendPayload(err, user) {
            if (err) { return callback(err, d); }

            var key = d.error_key;
            var url = _impOsmUrls[key] + '/comment';
            var payload = {
                username: user.display_name
            };

            // Each error type has different data for identification
            if (key === 'ow') {
                payload.roadSegments = [ d.identifier ];
            } else if (key === 'mr') {
                payload.tiles = [ d.identifier ];
            } else if (key === 'tr') {
                payload.targetIds = [ d.identifier ];
            }

            if (d.newStatus !== undefined) {
                payload.status = d.newStatus;
                payload.text = 'status changed';
            }

            // Comment take place of default text
            if (d.newComment !== undefined) {
                payload.text = d.newComment;
            }

            _erCache.inflightPost[d.id] = d3_request(url)
                .header('Content-Type', 'application/json')
                .post(JSON.stringify(payload), function(err) {
                    delete _erCache.inflightPost[d.id];

                    // Unsuccessful response status, keep issue open
                    if (err.status !== 200) { return callback(err, d); }
                    // Just a comment, error still open
                    if (d.newStatus === undefined) { return callback(err, d); }

                    that.removeError(d);

                    // No pretty identifier, so we just use coordinates
                    if (d.newStatus === 'SOLVED') {
                        var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
                        _erCache.closed[key + ':' + closedID] = true;
                    }

                    return callback(err, d);
                });
        }
    },

    // get all cached errors covering the viewport
    getErrors: function(projection) {
        var viewport = projection.clipExtent();
        var min = [viewport[0][0], viewport[1][1]];
        var max = [viewport[1][0], viewport[0][1]];
        var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();

        return _erCache.rtree.search(bbox).map(function(d) {
            return d.data;
        });
    },

    // get a single error from the cache
    getError: function(id) {
        return _erCache.data[id];
    },

    // replace a single error in the cache
    replaceError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        _erCache.data[error.id] = error;
        updateRtree(encodeErrorRtree(error), true); // true = replace
        return error;
    },

    // remove a single error from the cache
    removeError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        delete _erCache.data[error.id];
        updateRtree(encodeErrorRtree(error), false); // false = remove
    },

    // Used to populate `closed:improveosm` changeset tag
    getClosedIDs: function() {
        return Object.keys(_erCache.closed).sort();
    }
};<commit_msg>Retrieve ImproveOSM comments only once<commit_after>import _extend from 'lodash-es/extend';
import _find from 'lodash-es/find';
import _forEach from 'lodash-es/forEach';

import rbush from 'rbush';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';

import { geoExtent, geoVecAdd } from '../geo';
import { qaError } from '../osm';
import { services } from './index';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';

var tiler = utilTiler();
var dispatch = d3_dispatch('loaded');

var _erCache;
var _erZoom = 14;

var _impOsmUrls = {
    ow: 'http://directionofflow.skobbler.net/directionOfFlowService',
    mr: 'http://missingroads.skobbler.net/missingGeoService',
    tr: 'http://turnrestrictionservice.skobbler.net/turnRestrictionService'
};

function abortRequest(i) {
    _forEach(i, function(v) {
        if (v) {
            v.abort();
        }
    });
}

function abortUnwantedRequests(cache, tiles) {
    _forEach(cache.inflightTile, function(v, k) {
        var wanted = _find(tiles, function(tile) {
            return k === tile.id;
        });
        if (!wanted) {
            abortRequest(v);
            delete cache.inflightTile[k];
        }
    });
}

function encodeErrorRtree(d) {
    return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
}

// replace or remove error from rtree
function updateRtree(item, replace) {
    _erCache.rtree.remove(item, function isEql(a, b) {
        return a.data.id === b.data.id;
    });

    if (replace) {
        _erCache.rtree.insert(item);
    }
}

function linkErrorObject(d) {
    return '' + d + '';
}

function linkEntity(d) {
    return '' + d + '';
}

function pointAverage(points) {
    var x = 0;
    var y = 0;

    _forEach(points, function(v) {
        x += v.lon;
        y += v.lat;
    });

    x /= points.length;
    y /= points.length;

    return [x, y];
}

function relativeBearing(p1, p2) {
    var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
    if (angle < 0) {
        angle += 2 * Math.PI;
    }

    // Return degrees
    return angle * 180 / Math.PI;
}

// Assuming range [0,360)
function cardinalDirection(bearing) {
    var dir = 45 * Math.round(bearing / 45);
    var compass = {
        0: 'north',
        45: 'northeast',
        90: 'east',
        135: 'southeast',
        180: 'south',
        225: 'southwest',
        270: 'west',
        315: 'northwest',
        360: 'north'
    };

    return t('QA.improveOSM.directions.' + compass[dir]);
}

// Errors shouldn't obscure eachother
function preventCoincident(loc, bumpUp) {
    var coincident = false;
    do {
        // first time, move marker up. after that, move marker right.
        var delta = coincident ? [0.00001, 0] : (bumpUp ? [0, 0.00001] : [0, 0]);
        loc = geoVecAdd(loc, delta);
        var bbox = geoExtent(loc).bbox();
        coincident = _erCache.rtree.search(bbox).length;
    } while (coincident);

    return loc;
}

export default {
    init: function() {
        if (!_erCache) {
            this.reset();
        }

        this.event = utilRebind(this, dispatch, 'on');
    },

    reset: function() {
        if (_erCache) {
            _forEach(_erCache.inflightTile, abortRequest);
        }
        _erCache = {
            data: {},
            loadedTile: {},
            inflightTile: {},
            inflightPost: {},
            closed: {},
            rtree: rbush()
        };
    },

    loadErrors: function(projection) {
        var options = {
            client: 'iD',
            status: 'OPEN',
            zoom: '19' // Use a high zoom so that clusters aren't returned
        };

        // determine the needed tiles to cover the view
        var tiles = tiler
            .zoomExtent([_erZoom, _erZoom])
            .getTiles(projection);

        // abort inflight requests that are no longer needed
        abortUnwantedRequests(_erCache, tiles);

        // issue new requests..
        tiles.forEach(function(tile) {
            if (_erCache.loadedTile[tile.id] || _erCache.inflightTile[tile.id]) return;

            var rect = tile.extent.rectangle();
            var params = _extend({}, options, { east: rect[0], south: rect[3], west: rect[2], north: rect[1] });

            // 3 separate requests to store for each tile
            var requests = {};

            _forEach(_impOsmUrls, function(v, k) {
                // We exclude WATER from missing geometry as it doesn't seem useful
                // We use most confident one-way and turn restrictions only, still have false positives
                var kParams = _extend({}, params, (k === 'mr') ? { type: 'PARKING,ROAD,BOTH,PATH' } : { confidenceLevel: 'C1' });
                var url = v + '/search?' + utilQsString(kParams);

                requests[k] = d3_json(url,
                    function(err, data) {
                        delete _erCache.inflightTile[tile.id];

                        if (err) return;
                        _erCache.loadedTile[tile.id] = true;

                        // Road segments at high zoom == oneways
                        if (data.roadSegments) {
                            data.roadSegments.forEach(function(feature) {
                                // Position error at the approximate middle of the segment
                                var points = feature.points;
                                var mid = points.length / 2;
                                var loc;

                                // Even number of points, find midpoint of the middle two
                                // Odd number of points, use position of very middle point
                                if (mid % 1 === 0) {
                                    loc = pointAverage([points[mid - 1], points[mid]]);
                                } else {
                                    mid = points[Math.floor(mid)];
                                    loc = [mid.lon, mid.lat];
                                }

                                // One-ways can land on same segment in opposite direction
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { // this is used to post changes to the error
                                        wayId: feature.wayId,
                                        fromNodeId: feature.fromNodeId,
                                        toNodeId: feature.toNodeId
                                    },
                                    object_id: feature.wayId,
                                    object_type: 'way',
                                    status: feature.status
                                });

                                // Variables used in the description
                                d.replacements = {
                                    percentage: feature.percentOfTrips,
                                    num_trips: feature.numberOfTrips,
                                    highway: linkErrorObject(t('QA.keepRight.error_parts.highway')),
                                    from_node: linkEntity('n' + feature.fromNodeId),
                                    to_node: linkEntity('n' + feature.toNodeId)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Tiles at high zoom == missing roads
                        if (data.tiles) {
                            data.tiles.forEach(function(feature) {
                                var geoType = feature.type.toLowerCase();

                                // Average of recorded points should land on the missing geometry
                                // Missing geometry could happen to land on another error
                                var loc = pointAverage(feature.points);
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k + '-' + geoType,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { x: feature.x, y: feature.y },
                                    status: feature.status
                                });

                                d.replacements = {
                                    num_trips: feature.numberOfTrips,
                                    geometry_type: t('QA.improveOSM.geometry_types.' + geoType)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Entities at high zoom == turn restrictions
                        if (data.entities) {
                            data.entities.forEach(function(feature) {
                                // Turn restrictions could be missing at same junction
                                // We also want to bump the error up so node is accessible
                                var loc = feature.point;
                                loc = preventCoincident([loc.lon, loc.lat], true);

                                // Elements are presented in a strange way
                                var ids = feature.id.split(',');
                                var from_way = ids[0];
                                var via_node = ids[3];
                                var to_way = ids[2].split(':')[1];

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: feature.id,
                                    object_id: via_node,
                                    object_type: 'node',
                                    status: feature.status
                                });

                                // Travel direction along from_way clarifies the turn restriction
                                var p1 = feature.segments[0].points[0];
                                var p2 = feature.segments[0].points[1];

                                var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));

                                // Variables used in the description
                                d.replacements = {
                                    num_passed: feature.numberOfPasses,
                                    num_trips: feature.segments[0].numberOfTrips,
                                    turn_restriction: feature.turnType.toLowerCase(),
                                    from_way: linkEntity('w' + from_way),
                                    to_way: linkEntity('w' + to_way),
                                    travel_direction: dir_of_travel,
                                    junction: linkErrorObject(t('QA.keepRight.error_parts.this_node'))
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }
                    }
                );
            });

            _erCache.inflightTile[tile.id] = requests;
            dispatch.call('loaded');
        });
    },

    getComments: function(d, callback) {
        // If comments already retrieved no need to do so again
        if (d.comments !== undefined) { return callback({}, d); }

        var key = d.error_key;
        var qParams = {};

        if (key === 'ow') {
            qParams = d.identifier;
        } else if (key === 'mr') {
            qParams.tileX = d.identifier.x;
            qParams.tileY = d.identifier.y;
        } else if (key === 'tr') {
            qParams.targetId = d.identifier;
        }

        var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams);

        var that = this;
        d3_json(url, function(err, data) {
            // comments are served newest to oldest
            var comments = data.comments ? data.comments.reverse() : [];

            that.replaceError(d.update({
                comments: comments
            }));
            return callback(err, d);
        });
    },

    postUpdate: function(d, callback) {
        if (!services.osm.authenticated()) { // Username required in payload
            return callback({ message: 'Not Authenticated', status: -3}, d);
        }
        if (_erCache.inflightPost[d.id]) {
            return callback({ message: 'Error update already inflight', status: -2 }, d);
        }

        var that = this;

        // Payload can only be sent once username is established
        services.osm.userDetails(sendPayload);

        function sendPayload(err, user) {
            if (err) { return callback(err, d); }

            var key = d.error_key;
            var url = _impOsmUrls[key] + '/comment';
            var payload = {
                username: user.display_name
            };

            // Each error type has different data for identification
            if (key === 'ow') {
                payload.roadSegments = [ d.identifier ];
            } else if (key === 'mr') {
                payload.tiles = [ d.identifier ];
            } else if (key === 'tr') {
                payload.targetIds = [ d.identifier ];
            }

            if (d.newStatus !== undefined) {
                payload.status = d.newStatus;
                payload.text = 'status changed';
            }

            // Comment take place of default text
            if (d.newComment !== undefined) {
                payload.text = d.newComment;
            }

            _erCache.inflightPost[d.id] = d3_request(url)
                .header('Content-Type', 'application/json')
                .post(JSON.stringify(payload), function(err) {
                    delete _erCache.inflightPost[d.id];

                    // Unsuccessful response status, keep issue open
                    if (err.status !== 200) { return callback(err, d); }

                    // Just a comment, update error in cache
                    if (d.newStatus === undefined) {
                        var now = new Date();
                        var comments = d.comments ? d.comments : [];

                        comments.push({
                            username: payload.username,
                            text: payload.text,
                            timestamp: now.getTime() / 1000
                        });

                        that.replaceError(d.update({
                            comments: comments,
                            newComment: undefined
                        }));
                    } else {
                        that.removeError(d);

                        if (d.newStatus === 'SOLVED') {
                            // No pretty identifier, so we just use coordinates
                            var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
                            _erCache.closed[key + ':' + closedID] = true;
                        }
                    }

                    return callback(err, d);
                });
        }
    },

    // get all cached errors covering the viewport
    getErrors: function(projection) {
        var viewport = projection.clipExtent();
        var min = [viewport[0][0], viewport[1][1]];
        var max = [viewport[1][0], viewport[0][1]];
        var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();

        return _erCache.rtree.search(bbox).map(function(d) {
            return d.data;
        });
    },

    // get a single error from the cache
    getError: function(id) {
        return _erCache.data[id];
    },

    // replace a single error in the cache
    replaceError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        _erCache.data[error.id] = error;
        updateRtree(encodeErrorRtree(error), true); // true = replace
        return error;
    },

    // remove a single error from the cache
    removeError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        delete _erCache.data[error.id];
        updateRtree(encodeErrorRtree(error), false); // false = remove
    },

    // Used to populate `closed:improveosm` changeset tag
    getClosedIDs: function() {
        return Object.keys(_erCache.closed).sort();
    }
};<|endoftext|>


<commit_before>'use strict';
var React = require('react');
var _ = require('underscore');
var globals = require('../globals');
var RestMixin = require('../rest').RestMixin;

var VariantCurationHeader = require('./header').VariantCurationHeader;
var VariantCurationActions = require('./actions').VariantCurationActions;
var VariantCurationInterpretation = require('./interpretation').VariantCurationInterpretation;
var SO_terms = require('./interpretation/mapping/SO_term.json');
var genomic_chr_mapping = require('./interpretation/mapping/NC_genomic_chr_format.json');
var external_url_map = globals.external_url_map;
var dbxref_prefix_map = globals.dbxref_prefix_map;
var queryKeyValue = globals.queryKeyValue;
var parseClinvar = require('../../libs/parse-resources').parseClinvar;
import { getHgvsNotation } from './helpers/hgvs_notation';
import { setPrimaryTranscript } from './helpers/primary_transcript';
import { getClinvarRCVs, parseClinvarInterpretation } from './helpers/clinvar_interpretations';

var CurationInterpretationCriteria = require('./interpretation/criteria').CurationInterpretationCriteria;

// Variant Curation Hub
var VariantCurationHub = React.createClass({
    mixins: [RestMixin],

    getInitialState: function() {
        return {
            variantUuid: queryKeyValue('variant', this.props.href),
            interpretationUuid: queryKeyValue('interpretation', this.props.href),
            interpretation: null,
            editKey: queryKeyValue('edit', this.props.href),
            variantObj: null,
            isLoadingComplete: false,
            ext_myVariantInfo: null,
            ext_bustamante: null,
            ext_ensemblVEP: null,
            ext_ensemblVariation: null,
            ext_ensemblHgvsVEP: null,
            ext_clinvarEutils: null,
            ext_clinVarEsearch: null,
            ext_clinVarRCV: null,
            ext_myGeneInfo_MyVariant: null,
            ext_myGeneInfo_VEP: null
        };
    },

    componentDidMount: function() {
        this.getClinVarData(this.state.variantUuid);
        if (this.state.interpretationUuid) {
            this.getRestData('/interpretation/' + this.state.interpretationUuid).then(interpretation => {
                this.setState({interpretation: interpretation});
            });
        }
    },

    // Retrieve the variant object from db with the given uuid
    getClinVarData: function(uuid) {
        return this.getRestData('/variants/' + uuid, null, true).then(response => {
            // The variant object successfully retrieved
            this.setState({variantObj: response});
            this.setState({isLoadingComplete: true});
            // ping out external resources (all async)
            this.fetchClinVarEutils(this.state.variantObj);
            this.fetchMyVariantInfoAndBustamante(this.state.variantObj);
            this.fetchEnsemblVEP(this.state.variantObj);
            this.fetchEnsemblVariation(this.state.variantObj);
            this.fetchEnsemblHGVSVEP(this.state.variantObj);
        }).catch(function(e) {
            console.log('FETCH CLINVAR ERROR=: %o', e);
        });
    },

    // Retrieve ClinVar data from Eutils
    fetchClinVarEutils: function(variant) {
        if (variant) {
            if (variant.clinvarVariantId) {
                this.setState({clinvar_id: variant.clinvarVariantId});
                // Get ClinVar data via the parseClinvar method defined in parse-resources.js
                this.getRestDataXml(this.props.href_url.protocol + external_url_map['ClinVarEutils'] + variant.clinvarVariantId).then(xml => {
                    // Passing 'true' option to invoke 'mixin' function
                    // To extract more ClinVar data for 'Basic Information' tab
                    var variantData = parseClinvar(xml, true);
                    this.setState({ext_clinvarEutils: variantData});
                    this.handleCodonEsearch(variantData);
                    let clinVarRCVs = getClinvarRCVs(xml);
                    return Promise.resolve(clinVarRCVs);
                }).then(RCVs => {
                    // If RCVs is not an empty array,
                    // parse associated disease and clinical significance for each id
                    if (RCVs.length) {
                        let clinvarInterpretations = [];
                        for (let RCV of RCVs.values()) {
                            this.getRestDataXml(this.props.href_url.protocol + external_url_map['ClinVarEfetch'] + '&rettype=clinvarset&id=' + RCV).then(result => {
                                let clinvarInterpretation = parseClinvarInterpretation(RCV, result);
                                clinvarInterpretations.push(clinvarInterpretation);
                                this.setState({ext_clinVarRCV: clinvarInterpretations});
                            }).catch(function(e) {
                                console.log('ClinVarEfetch for RCV Error=: %o', e);
                            });
                        }
                    }
                }).catch(function(e) {
                    console.log('ClinVarEutils Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from MyVariantInfo and Bustamante data
    fetchMyVariantInfoAndBustamante: function(variant) {
        if (variant) {
            let hgvs_notation = getHgvsNotation(variant, 'GRCh37');
            if (hgvs_notation) {
                this.getRestData(this.props.href_url.protocol + external_url_map['MyVariantInfo'] + hgvs_notation).then(response => {
                    this.setState({ext_myVariantInfo: response});
                    this.parseMyVariantInfo(response);
                    // check dbsnfp data for bustamante query
                    var hgvsObj = {};
                    if (response.dbnsfp) {
                        hgvsObj.chrom = (response.dbnsfp.chrom) ? response.dbnsfp.chrom : null;
                        hgvsObj.pos = (response.dbnsfp.hg19.start) ? response.dbnsfp.hg19.start : null;
                        hgvsObj.alt = (response.dbnsfp.alt) ? response.dbnsfp.alt : null;
                        return Promise.resolve(hgvsObj);
                    } else if (response.clinvar) {
                        hgvsObj.chrom = (response.clinvar.chrom) ? response.clinvar.chrom : null;
                        hgvsObj.pos = (response.clinvar.hg19.start) ? response.clinvar.hg19.start : null;
                        hgvsObj.alt = (response.clinvar.alt) ? response.clinvar.alt : null;
                        return Promise.resolve(hgvsObj);
                    }
                }).then(data => {
                    this.getRestData('https:' + external_url_map['Bustamante'] + data.chrom + '/' + data.pos + '/' + data.alt + '/').then(result => {
                        this.setState({ext_bustamante: result});
                    });
                }).catch(function(e) {
                    console.log('MyVariant or Bustamante Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl VEP
    fetchEnsemblVEP: function(variant) {
        if (variant) {
            // Extract only the number portion of the dbSNP id
            var numberPattern = /\d+/g;
            var rsid = (variant.dbSNPIds && variant.dbSNPIds.length > 0) ? variant.dbSNPIds[0].match(numberPattern) : null;
            if (rsid) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblVEP'] + 'rs' + rsid + '?content-type=application/json').then(response => {
                    this.setState({ext_ensemblVEP: response});
                }).catch(function(e) {
                    console.log('VEP Allele Frequency Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl Variation
    fetchEnsemblVariation: function(variant) {
        if (variant) {
            // Extract only the number portion of the dbSNP id
            var numberPattern = /\d+/g;
            var rsid = (variant.dbSNPIds && variant.dbSNPIds.length > 0) ? variant.dbSNPIds[0].match(numberPattern) : null;
            if (rsid) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblVariation'] + 'rs' + rsid + '?content-type=application/json;pops=1;population_genotypes=1').then(response => {
                    this.setState({ext_ensemblVariation: response});
                    //this.parseTGenomesData(response);
                    //this.calculateHighestMAF();
                }).catch(function(e) {
                    console.log('Ensembl Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl HGVS VEP
    fetchEnsemblHGVSVEP: function(variant) {
        if (variant) {
            let hgvs_notation = getHgvsNotation(variant, 'GRCh38', true);
            let request_params = '?content-type=application/json&hgvs=1&protein=1&xref_refseq=1&ExAC=1&MaxEntScan=1&GeneSplicer=1&Conservation=1&numbers=1&domains=1&canonical=1&merged=1';
            if (hgvs_notation) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblHgvsVEP'] + hgvs_notation + request_params).then(response => {
                    this.setState({ext_ensemblHgvsVEP: response});
                    this.parseEnsemblHgvsVEP(response);
                }).catch(function(e) {
                    console.log('Ensembl Fetch Error=: %o', e);
                });
            }
        }
    },

    // Method to parse gene symbol and id from myvariant.info
    parseMyVariantInfo: function(myVariantInfo) {
        let geneSymbol, geneId;
        if (myVariantInfo) {
            if (myVariantInfo.clinvar) {
                geneSymbol = myVariantInfo.clinvar.gene.symbol;
                geneId = myVariantInfo.clinvar.gene.id;
            } else if (myVariantInfo.dbsnp) {
                geneSymbol = myVariantInfo.dbsnp.gene.symbol;
                geneId = myVariantInfo.dbsnp.gene.geneid;
            } else if (myVariantInfo.cadd) {
                geneSymbol = myVariantInfo.cadd.gene.genename;
                geneId = myVariantInfo.cadd.gene.gene_id;
            }
            this.fetchMyGeneInfo(geneSymbol, geneId, 'myVariantInfo');
        }
    },

    // Method to parse gene symbol and id from VEP
    parseEnsemblHgvsVEP: function(ensemblHgvsVEP) {
        let geneSymbol, geneId;
        if (ensemblHgvsVEP) {
            let primaryTranscript = setPrimaryTranscript(ensemblHgvsVEP);
            if (primaryTranscript) {
                geneSymbol = primaryTranscript.gene_symbol;
                geneId = primaryTranscript.gene_id;
            }
            this.fetchMyGeneInfo(geneSymbol, geneId, 'ensemblHgvsVEP');
        }
    },

    // Method to fetch Gene-centric data from mygene.info
    // and pass the data object to child component
    fetchMyGeneInfo: function(geneSymbol, geneId, source) {
        let synonyms, geneObj;
        if (geneSymbol) {
            this.getRestData('/genes/' + geneSymbol).then(response => {
                synonyms = response.synonyms;
            }).catch(function(e) {
                console.log('Local Gene Fetch ERROR=: %o', e);
            });
        }
        if (geneId) {
            let fields = 'fields=entrezgene,exac,HGNC,MIM,homologene.id,interpro,name,pathway.kegg,pathway.netpath,pathway.pid,pdb,pfam,pharmgkb,prosite,uniprot.Swiss-Prot,summary,symbol';
            this.getRestData(this.props.href_url.protocol + external_url_map['MyGeneInfo'] + geneId + '&species=human&' + fields).then(result => {
                geneObj = result.hits[0];
                geneObj.synonyms = synonyms;
                if (source === 'myVariantInfo') {
                    this.setState({ext_myGeneInfo_MyVariant: geneObj});
                } else if (source === 'ensemblHgvsVEP') {
                    this.setState({ext_myGeneInfo_VEP: geneObj});
                }
            }).catch(function(e) {
                console.log('MyGeneInfo Fetch Error=: %o', e);
            });
        }
    },

    // Retrieve codon data from ClinVar Esearch given Eutils/ClinVar response
    handleCodonEsearch: function(response) {
        let aminoAcidLocation = response.allele.ProteinChange;
        let symbol = response.gene.symbol;
        if (aminoAcidLocation && symbol) {
            let term = aminoAcidLocation.substr(0, aminoAcidLocation.length-1);
            this.getRestData(this.props.href_url.protocol + external_url_map['ClinVarEsearch'] + 'db=clinvar&term=' + term + '+%5Bvariant+name%5D+and+' + symbol + '&retmode=json').then(result => {
                // pass in these additional values, in case receiving component needs them
                result.vci_term = term;
                result.vci_symbol = symbol;
                this.setState({ext_clinVarEsearch: result});
            }).catch(function(e) {
                console.log('ClinVarEsearch Fetch Error=: %o', e);
            });
        }
    },

    // method to update the interpretation object and send it down to child components on demand
    updateInterpretationObj: function() {
        this.getRestData('/interpretation/' + this.state.interpretationUuid).then(interpretation => {
            this.setState({interpretation: interpretation});
        });
    },

    render: function() {
        var variantData = this.state.variantObj;
        var interpretation = (this.state.interpretation) ? this.state.interpretation : null;
        var interpretationUuid = (this.state.interpretationUuid) ? this.state.interpretationUuid : null;
        var editKey = this.state.editKey;
        var isLoadingComplete = this.state.isLoadingComplete;
        var session = (this.props.session && Object.keys(this.props.session).length) ? this.props.session : null;

        return (
            <div>
                <VariantCurationHeader variantData={variantData} interpretationUuid={interpretationUuid} session={session} interpretation={interpretation} />
                <CurationInterpretationCriteria interpretation={interpretation} />
                <VariantCurationActions variantData={variantData} interpretation={interpretation} editKey={editKey} session={session}
                    href_url={this.props.href} updateInterpretationObj={this.updateInterpretationObj} />
                <VariantCurationInterpretation variantData={variantData} interpretation={interpretation} editKey={editKey} session={session}
                    href_url={this.props.href_url} updateInterpretationObj={this.updateInterpretationObj}
                    ext_myGeneInfo={(this.state.ext_myGeneInfo_MyVariant) ? this.state.ext_myGeneInfo_MyVariant : this.state.ext_myGeneInfo_VEP}
                    ext_myVariantInfo={this.state.ext_myVariantInfo}
                    ext_bustamante={this.state.ext_bustamante}
                    ext_ensemblVEP={this.state.ext_ensemblVEP}
                    ext_ensemblVariation={this.state.ext_ensemblVariation}
                    ext_ensemblHgvsVEP={this.state.ext_ensemblHgvsVEP}
                    ext_clinvarEutils={this.state.ext_clinvarEutils}
                    ext_clinVarEsearch={this.state.ext_clinVarEsearch}
                    ext_clinVarRCV={this.state.ext_clinVarRCV} />
            </div>
        );
    }
});

globals.curator_page.register(VariantCurationHub, 'curator_page', 'variant-central');
<commit_msg>Added if statement for geneObj.synonyms property<commit_after>'use strict';
var React = require('react');
var _ = require('underscore');
var globals = require('../globals');
var RestMixin = require('../rest').RestMixin;

var VariantCurationHeader = require('./header').VariantCurationHeader;
var VariantCurationActions = require('./actions').VariantCurationActions;
var VariantCurationInterpretation = require('./interpretation').VariantCurationInterpretation;
var SO_terms = require('./interpretation/mapping/SO_term.json');
var genomic_chr_mapping = require('./interpretation/mapping/NC_genomic_chr_format.json');
var external_url_map = globals.external_url_map;
var dbxref_prefix_map = globals.dbxref_prefix_map;
var queryKeyValue = globals.queryKeyValue;
var parseClinvar = require('../../libs/parse-resources').parseClinvar;
import { getHgvsNotation } from './helpers/hgvs_notation';
import { setPrimaryTranscript } from './helpers/primary_transcript';
import { getClinvarRCVs, parseClinvarInterpretation } from './helpers/clinvar_interpretations';

var CurationInterpretationCriteria = require('./interpretation/criteria').CurationInterpretationCriteria;

// Variant Curation Hub
var VariantCurationHub = React.createClass({
    mixins: [RestMixin],

    getInitialState: function() {
        return {
            variantUuid: queryKeyValue('variant', this.props.href),
            interpretationUuid: queryKeyValue('interpretation', this.props.href),
            interpretation: null,
            editKey: queryKeyValue('edit', this.props.href),
            variantObj: null,
            isLoadingComplete: false,
            ext_myVariantInfo: null,
            ext_bustamante: null,
            ext_ensemblVEP: null,
            ext_ensemblVariation: null,
            ext_ensemblHgvsVEP: null,
            ext_clinvarEutils: null,
            ext_clinVarEsearch: null,
            ext_clinVarRCV: null,
            ext_myGeneInfo_MyVariant: null,
            ext_myGeneInfo_VEP: null
        };
    },

    componentDidMount: function() {
        this.getClinVarData(this.state.variantUuid);
        if (this.state.interpretationUuid) {
            this.getRestData('/interpretation/' + this.state.interpretationUuid).then(interpretation => {
                this.setState({interpretation: interpretation});
            });
        }
    },

    // Retrieve the variant object from db with the given uuid
    getClinVarData: function(uuid) {
        return this.getRestData('/variants/' + uuid, null, true).then(response => {
            // The variant object successfully retrieved
            this.setState({variantObj: response});
            this.setState({isLoadingComplete: true});
            // ping out external resources (all async)
            this.fetchClinVarEutils(this.state.variantObj);
            this.fetchMyVariantInfoAndBustamante(this.state.variantObj);
            this.fetchEnsemblVEP(this.state.variantObj);
            this.fetchEnsemblVariation(this.state.variantObj);
            this.fetchEnsemblHGVSVEP(this.state.variantObj);
        }).catch(function(e) {
            console.log('FETCH CLINVAR ERROR=: %o', e);
        });
    },

    // Retrieve ClinVar data from Eutils
    fetchClinVarEutils: function(variant) {
        if (variant) {
            if (variant.clinvarVariantId) {
                this.setState({clinvar_id: variant.clinvarVariantId});
                // Get ClinVar data via the parseClinvar method defined in parse-resources.js
                this.getRestDataXml(this.props.href_url.protocol + external_url_map['ClinVarEutils'] + variant.clinvarVariantId).then(xml => {
                    // Passing 'true' option to invoke 'mixin' function
                    // To extract more ClinVar data for 'Basic Information' tab
                    var variantData = parseClinvar(xml, true);
                    this.setState({ext_clinvarEutils: variantData});
                    this.handleCodonEsearch(variantData);
                    let clinVarRCVs = getClinvarRCVs(xml);
                    return Promise.resolve(clinVarRCVs);
                }).then(RCVs => {
                    // If RCVs is not an empty array,
                    // parse associated disease and clinical significance for each id
                    if (RCVs.length) {
                        let clinvarInterpretations = [];
                        for (let RCV of RCVs.values()) {
                            this.getRestDataXml(this.props.href_url.protocol + external_url_map['ClinVarEfetch'] + '&rettype=clinvarset&id=' + RCV).then(result => {
                                let clinvarInterpretation = parseClinvarInterpretation(RCV, result);
                                clinvarInterpretations.push(clinvarInterpretation);
                                this.setState({ext_clinVarRCV: clinvarInterpretations});
                            }).catch(function(e) {
                                console.log('ClinVarEfetch for RCV Error=: %o', e);
                            });
                        }
                    }
                }).catch(function(e) {
                    console.log('ClinVarEutils Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from MyVariantInfo and Bustamante data
    fetchMyVariantInfoAndBustamante: function(variant) {
        if (variant) {
            let hgvs_notation = getHgvsNotation(variant, 'GRCh37');
            if (hgvs_notation) {
                this.getRestData(this.props.href_url.protocol + external_url_map['MyVariantInfo'] + hgvs_notation).then(response => {
                    this.setState({ext_myVariantInfo: response});
                    this.parseMyVariantInfo(response);
                    // check dbsnfp data for bustamante query
                    var hgvsObj = {};
                    if (response.dbnsfp) {
                        hgvsObj.chrom = (response.dbnsfp.chrom) ? response.dbnsfp.chrom : null;
                        hgvsObj.pos = (response.dbnsfp.hg19.start) ? response.dbnsfp.hg19.start : null;
                        hgvsObj.alt = (response.dbnsfp.alt) ? response.dbnsfp.alt : null;
                        return Promise.resolve(hgvsObj);
                    } else if (response.clinvar) {
                        hgvsObj.chrom = (response.clinvar.chrom) ? response.clinvar.chrom : null;
                        hgvsObj.pos = (response.clinvar.hg19.start) ? response.clinvar.hg19.start : null;
                        hgvsObj.alt = (response.clinvar.alt) ? response.clinvar.alt : null;
                        return Promise.resolve(hgvsObj);
                    }
                }).then(data => {
                    this.getRestData('https:' + external_url_map['Bustamante'] + data.chrom + '/' + data.pos + '/' + data.alt + '/').then(result => {
                        this.setState({ext_bustamante: result});
                    });
                }).catch(function(e) {
                    console.log('MyVariant or Bustamante Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl VEP
    fetchEnsemblVEP: function(variant) {
        if (variant) {
            // Extract only the number portion of the dbSNP id
            var numberPattern = /\d+/g;
            var rsid = (variant.dbSNPIds && variant.dbSNPIds.length > 0) ? variant.dbSNPIds[0].match(numberPattern) : null;
            if (rsid) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblVEP'] + 'rs' + rsid + '?content-type=application/json').then(response => {
                    this.setState({ext_ensemblVEP: response});
                }).catch(function(e) {
                    console.log('VEP Allele Frequency Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl Variation
    fetchEnsemblVariation: function(variant) {
        if (variant) {
            // Extract only the number portion of the dbSNP id
            var numberPattern = /\d+/g;
            var rsid = (variant.dbSNPIds && variant.dbSNPIds.length > 0) ? variant.dbSNPIds[0].match(numberPattern) : null;
            if (rsid) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblVariation'] + 'rs' + rsid + '?content-type=application/json;pops=1;population_genotypes=1').then(response => {
                    this.setState({ext_ensemblVariation: response});
                    //this.parseTGenomesData(response);
                    //this.calculateHighestMAF();
                }).catch(function(e) {
                    console.log('Ensembl Fetch Error=: %o', e);
                });
            }
        }
    },

    // Retrieve data from Ensembl HGVS VEP
    fetchEnsemblHGVSVEP: function(variant) {
        if (variant) {
            let hgvs_notation = getHgvsNotation(variant, 'GRCh38', true);
            let request_params = '?content-type=application/json&hgvs=1&protein=1&xref_refseq=1&ExAC=1&MaxEntScan=1&GeneSplicer=1&Conservation=1&numbers=1&domains=1&canonical=1&merged=1';
            if (hgvs_notation) {
                this.getRestData(this.props.href_url.protocol + external_url_map['EnsemblHgvsVEP'] + hgvs_notation + request_params).then(response => {
                    this.setState({ext_ensemblHgvsVEP: response});
                    this.parseEnsemblHgvsVEP(response);
                }).catch(function(e) {
                    console.log('Ensembl Fetch Error=: %o', e);
                });
            }
        }
    },

    // Method to parse gene symbol and id from myvariant.info
    parseMyVariantInfo: function(myVariantInfo) {
        let geneSymbol, geneId;
        if (myVariantInfo) {
            if (myVariantInfo.clinvar) {
                geneSymbol = myVariantInfo.clinvar.gene.symbol;
                geneId = myVariantInfo.clinvar.gene.id;
            } else if (myVariantInfo.dbsnp) {
                geneSymbol = myVariantInfo.dbsnp.gene.symbol;
                geneId = myVariantInfo.dbsnp.gene.geneid;
            } else if (myVariantInfo.cadd) {
                geneSymbol = myVariantInfo.cadd.gene.genename;
                geneId = myVariantInfo.cadd.gene.gene_id;
            }
            this.fetchMyGeneInfo(geneSymbol, geneId, 'myVariantInfo');
        }
    },

    // Method to parse gene symbol and id from VEP
    parseEnsemblHgvsVEP: function(ensemblHgvsVEP) {
        let geneSymbol, geneId;
        if (ensemblHgvsVEP) {
            let primaryTranscript = setPrimaryTranscript(ensemblHgvsVEP);
            if (primaryTranscript) {
                geneSymbol = primaryTranscript.gene_symbol;
                geneId = primaryTranscript.gene_id;
            }
            this.fetchMyGeneInfo(geneSymbol, geneId, 'ensemblHgvsVEP');
        }
    },

    // Method to fetch Gene-centric data from mygene.info
    // and pass the data object to child component
    fetchMyGeneInfo: function(geneSymbol, geneId, source) {
        let synonyms = [], geneObj = {};
        if (geneSymbol) {
            this.getRestData('/genes/' + geneSymbol).then(response => {
                synonyms = response.synonyms;
            }).catch(function(e) {
                console.log('Local Gene Fetch ERROR=: %o', e);
            });
        }
        if (geneId) {
            let fields = 'fields=entrezgene,exac,HGNC,MIM,homologene.id,interpro,name,pathway.kegg,pathway.netpath,pathway.pid,pdb,pfam,pharmgkb,prosite,uniprot.Swiss-Prot,summary,symbol';
            this.getRestData(this.props.href_url.protocol + external_url_map['MyGeneInfo'] + geneId + '&species=human&' + fields).then(result => {
                geneObj = result.hits[0];
                if (synonyms) {
                    geneObj.synonyms = synonyms;
                }
                if (source === 'myVariantInfo') {
                    this.setState({ext_myGeneInfo_MyVariant: geneObj});
                } else if (source === 'ensemblHgvsVEP') {
                    this.setState({ext_myGeneInfo_VEP: geneObj});
                }
            }).catch(function(e) {
                console.log('MyGeneInfo Fetch Error=: %o', e);
            });
        }
    },

    // Retrieve codon data from ClinVar Esearch given Eutils/ClinVar response
    handleCodonEsearch: function(response) {
        let aminoAcidLocation = response.allele.ProteinChange;
        let symbol = response.gene.symbol;
        if (aminoAcidLocation && symbol) {
            let term = aminoAcidLocation.substr(0, aminoAcidLocation.length-1);
            this.getRestData(this.props.href_url.protocol + external_url_map['ClinVarEsearch'] + 'db=clinvar&term=' + term + '+%5Bvariant+name%5D+and+' + symbol + '&retmode=json').then(result => {
                // pass in these additional values, in case receiving component needs them
                result.vci_term = term;
                result.vci_symbol = symbol;
                this.setState({ext_clinVarEsearch: result});
            }).catch(function(e) {
                console.log('ClinVarEsearch Fetch Error=: %o', e);
            });
        }
    },

    // method to update the interpretation object and send it down to child components on demand
    updateInterpretationObj: function() {
        this.getRestData('/interpretation/' + this.state.interpretationUuid).then(interpretation => {
            this.setState({interpretation: interpretation});
        });
    },

    render: function() {
        var variantData = this.state.variantObj;
        var interpretation = (this.state.interpretation) ? this.state.interpretation : null;
        var interpretationUuid = (this.state.interpretationUuid) ? this.state.interpretationUuid : null;
        var editKey = this.state.editKey;
        var isLoadingComplete = this.state.isLoadingComplete;
        var session = (this.props.session && Object.keys(this.props.session).length) ? this.props.session : null;

        return (
            <div>
                <VariantCurationHeader variantData={variantData} interpretationUuid={interpretationUuid} session={session} interpretation={interpretation} />
                <CurationInterpretationCriteria interpretation={interpretation} />
                <VariantCurationActions variantData={variantData} interpretation={interpretation} editKey={editKey} session={session}
                    href_url={this.props.href} updateInterpretationObj={this.updateInterpretationObj} />
                <VariantCurationInterpretation variantData={variantData} interpretation={interpretation} editKey={editKey} session={session}
                    href_url={this.props.href_url} updateInterpretationObj={this.updateInterpretationObj}
                    ext_myGeneInfo={(this.state.ext_myGeneInfo_MyVariant) ? this.state.ext_myGeneInfo_MyVariant : this.state.ext_myGeneInfo_VEP}
                    ext_myVariantInfo={this.state.ext_myVariantInfo}
                    ext_bustamante={this.state.ext_bustamante}
                    ext_ensemblVEP={this.state.ext_ensemblVEP}
                    ext_ensemblVariation={this.state.ext_ensemblVariation}
                    ext_ensemblHgvsVEP={this.state.ext_ensemblHgvsVEP}
                    ext_clinvarEutils={this.state.ext_clinvarEutils}
                    ext_clinVarEsearch={this.state.ext_clinVarEsearch}
                    ext_clinVarRCV={this.state.ext_clinVarRCV} />
            </div>
        );
    }
});

globals.curator_page.register(VariantCurationHub, 'curator_page', 'variant-central');
<|endoftext|>