<commit_before>function SpazPhotoMailer(opts) {

	this.apis = this.getAPIs();

}

SpazPhotoMailer.prototype.getAPILabels = function() {
	var labels = [];
	for ( var key in this.getAPIs() ) {
		labels.push(key);
	}
	return labels;
};

SpazPhotoMailer.prototype.getAPIs = function() {

	thisSPM = this;

	var apis = {
		"yfrog": {
			"email_tpl"  :"{{username}}.??????@yfrog.com",
			"message_in" :"subject",
			"email_info_url":"http://yfrog.com/froggy.php",
			'help_text'  :"Log-in to yfrog.com with your Twitter username and password, and click 'my yfrog.' Your customized posting email will be listed on the right.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['yfrog'].email_tpl.replace('{{username}}', username);
			}
		},

		"posterous": {
			"email_tpl"  :"post@posterous.com",
			"message_in" :"subject",
			"email_info_url":"http://posterous.com/main/editemails",
			'help_text'  :"Log into posterous, click 'Manage' submit your email address for posting",
			'getToAddress': function(opts) {
				return thisSPM.apis['posterous'].email_tpl;
			}
		},

		"twitgoo": {
			"email_tpl"  :"m@twitgoo.com",
			"message_in" :"subject",
			"email_info_url":"http://twitgoo.com/-settings/mobile",
			'help_text'  :"Log-in to twitgoo.com and click 'Settings.' Add the email address from which you'll be sending messages.",
			'getToAddress': function(opts) {
				return thisSPM.apis['twitgoo'].email_tpl;
			}
		},

		"twitpic": {
			"email_tpl"  :"{{username}}.####@twitpic.com",
			"message_in" :"subject",
			"email_info_url":"http://twitpic.com/settings.do",
			'help_text'  :"Log-in to twitpic.com, and click 'Settings.' Your custom email address will be listed.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['twitpic'].email_tpl.replace('{{username}}', username);
			}
		},

		"tweetphoto": {
			"email_tpl"  :"{{username}}.####@tweetphoto.com",
			"message_in" :"subject",
			"email_info_url":"http://www.tweetphoto.com/mysettings.php",
			'help_text'  :"Log-in to tweetphoto.com and click 'My Settings.' Your custom email address will be listed.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['tweetphoto'].email_tpl.replace('{{username}}', username);
			},
			'retrievePostingAddress': function(username, password, success, failure) {

				function getTweetPhotoProfile(username, password) {

					var url = "http://tweetphotoapi.com/api/tpapi.svc/json/users/"+username;
					var TPAPI_header = 'TPAPI: '+username+","+password;

					jQuery.ajax({

						'success':function(data, textStatus) {
							var profile = sch.deJSON(data);

						},

						'error':function(xhr, testStatus, errorThrown) {
							failure(xhr, testStatus, errorThrown);
						},

						'beforeSend':function(xhr){
							xhr.setRequestHeader("TPAPI", username+","+password);
				        },

						'url':url

					});

				};

				function getTweetPhoto(username, password, settings_url) {

					var TPAPI_header = 'TPAPI: '+username+","+password;

					jQuery.ajax({

						'success':function(data, textStatus) {
							var settings = sch.deJSON(data);
							success(settings.Email);
						},

						'error':function(xhr, testStatus, errorThrown) {
							failure(xhr, testStatus, errorThrown);
						},

						'beforeSend':function(xhr){
							xhr.setRequestHeader("TPAPI", username+","+password);
				        },

						'url':settings_url

					});

				}

			},
		}
	}

	return apis;

};

SpazPhotoMailer.prototype.setAPI = function(apilabel) {
	this.api = this.apis[apilabel];
};

SpazPhotoMailer.prototype.send = function(api, photo_url, message) {

};
<commit_msg>added Posterous support (thanks Patrick!) including brief instructions on setting up autopost to Twitter<commit_after>function SpazPhotoMailer(opts) {

	this.apis = this.getAPIs();

}

SpazPhotoMailer.prototype.getAPILabels = function() {
	var labels = [];
	for ( var key in this.getAPIs() ) {
		labels.push(key);
	}
	return labels;
};

