<commit_before>NCI.setupCommunities = function(data){
	NCI.Communities = data.Communities;
	NCI.Communities.sort(function(a, b){
		return a.Endpoints.length > b.Endpoints.length;
	});
	NCI.timestampNCI = data.NCI;
	NCI.timestamp = data.Time;
	$("#histogramGeneral").html("NETWORK COMPLEXITY INDEX at    " + NCI.parceDateForLastUpdate(NCI.timestamp) + "" +
	    "   NCI " + NCI.timestampNCI + "" );
};

NCI.nciHistogram = (function(){
	var me = $('#nciHistogram');

	var barWidth = 6;
	var chart = d3.select("#nciHistogram");
	var margin = {top: 40, right: 40, bottom: 40, left:40},
	    width = 600,
	    height = 300;

	me.show = function(){
		chart.text("");
		var endpointsScale = d3.scale.linear()
		    .domain([d3.max(NCI.Communities, function(d) { return d.Endpoints.length; }), 0])
		    .range([0, height - margin.top - margin.bottom]);

		var activitiesScale = d3.scale.linear()
		    .domain([NCI.Communities.length, 0])
		    .range([width - margin.right - margin.left, margin.left]);

		var activitiesAxis = d3.svg.axis()
		    .scale(activitiesScale)
		    .orient('bottom')
		    .tickSize(0)
			.tickFormat(d3.format("d"))
		    .tickPadding(8);

		var endpointsAxis = d3.svg.axis()
		    .scale(endpointsScale)
		    .orient('left')
			.tickSize(0)
			.tickFormat(d3.format("d"))
		    .tickPadding(10);

		var barChartSvg = chart.append('svg')
		    .attr('width', width)
		    .attr('height', height)
		    .append('g')
		    .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');

        //draw and animate bars
        var index = 1;
		barChartSvg.selectAll('g')
		    .data(NCI.Communities)
		    .enter().append('rect')
		    .attr('x', function(d) { return activitiesScale(index++) - barWidth/2})
		    .attr('y', function(d) { return   endpointsScale(d.Endpoints.length)}) //- selfwidth
		    .attr('width', function(d) { return barWidth})
		    .attr('height',function(d) { return height - margin.top - margin.bottom - endpointsScale(d.Endpoints.length) })
			.on("click", me.showDetails);
		// barChartSvg.selectAll('rect').data(NCI.Communities).transition()
		//     .duration(1000)
		// 	.attr("y", function(d) { return height - margin.top - margin.bottom - endpointsScale(d.Endpoints.length) })
		// 	.attr('height', function(d) { return   endpointsScale(d.Endpoints.length) });
		//draw NCI point
		barChartSvg.append("circle").attr("cy", endpointsScale(NCI.timestampNCI))
		    .attr("cx", activitiesScale(NCI.timestampNCI) ).style("fill", "red").attr("r", 6);
	    barChartSvg.append("circle").attr("cy", endpointsScale(0))
		    .attr("cx", activitiesScale(NCI.timestampNCI) ).style("fill", "red").attr("r", 4);
		barChartSvg.append("circle").attr("cy", endpointsScale(NCI.timestampNCI))
		    .attr("cx", activitiesScale(0) ).style("fill", "red").attr("r", 4);
		//draw axis
		barChartSvg.append('g')
		    .attr('class', 'x axis')
			.attr('transform', 'translate(0,' + (height - margin.top - margin.bottom) + ')')
			.call(activitiesAxis);
		barChartSvg.append('g')
			.attr('class', 'y axis')
			.attr('transform', 'translate(' + margin.left + ')')
			.call(endpointsAxis);
		//draw axis labels
		barChartSvg.append('text').text('Activities Sorted by Size').attr('x', width/2 - 100).attr('y', height - 45);
		barChartSvg.append('text').text('Number of Endpoints per Activity').attr('x', -height/2 - 40).attr('y', 0)
		.attr('transform', 'rotate(-90)');

	};

	me.showDetails = function(d){
		chart.select("#bar_endpoints").remove();
		var activityDetails = chart.append('svg')
			.attr("id","bar_endpoints")
			.attr('width', height)
		    .attr('height', height);

	    var graph = NCI.prepareDataForForceGraph([d]);

		var force = d3.layout.force()
			.charge(-60)
			.linkDistance(30)
			.size([ height, height])
			.linkStrength(1).nodes(graph.nodes).links(graph.links).start();

		var link = activityDetails.selectAll(".link")
		    .data(graph.links)
			.enter().append("line")
			.attr("class", "link")
			.style("stroke-width", function(d) { return Math.sqrt(d.value); });

		var node = activityDetails.selectAll(".node")
		    .data(graph.nodes)
		    .enter().append("circle")
		    .attr("r", 5)
		    .call(force.drag);

		node.append("title").text(function(d) { return d.name; });

		force.on("tick", function() {
			link.attr("x1", function(d) { return d.source.x; })
			    .attr("y1", function(d) { return d.source.y; })
				.attr("x2", function(d) { return d.target.x; })
				.attr("y2", function(d) { return d.target.y; });

			node.attr("cx", function(d) { return d.x; })
			    .attr("cy", function(d) { return d.y; });
		});
	};

	return me;
}());

NCI.socialGraph  = (function(){
	var me = $("#socialGraph");

	var force;
	var color = d3.scale.category10();
	me.clustered = false;
	var networkColor = "#000000";

	me.show = function(devided, clustered, filtered){
		if (me.clustered && !clustered){
			NCI.socialGraph.text("");
			me.draw(devided, clustered);
			me.clustered  = clustered;
			return;
		}
		me.clustered = clustered;
		if (me.text().length<20){
			me.draw(devided, clustered, filtered);
		} else {
			me.node.style("fill", function(d) {
  			    if ( filtered && (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)){
  				    return networkColor;
  			    }
  			    return devided ? color(d.group) : color(0);
			});
			me.node.attr("r", function(d) { return (filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)) ? 7 : 5})
			force.linkStrength(clustered ? 1 : 0);
	  	    force.start();
		}
	};

	me.draw = function(devided, clustered, filtered){
		d3.select("#activities_graph").remove();
	    me.graph = NCI.prepareDataForForceGraph(NCI.Communities);

		force = d3.layout.force()
			.charge(-60)
			.linkDistance(30)
			.size([ me.width(), $('#nciDetails').height() - 150])
			.linkStrength(clustered ? 1 : 0)
			.nodes(me.graph.nodes).links(me.graph.links).start();

	    me.activitiesGraphSvg = d3.select("#socialGraph").append("svg")
		    .attr("id","activities_graph")
			.attr("width", me.width())
			.attr("height", me.height());

		var link = me.activitiesGraphSvg.selectAll(".link")
		    .data(me.graph.links)
			.enter().append("line")
			.attr("class", "link")
			.style("stroke-width", function(d) { return Math.sqrt(d.value); });

		me.node = me.activitiesGraphSvg.selectAll(".node")
		    .data(me.graph.nodes)
			.enter().append("circle")
			.attr("class", "node")
			.attr("r", function(d) { return (filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)) ? 7 : 5})
			.style("fill", function(d) {
				if ( filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)){
				  return networkColor;
			    }
				return devided ? color(d.group) : color(0);
			})
			.call(force.drag);

		me.node.append("title").text(function(d) { return d.name; });

	    force.on("tick", function() {
	        link.attr("x1", function(d) { return d.source.x; })
	            .attr("y1", function(d) { return d.source.y; })
	            .attr("x2", function(d) { return d.target.x; })
	            .attr("y2", function(d) { return d.target.y; });

	        me.node.attr("cx", function(d) { return d.x; })
	            .attr("cy", function(d) { return d.y; });
		});
	}

	return me;
}());

NCI.prepareDataForForceGraph = function(communities){
    var graph = { "nodes":[], "links": []};

	var nodeIndex = function(ip){
		var val = 0;
		$.each(graph.nodes, function(index, node){
			if (node.name == ip)
			   val = index;
		});
		return val;
	};

	$.each(communities, function(index, community){
		$.each(community.Endpoints, function(index2, endpoint){
			graph.nodes.push({
				"name": endpoint,
				"group": index
			});
		});
		$.each(community.Interactions, function(index2, interacton){
			graph.links.push({
				"source": nodeIndex(interacton[1]),
				"target": nodeIndex(interacton[0]),
				"value":1});
		});
	});

	return graph;
};<commit_msg>Order activities from largest to small on histogram<commit_after>NCI.setupCommunities = function(data){
	NCI.Communities = data.Communities;
	NCI.Communities.sort(function(a, b){
		return a.Endpoints.length > b.Endpoints.length;
	});
	NCI.timestampNCI = data.NCI;
	NCI.timestamp = data.Time;
	$("#histogramGeneral").html("NETWORK COMPLEXITY INDEX at    " + NCI.parceDateForLastUpdate(NCI.timestamp) + "" +
	    "   NCI " + NCI.timestampNCI + "" );
};

