<commit_before>// Variable declarations
var PERFORMANCE_TAGS = {"label":"Moves per Manhour",
                        "unit":"M/h",
                        "locationbound":true,
                        "count":[
                          {"form":6,"names":["bargemovesin","bargemovesout","truckmovesin","truckmovesout","trainmovesin","trainmovesout"]}
                        ],
                        "split":[
                          {"form":6,"names":["terminalmanhours"]}
                        ]};
/*var PERFORMANCE_TAGS = {"label":"KwH Factor",
                        "unit":"",
                        "locationbound":false,
                        "count":[
                          {"form":1,"names":["officekwh"]},
                          {"form":4,"names":["kwhrail"]},
                          {"form":6,"names":["cranekwh","terminalkwh"]}
                        ],
                        "split":[
                          {"form":6,"names":["terminalmanhours"]}
                        ]};*/

var PERFORMANCE_START_MONTH = 1;
var PERFORMANCE_START_YEAR = 2008;

/*****************************************************************************
 * General Template function
 *****************************************************************************/

/*
 * This function will initialize the default variables
 * which should be located in the 'this.data' variable.
 */
Template.performanceGraphs.initialize = function() {
  // Check if the data is available
  if (this.data) {
    //PERFORMANCE_TAGS = this.data;

  // If the data is not available we throw an error
  } else {
    PERFORMANCE_TAGS = undefined;
    throwError("The graphs are not correctly initialized! Please contact the administrator.");
  }
}

/*****************************************************************************
 * General function
 *****************************************************************************/

/*
 * This function calculates the amount of months between
 * two given timestamps.
 */
function monthDifference(d1, d2) {
    var months;
    months = (moment(d2).year() - moment(d1).year()) * 12;
    months -= moment(d1).month();
    months += moment(d2).month() + 1;
    return months <= 0 ? 0 : months;
}

/*
 * Convert timestamp into year
 */
function toYear(timestamp) {
  if (timestamp && timestamp !== 0)
    return moment.unix(timestamp/1000).year();
  return "";
}

/*
 * Convert timestamp into month
 */
function toMonth(timestamp) {
  if (timestamp && timestamp !== 0)
    return GLOBAL_MONTHS[moment.unix(timestamp/1000).month()];
  return "";
}

/*
 * Convert timestamp into month
 */
function toMonthNumber(timestamp) {
  if (timestamp && timestamp !== 0)
    return moment.unix(timestamp/1000).month() + 1;
  return "";
}

/*
 * This function build the datasets.
 */
function buildSeries(line_data) {
  var datasets = new Array();

  // Build the datasets for line and bar graphs
  if (PERFORMANCE_TAGS.locationbound) {
    var locations = config_locations();
    for (var i=0;i<locations.length;i++) {
      datasets.push(
        {
          name:PERFORMANCE_TAGS.label + " " + locations[i].name,
          data:line_data[PERFORMANCE_TAGS.label + " " + locations[i].name],
          tooltip:{valueDecimals:2},
        });
    }
  } else {
    datasets.push(
      {
        name:PERFORMANCE_TAGS.label,
        data:line_data[PERFORMANCE_TAGS.label],
        tooltip:{valueDecimals:2}
      });
  }

  return datasets;
}

function countValues(doc, names) {
  var total = 0;

  for (var k=0;k<names.length;k++) {
    if (doc && doc[names[k]]) {
      total += Number(doc[names[k]]);
    }
  }

  return total;
}

/*
 * This function handles the complete constuction and displaying of the charts.
 */
function renderGraph() {
  // Initialize arrays
  var line_data = new Object();
  if (PERFORMANCE_TAGS.locationbound) {
    var locations = config_locations();
    for (var i=0;i<locations.length;i++) {
      line_data[PERFORMANCE_TAGS.label + " " + locations[i].name] = new Array();
    }
  } else {
    line_data[PERFORMANCE_TAGS.label] = new Array();
  }

  // Calculate timestamps
  var start_timestamp = Number(moment(PERFORMANCE_START_MONTH+"-"+PERFORMANCE_START_YEAR, "MM-YYYY").unix() * 1000);
  var end_timestamp = Number(moment());

  // Parse each month
  var month_count = monthDifference(start_timestamp, end_timestamp);
  var month = Number(PERFORMANCE_START_MONTH);
  var year = Number(PERFORMANCE_START_YEAR);
  for (var i=0;i<month_count;i++) {

    // Append data
    var ts1 = Number(moment(month+"-"+year, "MM-YYYY").unix() * 1000);
    var ts2 = Number(moment((month+1)+"-"+year, "MM-YYYY").unix() * 1000);

    if (PERFORMANCE_TAGS.locationbound) {

      var locations = config_locations();
      for (var k=0;k<locations.length;k++) {
        var totalCount = 0;
        var totalSplit = 0;

        // Count all tag values of every form
        for (var j=0;j<PERFORMANCE_TAGS.count.length;j++) {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {location:Number(locations[k].id), timestamp:{$gt:ts1,$lt:ts2}});
          totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
        }

        // Count all tag values of every form
        for (var j=0;j<PERFORMANCE_TAGS.split.length;j++) {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {location:Number(locations[k].id), timestamp:{$gt:ts1,$lt:ts2}});
          totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
        }

        // Split if posible
        if (totalSplit > 0) {
          totalCount = totalCount / totalSplit;
        }

        line_data[PERFORMANCE_TAGS.label + " " + locations[k].name].push([ts1 + 86400000, totalCount]);
      }
    } else {
      var totalCount = 0;
      var totalSplit = 0;

      // Count all tag values of every form
      for (var j=0;j<PERFORMANCE_TAGS.count.length;j++) {
        // If one of the forms is location bound
        if (form_isLocationBound(PERFORMANCE_TAGS.count[j].form)) {
          var locations = config_locations();
          for (var l=0;l<locations.length;l++) {
            var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {location:Number(locations[l].id), timestamp:{$gt:ts1,$lt:ts2}});
            totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
          }
        // Else we do not need to parse each location
        } else {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {timestamp:{$gt:ts1,$lt:ts2}});
          totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
        }
      }

      // Count all tag values of every form
      for (var j=0;j<PERFORMANCE_TAGS.split.length;j++) {
        // If one of the forms is location bound
        if (form_isLocationBound(PERFORMANCE_TAGS.split[j].form)) {
          var locations = config_locations();
          for (var l=0;l<locations.length;l++) {
            var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {location:Number(locations[l].id), timestamp:{$gt:ts1,$lt:ts2}});
            totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
          }
        // Else we do not need to parse each location
        } else {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {timestamp:{$gt:ts1,$lt:ts2}});
          totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
        }
      }

      // Split if posible
      if (totalSplit > 0) {
        totalCount = totalCount / totalSplit;
      }

      line_data[PERFORMANCE_TAGS.label].push([ts1 + 86400000, totalCount]);
    }

    // update the counter
    month++;
    if (month > GLOBAL_MONTHS.length) {
      month = 1;
      year++;
    }
  }

  // Now we have all data and labels so we can display the chart
  $('#chartContainer').highcharts('StockChart', {
    rangeSelector: {selected:4,inputDateFormat:'%b %Y',inputEditDateFormat:'%b %Y'},
    xAxis:{gridLineWidth:1,type:'datetime',tickInterval:30*24*3600000,labels:{formatter:function() {return Highcharts.dateFormat("%b %Y", this.value);}}},
    title: {text:''},
    legend: {enabled:true},
    chart: {type: 'spline'},
    tooltip: {
      headerFormat: '{point.key}
',
      pointFormat: '
{series.name}: 	' +
        '{point.y:.1f} ' + PERFORMANCE_TAGS.unit + '
',
      footerFormat: '
',
      shared: true,
      useHTML: true
    },
    series:buildSeries(line_data)
  });
}

/*****************************************************************************
 * Template rendered function
 *****************************************************************************/

Template.performanceGraphs.rendered = function ()  {
  renderGraph();
}<commit_msg>Performance graphs working dynamically.<commit_after>// Variable declarations
/*var PERFORMANCE_TAGS = {"label":"Moves per Manhour",
                        "unit":"M/h",
                        "locationbound":true,
                        "count":[
                          {"form":6,"names":["bargemovesin","bargemovesout","truckmovesin","truckmovesout","trainmovesin","trainmovesout"]}
                        ],
                        "split":[
                          {"form":6,"names":["terminalmanhours"]}
                        ]};*/
var PERFORMANCE_TAGS = {"label":"KwH Factor",
                        "unit":"",
                        "locationbound":false,
                        "count":[
                          {"form":1,"names":["officekwh"]},
                          {"form":4,"names":["kwhrail"]},
                          {"form":6,"names":["cranekwh","terminalkwh"]}
                        ],
                        "split":[
                          {"form":6,"names":["terminalmanhours"]}
                        ]};

var PERFORMANCE_START_MONTH = 1;
var PERFORMANCE_START_YEAR = 2008;

/*****************************************************************************
 * General Template function
 *****************************************************************************/

/*
 * This function will initialize the default variables
 * which should be located in the 'this.data' variable.
 */
Template.performanceGraphs.initialize = function() {
  // Check if the data is available
  if (this.data) {
    //PERFORMANCE_TAGS = this.data;

  // If the data is not available we throw an error
  } else {
    PERFORMANCE_TAGS = undefined;
    throwError("The graphs are not correctly initialized! Please contact the administrator.");
  }
}

/*****************************************************************************
 * General function
 *****************************************************************************/

/*
 * This function calculates the amount of months between
 * two given timestamps.
 */
function monthDifference(d1, d2) {
    var months;
    months = (moment(d2).year() - moment(d1).year()) * 12;
    months -= moment(d1).month();
    months += moment(d2).month() + 1;
    return months <= 0 ? 0 : months;
}

/*
 * Convert timestamp into year
 */
function toYear(timestamp) {
  if (timestamp && timestamp !== 0)
    return moment.unix(timestamp/1000).year();
  return "";
}

/*
 * Convert timestamp into month
 */
function toMonth(timestamp) {
  if (timestamp && timestamp !== 0)
    return GLOBAL_MONTHS[moment.unix(timestamp/1000).month()];
  return "";
}

/*
 * Convert timestamp into month
 */
function toMonthNumber(timestamp) {
  if (timestamp && timestamp !== 0)
    return moment.unix(timestamp/1000).month() + 1;
  return "";
}

/*
 * This function build the datasets.
 */