SpazPhotoMailer.prototype.getAPIs = function() {

	thisSPM = this;

	var apis = {
		"yfrog": {
			"email_tpl"  :"{{username}}.??????@yfrog.com",
			"message_in" :"subject",
			"email_info_url":"http://yfrog.com/froggy.php",
			'help_text'  :"Log-in to yfrog.com with your Twitter username and password, and click 'my yfrog.' Your customized posting email will be listed on the right.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['yfrog'].email_tpl.replace('{{username}}', username);
			}
		},

		"posterous": {
			"email_tpl"  :"post@posterous.com",
			"message_in" :"subject",
			"email_info_url":"http://posterous.com/autopost",
			'help_text'  :"Post instantly to your Posterous blog. Setup autopost to post back to Twitter! Login for more information and controls.",
			'getToAddress': function(opts) {
				return thisSPM.apis['posterous'].email_tpl;
			}
		},

		"twitgoo": {
			"email_tpl"  :"m@twitgoo.com",
			"message_in" :"subject",
			"email_info_url":"http://twitgoo.com/-settings/mobile",
			'help_text'  :"Log-in to twitgoo.com and click 'Settings.' Add the email address from which you'll be sending messages.",
			'getToAddress': function(opts) {
				return thisSPM.apis['twitgoo'].email_tpl;
			}
		},

		"twitpic": {
			"email_tpl"  :"{{username}}.####@twitpic.com",
			"message_in" :"subject",
			"email_info_url":"http://twitpic.com/settings.do",
			'help_text'  :"Log-in to twitpic.com, and click 'Settings.' Your custom email address will be listed.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['twitpic'].email_tpl.replace('{{username}}', username);
			}
		},

		"tweetphoto": {
			"email_tpl"  :"{{username}}.####@tweetphoto.com",
			"message_in" :"subject",
			"email_info_url":"http://www.tweetphoto.com/mysettings.php",
			'help_text'  :"Log-in to tweetphoto.com and click 'My Settings.' Your custom email address will be listed.",
			'getToAddress': function(opts) {
				var username = opts.username;
				return thisSPM.apis['tweetphoto'].email_tpl.replace('{{username}}', username);
			},
			'retrievePostingAddress': function(username, password, success, failure) {

				function getTweetPhotoProfile(username, password) {

					var url = "http://tweetphotoapi.com/api/tpapi.svc/json/users/"+username;
					var TPAPI_header = 'TPAPI: '+username+","+password;

					jQuery.ajax({

						'success':function(data, textStatus) {
							var profile = sch.deJSON(data);

						},

						'error':function(xhr, testStatus, errorThrown) {
							failure(xhr, testStatus, errorThrown);
						},

						'beforeSend':function(xhr){
							xhr.setRequestHeader("TPAPI", username+","+password);
				        },

						'url':url

					});

				};

				function getTweetPhoto(username, password, settings_url) {

					var TPAPI_header = 'TPAPI: '+username+","+password;

					jQuery.ajax({

						'success':function(data, textStatus) {
							var settings = sch.deJSON(data);
							success(settings.Email);
						},

						'error':function(xhr, testStatus, errorThrown) {
							failure(xhr, testStatus, errorThrown);
						},

						'beforeSend':function(xhr){
							xhr.setRequestHeader("TPAPI", username+","+password);
				        },

						'url':settings_url

					});

				}

			}
		}
	};

	return apis;

};

SpazPhotoMailer.prototype.setAPI = function(apilabel) {
	this.api = this.apis[apilabel];
};

SpazPhotoMailer.prototype.send = function(api, photo_url, message) {

};
<|endoftext|>


