<commit_before>var WinJS = require('winjs'),
  ioc = require('../ioc'),
  config = require('../config'),
  base = require('./base'),
  log = require('../log'),
  util = require("util");

var _constructor = function(options) {
  base.call(this, arguments);
  this._onNavigatingBinding = this._onNavigating.bind(this);
  this._onNavigatedBinding = this._onNavigated.bind(this);
  this._onBeforeNavigateBinding = this._onBeforeNavigate.bind(this);
  this._onResizedBinding = this._onResized.bind(this);
  this._onPopStateBinding = this._onPopState.bind(this);
  this._onNavigateToMessageBinding = this._onNavigateToMessage.bind(this);
  this._lastNavigationPromise = WinJS.Promise.as();
  this.MessageService = ioc.getService("message");
  this._element = document.createElement("div");
};

var instanceMembers = {
  start: function() {
    var that = this;
    return base.prototype.start.apply(this, arguments).then(function() {
      WinJS.Navigation.addEventListener("navigating",
        that._onNavigatingBinding);
      WinJS.Navigation.addEventListener("navigated",
        that._onNavigatedBinding);
      WinJS.Navigation.addEventListener("beforenavigate",
        that._onBeforeNavigateBinding);
      that.MessageService.register("navigateToMessage", that._onNavigateToMessageBinding);
      window.addEventListener("resize", that._onResizedBinding);
      window.addEventListener("popstate", that._onPopStateBinding);
    });
  },

  _onPopState: function(event) {
    this.MessageService.send("navigateBackMessage");
  },

  _onNavigateToMessage: function(type, args) {
    var viewKey = args.viewKey;
    var state = args.state;
    var viewTemplateUri = config.get("ui:view:template-uri");
    if (viewTemplateUri) {
      var actualViewTemplateUri = util.format(viewTemplateUri, viewKey);
      var viewClassDef = ioc.getViewDef(viewKey);
      var extendedClassDef = WinJS.UI.Pages.define(actualViewTemplateUri, {}, viewClassDef);
      ioc.override(viewKey, extendedClassDef);
      var vmInstance = ioc.getViewModel(viewKey);
      vmInstance.setKey(viewKey);
      vmInstance.setData(args.state);

      history.pushState({
        context: state,
        key: viewKey
      }, viewKey, actualViewTemplateUri);

      WinJS.Navigation.navigate(actualViewTemplateUri, vmInstance);
    }
  },

  _onBeforeNavigate: function(args) {
    if (this.getViewModel() && this.getViewModel().getBackNavigationDisabled()) {
      log.info("NavigationService: back navigation cancelled based on current vm getBackNavigationDisabled value");
      args.preventDefault();
    }
  },

  _onNavigating: function(args) {
    var that = this;
    var newElement = this.createDefaultPageElement();
    this._element.appendChild(newElement);
    this._lastNavigationPromise.cancel();

    function cleanup() {
      if (that._element.childElementCount > 1) {
        var oldElement = that._element.firstElementChild;
        // Cleanup and remove previous element
        if (oldElement.winControl) {

          if (oldElement.winControl.unload) {
            oldElement.winControl.unload();
          }
          oldElement.winControl.dispose();
        }
        oldElement.parentNode.removeChild(oldElement);
        oldElement.innerText = "";
      }
    }

    // TODO: archive the old view/viewModel
    this._lastNavigationPromise = WinJS.Promise.as().then(cleanup, cleanup).then(function() {
      return WinJS.UI.Pages.render(args.detail.location,
        newElement,
        args.detail.state);
    });
    args.detail.setPromise(this._lastNavigationPromise);

    this.MessageService.send("navigatingMessage", args);
  },

  _onNavigated: function() {
    this.MessageService.send("navigatedMessage");
  },

  _onResized: function() {
    var view = this.getView();
    if (view && view.updateLayout)
      view.updateLayout();
  },

  getRootElement: function() {
    return this._element;
  },

  setRootElement: function(val) {
    this._element = val;
  },

  createDefaultPageElement: function() {
    var element = document.createElement("div");
    element.setAttribute("dir", window.getComputedStyle(this._element, null).direction);
    element.style.position = "absolute";
    element.style.width = "100%";
    element.style.height = "100%";
    return element;
  },

  getView: function() {
    var viewElem = this.getViewElement();
    if (viewElem && viewElem.winControl)
      return viewElem.winControl
    return false;
  },

  getViewElement: function() {
    return this._element.firstElementChild;
  },

  getViewModel: function() {
    var view = this.getView();
    if (view) {
      var viewModel = view.getViewModel();
      if (viewModel)
        return viewModel;
    }
    return null;
  },

  stop: function() {
    var that = this;
    return base.prototype.start.apply(this, arguments)
      .then(function() {
        WinJS.Navigation.removeEventListener("navigating",
          that._onNavigatingBinding);
        WinJS.Navigation.removeEventListener("navigated",
          that._onNavigatedBinding);
        WinJS.Navigation.removeEventListener("beforenavigate",
          that._onBeforeNavigateBinding);
        that.MessageService.register("navigateToMessage", that._onNavigateToMessageBinding);
        window.removeEventListener("resize", that._onResizedBinding);
        window.removeEventListener("popstate", that._onPopStateBinding)

        if (this._element)
          WinJS.Utilities.disposeSubTree(this._element);
      });
  }
};

var staticMembers = {

};

module.exports = WinJS.Class.derive(base, _constructor,
  instanceMembers, staticMembers);
<commit_msg>Travis CI auto-merge from travis build 186  - [ci skip]<commit_after>var WinJS = require('winjs'),
  ioc = require('../ioc'),
  config = require('../config'),
  base = require('./base'),
  log = require('../log'),
  util = require("util");

var _constructor = function(options) {
  base.call(this, arguments);
  this._onNavigatingBinding = this._onNavigating.bind(this);
  this._onNavigatedBinding = this._onNavigated.bind(this);
  this._onBeforeNavigateBinding = this._onBeforeNavigate.bind(this);
  this._onResizedBinding = this._onResized.bind(this);
  this._onPopStateBinding = this._onPopState.bind(this);
  this._onNavigateToMessageBinding = this._onNavigateToMessage.bind(this);
  this._lastNavigationPromise = WinJS.Promise.as();
  this.MessageService = ioc.getService("message");
  this._element = document.createElement("div");
};

var instanceMembers = {
  start: function() {
    var that = this;
    return base.prototype.start.apply(this, arguments).then(function() {
      WinJS.Navigation.addEventListener("navigating",
        that._onNavigatingBinding);
      WinJS.Navigation.addEventListener("navigated",
        that._onNavigatedBinding);
      WinJS.Navigation.addEventListener("beforenavigate",
        that._onBeforeNavigateBinding);
      that.MessageService.register("navigateToMessage", that._onNavigateToMessageBinding);
      window.addEventListener("resize", that._onResizedBinding);
      window.addEventListener("popstate", that._onPopStateBinding);
    });
  },

  _onPopState: function(event) {
    this.MessageService.send("navigateBackMessage");
  },

  _onNavigateToMessage: function(type, args) {
    var viewKey = args.viewKey;
    var state = args.state;
    var viewTemplateUri = config.get("ui:view:template-uri");
    if (viewTemplateUri) {
      var actualViewTemplateUri = util.format(viewTemplateUri, viewKey);
      var viewClassDef = ioc.getViewDef(viewKey);
      var extendedClassDef = WinJS.UI.Pages.define(actualViewTemplateUri, {}, viewClassDef);
      ioc.override(viewKey, extendedClassDef);
      var vmInstance = ioc.getViewModel(viewKey);
      vmInstance.setKey(viewKey);
      vmInstance.setData(args.state);

      history.pushState({
        context: state,
        key: viewKey
      }, viewKey, viewKey);

      WinJS.Navigation.navigate(actualViewTemplateUri, vmInstance);
    }
  },

  _onBeforeNavigate: function(args) {
    if (this.getViewModel() && this.getViewModel().getBackNavigationDisabled()) {
      log.info("NavigationService: back navigation cancelled based on current vm getBackNavigationDisabled value");
      args.preventDefault();
    }
  },

  _onNavigating: function(args) {
    var that = this;
    var newElement = this.createDefaultPageElement();
    this._element.appendChild(newElement);
    this._lastNavigationPromise.cancel();

    function cleanup() {
      if (that._element.childElementCount > 1) {
        var oldElement = that._element.firstElementChild;
        // Cleanup and remove previous element
        if (oldElement.winControl) {

          if (oldElement.winControl.unload) {
            oldElement.winControl.unload();
          }
          oldElement.winControl.dispose();
        }
        oldElement.parentNode.removeChild(oldElement);
        oldElement.innerText = "";
      }
    }

    // TODO: archive the old view/viewModel
    this._lastNavigationPromise = WinJS.Promise.as().then(cleanup, cleanup).then(function() {
      return WinJS.UI.Pages.render(args.detail.location,
        newElement,
        args.detail.state);
    });
    args.detail.setPromise(this._lastNavigationPromise);

    this.MessageService.send("navigatingMessage", args);
  },

  _onNavigated: function() {
    this.MessageService.send("navigatedMessage");
  },

  _onResized: function() {
    var view = this.getView();
    if (view && view.updateLayout)
      view.updateLayout();
  },

  getRootElement: function() {
    return this._element;
  },

  setRootElement: function(val) {
    this._element = val;
  },

  createDefaultPageElement: function() {
    var element = document.createElement("div");
    element.setAttribute("dir", window.getComputedStyle(this._element, null).direction);
    element.style.position = "absolute";
    element.style.width = "100%";
    element.style.height = "100%";
    return element;
  },

  getView: function() {
    var viewElem = this.getViewElement();
    if (viewElem && viewElem.winControl)
      return viewElem.winControl
    return false;
  },

  getViewElement: function() {
    return this._element.firstElementChild;
  },

  getViewModel: function() {
    var view = this.getView();
    if (view) {
      var viewModel = view.getViewModel();
      if (viewModel)
        return viewModel;
    }
    return null;
  },

  stop: function() {
    var that = this;
    return base.prototype.start.apply(this, arguments)
      .then(function() {
        WinJS.Navigation.removeEventListener("navigating",
          that._onNavigatingBinding);
        WinJS.Navigation.removeEventListener("navigated",
          that._onNavigatedBinding);
        WinJS.Navigation.removeEventListener("beforenavigate",
          that._onBeforeNavigateBinding);
        that.MessageService.register("navigateToMessage", that._onNavigateToMessageBinding);
        window.removeEventListener("resize", that._onResizedBinding);
        window.removeEventListener("popstate", that._onPopStateBinding)

        if (this._element)
          WinJS.Utilities.disposeSubTree(this._element);
      });
  }
};

var staticMembers = {

};

module.exports = WinJS.Class.derive(base, _constructor,
  instanceMembers, staticMembers);
<|endoftext|>


<commit_before>const bodyParser = require('body-parser')
const express = require('express')
const jwt = require('express-jwt')
const path = require('path')
const html = require('@conveyal/woonerf/html')

const env = require('./utils/env').env
const routes = require('./routes')

const app = express()