NCI.nciHistogram = (function(){
	var me = $('#nciHistogram');

	var barWidth = 6;
	var chart = d3.select("#nciHistogram");
	var margin = {top: 40, right: 40, bottom: 40, left:40},
	    width = 600,
	    height = 300;

	me.show = function(){
		chart.text("");
		var endpointsScale = d3.scale.linear()
		    .domain([d3.max(NCI.Communities, function(d) { return d.Endpoints.length; }), 0])
		    .range([0, height - margin.top - margin.bottom]);

		var activitiesScale = d3.scale.linear()
		    .domain([1, NCI.Communities.length + 1])
		    .range([width - margin.right - margin.left, margin.left]);

		var activitiesAxis = d3.svg.axis()
		    .scale(activitiesScale)
		    .orient('bottom')
		    .tickSize(0)
			.tickFormat("")
		    .tickPadding(8);

		var endpointsAxis = d3.svg.axis()
		    .scale(endpointsScale)
		    .orient('left')
			.tickSize(0)
			.tickFormat(d3.format("d"))
		    .tickPadding(10);

		var barChartSvg = chart.append('svg')
		    .attr('width', width)
		    .attr('height', height)
		    .append('g')
		    .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');

        //draw and animate bars
        var index = 1;
		barChartSvg.selectAll('g')
		    .data(NCI.Communities)
		    .enter().append('rect')
		    .attr('x', function(d) { return activitiesScale(index++) - barWidth/2})
		    .attr('y', function(d) { return   endpointsScale(d.Endpoints.length)}) //- selfwidth
		    .attr('width', function(d) { return barWidth})
		    .attr('height',function(d) { return height - margin.top - margin.bottom - endpointsScale(d.Endpoints.length) })
			.on("click", me.showDetails);

		for (var i = 0; i< NCI.Communities.length; i++){
			barChartSvg.append('text')
				.text( NCI.Communities.length - i)
				.attr('dx', activitiesScale(i+1))
				.attr('y', height - 60);
		}

		barChartSvg.append("circle").attr("cy", endpointsScale(NCI.timestampNCI))
		    .attr("cx", activitiesScale(NCI.timestampNCI) ).style("fill", "red").attr("r", 6);
	    barChartSvg.append("circle").attr("cy", endpointsScale(0))
		    .attr("cx", activitiesScale(NCI.timestampNCI) ).style("fill", "red").attr("r", 4);
		barChartSvg.append("circle").attr("cy", endpointsScale(NCI.timestampNCI))
		    .attr("cx", activitiesScale(0) ).style("fill", "red").attr("r", 4);
		//draw axis
		barChartSvg.append('g')
		    .attr('class', 'x axis')
			.attr('transform', 'translate(0,' + (height - margin.top - margin.bottom) + ')')
			.call(activitiesAxis);
		barChartSvg.append('g')
			.attr('class', 'y axis')
			.attr('transform', 'translate(' + margin.left + ')')
			.call(endpointsAxis);
		//draw axis labels
		barChartSvg.append('text').text('Activities Sorted by Size').attr('x', width/2 - 100).attr('y', height - 45);
		barChartSvg.append('text').text('Number of Endpoints per Activity').attr('x', -height/2 - 40).attr('y', 0)
		.attr('transform', 'rotate(-90)');

	};

	me.showDetails = function(d){
		chart.select("#bar_endpoints").remove();
		var activityDetails = chart.append('svg')
			.attr("id","bar_endpoints")
			.attr('width', height)
		    .attr('height', height);

	    var graph = NCI.prepareDataForForceGraph([d]);

		var force = d3.layout.force()
			.charge(-60)
			.linkDistance(30)
			.size([ height, height])
			.linkStrength(1).nodes(graph.nodes).links(graph.links).start();

		var link = activityDetails.selectAll(".link")
		    .data(graph.links)
			.enter().append("line")
			.attr("class", "link")
			.style("stroke-width", function(d) { return Math.sqrt(d.value); });

		var node = activityDetails.selectAll(".node")
		    .data(graph.nodes)
		    .enter().append("circle")
		    .attr("r", 5)
		    .call(force.drag);

		node.append("title").text(function(d) { return d.name; });

		force.on("tick", function() {
			link.attr("x1", function(d) { return d.source.x; })
			    .attr("y1", function(d) { return d.source.y; })
				.attr("x2", function(d) { return d.target.x; })
				.attr("y2", function(d) { return d.target.y; });

			node.attr("cx", function(d) { return d.x; })
			    .attr("cy", function(d) { return d.y; });
		});
	};

	return me;
}());

NCI.socialGraph  = (function(){
	var me = $("#socialGraph");

	var force;
	var color = d3.scale.category10();
	me.clustered = false;
	var networkColor = "#000000";

	me.show = function(devided, clustered, filtered){
		if (me.clustered && !clustered){
			NCI.socialGraph.text("");
			me.draw(devided, clustered);
			me.clustered  = clustered;
			return;
		}
		me.clustered = clustered;
		if (me.text().length<20){
			me.draw(devided, clustered, filtered);
		} else {
			me.node.style("fill", function(d) {
  			    if ( filtered && (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)){
  				    return networkColor;
  			    }
  			    return devided ? color(d.group) : color(0);
			});
			me.node.attr("r", function(d) { return (filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)) ? 7 : 5})
			force.linkStrength(clustered ? 1 : 0);
	  	    force.start();
		}
	};

	me.draw = function(devided, clustered, filtered){
		d3.select("#activities_graph").remove();
	    me.graph = NCI.prepareDataForForceGraph(NCI.Communities);

		force = d3.layout.force()
			.charge(-60)
			.linkDistance(30)
			.size([ me.width(), $('#nciDetails').height() - 150])
			.linkStrength(clustered ? 1 : 0)
			.nodes(me.graph.nodes).links(me.graph.links).start();

	    me.activitiesGraphSvg = d3.select("#socialGraph").append("svg")
		    .attr("id","activities_graph")
			.attr("width", me.width())
			.attr("height", me.height());

		var link = me.activitiesGraphSvg.selectAll(".link")
		    .data(me.graph.links)
			.enter().append("line")
			.attr("class", "link")
			.style("stroke-width", function(d) { return Math.sqrt(d.value); });

		me.node = me.activitiesGraphSvg.selectAll(".node")
		    .data(me.graph.nodes)
			.enter().append("circle")
			.attr("class", "node")
			.attr("r", function(d) { return (filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)) ? 7 : 5})
			.style("fill", function(d) {
				if ( filtered &&  (d.name.search("10.") == 0 ||  d.name.search("192.168") == 0)){
				  return networkColor;
			    }
				return devided ? color(d.group) : color(0);
			})
			.call(force.drag);

		me.node.append("title").text(function(d) { return d.name; });

	    force.on("tick", function() {
	        link.attr("x1", function(d) { return d.source.x; })
	            .attr("y1", function(d) { return d.source.y; })
	            .attr("x2", function(d) { return d.target.x; })
	            .attr("y2", function(d) { return d.target.y; });

	        me.node.attr("cx", function(d) { return d.x; })
	            .attr("cy", function(d) { return d.y; });
		});
	}

	return me;
}());

NCI.prepareDataForForceGraph = function(communities){
    var graph = { "nodes":[], "links": []};

	var nodeIndex = function(ip){
		var val = 0;
		$.each(graph.nodes, function(index, node){
			if (node.name == ip)
			   val = index;
		});
		return val;
	};

	$.each(communities, function(index, community){
		$.each(community.Endpoints, function(index2, endpoint){
			graph.nodes.push({
				"name": endpoint,
				"group": index
			});
		});
		$.each(community.Interactions, function(index2, interacton){
			graph.links.push({
				"source": nodeIndex(interacton[1]),
				"target": nodeIndex(interacton[0]),
				"value":1});
		});
	});

	return graph;
};<|endoftext|>


<commit_before>import { React, observable, observer, action, cn, styled } from 'vendor';
import { partial } from 'lodash';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { Overlay, Popover, Button } from 'react-bootstrap';
import UiSettings from 'shared/model/ui-settings';
import { AddAssignmentLink } from './task-dnd';
import PastAssignments from './past-assignments';
import TourAnchor from '../../components/tours/anchor';
import Course from '../../models/course';
import AddMenu from './add-menu';
import GradingTemplateLink from '../grading-templates/link';
import CalendarHelper from './helper';
import { colors } from 'theme';

const IS_INTRO_VIEWED = 'viewed-plan-dnd-intro';
const USE_SETTINGS = false;

// eslint-disable-next-line
const IntroPopover = ({ show, onClose }) => (
  <Overlay
    show={show}
    placement="right"
    container={document.querySelector('.new-assignments')}>
    <Popover id="drag-intro">
      <Popover.Content>

          Click to add, or just drag to calendar.

        <Button size="small" onClick={onClose}>
          Got it
        </Button>
      </Popover.Content>
    </Popover>
  </Overlay>
);

const StyledSeparator = styled.hr`
  border-color: ${colors.neutral.pale};
  margin: 0.6rem 0.8rem 1.7rem;
`;

export default
@observer
@withRouter
class AddAssignmentSidebar extends React.Component {

  static propTypes = {
    course: PropTypes.instanceOf(Course).isRequired,
    isOpen: PropTypes.bool.isRequired,
    cloningPlanId: PropTypes.string,
    history: PropTypes.object.isRequired,
  }

