<commit_before>/*global Pusher*/

import { extend } from 'flarum/extend';
import app from 'flarum/app';
import DiscussionList from 'flarum/components/DiscussionList';
import DiscussionPage from 'flarum/components/DiscussionPage';
import IndexPage from 'flarum/components/IndexPage';
import Button from 'flarum/components/Button';

app.initializers.add('flarum-pusher', () => {
  const loadPusher = new Promise((resolve) => {
    $.getScript('//cdn.jsdelivr.net/npm/pusher-js@3.0.0/dist/pusher.min.js', () => {
      const socket = new Pusher(app.forum.attribute('pusherKey'), {
        authEndpoint: app.forum.attribute('apiUrl') + '/pusher/auth',
        cluster: app.forum.attribute('pusherCluster'),
        auth: {
          headers: {
            'X-CSRF-Token': app.session.csrfToken
          }
        }
      });

      return resolve({
        main: socket.subscribe('public'),
        user: app.session.user ? socket.subscribe('private-user' + app.session.user.id()) : null
      });
    });
  });

  app.pusher = loadPusher;
  app.pushedUpdates = [];

  extend(DiscussionList.prototype, 'oncreate', function() {
    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const params = app.discussions.getParams();

        if (!params.q && !params.sort && !params.filter) {
          if (params.tags) {
            const tag = app.store.getBy('tags', 'slug', params.tags);

            if (data.tagIds.indexOf(tag.id()) === -1) return;
          }

          const id = String(data.discussionId);

          if ((!app.current.get('discussion') || id !== app.current.get('discussion').id()) && app.pushedUpdates.indexOf(id) === -1) {
            app.pushedUpdates.push(id);

            if (app.current.matches(IndexPage)) {
              app.setTitleCount(app.pushedUpdates.length);
            }

            m.redraw();
          }
        }
      });
    });
  });

  extend(DiscussionList.prototype, 'onremove', function () {
    app.pusher.then(channels => {
      channels.main.unbind('newPost');
    });
  });

  extend(DiscussionList.prototype, 'view', function(vdom) {
    if (app.pushedUpdates) {
      const count = app.pushedUpdates.length;

      if (count) {
        vdom.children.unshift(
          Button.component({
            className: 'Button Button--block DiscussionList-update',
            onclick: () => {
              this.refresh(false).then(() => {
                this.loadingUpdated = false;
                app.pushedUpdates = [];
                app.setTitleCount(0);
                m.redraw();
              });
              this.loadingUpdated = true;
            },
            loading: this.loadingUpdated
          }, app.translator.transChoice('flarum-pusher.forum.discussion_list.show_updates_text', count, { count }))
        );
      }
    }
  });

  // Prevent any newly-created discussions from triggering the discussion list
  // update button showing.
  // TODO: Might be better pause the response to the push updates while the
  // composer is loading? idk
  extend(DiscussionList.prototype, 'addDiscussion', function(returned, discussion) {
    const index = app.pushedUpdates.indexOf(discussion.id());

    if (index !== -1) {
      app.pushedUpdates.splice(index, 1);
    }

    if (app.current.matches(IndexPage)) {
      app.setTitleCount(app.pushedUpdates.length);
    }

    m.redraw();
  });

  extend(DiscussionPage.prototype, 'oncreate', function() {
    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const id = String(data.discussionId);

        if (this.discussion && this.discussion.id() === id && this.stream) {
          const oldCount = this.discussion.commentCount();

          app.store.find('discussions', this.discussion.id()).then(() => {
            this.stream.update();

            if (!document.hasFocus()) {
              app.setTitleCount(Math.max(0, this.discussion.commentCount() - oldCount));

              $(window).one('focus', () => app.setTitleCount(0));
            }
          });
        }
      });
    });
  });

  extend(DiscussionPage.prototype, 'onremove', function () {
    app.pusher.then(channels => {
      channels.main.unbind('newPost')
    });
  });

  extend(IndexPage.prototype, 'actionItems', items => {
    items.remove('refresh');
  });

  app.pusher.then(channels => {
    if (channels.user) {
      channels.user.bind('notification', () => {
        app.session.user.pushAttributes({
          unreadNotificationCount: app.session.user.unreadNotificationCount() + 1,
          newNotificationCount: app.session.user.newNotificationCount() + 1
        });
        app.notifications.clear();
        m.redraw();
      });
    }
  });
});
<commit_msg>Fix refresh button (#27)<commit_after>/*global Pusher*/

import { extend } from 'flarum/extend';
import app from 'flarum/app';
import DiscussionList from 'flarum/components/DiscussionList';
import DiscussionPage from 'flarum/components/DiscussionPage';
import IndexPage from 'flarum/components/IndexPage';
import Button from 'flarum/components/Button';

app.initializers.add('flarum-pusher', () => {
  const loadPusher = new Promise((resolve) => {
    $.getScript('//cdn.jsdelivr.net/npm/pusher-js@3.0.0/dist/pusher.min.js', () => {
      const socket = new Pusher(app.forum.attribute('pusherKey'), {
        authEndpoint: app.forum.attribute('apiUrl') + '/pusher/auth',
        cluster: app.forum.attribute('pusherCluster'),
        auth: {
          headers: {
            'X-CSRF-Token': app.session.csrfToken
          }
        }
      });

      return resolve({
        main: socket.subscribe('public'),
        user: app.session.user ? socket.subscribe('private-user' + app.session.user.id()) : null
      });
    });
  });

  app.pusher = loadPusher;
  app.pushedUpdates = [];

  extend(DiscussionList.prototype, 'oncreate', function() {
    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const params = app.discussions.getParams();

        if (!params.q && !params.sort && !params.filter) {
          if (params.tags) {
            const tag = app.store.getBy('tags', 'slug', params.tags);

            if (data.tagIds.indexOf(tag.id()) === -1) return;
          }

          const id = String(data.discussionId);

          if ((!app.current.get('discussion') || id !== app.current.get('discussion').id()) && app.pushedUpdates.indexOf(id) === -1) {
            app.pushedUpdates.push(id);

            if (app.current.matches(IndexPage)) {
              app.setTitleCount(app.pushedUpdates.length);
            }

            m.redraw();
          }
        }
      });
    });
  });

  extend(DiscussionList.prototype, 'onremove', function () {
    app.pusher.then(channels => {
      channels.main.unbind('newPost');
    });
  });

  extend(DiscussionList.prototype, 'view', function(vdom) {
    if (app.pushedUpdates) {
      const count = app.pushedUpdates.length;

      if (count) {
        vdom.children.unshift(
          Button.component({
            className: 'Button Button--block DiscussionList-update',
            onclick: () => {
              this.attrs.state.refresh(false).then(() => {
                this.loadingUpdated = false;
                app.pushedUpdates = [];
                app.setTitleCount(0);
                m.redraw();
              });
              this.loadingUpdated = true;
            },
            loading: this.loadingUpdated
          }, app.translator.transChoice('flarum-pusher.forum.discussion_list.show_updates_text', count, { count }))
        );
      }
    }
  });

  // Prevent any newly-created discussions from triggering the discussion list
  // update button showing.
  // TODO: Might be better pause the response to the push updates while the
  // composer is loading? idk
  extend(DiscussionList.prototype, 'addDiscussion', function(returned, discussion) {
    const index = app.pushedUpdates.indexOf(discussion.id());

    if (index !== -1) {
      app.pushedUpdates.splice(index, 1);
    }

    if (app.current.matches(IndexPage)) {
      app.setTitleCount(app.pushedUpdates.length);
    }

    m.redraw();
  });

  extend(DiscussionPage.prototype, 'oncreate', function() {
    app.pusher.then(channels => {
      channels.main.bind('newPost', data => {
        const id = String(data.discussionId);

        if (this.discussion && this.discussion.id() === id && this.stream) {
          const oldCount = this.discussion.commentCount();

          app.store.find('discussions', this.discussion.id()).then(() => {
            this.stream.update();

            if (!document.hasFocus()) {
              app.setTitleCount(Math.max(0, this.discussion.commentCount() - oldCount));

              $(window).one('focus', () => app.setTitleCount(0));
            }
          });
        }
      });
    });
  });

  extend(DiscussionPage.prototype, 'onremove', function () {
    app.pusher.then(channels => {
      channels.main.unbind('newPost')
    });
  });

  extend(IndexPage.prototype, 'actionItems', items => {
    items.remove('refresh');
  });

  app.pusher.then(channels => {
    if (channels.user) {
      channels.user.bind('notification', () => {
        app.session.user.pushAttributes({
          unreadNotificationCount: app.session.user.unreadNotificationCount() + 1,
          newNotificationCount: app.session.user.newNotificationCount() + 1
        });
        app.notifications.clear();
        m.redraw();
      });
    }
  });
});
<|endoftext|>