// middleware
let jwtMiddleWare
if (env.AUTH0_SECRET && process.env.NODE_ENV !== 'test') {
  jwtMiddleWare = jwt({secret: env.AUTH0_SECRET})
} else {
  jwtMiddleWare = (req, res, next) => {
    req.user = {
      sub: 'test-user'
    }
    next()
  }
}
app.use(bodyParser.json())

// static assets
app.use('/assets', express.static(path.resolve(__dirname, '../assets')))

// api
routes(app, jwtMiddleWare)

let htmlString
const title = 'Commute'
if (process.env.STATIC_HOST) {
  // In heroku environment, get js and css from s3
  const host = process.env.STATIC_HOST
  htmlString = `<!DOCTYPE html>

      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
      <link href="${host}assets/index.css" rel="stylesheet">

      <title>${title}</title>

  `
} else {
  htmlString = html({title})
}

// webapp
app.get('*', (req, res) => res.status(200).type('html').send(htmlString))

module.exports = app
<commit_msg>fix(server): properly serve html on various routes<commit_after>const bodyParser = require('body-parser')
const express = require('express')
const jwt = require('express-jwt')
const path = require('path')
const html = require('@conveyal/woonerf/html')

const env = require('./utils/env').env
const routes = require('./routes')

const app = express()

// middleware
let jwtMiddleWare
if (env.AUTH0_SECRET && process.env.NODE_ENV !== 'test') {
  jwtMiddleWare = jwt({secret: env.AUTH0_SECRET})
} else {
  jwtMiddleWare = (req, res, next) => {
    req.user = {
      sub: 'test-user'
    }
    next()
  }
}
app.use(bodyParser.json())

// static assets
app.use('/assets', express.static(path.resolve(__dirname, '../assets')))

// api
routes(app, jwtMiddleWare)

let htmlString
const title = 'Commute'
if (process.env.STATIC_HOST) {
  // In heroku environment, get js and css from s3
  const host = process.env.STATIC_HOST
  htmlString = `<!DOCTYPE html>

      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
      <link href="${host}assets/index.css" rel="stylesheet">

      <title>${title}</title>

  `
} else {
  htmlString = html({title})
}

// webapp
app.get('*', (req, res) => {
  if (['/', '/login'].indexOf(req.originalUrl) === -1) {
    return res.redirect('/')
  }
  res.status(200).type('html').send(htmlString)
})

module.exports = app
<|endoftext|>


<commit_before>var apns = require('apn'),
express = require('express'),
connect = require('connect'),
fs = require('fs'),
path = require('path'),
util = require('util'),
couchdb = require('felix-couchdb');

var dbPort = process.env.COUCH_PORT || 5984;
var dbHost = process.env.COUCH_HOST || 'localhost';
var dbUser = process.env.COUCH_USER || null;
var dbPass = process.env.COUCH_PASS || null;
var client = couchdb.createClient(dbPort, dbHost, dbUser, dbPass);
var db = client.db('node_apn');

// a function to be used as a callback in the event that the push notification service has any errors
var apnErrorCallback = function(errorCode, note) {
    console.log("Push notification error, error code: " + errorCode + " Note: " + util.inspect(note));
}

// a function used to create a Notification object from a hash of parameters
var createNotification = function(params) {
    var note = new apns.notification();
    note.device = new apns.device(params.deviceToken);
    note.alert = params.notificationText;
    if (params.payload) {
        note.payload = {
            'info': params.payload
        }
    }
    note.badge = parseInt(params.badgeNumber, 10);
    note.sound = params.sound;
    return note;
}

var connections = null;
var createConnections = function(result){
    connections = {};
	result.rows.forEach(function(row) {
        var user = row.doc;
        user.applications.forEach(function(application) {
            var settings = application.settings;
            settings.errorCallback = apnErrorCallback;
            var apnsConnection = new apns.connection(settings);
            var appId = application.app_id;
            connections[appId] = apnsConnection;
        });
    });
}

var start = function(){

    // create the express server
    var app = express.createServer(express.logger());

    app.configure(function() {
        app.register('html', require('ejs'));
        app.set('view engine', 'html');
        app.set('views', __dirname + '/../views');
        app.set('view options', {
            layout: "layouts/layout.html"
        })
		app.use(connect.bodyParser());
        app.use(express.methodOverride());
        app.use(express.static(__dirname + '/public'));
    });

    // this is the handler for GET requests to '/'
    app.get('/', function(request, response) {
        response.render('index');
    });

    // this is the handler for POST requests to '/'
    app.post('/', function(request, response) {

        // TODO: Check the appID and appSecret
        // redirect if they don't match
        var appSecret = request.body.appSecret;
        var appId = request.body.appId;

        // create the notification and send it
        var note = createNotification(request.body);
        var connection = connections[appId];
        connection.sendNotification(note);

        // redirect to '/'
        response.redirect("/");
    });

	app.post('/upload', function(request, response){

        var appSecret = request.body.appSecret;
        var appId = request.body.appId;
        var certData = "";
        var keyData = "";
        var certComplete = false;
        var keyComplete = false;
        var certStream = fs.createReadStream(request.files.certificate.path);
        certStream.on('data', function(data){
	        certData = certData + data;
        });
        certStream.on('end', function(){
	        certComplete = true;
			console.log("appId: " + appId);
	        console.log("appSecret: " + appSecret);
	        console.log(certData);
	        if(certComplete && keyComplete){
		        // TODO: save the certData on the appropriate app
		        // TODO: insert a connection into the connections hash for the appropriate app
		        response.redirect("/");
	        }
        });
		var keyStream = fs.createReadStream(request.files.key.path);
        keyStream.on('data', function(data){
	        keyData = keyData + data;
        });
        keyStream.on('end', function(){
	        keyComplete = true;
			console.log("appId: " + appId);
	        console.log("appSecret: " + appSecret);
	        console.log(keyData);
			if(certComplete && keyComplete){
				// TODO: save the keyData on the appropriate app
		        // TODO: insert a connection into the connections hash for the appropriate app
		        response.redirect("/");
	        }
	    });
    });

	app.get('/upload', function(request, response){
	    response.render('upload');
	});

    var port = process.env.PORT || 3000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
}

var resetConnections = function(db, startServer)
{
	db.allDocs({ include_docs: true }, function(er, result) {
	    if (er) throw new Error(JSON.stringify(er));
	    createConnections(result);
	    if(startServer){
		    start();
	    }
	});
}

resetConnections(db, true);
<commit_msg>yay, now you can upload certificates<commit_after>var apns = require('apn'),
express = require('express'),
connect = require('connect'),
fs = require('fs'),
path = require('path'),
util = require('util'),
couchdb = require('felix-couchdb');

var dbPort = process.env.COUCH_PORT || 5984;
var dbHost = process.env.COUCH_HOST || 'localhost';
var dbUser = process.env.COUCH_USER || null;
var dbPass = process.env.COUCH_PASS || null;
var client = couchdb.createClient(dbPort, dbHost, dbUser, dbPass);
var db = client.db('node_apn');

// a function to be used as a callback in the event that the push notification service has any errors
var apnErrorCallback = function(errorCode, note) {
    console.log("Push notification error, error code: " + errorCode + " Note: " + util.inspect(note));
}

// a function used to create a Notification object from a hash of parameters
var createNotification = function(params) {
    var note = new apns.notification();
    note.device = new apns.device(params.deviceToken);
    note.alert = params.notificationText;
    if (params.payload) {
        note.payload = {
            'info': params.payload
        }
    }
    note.badge = parseInt(params.badgeNumber, 10);
    note.sound = params.sound;
    return note;
}

var connections = null;
var createConnections = function(result){
    connections = {};
	result.rows.forEach(function(row) {
        var user = row.doc;
        user.applications.forEach(function(application) {
            var settings = application.settings;
			if(!settings.certData || !settings.keyData ){
				return;
			}
            settings.errorCallback = apnErrorCallback;
            var apnsConnection = new apns.connection(settings);
            var appId = application.app_id;
            connections[appId] = apnsConnection;
        });
    });
}

var start = function(){

    // create the express server
    var app = express.createServer(express.logger());

    app.configure(function() {
        app.register('html', require('ejs'));
        app.set('view engine', 'html');
        app.set('views', __dirname + '/../views');
        app.set('view options', {
            layout: "layouts/layout.html"
        })
		app.use(connect.bodyParser());
        app.use(express.methodOverride());
        app.use(express.static(__dirname + '/public'));
    });

    // this is the handler for GET requests to '/'
    app.get('/', function(request, response) {
        response.render('index');
    });

    // this is the handler for POST requests to '/'
    app.post('/', function(request, response) {

        // TODO: Check the appID and appSecret
        // redirect if they don't match
        var appSecret = request.body.appSecret;
        var appId = request.body.appId;

        // create the notification and send it
        var note = createNotification(request.body);
        var connection = connections[appId];
        connection.sendNotification(note);

        // redirect to '/'
        response.redirect("/");
    });

	app.post('/upload', function(request, response){

        var appSecret = request.body.appSecret;
        var appId = request.body.appId;
        var certData = "";
        var keyData = "";
        var certComplete = false;
        var keyComplete = false;
        var certStream = fs.createReadStream(request.files.certificate.path);
        certStream.on('data', function(data){
	        certData = certData + data;
        });
        certStream.on('end', function(){
	        certComplete = true;
	        if(certComplete && keyComplete){
		        db.allDocs({include_docs:true}, function(er, result){
			        result.rows.forEach(function(row) {
				        var user = row.doc;
				        user.applications.forEach(function(application) {
					        if(application.app_id == appId){
						        application.settings.certData = certData;
						        application.settings.keyData = keyData;
						        db.saveDoc(user._id, user, function(er, result){
							        response.redirect("/");
							        resetConnections(db, false);
						        });
					        }
				        });
				    });
		        });
	        }
        });
		var keyStream = fs.createReadStream(request.files.key.path);
        keyStream.on('data', function(data){
	        keyData = keyData + data;
        });
        keyStream.on('end', function(){
	        keyComplete = true;
			if(certComplete && keyComplete){
		        db.allDocs({include_docs:true}, function(er, result){
			        result.rows.forEach(function(row) {
				        var user = row.doc;
				        user.applications.forEach(function(application) {
					        if(application.app_id == appId){
						        application.settings.certData = certData;
						        application.settings.keyData = keyData;
						        db.saveDoc(user._id, user, function(er, result){
							        response.redirect("/");
							        resetConnections(db, false);
						        });
					        }
				        });
				    });
		        });
	        }
	    });
    });

	app.get('/upload', function(request, response){
	    response.render('upload');
	});

    var port = process.env.PORT || 3000;
    app.listen(port, function() {
        console.log("Listening on " + port);
    });
}

var resetConnections = function(db, startServer)
{
	db.allDocs({ include_docs: true }, function(er, result) {
	    if (er) throw new Error(JSON.stringify(er));
	    createConnections(result);
	    if(startServer){
		    start();
	    }
	});
}

resetConnections(db, true);
<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require('fs')
var path = require('path')
var async = require('async')
var _ = require('underscore')._
var winston = require('winston')