function buildSeries(line_data) {
  var datasets = new Array();

  // Build the datasets for line and bar graphs
  if (PERFORMANCE_TAGS.locationbound) {
    var locations = config_locations();
    for (var i=0;i<locations.length;i++) {
      datasets.push(
        {
          name:PERFORMANCE_TAGS.label + " " + locations[i].name,
          data:line_data[PERFORMANCE_TAGS.label + " " + locations[i].name],
          tooltip:{valueDecimals:2},
        });
    }
  } else {
    datasets.push(
      {
        name:PERFORMANCE_TAGS.label,
        data:line_data[PERFORMANCE_TAGS.label],
        tooltip:{valueDecimals:2}
      });
  }

  return datasets;
}

function countValues(doc, names) {
  var total = 0;

  for (var k=0;k<names.length;k++) {
    if (doc && doc[names[k]]) {
      total += Number(doc[names[k]]);
    }
  }

  return total;
}

/*
 * This function handles the complete constuction and displaying of the charts.
 */
function renderGraph() {
  // Initialize arrays
  var line_data = new Object();
  if (PERFORMANCE_TAGS.locationbound) {
    var locations = config_locations();
    for (var i=0;i<locations.length;i++) {
      line_data[PERFORMANCE_TAGS.label + " " + locations[i].name] = new Array();
    }
  } else {
    line_data[PERFORMANCE_TAGS.label] = new Array();
  }

  // Calculate timestamps
  var start_timestamp = Number(moment(PERFORMANCE_START_MONTH+"-"+PERFORMANCE_START_YEAR, "MM-YYYY").unix() * 1000);
  var end_timestamp = Number(moment());

  // Parse each month
  var month_count = monthDifference(start_timestamp, end_timestamp);
  var month = Number(PERFORMANCE_START_MONTH);
  var year = Number(PERFORMANCE_START_YEAR);
  for (var i=0;i<month_count;i++) {

    // Append data
    var ts1 = Number(moment(month+"-"+year, "MM-YYYY").unix() * 1000);
    var ts2 = Number(moment((month+1)+"-"+year, "MM-YYYY").unix() * 1000);

    if (PERFORMANCE_TAGS.locationbound) {

      var locations = config_locations();
      for (var k=0;k<locations.length;k++) {
        var totalCount = 0;
        var totalSplit = 0;

        // Count all tag values of every form
        for (var j=0;j<PERFORMANCE_TAGS.count.length;j++) {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {location:Number(locations[k].id), timestamp:{$gt:ts1,$lt:ts2}});
          totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
        }

        // Count all tag values of every form
        for (var j=0;j<PERFORMANCE_TAGS.split.length;j++) {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {location:Number(locations[k].id), timestamp:{$gt:ts1,$lt:ts2}});
          totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
        }

        // Split if posible
        if (totalSplit > 0) {
          totalCount = totalCount / totalSplit;
        }

        line_data[PERFORMANCE_TAGS.label + " " + locations[k].name].push([ts1 + 86400000, totalCount]);
      }
    } else {
      var totalCount = 0;
      var totalSplit = 0;

      // Count all tag values of every form
      for (var j=0;j<PERFORMANCE_TAGS.count.length;j++) {
        // If one of the forms is location bound
        if (form_isLocationBound(PERFORMANCE_TAGS.count[j].form)) {
          var locations = config_locations();
          for (var l=0;l<locations.length;l++) {
            var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {location:Number(locations[l].id), timestamp:{$gt:ts1,$lt:ts2}});
            totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
          }
        // Else we do not need to parse each location
        } else {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.count[j].form), {timestamp:{$gt:ts1,$lt:ts2}});
          totalCount += countValues(doc, PERFORMANCE_TAGS.count[j].names);
        }
      }

      // Count all tag values of every form
      for (var j=0;j<PERFORMANCE_TAGS.split.length;j++) {
        // If one of the forms is location bound
        if (form_isLocationBound(PERFORMANCE_TAGS.split[j].form)) {
          var locations = config_locations();
          for (var l=0;l<locations.length;l++) {
            var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {location:Number(locations[l].id), timestamp:{$gt:ts1,$lt:ts2}});
            totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
          }
        // Else we do not need to parse each location
        } else {
          var doc = collection_findOne(form_name(PERFORMANCE_TAGS.split[j].form), {timestamp:{$gt:ts1,$lt:ts2}});
          totalSplit += countValues(doc, PERFORMANCE_TAGS.split[j].names);
        }
      }

      // Split if posible
      if (totalSplit > 0) {
        totalCount = totalCount / totalSplit;
      }

      line_data[PERFORMANCE_TAGS.label].push([ts1 + 86400000, totalCount]);
    }

    // update the counter
    month++;
    if (month > GLOBAL_MONTHS.length) {
      month = 1;
      year++;
    }
  }

  // Now we have all data and labels so we can display the chart
  $('#chartContainer').highcharts('StockChart', {
    rangeSelector: {selected:4,inputDateFormat:'%b %Y',inputEditDateFormat:'%b %Y'},
    xAxis:{gridLineWidth:1,type:'datetime',tickInterval:30*24*3600000,labels:{formatter:function() {return Highcharts.dateFormat("%b %Y", this.value);}}},
    title: {text:''},
    legend: {enabled:true},
    chart: {type: 'spline'},
    tooltip: {
      headerFormat: '{point.key}
',
      pointFormat: '
{series.name}: 	' +
        '{point.y:.1f} ' + PERFORMANCE_TAGS.unit + '
',
      footerFormat: '
',
      shared: true,
      useHTML: true
    },
    series:buildSeries(line_data)
  });
}

/*****************************************************************************
 * Template rendered function
 *****************************************************************************/

Template.performanceGraphs.rendered = function ()  {
  renderGraph();
}<|endoftext|>


<commit_before>var toId = global.toId;
var Tools = global.Tools;
var utils = require('./utils.js');
var statIDs = utils.statIDs;

// Mostly from http://github.com/Zarel/Pokemon-Showdown-Client/blob/master/js/client-teambuilder.js
module.exports = function parseTeams (text) {
	var teams = [];
	var lines = text.split('\n');
	var curSet = null;
	for (var i = 0; i < lines.length; i++) {
		var line = lines[i].trim();
		if (line === '' || line === '---') {
			curSet = null;
		} else if (line.slice(0, 3) === '===' && teams) {
			// Do nothing
			continue;
		} else if (!curSet) {
			curSet = {species: '', gender: ''};
			teams.push(curSet);
			var atIndex = line.lastIndexOf(' @ ');
			if (atIndex !== -1) {
				curSet.item = line.slice(atIndex + 3);
				if (toId(curSet.item) === 'noitem') curSet.item = '';
				line = line.slice(0, atIndex);
			}
			if (line.slice(line.length - 4) === ' (M)') {
				curSet.gender = 'M';
				line = line.slice(0, -4);
			}
			if (line.slice(line.length - 4) === ' (F)') {
				curSet.gender = 'F';
				line = line.slice(0, -4);
			}
			var parenIndex = line.lastIndexOf(' (');
			if (line.slice(-1) === ')' && parenIndex !== -1) {
				line = line.slice(0, -1);
				curSet.species = Tools.getTemplate(line.slice(parenIndex + 2)).name;
			} else {
				curSet.species = Tools.getTemplate(line).name;
			}
		} else if (line.slice(0, 7) === 'Trait: ') {
			line = line.slice(7);
			curSet.ability = line;
		} else if (line.slice(0, 9) === 'Ability: ') {
			line = line.slice(9);
			curSet.ability = line;
		} else if (line === 'Shiny: Yes') {
			curSet.shiny = true;
		} else if (line.slice(0, 7) === 'Level: ') {
			line = line.slice(7);
			curSet.level = +line;
		} else if (line.slice(0, 11) === 'Happiness: ') {
			line = line.slice(11);
			curSet.happiness = +line;
		} else if (line.slice(0, 9) === 'Ability: ') {
			line = line.slice(9);
			curSet.ability = line;
		} else if (line.slice(0, 5) === 'EVs: ') {
			line = line.slice(5);
			var evLines = line.split('/');
			curSet.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
			for (var j = 0; j < evLines.length; j++) {
				var evLine = evLines[j].trim();
				var spaceIndex = evLine.indexOf(' ');
				if (spaceIndex === -1) continue;
				var statid = statIDs[evLine.slice(spaceIndex + 1)];
				var statval = parseInt(evLine.slice(0, spaceIndex), 10);
				if (!statid) continue;
				curSet.evs[statid] = statval;
			}
		} else if (line.slice(0, 5) === 'IVs: ') {
			line = line.slice(5);
			var ivLines = line.split('/');
			curSet.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
			for (var j = 0; j < ivLines.length; j++) {
				var ivLine = ivLines[j].trim();
				var spaceIndex = ivLine.indexOf(' ');
				if (spaceIndex === -1) continue;
				var statid = statIDs[ivLine.slice(spaceIndex + 1)];
				var statval = parseInt(ivLine.slice(0, spaceIndex), 10);
				if (!statid) continue;
				curSet.ivs[statid] = statval;
			}
		} else if (line.match(/^[A-Za-z]+ (N|n)ature/)) {
			var natureIndex = line.indexOf(' Nature');
			if (natureIndex === -1) natureIndex = line.indexOf(' nature');
			if (natureIndex === -1) continue;
			line = line.slice(0, natureIndex);
			curSet.nature = line;
		} else if (line.charAt(0) === '-' || line.charAt(0) === '~') {
			line = line.slice(1);
			if (line.charAt(0) === ' ') line = line.slice(1);
			if (!curSet.moves) curSet.moves = [];
			curSet.moves.push(line.split(/\s*\/\s*/g));
		}
	}
	return teams;
};
<commit_msg>Permalink for team parser source<commit_after>var toId = global.toId;
var Tools = global.Tools;
var utils = require('./utils.js');
var statIDs = utils.statIDs;

