<commit_before>// vim: noexpandtab

// The project page
function ProjPage(team_selector) {
	// Whether _init has run
	this._initted = false;

	// The signals that we've set up for this object
	// signals that are for DOM objects that weren't created in this instance
	this._connections = [];

	// The project list -- a ProjList instance
	this._list = null;
	// The project selector -- a ProjSelect instance
	this._selector = null;
	// The calendar
	this._calendar = null;

	this._iframe = null;

	this._poll = null;

	// The selection operations
	this.selection_operations = null;

	//  The Projects tab
	this._tab = null;

	this.flist = null;
	this.project = "";

	// records the current revision of the project we're looking at
	this._projectRev = "";

	this.last_updated = new Date();

	this._read_only = false;

	// listen for changes in the selected team
	connect( team_selector, "onchange", bind(this.set_team, this) );

	// Member functions (declared below)
	// Public:
	//  - show: Show and activate the projects page
	//  - hide: Hide the project page
	//  - hide_filelist: Hide the file list
	//  - projects_exist: check that one, or more, projects exist
	// Private:
	//  - _init: Initialises members of the project page
	//  - _rpane_show: Show the right-hand pane
	//  - _rpane_hide: Hide the right-hand pane
	//  - _on_proj_change: Handler for when the selected project changes
	//                     Hides/shows the right-hand pane as necessary
}

// Initialise the project page -- but don't show it
ProjPage.prototype._init = function() {
	if (this._initted) {
		return;
	}

	// Hide the right-hand whilst we're loading
	this._rpane_hide();

	// The list of projects
	this._list = new ProjList();

	// The selection box for selecting a project
	this._selector = new ProjSelect(this._list, getElement("project-select"));
	connect( this._selector, "onchange", bind( this._on_proj_change, this ) );

	// Selection operations
	this.selection_operations = new ProjOps();

	this.flist = new ProjFileList();
	// Update the file list when the project changes
	connect( this._selector, "onchange", bind( this.flist.update, this.flist ) );

	this._calendar = new Calendar();
	// Refresh the calendar when the project changes
	connect(this._selector, "onchange", bind( this._calendar.change_proj, this._calendar ) );

	var searchpage = SearchPage.GetInstance();
	var proj_searcher = new ProjectNameSearchProvider(this, this._selector);
	searchpage.add_provider(proj_searcher);

	var file_searcher = new FileNameSearchProvider(this, this._selector);
	searchpage.add_provider(file_searcher);

	// Connect up the project management buttons
	connect("new-project",		'onclick', bind(this.clickNewProject, this));
//	Archive doesn't do anything yet!
//	connect("archive-project",	'onclick', bind(this.clickArchiveProject, this));
	connect("copy-project",		'onclick', bind(this.clickCopyProject, this));
	connect("check-code",		'onclick', bind(this.clickCheckCode, this));
	connect("export-project",	'onclick', bind(this.clickExportProject, this));

	// We have to synthesize the first "onchange" event from the ProjSelect,
	// as these things weren't connected to it when it happened
	this._on_proj_change( this._selector.project );
	this.flist.update( this._selector.project, this._selector._team );

	this._createTab();

	this._setupPolling();

	this._initted = true;

	// This triggers the tab to be shown, our hanlder for which (correctly)
	// verifies that we're inited, so we need to do this after setting the flag.
	tabbar.switch_to( this._tab );
};

ProjPage.prototype._createTab = function() {
	// Projects tab
	this._tab = new Tab( "Projects", { can_close: false } );
	connect( this._tab, "onfocus", bind( this.show, this ) );
	connect( this._tab, "onblur", bind( this.hide, this ) );
	tabbar.add_tab( this._tab );
};

ProjPage.prototype.has_focus = function() {
	return this._tab != null && this._tab.has_focus();
};

ProjPage.prototype.switch_to = function() {
	this._init();
	// only actually needed if we're not inited, but it's simpler to just call both
	tabbar.switch_to(this._tab);
};

ProjPage.prototype._setupPolling = function() {
	if (this._poll != null) {
		this._poll.cancel();
	}
	// 30 second delay, retry once.
	this._poll = new Poll('poll/poll', { team: team }, 30, 1);
	connect(this._poll, 'onchange', bind(this._pollChanged, this));
};

ProjPage.prototype._pollChanged = function(nodes) {
	logDebug('projpage: Poll changed');

	var projects = keys(nodes.projects);
	if (projects.length != this._list.projects.length) {
		// TODO: don't assume that this is a good enough check.
		this._list.projects = projects;
		signal(this._list, 'onchange', team);
	}

	if (IDE_string_empty(this.project)) {
		return;
	}

	var nodesRev = nodes.projects[this.project];
	// The project we're looking at has been changed on the server
	if (nodesRev != this._projectRev) {
		this._projectRev = nodesRev;
		// Update the calendar
		this._calendar.getDates();
		this.flist.mark_dirty();
	}
};

ProjPage.prototype.show = function() {
	logDebug( "Projpage.show: Current project is \"" + this.project + "\"" );
	this._init();

	showElement('projects-page');
};

ProjPage.prototype.hide = function() {
	logDebug( "Hiding the projects page" );
	hideElement('projects-page');
};

ProjPage.prototype.hide_filelist = function() {
	logDebug( "Hiding the file list" );
	this.flist._hide();
};

ProjPage.prototype.project_readonly = function(pname) {
	return this._read_only;
};

ProjPage.prototype.project_exists = function(pname) {
	return this._list != null && this._list.project_exists(pname);
};

ProjPage.prototype.projects_exist = function() {
	return (this.list_projects().length > 0);
};

ProjPage.prototype.list_projects = function() {
	if (this._list == null || this._list.projects == null) {
		return [];
	}
	return this._list.projects;
};

ProjPage.prototype._got_proj_info = function(nodes) {
	getElement('proj-info').innerHTML = nodes.repoUrl;
};

ProjPage.prototype._on_proj_change = function(proj, team) {
	logDebug( "ProjPage._on_proj_change(\"" + proj + "\", " + team + ")" );
	this.project = proj;

	if (proj == "") {
		this._rpane_hide();
	} else {
		IDE_backend_request('proj/info',
		                    { team: team, project: proj },
		                    bind(this._got_proj_info, this),
		                    function() {}
		                   );
		getElement('proj-info').innerHTML = '';
		getElement("proj-name").innerHTML = "Project " + this.project;
		this._rpane_show();
	}
};

ProjPage.prototype.set_team = function(team) {
	logDebug( "ProjPage.set_team( " + team + " )" );
	if (team == null || team == 0) {
		return;
	}

	this._rpane_hide();
	this._init();
	// reset polling for the new team
	this._setupPolling();

	// Start the chain of updates
	this._list.update(team);
	// The selector and filelist are connected to onchange on the list,
	// so they will update when it's updated

	var teamInfo = user.get_team(team);
	this._set_readonly(teamInfo.readOnly == true);
};

ProjPage.prototype._set_readonly = function(isReadOnly) {
	if (this._read_only != isReadOnly) {
		getElement('new-project').disabled = isReadOnly;
		getElement('copy-project').disabled = isReadOnly;
		this.selection_operations.set_readonly(isReadOnly);
	}
	this._read_only = isReadOnly;
	setReadOnly('projects-page', isReadOnly);
};

// ***** Project Page Right Hand pane *****
ProjPage.prototype._rpane_hide = function() {
	hideElement('proj-rpane');
};

ProjPage.prototype._rpane_show = function() {
	showElement('proj-rpane');
};

ProjPage.prototype.clickArchiveProject = function() {
	return;
};

ProjPage.prototype.clickCopyProject = function() {
	if (this.project == null || this.project == "") {
		status_msg( "Please select a project to copy", LEVEL_INFO );
	} else {
		var cb = bind(this.CreateCopyProject, this);
		var args = {'type' : 'isProj', 'title' : 'Copy Project'};
		var b = new Browser(cb, args);
	}
};

ProjPage.prototype.CreateCopyProject = function(newProjName) {
	log('Copying project ' + this.project + ' to ' + newProjName);

	var args = { "team": team,
	          "project": this.project,
	         "new-name": newProjName
	           };

	var failback = function() {
		status_button( "Copy Project: Error contacting server", LEVEL_ERROR, "retry",
		               bind(this.CreateCopyProject, this, newProjName) );
	};
	IDE_backend_request("proj/copy", args,
	                    bind( partial(this._CopyProjectSuccess, newProjName), this ),
	                    bind( failback, this ));
};

ProjPage.prototype._CopyProjectSuccess = function(newProjName, nodes) {
	if (nodes.status > 0) {
		status_msg("ERROR COPYING: "+nodes.message, LEVEL_ERROR);
	} else {
		status_msg("Project copy successful", LEVEL_OK);
		if (this.has_focus()) {
			log('Project Copied, need to update the list...');
			// Transition to the new project once the project list has loaded
			this._selector.trans_project = newProjName;
			this._list.update(team);
		}
	}
};

ProjPage.prototype.clickNewProject = function() {
	var b = new Browser(bind(this.CreateNewProject, this), {'type' : 'isProj'});
};

ProjPage.prototype.CreateNewProject = function(newProjName) {
	/* Postback to create a new project - then what? */

	IDE_backend_request("proj/new", {team: team, project: newProjName},
		bind(this._createProjectSuccess, this, newProjName),
		bind(this._createProjectFailure, this, newProjName)
	);
};

ProjPage.prototype._createProjectSuccess = function(newProjName) {
	status_msg("Created project successfully", LEVEL_OK);
	// TODO: what exactly is being updated here?
	update(team);
	// Transition to the new project once the project list has loaded
	this._selector.trans_project = newProjName;
	this._list.update(team);
};

ProjPage.prototype._createProjectFailure = function(newProjName) {
	/* XXX - check for preexisting projects perhaps */
	status_button('Error creating project', LEVEL_ERROR, 'retry',
		bind(this.CreateNewProject, this, newProjName)
	);
};

ProjPage.prototype.clickExportProject = function() {
	var tmpItem = getElement('projlist-tmpitem');
	if (tmpItem != null && tmpItem.selected == true) {
		status_msg( "No project selected, please select a project", LEVEL_ERROR );
		return;
	}

	if (!this.flist.robot) {	//if there's no robot.py script then it's going to fail
		status_msg( "A robot.py file is required for project code export", LEVEL_ERROR );
		return false;
	}

	var options = {'switch_to':true, 'alert':false, 'quietpass':true, 'callback':bind(this._exportProjectCheckResult, this)};
	errorspage.check("/"+this.project+"/robot.py", options, false, this.flist.rev);
};