var optimist = require('optimist')
  .options('output', {
    alias: 'o'
  , 'default': 'output'
  , describe: 'Name for the output files.'
  })
  .options('path', {
    alias: 'u'
  , 'default': ''
  , describe: 'Path for files to be used on final JSON.'
  })
  .options('export', {
    alias: 'e'
  , 'default': 'ogg,m4a,mp3,ac3'
  , describe: 'Limit exported file types. Comma separated extension list.'
  })
  .options('format', {
    alias: 'f'
  , 'default': 'jukebox'
  , describe: 'Format of the output JSON file (jukebox, howler).'
  })
  .options('log', {
    alias: 'l'
  , 'default': 'info'
  , describe: 'Log level (debug, info, notice, warning, error).'
  })
  .options('autoplay', {
    alias: 'a'
  , 'default': null
  , describe: 'Autoplay sprite name.'
  })
  .options('loop', {
    'default': null
  , describe: 'Loop sprite name, can be passed multiple times.'
  })
  .options('silence', {
    alias: 's'
  , 'default': 0
  , describe: 'Add special "silence" track with specified duration.'
  })
  .options('gap', {
    alias: 'g'
  , 'default': 1
  , describe: 'Silence gap between sounds (in seconds).'
  })
  .options('minlength', {
    alias: 'm'
  , 'default': 0
  , describe: 'Minimum sound duration (in seconds).'
  })
  .options('bitrate', {
    alias: 'b'
  , 'default': 128
  , describe: 'Bit rate. Works for: ac3, mp3, mp4, m4a, ogg.'
  })
  .options('vbr', {
    alias: 'v'
  , 'default': -1
  , describe: 'VBR [0-9]. Works for: mp3. -1 disables VBR.'
  })
  .options('samplerate', {
    alias: 'r'
  , 'default': 44100
  , describe: 'Sample rate.'
  })
  .options('channels', {
    alias: 'c'
  , 'default': 1
  , describe: 'Number of channels (1=mono, 2=stereo).'
  })
  .options('rawparts', {
    alias: 'p'
  , 'default': ''
  , describe: 'Include raw slices(for Web Audio API) in specified formats.'
  })
  .options('help', {
    alias: 'h'
  , describe: 'Show this help message.'
  })

var argv = optimist.argv

winston.remove(winston.transports.Console)
winston.add(winston.transports.Console, {
  colorize: true
, level: argv.log
, handleExceptions: false
})
winston.debug('Parsed arguments', argv)

var BIT_RATE = parseInt(argv.bitrate, 10)
var SAMPLE_RATE = parseInt(argv.samplerate, 10)
var NUM_CHANNELS = parseInt(argv.channels, 10)
var GAP_SECONDS = parseFloat(argv.gap)
var MINIMUM_SOUND_LENGTH = parseFloat(argv.minlength)
var VBR = parseInt(argv.vbr, 10)

var loops = argv.loop ? [].concat(argv.loop) : []

var files = _.uniq(argv._)

if (argv.help || !files.length) {
  if (!argv.help) {
    winston.error('No input files specified.')
  }
  winston.info('Usage: audiosprite [options] file1.mp3 file2.mp3 *.wav')
  winston.info(optimist.help())
  process.exit(1)
}

// make sure output directory exists
var outputDir = path.dirname(argv.output)
if (!fs.existsSync(outputDir)) {
  require('mkdirp').sync(outputDir)
}

var offsetCursor = 0
var wavArgs = ['-ar', SAMPLE_RATE, '-ac', NUM_CHANNELS, '-f', 's16le']
var tempFile = mktemp('audiosprite')

winston.debug('Created temporary file', { file: tempFile })

var json = {
  resources: []
, spritemap: {}
}

spawn('ffmpeg', ['-version']).on('exit', function(code) {
  if (code) {
    winston.error('ffmpeg was not found on your path')
    process.exit(1)
  }
  if (argv.silence) {
    json.spritemap.silence = {
      start: 0
    , end: argv.silence
    , loop: true
    }
    if (!argv.autoplay) {
      json.autoplay = 'silence'
    }
    appendSilence(argv.silence + GAP_SECONDS, tempFile, processFiles)
  } else {
    processFiles()
  }
})

function mktemp(prefix) {
  var tmpdir = require('os').tmpDir() || '.'
  return path.join(tmpdir, prefix + '.' + Math.random().toString().substr(2))
}

function spawn(name, opt) {
  winston.debug('Spawn', { cmd: [name].concat(opt).join(' ') })
  return require('child_process').spawn(name, opt)
}

function pad(num, size) {
  var str = num.toString()
  while (str.length < size) {
    str = '0' + str
  }
  return str
}

function makeRawAudioFile(src, cb) {
  var dest = mktemp('audiosprite')

  winston.debug('Start processing', { file: src})

  fs.exists(src, function(exists) {
    if (exists) {
      var ffmpeg = spawn('ffmpeg', ['-i', path.resolve(src)]
        .concat(wavArgs).concat('pipe:'))
      ffmpeg.stdout.pipe(fs.createWriteStream(dest, {flags: 'w'}))
      ffmpeg.on('exit', function(code, signal) {
        if (code) {
          return cb({
            msg: 'File could not be added',
            file: src,
            retcode: code,
            signal: signal
          })
        }
        cb(null, dest)
      })
    }
    else {
      cb({ msg: 'File does not exist', file: src })
    }
  })
}

function appendFile(name, src, dest, cb) {
  var size = 0
  var reader = fs.createReadStream(src)
  var writer = fs.createWriteStream(dest, {
    flags: 'a'
  })
  reader.on('data', function(data) {
    size += data.length
  })
  reader.on('close', function() {
    var originalDuration = size / SAMPLE_RATE / NUM_CHANNELS / 2
    winston.info('File added OK', { file: src, duration: originalDuration })
    var extraDuration = Math.max(0, MINIMUM_SOUND_LENGTH - originalDuration)
    var duration = originalDuration + extraDuration
    json.spritemap[name] = {
      start: offsetCursor
    , end: offsetCursor + duration
    , loop: name === argv.autoplay || loops.indexOf(name) !== -1
    }
    offsetCursor += originalDuration
    appendSilence(extraDuration + Math.ceil(duration) - duration + GAP_SECONDS, dest, cb)
  })
  reader.pipe(writer)
}

function appendSilence(duration, dest, cb) {
  var buffer = new Buffer(Math.round(SAMPLE_RATE * 2 * NUM_CHANNELS * duration))
  buffer.fill(0)
  var writeStream = fs.createWriteStream(dest, { flags: 'a' })
  writeStream.end(buffer)
  writeStream.on('close', function() {
    winston.info('Silence gap added', { duration: duration })
    offsetCursor += duration
    cb()
  })
}

exportFile = function(src, dest, ext, opt, store, cb) {
  var outfile = dest + '.' + ext
  spawn('ffmpeg',['-y', '-ar', SAMPLE_RATE, '-ac', NUM_CHANNELS, '-f', 's16le', '-i', src]
      .concat(opt).concat(outfile))
    .on('exit', function(code, signal) {
      if (code) {
        return cb({
          msg: 'Error exporting file',
          format: ext,
          retcode: code,
          signal: signal
        })
      }
      if (ext === 'aiff') {
        exportFileCaf(outfile, dest + '.caf', function(err) {
          if (!err && store) {
            json.resources.push(dest + '.caf')
          }
          fs.unlinkSync(outfile)
          cb()
        })
      } else {
        winston.info('Exported ' + ext + ' OK', { file: outfile })
        if (store) {
          json.resources.push(outfile)
        }
        cb()
      }
    })
}

function exportFileCaf(src, dest, cb) {
  if (process.platform !== 'darwin') {
    return cb(true)
  }
  spawn('afconvert', ['-f', 'caff', '-d', 'ima4', src, dest])
    .on('exit', function(code, signal) {
      if (code) {
        return cb({
          msg: 'Error exporting file',
          format: 'caf',
          retcode: code,
          signal: signal
        })
      }
      winston.info('Exported caf OK', { file: dest })
      return cb()
    })
}

function processFiles() {
  var formats = {
    aiff: []
  , wav: []
  , ac3: ['-acodec', 'ac3', '-ab', BIT_RATE + 'k']
  , mp3: ['-ar', SAMPLE_RATE, '-f', 'mp3']
  , mp4: ['-ab', BIT_RATE + 'k']
  , m4a: ['-ab', BIT_RATE + 'k']
  , ogg: ['-acodec', 'libvorbis', '-f', 'ogg', '-ab', BIT_RATE + 'k']
  }

  if (VBR >= 0 && VBR <= 9) {
    formats.mp3 = formats.mp3.concat(['-aq', VBR])
  }
  else {
    formats.mp3 = formats.mp3.concat(['-ab', BIT_RATE + 'k'])
  }

  if (argv.export.length) {
    formats = argv.export.split(',').reduce(function(memo, val) {
      if (formats[val]) {
        memo[val] = formats[val]
      }
      return memo
    }, {})
  }

  var rawparts = argv.rawparts.length ? argv.rawparts.split(',') : null
  var i = 0
  async.forEachSeries(files, function(file, cb) {
    i++
    makeRawAudioFile(file, function(err, tmp) {
      if (err) {
        return cb(err)
      }

      function tempProcessed() {
        fs.unlinkSync(tmp)
        cb()
      }

      var name = path.basename(file).replace(/\.[a-zA-Z0-9]+$/, '')
      appendFile(name, tmp, tempFile, function(err) {
        if (rawparts != null ? rawparts.length : void 0) {
        async.forEachSeries(rawparts, function(ext, cb) {
          winston.debug('Start export slice', { name: name, format: ext, i: i })
          exportFile(tmp, argv.output + '_' + pad(i, 3), ext, formats[ext]
            , false, cb)
          }, tempProcessed)
        } else {
          tempProcessed()
        }
      })
    })
  }, function(err) {
    if (err) {
      winston.error('Error adding file', err)
      process.exit(1)
    }
    async.forEachSeries(Object.keys(formats), function(ext, cb) {
      winston.debug('Start export', { format: ext })
      exportFile(tempFile, argv.output, ext, formats[ext], true, cb)
    }, function(err) {
      if (err) {
        winston.error('Error exporting file', err)
        process.exit(1)
      }
      if (argv.autoplay) {
        json.autoplay = argv.autoplay
      }

      json.resources = json.resources.map(function(e) {
        return argv.path ? path.join(argv.path, path.basename(e)) : e
      })

      var finalJson = {}

      switch (argv.format) {

        case 'howler':
          finalJson.urls = [].concat(json.resources)
          finalJson.sprite = {}
          for (var sn in json.spritemap) {
            var spriteInfo = json.spritemap[sn]
            finalJson.sprite[sn] = [spriteInfo.start * 1000, (spriteInfo.end - spriteInfo.start) * 1000]
            if (spriteInfo.loop) {
              finalJson.sprite[sn].push(true)
            }
          }
          break

        case 'createjs':
          finalJson.src = json.resources[0];
          finalJson.data = {audioSprite: []};
          for (var sn in json.spritemap) {
            var spriteInfo = json.spritemap[sn]
            finalJson.data.audioSprite.push({
              id: sn,
              startTime: spriteInfo.start * 1000,
              duration: (spriteInfo.end - spriteInfo.start) * 1000
            });
          }
          break;

        case 'default': // legacy support
        default:
          finalJson = json
          break
      }

      var jsonfile = argv.output + '.json'
      fs.writeFileSync(jsonfile, JSON.stringify(finalJson, null, 2))
      winston.info('Exported json OK', { file: jsonfile })
      fs.unlinkSync(tempFile)
      winston.info('All done')
    })
  })
}
<commit_msg>Updated output format list in help description.<commit_after>#!/usr/bin/env node