  @observable shouldshowIntro = false;
  @observable shouldShowPopover = false;
  @observable pendingIntroTimeout;
  @observable willShowIntro = Boolean(
    CalendarHelper.shouldIntro() && !(USE_SETTINGS ? UiSettings.get(IS_INTRO_VIEWED) : false)
  )

  addMenu = new AddMenu({
    history: this.props.history, renderMenuLink: this.renderMenuLink,
    isSidebar: true,
  });

  componentDidUpdate(oldProps) {
    // kickoff intro if we're opening after being closed
    if (this.willShowIntro && !oldProps.isOpen && this.props.isOpen) {
      this.pendingIntroTimeout = CalendarHelper.scheduleIntroEvent(this.showIntro);
    }
  }

  componentWillUnmount() {
    CalendarHelper.clearScheduledEvent(this.pendingIntroTimeout);
  }

  @action.bound showIntro() {
    this.shouldShowIntro = true;
    this.willShowIntro = false;
    this.pendingIntroTimeout = CalendarHelper.scheduleIntroEvent(this.showPopover);
  }

  @action.bound showPopover() {
    this.shouldShowPopover = true;
    this.pendingIntroTimeout = false;
  }

  @action.bound onPopoverClose() {
    if (USE_SETTINGS) { UiSettings.set(IS_INTRO_VIEWED, true); }
    this.shouldShowPopover = false;
    this.shouldShowIntro = false;
  }

  @action.bound renderMenuLink(link, goToBuilder) {
    return (
      <TourAnchor tag="li" key={link.type} id={`sidebar-add-${link.type}-assignment`}>
        <AddAssignmentLink link={link} goToBuilder={partial(goToBuilder, link.pathname, null)} onDrag={this.onPopoverClose} />
      </TourAnchor>
    );
  }

  render() {
    return (

        <TourAnchor id="sidebar-add-tasks" className="sidebar-section">

          <GradingTemplateLink course={this.props.course} />


<commit_before>var $ = require('jquery');

require('jquery-ui/ui/core');
require('jquery-ui/ui/datepicker');

require('timepicker');

var DateTimeWidgets = function() { };

DateTimeWidgets.prototype = {
    removeInputTextNode: function($input) {
        if ($input.length == 0) {
            return;
        }

        var node = $input.get(0).previousSibling;

        if (node.nodeType == 3) {
            $(node).remove();
        }
    },
    updateDatetimeLayout: function() {
        var self = this;

        $('.form-row .datetime').each(function () {
            var $dateTime = $(this);
            var $dateField = $dateTime.find('.vDateField');
            var $timeField = $dateTime.find('.vTimeField');

            self.removeInputTextNode($dateField);
            self.removeInputTextNode($timeField);

            $dateField.nextAll('br').first().remove();
        });
    },
    djangoDateTimeFormatToJs: function(format) {
        return format.toLowerCase().replace(/%\w/g, function(format) {
            format = format.replace(/%/,"");
            return format + format;
        });
    },
    initDateWidgets: function() {
        var self = this;

        $('.form-row .vDateField').each(function () {
            var $dateField = $(this);
            var $dateButton = $('<span>').addClass('icon-calendar');
            var $dateLink = $('<a>')
                .attr('href', '#')
                .addClass('vDateField-link')
                .append($dateButton)
                .insertAfter($dateField);

            $dateField.datepicker({
                dateFormat: self.djangoDateTimeFormatToJs(DATE_FORMAT),
                showButtonPanel: true,
                nextText: '',
                prevText: ''
            });

            $dateLink.on('click', function (e) {
                if ($dateField.datepicker('widget').is(':visible')) {
                    $dateField.datepicker('hide');
                } else {
                    $dateField.datepicker('show');
                }

                e.preventDefault();
            });
        });

        var old_goToToday = $.datepicker._gotoToday;
        $.datepicker._gotoToday = function(id) {
            old_goToToday.call(this,id);
            this._selectDate(id);
        };
    },
    initTimeWidgets: function() {
        $('.form-row .vTimeField').each(function () {
            var $timeField = $(this);
            var $timeButton = $('<span>').addClass('icon-clock');
            var $timeLink = $('<a>')
                .attr('href', '#')
                .addClass('vTimeField-link')
                .append($timeButton)
                .insertAfter($timeField);

            $timeField.timepicker({
                showPeriodLabels: false,
                showCloseButton: true,
                showNowButton: true
            });

            $timeLink.on('click', function (e) {
                if ($timeField.datepicker('widget').is(':visible')) {
                    $timeField.datepicker('hide');
                } else {
                    $timeField.timepicker('show');
                }

                e.preventDefault();
            });
        });
    },
    run: function() {
        try {
            this.updateDatetimeLayout();
            this.initDateWidgets();
            this.initTimeWidgets();
        } catch (e) {
            console.error(e, e.stack);
        }
    }
};

$(document).ready(function() {
    new DateTimeWidgets().run();
});
<commit_msg>Fix datetime init after inline row added<commit_after>var $ = require('jquery');

require('jquery-ui/ui/core');
require('jquery-ui/ui/datepicker');

require('timepicker');

var DateTimeWidgets = function() { };

DateTimeWidgets.prototype = {
    removeInputTextNode: function($input) {
        if ($input.length == 0) {
            return;
        }

        var node = $input.get(0).previousSibling;

        if (node.nodeType == 3) {
            $(node).remove();
        }
    },
    updateDatetimeLayout: function() {
        var self = this;

        $('.form-row .datetime').each(function () {
            var $dateTime = $(this);
            var $dateField = $dateTime.find('.vDateField');
            var $timeField = $dateTime.find('.vTimeField');

            self.removeInputTextNode($dateField);
            self.removeInputTextNode($timeField);

            $dateField.nextAll('br').first().remove();
        });

        $('.form-row .vDateField').each(function () {
            var $dateField = $(this);
            var $dateButton = $('<span>').addClass('icon-calendar');
            $('<a>')
                .attr('href', '#')
                .addClass('vDateField-link')
                .append($dateButton)
                .insertAfter($dateField);
        });

        $('.form-row .vTimeField').each(function () {
            var $timeField = $(this);
            var $timeButton = $('<span>').addClass('icon-clock');
            $('<a>')
                .attr('href', '#')
                .addClass('vTimeField-link')
                .append($timeButton)
                .insertAfter($timeField);
        });
    },
    djangoDateTimeFormatToJs: function(format) {
        return format.toLowerCase().replace(/%\w/g, function(format) {
            format = format.replace(/%/,"");
            return format + format;
        });
    },
    initDateWidgets: function($container) {
        $container = $container || $(document);

        var self = this;

        $container.find('.form-row .vDateField').each(function () {
            var $dateField = $(this);
            var $dateLink = $dateField.next('.vDateField-link');

            $dateField.datepicker({
                dateFormat: self.djangoDateTimeFormatToJs(DATE_FORMAT),
                showButtonPanel: true,
                nextText: '',
                prevText: ''
            });

            $dateLink.on('click', function (e) {
                if ($dateField.datepicker('widget').is(':visible')) {
                    $dateField.datepicker('hide');
                } else {
                    $dateField.datepicker('show');
                }

                e.preventDefault();
            });
        });

        var old_goToToday = $.datepicker._gotoToday;
        $.datepicker._gotoToday = function(id) {
            old_goToToday.call(this,id);
            this._selectDate(id);
        };
    },
    initTimeWidgets: function($container) {
        $container = $container || $(document);

        $container.find('.form-row .vTimeField').each(function () {
            var $timeField = $(this);
            var $timeLink = $timeField.next('.vTimeField-link');

            $timeField.timepicker({
                showPeriodLabels: false,
                showCloseButton: true,
                showNowButton: true
            });

            $timeLink.on('click', function (e) {
                if ($timeField.datepicker('widget').is(':visible')) {
                    $timeField.datepicker('hide');
                } else {
                    $timeField.timepicker('show');
                }

                e.preventDefault();
            });
        });
    },
    run: function() {
        try {
            this.updateDatetimeLayout();
            this.initDateWidgets();
            this.initTimeWidgets();

            var self = this;

            $('.inline-group').on('inline-group-row:added', function(e, $inlineItem) {
                $inlineItem.find('.hasDatepicker').removeClass('hasDatepicker');
                $inlineItem.find('.hasTimepicker').removeClass('hasTimepicker');
                self.initDateWidgets($inlineItem);
                self.initTimeWidgets($inlineItem);
            });
        } catch (e) {
            console.error(e, e.stack);
        }
    }
};

$(document).ready(function() {
    new DateTimeWidgets().run();
});
<|endoftext|>


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

// jshint esversion: 6
// jshint strict: global

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import path from 'path';
import gulp from 'gulp';
import del from 'del';
import {Server} from 'karma';
import runSequence from 'run-sequence';
import browserify from 'browserify';
import babelify from 'babelify';
import source from 'vinyl-source-stream';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const bs = browserSync.create();
const babelOptions = {
	presets: ['es2015']
};

// Lint JavaScript
gulp.task('lint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.if(!bs.active, $.eslint.failOnError()))
);

// Optimize images
gulp.task('images', () => {
  gulp.src('app/images/**/*')
    .pipe($.imagemin({ // DEBUG removed $.cache( before imagemin
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}));

		gulp.src('../third_party/images/**/*')
			.pipe($.imagemin({ // DEBUG removed $.cache( before imagemin
				progressive: true,
				interlaced: true
			}))
			.pipe(gulp.dest('dist/images'))
			.pipe($.size({title: 'product images'}))
}

);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.cssnano()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('./'))
    .pipe(gulp.dest('dist/styles'));
});