<commit_before>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];
	var doneNames = ["Closed", "Resolved", "Done"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;

    $scope.workType = 1;

    $scope.$watch('workType', $scope.refresh);

    $scope.points = [[]];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullsStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories) {
        $scope.notStarted = 0;
        $scope.inProgress = 0;
        $scope.done = 0;

    	angular.forEach(stories, function(story, index) {
    		if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			$scope.notStarted += getValue(story);
    		} else if (jQuery.inArray(story.fields.status.name, doneNames) != -1) {
    			$scope.done += getValue(story);
    		} else {
    			$scope.inProgress += getValue(story);
    		}
    	});
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = getValue(story);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            if(story.fields.resolutiondate !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    function getValue(story) {
        switch($scope.workType) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600).toFixed(2);
        }
    }

    // update the story counts and chart points
    $scope.refresh = function() {
    	countStories($scope.stories);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });
        $scope.points = [[]];
        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].push([elem.date, runningTotal]);
        });
    };

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;
            var opts = {
                xaxis: {
                	mode: "time",
                    ticks: 4,
                    tickFormatter: function(val, axis) {
                        return new Date(val).toLocaleDateString();
                    }
                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			}
            };
            var overviewOpts = {
            	series: {
        			lines: {
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };
            scope.$watch(attrs.ngModel, function(v) {
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');
                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {
                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<commit_msg>fixed the tick labels to be on the axes and testing if the lines should use stepping instead of lines<commit_after>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];
	var doneNames = ["Closed", "Resolved", "Done"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;

    $scope.workType = 1;

    $scope.$watch('workType', $scope.refresh);

    $scope.points = [[]];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullsStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories) {
        $scope.notStarted = 0;
        $scope.inProgress = 0;
        $scope.done = 0;

    	angular.forEach(stories, function(story, index) {
    		if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			$scope.notStarted += getValue(story);
    		} else if (jQuery.inArray(story.fields.status.name, doneNames) != -1) {
    			$scope.done += getValue(story);
    		} else {
    			$scope.inProgress += getValue(story);
    		}
    	});
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = getValue(story);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            if(story.fields.resolutiondate !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    function getValue(story) {
        switch($scope.workType) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600).toFixed(2);
        }
    }

    // update the story counts and chart points
    $scope.refresh = function() {
    	countStories($scope.stories);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });
        $scope.points = [[]];
        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].push([elem.date, runningTotal]);
        });
    };

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;

            var opts = {
                xaxis: {
                    ticks: function(axis) {
                        var min = axis.min;
                        var step = Math.floor((axis.max - axis.min) / 4);

                        var ticks = [];

                        for(var i = 0; i < 5; i++) {
                            ticks.push([min, new Date(min).toLocaleDateString()]);
                            min += step;
                        }
                        return ticks;
                    }
                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			},
                series: {
                    lines: {
                        steps: true
                    }
                }
            };

            var overviewOpts = {
            	series: {
        			lines: {
                        steps: true,
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };

            scope.$watch(attrs.ngModel, function(v) {
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');

                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {

                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<|endoftext|>


<commit_before>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;
	$scope.averageTime = 0;
	$scope.chartMin = 0;
	$scope.chartMax = 0;

    $scope.workType = 1;

    $scope.$watch('workType', function() {
    	$scope.refresh();
    });

    $scope.points = [[],{}];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories, worktype) {
        var notStarted = 0;
        var inProgress = 0;
        var done = 0;

    	angular.forEach(stories, function(story, index) {
    		var resolution = story.fields.resolutiondate;
    		if (resolution !== undefined && resolution !== null) {
    			done += $scope.getValue(story, worktype);
    		} else if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			notStarted += $scope.getValue(story, worktype);
    		} else {
    			inProgress += $scope.getValue(story, worktype);
    		}
    	});

        return [notStarted, inProgress, done];
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = $scope.getValue(story, $scope.workType);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            var resolution = story.fields.resolutiondate;
            if(resolution !== undefined && resolution !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    $scope.getValue = function(story, worktype) {
        switch(worktype) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600);
        }
    };

    function getAverageTime(stories) {
    	var completedStories = 0;
    	var sumTime = 0;

        var day = 1000 * 60 * 60 * 24;
        var week = day * 7;

    	angular.forEach(stories, function(story, index) {
    		if(story.fields.resolutiondate !== null) {
    			completedStories++;
    			var completedTime = Date.parse(story.fields.resolutiondate) - Date.parse(story.fields.created);

                completedTime -= (2 * day * Math.floor(completedTime / week));
                completedTime /= 3;

    			sumTime+= completedTime;
            }
        });

		if(completedStories !== 0) {
        	return (sumTime/(completedStories* 60 * 60 * 1000));
        }
        return 0;
    }

    // format the given number for display
    $scope.format = function(number) {
    	if ($scope.workType == 3) {
    		return number.toFixed(2);
    	}
    	return number;
    };

    // update the story counts and chart points
    $scope.refresh = function() {
    	var counts = countStories($scope.stories, $scope.workType);
        $scope.notStarted = counts[0];
        $scope.inProgress = counts[1];
        $scope.done = counts[2];

    	$scope.averageTime = getAverageTime($scope.stories).toFixed(2);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });

        $scope.points = [
        {
            label: 'In Flight',
            lines: { steps: true },
            data: []
        },
        {
            label: 'Forecast',
            lines: { show: true },
            points: { show: true },
            data: []
        },
        {
            label: 'Created',
            data: []
        },
        {
            label: 'Resolved',
            data: []
        }];

        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].data.push([elem.date, runningTotal]);
        });

        //set the second series data to the forecasted list
        var temp = getAverageTime($scope.fullStories);
        temp *= 3;
        temp += (temp/(5 * 24)) * 48;
        $scope.points[1].data = getForecastLine($scope.points[0].data[$scope.points[0].data.length - 1], temp);

        $scope.points[2].data = getDataPoints(true);
        $scope.points[3].data = getDataPoints(false);
    };

    // return a list of points with numbers associated with dates for either story creation
    // or resolution numbers, depending on the given boolean
    function getDataPoints(creation) {
    	var points = [];
    	var numPoints = 5;
    	// if not zoomed in, set chart min and max
    	if ($scope.chartMin == $scope.chartMax) {
    		$scope.chartMin = $scope.points[0].data[0][0];
    		$scope.chartMax = $scope.points[0].data[$scope.points[0].data.length - 1][0];
    	}

        if($scope.chartMax > $scope.points[0].data[$scope.points[0].data.length - 1][0]) {
            $scope.chartMax = $scope.points[0].data[$scope.points[0].data.length - 1][0];
        }

    	var range = ($scope.chartMax - $scope.chartMin) / numPoints;

        points.push([$scope.points[0].data[0][0], 0]);//adds the origin point to the line

    	for (var i = 0; i < numPoints; i++) {
    		points.push([($scope.chartMin + (i + 1) * range), 0]);
    	}
    	angular.forEach($scope.stories, function(story, index) {
    		var date = Date.parse(creation ? story.fields.created : story.fields.resolutiondate);
    		if (date > $scope.chartMin && date < $scope.chartMax) {
    			var i = Math.floor((date - $scope.chartMin) / range) + 1;
    			points[i][1] += $scope.getValue(story, $scope.workType);
    		}
    	});
    	return points;
    }

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };

    function getForecastLine(startPoint, averageTime) {
        var counts = countStories($scope.fullStories, 1);
        var stories = counts[0] + counts[1];
        return averageTime > 0 && stories > 0 ? [startPoint, [startPoint[0] + (stories * averageTime * 1000 * 60 * 60), 0]] : [];
    }

    // return a string representation of the work type
    $scope.workTypeToString = function() {
    	switch($scope.workType) {
        case 1:
            return "Stories";
        case 2:
            return "Points";
        case 3:
            return "Hours";
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;

            var opts = {
                xaxis: {
                    ticks: function(axis) {
                        var min = axis.min;
                        var step = Math.floor((axis.max - axis.min) / 4);

                        var ticks = [];

                        for(var i = 0; i < 5; i++) {
                            ticks.push(min);
                            min += step;
                        }
                        return ticks;
                    },
                    tickFormatter: function(val, axis) {
                        var range = axis.max - axis.min;
                        var day = 1000 * 60 * 60 * 24;

                        var date = new Date(val);

                        if(range >= 4 * day) {
                            return date.toLocaleDateString() + '
 ';//hack to force using up som whitespace
                        }
                        else if(range >= day) {
                            return date.toLocaleDateString() + '
' + date.toLocaleTimeString();
                        }
                        else {
                            return date.toLocaleTimeString() + '
 ';
                        }
                    }

                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			}
            };

            var overviewOpts = {
                legend: {
                    show: false
                },
            	series: {
        			lines: {
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };

            scope.$watch(attrs.ngModel, function(v) {
                var v2 = v.slice(0, 2);
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');

                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v2, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {

                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v2);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			scope.chartMin = min;
    			scope.chartMax = max;
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<commit_msg>Fixed the starting points for the resolved and created lines<commit_after>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;
	$scope.averageTime = 0;
	$scope.chartMin = 0;
	$scope.chartMax = 0;

    $scope.workType = 1;

    $scope.$watch('workType', function() {
    	$scope.refresh();
    });

    $scope.points = [[],{}];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories, worktype) {
        var notStarted = 0;
        var inProgress = 0;
        var done = 0;

    	angular.forEach(stories, function(story, index) {
    		var resolution = story.fields.resolutiondate;
    		if (resolution !== undefined && resolution !== null) {
    			done += $scope.getValue(story, worktype);
    		} else if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			notStarted += $scope.getValue(story, worktype);
    		} else {
    			inProgress += $scope.getValue(story, worktype);
    		}
    	});

        return [notStarted, inProgress, done];
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = $scope.getValue(story, $scope.workType);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            var resolution = story.fields.resolutiondate;
            if(resolution !== undefined && resolution !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    $scope.getValue = function(story, worktype) {
        switch(worktype) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600);
        }
    };

    function getAverageTime(stories) {
    	var completedStories = 0;
    	var sumTime = 0;

        var day = 1000 * 60 * 60 * 24;
        var week = day * 7;

    	angular.forEach(stories, function(story, index) {
    		if(story.fields.resolutiondate !== null) {
    			completedStories++;
    			var completedTime = Date.parse(story.fields.resolutiondate) - Date.parse(story.fields.created);

                completedTime -= (2 * day * Math.floor(completedTime / week));
                completedTime /= 3;

    			sumTime+= completedTime;
            }
        });

		if(completedStories !== 0) {
        	return (sumTime/(completedStories* 60 * 60 * 1000));
        }
        return 0;
    }

    // format the given number for display
    $scope.format = function(number) {
    	if ($scope.workType == 3) {
    		return number.toFixed(2);
    	}
    	return number;
    };

    // update the story counts and chart points
    $scope.refresh = function() {
    	var counts = countStories($scope.stories, $scope.workType);
        $scope.notStarted = counts[0];
        $scope.inProgress = counts[1];
        $scope.done = counts[2];

    	$scope.averageTime = getAverageTime($scope.stories).toFixed(2);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });

        $scope.points = [
        {
            label: 'In Flight',
            lines: { steps: true },
            data: []
        },
        {
            label: 'Forecast',
            lines: { show: true },
            points: { show: true },
            data: []
        },
        {
            label: 'Created',
            data: []
        },
        {
            label: 'Resolved',
            data: []
        }];

        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].data.push([elem.date, runningTotal]);
        });

        //set the second series data to the forecasted list
        var temp = getAverageTime($scope.fullStories);
        temp *= 3;
        temp += (temp/(5 * 24)) * 48;
        $scope.points[1].data = getForecastLine($scope.points[0].data[$scope.points[0].data.length - 1], temp);

        $scope.points[2].data = getDataPoints(true);
        $scope.points[3].data = getDataPoints(false);
    };

    // return a list of points with numbers associated with dates for either story creation
    // or resolution numbers, depending on the given boolean
    function getDataPoints(creation) {
    	var points = [];
    	var numPoints = 5;
    	// if not zoomed in, set chart min and max
    	if ($scope.chartMin == $scope.chartMax) {
    		$scope.chartMin = $scope.points[0].data[0][0];
    		$scope.chartMax = $scope.points[0].data[$scope.points[0].data.length - 1][0];
    	}

        if($scope.chartMax > $scope.points[0].data[$scope.points[0].data.length - 1][0]) {
            $scope.chartMax = $scope.points[0].data[$scope.points[0].data.length - 1][0];
        }

    	var range = ($scope.chartMax - $scope.chartMin) / numPoints;

        points.push([$scope.chartMin, 0]);//adds the origin point to the line

    	for (var i = 0; i < numPoints; i++) {
    		points.push([($scope.chartMin + (i + 1) * range), 0]);
    	}
    	angular.forEach($scope.stories, function(story, index) {
    		var date = Date.parse(creation ? story.fields.created : story.fields.resolutiondate);
    		if (date > $scope.chartMin && date < $scope.chartMax) {
    			var i = Math.floor((date - $scope.chartMin) / range) + 1;
    			points[i][1] += $scope.getValue(story, $scope.workType);
    		}
    	});
    	return points;
    }

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };

    function getForecastLine(startPoint, averageTime) {
        var counts = countStories($scope.fullStories, 1);
        var stories = counts[0] + counts[1];
        return averageTime > 0 && stories > 0 ? [startPoint, [startPoint[0] + (stories * averageTime * 1000 * 60 * 60), 0]] : [];
    }

    // return a string representation of the work type
    $scope.workTypeToString = function() {
    	switch($scope.workType) {
        case 1:
            return "Stories";
        case 2:
            return "Points";
        case 3:
            return "Hours";
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;

            var opts = {
                xaxis: {
                    ticks: function(axis) {
                        var min = axis.min;
                        var step = Math.floor((axis.max - axis.min) / 4);

                        var ticks = [];

                        for(var i = 0; i < 5; i++) {
                            ticks.push(min);
                            min += step;
                        }
                        return ticks;
                    },
                    tickFormatter: function(val, axis) {
                        var range = axis.max - axis.min;
                        var day = 1000 * 60 * 60 * 24;

                        var date = new Date(val);

                        if(range >= 4 * day) {
                            return date.toLocaleDateString() + '
 ';//hack to force using up som whitespace
                        }
                        else if(range >= day) {
                            return date.toLocaleDateString() + '
' + date.toLocaleTimeString();
                        }
                        else {
                            return date.toLocaleTimeString() + '
 ';
                        }
                    }

                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			}
            };

            var overviewOpts = {
                legend: {
                    show: false
                },
            	series: {
        			lines: {
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };

            scope.$watch(attrs.ngModel, function(v) {
                var v2 = v.slice(0, 2);
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');

                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v2, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {

                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v2);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			scope.chartMin = min;
    			scope.chartMax = max;
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<|endoftext|>


<commit_before>var TVCModule;
var g = {};
g.isRunning = true;
g.tvc = undefined;
g.canvas = undefined;
g.regs = undefined;
g.fb = undefined;
g.bgcolor = -1;

var callback = function(e) {
	var res,valstr;
	switch(e.id) {
		case "fb":
			res = g.fb;
			break;
		case "notify":
			notify(e.str);
			break;
	}
	return res;
};

function appStart() {
	requirejs(["scripts/tvc.js"], function(tvc) {
		TVCModule = tvc;
		emuInit();
	});
}

function getData(name, url) {
	var defer = $.Deferred();
	var oReq = new XMLHttpRequest();
	oReq.open("GET", url, true);
	oReq.responseType = "arraybuffer";
	oReq.onload = function (oEvent) {
		var arrayBuffer = oReq.response;
		if (arrayBuffer) {
			defer.resolve(name, arrayBuffer);
		}
		else {
			defer.reject(this);
		}
	};
	oReq.send(null);
	return defer.promise();
}
function emuBreak() {
	g.isRunning = false;
}
function emuReset() {
	g.tvc.reset();
}
function emuCreate(type) {
	notify("loading roms");
	g.isRunning = false;
	g.tvc = new TVCModule.TVC(type,callback);
	var roms;
	if (/2\.2/.test(type)) roms = ["TVC22_D4.64K", "TVC22_D6.64K", "TVC22_D7.64K"];
	else if (/DOS/.test(type)) roms = ["TVC12_D3.64K", "TVC12_D4.64K", "TVC12_D7.64K", "D_TVCDOS.128"];
	else roms = ["TVC12_D3.64K", "TVC12_D4.64K", "TVC12_D7.64K"];
	// load roms
	getData(roms[0], "roms/"+roms[0])
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		return getData(roms[1], "roms/"+roms[1]);
	})
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		return getData(roms[2], "roms/"+roms[2]);
	})
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		if (roms.length > 3) {
			return getData(roms[3], "roms/"+roms[3]);
		}
	})
	.then(function(dataname, data) {
		if (dataname) {
			g.tvc.addRom(dataname, new Uint8Array(data));
		}
		// start
		g.isRunning = true;
		emuContinue();
	});
}
function emuToggleRun() {
	g.isRunning = !g.isRunning;
	if (g.isRunning) {
		$("#bstop").text("stop");
		emuContinue();
	}
	else {
		$("#bstop").text("continue");
		notify("stopped");
	}
}
function notify(msg, msg2) {
	$("#statusline").text(msg);
}