var fs = require('fs')
var path = require('path')
var async = require('async')
var _ = require('underscore')._
var winston = require('winston')

var optimist = require('optimist')
  .options('output', {
    alias: 'o'
  , 'default': 'output'
  , describe: 'Name for the output files.'
  })
  .options('path', {
    alias: 'u'
  , 'default': ''
  , describe: 'Path for files to be used on final JSON.'
  })
  .options('export', {
    alias: 'e'
  , 'default': 'ogg,m4a,mp3,ac3'
  , describe: 'Limit exported file types. Comma separated extension list.'
  })
  .options('format', {
    alias: 'f'
  , 'default': 'jukebox'
  , describe: 'Format of the output JSON file (jukebox, howler, createjs).'
  })
  .options('log', {
    alias: 'l'
  , 'default': 'info'
  , describe: 'Log level (debug, info, notice, warning, error).'
  })
  .options('autoplay', {
    alias: 'a'
  , 'default': null
  , describe: 'Autoplay sprite name.'
  })
  .options('loop', {
    'default': null
  , describe: 'Loop sprite name, can be passed multiple times.'
  })
  .options('silence', {
    alias: 's'
  , 'default': 0
  , describe: 'Add special "silence" track with specified duration.'
  })
  .options('gap', {
    alias: 'g'
  , 'default': 1
  , describe: 'Silence gap between sounds (in seconds).'
  })
  .options('minlength', {
    alias: 'm'
  , 'default': 0
  , describe: 'Minimum sound duration (in seconds).'
  })
  .options('bitrate', {
    alias: 'b'
  , 'default': 128
  , describe: 'Bit rate. Works for: ac3, mp3, mp4, m4a, ogg.'
  })
  .options('vbr', {
    alias: 'v'
  , 'default': -1
  , describe: 'VBR [0-9]. Works for: mp3. -1 disables VBR.'
  })
  .options('samplerate', {
    alias: 'r'
  , 'default': 44100
  , describe: 'Sample rate.'
  })
  .options('channels', {
    alias: 'c'
  , 'default': 1
  , describe: 'Number of channels (1=mono, 2=stereo).'
  })
  .options('rawparts', {
    alias: 'p'
  , 'default': ''
  , describe: 'Include raw slices(for Web Audio API) in specified formats.'
  })
  .options('help', {
    alias: 'h'
  , describe: 'Show this help message.'
  })

var argv = optimist.argv

winston.remove(winston.transports.Console)
winston.add(winston.transports.Console, {
  colorize: true
, level: argv.log
, handleExceptions: false
})
winston.debug('Parsed arguments', argv)

var BIT_RATE = parseInt(argv.bitrate, 10)
var SAMPLE_RATE = parseInt(argv.samplerate, 10)
var NUM_CHANNELS = parseInt(argv.channels, 10)
var GAP_SECONDS = parseFloat(argv.gap)
var MINIMUM_SOUND_LENGTH = parseFloat(argv.minlength)
var VBR = parseInt(argv.vbr, 10)

var loops = argv.loop ? [].concat(argv.loop) : []

var files = _.uniq(argv._)

if (argv.help || !files.length) {
  if (!argv.help) {
    winston.error('No input files specified.')
  }
  winston.info('Usage: audiosprite [options] file1.mp3 file2.mp3 *.wav')
  winston.info(optimist.help())
  process.exit(1)
}

// make sure output directory exists
var outputDir = path.dirname(argv.output)
if (!fs.existsSync(outputDir)) {
  require('mkdirp').sync(outputDir)
}

var offsetCursor = 0
var wavArgs = ['-ar', SAMPLE_RATE, '-ac', NUM_CHANNELS, '-f', 's16le']
var tempFile = mktemp('audiosprite')

winston.debug('Created temporary file', { file: tempFile })

var json = {
  resources: []
, spritemap: {}
}

spawn('ffmpeg', ['-version']).on('exit', function(code) {
  if (code) {
    winston.error('ffmpeg was not found on your path')
    process.exit(1)
  }
  if (argv.silence) {
    json.spritemap.silence = {
      start: 0
    , end: argv.silence
    , loop: true
    }
    if (!argv.autoplay) {
      json.autoplay = 'silence'
    }
    appendSilence(argv.silence + GAP_SECONDS, tempFile, processFiles)
  } else {
    processFiles()
  }
})

function mktemp(prefix) {
  var tmpdir = require('os').tmpDir() || '.'
  return path.join(tmpdir, prefix + '.' + Math.random().toString().substr(2))
}

function spawn(name, opt) {
  winston.debug('Spawn', { cmd: [name].concat(opt).join(' ') })
  return require('child_process').spawn(name, opt)
}

function pad(num, size) {
  var str = num.toString()
  while (str.length < size) {
    str = '0' + str
  }
  return str
}

function makeRawAudioFile(src, cb) {
  var dest = mktemp('audiosprite')

  winston.debug('Start processing', { file: src})

  fs.exists(src, function(exists) {
    if (exists) {
      var ffmpeg = spawn('ffmpeg', ['-i', path.resolve(src)]
        .concat(wavArgs).concat('pipe:'))
      ffmpeg.stdout.pipe(fs.createWriteStream(dest, {flags: 'w'}))
      ffmpeg.on('exit', function(code, signal) {
        if (code) {
          return cb({
            msg: 'File could not be added',
            file: src,
            retcode: code,
            signal: signal
          })
        }
        cb(null, dest)
      })
    }
    else {
      cb({ msg: 'File does not exist', file: src })
    }
  })
}

function appendFile(name, src, dest, cb) {
  var size = 0
  var reader = fs.createReadStream(src)
  var writer = fs.createWriteStream(dest, {
    flags: 'a'
  })
  reader.on('data', function(data) {
    size += data.length
  })
  reader.on('close', function() {
    var originalDuration = size / SAMPLE_RATE / NUM_CHANNELS / 2
    winston.info('File added OK', { file: src, duration: originalDuration })
    var extraDuration = Math.max(0, MINIMUM_SOUND_LENGTH - originalDuration)
    var duration = originalDuration + extraDuration
    json.spritemap[name] = {
      start: offsetCursor
    , end: offsetCursor + duration
    , loop: name === argv.autoplay || loops.indexOf(name) !== -1
    }
    offsetCursor += originalDuration
    appendSilence(extraDuration + Math.ceil(duration) - duration + GAP_SECONDS, dest, cb)
  })
  reader.pipe(writer)
}

function appendSilence(duration, dest, cb) {
  var buffer = new Buffer(Math.round(SAMPLE_RATE * 2 * NUM_CHANNELS * duration))
  buffer.fill(0)
  var writeStream = fs.createWriteStream(dest, { flags: 'a' })
  writeStream.end(buffer)
  writeStream.on('close', function() {
    winston.info('Silence gap added', { duration: duration })
    offsetCursor += duration
    cb()
  })
}

exportFile = function(src, dest, ext, opt, store, cb) {
  var outfile = dest + '.' + ext
  spawn('ffmpeg',['-y', '-ar', SAMPLE_RATE, '-ac', NUM_CHANNELS, '-f', 's16le', '-i', src]
      .concat(opt).concat(outfile))
    .on('exit', function(code, signal) {
      if (code) {
        return cb({
          msg: 'Error exporting file',
          format: ext,
          retcode: code,
          signal: signal
        })
      }
      if (ext === 'aiff') {
        exportFileCaf(outfile, dest + '.caf', function(err) {
          if (!err && store) {
            json.resources.push(dest + '.caf')
          }
          fs.unlinkSync(outfile)
          cb()
        })
      } else {
        winston.info('Exported ' + ext + ' OK', { file: outfile })
        if (store) {
          json.resources.push(outfile)
        }
        cb()
      }
    })
}

function exportFileCaf(src, dest, cb) {
  if (process.platform !== 'darwin') {
    return cb(true)
  }
  spawn('afconvert', ['-f', 'caff', '-d', 'ima4', src, dest])
    .on('exit', function(code, signal) {
      if (code) {
        return cb({
          msg: 'Error exporting file',
          format: 'caf',
          retcode: code,
          signal: signal
        })
      }
      winston.info('Exported caf OK', { file: dest })
      return cb()
    })
}

function processFiles() {
  var formats = {
    aiff: []
  , wav: []
  , ac3: ['-acodec', 'ac3', '-ab', BIT_RATE + 'k']
  , mp3: ['-ar', SAMPLE_RATE, '-f', 'mp3']
  , mp4: ['-ab', BIT_RATE + 'k']
  , m4a: ['-ab', BIT_RATE + 'k']
  , ogg: ['-acodec', 'libvorbis', '-f', 'ogg', '-ab', BIT_RATE + 'k']
  }

  if (VBR >= 0 && VBR <= 9) {
    formats.mp3 = formats.mp3.concat(['-aq', VBR])
  }
  else {
    formats.mp3 = formats.mp3.concat(['-ab', BIT_RATE + 'k'])
  }

  if (argv.export.length) {
    formats = argv.export.split(',').reduce(function(memo, val) {
      if (formats[val]) {
        memo[val] = formats[val]
      }
      return memo
    }, {})
  }

  var rawparts = argv.rawparts.length ? argv.rawparts.split(',') : null
  var i = 0
  async.forEachSeries(files, function(file, cb) {
    i++
    makeRawAudioFile(file, function(err, tmp) {
      if (err) {
        return cb(err)
      }

      function tempProcessed() {
        fs.unlinkSync(tmp)
        cb()
      }

      var name = path.basename(file).replace(/\.[a-zA-Z0-9]+$/, '')
      appendFile(name, tmp, tempFile, function(err) {
        if (rawparts != null ? rawparts.length : void 0) {
        async.forEachSeries(rawparts, function(ext, cb) {
          winston.debug('Start export slice', { name: name, format: ext, i: i })
          exportFile(tmp, argv.output + '_' + pad(i, 3), ext, formats[ext]
            , false, cb)
          }, tempProcessed)
        } else {
          tempProcessed()
        }
      })
    })
  }, function(err) {
    if (err) {
      winston.error('Error adding file', err)
      process.exit(1)
    }
    async.forEachSeries(Object.keys(formats), function(ext, cb) {
      winston.debug('Start export', { format: ext })
      exportFile(tempFile, argv.output, ext, formats[ext], true, cb)
    }, function(err) {
      if (err) {
        winston.error('Error exporting file', err)
        process.exit(1)
      }
      if (argv.autoplay) {
        json.autoplay = argv.autoplay
      }

      json.resources = json.resources.map(function(e) {
        return argv.path ? path.join(argv.path, path.basename(e)) : e
      })

      var finalJson = {}

      switch (argv.format) {

        case 'howler':
          finalJson.urls = [].concat(json.resources)
          finalJson.sprite = {}
          for (var sn in json.spritemap) {
            var spriteInfo = json.spritemap[sn]
            finalJson.sprite[sn] = [spriteInfo.start * 1000, (spriteInfo.end - spriteInfo.start) * 1000]
            if (spriteInfo.loop) {
              finalJson.sprite[sn].push(true)
            }
          }
          break

        case 'createjs':
          finalJson.src = json.resources[0];
          finalJson.data = {audioSprite: []};
          for (var sn in json.spritemap) {
            var spriteInfo = json.spritemap[sn]
            finalJson.data.audioSprite.push({
              id: sn,
              startTime: spriteInfo.start * 1000,
              duration: (spriteInfo.end - spriteInfo.start) * 1000
            });
          }
          break;

        case 'default': // legacy support
        default:
          finalJson = json
          break
      }

      var jsonfile = argv.output + '.json'
      fs.writeFileSync(jsonfile, JSON.stringify(finalJson, null, 2))
      winston.info('Exported json OK', { file: jsonfile })
      fs.unlinkSync(tempFile)
      winston.info('All done')
    })
  })
}
<|endoftext|>