gulp.task('scripts', () => {
  return browserify([
    './app/scripts/main.js'
  ], { debug: true, paths: ['app/scripts/modules/'] })
    .transform(babelify, { presets: ['es2015']  })
    .bundle()
    .pipe(source('main.min.js'))
    .on('error', err => { console.log('ERROR:', err.message); })
    .pipe(gulp.dest('dist/scripts/'));
});

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  return gulp.src('app/**/*.html')
    .pipe($.useref({
      searchPath: '{.tmp,app}',
      noAssets: true
    }))

    // Minify any HTML
    .pipe($.if('*.html', $.htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      collapseBooleanAttributes: true,
      removeAttributeQuotes: true,
      removeRedundantAttributes: true,
      removeEmptyAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeOptionalTags: true
    })))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', () => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true}));

// Run unit tests
gulp.task('test', (done) => {
  new Server({
    configFile: __dirname + '/test-all.conf.js',
    singleRun: true
  }, done).start();
});

// Build and serve the output from the dist build
gulp.task('serve', ['default'], () =>
  bs.init({
    notify: false,
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist',
    port: 3001,
		// Implement a simple server running at /checkout.
		// This logs the form data and returns a dummy payment response.
		middleware: [(req, res, next) => {
			const url = require('url');
			const parseBody = require('body/form');
    	let urlObj = url.parse(req.url, true),
        method = req.method;
		  if (method !== 'POST' || urlObj.path !== '/checkout' ) {
				next();
			} else {
			  parseBody(req, (err, text) => {
					if (err) {
						res.status = 500;
						res.end('Failed with error ' + err);
					} else {
						console.log(text);
						res.writeHead(200, {'Content-Type': 'application/json'});
						// A successful payment request always returns a provider-specific
						// JSON object. We're looknig for "success" in the code later.
						res.end('{"success": true, "id": 12345}');
					}
				});
			}
    }]
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['html', 'scripts', 'images', 'copy'],
    // ['lint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('example.com', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// Copy over the scripts that are used in importScripts as part of the generate-service-worker task.
gulp.task('copy-sw-scripts', () => {
  return gulp.src(['node_modules/sw-toolbox/sw-toolbox.js', 'app/scripts/sw/runtime-caching.js'])
    .pipe(gulp.dest('dist/scripts/sw'));
});

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', ['copy-sw-scripts'], () => {
  const rootDir = 'dist';
  const filepath = path.join(rootDir, 'service-worker.js');

  return swPrecache.write(filepath, {
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'web-starter-kit',
    // sw-toolbox.js needs to be listed first. It sets up methods used in runtime-caching.js.
    importScripts: [
      'scripts/sw/sw-toolbox.js',
      'scripts/sw/runtime-caching.js'
    ],
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    // This is '/' rather than path.sep because the paths returned from
    // glob always use '/'.
    stripPrefix: rootDir + '/'
  });
});

// Load custom tasks from the `tasks` directory
// Run: `npm install --save-dev require-dir` from the command-line
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<commit_msg>Use app name for cache<commit_after>/**
 *
 *  Web Starter Kit
 *  Copyright 2015 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

// jshint esversion: 6
// jshint strict: global

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import path from 'path';
import gulp from 'gulp';
import del from 'del';
import {Server} from 'karma';
import runSequence from 'run-sequence';
import browserify from 'browserify';
import babelify from 'babelify';
import source from 'vinyl-source-stream';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const bs = browserSync.create();
const babelOptions = {
	presets: ['es2015']
};

// Lint JavaScript
gulp.task('lint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.eslint())
    .pipe($.eslint.format())
    .pipe($.if(!bs.active, $.eslint.failOnError()))
);

// Optimize images
gulp.task('images', () => {
  gulp.src('app/images/**/*')
    .pipe($.imagemin({ // DEBUG removed $.cache( before imagemin
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}));

		gulp.src('../third_party/images/**/*')
			.pipe($.imagemin({ // DEBUG removed $.cache( before imagemin
				progressive: true,
				interlaced: true
			}))
			.pipe(gulp.dest('dist/images'))
			.pipe($.size({title: 'product images'}))
}

);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.cssnano()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('./'))
    .pipe(gulp.dest('dist/styles'));
});

gulp.task('scripts', () => {
  return browserify([
    './app/scripts/main.js'
  ], { debug: true, paths: ['app/scripts/modules/'] })
    .transform(babelify, { presets: ['es2015']  })
    .bundle()
    .pipe(source('main.min.js'))
    .on('error', err => { console.log('ERROR:', err.message); })
    .pipe(gulp.dest('dist/scripts/'));
});

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  return gulp.src('app/**/*.html')
    .pipe($.useref({
      searchPath: '{.tmp,app}',
      noAssets: true
    }))

    // Minify any HTML
    .pipe($.if('*.html', $.htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      collapseBooleanAttributes: true,
      removeAttributeQuotes: true,
      removeRedundantAttributes: true,
      removeEmptyAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      removeOptionalTags: true
    })))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', () => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true}));

// Run unit tests
gulp.task('test', (done) => {
  new Server({
    configFile: __dirname + '/test-all.conf.js',
    singleRun: true
  }, done).start();
});