// Mostly from https://github.com/Zarel/Pokemon-Showdown-Client/blob/2a6a2667a751f/js/storage.js#L262
module.exports = function parseTeams (text) {
	var teams = [];
	var lines = text.split('\n');
	var curSet = null;
	for (var i = 0; i < lines.length; i++) {
		var line = lines[i].trim();
		if (line === '' || line === '---') {
			curSet = null;
		} else if (line.slice(0, 3) === '===' && teams) {
			// Do nothing
			continue;
		} else if (!curSet) {
			curSet = {species: '', gender: ''};
			teams.push(curSet);
			var atIndex = line.lastIndexOf(' @ ');
			if (atIndex !== -1) {
				curSet.item = line.slice(atIndex + 3);
				if (toId(curSet.item) === 'noitem') curSet.item = '';
				line = line.slice(0, atIndex);
			}
			if (line.slice(line.length - 4) === ' (M)') {
				curSet.gender = 'M';
				line = line.slice(0, -4);
			}
			if (line.slice(line.length - 4) === ' (F)') {
				curSet.gender = 'F';
				line = line.slice(0, -4);
			}
			var parenIndex = line.lastIndexOf(' (');
			if (line.slice(-1) === ')' && parenIndex !== -1) {
				line = line.slice(0, -1);
				curSet.species = Tools.getTemplate(line.slice(parenIndex + 2)).name;
			} else {
				curSet.species = Tools.getTemplate(line).name;
			}
		} else if (line.slice(0, 7) === 'Trait: ') {
			line = line.slice(7);
			curSet.ability = line;
		} else if (line.slice(0, 9) === 'Ability: ') {
			line = line.slice(9);
			curSet.ability = line;
		} else if (line === 'Shiny: Yes') {
			curSet.shiny = true;
		} else if (line.slice(0, 7) === 'Level: ') {
			line = line.slice(7);
			curSet.level = +line;
		} else if (line.slice(0, 11) === 'Happiness: ') {
			line = line.slice(11);
			curSet.happiness = +line;
		} else if (line.slice(0, 9) === 'Ability: ') {
			line = line.slice(9);
			curSet.ability = line;
		} else if (line.slice(0, 5) === 'EVs: ') {
			line = line.slice(5);
			var evLines = line.split('/');
			curSet.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
			for (var j = 0; j < evLines.length; j++) {
				var evLine = evLines[j].trim();
				var spaceIndex = evLine.indexOf(' ');
				if (spaceIndex === -1) continue;
				var statid = statIDs[evLine.slice(spaceIndex + 1)];
				var statval = parseInt(evLine.slice(0, spaceIndex), 10);
				if (!statid) continue;
				curSet.evs[statid] = statval;
			}
		} else if (line.slice(0, 5) === 'IVs: ') {
			line = line.slice(5);
			var ivLines = line.split('/');
			curSet.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
			for (var j = 0; j < ivLines.length; j++) {
				var ivLine = ivLines[j].trim();
				var spaceIndex = ivLine.indexOf(' ');
				if (spaceIndex === -1) continue;
				var statid = statIDs[ivLine.slice(spaceIndex + 1)];
				var statval = parseInt(ivLine.slice(0, spaceIndex), 10);
				if (!statid) continue;
				curSet.ivs[statid] = statval;
			}
		} else if (line.match(/^[A-Za-z]+ (N|n)ature/)) {
			var natureIndex = line.indexOf(' Nature');
			if (natureIndex === -1) natureIndex = line.indexOf(' nature');
			if (natureIndex === -1) continue;
			line = line.slice(0, natureIndex);
			curSet.nature = line;
		} else if (line.charAt(0) === '-' || line.charAt(0) === '~') {
			line = line.slice(1);
			if (line.charAt(0) === ' ') line = line.slice(1);
			if (!curSet.moves) curSet.moves = [];
			curSet.moves.push(line.split(/\s*\/\s*/g));
		}
	}
	return teams;
};
<|endoftext|>


<commit_before>import React from 'react';
import {connectToStores} from 'fluxible-addons-react';
import UserProfileStore from '../../../../stores/UserProfileStore';
import AttachSubdeckModalStore from '../../../../stores/AttachSubdeckModalStore';
import AttachDeckList from './AttachDeckList';
import {  Segment, Loader,Label, Image,Dimmer,Grid, Button,TextArea} from 'semantic-ui-react';
import {Microservices} from '../../../../configs/microservices';
import updateSelectedSlides  from '../../../../actions/attachSubdeck/updateSelectedSlides';

const KEY_CODE = {
    LEFT:   37,
    UP:     38,
    RIGHT:  39,
    DOWN:   40
};

class AttachSlides extends React.Component{

    constructor(props){
        super(props);
        this.numColumns = props.numColumns?props.numColumns:4; //if numColumns is not provided, 4 is used as default
        this.state = {

            userDecks:this.props.AttachSubdeckModalStore.userDecks,
            selectedDeckTitle:  this.props.AttachSubdeckModalStore.selectedDeckTitle,
            selectedDeckId: this.props.AttachSubdeckModalStore.selectedDeckId,
            deckSlides: this.props.AttachSubdeckModalStore.deckSlides,
            selectedSlides:this.props.AttachSubdeckModalStore.selectedSlides,
            deckSlidesTitles:this.props.AttachSubdeckModalStore.deckSlidesTitles,
            selectedSlidesLabel: this.props.AttachSubdeckModalStore.selectedSlides.length +' of ' + this.props.AttachSubdeckModalStore.deckSlides.length

        };

        this.handleAllSlides = this.handleAllSlides.bind(this);
        this.handleNone = this.handleNone.bind(this);

    }
    componentWillReceiveProps(nextProps){

        this.setState({
            userDecks: nextProps.AttachSubdeckModalStore.userDecks,
            selectedDeckId: nextProps.AttachSubdeckModalStore.selectedDeckId,
            deckSlides: nextProps.AttachSubdeckModalStore.deckSlides,
            selectedSlides:nextProps.AttachSubdeckModalStore.selectedSlides,
            selectedDeckTitle:nextProps.AttachSubdeckModalStore.selectedDeckTitle,
            deckSlidesTitles:nextProps.AttachSubdeckModalStore.deckSlidesTitles,
            selectedSlidesLabel: nextProps.AttachSubdeckModalStore.selectedSlides.length +' of ' + this.props.AttachSubdeckModalStore.deckSlides.length

        });

    }
    componentDidUpdate(){
        if(this.state.deckSlides.length !== 0) //We have the slides rendered
            $('#attachAllSlidesButtonId').focus();

    }

    checkNoEmpty(element){
        return (element.toString().length>0);
    }

    handleOnclick(selectedSlide){
      /*This method:
       - adds the selectedSlide into the selectedSlides list if it was not selectedSlide
       - removes the selectedSlide from the selectedSlides list if it was
      */
        let slides = this.state.selectedSlides;
        let index = slides.indexOf(selectedSlide);
        if(index === -1){//It was not selected
            slides.push(selectedSlide);
        } else { //It was selected...remove from it
            slides[index]='';
            slides = slides.filter(this.checkNoEmpty);
        };

        this.setState({
            selectedSlides: slides
        });

        this.context.executeAction(updateSelectedSlides,{selectedSlides:slides},null);

    }
    handleAllSlides(){
        this.setState({
            selectedSlides:this.state.deckSlides,
        });
        this.context.executeAction(updateSelectedSlides,{selectedSlides:this.state.deckSlides},null);
    }

    handleNone(){
        this.setState({
            selectedSlides: [],
        });

        this.context.executeAction(updateSelectedSlides,{selectedSlides:[]},null);

    }

    handleKeyPress(selectedSlide,event){
        if(event.key === 'Enter'){
            event.preventDefault();
            this.handleOnclick(selectedSlide);

        }
    }

    getNextPos(pos,numSlides,numColumns,numRows,eventKeyCode){
        /*******************************************
            Returns next position in a grid, taking into account
            the arrow key pressed and the number of rows and columns
            of the grid
        ********************************************/
        let currentRow = Math.floor(pos/numColumns);

        let lastPos = numSlides-1;
        let nextPos = parseInt(pos); //In case of overflow, we stayed in the same position. Force working with integers
        switch (eventKeyCode) {
            case KEY_CODE.RIGHT:
                if(pos !== lastPos) //No overflow by right direction
                    nextPos ++;
                break;
            case KEY_CODE.LEFT:
                if(pos !== 0)
                    nextPos --;
                break;
            case KEY_CODE.UP:
                if(currentRow !== 0)
                    nextPos = nextPos - parseInt(numColumns);
                break;
            case KEY_CODE.DOWN:
                if(currentRow !== (numRows-1))
                    nextPos = nextPos + parseInt(numColumns);
                break;

        }
        return nextPos;

    }
    handleKeyDown(pos,event){

        if(event.keyCode === KEY_CODE.RIGHT ||
           event.keyCode === KEY_CODE.LEFT ||
           event.keyCode === KEY_CODE.UP ||
           event.keyCode === KEY_CODE.DOWN ){
           //the user wants to navigate through the grid
            let numRows = Math.ceil(this.state.deckSlides.length/this.numColumns);
            let nextPos = this.getNextPos(pos,this.state.deckSlides.length,this.numColumns,numRows,event.keyCode);  //get next cell
             //get the id of the cell
            $('#slide'+nextPos).focus(); //move to the cell
        }

    }
    loadSlidesGrid(){
        let activeItemStyle = {
            backgroundColor:'#f8ffff',
            border:'2px solid #2185d0'

        };

        let numRows = Math.ceil(this.state.deckSlides.length/this.numColumns);
        let rowCount=0;
        let columnCount=0;
        let slidesShowed=0;
        let columnsContent=[];
        let singleColumn;
        let singleRow;
        let rowsContent=[];
        let slideId;

        for(let i=0;i<numRows;i++){
            while((columnCount<this.numColumns) && (slidesShowed < this.state.deckSlides.length)){
                slideId =this.state.deckSlides[slidesShowed];
                singleColumn =  <Grid.Column key={slidesShowed}
                                    id={'slide'+slidesShowed}
                                    onClick={this.handleOnclick.bind(this,slideId)}
                                    onKeyPress={this.handleKeyPress.bind(this,slideId)}
                                    onKeyDown={this.handleKeyDown.bind(this,slidesShowed)}
                                    style={this.state.selectedSlides.includes(slideId)?activeItemStyle:{}}
                                    role="gridcell"
                                    aria-selected ={this.state.selectedSlides.includes(slideId)}
                                    tabIndex="0">
                                    <Image src={Microservices.file.uri + '/slideThumbnail/' +slideId+'.jpeg'}
                                        alt={'Slide '+ (slidesShowed+1)+'. '+this.state.deckSlidesTitles[slidesShowed]} bordered size='medium' />
                                  </Grid.Column>;
                columnsContent[columnCount] = singleColumn;
                columnCount ++;
                slidesShowed ++;
            }

            singleRow = <Grid.Row  key={i.toString()}>
                          {columnsContent}
                        </Grid.Row>;
            rowsContent[i]=singleRow;
            columnCount = 0;
            columnsContent=[];
        }

        return rowsContent;

    }