<commit_before>import PureComponent from '../PureComponent';
var React = require('react');
import Input from 'react-toolbox/lib/input';
import {IconMenu, Menu, MenuItem} from 'react-toolbox/lib/menu';
var classNames = require('classnames');
var Rx = require('../rx').default;
import Tooltip from 'react-toolbox/lib/tooltip';
import {createVignette} from '../containers/vignette';
import HelpBox from '../views/HelpBox';

var TooltipInput = Tooltip(({inputRef, ...props}) =>
		<Input innerRef={inputRef} {...props}/>);
var TooltipI = Tooltip('i');

// The Tooltip component will get mouse enter/leave events on menu children,
// causing the tooltip to display in front of the menu when it is open. There's
// no way to disable the Tooltip while the menu is open. This is a workaround
// that intercepts the onMouseEnter method that is injected by the Tooltip
// component, and filters events based on the event target type.
class IconMenuWrapper extends PureComponent {
	onMouseEnter = ev => {
		if (ev.target.type === 'button') {
			this.props.onMouseEnter(ev);
		}
	}
	render() {
		var {onMouseEnter, ...props} = this.props; //eslint-disable-line no-unused-vars
		return <IconMenu onMouseEnter={this.onMouseEnter} {...props}/>;
	}
}

var TooltipIconMenu = Tooltip(IconMenuWrapper);

// Styles
var compStyles = require('./SampleSearch.module.css');

//var SampleIDInput = React.createClass({
//	getInitialState() {
//		return {
//			show: false,
//			value: undefined
//		};
//	},
//	onChange (ev) {
//		var value = ev.target.value;
//		this.setState({value});
//	},
//	close () {
//		this.setState({ show: false});
//	},
//	submit () {
//		var samplesList = this.state.value.split(/\s+/),
//			{onSearchIDAndFilterColumn, onSamplesSubmit} = this.props;
//		this.close();
//		this.state.value = '';
//		onSearchIDAndFilterColumn(samplesList);
//		onSamplesSubmit("A:true"); // since the new column is always column A
//	},
//	render() {
//		var tooltipButton = <Tooltip>Search by sample IDs</Tooltip>,
//			tooltipModalString = 'Samples match your list will be highlighted. \
//				A new binary column will be made using your list: matched samples vs. the rest.',
//			tooltipModal = <Tooltip>{tooltipModalString}</Tooltip>,
//			{cohortSamples, disabled} = this.props,
//			help = 'e.g.\n' + Object.values(cohortSamples)[0].slice(0, 5).join('\n') + '\n...';
//
//		return (
//
//				<OverlayTrigger trigger={['hover']} placement="top" overlay={tooltipButton}>
//					<Button
//						bsSize = "small"
//						onClick = {() => this.setState({ show: true})}
//						disabled={disabled}>
//						Custom Sample List
//					</Button>
//				</OverlayTrigger>
//				<Modal
//					show={this.state.show}
//					onHide={this.close}
//					container={this}
//					aria-labelledby="contained-modal-title">
//					<Modal.Header closeButton>
//						<Modal.Title id="contained-modal-title">
//							Enter a list of samaple IDs to highlight
//							<OverlayTrigger trigger={['hover']} placement="right" overlay={tooltipModal}>
//
//							</OverlayTrigger>
//						</Modal.Title>
//					</Modal.Header>
//					<Modal.Body>
//						<Input style={{width: 550, height: 200}}
//							value={this.state.value}
//							type ="textarea"
//							placeholder={help}
//							onChange={this.onChange}/>
//					</Modal.Body>
//					<Modal.Footer>
//						<Button bsStyle="primary" onClick={this.submit} disabled={!this.state.value}>Submit</Button>
//						<Button onClick={this.close}>Cancel</Button>
//					</Modal.Footer>
//				</Modal>
//
//		);
//	}
//});

function findSubExpr(offsets, i) {
	var j = 0;
	while (i > offsets[j]) {
		++j;
	}
	return j;
}
var tooltips = {
	filter: 'Filter samples',
	subgroup: 'Create sample subgroups from data on the screen',
	find: 'Highlight or zoom in on a selection of samples',
	clear: 'Clear current filter and show all samples in the study',
	input: 'Examples: "missense", "null","B:>2"',
	keep: 'Keep only the selected samples on the screen',
	remove: 'Remove the selected samples from the screen',
	makeSubgroup: 'Matched samples will be one subgroup and non-matched samples will be the other subgroup',
	highlight: 'Highlight selected samples',
	zoom: 'Zoom in to selected samples',
	history: 'Previous search text'
};

var placeholder = {
	true: 'Click on visual spreadsheet or type here to select samples',
	false: 'Type here to select samples'
};

var input = comp => {
	var {matches, mode, history, pickSamples} = comp.props,
		{value} = comp.state,
		hasHistory = history.length > 0,
		noshow = (mode !== "heatmap");

	return (<TooltipInput key='input'
			className={classNames(compStyles.inputContainer, pickSamples && compStyles.picking)}
			tooltip={tooltips.input}
			onKeyUp={comp.onCaret}
			onClick={comp.onCaret}
			onFocus={comp.onCaret}
			onBlur={comp.onHideCaret}
			inputRef={comp.setRef}
			spellCheck={false}
			type='text'
			value={value || ''}
			placeholder={placeholder[pickSamples]}
			onChange={comp.onChange}
			disabled={noshow}>
		<TooltipI tooltip={tooltips.history} onClick={hasHistory ? comp.onOpenHistory : null}
				className={classNames(compStyles.dropDownArrow,
					hasHistory && compStyles.hasHistory, 'material-icons')}>
				arrow_drop_down</TooltipI>

		<Menu theme={{static: compStyles.history, active: compStyles.historyActive}}
				onShow={comp.onShowHistory} onHide={comp.onHideHistory}
				position='static' active={comp.state.historyOpen}>
			{history.map((b, i) =>
					// Note that Menu onSelect is broken in dev because of component
					// identity comparisons, and MenuItem events are broken in prod
					// because of RT deferring callbacks with a setState call, thus
					// allowing currentTarget to be nulled, so our only option here
					// is to create closures on every render. Very annoying.
					<MenuItem className={compStyles.menuItem} onClick={() => comp.onHistory(b)}
						data-value={b} key={i} value={b} caption={b}/>)}
		</Menu>
		{`${matches} matching samples`}
	</TooltipInput>);
};

const searchHelp = 'https://ucsc-xena.gitbook.io/project/overview-of-features/filter-and-subgrouping';

var helpSteps = [
	'pickHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

Filtering and subgrouping

Toggle dropper mode to enable selecting samples interactively,
			then click or drag on a data column to add rows to your selection.

		</HelpBox>),
	'inputHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

As you select rows, the search expression will update with your selection.

You can also type a search expression here without using the dropper mode.

		</HelpBox>),
	'actionHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

When you have added all your rows of interest, use the menu to filter or create subgroups from your selection.

			Filter/subgroup documentation
		</HelpBox>)
];

class Search extends PureComponent {
	state = {value: this.props.value};

	componentDidMount() {
		var highlight = new Rx.Subject();
		this.highlight = highlight.next.bind(highlight);
		this.sub = highlight.startWith(undefined).distinctUntilChanged()
			.subscribe(hl => this.props.onHighlightSelect(hl));
	}

	componentWillUnmount() {
		this.sub.unsubscribe();
	}

	componentWillReceiveProps(newProps) {
		if (this.state.value === this.props.value) {
			this.setState({value: newProps.value});
		}
		// otherwise we have buffered changes to state, and
		// updating from props would revert the user input
		// and move the carat to the end.
	}

	componentDidUpdate() {
		var oldSearch = this.props.oldSearch;
		// oldSearch will update before value, so we have to check for both.
		if (oldSearch != null && this.props.value) {
			// We have to set focus for the selection to be visible. If this
			// is problematic, we'll need to implement our own selection highlight
			// until the element has focus.
			this.input.focus({preventScroll: true});
			this.input.setSelectionRange(oldSearch.length, this.props.value.length);
			this.input.scrollLeft = this.input.scrollWidth;
		}
	}

	onChange = (value) => {
		var {onChange} = this.props;
		this.setState({value});
		onChange(value);
	};

	onSamplesSubmit = (value) => {
		var {onChange} = this.props;
		this.setState({value});
		onChange(value);
	};

	setRef = ref => {
		this.input = ref && ref.inputNode;
	}

	onCaret = () => {
		var hl = this.input && this.props.offsets ?
			findSubExpr(this.props.offsets, this.input.selectionStart) :
			undefined;
		this.highlight(hl);
	}

	onHideCaret = () => {
		this.highlight(undefined);
	}

	onOpenHistory = () => {
		this.setState({historyOpen: !this.state.historyOpen});
	}

	onHistory = value => {
		this.setState({historyOpen: false});
		this.props.onHistory(value); // move to front of history
		this.props.onChange(value);
	}

	// These two are hacks to keep local state synced with the menu, e.g.
	// when the user closes it by clicking outside the menu.
	onShowHistory = () => {
		this.setState({historyOpen: true});
	}

	onHideHistory = () => {
		this.setState({historyOpen: false});
	}

	onRemove = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onFilter(true);
	}

	onKeep = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onFilter(false);
	}

	onCreateColumn = () => {
		// have to add to history before the columns reorder, to
		// prevent the history having wrong column ids.
		this.props.onHistory(this.state.value.trim());
		this.props.onCreateColumn();
	}

	onZoom = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onZoom();
	}

	render() {
		var {matches, sampleCount, mode, pickSamples, onPickSamples, onResetSampleFilter} = this.props,
			{inputHelp, pickHelp, actionHelp} = this.props,
			disableActions = !(matches > 0 && matches < sampleCount),
			noshow = (mode !== "heatmap");

		return (

				{inputHelp(input(this))}
				{pickHelp(
					<TooltipI className={classNames('material-icons', pickSamples ? compStyles.dark : '')}
						onClick={onPickSamples} tooltip='Pick samples'>colorize</TooltipI>)}
				{noshow ? filter_list :
				actionHelp(
					<TooltipIconMenu tooltip='Filter / subgroup actions' className={compStyles.filterMenu} icon='filter_list' iconRipple={false} position='topLeft'>
						<MenuItem disabled={disableActions} caption='Keep samples' onClick={this.onKeep}/>
						<MenuItem disabled={disableActions} caption='Remove samples' onClick={this.onRemove}/>
						<MenuItem caption='Clear samples filter' onClick={onResetSampleFilter}/>
						<MenuItem disabled={disableActions} caption='Zoom' onClick={this.onZoom}/>
						<MenuItem disabled={disableActions} caption='New subgroup column' onClick={this.onCreateColumn}/>
					</TooltipIconMenu>)}
				help_outline

		);
	}
}