ProjPage.prototype._exportProjectCheckResult = function(result, num_errors) {
	if (result == 'pass') {
		this._exportProject();
		return;
	}

	var message;
	var exportAnyway = {text:"export anyway", callback:bind( this._exportProject, this )};

	// check has failed
	if (result == 'checkfail') {
		message = "Failed to check code";
		var retry = {text:"retry", callback:bind( this.clickExportProject, this )};
		status_options(message, LEVEL_WARN, [retry, exportAnyway]);
	}

	// user's code has failed
	if (result == 'codefail') {
		message = num_errors + " errors found!";
		status_button(message, LEVEL_WARN, exportAnyway);
	}

	log('_exportProjectCheckResult:'+message);
};

ProjPage.prototype._exportProject = function() {
	var fail_retry = bind(function() {
		status_button('Error exporting project', LEVEL_ERROR, 'retry',
			bind(this._exportProject, this)
		);
	}, this);
	Checkout.GetInstance().checkout(team, this.project, this.flist.rev, function() {}, fail_retry);
};

ProjPage.prototype.clickCheckCode = function() {
	if (this.project == null || this.project == "") {	//if there's no project selected what's the point
		status_msg( "Please select a project for code checking", LEVEL_INFO );
		return false;
	}

	if (!this.flist.robot) {	//if there's no robot.py script then it's going to fail
		status_msg( "A robot.py file is required for project code checking", LEVEL_ERROR );
		return false;
	}

	errorspage.check("/"+this.project+"/robot.py", {'switch_to':true}, false, this.flist.rev);
};
<commit_msg>Remove redundant call to wrong function.<commit_after>// vim: noexpandtab

// The project page
function ProjPage(team_selector) {
	// Whether _init has run
	this._initted = false;

	// The signals that we've set up for this object
	// signals that are for DOM objects that weren't created in this instance
	this._connections = [];

	// The project list -- a ProjList instance
	this._list = null;
	// The project selector -- a ProjSelect instance
	this._selector = null;
	// The calendar
	this._calendar = null;

	this._iframe = null;

	this._poll = null;

	// The selection operations
	this.selection_operations = null;

	//  The Projects tab
	this._tab = null;

	this.flist = null;
	this.project = "";

	// records the current revision of the project we're looking at
	this._projectRev = "";

	this.last_updated = new Date();

	this._read_only = false;

	// listen for changes in the selected team
	connect( team_selector, "onchange", bind(this.set_team, this) );

	// Member functions (declared below)
	// Public:
	//  - show: Show and activate the projects page
	//  - hide: Hide the project page
	//  - hide_filelist: Hide the file list
	//  - projects_exist: check that one, or more, projects exist
	// Private:
	//  - _init: Initialises members of the project page
	//  - _rpane_show: Show the right-hand pane
	//  - _rpane_hide: Hide the right-hand pane
	//  - _on_proj_change: Handler for when the selected project changes
	//                     Hides/shows the right-hand pane as necessary
}

// Initialise the project page -- but don't show it
ProjPage.prototype._init = function() {
	if (this._initted) {
		return;
	}

	// Hide the right-hand whilst we're loading
	this._rpane_hide();

	// The list of projects
	this._list = new ProjList();

	// The selection box for selecting a project
	this._selector = new ProjSelect(this._list, getElement("project-select"));
	connect( this._selector, "onchange", bind( this._on_proj_change, this ) );

	// Selection operations
	this.selection_operations = new ProjOps();

	this.flist = new ProjFileList();
	// Update the file list when the project changes
	connect( this._selector, "onchange", bind( this.flist.update, this.flist ) );

	this._calendar = new Calendar();
	// Refresh the calendar when the project changes
	connect(this._selector, "onchange", bind( this._calendar.change_proj, this._calendar ) );

	var searchpage = SearchPage.GetInstance();
	var proj_searcher = new ProjectNameSearchProvider(this, this._selector);
	searchpage.add_provider(proj_searcher);

	var file_searcher = new FileNameSearchProvider(this, this._selector);
	searchpage.add_provider(file_searcher);

	// Connect up the project management buttons
	connect("new-project",		'onclick', bind(this.clickNewProject, this));
//	Archive doesn't do anything yet!
//	connect("archive-project",	'onclick', bind(this.clickArchiveProject, this));
	connect("copy-project",		'onclick', bind(this.clickCopyProject, this));
	connect("check-code",		'onclick', bind(this.clickCheckCode, this));
	connect("export-project",	'onclick', bind(this.clickExportProject, this));

	// We have to synthesize the first "onchange" event from the ProjSelect,
	// as these things weren't connected to it when it happened
	this._on_proj_change( this._selector.project );
	this.flist.update( this._selector.project, this._selector._team );

	this._createTab();

	this._setupPolling();

	this._initted = true;

	// This triggers the tab to be shown, our hanlder for which (correctly)
	// verifies that we're inited, so we need to do this after setting the flag.
	tabbar.switch_to( this._tab );
};

ProjPage.prototype._createTab = function() {
	// Projects tab
	this._tab = new Tab( "Projects", { can_close: false } );
	connect( this._tab, "onfocus", bind( this.show, this ) );
	connect( this._tab, "onblur", bind( this.hide, this ) );
	tabbar.add_tab( this._tab );
};

ProjPage.prototype.has_focus = function() {
	return this._tab != null && this._tab.has_focus();
};

ProjPage.prototype.switch_to = function() {
	this._init();
	// only actually needed if we're not inited, but it's simpler to just call both
	tabbar.switch_to(this._tab);
};

ProjPage.prototype._setupPolling = function() {
	if (this._poll != null) {
		this._poll.cancel();
	}
	// 30 second delay, retry once.
	this._poll = new Poll('poll/poll', { team: team }, 30, 1);
	connect(this._poll, 'onchange', bind(this._pollChanged, this));
};

ProjPage.prototype._pollChanged = function(nodes) {
	logDebug('projpage: Poll changed');

	var projects = keys(nodes.projects);
	if (projects.length != this._list.projects.length) {
		// TODO: don't assume that this is a good enough check.
		this._list.projects = projects;
		signal(this._list, 'onchange', team);
	}

	if (IDE_string_empty(this.project)) {
		return;
	}

	var nodesRev = nodes.projects[this.project];
	// The project we're looking at has been changed on the server
	if (nodesRev != this._projectRev) {
		this._projectRev = nodesRev;
		// Update the calendar
		this._calendar.getDates();
		this.flist.mark_dirty();
	}
};

ProjPage.prototype.show = function() {
	logDebug( "Projpage.show: Current project is \"" + this.project + "\"" );
	this._init();

	showElement('projects-page');
};

ProjPage.prototype.hide = function() {
	logDebug( "Hiding the projects page" );
	hideElement('projects-page');
};

ProjPage.prototype.hide_filelist = function() {
	logDebug( "Hiding the file list" );
	this.flist._hide();
};

ProjPage.prototype.project_readonly = function(pname) {
	return this._read_only;
};

ProjPage.prototype.project_exists = function(pname) {
	return this._list != null && this._list.project_exists(pname);
};

ProjPage.prototype.projects_exist = function() {
	return (this.list_projects().length > 0);
};

ProjPage.prototype.list_projects = function() {
	if (this._list == null || this._list.projects == null) {
		return [];
	}
	return this._list.projects;
};

ProjPage.prototype._got_proj_info = function(nodes) {
	getElement('proj-info').innerHTML = nodes.repoUrl;
};

ProjPage.prototype._on_proj_change = function(proj, team) {
	logDebug( "ProjPage._on_proj_change(\"" + proj + "\", " + team + ")" );
	this.project = proj;

	if (proj == "") {
		this._rpane_hide();
	} else {
		IDE_backend_request('proj/info',
		                    { team: team, project: proj },
		                    bind(this._got_proj_info, this),
		                    function() {}
		                   );
		getElement('proj-info').innerHTML = '';
		getElement("proj-name").innerHTML = "Project " + this.project;
		this._rpane_show();
	}
};

ProjPage.prototype.set_team = function(team) {
	logDebug( "ProjPage.set_team( " + team + " )" );
	if (team == null || team == 0) {
		return;
	}

	this._rpane_hide();
	this._init();
	// reset polling for the new team
	this._setupPolling();

	// Start the chain of updates
	this._list.update(team);
	// The selector and filelist are connected to onchange on the list,
	// so they will update when it's updated

	var teamInfo = user.get_team(team);
	this._set_readonly(teamInfo.readOnly == true);
};

ProjPage.prototype._set_readonly = function(isReadOnly) {
	if (this._read_only != isReadOnly) {
		getElement('new-project').disabled = isReadOnly;
		getElement('copy-project').disabled = isReadOnly;
		this.selection_operations.set_readonly(isReadOnly);
	}
	this._read_only = isReadOnly;
	setReadOnly('projects-page', isReadOnly);
};

// ***** Project Page Right Hand pane *****
ProjPage.prototype._rpane_hide = function() {
	hideElement('proj-rpane');
};

ProjPage.prototype._rpane_show = function() {
	showElement('proj-rpane');
};

ProjPage.prototype.clickArchiveProject = function() {
	return;
};

ProjPage.prototype.clickCopyProject = function() {
	if (this.project == null || this.project == "") {
		status_msg( "Please select a project to copy", LEVEL_INFO );
	} else {
		var cb = bind(this.CreateCopyProject, this);
		var args = {'type' : 'isProj', 'title' : 'Copy Project'};
		var b = new Browser(cb, args);
	}
};

ProjPage.prototype.CreateCopyProject = function(newProjName) {
	log('Copying project ' + this.project + ' to ' + newProjName);

	var args = { "team": team,
	          "project": this.project,
	         "new-name": newProjName
	           };

	var failback = function() {
		status_button( "Copy Project: Error contacting server", LEVEL_ERROR, "retry",
		               bind(this.CreateCopyProject, this, newProjName) );
	};
	IDE_backend_request("proj/copy", args,
	                    bind( partial(this._CopyProjectSuccess, newProjName), this ),
	                    bind( failback, this ));
};

ProjPage.prototype._CopyProjectSuccess = function(newProjName, nodes) {
	if (nodes.status > 0) {
		status_msg("ERROR COPYING: "+nodes.message, LEVEL_ERROR);
	} else {
		status_msg("Project copy successful", LEVEL_OK);
		if (this.has_focus()) {
			log('Project Copied, need to update the list...');
			// Transition to the new project once the project list has loaded
			this._selector.trans_project = newProjName;
			this._list.update(team);
		}
	}
};

ProjPage.prototype.clickNewProject = function() {
	var b = new Browser(bind(this.CreateNewProject, this), {'type' : 'isProj'});
};