<commit_before>(function() {
    //functions managing & updating the game state data, consisting of the `store` object and
    //visualization in the browser.

    var Diversibee = {};

    /**
     * Private methods
     */
    function setUpBoardState(cellsWide, cellsHigh) {
        // initializes board state

        var cells = [],
            distanceFromSeedCell,
            isForest,
            i,
            //The likelihood a cell contains a 'seed'; probability bound 0 <= seedRate <= 1
            seedRate = 0.02,
            //Cells containing an initial 'seed'. Seeds are used to determine the inital forest growth
            seededCells = generateUniqueRandomCells(cellsHigh, cellsWide, (cellsHigh*cellsWide*seedRate));

        for(i=0; i<cellsWide*cellsHigh; i++) {
            distanceFromSeedCell = distancefromSeed(i, cellsWide, seededCells);
            //Does this cell initially become a forest based on the cell's seed proximity
            isForest = shouldGrowForest(distanceFromSeedCell);

            if(isForest) {
		addToCell( i, cells, 'forest' );
            } else {
		addToCell( i, cells, 'grass' );
            }
        }

        return cells;
    }

    function addToCell( i, cells, typeName )
    {
	var currentTypeName = cells[ i ];

	addToTypeCount( Diversibee.store.typeCount[ currentTypeName ], -1 );
	addToTypeCount( Diversibee.store.typeCount[ typeName ], 1 );

    	cells[ i ] = { type: typeName };
    }

    // Add quantity to counter to typeName in store
    // Maintains bounds on type quantities
    function addToTypeCount( typeName, quantity )
    {
	var currentQuantity = Diversibee.store.typeCount[ typeName ];

	if( !currentQuantity ){
		console.log(
				'addToTypeCount(): ' +
			       	typeName +
				' not recognized' );
		return;
	}

	currentQuantity += quantity;
	if( currentQuantity < 0 )
	{
		currentQuantity = 0;
	}

	Diversibee.store.typeCount[ typeName ] = currentQuantity;
    }

    function setAnimation(i) {
        //set up the animation for cell i

        Diversibee.store.mapCell[i] = new createjs.Sprite(Diversibee.store.spriteSheet, Diversibee.store.state[i].type);
        Diversibee.store.mapCell[i].x = 20*(i%Diversibee.store.width);
        Diversibee.store.mapCell[i].y = 20*Math.floor(i/Diversibee.store.width);

        Diversibee.store.mapCell[i].addEventListener('mousedown', clickCell.bind(this, i));
        Diversibee.store.mapCell[i].addEventListener('mouseover', handleMouseOver(i));

        Diversibee.store.stage.addChild(Diversibee.store.mapCell[i]);
        Diversibee.store.mapCell[i].play(Diversibee.store.state[i].type);
    }

    function repaintBoard() {
        //re-paints the board state

        if(Diversibee.store.animationLoop) {
            clearInterval(Diversibee.store.animationLoop);
        }

        Diversibee.store.stage.update();
        Diversibee.store.animationLoop = setInterval(function() {Diversibee.store.stage.update();}, 300);
    }

    function handleMouseOver(i) {
        return function(e) {
            if (e.nativeEvent.buttons === 1 || e.nativeEvent.buttons === 3) {
                clickCell(i);
            }
        };
    }

    function clickCell(i) {
        //handle when a player clicks on the ith cell

    }

    function addToProfits(income) {
        //update the farmer's bank account

        Diversibee.store.profit += income;
        document.getElementById('profitText').innerHTML = '$'+Diversibee.store.cash;
    }

    function addCellProfitsToProfits(i, neighbours) {
        //collect profits from cell i, modified by its nearest neghbours

       	addToProfits(Profits.basicProfits(neighbours));
    }

    function calculateLevelOneProfit()
    {
	    var blueberryCount = Diversibee.store.typeCount.blueberries;
	    var treeCount = Diversibee.store.typeCount.forest;

	    return blueberryCount * treeCount;
    }

    function distancefromSeed(cell, width, seededCells) {
        //Returns the distance from a cell to the closest seed (seeds are given as an array of cell locations)
        var distance = Number.MAX_SAFE_INTEGER,
            i,
            temp_dist;

        for(i=0; i<seededCells.length; i++) {
            temp_dist = distanceBetweenCells(seededCells[i], cell, width);
            distance = Math.min(distance, temp_dist);
        }
        return distance;
    }

    function distanceBetweenCells(cellA, cellB, boardWidth) {
        //returns the distance between cell A and cell B

        var xDiff = (cellA % boardWidth) - (cellB % boardWidth),
            yDiff = (Math.floor(cellA / boardWidth)) - (Math.floor(cellB / boardWidth));
        return Math.abs(xDiff) + Math.abs(yDiff);
    }

    function shouldGrowForest(distanceFromSeed) {
        //A simple forest growth algorithm which clusters around seed locations

        return distanceFromSeed + distanceFromSeed * Math.random() < 5;
    }

    function generateUniqueRandomCells(height, width, numberOfCells) {
        //Returns an array of unique random cells

        var uniqueRandomCells = [],
            i,
            //we can safely choose the first unique random cell number
            cellNumber = Math.floor(Math.random() * (height * width));

        for(i=0; i<numberOfCells; i++) {
            while ( Utils.inArray(cellNumber, uniqueRandomCells) ) {
                cellNumber = Math.floor(Math.random() * (height * width));
            }
            uniqueRandomCells.push(cellNumber);
        }
        return uniqueRandomCells;
    }

    /**
     * Public methods
     */
    Diversibee.init = function(width, height) {
        // Initialize the play area on load

        var i;

        //declare global store with default values
        Diversibee.store.width = width;
        Diversibee.store.height = height;
        Diversibee.store.state = setUpBoardState(Diversibee.store.width, Diversibee.store.height);
        Diversibee.store.animationData = {
            images: ['img/spriteSheet.png'],
            frames: {width:20, height:20},
            animations: {
                grass: [0,3],
                forest: [4,7],
                blueberries: [8,11]
            }
        };

	// Stores current total number of each type, updated by addTypeToCell()
	Diversibee.store.typeCount = {
		grass: 0,
		forest: 0,
		blueberries: 0
	};
        Diversibee.store.stage = new createjs.Stage('board');
        Diversibee.store.stage.enableMouseOver(10);
        Diversibee.store.mapCell = [];
        Diversibee.store.spriteSheet = new createjs.SpriteSheet(Diversibee.store.animationData);
        Diversibee.store.w = Diversibee.store.stage.canvas.width;
        Diversibee.store.h = Diversibee.store.stage.canvas.height;
        Diversibee.store.profit = 1000;
        Diversibee.store.blueberryBuildPrice = 100;

        //set up initial cell animationsL
        for(i=0; i<Diversibee.store.width*Diversibee.store.height; i++) {
            setAnimation(i);
        }
        //paint the initial state of the board
        repaintBoard();
    };

    Diversibee.store = {};

    window.Diversibee = Diversibee;
})();
<commit_msg>Fixed tabbing/spacing (mostly hopefully?) and moved type count to above setup board function as it is used inside.<commit_after>(function() {
    //functions managing & updating the game state data, consisting of the `store` object and
    //visualization in the browser.

    var Diversibee = {};

    /**
     * Private methods
     */
    function setUpBoardState(cellsWide, cellsHigh) {
        // initializes board state

        var cells = [],
            distanceFromSeedCell,
            isForest,
            i,
            //The likelihood a cell contains a 'seed'; probability bound 0 <= seedRate <= 1
            seedRate = 0.02,
            //Cells containing an initial 'seed'. Seeds are used to determine the inital forest growth
            seededCells = generateUniqueRandomCells(cellsHigh, cellsWide, (cellsHigh*cellsWide*seedRate));

        for(i=0; i<cellsWide*cellsHigh; i++) {
            distanceFromSeedCell = distancefromSeed(i, cellsWide, seededCells);
            //Does this cell initially become a forest based on the cell's seed proximity
            isForest = shouldGrowForest(distanceFromSeedCell);

            if(isForest) {
                addToCell( i, cells, 'forest' );
            } else {
                addToCell( i, cells, 'grass' );
            }
        }

        return cells;
    }

    function addToCell( i, cells, typeName )
    {
        var currentTypeName = cells[ i ];

        addToTypeCount( Diversibee.store.typeCount[ currentTypeName ], -1 );
        addToTypeCount( Diversibee.store.typeCount[ typeName ], 1 );
        cells[ i ] = { type: typeName };
    }

    // Add quantity to counter to typeName in store
    // Maintains bounds on type quantities
    function addToTypeCount( typeName, quantity )
    {
        var currentQuantity = Diversibee.store.typeCount[ typeName ];

        if( !currentQuantity ){
            console.log(
                    'addToTypeCount(): ' +
                        typeName +
                    ' not recognized' );
             return;
        }

        currentQuantity += quantity;
        if( currentQuantity < 0 )
        {
            currentQuantity = 0;
        }

        Diversibee.store.typeCount[ typeName ] = currentQuantity;
    }

    function setAnimation(i) {
        //set up the animation for cell i

        Diversibee.store.mapCell[i] = new createjs.Sprite(Diversibee.store.spriteSheet, Diversibee.store.state[i].type);
        Diversibee.store.mapCell[i].x = 20*(i%Diversibee.store.width);
        Diversibee.store.mapCell[i].y = 20*Math.floor(i/Diversibee.store.width);

        Diversibee.store.mapCell[i].addEventListener('mousedown', clickCell.bind(this, i));
        Diversibee.store.mapCell[i].addEventListener('mouseover', handleMouseOver(i));

        Diversibee.store.stage.addChild(Diversibee.store.mapCell[i]);
        Diversibee.store.mapCell[i].play(Diversibee.store.state[i].type);
    }

    function repaintBoard() {
        //re-paints the board state

        if(Diversibee.store.animationLoop) {
            clearInterval(Diversibee.store.animationLoop);
        }

        Diversibee.store.stage.update();
        Diversibee.store.animationLoop = setInterval(function() {Diversibee.store.stage.update();}, 300);
    }

    function handleMouseOver(i) {
        return function(e) {
            if (e.nativeEvent.buttons === 1 || e.nativeEvent.buttons === 3) {
                clickCell(i);
            }
        };
    }

    function clickCell(i) {
        //handle when a player clicks on the ith cell

    }

    function addToProfits(income) {
        //update the farmer's bank account

        Diversibee.store.profit += income;
        document.getElementById('profitText').innerHTML = '$'+Diversibee.store.cash;
    }

    function addCellProfitsToProfits(i, neighbours) {
        //collect profits from cell i, modified by its nearest neghbours

               addToProfits(Profits.basicProfits(neighbours));
    }

    function calculateLevelOneProfit()
    {
            var blueberryCount = Diversibee.store.typeCount.blueberries;
            var treeCount = Diversibee.store.typeCount.forest;

            return blueberryCount * treeCount;
    }

    function distancefromSeed(cell, width, seededCells) {
        //Returns the distance from a cell to the closest seed (seeds are given as an array of cell locations)
        var distance = Number.MAX_SAFE_INTEGER,
            i,
            temp_dist;

        for(i=0; i<seededCells.length; i++) {
            temp_dist = distanceBetweenCells(seededCells[i], cell, width);
            distance = Math.min(distance, temp_dist);
        }
        return distance;
    }

    function distanceBetweenCells(cellA, cellB, boardWidth) {
        //returns the distance between cell A and cell B

        var xDiff = (cellA % boardWidth) - (cellB % boardWidth),
            yDiff = (Math.floor(cellA / boardWidth)) - (Math.floor(cellB / boardWidth));
        return Math.abs(xDiff) + Math.abs(yDiff);
    }

    function shouldGrowForest(distanceFromSeed) {
        //A simple forest growth algorithm which clusters around seed locations

        return distanceFromSeed + distanceFromSeed * Math.random() < 5;
    }

    function generateUniqueRandomCells(height, width, numberOfCells) {
        //Returns an array of unique random cells

        var uniqueRandomCells = [],
            i,
            //we can safely choose the first unique random cell number
            cellNumber = Math.floor(Math.random() * (height * width));

        for(i=0; i<numberOfCells; i++) {
            while ( Utils.inArray(cellNumber, uniqueRandomCells) ) {
                cellNumber = Math.floor(Math.random() * (height * width));
            }
            uniqueRandomCells.push(cellNumber);
        }
        return uniqueRandomCells;
    }

    /**
     * Public methods
     */
    Diversibee.init = function(width, height) {
        // Initialize the play area on load

        var i;

        //declare global store with default values
        Diversibee.store.width = width;
        Diversibee.store.height = height;
        // Stores current total number of each type, updated by addTypeToCell()
        Diversibee.store.typeCount = {
                grass: 0,
                forest: 0,
                blueberries: 0
        };
        Diversibee.store.state = setUpBoardState(Diversibee.store.width, Diversibee.store.height);
        Diversibee.store.animationData = {
            images: ['img/spriteSheet.png'],
            frames: {width:20, height:20},
            animations: {
                grass: [0,3],
                forest: [4,7],
                blueberries: [8,11]
            }
        };

        Diversibee.store.stage = new createjs.Stage('board');
        Diversibee.store.stage.enableMouseOver(10);
        Diversibee.store.mapCell = [];
        Diversibee.store.spriteSheet = new createjs.SpriteSheet(Diversibee.store.animationData);
        Diversibee.store.w = Diversibee.store.stage.canvas.width;
        Diversibee.store.h = Diversibee.store.stage.canvas.height;
        Diversibee.store.profit = 1000;
        Diversibee.store.blueberryBuildPrice = 100;

        //set up initial cell animationsL
        for(i=0; i<Diversibee.store.width*Diversibee.store.height; i++) {
            setAnimation(i);
        }
        //paint the initial state of the board
        repaintBoard();
    };

    Diversibee.store = {};

    window.Diversibee = Diversibee;
})();
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Parser for VCF files.
 */