export var SampleSearch = createVignette(helpSteps, Search);
<commit_msg>Update search placeholder<commit_after>import PureComponent from '../PureComponent';
var React = require('react');
import Input from 'react-toolbox/lib/input';
import {IconMenu, Menu, MenuItem} from 'react-toolbox/lib/menu';
var classNames = require('classnames');
var Rx = require('../rx').default;
import Tooltip from 'react-toolbox/lib/tooltip';
import {createVignette} from '../containers/vignette';
import HelpBox from '../views/HelpBox';

var TooltipInput = Tooltip(({inputRef, ...props}) =>
		<Input innerRef={inputRef} {...props}/>);
var TooltipI = Tooltip('i');

// The Tooltip component will get mouse enter/leave events on menu children,
// causing the tooltip to display in front of the menu when it is open. There's
// no way to disable the Tooltip while the menu is open. This is a workaround
// that intercepts the onMouseEnter method that is injected by the Tooltip
// component, and filters events based on the event target type.
class IconMenuWrapper extends PureComponent {
	onMouseEnter = ev => {
		if (ev.target.type === 'button') {
			this.props.onMouseEnter(ev);
		}
	}
	render() {
		var {onMouseEnter, ...props} = this.props; //eslint-disable-line no-unused-vars
		return <IconMenu onMouseEnter={this.onMouseEnter} {...props}/>;
	}
}

var TooltipIconMenu = Tooltip(IconMenuWrapper);

// Styles
var compStyles = require('./SampleSearch.module.css');

//var SampleIDInput = React.createClass({
//	getInitialState() {
//		return {
//			show: false,
//			value: undefined
//		};
//	},
//	onChange (ev) {
//		var value = ev.target.value;
//		this.setState({value});
//	},
//	close () {
//		this.setState({ show: false});
//	},
//	submit () {
//		var samplesList = this.state.value.split(/\s+/),
//			{onSearchIDAndFilterColumn, onSamplesSubmit} = this.props;
//		this.close();
//		this.state.value = '';
//		onSearchIDAndFilterColumn(samplesList);
//		onSamplesSubmit("A:true"); // since the new column is always column A
//	},
//	render() {
//		var tooltipButton = <Tooltip>Search by sample IDs</Tooltip>,
//			tooltipModalString = 'Samples match your list will be highlighted. \
//				A new binary column will be made using your list: matched samples vs. the rest.',
//			tooltipModal = <Tooltip>{tooltipModalString}</Tooltip>,
//			{cohortSamples, disabled} = this.props,
//			help = 'e.g.\n' + Object.values(cohortSamples)[0].slice(0, 5).join('\n') + '\n...';
//
//		return (
//
//				<OverlayTrigger trigger={['hover']} placement="top" overlay={tooltipButton}>
//					<Button
//						bsSize = "small"
//						onClick = {() => this.setState({ show: true})}
//						disabled={disabled}>
//						Custom Sample List
//					</Button>
//				</OverlayTrigger>
//				<Modal
//					show={this.state.show}
//					onHide={this.close}
//					container={this}
//					aria-labelledby="contained-modal-title">
//					<Modal.Header closeButton>
//						<Modal.Title id="contained-modal-title">
//							Enter a list of samaple IDs to highlight
//							<OverlayTrigger trigger={['hover']} placement="right" overlay={tooltipModal}>
//
//							</OverlayTrigger>
//						</Modal.Title>
//					</Modal.Header>
//					<Modal.Body>
//						<Input style={{width: 550, height: 200}}
//							value={this.state.value}
//							type ="textarea"
//							placeholder={help}
//							onChange={this.onChange}/>
//					</Modal.Body>
//					<Modal.Footer>
//						<Button bsStyle="primary" onClick={this.submit} disabled={!this.state.value}>Submit</Button>
//						<Button onClick={this.close}>Cancel</Button>
//					</Modal.Footer>
//				</Modal>
//
//		);
//	}
//});

function findSubExpr(offsets, i) {
	var j = 0;
	while (i > offsets[j]) {
		++j;
	}
	return j;
}
var tooltips = {
	filter: 'Filter samples',
	subgroup: 'Create sample subgroups from data on the screen',
	find: 'Highlight or zoom in on a selection of samples',
	clear: 'Clear current filter and show all samples in the study',
	input: 'Examples: "missense", "null","B:>2"',
	keep: 'Keep only the selected samples on the screen',
	remove: 'Remove the selected samples from the screen',
	makeSubgroup: 'Matched samples will be one subgroup and non-matched samples will be the other subgroup',
	highlight: 'Highlight selected samples',
	zoom: 'Zoom in to selected samples',
	history: 'Previous search text'
};

var placeholder = {
	true: 'Click on a column or type here to select samples',
	false: 'Type here to select samples'
};

var input = comp => {
	var {matches, mode, history, pickSamples} = comp.props,
		{value} = comp.state,
		hasHistory = history.length > 0,
		noshow = (mode !== "heatmap");

	return (<TooltipInput key='input'
			className={classNames(compStyles.inputContainer, pickSamples && compStyles.picking)}
			tooltip={tooltips.input}
			onKeyUp={comp.onCaret}
			onClick={comp.onCaret}
			onFocus={comp.onCaret}
			onBlur={comp.onHideCaret}
			inputRef={comp.setRef}
			spellCheck={false}
			type='text'
			value={value || ''}
			placeholder={placeholder[pickSamples]}
			onChange={comp.onChange}
			disabled={noshow}>
		<TooltipI tooltip={tooltips.history} onClick={hasHistory ? comp.onOpenHistory : null}
				className={classNames(compStyles.dropDownArrow,
					hasHistory && compStyles.hasHistory, 'material-icons')}>
				arrow_drop_down</TooltipI>

		<Menu theme={{static: compStyles.history, active: compStyles.historyActive}}
				onShow={comp.onShowHistory} onHide={comp.onHideHistory}
				position='static' active={comp.state.historyOpen}>
			{history.map((b, i) =>
					// Note that Menu onSelect is broken in dev because of component
					// identity comparisons, and MenuItem events are broken in prod
					// because of RT deferring callbacks with a setState call, thus
					// allowing currentTarget to be nulled, so our only option here
					// is to create closures on every render. Very annoying.
					<MenuItem className={compStyles.menuItem} onClick={() => comp.onHistory(b)}
						data-value={b} key={i} value={b} caption={b}/>)}
		</Menu>
		{`${matches} matching samples`}
	</TooltipInput>);
};

const searchHelp = 'https://ucsc-xena.gitbook.io/project/overview-of-features/filter-and-subgrouping';

var helpSteps = [
	'pickHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

Filtering and subgrouping

Toggle dropper mode to enable selecting samples interactively,
			then click or drag on a data column to add rows to your selection.

		</HelpBox>),
	'inputHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

As you select rows, the search expression will update with your selection.

You can also type a search expression here without using the dropper mode.

		</HelpBox>),
	'actionHelp',
	props => (
		<HelpBox o='Below' w={400} onClose={props.onClose}>

When you have added all your rows of interest, use the menu to filter or create subgroups from your selection.

			Filter/subgroup documentation
		</HelpBox>)
];

class Search extends PureComponent {
	state = {value: this.props.value};

	componentDidMount() {
		var highlight = new Rx.Subject();
		this.highlight = highlight.next.bind(highlight);
		this.sub = highlight.startWith(undefined).distinctUntilChanged()
			.subscribe(hl => this.props.onHighlightSelect(hl));
	}

	componentWillUnmount() {
		this.sub.unsubscribe();
	}

	componentWillReceiveProps(newProps) {
		if (this.state.value === this.props.value) {
			this.setState({value: newProps.value});
		}
		// otherwise we have buffered changes to state, and
		// updating from props would revert the user input
		// and move the carat to the end.
	}

	componentDidUpdate() {
		var oldSearch = this.props.oldSearch;
		// oldSearch will update before value, so we have to check for both.
		if (oldSearch != null && this.props.value) {
			// We have to set focus for the selection to be visible. If this
			// is problematic, we'll need to implement our own selection highlight
			// until the element has focus.
			this.input.focus({preventScroll: true});
			this.input.setSelectionRange(oldSearch.length, this.props.value.length);
			this.input.scrollLeft = this.input.scrollWidth;
		}
	}

	onChange = (value) => {
		var {onChange} = this.props;
		this.setState({value});
		onChange(value);
	};

	onSamplesSubmit = (value) => {
		var {onChange} = this.props;
		this.setState({value});
		onChange(value);
	};

	setRef = ref => {
		this.input = ref && ref.inputNode;
	}

	onCaret = () => {
		var hl = this.input && this.props.offsets ?
			findSubExpr(this.props.offsets, this.input.selectionStart) :
			undefined;
		this.highlight(hl);
	}

	onHideCaret = () => {
		this.highlight(undefined);
	}

	onOpenHistory = () => {
		this.setState({historyOpen: !this.state.historyOpen});
	}

	onHistory = value => {
		this.setState({historyOpen: false});
		this.props.onHistory(value); // move to front of history
		this.props.onChange(value);
	}

	// These two are hacks to keep local state synced with the menu, e.g.
	// when the user closes it by clicking outside the menu.
	onShowHistory = () => {
		this.setState({historyOpen: true});
	}

	onHideHistory = () => {
		this.setState({historyOpen: false});
	}

	onRemove = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onFilter(true);
	}

	onKeep = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onFilter(false);
	}

	onCreateColumn = () => {
		// have to add to history before the columns reorder, to
		// prevent the history having wrong column ids.
		this.props.onHistory(this.state.value.trim());
		this.props.onCreateColumn();
	}

	onZoom = () => {
		this.props.onHistory(this.state.value.trim());
		this.props.onZoom();
	}

	render() {
		var {matches, sampleCount, mode, pickSamples, onPickSamples, onResetSampleFilter} = this.props,
			{inputHelp, pickHelp, actionHelp} = this.props,
			disableActions = !(matches > 0 && matches < sampleCount),
			noshow = (mode !== "heatmap");

		return (

				{inputHelp(input(this))}
				{pickHelp(
					<TooltipI className={classNames('material-icons', pickSamples ? compStyles.dark : '')}
						onClick={onPickSamples} tooltip='Pick samples'>colorize</TooltipI>)}
				{noshow ? filter_list :
				actionHelp(
					<TooltipIconMenu tooltip='Filter / subgroup actions' className={compStyles.filterMenu} icon='filter_list' iconRipple={false} position='topLeft'>
						<MenuItem disabled={disableActions} caption='Keep samples' onClick={this.onKeep}/>
						<MenuItem disabled={disableActions} caption='Remove samples' onClick={this.onRemove}/>
						<MenuItem caption='Clear samples filter' onClick={onResetSampleFilter}/>
						<MenuItem disabled={disableActions} caption='Zoom' onClick={this.onZoom}/>
						<MenuItem disabled={disableActions} caption='New subgroup column' onClick={this.onCreateColumn}/>
					</TooltipIconMenu>)}
				help_outline

		);
	}
}

export var SampleSearch = createVignette(helpSteps, Search);
<|endoftext|>