function emuInit() {
	notify("init page");
	/* polyfills */
	if(window.requestAnimationFrame) g.requestAnimationFrame = function(f) {window.requestAnimationFrame(f);};
	else if (window.mozRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.mozRequestAnimationFrame(f);};
	else if (window.webkitRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.webkitRequestAnimationFrame(f);};
	else if (window.msRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.msRequestAnimationFrame(f);};
	if (typeof(performance) != "undefined")
		g.timenow = function() {return performance.now();};
	else
		g.timenow = Date.now;
	/* init */
	g.regs = $("#regs")[0];
	g.statusline = $("#statusline")[0];
	// frame buffer
	g.canvas = $("#tvcanvas");
	g.ctx = g.canvas[0].getContext("2d");
	g.fb = {};
	g.fb.updatetime = g.timenow();
	g.fb.updatecnt = 0;
	g.fb.fps = $("#fps")[0];
	g.fb.width = g.canvas[0].width;
	g.fb.height = g.canvas[0].height;
	g.fb.imageData = g.ctx.createImageData(g.fb.width, g.fb.height);
	g.fb.buf = new ArrayBuffer(g.fb.imageData.data.length);
	g.fb.buf8 = new Uint8ClampedArray(g.fb.buf);
	g.fb.buf32 = new Uint32Array(g.fb.buf);
	g.fb.refresh = function() {
		g.fb.imageData.data.set(g.fb.buf8);
		g.ctx.putImageData(g.fb.imageData, 0, 0);
		g.fb.updatecnt += 1;
		var timenow = g.timenow();
		if ((timenow - g.fb.updatetime) > 500) {
			var fps = ~~(g.fb.updatecnt / ((timenow - g.fb.updatetime) / 1000));
			notify("running " + fps.toString(10) + "fps");
			g.fb.updatetime = timenow;
			g.fb.updatecnt = 0;
		}
	};
	emuCreate("64k 1.2");
	// gui
	$("#breset").on("click", function() {
		emuReset();
	});
	$("#bstop").on("click", function() {
		emuToggleRun();
	});
	$("#bpoints").change(function (e) {
		var newbplst = [];
		var bplst = $("#bpoints")[0].value.split(",");
		g.tvc.setBreakPoints(bplst);
		$("#step").focus();
	});
	// img loading + selection
	var imgdrop = $("#scas");
	$("#bload").on("click", function () {
		var imgname = imgdrop[0].value;
		getData("data/" + imgname)
			.then(function(data) {
				g.tvc.reset();
				g.tvc.loadImg(imgname, new Uint8Array(data));
				$("#monitor").focus();
				notify("loaded", imgname + "\nTip: run + [enter]");
			});
	});
	for( var i = 0; i < datalist.length; i++ )
	{
			$("<option>").text(datalist[i]).val(datalist[i]).appendTo(imgdrop);
	}
	// machine type
	var machdrop = $("#smach");
	$("<option>").text("64k  1.2").val("64k 1.2").appendTo(machdrop);
	$("<option>").text("64k+ 1.2").val("64k+ 1.2").appendTo(machdrop);
	$("<option>").text("64k+ 2.2").val("64k+ 2.2").appendTo(machdrop);
	$("<option>").text("64k 1.2, VT-DOS").val("64k 1.2, VT-DOS").appendTo(machdrop);
	machdrop.on("change", function(e) {
		var machType = machdrop[0].value;
		emuCreate(machType);
	});
	// keyboard
	$(document).keydown(handleKeyDown);
	$(document).keyup(handleKeyUp);
	// focus on canvas
	$(window).focus(handleFocus);
	$(window).blur(handleFocusLost);
	// disable selection
	g.canvas.on("selectstart", function(e) { e.preventDefault(); return false; });
}