var igv = (function (igv) {

    igv.createVCFVariant = function (tokens) {

        var variant = new igv.Variant();

        var self = this,
            altTokens;

        variant.chr = tokens[0]; // TODO -- use genome aliases
        variant.pos = parseInt(tokens[1]);
        variant.names = tokens[2];    // id in VCF
        variant.referenceBases = tokens[3];
        variant.alternateBases = tokens[4];
        variant.quality = parseInt(tokens[5]);
        variant.filter = tokens[6];
        variant.info = getInfoObject(tokens[7]);

        variant.str = variant.info["PERIOD"] !== undefined;

        initAlleles(variant);

        return variant;

        function getInfoObject(infoStr) {

            if (!infoStr) return undefined;

            var info = {};
            infoStr.split(';').forEach(function (elem) {
                var element = elem.split('=');
                info[element[0]] = element[1];
            });
            return info;
        };

    }

    igv.createGAVariant = function (json) {

        var variant = new igv.Variant();

        variant.chr = json.referenceName;
        variant.pos = parseInt(json.start);
        variant.names = arrayToCommaString(json.names);
        variant.referenceBases = json.referenceBases + '';
        variant.alternateBases = json.alternateBases + '';
        variant.quality = json.quality;
        variant.filter = arrayToCommaString(json.filter);
        variant.info = json.info;

        variant.str = variant.info["PERIOD"] !== undefined;

        // Need to build a hash of calls for fast lookup
        // Note from the GA4GH spec on call ID:
        //
        // The ID of the call set this variant call belongs to. If this field is not present,
        // the ordering of the call sets from a SearchCallSetsRequest over this GAVariantSet
        // is guaranteed to match the ordering of the calls on this GAVariant.
        // The number of results will also be the same.
        variant.calls = {};
        var order = 0, id;
        if (json.calls) {
            json.calls.forEach(function (call) {
                id = call.callSetId;
                variant.calls[id] = call;
                order++;

            })
        }

        initAlleles(variant);

        return variant;

    }

    function initAlleles(variant) {

        //Alleles
        var altTokens = variant.alternateBases.split(","),
            minAltLength = variant.referenceBases.length,
            maxAltLength = variant.referenceBases.length;

        variant.alleles = [];

        // If an STR define start and end based on reference allele.  Otherwise start and end computed below based
        // on alternate allele type (snp, insertion, deletion)

        if(variant.str) {
            variant.start = variant.pos - 1;
            variant.end = variant.start + variant.referenceBases.length;
        }

        if (variant.alternateBases === ".") {     // "." => no alternate alleles
            variant.heterozygosity = 0;
        } else {
            altTokens.forEach(function (alt) {
                var a, s, e, diff;

                variant.alleles.push(alt);

                // Adjust for padding, used for insertions and deletions, unless variant is a short tandem repeat.

                if (!variant.str && alt.length > 0) {

                    diff = variant.referenceBases.length - alt.length;

                    if (diff > 0) {
                        // deletion, assume left padded
                        s = variant.pos - 1 + alt.length;
                        e = s + diff;
                    } else if (diff < 0) {
                        // Insertion, assume left padded, insertion begins to "right" of last ref base
                        s = variant.pos - 1 + variant.referenceBases.length;
                        e = s + 1;     // Insertion between s & 3
                    } else {
                        s = variant.pos - 1;
                        e = s + 1;
                    }
                    variant.start = variant.start === undefined ? s : Math.min(variant.start, s);
                    variant.end = variant.end === undefined ? e : Math.max(variant.end, e);
                }

                minAltLength = Math.min(minAltLength, alt.length);
                maxAltLength = Math.max(maxAltLength, alt.length);

            });
            if (variant.info.AC && variant.info.AN) {
                variant.heterozygosity = calcHeterozygosity(variant.info.AC, variant.info.AN).toFixed(3);
            }
        }

        // Alternate allele lengths used for STR color scale.
        variant.minAltLength = minAltLength;
        variant.maxAltLength = maxAltLength;

        function calcHeterozygosity(ac, an) {
            var sum = 0,
                altFreqs = ac.split(','),
                altCount = 0,
                refFrac;

            an = parseInt(an);
            altFreqs.forEach(function (altFreq) {
                var a = parseInt(altFreq),
                    altFrac = a / an;
                sum += altFrac * altFrac;
                altCount += a;
            });

            refFrac = (an - altCount) / an;
            sum += refFrac * refFrac;
            return 1 - sum;
        };

    }

    igv.Variant = function () {

    }

    igv.Variant.prototype.popupData = function (genomicLocation) {

        var self = this,
            fields, gt;

        fields = [
            {name: "Chr", value: this.chr},
            {name: "Pos", value: this.pos},
            {name: "Names", value: this.names ? this.names : ""},
            {name: "Ref", value: this.referenceBases},
            {name: "Alt", value: this.alternateBases},
            {name: "Qual", value: this.quality},
            {name: "Filter", value: this.filter}
        ];

        if (this.hasOwnProperty("heterozygosity")) {
            fields.push({name: "Heterozygosity", value: this.heterozygosity});
        }

        // Special case of VCF with a single sample
        if (this.calls && this.calls.length === 1) {
            fields.push('
');
            gt = this.alleles[this.calls[0].genotype[0]] + this.alleles[this.calls[0].genotype[1]];
            fields.push({name: "Genotype", value: gt});
        }

        if (this.info) {
            fields.push('
');
            Object.keys(this.info).forEach(function (key) {
                fields.push({name: key, value: arrayToCommaString(self.info[key])});
            });
        }
        return fields;

    };

    function arrayToCommaString(array) {
        if (!array) return;
        var str = '', i;
        if (array.length > 0)
            str = array[0];
        for (i = 1; i < array.length; i++) {
            str += ", " + array[1];
        }
        return str;

    }

    return igv;
})(igv || {});
<commit_msg>arrayToCommaString bug (#400)<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Parser for VCF files.
 */

var igv = (function (igv) {

    igv.createVCFVariant = function (tokens) {

        var variant = new igv.Variant();

        var self = this,
            altTokens;

        variant.chr = tokens[0]; // TODO -- use genome aliases
        variant.pos = parseInt(tokens[1]);
        variant.names = tokens[2];    // id in VCF
        variant.referenceBases = tokens[3];
        variant.alternateBases = tokens[4];
        variant.quality = parseInt(tokens[5]);
        variant.filter = tokens[6];
        variant.info = getInfoObject(tokens[7]);

        variant.str = variant.info["PERIOD"] !== undefined;

        initAlleles(variant);

        return variant;

        function getInfoObject(infoStr) {

            if (!infoStr) return undefined;

            var info = {};
            infoStr.split(';').forEach(function (elem) {
                var element = elem.split('=');
                info[element[0]] = element[1];
            });
            return info;
        };

    }

    igv.createGAVariant = function (json) {

        var variant = new igv.Variant();

        variant.chr = json.referenceName;
        variant.pos = parseInt(json.start);
        variant.names = arrayToCommaString(json.names);
        variant.referenceBases = json.referenceBases + '';
        variant.alternateBases = json.alternateBases + '';
        variant.quality = json.quality;
        variant.filter = arrayToCommaString(json.filter);
        variant.info = json.info;

        variant.str = variant.info["PERIOD"] !== undefined;

        // Need to build a hash of calls for fast lookup
        // Note from the GA4GH spec on call ID:
        //
        // The ID of the call set this variant call belongs to. If this field is not present,
        // the ordering of the call sets from a SearchCallSetsRequest over this GAVariantSet
        // is guaranteed to match the ordering of the calls on this GAVariant.
        // The number of results will also be the same.
        variant.calls = {};
        var order = 0, id;
        if (json.calls) {
            json.calls.forEach(function (call) {
                id = call.callSetId;
                variant.calls[id] = call;
                order++;

            })
        }

        initAlleles(variant);

        return variant;

    }

    function initAlleles(variant) {

        //Alleles
        var altTokens = variant.alternateBases.split(","),
            minAltLength = variant.referenceBases.length,
            maxAltLength = variant.referenceBases.length;

        variant.alleles = [];

        // If an STR define start and end based on reference allele.  Otherwise start and end computed below based
        // on alternate allele type (snp, insertion, deletion)

        if(variant.str) {
            variant.start = variant.pos - 1;
            variant.end = variant.start + variant.referenceBases.length;
        }

        if (variant.alternateBases === ".") {     // "." => no alternate alleles
            variant.heterozygosity = 0;
        } else {
            altTokens.forEach(function (alt) {
                var a, s, e, diff;

                variant.alleles.push(alt);

                // Adjust for padding, used for insertions and deletions, unless variant is a short tandem repeat.

                if (!variant.str && alt.length > 0) {

                    diff = variant.referenceBases.length - alt.length;

                    if (diff > 0) {
                        // deletion, assume left padded
                        s = variant.pos - 1 + alt.length;
                        e = s + diff;
                    } else if (diff < 0) {
                        // Insertion, assume left padded, insertion begins to "right" of last ref base
                        s = variant.pos - 1 + variant.referenceBases.length;
                        e = s + 1;     // Insertion between s & 3
                    } else {
                        s = variant.pos - 1;
                        e = s + 1;
                    }
                    variant.start = variant.start === undefined ? s : Math.min(variant.start, s);
                    variant.end = variant.end === undefined ? e : Math.max(variant.end, e);
                }

                minAltLength = Math.min(minAltLength, alt.length);
                maxAltLength = Math.max(maxAltLength, alt.length);

            });
            if (variant.info.AC && variant.info.AN) {
                variant.heterozygosity = calcHeterozygosity(variant.info.AC, variant.info.AN).toFixed(3);
            }
        }

        // Alternate allele lengths used for STR color scale.
        variant.minAltLength = minAltLength;
        variant.maxAltLength = maxAltLength;

        function calcHeterozygosity(ac, an) {
            var sum = 0,
                altFreqs = ac.split(','),
                altCount = 0,
                refFrac;

            an = parseInt(an);
            altFreqs.forEach(function (altFreq) {
                var a = parseInt(altFreq),
                    altFrac = a / an;
                sum += altFrac * altFrac;
                altCount += a;
            });

            refFrac = (an - altCount) / an;
            sum += refFrac * refFrac;
            return 1 - sum;
        };

    }

    igv.Variant = function () {

    }

    igv.Variant.prototype.popupData = function (genomicLocation) {

        var self = this,
            fields, gt;

        fields = [
            {name: "Chr", value: this.chr},
            {name: "Pos", value: this.pos},
            {name: "Names", value: this.names ? this.names : ""},
            {name: "Ref", value: this.referenceBases},
            {name: "Alt", value: this.alternateBases},
            {name: "Qual", value: this.quality},
            {name: "Filter", value: this.filter}
        ];

        if (this.hasOwnProperty("heterozygosity")) {
            fields.push({name: "Heterozygosity", value: this.heterozygosity});
        }

        // Special case of VCF with a single sample
        if (this.calls && this.calls.length === 1) {
            fields.push('
');
            gt = this.alleles[this.calls[0].genotype[0]] + this.alleles[this.calls[0].genotype[1]];
            fields.push({name: "Genotype", value: gt});
        }

        if (this.info) {
            fields.push('
');
            Object.keys(this.info).forEach(function (key) {
                fields.push({name: key, value: arrayToCommaString(self.info[key])});
            });
        }
        return fields;

    };

    function arrayToCommaString(array) {
        if (!(array instanceof Array)) return '';
        return array.join(',');

    }

    return igv;
})(igv || {});
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import TrackBase from "../trackBase.js"
import {StringUtils} from "../../node_modules/igv-utils/src/index.js"

const knownAltBases = new Set(["A", "C", "T", "G"].map(c => c.charCodeAt(0)))

function createVCFVariant(tokens) {
    return new Variant(tokens)
}

class Variant {

    constructor(tokens) {
        this.chr = tokens[0] // TODO -- use genome aliases
        this.pos = parseInt(tokens[1])
        this.names = tokens[2]    // id in VCF
        this.referenceBases = tokens[3]
        this.alternateBases = tokens[4]
        this.quality = tokens[5]
        this.filter = tokens[6]
        this.info = getInfoObject(tokens[7])
        this.init()
    }

    init() {

        const ref = this.referenceBases
        const altBases = this.alternateBases

        if (this.info) {
            if (this.info["VT"]) {
                this.type = this.info["VT"]
            } else if (this.info["SVTYPE"]) {
                this.type = "SV"
            } else if (this.info["PERIOD"]) {
                this.type = "STR"
            }
        }
        if (this.type === undefined) {
            this.type = determineType(ref, altBases)
        }
        if (this.type === "NONVARIANT") {
            this.heterozygosity = 0
        }

        // Determine start/end coordinates -- these are the coordinates representing the actual variant,
        // not the leading or trailing reference
        if (this.info["END"]) {
            this.start = this.pos - 1
            if (this.info["CHR2"] && this.info["CHR2"] !== this.chr) {
                this.end = this.start + 1
            } else {
                this.end = Number.parseInt(this.info["END"])
            }
        } else {
            if (this.type === "NONVARIANT") {
                this.start = this.pos - 1      // convert to 0-based coordinate convention
                this.end = this.start + ref.length
            } else {

                const altTokens = altBases.split(",").filter(token => token.length > 0)
                this.alleles = []
                this.start = undefined
                this.end = undefined

                for (let alt of altTokens) {

                    this.alleles.push(alt)

                    // We don't yet handle  SV and other special alt representations
                    if ("SV" !== this.type && isKnownAlt(alt)) {

                        let altLength = alt.length
                        let lengthOnRef = ref.length
                        const lmin = Math.min(altLength, lengthOnRef)

                        // Trim off matching bases.  Try first match, then right -> left,  then any remaining left -> right
                        let s = 0

                        while (s < lmin && (ref.charCodeAt(s) === alt.charCodeAt(s))) {
                            s++
                            altLength--
                            lengthOnRef--
                        }

                        // right -> left from end
                        while (altLength > 0 && lengthOnRef > 0) {
                            const altIdx = s + altLength - 1
                            const refIdx = s + lengthOnRef - 1
                            if (alt.charCodeAt(altIdx) === ref.charCodeAt(refIdx)) {
                                altLength--
                                lengthOnRef--
                            } else {
                                break
                            }
                        }

                        // if any remaining, left -> right
                        while (altLength > 0 && lengthOnRef > 0) {
                            const altIdx = s
                            const refIdx = s
                            if (alt.charCodeAt(altIdx) === ref.charCodeAt(refIdx)) {
                                s++
                                altLength--
                                lengthOnRef--
                            } else {
                                break
                            }
                        }

                        const alleleStart = this.pos + s - 1      // -1 for zero based coordinates
                        const alleleEnd = alleleStart + lengthOnRef
                        this.start = this.start === undefined ? alleleStart : Math.min(this.start, alleleStart)
                        this.end = this.end === undefined ? alleleEnd : Math.max(this.end, alleleEnd)
                    }
                }

                // Default to single base representation @ position for variant types not otherwise handled
                if (this.start === undefined) {
                    this.start = this.pos - 1
                    this.end = this.pos
                }
            }
        }
    }

    popupData(genomicLocation, genomeId) {

        const posString = `${StringUtils.numberFormatter(this.pos)}`
        const locString = this.start === this.end ?
            `${StringUtils.numberFormatter(this.start)} | ${StringUtils.numberFormatter(this.start + 1)}` :
            `${StringUtils.numberFormatter(this.start + 1)}-${StringUtils.numberFormatter(this.end)}`
        const fields = [
            {name: "Chr", value: this.chr},
            {name: "Pos", value: posString},
            {name: "Loc", value: locString},
            {name: "Names", value: this.names ? this.names : ""},
            {name: "Ref", value: this.referenceBases},
            {name: "Alt", value: this.alternateBases.replace("<", "<")},
            {name: "Qual", value: this.quality},
            {name: "Filter", value: this.filter}
        ]

        if ("SNP" === this.type) {
            let ref = this.referenceBases
            if (ref.length === 1) {
                let altArray = this.alternateBases.split(",")
                for (let alt of altArray) {
                    if (alt.length === 1) {
                        let l = TrackBase.getCravatLink(this.chr, this.pos, ref, alt, genomeId)
                        if (l) {
                            fields.push('
')
                            fields.push({html: l})
                        }
                    }
                }
            }
        }

        if (this.hasOwnProperty("heterozygosity")) {
            fields.push({name: "Heterozygosity", value: this.heterozygosity})
        }

        if (this.info) {
            fields.push({html: '
'})
            for (let key of Object.keys(this.info)) {
                fields.push({name: key, value: arrayToString(decodeURIComponent(this.info[key]))})
            }
        }

        return fields

    };

    isRefBlock() {
        return "NONVARIANT" === this.type
    }

}

function getInfoObject(infoStr) {
    var info = {}
    if (infoStr) {
        infoStr.split(';').forEach(function (elem) {
            var element = elem.split('=')
            info[element[0]] = element[1]
        })
    }
    return info
}

function isKnownAlt(alt) {
    for (let i = 0; i < alt.length; i++) {
        if (!knownAltBases.has(alt.charCodeAt(i))) {
            return false
        }
    }
    return true
}

function determineType(ref, altAlleles) {
    const refLength = ref.length
    if (altAlleles === undefined) {
        return "UNKNOWN"
    } else if (altAlleles.trim().length === 0 ||
        altAlleles === "<NON_REF>" ||
        altAlleles === "<*>" ||
        altAlleles === ".") {
        return "NONVARIANT"
    } else {
        const alleles = altAlleles.split(",")
        const types = alleles.map(function (a) {
            if (refLength === 1 && a.length === 1) {
                return "SNP"
            } else {
                return "<NON_REF>" === a ? "NONVARIANT" : "OTHER"
            }
        })
        let type = types[0]
        for (let t of types) {
            if (t !== type) {
                return "MIXED"
            }
        }
        return type
    }
}

function arrayToString(value, delim) {

    if (delim === undefined) delim = ","

    if (!(Array.isArray(value))) {
        return value
    }
    return value.join(delim)
}

/**
 * @deprecated - the GA4GH API has been deprecated.  This code no longer maintained.
 * @param json
 * @returns {Variant}
 */
function createGAVariant(json) {

    var variant = new Variant()

    variant.chr = json.referenceName
    variant.start = parseInt(json.start)  // Might get overriden below
    variant.end = parseInt(json.end)      // Might get overriden below
    variant.pos = variant.start + 1       // GA4GH is 0 based.
    variant.names = arrayToString(json.names, "; ")
    variant.referenceBases = json.referenceBases
    variant.alternateBases = arrayToString(json.alternateBases)
    variant.quality = json.quality
    variant.filter = arrayToString(json.filter)

    // Flatten GA4GH attributes array
    variant.info = {}
    if (json.info) {
        Object.keys(json.info).forEach(function (key) {
            var value,
                valueArray = json.info[key]

            if (Array.isArray(valueArray)) {
                value = valueArray.join(",")
            } else {
                value = valueArray
            }
            variant.info[key] = value
        })
    }

    // Need to build a hash of calls for fast lookup
    // Note from the GA4GH spec on call ID:
    //
    // The ID of the call set this variant call belongs to. If this field is not present,
    // the ordering of the call sets from a SearchCallSetsRequest over this GAVariantSet
    // is guaranteed to match the ordering of the calls on this GAVariant.
    // The number of results will also be the same.
    variant.calls = {}
    var order = 0, id
    if (json.calls) {
        json.calls.forEach(function (call) {
            id = call.callSetId
            variant.calls[id] = call
            order++

        })
    }

    init(variant)

    return variant

}

export {createVCFVariant, createGAVariant}

<commit_msg>Refactor Variant creation to make constructor generalization easier.   See #1451<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import TrackBase from "../trackBase.js"
import {StringUtils} from "../../node_modules/igv-utils/src/index.js"

/**
 * Create a variant from an array of tokens representing a line in a "VCF" file
 * @param tokens
 */
function createVCFVariant(tokens) {
    const variant = new Variant(tokens)
    variant.chr = tokens[0] // TODO -- use genome aliases
    variant.pos = parseInt(tokens[1])
    variant.names = tokens[2]    // id in VCF
    variant.referenceBases = tokens[3]
    variant.alternateBases = tokens[4]
    variant.quality = tokens[5]
    variant.filter = tokens[6]
    variant.info = {}
    const infoStr = tokens[7]
    if (infoStr) {
        for (let elem of infoStr.split(';')) {
            var element = elem.split('=')
            variant.info[element[0]] = element[1]
        }
    }
    variant.init()
    return variant;
}

/**
 * @deprecated - the GA4GH API has been deprecated.  This code no longer maintained.
 * @param json
 * @returns {Variant}
 */
function createGAVariant(json) {

    var variant = new Variant()

    variant.chr = json.referenceName
    variant.start = parseInt(json.start)  // Might get overriden below
    variant.end = parseInt(json.end)      // Might get overriden below
    variant.pos = variant.start + 1       // GA4GH is 0 based.
    variant.names = arrayToString(json.names, "; ")
    variant.referenceBases = json.referenceBases
    variant.alternateBases = arrayToString(json.alternateBases)
    variant.quality = json.quality
    variant.filter = arrayToString(json.filter)

    // Flatten GA4GH attributes array
    variant.info = {}
    if (json.info) {
        Object.keys(json.info).forEach(function (key) {
            var value,
                valueArray = json.info[key]

            if (Array.isArray(valueArray)) {
                value = valueArray.join(",")
            } else {
                value = valueArray
            }
            variant.info[key] = value
        })
    }

    // Need to build a hash of calls for fast lookup
    // Note from the GA4GH spec on call ID:
    //
    // The ID of the call set this variant call belongs to. If this field is not present,
    // the ordering of the call sets from a SearchCallSetsRequest over this GAVariantSet
    // is guaranteed to match the ordering of the calls on this GAVariant.
    // The number of results will also be the same.
    variant.calls = {}
    var order = 0, id
    if (json.calls) {
        json.calls.forEach(function (call) {
            id = call.callSetId
            variant.calls[id] = call
            order++

        })
    }

    init(variant)

    return variant

}

class Variant {

    init() {

        const ref = this.referenceBases
        const altBases = this.alternateBases

        if (this.info) {
            if (this.info["VT"]) {
                this.type = this.info["VT"]
            } else if (this.info["SVTYPE"]) {
                this.type = "SV"
            } else if (this.info["PERIOD"]) {
                this.type = "STR"
            }
        }
        if (this.type === undefined) {
            this.type = determineType(ref, altBases)
        }
        if (this.type === "NONVARIANT") {
            this.heterozygosity = 0
        }

        // Determine start/end coordinates -- these are the coordinates representing the actual variant,
        // not the leading or trailing reference
        if (this.info["END"]) {
            this.start = this.pos - 1
            if (this.info["CHR2"] && this.info["CHR2"] !== this.chr) {
                this.end = this.start + 1
            } else {
                this.end = Number.parseInt(this.info["END"])
            }
        } else {
            if (this.type === "NONVARIANT") {
                this.start = this.pos - 1      // convert to 0-based coordinate convention
                this.end = this.start + ref.length
            } else {

                const altTokens = altBases.split(",").filter(token => token.length > 0)
                this.alleles = []
                this.start = undefined
                this.end = undefined

                for (let alt of altTokens) {

                    this.alleles.push(alt)

                    // We don't yet handle  SV and other special alt representations
                    if ("SV" !== this.type && isKnownAlt(alt)) {

                        let altLength = alt.length
                        let lengthOnRef = ref.length
                        const lmin = Math.min(altLength, lengthOnRef)

                        // Trim off matching bases.  Try first match, then right -> left,  then any remaining left -> right
                        let s = 0

                        while (s < lmin && (ref.charCodeAt(s) === alt.charCodeAt(s))) {
                            s++
                            altLength--
                            lengthOnRef--
                        }

                        // right -> left from end
                        while (altLength > 0 && lengthOnRef > 0) {
                            const altIdx = s + altLength - 1
                            const refIdx = s + lengthOnRef - 1
                            if (alt.charCodeAt(altIdx) === ref.charCodeAt(refIdx)) {
                                altLength--
                                lengthOnRef--
                            } else {
                                break
                            }
                        }

                        // if any remaining, left -> right
                        while (altLength > 0 && lengthOnRef > 0) {
                            const altIdx = s
                            const refIdx = s
                            if (alt.charCodeAt(altIdx) === ref.charCodeAt(refIdx)) {
                                s++
                                altLength--
                                lengthOnRef--
                            } else {
                                break
                            }
                        }

                        const alleleStart = this.pos + s - 1      // -1 for zero based coordinates
                        const alleleEnd = alleleStart + lengthOnRef
                        this.start = this.start === undefined ? alleleStart : Math.min(this.start, alleleStart)
                        this.end = this.end === undefined ? alleleEnd : Math.max(this.end, alleleEnd)
                    }
                }

                // Default to single base representation @ position for variant types not otherwise handled
                if (this.start === undefined) {
                    this.start = this.pos - 1
                    this.end = this.pos
                }
            }
        }
    }

    popupData(genomicLocation, genomeId) {

        const posString = `${StringUtils.numberFormatter(this.pos)}`
        const locString = this.start === this.end ?
            `${StringUtils.numberFormatter(this.start)} | ${StringUtils.numberFormatter(this.start + 1)}` :
            `${StringUtils.numberFormatter(this.start + 1)}-${StringUtils.numberFormatter(this.end)}`
        const fields = [
            {name: "Chr", value: this.chr},
            {name: "Pos", value: posString},
            {name: "Loc", value: locString},
            {name: "Names", value: this.names ? this.names : ""},
            {name: "Ref", value: this.referenceBases},
            {name: "Alt", value: this.alternateBases.replace("<", "<")},
            {name: "Qual", value: this.quality},
            {name: "Filter", value: this.filter}
        ]

        if ("SNP" === this.type) {
            let ref = this.referenceBases
            if (ref.length === 1) {
                let altArray = this.alternateBases.split(",")
                for (let alt of altArray) {
                    if (alt.length === 1) {
                        let l = TrackBase.getCravatLink(this.chr, this.pos, ref, alt, genomeId)
                        if (l) {
                            fields.push('
')
                            fields.push({html: l})
                        }
                    }
                }
            }
        }

        if (this.hasOwnProperty("heterozygosity")) {
            fields.push({name: "Heterozygosity", value: this.heterozygosity})
        }

        if (this.info) {
            fields.push({html: '
'})
            for (let key of Object.keys(this.info)) {
                fields.push({name: key, value: arrayToString(decodeURIComponent(this.info[key]))})
            }
        }

        return fields

    };

    isRefBlock() {
        return "NONVARIANT" === this.type
    }

}

const knownAltBases = new Set(["A", "C", "T", "G"].map(c => c.charCodeAt(0)))

function isKnownAlt(alt) {
    for (let i = 0; i < alt.length; i++) {
        if (!knownAltBases.has(alt.charCodeAt(i))) {
            return false
        }
    }
    return true
}

function determineType(ref, altAlleles) {
    const refLength = ref.length
    if (altAlleles === undefined) {
        return "UNKNOWN"
    } else if (altAlleles.trim().length === 0 ||
        altAlleles === "<NON_REF>" ||
        altAlleles === "<*>" ||
        altAlleles === ".") {
        return "NONVARIANT"
    } else {
        const alleles = altAlleles.split(",")
        const types = alleles.map(function (a) {
            if (refLength === 1 && a.length === 1) {
                return "SNP"
            } else {
                return "<NON_REF>" === a ? "NONVARIANT" : "OTHER"
            }
        })
        let type = types[0]
        for (let t of types) {
            if (t !== type) {
                return "MIXED"
            }
        }
        return type
    }
}

function arrayToString(value, delim) {

    if (delim === undefined) delim = ","

    if (!(Array.isArray(value))) {
        return value
    }
    return value.join(delim)
}

export {createVCFVariant, createGAVariant}

<|endoftext|>


<commit_before>var app = app || {};

app.UserEditView = Backbone.View.extend({
    template: _.template($('#user-edit-template').html()),
    tagName: 'div',
    className: 'user-edit',

    events: {
        'keyup .search': 'onUsersKeyUp',
        'click #user-select-btn': 'onUserSelectBtnClick',
        'click .save-permissions-btn': 'onSaveClick',
        'click #clear-console': 'onClearConsoleClick',
        'click .export-permissions': 'onExportBtnClick',
        'click .purge-user-btn': 'onPurgeBtnClick',
        'click .search-clear': 'onSearchClear',
        'click .search': 'onSearchClick',
        'blur .search': 'onSearchBlur',
        'focus .search': 'onSearchFocus'
    },

    initialize: function(options) {
        Backbone.pubSub.on('user:selected-permissions-fetched', this.onSelectedPermissionsFetched, this);
        this.state_map = {
            user_permissions: null,
            pendingSaves: 0,
            pendingRemoves: 0,
            totalUpdates: 0,
            progressUpdateRatio: 0,
            currentProgress: 0,
            failed: {
                add: [],
                remove: [],
                purge: []
            },
            success: {
                add: [],
                remove: [],
                purge: []
            }
        }

    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        this.$users = this.$('#users');
        this.$user_info = this.$('#user-info');
        this.$user_search_container = this.$('.search-container');
        this.$user_search = this.$('.search');
        this.$user_attributes = this.$('.user-attr');
        this.$name = this.$('#name');
        this.$name_container = this.$('#name-container');
        this.$messages = this.$('.messages');
        this.$notify = this.$('.notify');
        this.$progress_meter = this.$('.meter');
        this.$progressbar = this.$('.progress');
        this.$progress_text = this.$('.current-progress');
        this.$search_clear = this.$('.search-clear');
        this.$search = this.$('.search');
        this.$user_select_btn = this.$('#user-select-btn');
        this.$save_btn = this.$('.save-permissions-btn');
        this.$purge_btn = this.$('.purge-btn');
        this.$export_btn = this.$('.export-permissions');

        if (this.model.get('name') == '') {
            this.toggleButtons(false);
        }

        $('body').on('click', this.onBodyClick);

        this.libraryViewUsers = new app.LibraryUserView({
            el: this.$users[0],
            collection: app.UserCollection,
            itemView: app.UserView
        });

        this.libraryViewUsers.render();

        Backbone.pubSub.on('user:select', this.userSelect, this);
        if (this.model.get('id') != '') {
            this.userSelect(this.model, false);
        }
        return this;
    },

    search: function() {
        var val = this.$user_search.val(),
            options,
            searchAllAttributes = false;

        if (val.length > 0) {
            this.$search_clear.removeClass('hidden');
        } else {
            this.$search_clear.addClass('hidden');
        }

        //check for search operand character '~'
        if (val.indexOf('~') == 0 && val.length > 1) {
            //set val to exclude '~'
            val = val.substring(1);
            searchAllAttributes = true;
        } else if (val.indexOf('~') == 0) {
            //don't commit to search yet since not enough info has been entered.
            return;
        }

        options = (searchAllAttributes ? {
            val: val
        } : {
            key: 'name',
            val: val
        });

        Backbone.pubSub.trigger('library_users:search', options);
    },
    onBodyClick: function(e) {
        var $currentTarget = $(e.currentTarget);

        (function(that) {
            setTimeout(function() {
                //return if users are not visible or the current target is the user search bar
                if (!that.$users.is(':visible') || $currentTarget[0] === that.$user_search[0]) {
                    return;
                }
                if (document.activeElement === that.$user_info[0]) {
                    return;
                } else {
                    that.$users.hide();
                }

            }, 10);
        })(this);
    },
    onSearchClear: function(e) {
        this.$search.val('');
        this.$search.trigger('keyup');
        e.stopPropagation();
        this.$user_search.focus();
    },
    onUsersKeyUp: function(e) {
        this.search();
    },
    onSelectedPermissionsFetched: function(selected_permissions) {
        var user_permissions = new Backbone.Collection(this.model.get('permissions')),
            user_permission_index,
            add_permissions_arr = [],
            remove_permissions_arr = [],
            user = this.model.attributes;

        this.toggleButtons(false);

        //iterate through selected permissions
        selected_permissions.forEach(function(permission) {
            //check if permissions is in user permissions
            //user_permission_index = user_permissions_arr.indexOf(permission);
            user_permission = user_permissions.findWhere({
                name: permission
            });
            if (typeof user_permission !== 'undefined') {
                //remove permission from user permissions
                user_permissions.remove(user_permission);

            } else {
                //add permission to add permission array
                add_permissions_arr.push(permission);
            }
        });
        this.state_map.user_permissions = user_permissions;
        //set remove permissions to the remaining user permissions...for contextual reasons
        remove_permissions_arr = user_permissions.map(function(obj) {
                return obj.get('name');
            }),

            //set state map variables
            this.state_map.pendingSaves = add_permissions_arr.length;
        this.state_map.pendingRemoves = remove_permissions_arr.length;
        this.state_map.totalUpdates = this.state_map.pendingRemoves + this.state_map.pendingSaves;
        this.state_map.progressUpdateRatio = 100 / this.state_map.totalUpdates;
        if (add_permissions_arr.length == 0 && remove_permissions_arr.length == 0) {
            return;
        }

        this.$messages.append('' + app.utility.getDateTime() + '
Modifying [' + user.name + ']\'s permissions
');
        //add user permissions
        if (add_permissions_arr.length > 0) {
            (function(that) {
                app.data.modifyPermissions(add_permissions_arr, 0, user, app.config.url, 'add', function(results) {
                    that.processPermissionModify(results);
                });
            })(this);
        }
        //remove user permissions
        if (remove_permissions_arr.length > 0) {
            (function(that) {
                app.data.modifyPermissions(remove_permissions_arr, 0, user, app.config.url, 'remove', function(results) {
                    that.processPermissionModify(results);
                });
            })(this);
        }
    },

    onSearchClick: function(e) {
        e.stopPropagation();
    },

    onSaveClick: function(e) {
        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }
        this.resetStateMap();
        //update progress bar
        this.$progress_meter.width('0%');
        this.$progress_text.text('0%');
        //publish user selected event
        Backbone.pubSub.trigger('user:save-permissions');
    },

    onClearConsoleClick: function(e) {
        this.clearConsole();
    },

    onExportBtnClick: function(e) {
        var permissions = (this.model ? this.model.get('permissions') : false),
            ua = window.navigator.userAgent,
            msie = ua.indexOf("MSIE "),
            permissionsElement;

        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }

        if (msie > 0) { // If Internet Explorer, return version number
            permissionsElement = '
' + this.model.get('name') + '\'s Permissions

';
            permissionsElement += '
' + this.model.get('name') + '
';
            permissionsElement += '
' + this.model.get('loginname') + '
';
            permissionsElement += '
' + this.models.get('email') + '
';

            if (permissions.length > 1) {
                permissionsElement += permissions.reduce(function(memo, obj) {
                    return (typeof memo == "string" ? memo : '
' + memo.name + '
') + '
' + obj.name + '
';
                });
            } else {
                permissionsElement = '
' + permissions[0].name + '
';
            }

            permissionsElement += '
';
            app.utility.printToNewWindow(permissionsElement);
        } else {
            app.utility.JSONToCSVConvertor(permissions, this.model.get('name') + ' Permissions', true);
        }

    },

    onPurgeBtnClick: function(e) {
        var user = (this.model ? this.model.attributes : false);

        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }

        this.$messages.append('' + app.utility.getDateTime() + '
Purging [' + user.name + ']
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        (function(that) {
            app.data.removeUserFromWeb(app.config.url, user, function(results) {
                that.model.collection.remove(this.model);
                that.onRemoveUserComplete(results);
                app.router.navigate('edit/user/', true);
            });
        })(this);

    },

    onRemoveUserComplete: function(results) {
        var type = results.type,
            data = results.data,
            name = this.model.get('name'),
            message = '';

        if (type != 'error') {
            message = 'Succesfully removed ' + name + ' from site.';
        } else {
            message = 'Unsuccessfully removed ' + name + ' from site.';
        }

        this.$messages.append('' + app.utility.getDateTime() + '
' + message + '
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
    },

    clearConsole: function() {
        this.$messages.html('');
    },

    processPermissionModify: function(results) {
        var operation = results.operation,
            type = results.type,
            data = results.data,
            permission = results.permission,
            message = '',
            class_map = {
                'success': 'success',
                'error': 'error'
            };

        if (type == 'success') {
            this.state_map.success[operation].push(permission);
            message = '[' + permission + '] succesfully ' + (operation == 'add' ? 'added.' : 'removed.');
        } else { //error
            this.state_map.failed[operation].push(permission);
            message = 'Unable to ' + operation + ' [' + permission + '].'
        }
        this.updateProgress(operation);
        this.$messages.append('' + app.utility.getDateTime() + '
' + message + '
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        if (this.state_map.pendingSaves == 0 && this.state_map.pendingRemoves == 0) {
            this.permissionModifyComplete();
        }
    },
    permissionModifyComplete: function() {
        var user = this.model;
        this.$messages.append('' + app.utility.getDateTime() + '
Completed modifying [' + user.get('name') + ']\'s permissions
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);

        this.userSelect(user, true);
    },
    updateProgress: function(operation) {
        this.state_map.currentProgress += this.state_map.progressUpdateRatio;
        //clip progress if needed
        this.state_map.currentProgress = (this.state_map.currentProgress >= 100 ? 100 : this.state_map.currentProgress);
        this.state_map.currentProgress = Math.ceil(this.state_map.currentProgress * 100) * 0.01;
        this.state_map.pendingRemoves -= (operation == 'remove' ? 1 : 0);
        this.state_map.pendingSaves -= (operation == 'add' ? 1 : 0);

        //update progress bar
        this.$progress_meter.width(this.state_map.currentProgress + '%');
        this.$progress_text.text(this.state_map.currentProgress + '%');
    },
    resetStateMap: function() {
        this.state_map.pendingRemoves = 0;
        this.state_map.pendingSaves = 0;
        this.state_map.pendingSaves = 0;
        this.state_map.pendingRemoves = 0;
        this.state_map.totalUpdates = 0;
        this.state_map.progressUpdateRatio = 0;
        this.state_map.failed.add = [];
        this.state_map.failed.remove = [];
        this.state_map.failed.purge = [];
        this.state_map.success.add = [];
        this.state_map.success.remove = [];
        this.state_map.success.purge = [];
    },
    userSelect: function(user, options) {
        if (!user.hasOwnProperty('attributes')) {
            return;
        }
        this.model = user;
        this.$user_attributes.each(function(i, el) {
            $(el).val(user.attributes[el.id]);
        });

        if (!user.attributes.hasOwnProperty('loginname')) {
            return false;
        }

        this.$search.val(user.get('name'));
        this.$search_clear.show();

        //fetch user permissions
        this.getUserPermissions(user.attributes.loginname);

        //update message
        this.$messages.append('' + app.utility.getDateTime() + '
Fetching [' + this.model.get('name') + ']\'s permissions
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        //update progress bar
        this.$progress_meter.width('0%');
        this.$progress_text.text('0%');
        //publish user selected event
        //set router
        if (options && options.route) {
            app.router.navigate('edit/user/' + user.attributes.loginname.replace('/', '\\'), false);

            Backbone.pubSub.trigger('user:selected');
        } else {
            app.router.navigate('edit/user/' + user.attributes.loginname.replace('/', '\\'), false);
            Backbone.pubSub.trigger('user:selected');
        }
        Backbone.pubSub.trigger('breadcrumbs');

    },
    getUserPermissions: function(loginname) {
        (function(that) {
            app.data.getPermissions(app.config.url, loginname, function(results) {
                //format results
                results = app.utility.processData(results);
                //set selected user model permissions
                that.model.set({
                    permissions: results
                });
                //endable edit buttons
                that.toggleButtons(true);
                //publish results globally
                Backbone.pubSub.trigger('user:permissions-fetched', results);
                that.$messages.append('' + app.utility.getDateTime() + '
Completed fetching [' + that.model.get('name') + ']\'s permissions
');
                that.$messages.scrollTop(that.$messages[0].scrollHeight);
                that.$progress_meter.width('100%');
                that.$progress_text.text('100%');
            });
        })(this);
    },
    toggleButtons: function(enable) {
        if (enable) {
            this.$save_btn.removeClass('disabled');
            this.$purge_btn.removeClass('disabled');
            this.$export_btn.removeClass('disabled');
        } else {
            this.$save_btn.addClass('disabled');
            this.$purge_btn.addClass('disabled');
            this.$export_btn.addClass('disabled');
        }
    },
    clearInfo: function(enable) {
        this.$user_attributes.each(function(i, el) {
            $(el).val('');
        });
    },

    onSearchFocus: function(e) {
        this.$users.show();
    }
});
<commit_msg>change function context on body click<commit_after>var app = app || {};

app.UserEditView = Backbone.View.extend({
    template: _.template($('#user-edit-template').html()),
    tagName: 'div',
    className: 'user-edit',

    events: {
        'keyup .search': 'onUsersKeyUp',
        'click #user-select-btn': 'onUserSelectBtnClick',
        'click .save-permissions-btn': 'onSaveClick',
        'click #clear-console': 'onClearConsoleClick',
        'click .export-permissions': 'onExportBtnClick',
        'click .purge-user-btn': 'onPurgeBtnClick',
        'click .search-clear': 'onSearchClear',
        'click .search': 'onSearchClick',
        'blur .search': 'onSearchBlur',
        'focus .search': 'onSearchFocus'
    },

    initialize: function(options) {
        Backbone.pubSub.on('user:selected-permissions-fetched', this.onSelectedPermissionsFetched, this);
        this.state_map = {
            user_permissions: null,
            pendingSaves: 0,
            pendingRemoves: 0,
            totalUpdates: 0,
            progressUpdateRatio: 0,
            currentProgress: 0,
            failed: {
                add: [],
                remove: [],
                purge: []
            },
            success: {
                add: [],
                remove: [],
                purge: []
            }
        }

    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        this.$users = this.$('#users');
        this.$user_info = this.$('#user-info');
        this.$user_search_container = this.$('.search-container');
        this.$user_search = this.$('.search');
        this.$user_attributes = this.$('.user-attr');
        this.$name = this.$('#name');
        this.$name_container = this.$('#name-container');
        this.$messages = this.$('.messages');
        this.$notify = this.$('.notify');
        this.$progress_meter = this.$('.meter');
        this.$progressbar = this.$('.progress');
        this.$progress_text = this.$('.current-progress');
        this.$search_clear = this.$('.search-clear');
        this.$search = this.$('.search');
        this.$user_select_btn = this.$('#user-select-btn');
        this.$save_btn = this.$('.save-permissions-btn');
        this.$purge_btn = this.$('.purge-btn');
        this.$export_btn = this.$('.export-permissions');

        if (this.model.get('name') == '') {
            this.toggleButtons(false);
        }

        (function(that){
              $('body').on('click',that.onBodyClick);
        })(this);

        this.libraryViewUsers = new app.LibraryUserView({
            el: this.$users[0],
            collection: app.UserCollection,
            itemView: app.UserView
        });

        this.libraryViewUsers.render();

        Backbone.pubSub.on('user:select', this.userSelect, this);
        if (this.model.get('id') != '') {
            this.userSelect(this.model, false);
        }
        return this;
    },

    search: function() {
        var val = this.$user_search.val(),
            options,
            searchAllAttributes = false;

        if (val.length > 0) {
            this.$search_clear.removeClass('hidden');
        } else {
            this.$search_clear.addClass('hidden');
        }

        //check for search operand character '~'
        if (val.indexOf('~') == 0 && val.length > 1) {
            //set val to exclude '~'
            val = val.substring(1);
            searchAllAttributes = true;
        } else if (val.indexOf('~') == 0) {
            //don't commit to search yet since not enough info has been entered.
            return;
        }

        options = (searchAllAttributes ? {
            val: val
        } : {
            key: 'name',
            val: val
        });

        Backbone.pubSub.trigger('library_users:search', options);
    },
    onBodyClick: function(e) {
        var $currentTarget = $(e.currentTarget);

        (function(that) {
            setTimeout(function() {
                //return if users are not visible or the current target is the user search bar
                if (!that.$users.is(':visible') || $currentTarget[0] === that.$user_search[0]) {
                    return;
                }
                if (document.activeElement === that.$user_info[0]) {
                    return;
                } else {
                    that.$users.hide();
                }

            }, 10);
        })(this);
    },
    onSearchClear: function(e) {
        this.$search.val('');
        this.$search.trigger('keyup');
        e.stopPropagation();
        this.$user_search.focus();
    },
    onUsersKeyUp: function(e) {
        this.search();
    },
    onSelectedPermissionsFetched: function(selected_permissions) {
        var user_permissions = new Backbone.Collection(this.model.get('permissions')),
            user_permission_index,
            add_permissions_arr = [],
            remove_permissions_arr = [],
            user = this.model.attributes;

        this.toggleButtons(false);

        //iterate through selected permissions
        selected_permissions.forEach(function(permission) {
            //check if permissions is in user permissions
            //user_permission_index = user_permissions_arr.indexOf(permission);
            user_permission = user_permissions.findWhere({
                name: permission
            });
            if (typeof user_permission !== 'undefined') {
                //remove permission from user permissions
                user_permissions.remove(user_permission);

            } else {
                //add permission to add permission array
                add_permissions_arr.push(permission);
            }
        });
        this.state_map.user_permissions = user_permissions;
        //set remove permissions to the remaining user permissions...for contextual reasons
        remove_permissions_arr = user_permissions.map(function(obj) {
                return obj.get('name');
            }),

            //set state map variables
            this.state_map.pendingSaves = add_permissions_arr.length;
        this.state_map.pendingRemoves = remove_permissions_arr.length;
        this.state_map.totalUpdates = this.state_map.pendingRemoves + this.state_map.pendingSaves;
        this.state_map.progressUpdateRatio = 100 / this.state_map.totalUpdates;
        if (add_permissions_arr.length == 0 && remove_permissions_arr.length == 0) {
            return;
        }

        this.$messages.append('' + app.utility.getDateTime() + '
Modifying [' + user.name + ']\'s permissions
');
        //add user permissions
        if (add_permissions_arr.length > 0) {
            (function(that) {
                app.data.modifyPermissions(add_permissions_arr, 0, user, app.config.url, 'add', function(results) {
                    that.processPermissionModify(results);
                });
            })(this);
        }
        //remove user permissions
        if (remove_permissions_arr.length > 0) {
            (function(that) {
                app.data.modifyPermissions(remove_permissions_arr, 0, user, app.config.url, 'remove', function(results) {
                    that.processPermissionModify(results);
                });
            })(this);
        }
    },

    onSearchClick: function(e) {
        e.stopPropagation();
    },

    onSaveClick: function(e) {
        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }
        this.resetStateMap();
        //update progress bar
        this.$progress_meter.width('0%');
        this.$progress_text.text('0%');
        //publish user selected event
        Backbone.pubSub.trigger('user:save-permissions');
    },

    onClearConsoleClick: function(e) {
        this.clearConsole();
    },

    onExportBtnClick: function(e) {
        var permissions = (this.model ? this.model.get('permissions') : false),
            ua = window.navigator.userAgent,
            msie = ua.indexOf("MSIE "),
            permissionsElement;

        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }

        if (msie > 0) { // If Internet Explorer, return version number
            permissionsElement = '
' + this.model.get('name') + '\'s Permissions

';
            permissionsElement += '
' + this.model.get('name') + '
';
            permissionsElement += '
' + this.model.get('loginname') + '
';
            permissionsElement += '
' + this.models.get('email') + '
';

            if (permissions.length > 1) {
                permissionsElement += permissions.reduce(function(memo, obj) {
                    return (typeof memo == "string" ? memo : '
' + memo.name + '
') + '
' + obj.name + '
';
                });
            } else {
                permissionsElement = '
' + permissions[0].name + '
';
            }

            permissionsElement += '
';
            app.utility.printToNewWindow(permissionsElement);
        } else {
            app.utility.JSONToCSVConvertor(permissions, this.model.get('name') + ' Permissions', true);
        }

    },

    onPurgeBtnClick: function(e) {
        var user = (this.model ? this.model.attributes : false);

        if ($(e.currentTarget).hasClass('disabled')) {
            return;
        }

        this.$messages.append('' + app.utility.getDateTime() + '
Purging [' + user.name + ']
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        (function(that) {
            app.data.removeUserFromWeb(app.config.url, user, function(results) {
                that.model.collection.remove(this.model);
                that.onRemoveUserComplete(results);
                app.router.navigate('edit/user/', true);
            });
        })(this);

    },

    onRemoveUserComplete: function(results) {
        var type = results.type,
            data = results.data,
            name = this.model.get('name'),
            message = '';

        if (type != 'error') {
            message = 'Succesfully removed ' + name + ' from site.';
        } else {
            message = 'Unsuccessfully removed ' + name + ' from site.';
        }

        this.$messages.append('' + app.utility.getDateTime() + '
' + message + '
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
    },

    clearConsole: function() {
        this.$messages.html('');
    },

    processPermissionModify: function(results) {
        var operation = results.operation,
            type = results.type,
            data = results.data,
            permission = results.permission,
            message = '',
            class_map = {
                'success': 'success',
                'error': 'error'
            };

        if (type == 'success') {
            this.state_map.success[operation].push(permission);
            message = '[' + permission + '] succesfully ' + (operation == 'add' ? 'added.' : 'removed.');
        } else { //error
            this.state_map.failed[operation].push(permission);
            message = 'Unable to ' + operation + ' [' + permission + '].'
        }
        this.updateProgress(operation);
        this.$messages.append('' + app.utility.getDateTime() + '
' + message + '
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        if (this.state_map.pendingSaves == 0 && this.state_map.pendingRemoves == 0) {
            this.permissionModifyComplete();
        }
    },
    permissionModifyComplete: function() {
        var user = this.model;
        this.$messages.append('' + app.utility.getDateTime() + '
Completed modifying [' + user.get('name') + ']\'s permissions
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);

        this.userSelect(user, true);
    },
    updateProgress: function(operation) {
        this.state_map.currentProgress += this.state_map.progressUpdateRatio;
        //clip progress if needed
        this.state_map.currentProgress = (this.state_map.currentProgress >= 100 ? 100 : this.state_map.currentProgress);
        this.state_map.currentProgress = Math.ceil(this.state_map.currentProgress * 100) * 0.01;
        this.state_map.pendingRemoves -= (operation == 'remove' ? 1 : 0);
        this.state_map.pendingSaves -= (operation == 'add' ? 1 : 0);

        //update progress bar
        this.$progress_meter.width(this.state_map.currentProgress + '%');
        this.$progress_text.text(this.state_map.currentProgress + '%');
    },
    resetStateMap: function() {
        this.state_map.pendingRemoves = 0;
        this.state_map.pendingSaves = 0;
        this.state_map.pendingSaves = 0;
        this.state_map.pendingRemoves = 0;
        this.state_map.totalUpdates = 0;
        this.state_map.progressUpdateRatio = 0;
        this.state_map.failed.add = [];
        this.state_map.failed.remove = [];
        this.state_map.failed.purge = [];
        this.state_map.success.add = [];
        this.state_map.success.remove = [];
        this.state_map.success.purge = [];
    },
    userSelect: function(user, options) {
        if (!user.hasOwnProperty('attributes')) {
            return;
        }
        this.model = user;
        this.$user_attributes.each(function(i, el) {
            $(el).val(user.attributes[el.id]);
        });

        if (!user.attributes.hasOwnProperty('loginname')) {
            return false;
        }

        this.$search.val(user.get('name'));
        this.$search_clear.show();

        //fetch user permissions
        this.getUserPermissions(user.attributes.loginname);

        //update message
        this.$messages.append('' + app.utility.getDateTime() + '
Fetching [' + this.model.get('name') + ']\'s permissions
');
        this.$messages.scrollTop(this.$messages[0].scrollHeight);
        //update progress bar
        this.$progress_meter.width('0%');
        this.$progress_text.text('0%');
        //publish user selected event
        //set router
        if (options && options.route) {
            app.router.navigate('edit/user/' + user.attributes.loginname.replace('/', '\\'), false);

            Backbone.pubSub.trigger('user:selected');
        } else {
            app.router.navigate('edit/user/' + user.attributes.loginname.replace('/', '\\'), false);
            Backbone.pubSub.trigger('user:selected');
        }
        Backbone.pubSub.trigger('breadcrumbs');

    },
    getUserPermissions: function(loginname) {
        (function(that) {
            app.data.getPermissions(app.config.url, loginname, function(results) {
                //format results
                results = app.utility.processData(results);
                //set selected user model permissions
                that.model.set({
                    permissions: results
                });
                //endable edit buttons
                that.toggleButtons(true);
                //publish results globally
                Backbone.pubSub.trigger('user:permissions-fetched', results);
                that.$messages.append('' + app.utility.getDateTime() + '
Completed fetching [' + that.model.get('name') + ']\'s permissions
');
                that.$messages.scrollTop(that.$messages[0].scrollHeight);
                that.$progress_meter.width('100%');
                that.$progress_text.text('100%');
            });
        })(this);
    },
    toggleButtons: function(enable) {
        if (enable) {
            this.$save_btn.removeClass('disabled');
            this.$purge_btn.removeClass('disabled');
            this.$export_btn.removeClass('disabled');
        } else {
            this.$save_btn.addClass('disabled');
            this.$purge_btn.addClass('disabled');
            this.$export_btn.addClass('disabled');
        }
    },
    clearInfo: function(enable) {
        this.$user_attributes.each(function(i, el) {
            $(el).val('');
        });
    },

    onSearchFocus: function(e) {
        this.$users.show();
    }
});
<|endoftext|>