<commit_before>var DayView = Marionette.ItemView.extend({
    tagName: 'li',
    attributes: {role: 'presentation'},
    events: {
        'click': 'tabChanged'
    },
    className: function () {
        if (this.model.get('active')) {
            return 'active';
        }
    },
    template: function (day) {
        return nunjucks.renderString('{{name}}', {name: day.name})
    },
    tabChanged: function () {
        this.model.collection.switchTo(this.model);
    }
});

var DayCollectionView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    childView: DayView,
    initialize: function () {
        this.collection.on('change', this.render, this);
    }
});

var DaysPanelView = Marionette.LayoutView.extend({
    el: $('.outerView'),
    template: function() {},
    regions: {
        days: ".days-list",
        meals: "#meals-container"
    },
    initialize: function () {
    }
});<commit_msg>Removed # url from days<commit_after>var DayView = Marionette.ItemView.extend({
    tagName: 'li',
    attributes: {role: 'presentation'},
    events: {
        'click': 'tabChanged'
    },
    className: function () {
        if (this.model.get('active')) {
            return 'active';
        }
    },
    template: function (day) {
        return nunjucks.renderString('{{name}}', {name: day.name})
    },
    tabChanged: function () {
        this.model.collection.switchTo(this.model);
    }
});

var DayCollectionView = Marionette.CollectionView.extend({
    tagName: 'ul',
    className: 'nav nav-tabs',
    childView: DayView,
    initialize: function () {
        this.collection.on('change', this.render, this);
    }
});

var DaysPanelView = Marionette.LayoutView.extend({
    el: $('.outerView'),
    template: function() {},
    regions: {
        days: ".days-list",
        meals: "#meals-container"
    },
    initialize: function () {
    }
});<|endoftext|>


<commit_before>/**
 * @author Mino Togna
 */
// var moment   = require( 'moment' )
var EventBus           = require('../event/event-bus')
var Events             = require('../event/events')
var Loader             = require('../loader/loader')
var View               = require('./search-v')
var Model              = require('./model/search-model')
var SearchRequestUtils = require('./search-request-utils')
var moment             = require('moment')
var guid               = require('../guid/guid')

require('../scene-areas/scene-areas-mv')
require('../scenes-selection/scenes-selection-mv')
require('../search-retrieve/search-retrieve-mv')
require('../scene-area-mosaics/scene-area-mosaics-mv')

var show = function (e, type, params) {
  if (type == 'search') {
    View.init()
  }
  if (!(params && params.source === 'app-section'))
    View.showList()
}

var requestSceneAreas = function (e, state) {
  var data     = {}
  data.dataSet = state.sensorGroup
  SearchRequestUtils.addAoiRequestParameter(state, data)

  var params = {
    url         : '/api/data/sceneareas'
    , data      : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.sceneAreas = {}
      $.each(response, function (i, sceneArea) {
        state.sceneAreas[sceneArea.sceneAreaId] = {polygon: sceneArea.polygon, selection: []}
      })
      state.mosaicPreviewBand = null
      state.scenesPreview     = true

      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {resetSceneAreas: true})
      EventBus.dispatch(Events.SECTION.REDUCE)
      Loader.hide({delay: 300})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)

}

EventBus.addEventListener(Events.SECTION.SHOW, show)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_SCENE_AREAS, requestSceneAreas)

// list actions
var loadList = function (e) {
  var params = {
    url    : '/processing-recipes',
    success: function (response) {
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.LIST_CHANGE, null, response)
    }
  }
  EventBus.dispatch(Events.AJAX.GET, null, params)
}

var _loadMosaic = function (id, callback) {
  var params = {
    url         : '/processing-recipes/' + id
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      Loader.hide({delay: 1000})

      var state = typeof response === 'string' ? JSON.parse(response) : response
      delete state['mosaic']

      setTimeout(function () {
        switch (state.type) {
          case Model.TYPES.MOSAIC:
            View.showMosaic()
            break
          case Model.TYPES.CLASSIFICATION:
            View.showClassification()
            break
          case Model.TYPES.CHANGE_DETECTION:
            View.showChangeDetection()
            break
        }
      }, 200)
      setTimeout(function () {
        callback(state)
      }, 600)
    }
  }
  EventBus.dispatch(Events.AJAX.GET, null, params)
}

var loadMosaic = function (e, id) {
  _loadMosaic(id, function (state) {
    EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {resetSceneAreas: true, isNew: true})
    EventBus.dispatch(Events.SECTION.REDUCE)
    if (state.mosaicPreview) {
      switch (state.type) {

        case Model.TYPES.MOSAIC:
          if (state.mosaicPreviewBand)
            EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.PREVIEW_MOSAIC, null, state)
          break
        case Model.TYPES.CLASSIFICATION:
          requestClassification(e, state)
          break
        case Model.TYPES.CHANGE_DETECTION:
          requestChangeDetection(e, state)
          break

      }
    }
  })
}

var cloneMosaic = function (e, id) {
  _loadMosaic(id, function (state) {
    state.id   = guid.random()
    state.name = state.name + '-clone'
    EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {
      resetSceneAreas: true,
      hideSceneAreas : true,
      isNew          : true
    })
  })
}

var deleteMosaic = function (e, id) {
  var params = {
    url         : '/processing-recipes/' + id
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      Loader.hide({delay: 1000})

      EventBus.dispatch(Events.SECTION.SEARCH.STATE.LIST_CHANGE, null, response)

      if (Model.isActive(id)) {
        EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, null)
      }
    }
  }
  EventBus.dispatch(Events.AJAX.DELETE, null, params)
}

var showList = function () {
  View.showList()
}

// add mosaics
var addMosaic = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var sensors      = Model.getSensors(Model.getSensorGroups()[0])
    var defaultState = {
      id         : guid.random(),
      type       : Model.TYPES.MOSAIC,
      name       : 'mosaic-' + date.format('YYYY-MM-DD-HHmm'),
      aoiCode    : null,
      aoiName    : null,
      sensorGroup: Model.getSensorGroups()[0],
      targetDate : date.format('YYYY-MM-DD'),

      sortWeight           : 0.5,
      sensors              : Object.keys(sensors).filter(function (sensor) {
        return sensors[sensor].selected
      }),
      offsetToTargetDay    : 0,
      minScenes            : 1,
      maxScenes            : null,
      maskClouds           : false,
      maskSnow             : true,
      brdfCorrect          : true,
      median               : false,
      mosaicTargetDayWeight: 0,
      mosaicShadowTolerance: 1,
      mosaicHazeTolerance  : 0.05,
      mosaicGreennessWeight: 0
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

//classification
var addClassification = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id                    : guid.random(),
      type                  : Model.TYPES.CLASSIFICATION,
      name                  : 'classification-' + date.format('YYYY-MM-DD-HHmm'),
      inputRecipe           : null,
      fusionTableId         : null,
      fusionTableClassColumn: null,
      algorithm             : 'cart'
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var classificationRequestData = function (state) {
  return {
    imageType    : Model.TYPES.CLASSIFICATION,
    imageRecipeId: state.inputRecipe,
    assetId      : state.geeAssetId,
    tableName    : state.fusionTableId,
    classProperty: state.fusionTableClassColumn,
    algorithm    : state.algorithm
  }
}

var requestClassification = function (e, state) {
  var data   = classificationRequestData(state)
  var params = {
    url         : '/api/data/classification/preview',
    data        : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.mosaicPreview = true
      state.mosaic        = {mapId: response.mapId, token: response.token}
      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.MOSAIC_LOADED, null, state.mosaic.mapId, state.mosaic.token)
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state)

      EventBus.dispatch(Events.SECTION.REDUCE)

      Loader.hide({delay: 500})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

var retrieveClassification = function (e, state, obj) {
  var data         = classificationRequestData(state)
  data.name        = obj.name
  data.destination = obj.destination

  var params = {
    url         : '/api/data/classification/retrieve'
    , data      : data
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)

      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.COLLAPSE_VIEW)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

//change detection
var addChangeDetection = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id                    : guid.random(),
      type                  : Model.TYPES.CHANGE_DETECTION,
      name                  : 'change-detection-' + date.format('YYYY-MM-DD-HHmm'),
      inputRecipe1          : null,
      inputRecipe2          : null,
      fusionTableId         : null,
      fusionTableClassColumn: null,
      algorithm             : 'cart'
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var changeDetectionRequestData = function (state) {
  return {
    imageType        : Model.TYPES.CHANGE_DETECTION,
    fromImageRecipeId: state.inputRecipe1,
    toImageRecipeId  : state.inputRecipe2,
    fromAssetId      : state.geeAssetId1,
    toAssetId        : state.geeAssetId2,
    tableName        : state.fusionTableId,
    classProperty    : state.fusionTableClassColumn,
    algorithm        : state.algorithm
  }
}

var requestChangeDetection = function (e, state) {
  var data   = changeDetectionRequestData(state)
  var params = {
    url         : '/api/data/change-detection/preview',
    data        : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.mosaicPreview = true
      state.mosaic        = {mapId: response.mapId, token: response.token}
      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.MOSAIC_LOADED, null, state.mosaic.mapId, state.mosaic.token)
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state)

      EventBus.dispatch(Events.SECTION.REDUCE)

      Loader.hide({delay: 500})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

var retrieveChangeDetection = function (e, state, obj) {
  var data         = changeDetectionRequestData(state)
  data.name        = obj.name
  data.destination = obj.destination

  var params = {
    url         : '/api/data/change-detection/retrieve'
    , data      : data
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)

      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.COLLAPSE_VIEW)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

//change detection
var addTimeSeries = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id         : guid.random(),
      type       : Model.TYPES.TIME_SERIES,
      description: 'time-series-' + date.format('YYYY-MM-DD-HHmm'),
      aoi        : null,
      dataSets   : [],
      fromDate   : date.format('YYYY-MM-DD'),
      toDate     : date.format('YYYY-MM-DD'),
      indicator  : null,
      maskClouds : false,
      maskSnow   : false,
      brdfCorrect: false
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var getRequestTimeSeriesData = function (state) {

  switch (state.indicator) {
    case 'NDVI' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
    case 'NDMI' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
    case 'EVI' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
    case 'EVI2' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
  }

  state.tile = 'Download time-series: \'' + state.description + '\''

  return {
    operation: 'sepal.timeseries.download',
    params   : state
  }
}

var requestTimeSeries = function (e, state) {
  var data = getRequestTimeSeriesData(state)

  var params = {
    url         : '/api/tasks'
    , data      : JSON.stringify(data)
    , contentType: "application/json; charset=utf-8"
    , dataType   : "json"
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

EventBus.addEventListener(Events.SECTION.SEARCH.STATE.LIST_LOAD, loadList)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_LOAD, loadMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_CLONE, cloneMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_DELETE, deleteMosaic)

EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.SHOW_LIST, showList)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_MOSAIC, addMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_CLASSIFICATION, addClassification)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_CLASSIFICATION, requestClassification)
EventBus.addEventListener(Events.SECTION.SEARCH_RETRIEVE.RETRIEVE_CLASSIFICATION, retrieveClassification)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_CHANGE_DETECTION, addChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_CHANGE_DETECTION, requestChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH_RETRIEVE.RETRIEVE_CHANGE_DETECTION, retrieveChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_TIME_SERIES, addTimeSeries)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_TIME_SERIES, requestTimeSeries)
<commit_msg>Fixed typo: tile -> title.<commit_after>/**
 * @author Mino Togna
 */