// Build and serve the output from the dist build
gulp.task('serve', ['default'], () =>
  bs.init({
    notify: false,
    logPrefix: 'WSK',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist',
    port: 3001,
		// Implement a simple server running at /checkout.
		// This logs the form data and returns a dummy payment response.
		middleware: [(req, res, next) => {
			const url = require('url');
			const parseBody = require('body/form');
    	let urlObj = url.parse(req.url, true),
        method = req.method;
		  if (method !== 'POST' || urlObj.path !== '/checkout' ) {
				next();
			} else {
			  parseBody(req, (err, text) => {
					if (err) {
						res.status = 500;
						res.end('Failed with error ' + err);
					} else {
						console.log(text);
						res.writeHead(200, {'Content-Type': 'application/json'});
						// A successful payment request always returns a provider-specific
						// JSON object. We're looknig for "success" in the code later.
						res.end('{"success": true, "id": 12345}');
					}
				});
			}
    }]
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['html', 'scripts', 'images', 'copy'],
    // ['lint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('example.com', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// Copy over the scripts that are used in importScripts as part of the generate-service-worker task.
gulp.task('copy-sw-scripts', () => {
  return gulp.src(['node_modules/sw-toolbox/sw-toolbox.js', 'app/scripts/sw/runtime-caching.js'])
    .pipe(gulp.dest('dist/scripts/sw'));
});

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', ['copy-sw-scripts'], () => {
  const rootDir = 'dist';
  const filepath = path.join(rootDir, 'service-worker.js');

  return swPrecache.write(filepath, {
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'modern-furniture-store',
    // sw-toolbox.js needs to be listed first. It sets up methods used in runtime-caching.js.
    importScripts: [
      'scripts/sw/sw-toolbox.js',
      'scripts/sw/runtime-caching.js'
    ],
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    // This is '/' rather than path.sep because the paths returned from
    // glob always use '/'.
    stripPrefix: rootDir + '/'
  });
});

// Load custom tasks from the `tasks` directory
// Run: `npm install --save-dev require-dir` from the command-line
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<|endoftext|>


<commit_before>// vi: sw=2 sts=2 et

const Lang = imports.lang;
const Signals = imports.signals;
const Soup = imports.gi.Soup;
const Mainloop = imports.mainloop;

const Local = imports.misc.extensionUtils.getCurrentExtension();
const Accounting = Local.imports.accounting.accounting;
const CurrencyMap = Local.imports.CurrencyMap.CurrencyMap;

/*
 * Init HTTP
 *
 */

const _version = "0.2.0";
const _repository = "http://github.com/OttoAllmendinger/" +
                    "gnome-shell-bitcoin-markets";

const _userAgent =  "gnome-shell-bitcoin-markets/" + _version +
                    " (" + _repository + ")";

const _httpSession = new Soup.SessionAsync();

_httpSession['user-agent'] = _userAgent;

Soup.Session.prototype.add_feature.call(
  _httpSession,
  new Soup.ProxyResolverDefault()
);

const getJSON = function (url, callback) {
  // log('getJSON ' + url);
  _httpSession.queue_message(
    Soup.Message.new("GET", url),
    function (session, message) {
      if (message.status_code == 200) {
        callback(null, JSON.parse(message.response_body.data));
      } else {
        log('getJSON error url: ' + url);
        log('getJSON error status code: ' + message.status_code);
        log('getJSON error response: ' + message.response_body.data);
        callback(message.status_code, null);
      }
    }
  );
};

const Selector = function (path) {
  /**
   * returns a function that returns a nested attribute
   * path format: a.b.c.d
   */
  return function (obj) {
    return path.split('.').reduce(function (obj, key) {
      if (obj[key]) {
        return obj[key];
      } else {
        throw new Error('invalid path: ' + path);
      }
    }, obj);
  };
};

const IndicatorChange = {
  up: "up",
  down: "down",
  unchanged: "unchanged"
};

const ChangeRenderer = function (getValue)  {
  /**
   * Returns a function that returns the change
   * in value between consecutive calls.
   */
  let lastValue;

  return function (data) {
    let ret = IndicatorChange.unchanged;
    let newValue = getValue(data);

    if (lastValue !== undefined) {
      if (lastValue > newValue) {
        ret = IndicatorChange.down;
      } else if (lastValue < newValue) {
        ret = IndicatorChange.up;
      }
    }

    lastValue = newValue;

    return ret;
  }
};

const CurrencyRenderer = function ({currency}) {
  const getFormat = function (currency) {
    /* determined after mtgox api */
    const front = "%s%v";
    const back = "%v %s";
    const frontFormats = {
      USD: front, CAD: front, AUD: front, GBP: front,
      HKD: front, NZD: front, SGD: front, THB: front
    };

    return frontFormats[currency] || back;
  }

  let format = getFormat(currency);
  let symbol = currency;
  let precision = 2;

  let info = CurrencyMap[currency];

  if (info) {
    symbol = info.symbol_native;

    /* disambiguate dollar currencies */
    if (symbol === '$') symbol = info.symbol;

    precision = info.decimal_digits;
  }

  return function (number)
    Accounting.formatMoney(number, {
      symbol: symbol,
      format: format,
      precision: precision
    });
}

/**
 * Instances of this class emit update signals periodically with formatted
 * data from api sources
 */

const IndicatorModel = new Lang.Class({
  Name: "IndicatorModel",

  _init: function (options, handler, formatter) {
    this._formatter = formatter;
    this._handler = handler;

    let onUpdate = Lang.bind(this, function (error, data) {
      if (error) {
        this.emit("update", error, null);
      } else {
        this.emit("update", null, {
          text: formatter.text(data, options),
          change: formatter.change(data, options)
        });
      }
    });

    this._signalUpdateStart = handler.connect(
        "update-start", Lang.bind(this, function () {
          this.emit("update-start");
        }));

    this._signalUpdate = handler.connect(
        "update", function (obj, error, data) {
          onUpdate(error, data);
        });

    if (handler._lastError || handler._lastData) {
      Mainloop.idle_add(function () {
        onUpdate(handler._lastError, handler._lastData);
      });
    }
  },

  destroy: function () {
    this.disconnectAll();
    this._handler.disconnect(this._signalUpdateStart);
    this._handler.disconnect(this._signalUpdate);
  }
});

Signals.addSignalMethods(IndicatorModel.prototype);

/**
 * We want to return a handler for each data source. The handler
 * polls the API in periodic intervals and calls onUpdate and
 * onUpdateStart methods.
 *
 * Some URLs respond with data for many different indicators, others
 * have different URLs for each poll request. Thus we request a handler
 * id for each set of query options
 */
const Handler = new Lang.Class({
  Name: "Handler",

  _init: function (id, options, poll, interval) {
    if ((!interval) || (interval < 1)) {
      throw new Error('invalid interval ' + interval);
    };

    this._id = id;

    let loop = Lang.bind(this, function() {
      this.emit("update-start");

      poll(options, Lang.bind(this, function (error, data) {
        this._lastError = error;
        this._lastData = data;
        this.emit("update", error, data);
      }));

      this._signalTimeout = Mainloop.timeout_add_seconds(
        interval, loop
      );
    });

    Mainloop.idle_add(loop);
  },

  destroy: function () {
    if (this._signalTimeout) {
      Mainloop.source_remove(this._signalTimeout);
    }

    this.disconnectAll();
  }
});

Signals.addSignalMethods(Handler.prototype);

/**
 * Api definitions
 */

const BaseApi = new Lang.Class({
  Name: "BaseApi",

  _init: function () {
    this._urlHandlers = {};
    this._update = undefined;
  },

  poll: function (options, callback) {
    let url = this.getUrl(options);
    getJSON(url, callback);
  },

  _getHandlerId: function (options) {
    /**
     * default case: each poll URL gets a separate handler. This doesn't
     * work so well if the url needs to be dynamic (MtGox nonce)
     */
    return this.getUrl(options);
  },

  getHandler: function (options) {

    let id = this._getHandlerId(options);
    let handler = this._urlHandlers[id];

    if (handler === undefined) {
      handler = this._urlHandlers[id] =
        new Handler(
          id, options, Lang.bind(this, this.poll), this.interval
        );
    }

    return handler;
  },

  getFormatter: function (options) {
    if (this.attributes[options.attribute]) {
      return this.attributes[options.attribute](options);
    } else {
      throw new Error("unknown attribute: " + options.attribute);
    }
  },

  getIndicatorModel: function (options) {
    return new IndicatorModel(
      options,
      this.getHandler(options),
      this.getFormatter(options)
    );
  },

  destroy: function () {
    for each (let handler in this._urlHandlers) {
      handler.destroy();
    };
  }
});

const MtGoxApi = new Lang.Class({
  Name: "MtGoxApi",
  Extends: BaseApi,

  apiName: "MtGox",

  interval: 30,

  currencies: [
    "USD", "EUR", "GBP", "AUD", "CAD", "CHF", "CNY", "DKK",
    "HKD", "NZD", "PLN", "RUB", "SGD", "THB", "NOK", "CZK"
  ],

  attributes: {
    last_local: function (options) {
      return {
        text: new Selector("data.last_local.display"),
        change: new ChangeRenderer(
          new Selector('data.last_local.value_int')
        )
      };
    }
  },

  _getHandlerId: function (options) {
    return "mtgox://" + options.currency;
  },

  getUrl: function (options) {
    return "http://data.mtgox.com/"
      + "api/2/BTC" + (options.currency)
      + "/money/ticker";
  },
});

const BitstampApi = new Lang.Class({
  Name: 'BitstampApi',
  Extends: BaseApi,

  // Quote 2013-08-09  ---  https://www.bitstamp.net/api/
  // `` Do not make more than 600 request per 10 minutes or we will ban your
  //  IP address. ''
  apiName: "Bitstamp",

  currencies: ['USD'],

  interval: 10, // 60 requests per 10 minutes

  attributes: {
    last: function (options) {
      let currencyRenderer = new CurrencyRenderer(options);

      return {
        text: function (data) currencyRenderer(data.last),
        change: new ChangeRenderer(new Selector("last"))
      }
    }
  },

  getUrl: function (options) {
    return "https://www.bitstamp.net/api/ticker/";
  }
});

const BitcoinAverageApi = new Lang.Class({
  Name: 'BitcoinAverageApi',
  Extends: BaseApi,

  apiName: "BitcoinAverage",

  currencies: [ 'USD', 'EUR', 'GBP', 'CAD', 'RUB', 'AUD', 'BRL',
                'CNY', 'CZK', 'JPY', 'NZD', 'SEK', 'SGD', 'PLN'],

  interval: 10, // 60 requests per 10 minutes

  attributes: {
    last: function (options) {
      let currencyRenderer = new CurrencyRenderer(options);

      return {
        text: function (data) currencyRenderer(data.last),
        change: new ChangeRenderer(new Selector("last"))
      }
    }
  },

  getUrl: function (options) (
    "http://api.bitcoinaverage.com/ticker/" + options.currency
  )
});

const BitcoinChartsApi = new Lang.Class({
  Name: "BitcoinChartsApi",

  Extends: BaseApi,

  apiName: "BitcoinCharts",

  interval: 15 * 60 * 1000,

  currencies: ["USD", "CHF"],

  getUrl: function (options) {
    return "http://bitcoincharts.com/t/markets.json";
  }
});

const ApiProvider = new Lang.Class({
  Name: "ApiProvider",

  apis: {
    mtgox: new MtGoxApi(),
    bitstamp: new BitstampApi(),
    bitcoinaverage: new BitcoinAverageApi()
    // btcharts: new BitcoinChartsApi()
  },

  get: function (name, options) {
    let api;

    if ((api = this.apis[name]) === undefined) {
      throw new Error('unknown api ' + name);
    } else {
      return api.getIndicatorModel(options);
    }
  },

  destroy: function () {
    for each (let api in this.apis) {
      api.destroy();
    }
  }
});

if (this['ARGV'] !== undefined) {
  // run by gjs
  log("command line");

  let apiProvider = new ApiProvider();

  let options = {currency: "USD", attribute: "last"};

  let indicator = apiProvider.get('bitstamp', options);

  indicator.connect("update-start", function () {
    log("signal update-start");
  });

  indicator.connect("update", function (obj, error, data) {
    log("signal update");
    log("error: " + JSON.stringify(error));
    log("data: " + JSON.stringify(data));
    // apiProvider.destroy();
  });

  Mainloop.run("main");
}
<commit_msg>fix BitcoinAverage.com poll interval<commit_after>// vi: sw=2 sts=2 et

const Lang = imports.lang;
const Signals = imports.signals;
const Soup = imports.gi.Soup;
const Mainloop = imports.mainloop;

const Local = imports.misc.extensionUtils.getCurrentExtension();
const Accounting = Local.imports.accounting.accounting;
const CurrencyMap = Local.imports.CurrencyMap.CurrencyMap;

/*
 * Init HTTP
 *
 */

const _version = "0.2.0";
const _repository = "http://github.com/OttoAllmendinger/" +
                    "gnome-shell-bitcoin-markets";

const _userAgent =  "gnome-shell-bitcoin-markets/" + _version +
                    " (" + _repository + ")";

const _httpSession = new Soup.SessionAsync();

_httpSession['user-agent'] = _userAgent;

Soup.Session.prototype.add_feature.call(
  _httpSession,
  new Soup.ProxyResolverDefault()
);

const getJSON = function (url, callback) {
  // log('getJSON ' + url);
  _httpSession.queue_message(
    Soup.Message.new("GET", url),
    function (session, message) {
      if (message.status_code == 200) {
        callback(null, JSON.parse(message.response_body.data));
      } else {
        log('getJSON error url: ' + url);
        log('getJSON error status code: ' + message.status_code);
        log('getJSON error response: ' + message.response_body.data);
        callback(message.status_code, null);
      }
    }
  );
};

const Selector = function (path) {
  /**
   * returns a function that returns a nested attribute
   * path format: a.b.c.d
   */
  return function (obj) {
    return path.split('.').reduce(function (obj, key) {
      if (obj[key]) {
        return obj[key];
      } else {
        throw new Error('invalid path: ' + path);
      }
    }, obj);
  };
};

const IndicatorChange = {
  up: "up",
  down: "down",
  unchanged: "unchanged"
};

const ChangeRenderer = function (getValue)  {
  /**
   * Returns a function that returns the change
   * in value between consecutive calls.
   */
  let lastValue;

  return function (data) {
    let ret = IndicatorChange.unchanged;
    let newValue = getValue(data);

    if (lastValue !== undefined) {
      if (lastValue > newValue) {
        ret = IndicatorChange.down;
      } else if (lastValue < newValue) {
        ret = IndicatorChange.up;
      }
    }

    lastValue = newValue;

    return ret;
  }
};

const CurrencyRenderer = function ({currency}) {
  const getFormat = function (currency) {
    /* determined after mtgox api */
    const front = "%s%v";
    const back = "%v %s";
    const frontFormats = {
      USD: front, CAD: front, AUD: front, GBP: front,
      HKD: front, NZD: front, SGD: front, THB: front
    };

    return frontFormats[currency] || back;
  }

  let format = getFormat(currency);
  let symbol = currency;
  let precision = 2;

  let info = CurrencyMap[currency];

  if (info) {
    symbol = info.symbol_native;

    /* disambiguate dollar currencies */
    if (symbol === '$') symbol = info.symbol;

    precision = info.decimal_digits;
  }

  return function (number)
    Accounting.formatMoney(number, {
      symbol: symbol,
      format: format,
      precision: precision
    });
}

/**
 * Instances of this class emit update signals periodically with formatted
 * data from api sources
 */

const IndicatorModel = new Lang.Class({
  Name: "IndicatorModel",

  _init: function (options, handler, formatter) {
    this._formatter = formatter;
    this._handler = handler;

    let onUpdate = Lang.bind(this, function (error, data) {
      if (error) {
        this.emit("update", error, null);
      } else {
        this.emit("update", null, {
          text: formatter.text(data, options),
          change: formatter.change(data, options)
        });
      }
    });

    this._signalUpdateStart = handler.connect(
        "update-start", Lang.bind(this, function () {
          this.emit("update-start");
        }));

    this._signalUpdate = handler.connect(
        "update", function (obj, error, data) {
          onUpdate(error, data);
        });

    if (handler._lastError || handler._lastData) {
      Mainloop.idle_add(function () {
        onUpdate(handler._lastError, handler._lastData);
      });
    }
  },

  destroy: function () {
    this.disconnectAll();
    this._handler.disconnect(this._signalUpdateStart);
    this._handler.disconnect(this._signalUpdate);
  }
});

Signals.addSignalMethods(IndicatorModel.prototype);

/**
 * We want to return a handler for each data source. The handler
 * polls the API in periodic intervals and calls onUpdate and
 * onUpdateStart methods.
 *
 * Some URLs respond with data for many different indicators, others
 * have different URLs for each poll request. Thus we request a handler
 * id for each set of query options
 */
const Handler = new Lang.Class({
  Name: "Handler",

  _init: function (id, options, poll, interval) {
    if ((!interval) || (interval < 1)) {
      throw new Error('invalid interval ' + interval);
    };

    this._id = id;

    let loop = Lang.bind(this, function() {
      this.emit("update-start");

      poll(options, Lang.bind(this, function (error, data) {
        this._lastError = error;
        this._lastData = data;
        this.emit("update", error, data);
      }));

      this._signalTimeout = Mainloop.timeout_add_seconds(
        interval, loop
      );
    });

    Mainloop.idle_add(loop);
  },

  destroy: function () {
    if (this._signalTimeout) {
      Mainloop.source_remove(this._signalTimeout);
    }

    this.disconnectAll();
  }
});

Signals.addSignalMethods(Handler.prototype);

/**
 * Api definitions
 */

const BaseApi = new Lang.Class({
  Name: "BaseApi",

  _init: function () {
    this._urlHandlers = {};
    this._update = undefined;
  },

  poll: function (options, callback) {
    let url = this.getUrl(options);
    getJSON(url, callback);
  },

  _getHandlerId: function (options) {
    /**
     * default case: each poll URL gets a separate handler. This doesn't
     * work so well if the url needs to be dynamic (MtGox nonce)
     */
    return this.getUrl(options);
  },

  getHandler: function (options) {

    let id = this._getHandlerId(options);
    let handler = this._urlHandlers[id];

    if (handler === undefined) {
      handler = this._urlHandlers[id] =
        new Handler(
          id, options, Lang.bind(this, this.poll), this.interval
        );
    }

    return handler;
  },

  getFormatter: function (options) {
    if (this.attributes[options.attribute]) {
      return this.attributes[options.attribute](options);
    } else {
      throw new Error("unknown attribute: " + options.attribute);
    }
  },

  getIndicatorModel: function (options) {
    return new IndicatorModel(
      options,
      this.getHandler(options),
      this.getFormatter(options)
    );
  },

  destroy: function () {
    for each (let handler in this._urlHandlers) {
      handler.destroy();
    };
  }
});

const MtGoxApi = new Lang.Class({
  Name: "MtGoxApi",
  Extends: BaseApi,

  apiName: "MtGox",

  interval: 30,

  currencies: [
    "USD", "EUR", "GBP", "AUD", "CAD", "CHF", "CNY", "DKK",
    "HKD", "NZD", "PLN", "RUB", "SGD", "THB", "NOK", "CZK"
  ],

  attributes: {
    last_local: function (options) {
      return {
        text: new Selector("data.last_local.display"),
        change: new ChangeRenderer(
          new Selector('data.last_local.value_int')
        )
      };
    }
  },

  _getHandlerId: function (options) {
    return "mtgox://" + options.currency;
  },

  getUrl: function (options) {
    return "http://data.mtgox.com/"
      + "api/2/BTC" + (options.currency)
      + "/money/ticker";
  },
});

const BitstampApi = new Lang.Class({
  Name: 'BitstampApi',
  Extends: BaseApi,

  // Quote 2013-08-09  ---  https://www.bitstamp.net/api/
  // `` Do not make more than 600 request per 10 minutes or we will ban your
  //  IP address. ''
  apiName: "Bitstamp",

  currencies: ['USD'],

  interval: 10, // 60 requests per 10 minutes

  attributes: {
    last: function (options) {
      let currencyRenderer = new CurrencyRenderer(options);

      return {
        text: function (data) currencyRenderer(data.last),
        change: new ChangeRenderer(new Selector("last"))
      }
    }
  },

  getUrl: function (options) {
    return "https://www.bitstamp.net/api/ticker/";
  }
});

const BitcoinAverageApi = new Lang.Class({
  Name: 'BitcoinAverageApi',
  Extends: BaseApi,

  apiName: "BitcoinAverage",

  currencies: [ 'USD', 'EUR', 'GBP', 'CAD', 'RUB', 'AUD', 'BRL',
                'CNY', 'CZK', 'JPY', 'NZD', 'SEK', 'SGD', 'PLN'],

  /* quote https://bitcoinaverage.com/api.htm
   *
   * > API is updated along with the site, normally around every minute. There
   * > is no explicit restriction about how often you can call the API, yet
   * > calling it more often than once a minute makes no sense. Please be good.
   */
  interval: 60,

  attributes: {
    last: function (options) {
      let currencyRenderer = new CurrencyRenderer(options);

      return {
        text: function (data) currencyRenderer(data.last),
        change: new ChangeRenderer(new Selector("last"))
      }
    }
  },

  getUrl: function (options) (
    "http://api.bitcoinaverage.com/ticker/" + options.currency
  )
});

const BitcoinChartsApi = new Lang.Class({
  Name: "BitcoinChartsApi",

  Extends: BaseApi,

  apiName: "BitcoinCharts",

  interval: 15 * 60 * 1000,

  currencies: ["USD", "CHF"],

  getUrl: function (options) {
    return "http://bitcoincharts.com/t/markets.json";
  }
});

const ApiProvider = new Lang.Class({
  Name: "ApiProvider",

  apis: {
    mtgox: new MtGoxApi(),
    bitstamp: new BitstampApi(),
    bitcoinaverage: new BitcoinAverageApi()
    // btcharts: new BitcoinChartsApi()
  },

  get: function (name, options) {
    let api;

    if ((api = this.apis[name]) === undefined) {
      throw new Error('unknown api ' + name);
    } else {
      return api.getIndicatorModel(options);
    }
  },

  destroy: function () {
    for each (let api in this.apis) {
      api.destroy();
    }
  }
});

if (this['ARGV'] !== undefined) {
  // run by gjs
  log("command line");

  let apiProvider = new ApiProvider();

  let options = {currency: "USD", attribute: "last"};

  let indicator = apiProvider.get('bitstamp', options);

  indicator.connect("update-start", function () {
    log("signal update-start");
  });

  indicator.connect("update", function (obj, error, data) {
    log("signal update");
    log("error: " + JSON.stringify(error));
    log("data: " + JSON.stringify(data));
    // apiProvider.destroy();
  });

  Mainloop.run("main");
}
<|endoftext|>


<commit_before>import React from 'react';

import CoreChecklist, { Rule, Requirement, GenericCastEfficiencyRequirement } from 'Parser/Core/Modules/Features/Checklist';
import Combatants from 'Parser/Core/Modules/Combatants';
import LegendaryCountChecker from 'Parser/Core/Modules/Items/LegendaryCountChecker';
import LegendaryUpgradeChecker from 'Parser/Core/Modules/Items/LegendaryUpgradeChecker';
import PrePotion from 'Parser/Core/Modules/Items/PrePotion';
import Wrapper from 'common/Wrapper';
import SPELLS from 'common/SPELLS';
import CastEfficiency from 'Parser/Core/Modules/CastEfficiency';
import AlwaysBeCasting from 'Parser/Hunter/BeastMastery/Modules/Features/AlwaysBeCasting';
import AMurderOfCrows from 'Parser/Hunter/BeastMastery/Modules/Talents/AMurderOfCrows';
import SpellLink from 'common/SpellLink';
import Icon from "common/Icon";
import EnchantChecker from 'Parser/Core/Modules/Items/EnchantChecker';
import DireBeast from 'Parser/Hunter/BeastMastery/Modules/Spells/DireBeast/DireBeast';
import BestialWrathAverageFocus from 'Parser/Hunter/BeastMastery/Modules/Spells/BestialWrath/BestialWrathAverageFocus';
import KillerCobra from 'Parser/Hunter/BeastMastery/Modules/Talents/KillerCobra';
import AspectOfTheBeast from 'Parser/Hunter/BeastMastery/Modules/Talents/AspectOfTheBeast';
import AspectOfTheWild from 'Parser/Hunter/BeastMastery/Modules/Spells/AspectOfTheWild';
import DireFrenzy from 'Parser/Hunter/BeastMastery/Modules/Talents/DireFrenzy';

class Checklist extends CoreChecklist {
  static dependencies = {
    combatants: Combatants,

    //general
    legendaryUpgradeChecker: LegendaryUpgradeChecker,
    legendaryCountChecker: LegendaryCountChecker,
    prePotion: PrePotion,

    //features:
    castEfficiency: CastEfficiency,
    alwaysBeCasting: AlwaysBeCasting,
    enchantChecker: EnchantChecker,

    //talents
    aMurderOfCrows: AMurderOfCrows,
    killerCobra: KillerCobra,
    aspectOfTheBeast: AspectOfTheBeast,

    //Spells
    aspectOfTheWild: AspectOfTheWild,
    direBeast: DireBeast,
    direFrenzy: DireFrenzy,
    bestialWrathAverageFocus: BestialWrathAverageFocus,
  };

  rules = [
    new Rule({
      name: 'Use core spells as often as possible',
      description: <Wrapper>Spells such as <SpellLink id={SPELLS.KILL_COMMAND.id} icon /> and <SpellLink id={SPELLS.DIRE_BEAST.id} icon /> should be used on cooldown. <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> should never be capping stacks, but you also want to maximize buff uptime by spreading out the casts as much as possible. You'll want as many good casts of <SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon />, <SpellLink id={SPELLS.TITANS_THUNDER.id} icon /> and <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> as possible - this is achieved by lining them up with <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> for each cast, and in preparation for each <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> you want to have saved up some focus. More info.</Wrapper>,
      requirements: () => {
        const combatant = this.combatants.selected;
        return [
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.DIRE_BEAST,
            when: !combatant.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.DIRE_FRENZY_TALENT,
            when: combatant.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.KILL_COMMAND,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.BESTIAL_WRATH,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.TITANS_THUNDER,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.ASPECT_OF_THE_WILD,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.BARRAGE_TALENT,
            when: combatant.hasTalent(SPELLS.BARRAGE_TALENT.id),
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper> Use your Dire ability properly </Wrapper>,
      description: <Wrapper>Using either <SpellLink id={SPELLS.DIRE_BEAST.id} icon /> or <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> properly is a key to achieving high dps. This means maintaining the buff from <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> as long as possible, and utilising the cooldown reduction from <SpellLink id={SPELLS.DIRE_BEAST.id} icon /> as much as possible, to ensure high uptime on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /></Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper>Casts with less than 3s CD on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /></Wrapper>,
            when: !this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direBeast.badDireBeastThreshold,
          }),
          new Requirement({
            name: <Wrapper>Uptime of <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> </Wrapper>,
            when: this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direFrenzy.direFrenzyUptimeThreshold,
          }),
          new Requirement({
            name: <Wrapper>3 Stacks Uptime of <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> </Wrapper>,
            when: this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direFrenzy.direFrenzy3StackThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper>Cooldown efficiency</Wrapper>,
      description: <Wrapper> Use <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> & <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> properly. This means having a high amount of focus going into <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> windows, and by using your <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> when <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> is up.</Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper>Average focus on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> cast</Wrapper>,
            check: () => this.bestialWrathAverageFocus.focusOnBestialWrathCastThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> casts w/o <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> up</Wrapper>,
            check: () => this.aspectOfTheWild.badCastThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper>Talent Efficiency</Wrapper>,
      description: <Wrapper>Utilising your talents to their maximum is important when trying to do as much damage as possible, if these aren't utilised properly, you may be better off using a different talent. </Wrapper>,
      requirements: () => {
        const combatant = this.combatants.selected;
        return [
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.ASPECT_OF_THE_BEAST_TALENT.id} icon /> damage</Wrapper>,
            when: combatant.hasTalent(SPELLS.ASPECT_OF_THE_BEAST_TALENT.id),
            check: () => this.aspectOfTheBeast.aspectOfTheBeastDamageThreshold,
          }),
          new Requirement({
            name: <Wrapper>Wasted <SpellLink id={SPELLS.KILLER_COBRA_TALENT.id} icon /> resets</Wrapper>,
            when: combatant.hasTalent(SPELLS.KILLER_COBRA_TALENT.id),
            check: () => this.killerCobra.wastedKillerCobraThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon /> casts with less than 7s remaining of <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> duration</Wrapper>,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
            check: () => this.aMurderOfCrows.shouldHaveSavedThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon /> casts without <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> ready to cast after</Wrapper>,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
            check: () => this.aMurderOfCrows.badCastThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper><Icon
        icon='spell_mage_altertime'
        alt='Casting downtime'
        style={{
          height: '1.3em',
          marginTop: '-.1em',
        }}
      /> Downtime</Wrapper>,
      description: <Wrapper> Try to reduce the delay between casting spells. If everything is on cooldown, try and use <SpellLink id={SPELLS.COBRA_SHOT.id} icon /> to stay off the focus cap and do some damage.</Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper><Icon
              icon='spell_mage_altertime'
              alt='Casting downtime'
              style={{
                height: '1.3em',
                marginTop: '-.1em',
              }}
            /> Downtime</Wrapper>,
            check: () => this.alwaysBeCasting.suggestionThresholds,
          }),
        ];
      },
    }),
    new Rule({
      name: 'Be well prepared',
      description: 'Being prepared is important if you want to perform to your highest potential',
      requirements: () => {
        return [
          new Requirement({
            name: 'All legendaries upgraded to max item level',
            check: () => ({
              actual: this.legendaryUpgradeChecker.upgradedLegendaries.length,
              isLessThan: this.legendaryCountChecker.max,
              style: 'number',
            }),
          }),
          new Requirement({
            name: 'Used max possible legendaries',
            check: () => ({
              actual: this.legendaryCountChecker.equipped,
              isLessThan: this.legendaryCountChecker.max,
              style: 'number',
            }),
          }),
          new Requirement({
            name: 'Used a pre-potion',
            check: () => this.prePotion.prePotionSuggestionThresholds,
          }),
          new Requirement({
            name: 'Used a second potion',
            check: () => this.prePotion.secondPotionSuggestionThresholds,
          }),
          new Requirement({
            name: 'Gear has best enchants',
            check: () => {
              const numEnchantableSlots = Object.keys(this.enchantChecker.enchantableGear).length;
              return {
                actual: numEnchantableSlots - (this.enchantChecker.slotsMissingEnchant.length + this.enchantChecker.slotsMissingMaxEnchant.length),
                isLessThan: numEnchantableSlots,
                style: 'number',
              };
            },
          }),
        ];
      },
    }),

  ];

}