// event handlers
function handleKeyDown(e) {
	if (g.tvc) {
		g.tvc.keyDown(e.which);
		e.preventDefault();
	}
}
function handleKeyUp(e) {
	if (g.tvc) {
		g.tvc.keyUp(e.which);
	}
}
function handleFocus(e) {
	if (g.tvc)
		g.tvc.focusChange(true);
}
function handleFocusLost(e) {
	if (g.tvc)
		g.tvc.focusChange(false);
}

// emulator functions
function emuContinue() {
	g.requestAnimationFrame(emuRunFrame);
}
function emuRunFrame() {
	if (!g.isRunning) {
		return;
	}
	// run + update + continue
	if (g.tvc.runForAFrame()) {
		g.isRunning = false;
		$("#bstop").text("continue");
	}
	else {
		//g.regs.innerHTML = g.tvc._z80.toString();
		emuContinue();
	}
}

<commit_msg>try to limit to 50fps<commit_after>var TVCModule;
var g = {};
g.isRunning = true;
g.tvc = undefined;
g.canvas = undefined;
g.regs = undefined;
g.fb = undefined;
g.bgcolor = -1;

var callback = function(e) {
	var res,valstr;
	switch(e.id) {
		case "fb":
			res = g.fb;
			break;
		case "notify":
			notify(e.str);
			break;
	}
	return res;
};