// var moment   = require( 'moment' )
var EventBus           = require('../event/event-bus')
var Events             = require('../event/events')
var Loader             = require('../loader/loader')
var View               = require('./search-v')
var Model              = require('./model/search-model')
var SearchRequestUtils = require('./search-request-utils')
var moment             = require('moment')
var guid               = require('../guid/guid')

require('../scene-areas/scene-areas-mv')
require('../scenes-selection/scenes-selection-mv')
require('../search-retrieve/search-retrieve-mv')
require('../scene-area-mosaics/scene-area-mosaics-mv')

var show = function (e, type, params) {
  if (type == 'search') {
    View.init()
  }
  if (!(params && params.source === 'app-section'))
    View.showList()
}

var requestSceneAreas = function (e, state) {
  var data     = {}
  data.dataSet = state.sensorGroup
  SearchRequestUtils.addAoiRequestParameter(state, data)

  var params = {
    url         : '/api/data/sceneareas'
    , data      : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.sceneAreas = {}
      $.each(response, function (i, sceneArea) {
        state.sceneAreas[sceneArea.sceneAreaId] = {polygon: sceneArea.polygon, selection: []}
      })
      state.mosaicPreviewBand = null
      state.scenesPreview     = true

      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {resetSceneAreas: true})
      EventBus.dispatch(Events.SECTION.REDUCE)
      Loader.hide({delay: 300})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)

}

EventBus.addEventListener(Events.SECTION.SHOW, show)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_SCENE_AREAS, requestSceneAreas)

// list actions
var loadList = function (e) {
  var params = {
    url    : '/processing-recipes',
    success: function (response) {
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.LIST_CHANGE, null, response)
    }
  }
  EventBus.dispatch(Events.AJAX.GET, null, params)
}

var _loadMosaic = function (id, callback) {
  var params = {
    url         : '/processing-recipes/' + id
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      Loader.hide({delay: 1000})

      var state = typeof response === 'string' ? JSON.parse(response) : response
      delete state['mosaic']

      setTimeout(function () {
        switch (state.type) {
          case Model.TYPES.MOSAIC:
            View.showMosaic()
            break
          case Model.TYPES.CLASSIFICATION:
            View.showClassification()
            break
          case Model.TYPES.CHANGE_DETECTION:
            View.showChangeDetection()
            break
        }
      }, 200)
      setTimeout(function () {
        callback(state)
      }, 600)
    }
  }
  EventBus.dispatch(Events.AJAX.GET, null, params)
}

var loadMosaic = function (e, id) {
  _loadMosaic(id, function (state) {
    EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {resetSceneAreas: true, isNew: true})
    EventBus.dispatch(Events.SECTION.REDUCE)
    if (state.mosaicPreview) {
      switch (state.type) {

        case Model.TYPES.MOSAIC:
          if (state.mosaicPreviewBand)
            EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.PREVIEW_MOSAIC, null, state)
          break
        case Model.TYPES.CLASSIFICATION:
          requestClassification(e, state)
          break
        case Model.TYPES.CHANGE_DETECTION:
          requestChangeDetection(e, state)
          break

      }
    }
  })
}

var cloneMosaic = function (e, id) {
  _loadMosaic(id, function (state) {
    state.id   = guid.random()
    state.name = state.name + '-clone'
    EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state, {
      resetSceneAreas: true,
      hideSceneAreas : true,
      isNew          : true
    })
  })
}

var deleteMosaic = function (e, id) {
  var params = {
    url         : '/processing-recipes/' + id
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      Loader.hide({delay: 1000})

      EventBus.dispatch(Events.SECTION.SEARCH.STATE.LIST_CHANGE, null, response)

      if (Model.isActive(id)) {
        EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, null)
      }
    }
  }
  EventBus.dispatch(Events.AJAX.DELETE, null, params)
}

var showList = function () {
  View.showList()
}

// add mosaics
var addMosaic = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var sensors      = Model.getSensors(Model.getSensorGroups()[0])
    var defaultState = {
      id         : guid.random(),
      type       : Model.TYPES.MOSAIC,
      name       : 'mosaic-' + date.format('YYYY-MM-DD-HHmm'),
      aoiCode    : null,
      aoiName    : null,
      sensorGroup: Model.getSensorGroups()[0],
      targetDate : date.format('YYYY-MM-DD'),

      sortWeight           : 0.5,
      sensors              : Object.keys(sensors).filter(function (sensor) {
        return sensors[sensor].selected
      }),
      offsetToTargetDay    : 0,
      minScenes            : 1,
      maxScenes            : null,
      maskClouds           : false,
      maskSnow             : true,
      brdfCorrect          : true,
      median               : false,
      mosaicTargetDayWeight: 0,
      mosaicShadowTolerance: 1,
      mosaicHazeTolerance  : 0.05,
      mosaicGreennessWeight: 0
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

//classification
var addClassification = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id                    : guid.random(),
      type                  : Model.TYPES.CLASSIFICATION,
      name                  : 'classification-' + date.format('YYYY-MM-DD-HHmm'),
      inputRecipe           : null,
      fusionTableId         : null,
      fusionTableClassColumn: null,
      algorithm             : 'cart'
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var classificationRequestData = function (state) {
  return {
    imageType    : Model.TYPES.CLASSIFICATION,
    imageRecipeId: state.inputRecipe,
    assetId      : state.geeAssetId,
    tableName    : state.fusionTableId,
    classProperty: state.fusionTableClassColumn,
    algorithm    : state.algorithm
  }
}

var requestClassification = function (e, state) {
  var data   = classificationRequestData(state)
  var params = {
    url         : '/api/data/classification/preview',
    data        : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.mosaicPreview = true
      state.mosaic        = {mapId: response.mapId, token: response.token}
      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.MOSAIC_LOADED, null, state.mosaic.mapId, state.mosaic.token)
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state)

      EventBus.dispatch(Events.SECTION.REDUCE)

      Loader.hide({delay: 500})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

var retrieveClassification = function (e, state, obj) {
  var data         = classificationRequestData(state)
  data.name        = obj.name
  data.destination = obj.destination

  var params = {
    url         : '/api/data/classification/retrieve'
    , data      : data
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)

      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.COLLAPSE_VIEW)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

//change detection
var addChangeDetection = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id                    : guid.random(),
      type                  : Model.TYPES.CHANGE_DETECTION,
      name                  : 'change-detection-' + date.format('YYYY-MM-DD-HHmm'),
      inputRecipe1          : null,
      inputRecipe2          : null,
      fusionTableId         : null,
      fusionTableClassColumn: null,
      algorithm             : 'cart'
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var changeDetectionRequestData = function (state) {
  return {
    imageType        : Model.TYPES.CHANGE_DETECTION,
    fromImageRecipeId: state.inputRecipe1,
    toImageRecipeId  : state.inputRecipe2,
    fromAssetId      : state.geeAssetId1,
    toAssetId        : state.geeAssetId2,
    tableName        : state.fusionTableId,
    classProperty    : state.fusionTableClassColumn,
    algorithm        : state.algorithm
  }
}

var requestChangeDetection = function (e, state) {
  var data   = changeDetectionRequestData(state)
  var params = {
    url         : '/api/data/change-detection/preview',
    data        : data
    , beforeSend: function () {
      Loader.show()
    }
    , success   : function (response) {
      state.mosaicPreview = true
      state.mosaic        = {mapId: response.mapId, token: response.token}
      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.MOSAIC_LOADED, null, state.mosaic.mapId, state.mosaic.token)
      EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, state)

      EventBus.dispatch(Events.SECTION.REDUCE)

      Loader.hide({delay: 500})
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

var retrieveChangeDetection = function (e, state, obj) {
  var data         = changeDetectionRequestData(state)
  data.name        = obj.name
  data.destination = obj.destination

  var params = {
    url         : '/api/data/change-detection/retrieve'
    , data      : data
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)

      EventBus.dispatch(Events.SECTION.SEARCH_RETRIEVE.COLLAPSE_VIEW)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

//change detection
var addTimeSeries = function () {

  var getDefaultState = function () {
    var date         = moment(new Date())
    var defaultState = {
      id         : guid.random(),
      type       : Model.TYPES.TIME_SERIES,
      description: 'time-series-' + date.format('YYYY-MM-DD-HHmm'),
      aoi        : null,
      dataSets   : [],
      fromDate   : date.format('YYYY-MM-DD'),
      toDate     : date.format('YYYY-MM-DD'),
      indicator  : null,
      maskClouds : false,
      maskSnow   : false,
      brdfCorrect: false
    }
    return defaultState
  }
  // View.showMosaic()
  EventBus.dispatch(Events.SECTION.SEARCH.STATE.ACTIVE_CHANGE, null, getDefaultState(), {isNew: true})
}

var getRequestTimeSeriesData = function (state) {

  switch (state.indicator) {
    case 'NDVI' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
    case 'NDMI' :
      state.expression = '10000 * (1 + (i.nir - i.swir1) / (i.nir + i.swir1))'
      break
    case 'EVI' :
      state.expression = '10000 * (1 + 2.5 * (i.nir - i.red) / (i.nir + 6 * i.red - 7.5 * i.blue + 1))'
      break
    case 'EVI2' :
      state.expression = '10000 * (1 + (i.nir - i.red) / (i.nir + i.red))'
      break
  }

  state.title = 'Download time-series: \'' + state.description + '\''

  return {
    operation: 'sepal.timeseries.download',
    params   : state
  }
}

var requestTimeSeries = function (e, state) {
  var data = getRequestTimeSeriesData(state)

  var params = {
    url         : '/api/tasks'
    , data      : JSON.stringify(data)
    , contentType: "application/json; charset=utf-8"
    , dataType   : "json"
    , beforeSend: function () {
      setTimeout(function () {
        EventBus.dispatch(Events.ALERT.SHOW_INFO, null, 'The download will start shortly.
You can monitor the progress in the task manager')
      }, 100)
    }
    , success   : function (e) {
      EventBus.dispatch(Events.SECTION.TASK_MANAGER.CHECK_STATUS)
    }
  }
  EventBus.dispatch(Events.AJAX.POST, null, params)
}

EventBus.addEventListener(Events.SECTION.SEARCH.STATE.LIST_LOAD, loadList)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_LOAD, loadMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_CLONE, cloneMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.MOSAIC_DELETE, deleteMosaic)

EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.SHOW_LIST, showList)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_MOSAIC, addMosaic)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_CLASSIFICATION, addClassification)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_CLASSIFICATION, requestClassification)
EventBus.addEventListener(Events.SECTION.SEARCH_RETRIEVE.RETRIEVE_CLASSIFICATION, retrieveClassification)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_CHANGE_DETECTION, addChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_CHANGE_DETECTION, requestChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH_RETRIEVE.RETRIEVE_CHANGE_DETECTION, retrieveChangeDetection)
EventBus.addEventListener(Events.SECTION.SEARCH.VIEW.ADD_TIME_SERIES, addTimeSeries)
EventBus.addEventListener(Events.SECTION.SEARCH.REQUEST_TIME_SERIES, requestTimeSeries)
<|endoftext|>