export default Checklist;
<commit_msg>more descriptions<commit_after>import React from 'react';

import CoreChecklist, { Rule, Requirement, GenericCastEfficiencyRequirement } from 'Parser/Core/Modules/Features/Checklist';
import Combatants from 'Parser/Core/Modules/Combatants';
import LegendaryCountChecker from 'Parser/Core/Modules/Items/LegendaryCountChecker';
import LegendaryUpgradeChecker from 'Parser/Core/Modules/Items/LegendaryUpgradeChecker';
import PrePotion from 'Parser/Core/Modules/Items/PrePotion';
import Wrapper from 'common/Wrapper';
import SPELLS from 'common/SPELLS';
import CastEfficiency from 'Parser/Core/Modules/CastEfficiency';
import AlwaysBeCasting from 'Parser/Hunter/BeastMastery/Modules/Features/AlwaysBeCasting';
import AMurderOfCrows from 'Parser/Hunter/BeastMastery/Modules/Talents/AMurderOfCrows';
import SpellLink from 'common/SpellLink';
import Icon from "common/Icon";
import EnchantChecker from 'Parser/Core/Modules/Items/EnchantChecker';
import DireBeast from 'Parser/Hunter/BeastMastery/Modules/Spells/DireBeast/DireBeast';
import BestialWrathAverageFocus from 'Parser/Hunter/BeastMastery/Modules/Spells/BestialWrath/BestialWrathAverageFocus';
import KillerCobra from 'Parser/Hunter/BeastMastery/Modules/Talents/KillerCobra';
import AspectOfTheBeast from 'Parser/Hunter/BeastMastery/Modules/Talents/AspectOfTheBeast';
import AspectOfTheWild from 'Parser/Hunter/BeastMastery/Modules/Spells/AspectOfTheWild';
import DireFrenzy from 'Parser/Hunter/BeastMastery/Modules/Talents/DireFrenzy';