function appStart() {
	requirejs(["scripts/tvc.js"], function(tvc) {
		TVCModule = tvc;
		emuInit();
	});
}

function getData(name, url) {
	var defer = $.Deferred();
	var oReq = new XMLHttpRequest();
	oReq.open("GET", url, true);
	oReq.responseType = "arraybuffer";
	oReq.onload = function (oEvent) {
		var arrayBuffer = oReq.response;
		if (arrayBuffer) {
			defer.resolve(name, arrayBuffer);
		}
		else {
			defer.reject(this);
		}
	};
	oReq.send(null);
	return defer.promise();
}
function emuBreak() {
	g.isRunning = false;
}
function emuReset() {
	g.tvc.reset();
}
function emuCreate(type) {
	notify("loading roms");
	g.isRunning = false;
	g.tvc = new TVCModule.TVC(type,callback);
	var roms;
	if (/2\.2/.test(type)) roms = ["TVC22_D4.64K", "TVC22_D6.64K", "TVC22_D7.64K"];
	else if (/DOS/.test(type)) roms = ["TVC12_D3.64K", "TVC12_D4.64K", "TVC12_D7.64K", "D_TVCDOS.128"];
	else roms = ["TVC12_D3.64K", "TVC12_D4.64K", "TVC12_D7.64K"];
	// load roms
	getData(roms[0], "roms/"+roms[0])
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		return getData(roms[1], "roms/"+roms[1]);
	})
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		return getData(roms[2], "roms/"+roms[2]);
	})
	.then(function(dataname, data) {
		g.tvc.addRom(dataname, new Uint8Array(data));
		if (roms.length > 3) {
			return getData(roms[3], "roms/"+roms[3]);
		}
	})
	.then(function(dataname, data) {
		if (dataname) {
			g.tvc.addRom(dataname, new Uint8Array(data));
		}
		// start
		g.isRunning = true;
		emuContinue();
	});
}
function emuToggleRun() {
	g.isRunning = !g.isRunning;
	if (g.isRunning) {
		$("#bstop").text("stop");
		emuContinue();
	}
	else {
		$("#bstop").text("continue");
		notify("stopped");
	}
}
function notify(msg, msg2) {
	$("#statusline").text(msg);
}