    render(){
        let userInfo = {
            userId: this.props.UserProfileStore.userid,
            username: this.props.UserProfileStore.username

        };

        let slidesContent;

        if(this.state.deckSlides.length === 0){ //No slides loaded
            slidesContent = <Segment id="panelSlidesContent">
                                <Dimmer active inverted>
                                  <Loader inverted>Loading</Loader>
                                </Dimmer>
                                <Image src="http://semantic-ui.com/images/wireframe/paragraph.png" />
                              </Segment>;
        }else{

            let headerContent =  <Grid>
                                    <Grid.Row columns={1}>
                                      <Grid.Column>
                                        <Label htmlFor="selectedDeckTitleId" as="label"  color="blue" pointing="right" content='Selected Deck'/>
                                        <Label  id="selectedDeckTitleId" content={this.state.selectedDeckTitle} basic color="blue"/>
                                      </Grid.Column>
                                    </Grid.Row>
                                    <Grid.Row columns={2}>
                                      <Grid.Column textAlign="left">
                                      <Label htmlFor="slidesContentId"  color='blue'  pointing="right"  content="Selected slides:"/>
                                      <Label  id="slidesContentId" content={this.state.selectedSlidesLabel} basic color="blue"/>

                                      </Grid.Column>
                                      <Grid.Column textAlign="right" >
                                        <Button type="button"
                                           id="attachAllSlidesButtonId"
                                           color="blue"
                                           aria-label="Select All"
                                           data-tooltip="Select All"
                                           onClick={this.handleAllSlides} >Select All</Button>
                                        <Button type="button"
                                            color="blue"
                                            aria-label="Clear Selection"
                                            data-tooltip="Clear Selection"
                                            onClick={this.handleNone}>Clear Selection</Button>
                                            <TextArea className="sr-only" id="gridInstructions" value="Use arrow keys to navigate through the grid and then enter to select a slide. You can select more than one slide." tabIndex ='-1'/>
                                      </Grid.Column>
                                    </Grid.Row>
                                  </Grid>;
            let rowsContent=this.loadSlidesGrid();
            slidesContent = <Segment id="panelSlidesContent">
                               {headerContent}
                               <Grid columns={this.numColumns}
                                 style={{maxHeight:'400px',minHeight:'320px',overflowY:'auto'}}
                                 role="grid"
                                 aria-expanded="true"
                                  aria-describedby="gridInstructions">
                                 {/*<TextArea className="sr-only" id="gridInstructions" value="Use arrow keys to navigate through the grid and then enter to select a slide. You can select more than one slide." tabIndex ='-1'/>*/}
                                {rowsContent}
                               </Grid>

                              </Segment>;
        }

        return slidesContent;

    }

}

AttachSlides.contextTypes = {
    executeAction: React.PropTypes.func.isRequired
};
AttachSlides = connectToStores(AttachSlides,[UserProfileStore,AttachSubdeckModalStore],(context,props) => {
    return {
        UserProfileStore: context.getStore(UserProfileStore).getState(),
        AttachSubdeckModalStore: context.getStore(AttachSubdeckModalStore).getState()
    };
});
export default AttachSlides;
<commit_msg>SWIK-1447-MoveAcrossGrid Moved the grid explanation to the original place<commit_after>import React from 'react';
import {connectToStores} from 'fluxible-addons-react';
import UserProfileStore from '../../../../stores/UserProfileStore';
import AttachSubdeckModalStore from '../../../../stores/AttachSubdeckModalStore';
import AttachDeckList from './AttachDeckList';
import {  Segment, Loader,Label, Image,Dimmer,Grid, Button,TextArea} from 'semantic-ui-react';
import {Microservices} from '../../../../configs/microservices';
import updateSelectedSlides  from '../../../../actions/attachSubdeck/updateSelectedSlides';

const KEY_CODE = {
    LEFT:   37,
    UP:     38,
    RIGHT:  39,
    DOWN:   40
};

class AttachSlides extends React.Component{

    constructor(props){
        super(props);
        this.numColumns = props.numColumns?props.numColumns:4; //if numColumns is not provided, 4 is used as default
        this.state = {

            userDecks:this.props.AttachSubdeckModalStore.userDecks,
            selectedDeckTitle:  this.props.AttachSubdeckModalStore.selectedDeckTitle,
            selectedDeckId: this.props.AttachSubdeckModalStore.selectedDeckId,
            deckSlides: this.props.AttachSubdeckModalStore.deckSlides,
            selectedSlides:this.props.AttachSubdeckModalStore.selectedSlides,
            deckSlidesTitles:this.props.AttachSubdeckModalStore.deckSlidesTitles,
            selectedSlidesLabel: this.props.AttachSubdeckModalStore.selectedSlides.length +' of ' + this.props.AttachSubdeckModalStore.deckSlides.length

        };

        this.handleAllSlides = this.handleAllSlides.bind(this);
        this.handleNone = this.handleNone.bind(this);

    }
    componentWillReceiveProps(nextProps){

        this.setState({
            userDecks: nextProps.AttachSubdeckModalStore.userDecks,
            selectedDeckId: nextProps.AttachSubdeckModalStore.selectedDeckId,
            deckSlides: nextProps.AttachSubdeckModalStore.deckSlides,
            selectedSlides:nextProps.AttachSubdeckModalStore.selectedSlides,
            selectedDeckTitle:nextProps.AttachSubdeckModalStore.selectedDeckTitle,
            deckSlidesTitles:nextProps.AttachSubdeckModalStore.deckSlidesTitles,
            selectedSlidesLabel: nextProps.AttachSubdeckModalStore.selectedSlides.length +' of ' + this.props.AttachSubdeckModalStore.deckSlides.length

        });

    }
    componentDidUpdate(){
        if(this.state.deckSlides.length !== 0) //We have the slides rendered
            $('#attachAllSlidesButtonId').focus();

    }

    checkNoEmpty(element){
        return (element.toString().length>0);
    }

    handleOnclick(selectedSlide){
      /*This method:
       - adds the selectedSlide into the selectedSlides list if it was not selectedSlide
       - removes the selectedSlide from the selectedSlides list if it was
      */
        let slides = this.state.selectedSlides;
        let index = slides.indexOf(selectedSlide);
        if(index === -1){//It was not selected
            slides.push(selectedSlide);
        } else { //It was selected...remove from it
            slides[index]='';
            slides = slides.filter(this.checkNoEmpty);
        };

        this.setState({
            selectedSlides: slides
        });

        this.context.executeAction(updateSelectedSlides,{selectedSlides:slides},null);

    }
    handleAllSlides(){
        this.setState({
            selectedSlides:this.state.deckSlides,
        });
        this.context.executeAction(updateSelectedSlides,{selectedSlides:this.state.deckSlides},null);
    }

    handleNone(){
        this.setState({
            selectedSlides: [],
        });

        this.context.executeAction(updateSelectedSlides,{selectedSlides:[]},null);

    }

    handleKeyPress(selectedSlide,event){
        if(event.key === 'Enter'){
            event.preventDefault();
            this.handleOnclick(selectedSlide);

        }
    }

    getNextPos(pos,numSlides,numColumns,numRows,eventKeyCode){
        /*******************************************
            Returns next position in a grid, taking into account
            the arrow key pressed and the number of rows and columns
            of the grid
        ********************************************/
        let currentRow = Math.floor(pos/numColumns);

        let lastPos = numSlides-1;
        let nextPos = parseInt(pos); //In case of overflow, we stayed in the same position. Force working with integers
        switch (eventKeyCode) {
            case KEY_CODE.RIGHT:
                if(pos !== lastPos) //No overflow by right direction
                    nextPos ++;
                break;
            case KEY_CODE.LEFT:
                if(pos !== 0)
                    nextPos --;
                break;
            case KEY_CODE.UP:
                if(currentRow !== 0)
                    nextPos = nextPos - parseInt(numColumns);
                break;
            case KEY_CODE.DOWN:
                if(currentRow !== (numRows-1))
                    nextPos = nextPos + parseInt(numColumns);
                break;

        }
        return nextPos;

    }
    handleKeyDown(pos,event){

        if(event.keyCode === KEY_CODE.RIGHT ||
           event.keyCode === KEY_CODE.LEFT ||
           event.keyCode === KEY_CODE.UP ||
           event.keyCode === KEY_CODE.DOWN ){
           //the user wants to navigate through the grid
            let numRows = Math.ceil(this.state.deckSlides.length/this.numColumns);
            let nextPos = this.getNextPos(pos,this.state.deckSlides.length,this.numColumns,numRows,event.keyCode);  //get next cell
             //get the id of the cell
            $('#slide'+nextPos).focus(); //move to the cell
        }

    }
    loadSlidesGrid(){
        let activeItemStyle = {
            backgroundColor:'#f8ffff',
            border:'2px solid #2185d0'

        };

        let numRows = Math.ceil(this.state.deckSlides.length/this.numColumns);
        let rowCount=0;
        let columnCount=0;
        let slidesShowed=0;
        let columnsContent=[];
        let singleColumn;
        let singleRow;
        let rowsContent=[];
        let slideId;

        for(let i=0;i<numRows;i++){
            while((columnCount<this.numColumns) && (slidesShowed < this.state.deckSlides.length)){
                slideId =this.state.deckSlides[slidesShowed];
                singleColumn =  <Grid.Column key={slidesShowed}
                                    id={'slide'+slidesShowed}
                                    onClick={this.handleOnclick.bind(this,slideId)}
                                    onKeyPress={this.handleKeyPress.bind(this,slideId)}
                                    onKeyDown={this.handleKeyDown.bind(this,slidesShowed)}
                                    style={this.state.selectedSlides.includes(slideId)?activeItemStyle:{}}
                                    role="gridcell"
                                    aria-selected ={this.state.selectedSlides.includes(slideId)}
                                    tabIndex="0">
                                    <Image src={Microservices.file.uri + '/slideThumbnail/' +slideId+'.jpeg'}
                                        alt={'Slide '+ (slidesShowed+1)+'. '+this.state.deckSlidesTitles[slidesShowed]} bordered size='medium' />
                                  </Grid.Column>;
                columnsContent[columnCount] = singleColumn;
                columnCount ++;
                slidesShowed ++;
            }

            singleRow = <Grid.Row  key={i.toString()}>
                          {columnsContent}
                        </Grid.Row>;
            rowsContent[i]=singleRow;
            columnCount = 0;
            columnsContent=[];
        }

        return rowsContent;

    }