class Checklist extends CoreChecklist {
  static dependencies = {
    combatants: Combatants,

    //general
    legendaryUpgradeChecker: LegendaryUpgradeChecker,
    legendaryCountChecker: LegendaryCountChecker,
    prePotion: PrePotion,

    //features:
    castEfficiency: CastEfficiency,
    alwaysBeCasting: AlwaysBeCasting,
    enchantChecker: EnchantChecker,

    //talents
    aMurderOfCrows: AMurderOfCrows,
    killerCobra: KillerCobra,
    aspectOfTheBeast: AspectOfTheBeast,

    //Spells
    aspectOfTheWild: AspectOfTheWild,
    direBeast: DireBeast,
    direFrenzy: DireFrenzy,
    bestialWrathAverageFocus: BestialWrathAverageFocus,
  };

  rules = [
    new Rule({
      name: 'Use core spells as often as possible',
      description: <Wrapper>Spells such as <SpellLink id={SPELLS.KILL_COMMAND.id} icon /> and <SpellLink id={SPELLS.DIRE_BEAST.id} icon /> should be used on cooldown. <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> should never be capping stacks, but you also want to maximize buff uptime by spreading out the casts as much as possible. You'll want as many good casts of <SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon />, <SpellLink id={SPELLS.TITANS_THUNDER.id} icon /> and <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> as possible - this is achieved by lining them up with <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> for each cast, and in preparation for each <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> you want to have saved up some focus. More info.</Wrapper>,
      requirements: () => {
        const combatant = this.combatants.selected;
        return [
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.DIRE_BEAST,
            when: !combatant.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.DIRE_FRENZY_TALENT,
            when: combatant.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.KILL_COMMAND,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.BESTIAL_WRATH,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.TITANS_THUNDER,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.ASPECT_OF_THE_WILD,
          }),
          new GenericCastEfficiencyRequirement({
            spell: SPELLS.BARRAGE_TALENT,
            when: combatant.hasTalent(SPELLS.BARRAGE_TALENT.id),
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper> Use your Dire ability properly </Wrapper>,
      description: <Wrapper>Using either <SpellLink id={SPELLS.DIRE_BEAST.id} icon /> or <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> properly is a key to achieving high dps. This means maintaining the buff from <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> as long as possible, and utilising the cooldown reduction from <SpellLink id={SPELLS.DIRE_BEAST.id} icon />/<SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> as much as possible, to ensure high uptime on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /></Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper>Casts with less than 3s CD on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /></Wrapper>,
            when: !this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direBeast.badDireBeastThreshold,
          }),
          new Requirement({
            name: <Wrapper>Uptime of <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> </Wrapper>,
            when: this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direFrenzy.direFrenzyUptimeThreshold,
          }),
          new Requirement({
            name: <Wrapper>3 Stacks Uptime of <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> </Wrapper>,
            when: this.combatants.selected.hasTalent(SPELLS.DIRE_FRENZY_TALENT.id),
            check: () => this.direFrenzy.direFrenzy3StackThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper>Cooldown efficiency</Wrapper>,
      description: <Wrapper> Use <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> & <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> properly. This means having a high amount of focus going into <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> windows, and by using your <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> when <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> is up.</Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper>Average focus on <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> cast</Wrapper>,
            check: () => this.bestialWrathAverageFocus.focusOnBestialWrathCastThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> casts w/o <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> up</Wrapper>,
            check: () => this.aspectOfTheWild.badCastThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper>Talent Efficiency</Wrapper>,
      description: <Wrapper>Utilising your talents to their maximum is important when trying to do as much damage as possible, if these aren't utilised properly, you may be better off using a different talent. </Wrapper>,
      requirements: () => {
        const combatant = this.combatants.selected;
        return [
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.ASPECT_OF_THE_BEAST_TALENT.id} icon /> damage</Wrapper>,
            when: combatant.hasTalent(SPELLS.ASPECT_OF_THE_BEAST_TALENT.id),
            check: () => this.aspectOfTheBeast.aspectOfTheBeastDamageThreshold,
          }),
          new Requirement({
            name: <Wrapper>Wasted <SpellLink id={SPELLS.KILLER_COBRA_TALENT.id} icon /> resets</Wrapper>,
            when: combatant.hasTalent(SPELLS.KILLER_COBRA_TALENT.id),
            check: () => this.killerCobra.wastedKillerCobraThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon /> casts with less than 7s remaining of <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> duration</Wrapper>,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
            check: () => this.aMurderOfCrows.shouldHaveSavedThreshold,
          }),
          new Requirement({
            name: <Wrapper><SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} icon /> casts without <SpellLink id={SPELLS.BESTIAL_WRATH.id} icon /> ready to cast after</Wrapper>,
            when: combatant.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id),
            check: () => this.aMurderOfCrows.badCastThreshold,
          }),
        ];
      },
    }),
    new Rule({
      name: <Wrapper><Icon
        icon='spell_mage_altertime'
        alt='Casting downtime'
        style={{
          height: '1.3em',
          marginTop: '-.1em',
        }}
      /> Downtime</Wrapper>,
      description: <Wrapper> Try to reduce the delay between casting spells. If everything is on cooldown, try and use <SpellLink id={SPELLS.COBRA_SHOT.id} icon /> to stay off the focus cap and do some damage.</Wrapper>,
      requirements: () => {
        return [
          new Requirement({
            name: <Wrapper><Icon
              icon='spell_mage_altertime'
              alt='Casting downtime'
              style={{
                height: '1.3em',
                marginTop: '-.1em',
              }}
            /> Downtime</Wrapper>,
            check: () => this.alwaysBeCasting.suggestionThresholds,
          }),
        ];
      },
    }),
    new Rule({
      name: 'Be well prepared',
      description: 'Being prepared is important if you want to perform to your highest potential',
      requirements: () => {
        return [
          new Requirement({
            name: 'All legendaries upgraded to max item level',
            check: () => ({
              actual: this.legendaryUpgradeChecker.upgradedLegendaries.length,
              isLessThan: this.legendaryCountChecker.max,
              style: 'number',
            }),
          }),
          new Requirement({
            name: 'Used max possible legendaries',
            check: () => ({
              actual: this.legendaryCountChecker.equipped,
              isLessThan: this.legendaryCountChecker.max,
              style: 'number',
            }),
          }),
          new Requirement({
            name: 'Used a pre-potion',
            check: () => this.prePotion.prePotionSuggestionThresholds,
          }),
          new Requirement({
            name: 'Used a second potion',
            check: () => this.prePotion.secondPotionSuggestionThresholds,
          }),
          new Requirement({
            name: 'Gear has best enchants',
            check: () => {
              const numEnchantableSlots = Object.keys(this.enchantChecker.enchantableGear).length;
              return {
                actual: numEnchantableSlots - (this.enchantChecker.slotsMissingEnchant.length + this.enchantChecker.slotsMissingMaxEnchant.length),
                isLessThan: numEnchantableSlots,
                style: 'number',
              };
            },
          }),
        ];
      },
    }),

  ];

}

export default Checklist;
<|endoftext|>