function emuInit() {
	notify("init page");
	/* polyfills */
	if(window.requestAnimationFrame) g.requestAnimationFrame = function(f) {window.requestAnimationFrame(f);};
	else if (window.mozRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.mozRequestAnimationFrame(f);};
	else if (window.webkitRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.webkitRequestAnimationFrame(f);};
	else if (window.msRequestAnimationFrame) g.requestAnimationFrame = function(f) {window.msRequestAnimationFrame(f);};
	if (typeof(performance) != "undefined")
		g.timenow = function() {return performance.now();};
	else
		g.timenow = Date.now;
	/* init */
	g.regs = $("#regs")[0];
	g.statusline = $("#statusline")[0];
	// frame buffer
	g.canvas = $("#tvcanvas");
	g.ctx = g.canvas[0].getContext("2d");
	g.fb = {};
	g.fb.updatetime = g.timenow();
	g.fb.updatecnt = 0;
	g.fb.updates = [];
	g.fb.skipcnt = 0;
	g.fb.fps = $("#fps")[0];
	g.fb.fpsv = 0;
	g.fb.width = g.canvas[0].width;
	g.fb.height = g.canvas[0].height;
	g.fb.imageData = g.ctx.createImageData(g.fb.width, g.fb.height);
	g.fb.buf = new ArrayBuffer(g.fb.imageData.data.length);
	g.fb.buf8 = new Uint8ClampedArray(g.fb.buf);
	g.fb.buf32 = new Uint32Array(g.fb.buf);
	g.fb.refresh = function() {
		g.fb.imageData.data.set(g.fb.buf8);
		g.ctx.putImageData(g.fb.imageData, 0, 0);
		g.fb.updatecnt++;
		var timenow = g.timenow();
		if ((timenow - g.fb.updatetime) > 1000) {
			g.fb.updates.push([g.fb.updatecnt, timenow]);
			if (g.fb.updates.length > 5) g.fb.updates.shift();
			g.fb.updatetime = timenow;

			var cntdiff = g.fb.updates[g.fb.updates.length -1][0] - g.fb.updates[0][0];
			var timediff = g.fb.updates[g.fb.updates.length -1][1] - g.fb.updates[0][1];

			g.fb.fpsv = ~~(cntdiff / (timediff / 1000));
			notify("running " + g.fb.fpsv.toString(10) + "fps");
		}
	};
	emuCreate("64k 1.2");
	// gui
	$("#breset").on("click", function() {
		emuReset();
	});
	$("#bstop").on("click", function() {
		emuToggleRun();
	});
	$("#bpoints").change(function (e) {
		var newbplst = [];
		var bplst = $("#bpoints")[0].value.split(",");
		g.tvc.setBreakPoints(bplst);
		$("#step").focus();
	});
	// img loading + selection
	var imgdrop = $("#scas");
	$("#bload").on("click", function () {
		var imgname = imgdrop[0].value;
		getData("data/" + imgname)
			.then(function(data) {
				g.tvc.reset();
				g.tvc.loadImg(imgname, new Uint8Array(data));
				$("#monitor").focus();
				notify("loaded", imgname + "\nTip: run + [enter]");
			});
	});
	for( var i = 0; i < datalist.length; i++ )
	{
			$("<option>").text(datalist[i]).val(datalist[i]).appendTo(imgdrop);
	}
	// machine type
	var machdrop = $("#smach");
	$("<option>").text("64k  1.2").val("64k 1.2").appendTo(machdrop);
	$("<option>").text("64k+ 1.2").val("64k+ 1.2").appendTo(machdrop);
	$("<option>").text("64k+ 2.2").val("64k+ 2.2").appendTo(machdrop);
	$("<option>").text("64k 1.2, VT-DOS").val("64k 1.2, VT-DOS").appendTo(machdrop);
	machdrop.on("change", function(e) {
		var machType = machdrop[0].value;
		emuCreate(machType);
	});
	// keyboard
	$(document).keydown(handleKeyDown);
	$(document).keyup(handleKeyUp);
	// focus on canvas
	$(window).focus(handleFocus);
	$(window).blur(handleFocusLost);
	// disable selection
	g.canvas.on("selectstart", function(e) { e.preventDefault(); return false; });
}