    render(){
        let userInfo = {
            userId: this.props.UserProfileStore.userid,
            username: this.props.UserProfileStore.username

        };

        let slidesContent;

        if(this.state.deckSlides.length === 0){ //No slides loaded
            slidesContent = <Segment id="panelSlidesContent">
                                <Dimmer active inverted>
                                  <Loader inverted>Loading</Loader>
                                </Dimmer>
                                <Image src="http://semantic-ui.com/images/wireframe/paragraph.png" />
                              </Segment>;
        }else{

            let headerContent =  <Grid>
                                    <Grid.Row columns={1}>
                                      <Grid.Column>
                                        <Label htmlFor="selectedDeckTitleId" as="label"  color="blue" pointing="right" content='Selected Deck'/>
                                        <Label  id="selectedDeckTitleId" content={this.state.selectedDeckTitle} basic color="blue"/>
                                      </Grid.Column>
                                    </Grid.Row>
                                    <Grid.Row columns={2}>
                                      <Grid.Column textAlign="left">
                                      <Label htmlFor="slidesContentId"  color='blue'  pointing="right"  content="Selected slides:"/>
                                      <Label  id="slidesContentId" content={this.state.selectedSlidesLabel} basic color="blue"/>

                                      </Grid.Column>
                                      <Grid.Column textAlign="right" >
                                        <Button type="button"
                                           id="attachAllSlidesButtonId"
                                           color="blue"
                                           aria-label="Select All"
                                           data-tooltip="Select All"
                                           onClick={this.handleAllSlides} >Select All</Button>
                                        <Button type="button"
                                            color="blue"
                                            aria-label="Clear Selection"
                                            data-tooltip="Clear Selection"
                                            onClick={this.handleNone}>Clear Selection</Button>

                                      </Grid.Column>
                                    </Grid.Row>
                                  </Grid>;
            let rowsContent=this.loadSlidesGrid();
            slidesContent = <Segment id="panelSlidesContent">
                               {headerContent}
                               <Grid columns={this.numColumns}
                                 style={{maxHeight:'400px',minHeight:'320px',overflowY:'auto'}}
                                 role="grid"
                                 aria-expanded="true"
                                  aria-describedby="gridInstructions">
                                 <TextArea className="sr-only" id="gridInstructions" value="Use arrow keys to navigate through the grid and then enter to select a slide. You can select more than one slide." tabIndex ='-1'/>
                                {rowsContent}
                               </Grid>
                            </Segment>;
        }

        return slidesContent;

    }

}

AttachSlides.contextTypes = {
    executeAction: React.PropTypes.func.isRequired
};
AttachSlides = connectToStores(AttachSlides,[UserProfileStore,AttachSubdeckModalStore],(context,props) => {
    return {
        UserProfileStore: context.getStore(UserProfileStore).getState(),
        AttachSubdeckModalStore: context.getStore(AttachSubdeckModalStore).getState()
    };
});
export default AttachSlides;
<|endoftext|>


<commit_before>import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';

import '@polymer/app-layout/app-drawer/app-drawer.js';
import '@polymer/app-layout/app-drawer-layout/app-drawer-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-scroll-effects/app-scroll-effects.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/app-route/app-location.js';
import '@polymer/app-route/app-route.js';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/iron-collapse/iron-collapse.js';
import '@polymer/iron-selector/iron-selector.js';
import '@polymer/iron-flex-layout/iron-flex-layout';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-tabs/paper-tabs.js';
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-item/paper-icon-item.js';
import '@polymer/paper-item/paper-item-body.js';
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
import 'web-animations-js/web-animations-next.min.js';
import '@polymer/neon-animation/neon-animatable.js';
import '@polymer/neon-animation/neon-animated-pages.js';
import '@polymer/neon-animation/animations/fade-in-animation.js';
import '@polymer/neon-animation/animations/fade-out-animation.js';

import css from './main-page.css';
import template from './main-page.pug';

/**
 * Entry point for application UI.
 */
export class MainPage extends PolymerElement {
    static get template() {
        return html([`<style>${css.toString()}</style> ${template()}`]);
    }

    static get properties() {
        return {
            links: {
                type: Array,
                value: [
                    {text: 'Home', defaultPage: true, hasDivider: true},
                    {
                        link: 'https://www.kubeflow.org/docs/about/kubeflow/',
                        text: 'Kubeflow docs',
                    },
                    {link: '/jupyter/', text: 'JupyterHub'},
                    {link: '/tfjobs/ui/', text: 'TFJob Dashboard'},
                    {link: '/katib/', text: 'Katib Dashboard'},
                    {link: '/pipeline/', text: 'Pipeline Dashboard'},
                ],
            },
            gettingStartedItems: {
                type: Array,
                value: [
                    {text: 'Build a model in a notebook', icon: 'donut-large'},
                    {text: 'Launch a pipeline', icon: 'donut-large'},
                    {text: 'Deploy a solution', icon: 'donut-large'},
                    {text: 'Compare run output', icon: 'donut-large'},
                    {text: 'Share solution on AI Hub', icon: 'donut-large'},
                ],
            },
            quickLinks: {
                type: Array,
                value: [
                    {
                        text: 'Open docs',
                        link: 'https://www.kubeflow.org/docs/started/getting-started/',
                    },
                    {
                        text: 'Open Github',
                        link: 'https://github.com/kubeflow/kubeflow',
                    },
                ],
            },
            sidebarItemIndex: {type: Number, value: 0},
            primaryViewIndex: {type: Number, value: 0},
            homeOrIframeViewIndex: {type: Number, value: 0},
            url: {type: String, value: ''},
            buildVersion: {type: String, value: '0.4.1'},
            dashVersion: {type: String, value: VERSION},
            _devMode: {type: Boolean, value: false},
        };
    }

    openExternalLink(href) {
        const a = document.createElement('a');
        a.href = href;
        a.target = '_blank';
        a.click();
    }

    openQuickLink(e) {
        const {link} = e.model.item;
        this.openExternalLink(link);
    }

    openLink(e) {
        const {link, defaultPage} = e.model.item;
        this.homeOrIframeViewIndex = defaultPage ? 0 : 1;
        if (defaultPage) return;
        this.url = link;
    }

    toggleSidebar() {
        this.$.MainDrawer.toggle();
    }

    isZero(i) {
        return i === 0;
    }
}

window.customElements.define('main-page', MainPage);
<commit_msg>Changed sidebar tab from JupyterHub to Notebooks (#2619)<commit_after>import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';

import '@polymer/app-layout/app-drawer/app-drawer.js';
import '@polymer/app-layout/app-drawer-layout/app-drawer-layout.js';
import '@polymer/app-layout/app-header/app-header.js';
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
import '@polymer/app-layout/app-scroll-effects/app-scroll-effects.js';
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
import '@polymer/app-route/app-location.js';
import '@polymer/app-route/app-route.js';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/iron-collapse/iron-collapse.js';
import '@polymer/iron-selector/iron-selector.js';
import '@polymer/iron-flex-layout/iron-flex-layout';
import '@polymer/paper-card/paper-card.js';
import '@polymer/paper-tabs/paper-tabs.js';
import '@polymer/paper-item/paper-item.js';
import '@polymer/paper-item/paper-icon-item.js';
import '@polymer/paper-item/paper-item-body.js';
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
import 'web-animations-js/web-animations-next.min.js';
import '@polymer/neon-animation/neon-animatable.js';
import '@polymer/neon-animation/neon-animated-pages.js';
import '@polymer/neon-animation/animations/fade-in-animation.js';
import '@polymer/neon-animation/animations/fade-out-animation.js';

import css from './main-page.css';
import template from './main-page.pug';

/**
 * Entry point for application UI.
 */
export class MainPage extends PolymerElement {
    static get template() {
        return html([`<style>${css.toString()}</style> ${template()}`]);
    }

    static get properties() {
        return {
            links: {
                type: Array,
                value: [
                    {text: 'Home', defaultPage: true, hasDivider: true},
                    {
                        link: 'https://www.kubeflow.org/docs/about/kubeflow/',
                        text: 'Kubeflow docs',
                    },
                    {link: '/jupyter/', text: 'Notebooks'},
                    {link: '/tfjobs/ui/', text: 'TFJob Dashboard'},
                    {link: '/katib/', text: 'Katib Dashboard'},
                    {link: '/pipeline/', text: 'Pipeline Dashboard'},
                ],
            },
            gettingStartedItems: {
                type: Array,
                value: [
                    {text: 'Build a model in a notebook', icon: 'donut-large'},
                    {text: 'Launch a pipeline', icon: 'donut-large'},
                    {text: 'Deploy a solution', icon: 'donut-large'},
                    {text: 'Compare run output', icon: 'donut-large'},
                    {text: 'Share solution on AI Hub', icon: 'donut-large'},
                ],
            },
            quickLinks: {
                type: Array,
                value: [
                    {
                        text: 'Open docs',
                        link: 'https://www.kubeflow.org/docs/started/getting-started/',
                    },
                    {
                        text: 'Open Github',
                        link: 'https://github.com/kubeflow/kubeflow',
                    },
                ],
            },
            sidebarItemIndex: {type: Number, value: 0},
            primaryViewIndex: {type: Number, value: 0},
            homeOrIframeViewIndex: {type: Number, value: 0},
            url: {type: String, value: ''},
            buildVersion: {type: String, value: '0.4.1'},
            dashVersion: {type: String, value: VERSION},
            _devMode: {type: Boolean, value: false},
        };
    }

    openExternalLink(href) {
        const a = document.createElement('a');
        a.href = href;
        a.target = '_blank';
        a.click();
    }

    openQuickLink(e) {
        const {link} = e.model.item;
        this.openExternalLink(link);
    }

    openLink(e) {
        const {link, defaultPage} = e.model.item;
        this.homeOrIframeViewIndex = defaultPage ? 0 : 1;
        if (defaultPage) return;
        this.url = link;
    }

    toggleSidebar() {
        this.$.MainDrawer.toggle();
    }

    isZero(i) {
        return i === 0;
    }
}

window.customElements.define('main-page', MainPage);
<|endoftext|>


<commit_before>import React from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';
import {
    classnames,
    Alert,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Copy,
    Badge,
    Button,
    Time,
    useModals,
    useNotifications
} from 'react-components';
import { DKIM_RSA_1024, DKIM_RSA_2048, DKIM_KEY_STATUS, DKIM_KEY_DNS_STATUS } from 'proton-shared/lib/constants';
import { orderBy } from 'proton-shared/lib/helpers/array';

import GenerateKeyModal from './GenerateKeyModal';

const ALGO = {
    [DKIM_RSA_1024]: 'RSA 1024',
    [DKIM_RSA_2048]: 'RSA 2048'
};

const getKeyStatusBadge = (status) =>
    ({
        [DKIM_KEY_STATUS.ACTIVE]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`This is the key we currently use for signing your emails.`}
                type="success"
            >{c('Status').t`Active`}</Badge>
        ),
        [DKIM_KEY_STATUS.PENDING]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`We are waiting for you to insert this record correctly in your DNS.`}
                type="warning"
            >{c('Status').t`Pending`}</Badge>
        ),
        [DKIM_KEY_STATUS.RETIRED]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`This key is no longer used for DKIM signing.`}
                type="origin"
            >{c('Status').t`Retired`}</Badge>
        )
    }[status]);