ProjPage.prototype.CreateNewProject = function(newProjName) {
	/* Postback to create a new project - then what? */

	IDE_backend_request("proj/new", {team: team, project: newProjName},
		bind(this._createProjectSuccess, this, newProjName),
		bind(this._createProjectFailure, this, newProjName)
	);
};

ProjPage.prototype._createProjectSuccess = function(newProjName) {
	status_msg("Created project successfully", LEVEL_OK);

	// Transition to the new project once the project list has loaded
	this._selector.trans_project = newProjName;
	this._list.update(team);
};

ProjPage.prototype._createProjectFailure = function(newProjName) {
	/* XXX - check for preexisting projects perhaps */
	status_button('Error creating project', LEVEL_ERROR, 'retry',
		bind(this.CreateNewProject, this, newProjName)
	);
};

ProjPage.prototype.clickExportProject = function() {
	var tmpItem = getElement('projlist-tmpitem');
	if (tmpItem != null && tmpItem.selected == true) {
		status_msg( "No project selected, please select a project", LEVEL_ERROR );
		return;
	}

	if (!this.flist.robot) {	//if there's no robot.py script then it's going to fail
		status_msg( "A robot.py file is required for project code export", LEVEL_ERROR );
		return false;
	}

	var options = {'switch_to':true, 'alert':false, 'quietpass':true, 'callback':bind(this._exportProjectCheckResult, this)};
	errorspage.check("/"+this.project+"/robot.py", options, false, this.flist.rev);
};

ProjPage.prototype._exportProjectCheckResult = function(result, num_errors) {
	if (result == 'pass') {
		this._exportProject();
		return;
	}

	var message;
	var exportAnyway = {text:"export anyway", callback:bind( this._exportProject, this )};

	// check has failed
	if (result == 'checkfail') {
		message = "Failed to check code";
		var retry = {text:"retry", callback:bind( this.clickExportProject, this )};
		status_options(message, LEVEL_WARN, [retry, exportAnyway]);
	}

	// user's code has failed
	if (result == 'codefail') {
		message = num_errors + " errors found!";
		status_button(message, LEVEL_WARN, exportAnyway);
	}

	log('_exportProjectCheckResult:'+message);
};

ProjPage.prototype._exportProject = function() {
	var fail_retry = bind(function() {
		status_button('Error exporting project', LEVEL_ERROR, 'retry',
			bind(this._exportProject, this)
		);
	}, this);
	Checkout.GetInstance().checkout(team, this.project, this.flist.rev, function() {}, fail_retry);
};

ProjPage.prototype.clickCheckCode = function() {
	if (this.project == null || this.project == "") {	//if there's no project selected what's the point
		status_msg( "Please select a project for code checking", LEVEL_INFO );
		return false;
	}

	if (!this.flist.robot) {	//if there's no robot.py script then it's going to fail
		status_msg( "A robot.py file is required for project code checking", LEVEL_ERROR );
		return false;
	}

	errorspage.check("/"+this.project+"/robot.py", {'switch_to':true}, false, this.flist.rev);
};
<|endoftext|>