// event handlers
function handleKeyDown(e) {
	if (g.tvc) {
		g.tvc.keyDown(e.which);
		e.preventDefault();
	}
}
function handleKeyUp(e) {
	if (g.tvc) {
		g.tvc.keyUp(e.which);
	}
}
function handleFocus(e) {
	if (g.tvc)
		g.tvc.focusChange(true);
}
function handleFocusLost(e) {
	if (g.tvc)
		g.tvc.focusChange(false);
}

// emulator functions
function emuContinue() {
	g.requestAnimationFrame(emuRunFrame);
}
function emuRunFrame() {
	if (!g.isRunning) {
		return;
	}
	g.fb.skipcnt++;
	var skip = (g.fb.fpsv >= 45) && (g.fb.skipcnt == 8);
	if (g.fb.skipcnt >= 8) g.fb.skipcnt = 0;

	if (!skip && g.tvc.runForAFrame()) {
		g.isRunning = false;
		$("#bstop").text("continue");
	}
	else {
		emuContinue();
	}
}

<|endoftext|>


<commit_before>var styleUtils = { // TODO: Manifest! (images too)
     imageBase: "img/",
     imageExt: ".jpg",
     trackLookup: { // might be a better way to do this; also the icons are from 2016 and don't fully match
          "Cloud and Big Data" : "track_enterprise-java-cloud",
          "Core Java" : "track_jvm-languages",
          "Enterprise Java" : "track_enterprise-java-cloud",
          "Frontend" : "track_frontend-mobile",
          "IDEs and tools" : "track_ide-tools",
          "Internet of things" : "track_internet-of-things",
          "JVM languages" : "track_jvm-languages",
          "Mobile" : "track_frontend-mobile",
          "Security" : "track_newcomer",
          "Software architecture and methods" : "track_architecture"
     },

     getTrackCssStyle: function(track) {
          return (trackLookup[track] ? trackLookup[track] : "";
     },

     getTackIcon: function(track) {
          if (trackLookup[track]) {
               return imageBase + trackLookup[track] + imageBase;
          } else {
               return imageBase + "track_newcomer.jpg";
          }
     },

     getLanguageStyle: function(language) {
          return language.toLowerCase();
     }
};<commit_msg>Delete unused file<commit_after><|endoftext|>