const getDNSStatusBadge = (status) =>
    ({
        [DKIM_KEY_DNS_STATUS.NOT_SET]: <Badge className="flex mr1" type="error">{c('Status').t`Warning`}</Badge>,
        [DKIM_KEY_DNS_STATUS.INVALID]: (
            <Badge
                className="flex mr1"
                tooltip={c('Description').t`There is a problem with this record. Please check your DNS.`}
                type="error"
            >{c('Status').t`Warning`}</Badge>
        )
    }[status]);

const DKIMSection = ({ domain }) => {
    const { createModal } = useModals();
    const { createNotification } = useNotifications();
    const openGenerateKeyModal = () => createModal(<GenerateKeyModal domain={domain} />);
    const handleCopy = () => createNotification({ text: c('Success').t`Key copied to clipboard!` });

    return (
        <>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/anti-spoofing/">
                {c('Info')
                    .t`ProtonMail supports DKIM signing for custom domains. To use DKIM authentication, please add the following TXT record into your DNS settings for this domain. This can typically be done in the control panel of your domain name registrar.`}
            </Alert>

{c('Label')
                .t`Please add the following TXT record. Note, DNS records can take several hours to update.`}

            {orderBy(
                domain.Keys.filter(({ State }) => State !== DKIM_KEY_STATUS.DECEASED),
                'State'
            ).map(({ PublicKey, Algorithm, Selector, CreateTime, State, DNSState }, index) => {
                const value = `v=DKIM1;k=rsa;p=${State === DKIM_KEY_STATUS.RETIRED ? '' : `${PublicKey};`}`;
                return (
                    <React.Fragment key={index}>

                                {c('Label').t`Key:`}
                                {ALGO[Algorithm]}
                                {State === DKIM_KEY_STATUS.ACTIVE ? (
                                    <>
                                        {c('Label').t`Active since:`}
                                        <Time>{CreateTime}</Time>
                                    </>
                                ) : null}

                                {getDNSStatusBadge(DNSState)}
                                {DNSState === DKIM_KEY_STATUS.RETIRED ? null : getKeyStatusBadge(State)}

                        {DNSState === DKIM_KEY_DNS_STATUS.INVALID ? (
                            <Alert type="error">{c('Warning')
                                .t`Please check this DNS entry. It's not set properly. Most common problems include wrong format, having multiple entries with the same selector or having a wrong public key.`}</Alert>
                        ) : null}

                            <TableHeader
                                cells={[
                                    c('Header for domain modal').t`Type`,
                                    c('Header for domain modal').t`Host name`,
                                    c('Header for domain modal').t`Value`
                                ]}
                            />
                            <TableBody>
                                <TableRow
                                    cells={[
                                        TXT,
                                        {`${Selector}._domainkey`},

                                            <Copy
                                                onCopy={handleCopy}
                                                className="flex-item-noshrink pm-button--small mr0-5"
                                                value={value}
                                            />{' '}

                                                {value}

                                    ]}
                                />
                            </TableBody>

                    </React.Fragment>
                );
            })}
            <Button className="mb1" onClick={openGenerateKeyModal}>{c('Action').t`Generate a new key`}</Button>
            <Alert type="warning">{c('Info')
                .t`Keep this record in your DNS for as long as you want to use DKIM.`}</Alert>
        </>
    );
};

DKIMSection.propTypes = {
    domain: PropTypes.object.isRequired
};

export default DKIMSection;
<commit_msg>Review condition for DKIM key status<commit_after>import React from 'react';
import { c } from 'ttag';
import PropTypes from 'prop-types';
import {
    classnames,
    Alert,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Copy,
    Badge,
    Button,
    Time,
    useModals,
    useNotifications
} from 'react-components';
import { DKIM_RSA_1024, DKIM_RSA_2048, DKIM_KEY_STATUS, DKIM_KEY_DNS_STATUS } from 'proton-shared/lib/constants';
import { orderBy } from 'proton-shared/lib/helpers/array';

import GenerateKeyModal from './GenerateKeyModal';

const ALGO = {
    [DKIM_RSA_1024]: 'RSA 1024',
    [DKIM_RSA_2048]: 'RSA 2048'
};

const getKeyStatusBadge = (status) =>
    ({
        [DKIM_KEY_STATUS.ACTIVE]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`This is the key we currently use for signing your emails.`}
                type="success"
            >{c('Status').t`Active`}</Badge>
        ),
        [DKIM_KEY_STATUS.PENDING]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`We are waiting for you to insert this record correctly in your DNS.`}
                type="warning"
            >{c('Status').t`Pending`}</Badge>
        ),
        [DKIM_KEY_STATUS.RETIRED]: (
            <Badge
                className="flex"
                tooltip={c('Description').t`This key is no longer used for DKIM signing.`}
                type="origin"
            >{c('Status').t`Retired`}</Badge>
        )
    }[status]);

const getDNSStatusBadge = (status) =>
    ({
        [DKIM_KEY_DNS_STATUS.NOT_SET]: <Badge className="flex mr1" type="error">{c('Status').t`Warning`}</Badge>,
        [DKIM_KEY_DNS_STATUS.INVALID]: (
            <Badge
                className="flex mr1"
                tooltip={c('Description').t`There is a problem with this record. Please check your DNS.`}
                type="error"
            >{c('Status').t`Warning`}</Badge>
        )
    }[status]);

const DKIMSection = ({ domain }) => {
    const { createModal } = useModals();
    const { createNotification } = useNotifications();
    const openGenerateKeyModal = () => createModal(<GenerateKeyModal domain={domain} />);
    const handleCopy = () => createNotification({ text: c('Success').t`Key copied to clipboard!` });

    return (
        <>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/anti-spoofing/">
                {c('Info')
                    .t`ProtonMail supports DKIM signing for custom domains. To use DKIM authentication, please add the following TXT record into your DNS settings for this domain. This can typically be done in the control panel of your domain name registrar.`}
            </Alert>

{c('Label')
                .t`Please add the following TXT record. Note, DNS records can take several hours to update.`}

            {orderBy(
                domain.Keys.filter(({ State }) => State !== DKIM_KEY_STATUS.DECEASED),
                'State'
            ).map(({ PublicKey, Algorithm, Selector, CreateTime, State, DNSState }, index) => {
                const value = `v=DKIM1;k=rsa;p=${State === DKIM_KEY_STATUS.RETIRED ? '' : `${PublicKey};`}`;
                return (
                    <React.Fragment key={index}>

                                {c('Label').t`Key:`}
                                {ALGO[Algorithm]}
                                {State === DKIM_KEY_STATUS.ACTIVE ? (
                                    <>
                                        {c('Label').t`Active since:`}
                                        <Time>{CreateTime}</Time>
                                    </>
                                ) : null}

                                {State === DKIM_KEY_STATUS.RETIRED ? null : getDNSStatusBadge(DNSState)}
                                {getKeyStatusBadge(State)}

                        {DNSState === DKIM_KEY_DNS_STATUS.INVALID ? (
                            <Alert type="error">{c('Warning')
                                .t`Please check this DNS entry. It's not set properly. Most common problems include wrong format, having multiple entries with the same selector or having a wrong public key.`}</Alert>
                        ) : null}

                            <TableHeader
                                cells={[
                                    c('Header for domain modal').t`Type`,
                                    c('Header for domain modal').t`Host name`,
                                    c('Header for domain modal').t`Value`
                                ]}
                            />
                            <TableBody>
                                <TableRow
                                    cells={[
                                        TXT,
                                        {`${Selector}._domainkey`},

                                            <Copy
                                                onCopy={handleCopy}
                                                className="flex-item-noshrink pm-button--small mr0-5"
                                                value={value}
                                            />{' '}

                                                {value}

                                    ]}
                                />
                            </TableBody>

                    </React.Fragment>
                );
            })}
            <Button className="mb1" onClick={openGenerateKeyModal}>{c('Action').t`Generate a new key`}</Button>
            <Alert type="warning">{c('Info')
                .t`Keep this record in your DNS for as long as you want to use DKIM.`}</Alert>
        </>
    );
};

DKIMSection.propTypes = {
    domain: PropTypes.object.isRequired
};

export default DKIMSection;
<|endoftext|>