<commit_before>(function ($) {
  $.extend(true, window, {
    Slick: {
      Data: {
        GroupItemMetadataProvider: GroupItemMetadataProvider
      }
    }
  });

  /***
   * Provides item metadata for group (Slick.Group) and totals (Slick.Totals) rows produced by the DataView.
   * This metadata overrides the default behavior and formatting of those rows so that they appear and function
   * correctly when processed by the grid.
   *
   * This class also acts as a grid plugin providing event handlers to expand & collapse groups.
   * If "grid.registerPlugin(...)" is not called, expand & collapse will not work.
   *
   * @class GroupItemMetadataProvider
   * @module Data
   * @namespace Slick.Data
   * @constructor
   * @param options
   */
  function GroupItemMetadataProvider(options) {
    var _grid;
    var _defaults = {
      groupCssClass: "slick-group",
      groupTitleCssClass: "slick-group-title",
      totalsCssClass: "slick-group-totals",
      groupFocusable: true,
      totalsFocusable: false,
      toggleCssClass: "slick-group-toggle",
      toggleExpandedCssClass: "expanded",
      toggleCollapsedCssClass: "collapsed",
      enableExpandCollapse: true,
      groupFormatter: defaultGroupCellFormatter,
      totalsFormatter: defaultTotalsCellFormatter
    };

    options = $.extend(true, {}, _defaults, options);

    function defaultGroupCellFormatter(row, cell, value, columnDef, item) {
      if (!options.enableExpandCollapse) {
        return item.title;
      }

      var indentation = item.level * 15 + "px";

      return "" +
          "" +
          "" +
            item.title +
          "";
    }

    function defaultTotalsCellFormatter(row, cell, value, columnDef, item) {
      return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
    }

    function init(grid) {
      _grid = grid;
      _grid.onClick.subscribe(handleGridClick);
      _grid.onKeyDown.subscribe(handleGridKeyDown);

    }

    function destroy() {
      if (_grid) {
        _grid.onClick.unsubscribe(handleGridClick);
        _grid.onKeyDown.unsubscribe(handleGridKeyDown);
      }
    }

    function handleGridClick(e, args) {
      var item = this.getDataItem(args.row);
      if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass)) {
        var range = _grid.getRenderedRange();
        this.getData().setRefreshHints({
          ignoreDiffsBefore: range.top,
          ignoreDiffsAfter: range.bottom
        });

        if (item.collapsed) {
          this.getData().expandGroup(item.groupingKey);
        } else {
          this.getData().collapseGroup(item.groupingKey);
        }

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

    // TODO:  add -/+ handling
    function handleGridKeyDown(e, args) {
      if (options.enableExpandCollapse && (e.which == $.ui.keyCode.SPACE)) {
        var activeCell = this.getActiveCell();
        if (activeCell) {
          var item = this.getDataItem(activeCell.row);
          if (item && item instanceof Slick.Group) {
            var range = _grid.getRenderedRange();
            this.getData().setRefreshHints({
              ignoreDiffsBefore: range.top,
              ignoreDiffsAfter: range.bottom
            });

            if (item.collapsed) {
              this.getData().expandGroup(item.groupingKey);
            } else {
              this.getData().collapseGroup(item.groupingKey);
            }

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

    function getGroupRowMetadata(item) {
      return {
        selectable: false,
        focusable: options.groupFocusable,
        cssClasses: options.groupCssClass,
        columns: {
          0: {
            colspan: "*",
            formatter: options.groupFormatter,
            editor: null
          }
        }
      };
    }

    function getTotalsRowMetadata(item) {
      return {
        selectable: false,
        focusable: options.totalsFocusable,
        cssClasses: options.totalsCssClass,
        formatter: options.totalsFormatter,
        editor: null
      };
    }

    return {
      "init": init,
      "destroy": destroy,
      "getGroupRowMetadata": getGroupRowMetadata,
      "getTotalsRowMetadata": getTotalsRowMetadata
    };
  }
})(jQuery);
<commit_msg>add +1 to range.bottom for ignoreDiffsAfter<commit_after>(function ($) {
  $.extend(true, window, {
    Slick: {
      Data: {
        GroupItemMetadataProvider: GroupItemMetadataProvider
      }
    }
  });

  /***
   * Provides item metadata for group (Slick.Group) and totals (Slick.Totals) rows produced by the DataView.
   * This metadata overrides the default behavior and formatting of those rows so that they appear and function
   * correctly when processed by the grid.
   *
   * This class also acts as a grid plugin providing event handlers to expand & collapse groups.
   * If "grid.registerPlugin(...)" is not called, expand & collapse will not work.
   *
   * @class GroupItemMetadataProvider
   * @module Data
   * @namespace Slick.Data
   * @constructor
   * @param options
   */
  function GroupItemMetadataProvider(options) {
    var _grid;
    var _defaults = {
      groupCssClass: "slick-group",
      groupTitleCssClass: "slick-group-title",
      totalsCssClass: "slick-group-totals",
      groupFocusable: true,
      totalsFocusable: false,
      toggleCssClass: "slick-group-toggle",
      toggleExpandedCssClass: "expanded",
      toggleCollapsedCssClass: "collapsed",
      enableExpandCollapse: true,
      groupFormatter: defaultGroupCellFormatter,
      totalsFormatter: defaultTotalsCellFormatter
    };

    options = $.extend(true, {}, _defaults, options);

    function defaultGroupCellFormatter(row, cell, value, columnDef, item) {
      if (!options.enableExpandCollapse) {
        return item.title;
      }

      var indentation = item.level * 15 + "px";

      return "" +
          "" +
          "" +
            item.title +
          "";
    }

    function defaultTotalsCellFormatter(row, cell, value, columnDef, item) {
      return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
    }

    function init(grid) {
      _grid = grid;
      _grid.onClick.subscribe(handleGridClick);
      _grid.onKeyDown.subscribe(handleGridKeyDown);

    }

    function destroy() {
      if (_grid) {
        _grid.onClick.unsubscribe(handleGridClick);
        _grid.onKeyDown.unsubscribe(handleGridKeyDown);
      }
    }

    function handleGridClick(e, args) {
      var item = this.getDataItem(args.row);
      if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass)) {
        var range = _grid.getRenderedRange();
        this.getData().setRefreshHints({
          ignoreDiffsBefore: range.top,
          ignoreDiffsAfter: range.bottom + 1
        }); //take diff till range.bottom , and ignore it after.

        if (item.collapsed) {
          this.getData().expandGroup(item.groupingKey);
        } else {
          this.getData().collapseGroup(item.groupingKey);
        }

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

    // TODO:  add -/+ handling
    function handleGridKeyDown(e, args) {
      if (options.enableExpandCollapse && (e.which == $.ui.keyCode.SPACE)) {
        var activeCell = this.getActiveCell();
        if (activeCell) {
          var item = this.getDataItem(activeCell.row);
          if (item && item instanceof Slick.Group) {
            var range = _grid.getRenderedRange();
            this.getData().setRefreshHints({
              ignoreDiffsBefore: range.top,
              ignoreDiffsAfter: range.bottom + 1
            }); //take diff till range.bottom , and ignore it after.

            if (item.collapsed) {
              this.getData().expandGroup(item.groupingKey);
            } else {
              this.getData().collapseGroup(item.groupingKey);
            }

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

    function getGroupRowMetadata(item) {
      return {
        selectable: false,
        focusable: options.groupFocusable,
        cssClasses: options.groupCssClass,
        columns: {
          0: {
            colspan: "*",
            formatter: options.groupFormatter,
            editor: null
          }
        }
      };
    }

    function getTotalsRowMetadata(item) {
      return {
        selectable: false,
        focusable: options.totalsFocusable,
        cssClasses: options.totalsCssClass,
        formatter: options.totalsFormatter,
        editor: null
      };
    }

    return {
      "init": init,
      "destroy": destroy,
      "getGroupRowMetadata": getGroupRowMetadata,
      "getTotalsRowMetadata": getTotalsRowMetadata
    };
  }
})(jQuery);
<|endoftext|>


<commit_before>var
	kind = require('/kind');

var
	Control = require('./Control');

/** @lends enyo.ViewPreloadSupport.prototype */
module.exports = {

	/**
	* @method
	* @private
	*/
	name: 'enyo.ViewPreloadSupport',

	/**
	* @private
	*/
	published: {

		/**
		* When `true`, existing views are cached for reuse, otherwise they are destroyed.
		*
		* @type {Boolean}
		* @default true
		* @public
		*/
		cacheViews: true
	},

	/**
	* @method
	* @private
	*/
	create: kind.inherit(function (sup) {
		return function () {
			sup.apply(this, arguments);

			if (this.cacheViews) {
				this.createChrome([
					{name: 'viewCache', kind: Control, canGenerate: false}
				]);
				this.removeChild(this.$.viewCache);
				this._cachedViews = {};
			}
		};
	}),

	/*
		===================================
		Interface methods to be implemented
		===================================
	*/

	/**
	* Determines the id of the given view. This is something that should generally be
	* implemented in the actual kind as this will vary from case to case.
	*
	* @param {Object} view - The view whose id we will determine.
	* @return {String} The id of the given view.
	* @public
	*/
	getViewId: kind.inherit(function (sup) {
		return function (view) {
			return sup.apply(this, arguments);
		};
	}),

	/**
	* Reset the state of the given view. This is something that should generally be implemented
	* in the actual kind.
	*
	* @param {Object} view - The view whose state we wish to reset.
	* @private
	*/
	resetView: kind.inherit(function (sup) {
		return function (view) {
			return sup.apply(this, arguments);
		};
	}),

	/*
		==============
		Public methods
		==============
	*/

	/**
	* Caches a given view so that it can be quickly instantiated and utilized at a later point.
	* This is typically performed on a view which has already been rendered and which no longer
	* needs to remain in view, but may be revisited at a later time.
	*
	* @param {Object} view - The view to cache for later use.
	* @param {Boolean} preserve - If `true`, preserves the view's position both on the screen
	*	and within the component hierarchy.
	* @public
	*/
	cacheView: function (view, preserve) {
		var id = this.getViewId(view);

		// The panel could have already been removed from DOM and torn down if we popped when
		// moving forward.
		if (view.node) {
			view.node.remove();
			view.teardownRender(true);
		}

		if (!preserve) {
			this.resetView(view);

			this.removeControl(view);
			this.$.viewCache.addControl(view);
		}

		this._cachedViews[id] = view;
	},

	/**
	* Restores the specified view that was previously cached.
	*
	* @param {String} id - The unique identifier for the cached view that is being restored.
	* @return {Object} The restored view.
	* @public
	*/
	restoreView: function (id) {
		var cp = this._cachedViews,
			view = cp[id];

		if (view) {
			this.$.viewCache.removeControl(view);
			this.addControl(view);

			this._cachedViews[id] = null;
		}

		return view;
	},

	/**
	* Pre-caches a set of views by creating and caching them, even if they have not yet been
	* rendered into view. This is helpful for reducing the instantiation time for views which
	* have yet to be shown, but can and eventually will be shown to the user.
	*
	* @param {Object} info - The declarative {@glossary kind} definition.
	* @param {Object} commonInfo - Additional properties to be applied (defaults).
	* @param {Boolean} [cbComplete] - If specified, this callback will be executed, with the
	*	created view being passed as a parameter.
	* @public
	*/
	preCacheViews: function(info, commonInfo, cbComplete) {
		var vc, views, i, view;

		if (this.cacheViews) {
			vc = this.$.viewCache;
			commonInfo = commonInfo || {};
			commonInfo.owner = this;
			views = vc.createComponents(info, commonInfo);
			for (i = 0; i < views.length; i++) {
				view = views[i];
				this._cachedViews[this.getViewId(view)] = view;
				if (cbComplete) {
					cbComplete.call(this, view);
				}
			}
		}
	},

	/**
	* Pre-caches a single view by creating and caching the view, even if it has not yet been
	* rendered into view. This is helpful for reducing the instantiation time for panels which
	* have yet to be shown, but can and eventually will be shown to the user.
	*
	* @param {Object} info - The declarative {@glossary kind} definition.
	* @param {Object} commonInfo - Additional properties to be applied (defaults).
	* @param {Function} [cbComplete] - If specified, this callback will be executed, with the
	*	created view being passed as a parameter.
	* @public
	*/
	preCacheView: function(info, commonInfo, cbComplete) {
		var vc, view;

		if (this.cacheViews && !this._cachedViews[info.kind]) {
			vc = this.$.viewCache;
			commonInfo = commonInfo || {};
			commonInfo.owner = this;
			view = vc.createComponent(info, commonInfo);
			this._cachedViews[this.getViewId(info)] = view;
			if (cbComplete) {
				cbComplete.call(this, view);
			}
		}
	}

};<commit_msg>fix missing ref path<commit_after>var
	kind = require('./kind');

var
	Control = require('./Control');

/** @lends enyo.ViewPreloadSupport.prototype */
module.exports = {

	/**
	* @method
	* @private
	*/
	name: 'enyo.ViewPreloadSupport',

	/**
	* @private
	*/
	published: {

		/**
		* When `true`, existing views are cached for reuse, otherwise they are destroyed.
		*
		* @type {Boolean}
		* @default true
		* @public
		*/
		cacheViews: true
	},

	/**
	* @method
	* @private
	*/
	create: kind.inherit(function (sup) {
		return function () {
			sup.apply(this, arguments);

			if (this.cacheViews) {
				this.createChrome([
					{name: 'viewCache', kind: Control, canGenerate: false}
				]);
				this.removeChild(this.$.viewCache);
				this._cachedViews = {};
			}
		};
	}),

	/*
		===================================
		Interface methods to be implemented
		===================================
	*/

	/**
	* Determines the id of the given view. This is something that should generally be
	* implemented in the actual kind as this will vary from case to case.
	*
	* @param {Object} view - The view whose id we will determine.
	* @return {String} The id of the given view.
	* @public
	*/
	getViewId: kind.inherit(function (sup) {
		return function (view) {
			return sup.apply(this, arguments);
		};
	}),

	/**
	* Reset the state of the given view. This is something that should generally be implemented
	* in the actual kind.
	*
	* @param {Object} view - The view whose state we wish to reset.
	* @private
	*/
	resetView: kind.inherit(function (sup) {
		return function (view) {
			return sup.apply(this, arguments);
		};
	}),

	/*
		==============
		Public methods
		==============
	*/

	/**
	* Caches a given view so that it can be quickly instantiated and utilized at a later point.
	* This is typically performed on a view which has already been rendered and which no longer
	* needs to remain in view, but may be revisited at a later time.
	*
	* @param {Object} view - The view to cache for later use.
	* @param {Boolean} preserve - If `true`, preserves the view's position both on the screen
	*	and within the component hierarchy.
	* @public
	*/
	cacheView: function (view, preserve) {
		var id = this.getViewId(view);

		// The panel could have already been removed from DOM and torn down if we popped when
		// moving forward.
		if (view.node) {
			view.node.remove();
			view.teardownRender(true);
		}

		if (!preserve) {
			this.resetView(view);

			this.removeControl(view);
			this.$.viewCache.addControl(view);
		}

		this._cachedViews[id] = view;
	},

	/**
	* Restores the specified view that was previously cached.
	*
	* @param {String} id - The unique identifier for the cached view that is being restored.
	* @return {Object} The restored view.
	* @public
	*/
	restoreView: function (id) {
		var cp = this._cachedViews,
			view = cp[id];

		if (view) {
			this.$.viewCache.removeControl(view);
			this.addControl(view);

			this._cachedViews[id] = null;
		}

		return view;
	},

	/**
	* Pre-caches a set of views by creating and caching them, even if they have not yet been
	* rendered into view. This is helpful for reducing the instantiation time for views which
	* have yet to be shown, but can and eventually will be shown to the user.
	*
	* @param {Object} info - The declarative {@glossary kind} definition.
	* @param {Object} commonInfo - Additional properties to be applied (defaults).
	* @param {Boolean} [cbComplete] - If specified, this callback will be executed, with the
	*	created view being passed as a parameter.
	* @public
	*/
	preCacheViews: function(info, commonInfo, cbComplete) {
		var vc, views, i, view;

		if (this.cacheViews) {
			vc = this.$.viewCache;
			commonInfo = commonInfo || {};
			commonInfo.owner = this;
			views = vc.createComponents(info, commonInfo);
			for (i = 0; i < views.length; i++) {
				view = views[i];
				this._cachedViews[this.getViewId(view)] = view;
				if (cbComplete) {
					cbComplete.call(this, view);
				}
			}
		}
	},

	/**
	* Pre-caches a single view by creating and caching the view, even if it has not yet been
	* rendered into view. This is helpful for reducing the instantiation time for panels which
	* have yet to be shown, but can and eventually will be shown to the user.
	*
	* @param {Object} info - The declarative {@glossary kind} definition.
	* @param {Object} commonInfo - Additional properties to be applied (defaults).
	* @param {Function} [cbComplete] - If specified, this callback will be executed, with the
	*	created view being passed as a parameter.
	* @public
	*/
	preCacheView: function(info, commonInfo, cbComplete) {
		var vc, view;

		if (this.cacheViews && !this._cachedViews[info.kind]) {
			vc = this.$.viewCache;
			commonInfo = commonInfo || {};
			commonInfo.owner = this;
			view = vc.createComponent(info, commonInfo);
			this._cachedViews[this.getViewId(info)] = view;
			if (cbComplete) {
				cbComplete.call(this, view);
			}
		}
	}

};<|endoftext|>


<commit_before>// VVVV.js -- Visual Web Client Programming
// (c) 2011 Matthias Zauner
// VVVV.js is freely distributable under the MIT license.
// Additional authors of sub components are mentioned at the specific code locations.
// This component was developed is (c) 2014 Lukas Winter, distributed under the MIT license.

VVVV.PinTypes.WebAudio = {
  typeName: "WebAudio",
  reset_on_disconnect: true,
  defaultValue: function() {
    return "Unconnected Audio";
  },
  connectionChangedHandlers: {
    'webaudio': function() {
      if(this.direction == PinDirection.Output)
      {
        console.log("Connection changed!", this);
        this.audioConnectionChanged = true;
      }
    }
  }
}

var audioContext = null;

function WebAudioNode(id, name, graph) {
  if(graph) //constructing actual node
  {
    this.constructor(id, name, graph);
    if(!audioContext)
    {
      audioContext = new AudioContext();
    }
    this.initialize = function()
    {
      this.createAPINode();
      this.createAudioPins();
      this.createParamPins();
    }
    this.audioInputPins = [];
    this.audioOutputPins = [];
    this.paramPins = [];
  }
  else //constructing prototype
  {
    this.createAPINode = function(arg)
    {
      //this is just for debugging purposes with firefox's web audio visualiser
      if(id == 'Analyser')
        this.apiNode = audioContext.createAnalyser(arg);
      else if(id == 'MediaElementSource')
        this.apiNode = audioContext.createMediaElementSource(arg);
      else if(id == 'Oscillator')
        this.apiNode = audioContext.createOscillator(arg);
      else if(id == 'Delay')
        this.apiNode = audioContext.createDelay(arg);
      else if(id == 'Gain')
        this.apiNode = audioContext.createGain(arg);
      else //this is the normal code
        this.apiNode = audioContext['create'+id].apply(audioContext, arguments);
    }
    this.auto_evaluate = false;
  }
}
WebAudioNode.prototype = new VVVV.Core.Node();
WebAudioNode.prototype.createAudioPins = function()
{
  for(var i = 0; i < this.apiNode.numberOfInputs; i++)
  {
    this.audioInputPins.push(this.addInputPin('Input '+(i+1), [], VVVV.PinTypes.WebAudio));
  }
  for(var i = 0; i < this.apiNode.numberOfOutputs; i++)
  {
    this.audioOutputPins.push(this.addOutputPin('Output '+(i+1), [this.apiNode], VVVV.PinTypes.WebAudio));
  }
}
WebAudioNode.prototype.createParamPins = function()
{
  for(var key in this.apiNode)
  {
    var param = this.apiNode[key];
    if(param instanceof AudioParam)
    {
      this.paramPins.push(this.addInputPin(key.replace(/([a-z^])([A-Z])/g, '$1 $2'), [param.defaultValue], VVVV.PinTypes.Value)); //FIXME: params can be connected to a value or an audio stream
      this.paramPins[this.paramPins.length - 1].apiName = key;
    }
  }
}
WebAudioNode.prototype.updateParamPins = function()
{
  var that = this;
  this.paramPins.forEach( function(pin, i)
  {
    if(pin.pinIsChanged() && that.apiNode)
    {
      that.apiNode[pin.apiName].value = pin.getValue(0);
    }
  });
}
WebAudioNode.prototype.updateAudioConnections = function()
{
  var that = this;
  this.audioOutputPins.forEach(function(audioOut, outIndex) {
    if(audioOut.audioConnectionChanged && that.apiNode)
    {
      console.log("Re-connecting!");
      that.apiNode.disconnect(outIndex);
      audioOut.links.forEach(function(link)
      {
        var inPin = link.toPin;
        var inNode = inPin.node;
        var inIndex = inNode.audioInputPins.indexOf(inPin);
        that.apiNode.connect(inNode.apiNode, outIndex, inIndex);
      });
      audioOut.audioConnectionChanged = false;
    }
  });
}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: AnalyserNode (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.AnalyserNode = function(id, graph) {
  WebAudioNode.call(this, id, 'AnalyserNode (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var that = this;

  var fftSizeIn = this.addInputPin('FFTSize', ['2048'], VVVV.PinTypes.Value);
  var smoothingIn = this.addInputPin('Smoothing', [0.8], VVVV.PinTypes.Value);
  var fftOut = this.addOutputPin('FFT', [], VVVV.PinTypes.Value);
  var fftData;

  function setFFTSize(size)
  {
    if(!size)
      size = 32;
    fftOut.setSliceCount(size);
    fftData = new Float32Array(size);
  }

  setFFTSize(2048);

  this.evaluate = function()
  {
    if(fftSizeIn.pinIsChanged())
      setFFTSize(fftSizeIn.getValue(0));
    if(smoothingIn.pinIsChanged())
      this.apiNode.smoothingTimeConstant = smoothingIn.getValue(0);

    this.updateAudioConnections();
    this.apiNode.getFloatFrequencyData(fftData);
    for(var i = 0; i < fftData.length; i++)
    {
      fftOut.setValue(i, fftData[i]); //FIXME: veeeeery inefficient!
    }

    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.AnalyserNode.prototype = new WebAudioNode('Analyser');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: MediaElementSource (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.MediaElementSource = function(id, graph) {
  WebAudioNode.call(this, id, 'MediaElementSource (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var audioIn = this.addInputPin('Audio', [], this);
  var audioOut = this.addOutputPin('Output', [], VVVV.PinTypes.WebAudio);
  this.audioOutputPins.push(audioOut);

  this.initialize = function() {};

  var mediaElements = [ 7 ];

  this.evaluate = function() {
    this.updateAudioConnections();
    if(audioIn.pinIsChanged())
    {
      var inElement = audioIn.getValue(0);
      if(inElement != mediaElements[0] && inElement)
      {
        mediaElements[0] = inElement;
        this.createAPINode(audioIn.getValue(0));
        inElement.volume = 1;
      }

      if(this.apiNode)
      {
        audioOut.setValue(0, this.apiNode);
      }
    }

  }
}
VVVV.Nodes.MediaElementSource.prototype = new WebAudioNode('MediaElementSource');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: AudioDestination (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.AudioDestination = function(id, graph) {
  WebAudioNode.call(this, id, 'AudioDestination (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  this.createAPINode = function() { this.apiNode = audioContext.destination; };

  this.evaluate = function() {
    this.updateAudioConnections();
  }
}
VVVV.Nodes.AudioDestination.prototype = new WebAudioNode('AudioDestination');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Oscillator (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Oscillator = function(id, graph) {
  WebAudioNode.call(this, id, 'Oscillator (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var typeIn = this.addInputPin("Type", ['sine'], VVVV.PinTypes.Enum);
  typeIn.enumOptions = ['sine', 'square', 'sawtooth', 'triangle', 'custom' ];
  var enableIn = this.addInputPin("Enabled", [1], VVVV.PinTypes.Value);

  this.auto_evaluate = true;

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );

    if(typeIn.pinIsChanged() && this.apiNode)
    {
      this.apiNode.type = typeIn.getValue(0);
    }

    if(enableIn.pinIsChanged() && this.apiNode)
    {
      if(enableIn.getValue(0) > 0)
      {
        this.apiNode.start();
      }
      else
      {
        this.apiNode.stop();
      }
    }
  }
}
VVVV.Nodes.Oscillator.prototype = new WebAudioNode('Oscillator');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Delay (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Delay = function(id, graph) {
  WebAudioNode.call(this, id, 'Delay (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var createAPINode = this.createAPINode;
  this.createAPINode = function() { createAPINode.call(this, 10); }

  this.delays_output = true;

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.Delay.prototype = new WebAudioNode('Delay');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Gain (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Gain = function(id, graph) {
  WebAudioNode.call(this, id, 'Gain (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.Gain.prototype = new WebAudioNode('Gain');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: BeatDetector (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.BeatDetector = function(id, graph) {
  WebAudioNode.call(this, id, 'BeatDetector (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var that = this;
  var fftSize = 1024;
  var fftData = new Float32Array(fftSize);
  var beatDetector;

  var beatCounterOut = this.addOutputPin('Beat Counter', [ 0 ], VVVV.PinTypes.Value);
  var bpmOut = this.addOutputPin('BPM', [ 0 ], VVVV.PinTypes.Value);

  var initialize = this.initialize;
  this.initialize = function() {
    initialize.call(this);
    this.apiNode.fftSize = fftSize;
    this.apiNode.smoothingTimeConstant = 0;
  }

  this.evaluate = function()
  {
    if(!beatDetector)
      beatDetector = new BeatDetektor();
    this.updateAudioConnections();
    this.apiNode.getFloatFrequencyData(fftData);
    beatDetector.process(audioContext.currentTime, fftData);
    beatCounterOut.setValue(0, beatDetector.beat_counter);
    bpmOut.setValue(0, beatDetector.win_bpm_int / 10);

    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.BeatDetector.prototype = new WebAudioNode('Analyser');

/*VVVV.Nodes.BiquadFilterNode = makeAudioNodeConstructor('BiquadFilter');
VVVV.Nodes.ChannelMergerNode = makeAudioNodeConstructor('ChannelMerger');
VVVV.Nodes.ChannelSplitterNode = makeAudioNodeConstructor('ChannelSplitter');
VVVV.Nodes.ConvolverNode = makeAudioNodeConstructor('Convolver');
VVVV.Nodes.DelayNode = makeAudioNodeConstructor('Delay');
VVVV.Nodes.DynamicsCompressorNode = makeAudioNodeConstructor('DynamicsCompressor');
VVVV.Nodes.GainNode = makeAudioNodeConstructor('Gain');
VVVV.Nodes.OscillatorNode = makeAudioNodeConstructor('Oscillator');
VVVV.Nodes.PannerNode = makeAudioNodeConstructor('Panner');
VVVV.Nodes.ScriptProcessorNode = makeAudioNodeConstructor('ScriptProcessor');
VVVV.Nodes.WaveShaperNode = makeAudioNodeConstructor('WaveShaper');*/

<commit_msg>Add DynamicsCompressor (HTML5 Audio)<commit_after>// VVVV.js -- Visual Web Client Programming
// (c) 2011 Matthias Zauner
// VVVV.js is freely distributable under the MIT license.
// Additional authors of sub components are mentioned at the specific code locations.
// This component was developed is (c) 2014 Lukas Winter, distributed under the MIT license.

VVVV.PinTypes.WebAudio = {
  typeName: "WebAudio",
  reset_on_disconnect: true,
  defaultValue: function() {
    return "Unconnected Audio";
  },
  connectionChangedHandlers: {
    'webaudio': function() {
      if(this.direction == PinDirection.Output)
      {
        console.log("Connection changed!", this);
        this.audioConnectionChanged = true;
      }
    }
  }
}

var audioContext = null;

function WebAudioNode(id, name, graph) {
  if(graph) //constructing actual node
  {
    this.constructor(id, name, graph);
    if(!audioContext)
    {
      audioContext = new AudioContext();
    }
    this.initialize = function()
    {
      this.createAPINode();
      this.createAudioPins();
      this.createParamPins();
    }
    this.audioInputPins = [];
    this.audioOutputPins = [];
    this.paramPins = [];
  }
  else //constructing prototype
  {
    this.createAPINode = function(arg)
    {
      //this is just for debugging purposes with firefox's web audio visualiser
      if(id == 'Analyser')
        this.apiNode = audioContext.createAnalyser(arg);
      else if(id == 'MediaElementSource')
        this.apiNode = audioContext.createMediaElementSource(arg);
      else if(id == 'Oscillator')
        this.apiNode = audioContext.createOscillator(arg);
      else if(id == 'Delay')
        this.apiNode = audioContext.createDelay(arg);
      else if(id == 'Gain')
        this.apiNode = audioContext.createGain(arg);
      else if(id == 'DynamicsCompressor')
        this.apiNode = audioContext.createDynamicsCompressor(arg);
      else //this is the normal code
        this.apiNode = audioContext['create'+id].apply(audioContext, arguments);
    }
    this.auto_evaluate = false;
  }
}
WebAudioNode.prototype = new VVVV.Core.Node();
WebAudioNode.prototype.createAudioPins = function()
{
  for(var i = 0; i < this.apiNode.numberOfInputs; i++)
  {
    this.audioInputPins.push(this.addInputPin('Input '+(i+1), [], VVVV.PinTypes.WebAudio));
  }
  for(var i = 0; i < this.apiNode.numberOfOutputs; i++)
  {
    this.audioOutputPins.push(this.addOutputPin('Output '+(i+1), [this.apiNode], VVVV.PinTypes.WebAudio));
  }
}
WebAudioNode.prototype.createParamPins = function()
{
  for(var key in this.apiNode)
  {
    var param = this.apiNode[key];
    if(param instanceof AudioParam)
    {
      this.paramPins.push(this.addInputPin(key.replace(/([a-z^])([A-Z])/g, '$1 $2'), [param.defaultValue], VVVV.PinTypes.Value)); //FIXME: params can be connected to a value or an audio stream
      this.paramPins[this.paramPins.length - 1].apiName = key;
    }
  }
}
WebAudioNode.prototype.updateParamPins = function()
{
  var that = this;
  this.paramPins.forEach( function(pin, i)
  {
    if(pin.pinIsChanged() && that.apiNode)
    {
      that.apiNode[pin.apiName].value = pin.getValue(0);
    }
  });
}
WebAudioNode.prototype.updateAudioConnections = function()
{
  var that = this;
  this.audioOutputPins.forEach(function(audioOut, outIndex) {
    if(audioOut.audioConnectionChanged && that.apiNode)
    {
      console.log("Re-connecting!");
      that.apiNode.disconnect(outIndex);
      audioOut.links.forEach(function(link)
      {
        var inPin = link.toPin;
        var inNode = inPin.node;
        var inIndex = inNode.audioInputPins.indexOf(inPin);
        that.apiNode.connect(inNode.apiNode, outIndex, inIndex);
      });
      audioOut.audioConnectionChanged = false;
    }
  });
}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: AnalyserNode (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.AnalyserNode = function(id, graph) {
  WebAudioNode.call(this, id, 'AnalyserNode (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var that = this;

  var fftSizeIn = this.addInputPin('FFTSize', ['2048'], VVVV.PinTypes.Value);
  var smoothingIn = this.addInputPin('Smoothing', [0.8], VVVV.PinTypes.Value);
  var fftOut = this.addOutputPin('FFT', [], VVVV.PinTypes.Value);
  var fftData;

  function setFFTSize(size)
  {
    if(!size)
      size = 32;
    fftOut.setSliceCount(size);
    fftData = new Float32Array(size);
  }

  setFFTSize(2048);

  this.evaluate = function()
  {
    if(fftSizeIn.pinIsChanged())
      setFFTSize(fftSizeIn.getValue(0));
    if(smoothingIn.pinIsChanged())
      this.apiNode.smoothingTimeConstant = smoothingIn.getValue(0);

    this.updateAudioConnections();
    this.apiNode.getFloatFrequencyData(fftData);
    for(var i = 0; i < fftData.length; i++)
    {
      fftOut.setValue(i, fftData[i]); //FIXME: veeeeery inefficient!
    }

    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.AnalyserNode.prototype = new WebAudioNode('Analyser');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: MediaElementSource (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.MediaElementSource = function(id, graph) {
  WebAudioNode.call(this, id, 'MediaElementSource (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var audioIn = this.addInputPin('Audio', [], this);
  var audioOut = this.addOutputPin('Output', [], VVVV.PinTypes.WebAudio);
  this.audioOutputPins.push(audioOut);

  this.initialize = function() {};

  var mediaElements = [ 7 ];

  this.evaluate = function() {
    this.updateAudioConnections();
    if(audioIn.pinIsChanged())
    {
      var inElement = audioIn.getValue(0);
      if(inElement != mediaElements[0] && inElement)
      {
        mediaElements[0] = inElement;
        this.createAPINode(audioIn.getValue(0));
        inElement.volume = 1;
      }

      if(this.apiNode)
      {
        audioOut.setValue(0, this.apiNode);
      }
    }

  }
}
VVVV.Nodes.MediaElementSource.prototype = new WebAudioNode('MediaElementSource');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: AudioDestination (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.AudioDestination = function(id, graph) {
  WebAudioNode.call(this, id, 'AudioDestination (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  this.createAPINode = function() { this.apiNode = audioContext.destination; };

  this.evaluate = function() {
    this.updateAudioConnections();
  }
}
VVVV.Nodes.AudioDestination.prototype = new WebAudioNode('AudioDestination');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Oscillator (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Oscillator = function(id, graph) {
  WebAudioNode.call(this, id, 'Oscillator (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var typeIn = this.addInputPin("Type", ['sine'], VVVV.PinTypes.Enum);
  typeIn.enumOptions = ['sine', 'square', 'sawtooth', 'triangle', 'custom' ];
  var enableIn = this.addInputPin("Enabled", [1], VVVV.PinTypes.Value);

  this.auto_evaluate = true;

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );

    if(typeIn.pinIsChanged() && this.apiNode)
    {
      this.apiNode.type = typeIn.getValue(0);
    }

    if(enableIn.pinIsChanged() && this.apiNode)
    {
      if(enableIn.getValue(0) > 0)
      {
        this.apiNode.start();
      }
      else
      {
        this.apiNode.stop();
      }
    }
  }
}
VVVV.Nodes.Oscillator.prototype = new WebAudioNode('Oscillator');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Delay (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Delay = function(id, graph) {
  WebAudioNode.call(this, id, 'Delay (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var createAPINode = this.createAPINode;
  this.createAPINode = function() { createAPINode.call(this, 10); }

  this.delays_output = true;

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.Delay.prototype = new WebAudioNode('Delay');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Gain (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Gain = function(id, graph) {
  WebAudioNode.call(this, id, 'Gain (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.Gain.prototype = new WebAudioNode('Gain');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: DynamicsCompressor (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.DynamicsCompressor = function(id, graph) {
  WebAudioNode.call(this, id, 'DynamicsCompressor (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var reductionOut = this.addOutputPin('Reduction', [ 0 ], VVVV.PinTypes.Value);

  this.evaluate = function() {
    this.updateAudioConnections();
    this.updateParamPins();
    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );

    //according to the spec, reduction shouldn't be an AudioParam, but browsers seem to implement it as such
    if(this.apiNode)
      reductionOut.setValue(0, this.apiNode.reduction.value);
  }
}
VVVV.Nodes.DynamicsCompressor.prototype = new WebAudioNode('DynamicsCompressor');

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: BeatDetector (HTML5 Audio)
 Author(s): 'Lukas Winter'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.BeatDetector = function(id, graph) {
  WebAudioNode.call(this, id, 'BeatDetector (HTML5 Audio)', graph);

  this.meta = {
    authors: ['Lukas Winter'],
    original_authors: [],
    credits: [],
    compatibility_issues: []
  };

  var that = this;
  var fftSize = 1024;
  var fftData = new Float32Array(fftSize);
  var beatDetector;

  var beatCounterOut = this.addOutputPin('Beat Counter', [ 0 ], VVVV.PinTypes.Value);
  var bpmOut = this.addOutputPin('BPM', [ 0 ], VVVV.PinTypes.Value);

  var initialize = this.initialize;
  this.initialize = function() {
    initialize.call(this);
    this.apiNode.fftSize = fftSize;
    this.apiNode.smoothingTimeConstant = 0;
  }

  this.evaluate = function()
  {
    if(!beatDetector)
      beatDetector = new BeatDetektor();
    this.updateAudioConnections();
    this.apiNode.getFloatFrequencyData(fftData);
    beatDetector.process(audioContext.currentTime, fftData);
    beatCounterOut.setValue(0, beatDetector.beat_counter);
    bpmOut.setValue(0, beatDetector.win_bpm_int / 10);

    this.audioOutputPins.forEach( function(pin) { pin.markPinAsChanged(); } );
  }
}
VVVV.Nodes.BeatDetector.prototype = new WebAudioNode('Analyser');

/*VVVV.Nodes.BiquadFilterNode = makeAudioNodeConstructor('BiquadFilter');
VVVV.Nodes.ChannelMergerNode = makeAudioNodeConstructor('ChannelMerger');
VVVV.Nodes.ChannelSplitterNode = makeAudioNodeConstructor('ChannelSplitter');
VVVV.Nodes.ConvolverNode = makeAudioNodeConstructor('Convolver');
VVVV.Nodes.DelayNode = makeAudioNodeConstructor('Delay');
VVVV.Nodes.DynamicsCompressorNode = makeAudioNodeConstructor('DynamicsCompressor');
VVVV.Nodes.GainNode = makeAudioNodeConstructor('Gain');
VVVV.Nodes.OscillatorNode = makeAudioNodeConstructor('Oscillator');
VVVV.Nodes.PannerNode = makeAudioNodeConstructor('Panner');
VVVV.Nodes.ScriptProcessorNode = makeAudioNodeConstructor('ScriptProcessor');
VVVV.Nodes.WaveShaperNode = makeAudioNodeConstructor('WaveShaper');*/

<|endoftext|>


<commit_before>CLASS({
  package: 'foam.u2',
  name: 'EIDProperty',
  extendsModel: 'Property',
  help: 'Describes a property used to store a DOM element id.',

  constants: {
    NEXT_ID: function() {
      return "view" + (arguments.callee._nextId = (arguments.callee._nextId || 0) + 1);
    }
  },

  properties: [
    {
      name: 'lazyFactory',
      defaultValue: function() { return foam.u2.EIDProperty.NEXT_ID(); }
    },
    {
      name: 'transient',
      defaultValue: true
    },
    {
      name: 'hidden',
      defaultValue: true
    },
    {
      name: 'install',
      defaultValue: function(prop) {
        defineLazyProperty(this, prop.name + '$el', function() {
          return this.X.$(this[prop.name]);
        });
      }
    }
  ]
});
<commit_msg>EIDProperty now works.<commit_after>CLASS({
  package: 'foam.u2',
  name: 'EIDProperty',
  extendsModel: 'Property',
  help: 'Describes a property used to store a DOM element id.',

  constants: {
    NEXT_ID: function() {
      return 'view' + (arguments.callee._nextId = (arguments.callee._nextId || 0) + 1);
    }
  },

  properties: [
    {
      name: 'lazyFactory',
      defaultValue: function() { return foam.u2.EIDProperty.NEXT_ID(); }
    },
    {
      name: 'transient',
      defaultValue: true
    },
    {
      name: 'hidden',
      defaultValue: true
    },
    {
      name: 'install',
      defaultValue: function(prop) {
        defineLazyProperty(this, prop.name + '$el', function() {
          return { get: function() {
            return this.X.$(this[prop.name]);
          }}});
      }
    }
  ]
});
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var test = require('tap').test;
var browserify = require('browserify');
var concatStream = require('concat-stream');
var jsdom = require('jsdom');
var browserifyCSSTransform = require('../');

test('load style sheets at run time', function(t) {
    var b = browserify()
        .add('test/fixtures/submodules')
        .transform(browserifyCSSTransform)
        .require('./browser', {expose: 'browserify-css'})
        .bundle();

    b.pipe(concatStream(function(bundle) {
        var html = fs.readFileSync('./test/fixtures/submodules/index.html');
        jsdom.env({
            html: html,
            src: [
                fs.readFileSync('./test/fixtures/jquery.js'),
                bundle
            ],
            features: {
                FetchExternalResources: ['script'],
                ProcessExternalResources: ['script'],
                SkipExternalResources: false
            },
            done: function(errors, window) {
                if (errors) {
                    t.fail(errors);
                    return t.end();
                }

                var $ = window.jQuery;
                var $foo = $('#container .foo');
                var $bar = $('#container .bar');

                t.ok($foo.length > 0);
                t.equal($foo.text(), 'foo module', 'the inner text should be "foo module"');
                t.equal(window.getComputedStyle($foo.get(0))._values['background-color'], 'rgb(204, 204, 204)', 'the computed style of background-color property for the element should be #ccc');
                t.ok($bar.length > 0);
                t.equal($bar.text(), 'bar module', 'the inner text should be "bar module"');
                t.equal(window.getComputedStyle($bar.get(0))._values['background-color'], 'rgb(238, 238, 238)', 'the computed style of background-color property for the element should be #eee');

                t.end();
            }
        });
    }));
});

test('processRelativeUrl with media query', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.output.css');

    cssTransform({
        rootDir: path.resolve(__dirname, 'fixtures'),
        rebaseUrls: true,
        processRelativeUrl: function(relativeUrl) {
            return 'images/' + relativeUrl;
        }
    }, inputFile, function(data) {
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});

test('processInlineImages in css file', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.inline.css');

    cssTransform({
				inlineImages: true
    }, inputFile, function(data) {
				fs.writeFileSync('/tmp/output',data);
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});

test('processInlineImages in css file with files larger than ', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.inline-small.css');

    cssTransform({
				inlineImages: {
            options: {
                limit: 100000
						}
				},
				rebaseUrls: false
    }, inputFile, function(data) {
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});
<commit_msg>[test/index.js] 修改test用例参数<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var test = require('tap').test;
var browserify = require('browserify');
var concatStream = require('concat-stream');
var jsdom = require('jsdom');
var browserifyCSSTransform = require('../');

test('load style sheets at run time', function(t) {
    var b = browserify()
        .add('test/fixtures/submodules')
        .transform(browserifyCSSTransform)
        .require('./browser', {expose: 'browserify-css'})
        .bundle();

    b.pipe(concatStream(function(bundle) {
        var html = fs.readFileSync('./test/fixtures/submodules/index.html');
        jsdom.env({
            html: html,
            src: [
                fs.readFileSync('./test/fixtures/jquery.js'),
                bundle
            ],
            features: {
                FetchExternalResources: ['script'],
                ProcessExternalResources: ['script'],
                SkipExternalResources: false
            },
            done: function(errors, window) {
                if (errors) {
                    t.fail(errors);
                    return t.end();
                }

                var $ = window.jQuery;
                var $foo = $('#container .foo');
                var $bar = $('#container .bar');

                t.ok($foo.length > 0);
                t.equal($foo.text(), 'foo module', 'the inner text should be "foo module"');
                t.equal(window.getComputedStyle($foo.get(0))._values['background-color'], 'rgb(204, 204, 204)', 'the computed style of background-color property for the element should be #ccc');
                t.ok($bar.length > 0);
                t.equal($bar.text(), 'bar module', 'the inner text should be "bar module"');
                t.equal(window.getComputedStyle($bar.get(0))._values['background-color'], 'rgb(238, 238, 238)', 'the computed style of background-color property for the element should be #eee');

                t.end();
            }
        });
    }));
});

test('processRelativeUrl with media query', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.output.css');

    cssTransform({
        rootDir: path.resolve(__dirname, 'fixtures'),
        rebaseUrls: true,
        processRelativeUrl: function(relativeUrl) {
            return 'images/' + relativeUrl;
        }
    }, inputFile, function(data) {
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});

test('processInlineImages in css file', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.inline.css');

    cssTransform({
				inlineImages: true
    }, inputFile, function(data) {
				fs.writeFileSync('/tmp/output',data);
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});

test('processInlineImages in css file with files larger than ', function(t) {
    var cssTransform = require('../css-transform');
    var inputFile = path.resolve(__dirname, 'fixtures/app.css');
    var outputFile = path.resolve(__dirname, 'fixtures/app.inline-small.css');

    cssTransform({
		inlineImages: true,
        inlineImagesOptions: {
            limit: 100000
        },
        rebaseUrls: false
    }, inputFile, function(data) {
        t.same(data, fs.readFileSync(outputFile, 'utf-8'));
        t.end();
    });
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/cs-create-menu';
import { transitionTo } from '../private-api';
import { task } from 'ember-concurrency';

export default Ember.Component.extend({
  layout,
  classNames: ['cs-create-menu'],
  cardstackRouting: Ember.inject.service(),
  tools: Ember.inject.service('cardstack-tools'),
  store: Ember.inject.service(),

  availableTypes: [],

  loadAvailableTypes: task(function * () {
    let types = yield this.get('store').query('content-type', { filter: { not: { 'is-built-in' : true } } });
    this.set('availableTypes', types);
  }).on('init'),

  actions: {
    create(which) {
      let { name, args, queryParams } = this.get('cardstackRouting').routeForNew(which.id, this.get('tools.branch'));
      transitionTo(Ember.getOwner(this), name, args, queryParams);
      this.get('tools').setActivePanel('cs-composition-panel');
    }
  }
});
<commit_msg>set editing at create<commit_after>import Ember from 'ember';
import layout from '../templates/components/cs-create-menu';
import { transitionTo } from '../private-api';
import { task } from 'ember-concurrency';

export default Ember.Component.extend({
  layout,
  classNames: ['cs-create-menu'],
  cardstackRouting: Ember.inject.service(),
  tools: Ember.inject.service('cardstack-tools'),
  store: Ember.inject.service(),

  availableTypes: [],

  loadAvailableTypes: task(function * () {
    let types = yield this.get('store').query('content-type', { filter: { not: { 'is-built-in' : true } } });
    this.set('availableTypes', types);
  }).on('init'),

  actions: {
    create(which) {
      let { name, args, queryParams } = this.get('cardstackRouting').routeForNew(which.id, this.get('tools.branch'));
      transitionTo(Ember.getOwner(this), name, args, queryParams);
      this.get('tools').setActivePanel('cs-composition-panel');
      this.get('tools').setEditing(true);
    }
  }
});
<|endoftext|>


<commit_before>/**
 * FoodCoopShop - The open source software for your foodcoop
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @since         FoodCoopShop 2.5.0
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 * @author        Mario Rothauer <office@foodcoopshop.com>
 * @copyright     Copyright (c) Mario Rothauer, https://www.rothauer-it.com
 * @link          https://www.foodcoopshop.com
 */
foodcoopshop.AppChart = {

    chartOptions : {
        tension : 0.3,
        datasetStrokeWidth : 1,
        scaleOverride: true,
        scaleStartValue: 0,
        scaleGridLineColor: 'rgba(0,0,0,.15)',
        animation: false,
        legend: {
            display: false
        },
        scales: {
            xAxes: [{
                gridLines: {
                    display: false
                }
            }],
            yAxes: []
        }
    },

    init : function(xAxisData, yAxisData) {

        var ctx = $("#myChart").get(0).getContext("2d");
        xAxisData = $.parseJSON(xAxisData);
        yAxisData = $.parseJSON(yAxisData);

//        for(y in yAxis) {
//            yAxis[y] = foodcoopshop.Helper.getStringAsFloat(yAxis[y]);
//        }
//        console.log(yAxis);

        var barChartData = {
            labels: xAxisData,
            datasets: [{
                data: yAxisData,
                backgroundColor: 'rgba(113,159,65,.7)',
            }]
        };

        var ctx = $("#myChart").get(0).getContext("2d");
          var myNewChart = new Chart(ctx, {
          type: 'bar',
          data: barChartData,
          options: this.chartOptions
       });
    }

};<commit_msg>chart layout<commit_after>/**
 * FoodCoopShop - The open source software for your foodcoop
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @since         FoodCoopShop 2.5.0
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 * @author        Mario Rothauer <office@foodcoopshop.com>
 * @copyright     Copyright (c) Mario Rothauer, https://www.rothauer-it.com
 * @link          https://www.foodcoopshop.com
 */
foodcoopshop.AppChart = {

    chartOptions : {
        tension : 0.3,
        datasetStrokeWidth : 1,
        scaleOverride: true,
        scaleStartValue: 0,
        scaleGridLineColor: 'rgba(0,0,0,.15)',
        animation: false,
        legend: {
            display: false
        },
        scales: {
            xAxes: [{
                gridLines: {
                    display: false
                },
                maxBarThickness: 40,
                ticks: {
                    autoSkip: false,
                    maxRotation: 90,
                    minRotation: 90
                }
            }],
            yAxes: []
        }
    },

    init : function(xAxisData, yAxisData) {

        var ctx = $("#myChart").get(0).getContext("2d");
        xAxisData = $.parseJSON(xAxisData);
        yAxisData = $.parseJSON(yAxisData);

        var barChartData = {
            labels: xAxisData,
            datasets: [{
                data: yAxisData,
                backgroundColor: 'rgba(113,159,65,.7)',
            }]
        };

        var ctx = $("#myChart").get(0).getContext("2d");
          var myNewChart = new Chart(ctx, {
          responsive : true,
          type: 'bar',
          data: barChartData,
          options: this.chartOptions
       });
    }

};<|endoftext|>


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

'use strict';

/* jshint maxparams:5 */

var assert = require('assert');
var util = require('util');

var TChannelJSON = require('../as/json');
var TChannelEndpointHandler = require('../endpoint-handler');

module.exports = HyperbahnHandler;

function HyperbahnHandler(options) {
    if (!(this instanceof HyperbahnHandler)) {
        return new HyperbahnHandler(options);
    }

    var self = this;
    TChannelEndpointHandler.call(self, 'hyperbahn');

    assert(options && options.channel, 'channel required');
    assert(options && options.egressNodes, 'egressNodes required');

    // TODO support blackList

    self.channel = options.channel;
    self.egressNodes = options.egressNodes;

    self.tchannelJSON = TChannelJSON({
        logger: self.channel.logger
    });

    // TODO replace JSON with real bufrw handlers for this
    self.tchannelJSON.register(self, 'ad', self,
        self.handleAdvertise);
    self.tchannelJSON.register(self, 'relay-ad', self,
        self.handleRelayAdvertise);
}
util.inherits(HyperbahnHandler, TChannelEndpointHandler);

HyperbahnHandler.prototype.type = 'hyperbahn.advertisement-handler';

/*  req: {
        services: Array<{
            serviceName: String,
            cost: Number
        }>
    }

    res: {
        connectionCount: Number
    }
*/
HyperbahnHandler.prototype.handleAdvertise =
function handleAdvertise(self, req, arg2, arg3, cb) {
    /*eslint max-statements: [2, 25], max-params: [2, 5]*/
    var services = arg3.services;

    var servicesByExitNode = {};

    for (var i = 0; i < services.length; i++) {
        var service = services[i];
        service.hostPort = req.remoteAddr;

        var serviceName = service.serviceName;
        if (serviceName === '') {
            continue;
        }

        var exitNodes = self.egressNodes.exitsFor(serviceName);
        var exitHosts = Object.keys(exitNodes);

        for (var j = 0; j < exitHosts.length; j++) {
            var exitNode = exitHosts[j];

            var relayReq = servicesByExitNode[exitNode];
            if (!relayReq) {
                relayReq = servicesByExitNode[exitNode] = [];
            }

            relayReq.push(service);
        }
    }

    var exitNodeKeys = Object.keys(servicesByExitNode);
    var counter = 1 + exitNodeKeys.length;
    for (var k = 0; k < exitNodeKeys.length; k++) {
        var hostPort = exitNodeKeys[k];
        var exitNodeServices = servicesByExitNode[hostPort];

        self.sendRelayAdvertise(
            hostPort, exitNodeServices, onFinish
        );
    }

    onFinish();

    // TODO remove blocking on fanout finish. Requires fixing
    // autobahn tests upstream
    function onFinish() {
        if (--counter === 0) {
            cb(null, {
                ok: true,
                head: null,
                body: {
                    connectionCount: exitNodeKeys.length
                }
            });
        }
    }
};

HyperbahnHandler.prototype.sendAdvertise =
function sendAdvertise(services, options, callback) {
    var self = this;

    if (typeof options === 'function') {
        callback = options;
        options = {};
    }

    options.serviceName = 'hyperbahn';

    var req = self.channel.request(options);
    self.tchannelJSON.send(req, 'ad', null, {
        services: services
    }, callback);
};

/*  req: {
        services: Array<{
            serviceName: String,
            hostPort: String,
            cost: Number
        }>
    }

    res: {}
*/
HyperbahnHandler.prototype.handleRelayAdvertise =
function handleRelayAdvertise(self, req, arg2, arg3, cb) {
    /*eslint max-params: [2, 5]*/
    var services = arg3.services;
    var logger = self.channel.logger;

    for (var i = 0; i < services.length; i++) {
        var service = services[i];

        var exitNodes = self.egressNodes
            .exitsFor(service.serviceName);
        var exitHosts = Object.keys(exitNodes);

        var myHost = self.channel.hostPort;
        if (exitHosts.indexOf(myHost) !== -1) {
            self.advertise(service);
        } else {
            logger.warn('Non-exit node got relay handle advertise', {
                myHost: myHost,
                exitHosts: exitHosts,
                service: service
            });
        }
    }

    cb(null, {
        ok: true,
        head: null,
        body: {}
    });
};

HyperbahnHandler.prototype.sendRelayAdvertise =
function sendRelayAdvertise(hostPort, services, callback) {
    var self = this;

    self.tchannelJSON.send(self.channel.request({
        host: hostPort,
        serviceName: 'hyperbahn'
    }), 'relay-ad', null, {
        services: services
    }, onRelayAdvertise);

    function onRelayAdvertise(err, response) {
        if (response && response.ok) {
            return callback(null, null);
        }

        var logger = self.channel.logger;
        logger.error('Could not send relay advertise', {
            exitNode: hostPort,
            services: services,
            err: err,
            responseBody: response && response.body
        });

        callback(null, null);
    }
};

HyperbahnHandler.prototype.advertise =
function advertise(service) {
    throw new Error('not implemented');
};
<commit_msg>hyperbahn/handler: retry the send relay ad endpoint<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

/* jshint maxparams:5 */

var assert = require('assert');
var util = require('util');

var TChannelJSON = require('../as/json');
var TChannelEndpointHandler = require('../endpoint-handler');

var MAX_RELAY_AD_ATTEMPTS = 2;
var RELAY_AD_RETRY_TIME = 2 * 1000;

module.exports = HyperbahnHandler;

function HyperbahnHandler(options) {
    if (!(this instanceof HyperbahnHandler)) {
        return new HyperbahnHandler(options);
    }

    var self = this;
    TChannelEndpointHandler.call(self, 'hyperbahn');

    assert(options && options.channel, 'channel required');
    assert(options && options.egressNodes, 'egressNodes required');

    // TODO support blackList

    self.channel = options.channel;
    self.egressNodes = options.egressNodes;

    self.tchannelJSON = TChannelJSON({
        logger: self.channel.logger
    });

    // TODO replace JSON with real bufrw handlers for this
    self.tchannelJSON.register(self, 'ad', self,
        self.handleAdvertise);
    self.tchannelJSON.register(self, 'relay-ad', self,
        self.handleRelayAdvertise);
}
util.inherits(HyperbahnHandler, TChannelEndpointHandler);

HyperbahnHandler.prototype.type = 'hyperbahn.advertisement-handler';

/*  req: {
        services: Array<{
            serviceName: String,
            cost: Number
        }>
    }

    res: {
        connectionCount: Number
    }
*/
HyperbahnHandler.prototype.handleAdvertise =
function handleAdvertise(self, req, arg2, arg3, cb) {
    /*eslint max-statements: [2, 25], max-params: [2, 5]*/
    var services = arg3.services;

    var servicesByExitNode = {};

    for (var i = 0; i < services.length; i++) {
        var service = services[i];
        service.hostPort = req.remoteAddr;

        var serviceName = service.serviceName;
        if (serviceName === '') {
            continue;
        }

        var exitNodes = self.egressNodes.exitsFor(serviceName);
        var exitHosts = Object.keys(exitNodes);

        for (var j = 0; j < exitHosts.length; j++) {
            var exitNode = exitHosts[j];

            var relayReq = servicesByExitNode[exitNode];
            if (!relayReq) {
                relayReq = servicesByExitNode[exitNode] = [];
            }

            relayReq.push(service);
        }
    }

    var exitNodeKeys = Object.keys(servicesByExitNode);
    var counter = 1 + exitNodeKeys.length;
    for (var k = 0; k < exitNodeKeys.length; k++) {
        var hostPort = exitNodeKeys[k];
        var exitNodeServices = servicesByExitNode[hostPort];

        self.sendRelayAdvertise(
            hostPort, exitNodeServices, onFinish
        );
    }

    onFinish();

    // TODO remove blocking on fanout finish. Requires fixing
    // autobahn tests upstream
    function onFinish() {
        if (--counter === 0) {
            cb(null, {
                ok: true,
                head: null,
                body: {
                    connectionCount: exitNodeKeys.length
                }
            });
        }
    }
};

HyperbahnHandler.prototype.sendAdvertise =
function sendAdvertise(services, options, callback) {
    var self = this;

    if (typeof options === 'function') {
        callback = options;
        options = {};
    }

    options.serviceName = 'hyperbahn';

    var req = self.channel.request(options);
    self.tchannelJSON.send(req, 'ad', null, {
        services: services
    }, callback);
};

/*  req: {
        services: Array<{
            serviceName: String,
            hostPort: String,
            cost: Number
        }>
    }

    res: {}
*/
HyperbahnHandler.prototype.handleRelayAdvertise =
function handleRelayAdvertise(self, req, arg2, arg3, cb) {
    /*eslint max-params: [2, 5]*/
    var services = arg3.services;
    var logger = self.channel.logger;

    for (var i = 0; i < services.length; i++) {
        var service = services[i];

        var exitNodes = self.egressNodes
            .exitsFor(service.serviceName);
        var exitHosts = Object.keys(exitNodes);

        var myHost = self.channel.hostPort;
        if (exitHosts.indexOf(myHost) !== -1) {
            self.advertise(service);
        } else {
            logger.warn('Non-exit node got relay handle advertise', {
                myHost: myHost,
                exitHosts: exitHosts,
                service: service
            });
        }
    }

    cb(null, {
        ok: true,
        head: null,
        body: {}
    });
};

HyperbahnHandler.prototype.sendRelayAdvertise =
function sendRelayAdvertise(hostPort, services, callback) {
    var self = this;

    var attempts = 0;

    tryRequest();

    function tryRequest() {
        attempts++;

        self.tchannelJSON.send(self.channel.request({
            host: hostPort,
            serviceName: 'hyperbahn'
        }), 'relay-ad', null, {
            services: services
        }, onResponse);

        function onResponse(err, response) {
            if (response && response.ok) {
                return callback(null, null);
            }

            if (attempts <= MAX_RELAY_AD_ATTEMPTS && err &&
                err.type === 'tchannel.socket'
            ) {
                setTimeout(tryRequest, RELAY_AD_RETRY_TIME);
            } else {
                var logger = self.channel.logger;
                logger.error('Could not send relay advertise', {
                    exitNode: hostPort,
                    services: services,
                    err: err,
                    responseBody: response && response.body
                });

                callback(null, null);
            }
        }
    }
};

HyperbahnHandler.prototype.advertise =
function advertise(service) {
    throw new Error('not implemented');
};
<|endoftext|>