<commit_before>(function () {
    var Ext = window.Ext4 || window.Ext;

    Ext.define("Rally.apps.charts.PortfolioChartAppBase", {
        extend: "Rally.app.App",
        settingsScope: "workspace",

        requires: [
            'Rally.ui.combobox.ComboBox'
        ],

        scheduleStates: ["Defined", "In-Progress", "Completed", "Accepted"],

        PI_SETTING: "buttonchooser",

        items: [
            {
                xtype: 'container',
                itemId: 'header',
                cls: 'header'
            }
        ],

        dateFormatters: [
            {key: "MMM", value: "%b"},
            {key: "MM", value: "%m"},
            {key: "dd", value: "%d"},
            {key: "yyyy", value: "%Y"}
        ],

        getSettingsFields: function () {
            var self = this;

            return [
                {
                    type: "buttonchooser",
                    label: "Portfolio Item",
                    config: {
                        type: "portfolioitem",
                        cls: "pichooser",
                        context: this.getContext()
                    }
                },
                {
                    xtype: "rallycombobox",
                    name: "chartAggregationType",
                    fieldLabel: "Data Type",
                    queryMode: "local",
                    editable: false,
                    store: {
                        xtype: "store",
                        fields: ["name", "value"],
                        data: [
                            {name: "Story Count", value: "storycount"},
                            {name: "Story Plan Estimate", value: "storypoints"}
                        ]
                    },
                    displayField: "name",
                    valueField: "value",
                    listeners: {
                        load: function() {
                            this.setValue(self.getSetting("chartAggregationType") || "storycount");
                        },
                        select: function() {
                            this.setValue(this.value);
                        }
                    }
                }
            ];
        },

        clientMetrics: [
            {
                beginEvent: 'updateBeforeRender',
                endEvent: 'updateAfterRender',
                defaultUserAction: 'pichartapp - elapsed chart load'
            }
        ],

        launch: function () {
            this._setupEvents();

            this._addHelpComponent();
            this._setDefaultConfigValues();
            this._setupUpdateBeforeRender();

            var savedPortfolioItem = this._loadSavedPortfolioItem();
            if (savedPortfolioItem) {
                this._loadPortfolioItem(savedPortfolioItem);
            }
            else {
                // this.owner is the panel this app belongs to
                this.owner.showSettings();
            }
        },

        _setupUpdateBeforeRender: function() {
            this.chartComponentConfig.updateBeforeRender = this._setupDynamicHooksWithEvents(
                this.chartComponentConfig.updateBeforeRender,
                'updateBeforeRender'
            );

            this.chartComponentConfig.updateAfterRender = this._setupDynamicHooksWithEvents(
                this.chartComponentConfig.updateAfterRender,
                'updateAfterRender'
            );
        },

        _setupDynamicHooksWithEvents: function(func, event) {
            var self = this;

            return function() {
                self.fireEvent(event);
                if ('function' === typeof func) {
                    func.apply(this);
                }
            };
        },

        _setupEvents: function () {
            this.addEvents(
                'updateBeforeRender',
                'updateAfterRender'
            );
        },

        _addHelpComponent: function () {
            this.down('#header').add(this._buildHelpComponent());
        },

        _setDefaultConfigValues: function () {
            var config = Ext.clone(this.chartComponentConfig);

            config.storeConfig.find = config.storeConfig.find || {};

            config.calculatorConfig = config.calculatorConfig || {};

            config.chartConfig = config.chartConfig || {};
            config.chartConfig.title = config.chartConfig.title || {};
            config.chartConfig.xAxis = config.chartConfig.xAxis || {};
            config.chartConfig.xAxis.type = config.chartConfig.xAxis.type || "datetime";
            config.chartConfig.yAxis = config.chartConfig.yAxis || [
                {
                    title: {}
                }
            ];

            this.chartComponentConfig = config;
        },

        _buildHelpComponent: function () {
            return Ext.create('Ext.Component', {
                renderTpl: Rally.util.Help.getIcon({
                    cls: Rally.util.Test.toBrowserTestCssClass(this.help.cls),
                    id: this.help.id
                })
            });
        },

        _loadSavedPortfolioItem: function () {
            var piJson = this.getSetting(this.PI_SETTING);
            if (!piJson) {
                return false;
            }

            var savedPi = Ext.JSON.decode(piJson);
            if (!this._savedPortfolioItemValid(savedPi)) {
                return false;
            }

            return savedPi;
        },

        _savedPortfolioItemValid: function (savedPi) {
            return savedPi &&
                savedPi.context &&
                savedPi.context.workspace &&
                savedPi.context.project &&
                savedPi.artifact &&
                savedPi.artifact._type &&
                savedPi.artifact.ObjectID;
        },

        _loadPortfolioItem: function (savedPortfolioItem) {
            var context = savedPortfolioItem.context;
            var portfolioItem = savedPortfolioItem.artifact;

            Rally.data.ModelFactory.getModel({
                context: context,
                type: portfolioItem._type,
                success: function (model) {
                    this._onModelRetrieved(model, savedPortfolioItem);
                },
                scope: this
            });
        },

        _onModelRetrieved: function (model, savedPortfolioItem) {
            if (model) {
                model.find({
                    filters: {
                        property: "ObjectID",
                        value: savedPortfolioItem.artifact.ObjectID
                    },
                    context: {
                        workspace: this.getContext().getWorkspace()._ref,
                        project: null
                    },
                    scope: this,
                    callback: this._onPortfolioItemRetrieved
                });
            } else {
                this._portfolioItemNotValid();
            }
        },

        _onPortfolioItemRetrieved: function (portfolioItemRecord) {
            if (portfolioItemRecord) {
                Rally.data.ModelFactory.getModel({
                    type: 'UserStory',
                    success: function (model) {
                        this._onUserStoryModelRetrieved(model, portfolioItemRecord);
                    },
                    scope: this
                });
            } else {
                this._setErrorTextMessage("A server error occurred, please refresh the page.");
            }
        },

        _onUserStoryModelRetrieved: function (model, portfolioItemRecord) {
            this._updateChartComponentConfig(model, portfolioItemRecord.data);
            this.add(this.chartComponentConfig);

            Rally.environment.getMessageBus().publish(Rally.Message.piChartAppReady);
        },

        _updateChartComponentConfig: function (model, portfolioItem) {
            this._setScheduleStateFieldValues(model);
            this._setDynamicConfigValues(portfolioItem);
            this._calculateDateRange(portfolioItem);
            this._updateQueryConfig(portfolioItem);
        },

        _setScheduleStateFieldValues: function (model) {
            if (model) {
                var allowedScheduleStates = model.getField('ScheduleState').allowedValues;
                var i, length, scheduleStateValues = [];

                for (i = 0, length = allowedScheduleStates.length; i < length; i++) {
                    scheduleStateValues.push(allowedScheduleStates[i].StringValue);
                }

                this.chartComponentConfig.calculatorConfig.scheduleStates = scheduleStateValues;
            } else {
                this.chartComponentConfig.calculatorConfig.scheduleStates = this.scheduleStates;
            }
        },

        _setDynamicConfigValues: function (portfolioItem) {
            this._updateChartConfigDateFormat();
            this.chartComponentConfig.chartConfig.title = this._buildChartTitle(portfolioItem);

            this.chartComponentConfig.calculatorConfig.chartAggregationType = this._getAggregationType();
            this.chartComponentConfig.chartConfig.yAxis[0].title.text = this._getYAxisTitle();
            this.chartComponentConfig.chartConfig.yAxis[0].labels = {
                x: -5,
                y: 4
            };
        },

        _updateChartConfigDateFormat: function () {
            var rallyDateFormat = this._parseRallyDateFormatToHighchartsDateFormat();

            this.chartComponentConfig.chartConfig.xAxis.labels = {
                x: 0,
                y: 20,
                formatter: function () {
                    return Highcharts.dateFormat(rallyDateFormat, new Date(this.value).getTime());
                }
            };
        },

        _parseRallyDateFormatToHighchartsDateFormat: function () {
            var rallyDateFormat = this._getWorkspaceConfiguredDateFormat(),
                i, length;

            for (i = 0, length = this.dateFormatters.length; i < length; i++) {
                rallyDateFormat = rallyDateFormat.replace(this.dateFormatters[i].key, this.dateFormatters[i].value);
            }
            return rallyDateFormat;
        },

        _calculateDateRange: function (portfolioItem) {
            var calcConfig = this.chartComponentConfig.calculatorConfig;
            calcConfig.startDate = calcConfig.startDate || this._getChartStartDate(portfolioItem);
            calcConfig.endDate = calcConfig.endDate || this._getChartEndDate(portfolioItem);
            calcConfig.timeZone = calcConfig.timeZone || this._getTimeZone();

            this.chartComponentConfig.chartConfig.xAxis.tickInterval = this._configureChartTicks(calcConfig.startDate, calcConfig.endDate);
        },

        _updateQueryConfig: function (portfolioItem) {
            this.chartComponentConfig.storeConfig.find._ItemHierarchy = portfolioItem.ObjectID;
        },

        _configureChartTicks: function (startDate, endDate) {
            var tickWidth = 125, // in pixels
                width = this.getWidth(), // app width
                ticks = Math.floor(width / tickWidth);

            var startDateObj = new Date(startDate),
                endDateObj = new Date(endDate);

            var days = Math.floor((endDateObj - startDateObj) / 86400000); // Converting ms to days

            return Math.floor(days / ticks); // number of dates that will fit on the xAxis
        },

        _getWorkspaceConfiguredDateFormat: function () {
            return this.getContext().getWorkspace().WorkspaceConfiguration.DateFormat;
        },

        _buildChartTitle: function (portfolioItem) {
            var widthPerCharacter = 10,
                totalCharacters = Math.floor(this.getWidth() / widthPerCharacter);

            var title = "Portfolio Item Chart",
                align = "center";

            if (portfolioItem) {
                var fullTitle = portfolioItem.FormattedID + ": " + portfolioItem.Name;

                if(totalCharacters < fullTitle.length) {
                    title = fullTitle.substring(0, totalCharacters) + "...";
                    align = "left";
                } else {
                    title = fullTitle;
                }
            }

            return {
                text: title,
                align: align
            };
        },

        _getAggregationType: function () {
            return this.getSetting("chartAggregationType") || "storycount";
        },

        _getYAxisTitle: function () {
            return this._getAggregationType() === "storypoints" ?
                "Points" :
                "Count";
        },

        _getChartStartDate: function (portfolioItem) {
            var date = portfolioItem.ActualStartDate || portfolioItem.ActualEndDate || new Date();
            return Ext.Date.format(date, 'Y-m-d\\TH:i:s.u\\Z');
        },

        _getChartEndDate: function (portfolioItem) {
            var date = portfolioItem.ActualEndDate || new Date();
            return Ext.Date.format(date, 'Y-m-d\\TH:i:s.u\\Z');
        },

        _getTimeZone: function () {
            return this.getContext().getUser().UserProfile.TimeZone || this.getContext().getWorkspace().WorkspaceConfiguration.TimeZone;
        },

        _portfolioItemNotValid: function () {
            this._setErrorTextMessage('Cannot find the chosen portfolio item.  Please click the gear and "Edit Settings" to choose another.');
        },

        _setErrorTextMessage: function (message) {
            this.down('#header').add({
                xtype: 'displayfield',
                value: message
            });
        }
    });
}());
<commit_msg>S49149 - Use the user date format as default, fallback to workspace date format<commit_after>(function () {
    var Ext = window.Ext4 || window.Ext;

    Ext.define("Rally.apps.charts.PortfolioChartAppBase", {
        extend: "Rally.app.App",
        settingsScope: "workspace",

        requires: [
            'Rally.ui.combobox.ComboBox'
        ],

        scheduleStates: ["Defined", "In-Progress", "Completed", "Accepted"],

        PI_SETTING: "buttonchooser",

        items: [
            {
                xtype: 'container',
                itemId: 'header',
                cls: 'header'
            }
        ],

        dateFormatters: [
            {key: "MMM", value: "%b"},
            {key: "MM", value: "%m"},
            {key: "dd", value: "%d"},
            {key: "yyyy", value: "%Y"}
        ],

        getSettingsFields: function () {
            var self = this;

            return [
                {
                    type: "buttonchooser",
                    label: "Portfolio Item",
                    config: {
                        type: "portfolioitem",
                        cls: "pichooser",
                        context: this.getContext()
                    }
                },
                {
                    xtype: "rallycombobox",
                    name: "chartAggregationType",
                    fieldLabel: "Data Type",
                    queryMode: "local",
                    editable: false,
                    store: {
                        xtype: "store",
                        fields: ["name", "value"],
                        data: [
                            {name: "Story Count", value: "storycount"},
                            {name: "Story Plan Estimate", value: "storypoints"}
                        ]
                    },
                    displayField: "name",
                    valueField: "value",
                    listeners: {
                        load: function() {
                            this.setValue(self.getSetting("chartAggregationType") || "storycount");
                        },
                        select: function() {
                            this.setValue(this.value);
                        }
                    }
                }
            ];
        },

        clientMetrics: [
            {
                beginEvent: 'updateBeforeRender',
                endEvent: 'updateAfterRender',
                defaultUserAction: 'pichartapp - elapsed chart load'
            }
        ],

        launch: function () {
            this._setupEvents();

            this._addHelpComponent();
            this._setDefaultConfigValues();
            this._setupUpdateBeforeRender();

            var savedPortfolioItem = this._loadSavedPortfolioItem();
            if (savedPortfolioItem) {
                this._loadPortfolioItem(savedPortfolioItem);
            }
            else {
                // this.owner is the panel this app belongs to
                this.owner.showSettings();
            }
        },

        _setupUpdateBeforeRender: function() {
            this.chartComponentConfig.updateBeforeRender = this._setupDynamicHooksWithEvents(
                this.chartComponentConfig.updateBeforeRender,
                'updateBeforeRender'
            );

            this.chartComponentConfig.updateAfterRender = this._setupDynamicHooksWithEvents(
                this.chartComponentConfig.updateAfterRender,
                'updateAfterRender'
            );
        },

        _setupDynamicHooksWithEvents: function(func, event) {
            var self = this;

            return function() {
                self.fireEvent(event);
                if ('function' === typeof func) {
                    func.apply(this);
                }
            };
        },

        _setupEvents: function () {
            this.addEvents(
                'updateBeforeRender',
                'updateAfterRender'
            );
        },

        _addHelpComponent: function () {
            this.down('#header').add(this._buildHelpComponent());
        },

        _setDefaultConfigValues: function () {
            var config = Ext.clone(this.chartComponentConfig);

            config.storeConfig.find = config.storeConfig.find || {};

            config.calculatorConfig = config.calculatorConfig || {};

            config.chartConfig = config.chartConfig || {};
            config.chartConfig.title = config.chartConfig.title || {};
            config.chartConfig.xAxis = config.chartConfig.xAxis || {};
            config.chartConfig.xAxis.type = config.chartConfig.xAxis.type || "datetime";
            config.chartConfig.yAxis = config.chartConfig.yAxis || [
                {
                    title: {}
                }
            ];

            this.chartComponentConfig = config;
        },

        _buildHelpComponent: function () {
            return Ext.create('Ext.Component', {
                renderTpl: Rally.util.Help.getIcon({
                    cls: Rally.util.Test.toBrowserTestCssClass(this.help.cls),
                    id: this.help.id
                })
            });
        },

        _loadSavedPortfolioItem: function () {
            var piJson = this.getSetting(this.PI_SETTING);
            if (!piJson) {
                return false;
            }

            var savedPi = Ext.JSON.decode(piJson);
            if (!this._savedPortfolioItemValid(savedPi)) {
                return false;
            }

            return savedPi;
        },

        _savedPortfolioItemValid: function (savedPi) {
            return savedPi &&
                savedPi.context &&
                savedPi.context.workspace &&
                savedPi.context.project &&
                savedPi.artifact &&
                savedPi.artifact._type &&
                savedPi.artifact.ObjectID;
        },

        _loadPortfolioItem: function (savedPortfolioItem) {
            var context = savedPortfolioItem.context;
            var portfolioItem = savedPortfolioItem.artifact;

            Rally.data.ModelFactory.getModel({
                context: context,
                type: portfolioItem._type,
                success: function (model) {
                    this._onModelRetrieved(model, savedPortfolioItem);
                },
                scope: this
            });
        },

        _onModelRetrieved: function (model, savedPortfolioItem) {
            if (model) {
                model.find({
                    filters: {
                        property: "ObjectID",
                        value: savedPortfolioItem.artifact.ObjectID
                    },
                    context: {
                        workspace: this.getContext().getWorkspace()._ref,
                        project: null
                    },
                    scope: this,
                    callback: this._onPortfolioItemRetrieved
                });
            } else {
                this._portfolioItemNotValid();
            }
        },

        _onPortfolioItemRetrieved: function (portfolioItemRecord) {
            if (portfolioItemRecord) {
                Rally.data.ModelFactory.getModel({
                    type: 'UserStory',
                    success: function (model) {
                        this._onUserStoryModelRetrieved(model, portfolioItemRecord);
                    },
                    scope: this
                });
            } else {
                this._setErrorTextMessage("A server error occurred, please refresh the page.");
            }
        },

        _onUserStoryModelRetrieved: function (model, portfolioItemRecord) {
            this._updateChartComponentConfig(model, portfolioItemRecord.data);
            this.add(this.chartComponentConfig);

            Rally.environment.getMessageBus().publish(Rally.Message.piChartAppReady);
        },

        _updateChartComponentConfig: function (model, portfolioItem) {
            this._setScheduleStateFieldValues(model);
            this._setDynamicConfigValues(portfolioItem);
            this._calculateDateRange(portfolioItem);
            this._updateQueryConfig(portfolioItem);
        },

        _setScheduleStateFieldValues: function (model) {
            if (model) {
                var allowedScheduleStates = model.getField('ScheduleState').allowedValues;
                var i, length, scheduleStateValues = [];

                for (i = 0, length = allowedScheduleStates.length; i < length; i++) {
                    scheduleStateValues.push(allowedScheduleStates[i].StringValue);
                }

                this.chartComponentConfig.calculatorConfig.scheduleStates = scheduleStateValues;
            } else {
                this.chartComponentConfig.calculatorConfig.scheduleStates = this.scheduleStates;
            }
        },

        _setDynamicConfigValues: function (portfolioItem) {
            this._updateChartConfigDateFormat();
            this.chartComponentConfig.chartConfig.title = this._buildChartTitle(portfolioItem);

            this.chartComponentConfig.calculatorConfig.chartAggregationType = this._getAggregationType();
            this.chartComponentConfig.chartConfig.yAxis[0].title.text = this._getYAxisTitle();
            this.chartComponentConfig.chartConfig.yAxis[0].labels = {
                x: -5,
                y: 4
            };
        },

        _updateChartConfigDateFormat: function () {
            var rallyDateFormat = this._parseRallyDateFormatToHighchartsDateFormat();

            this.chartComponentConfig.chartConfig.xAxis.labels = {
                x: 0,
                y: 20,
                formatter: function () {
                    return Highcharts.dateFormat(rallyDateFormat, new Date(this.value).getTime());
                }
            };
        },

        _parseRallyDateFormatToHighchartsDateFormat: function () {
            var dateFormat = this._getUserConfiguredDateFormat() || this._getWorkspaceConfiguredDateFormat();

            for (var i = 0; i < this.dateFormatters.length; i++) {
                dateFormat = dateFormat.replace(this.dateFormatters[i].key, this.dateFormatters[i].value);
            }

            return dateFormat;
        },

        _calculateDateRange: function (portfolioItem) {
            var calcConfig = this.chartComponentConfig.calculatorConfig;
            calcConfig.startDate = calcConfig.startDate || this._getChartStartDate(portfolioItem);
            calcConfig.endDate = calcConfig.endDate || this._getChartEndDate(portfolioItem);
            calcConfig.timeZone = calcConfig.timeZone || this._getTimeZone();

            this.chartComponentConfig.chartConfig.xAxis.tickInterval = this._configureChartTicks(calcConfig.startDate, calcConfig.endDate);
        },

        _updateQueryConfig: function (portfolioItem) {
            this.chartComponentConfig.storeConfig.find._ItemHierarchy = portfolioItem.ObjectID;
        },

        _configureChartTicks: function (startDate, endDate) {
            var tickWidth = 125, // in pixels
                width = this.getWidth(), // app width
                ticks = Math.floor(width / tickWidth);

            var startDateObj = new Date(startDate),
                endDateObj = new Date(endDate);

            var days = Math.floor((endDateObj - startDateObj) / 86400000); // Converting ms to days

            return Math.floor(days / ticks); // number of dates that will fit on the xAxis
        },

        _getUserConfiguredDateFormat: function() {
            return this.getContext().getUser().UserProfile.DateFormat;
        },

        _getWorkspaceConfiguredDateFormat: function () {
            return this.getContext().getWorkspace().WorkspaceConfiguration.DateFormat;
        },

        _buildChartTitle: function (portfolioItem) {
            var widthPerCharacter = 10,
                totalCharacters = Math.floor(this.getWidth() / widthPerCharacter);

            var title = "Portfolio Item Chart",
                align = "center";

            if (portfolioItem) {
                var fullTitle = portfolioItem.FormattedID + ": " + portfolioItem.Name;

                if(totalCharacters < fullTitle.length) {
                    title = fullTitle.substring(0, totalCharacters) + "...";
                    align = "left";
                } else {
                    title = fullTitle;
                }
            }

            return {
                text: title,
                align: align
            };
        },

        _getAggregationType: function () {
            return this.getSetting("chartAggregationType") || "storycount";
        },

        _getYAxisTitle: function () {
            return this._getAggregationType() === "storypoints" ?
                "Points" :
                "Count";
        },

        _getChartStartDate: function (portfolioItem) {
            var date = portfolioItem.ActualStartDate || portfolioItem.ActualEndDate || new Date();
            return Ext.Date.format(date, 'Y-m-d\\TH:i:s.u\\Z');
        },

        _getChartEndDate: function (portfolioItem) {
            var date = portfolioItem.ActualEndDate || new Date();
            return Ext.Date.format(date, 'Y-m-d\\TH:i:s.u\\Z');
        },

        _getTimeZone: function () {
            return this.getContext().getUser().UserProfile.TimeZone || this.getContext().getWorkspace().WorkspaceConfiguration.TimeZone;
        },

        _portfolioItemNotValid: function () {
            this._setErrorTextMessage('Cannot find the chosen portfolio item.  Please click the gear and "Edit Settings" to choose another.');
        },

        _setErrorTextMessage: function (message) {
            this.down('#header').add({
                xtype: 'displayfield',
                value: message
            });
        }
    });
}());
<|endoftext|>