<commit_before>// Copyright 2017-2019, University of Colorado Boulder

'use strict';

const addTranslator = require( './addTranslator' );
const ChipperVersion = require( '../common/ChipperVersion' );
const constants = require( './constants' );
const createTranslationsXML = require( './createTranslationsXML' );
const devDeploy = require( './devDeploy' );
const execute = require( '../common/execute' );
const fs = require( 'graceful-fs' ); //eslint-disable-line
const gitCheckout = require( '../common/gitCheckout' );
const gitPull = require( '../common/gitPull' );
const getLocales = require( './getLocales' );
const notifyServer = require( './notifyServer' );
const pullMaster = require( './pullMaster' );
const rsync = require( 'rsync' );
const SimVersion = require( '../common/SimVersion' );
const winston = require( 'winston' );
const writeFile = require( '../common/writeFile' );
const writePhetHtaccess = require( './writePhetHtaccess' );
const writePhetioHtaccess = require( '../common/writePhetioHtaccess' );
const deployImages = require( './deployImages' );

const buildDir = './js/build-server/tmp';

/**
 * checkout master everywhere and abort build with err
 * @param {String|Error} err - error logged and sent via email
 */
const abortBuild = async err => {
  winston.log( 'error', 'BUILD ABORTED! ' + err );
  winston.log( 'info', 'build aborted: checking out master for every repo in case build shas are still checked out' );
  await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
  return Promise.reject( 'Build aborted,' + err );
};

/**
 * Clean up after deploy. Checkout master for every repo and remove tmp dir.
 */
const afterDeploy = async buildDir => {
  try {
    await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
    await execute( 'rm', [ '-rf', buildDir ], '.' );
  }
  catch ( err ) {
    return abortBuild( err );
  }
};

/**
 * taskQueue ensures that only one build/deploy process will be happening at the same time.  The main build/deploy logic is here.
 *
 * @param {Object}
 * @property {JSON} repos
 * @property {String} api
 * @property {String} locales - comma separated list of locale codes
 * @property {String} simName - lower case simulation name used for creating files/directories
 * @property {String} version - sim version identifier string
 * @property {String} servers - deployment targets, subset of [ 'dev', 'production' ]
 * @property {String} brands - deployment brands
 * @property {String} email - used for sending notifications about success/failure
 * @property {String} translatorId - rosetta user id for adding translators to the website
 * @property {String} res - express response object
 * @property {winston} winston - logger
 */
async function taskWorker( options ) {
  if ( options.deployImages ) {
    try {
      await deployImages( options );
      await gitCheckout( 'chipper', 'master' );
      await gitPull( 'chipper' );
      winston.info( 'Deploy images completed successfully.' );
      return Promise.resolve();
    }
    catch ( e ) {
      winston.error( e );
      winston.error( 'Deploy images failed. See previous logs for details.' );
      return Promise.reject( e );
    }

  }
  try {
    //-------------------------------------------------------------------------------------
    // Parse and validate parameters
    //-------------------------------------------------------------------------------------
    const api = options.api;
    const repos = options.repos;
    let locales = options.locales;
    const simName = options.simName;
    let version = options.version;
    const email = options.email;
    const brands = options.brands;
    const servers = options.servers;
    const userId = options.userId;
    let branch = options.branch;

    if ( userId ) {
      winston.log( 'info', 'setting userId = ' + userId );
    }

    const simNameRegex = /^[a-z-]+$/;

    winston.debug( JSON.stringify( repos ) );

    if ( branch === null ) {
      branch = repos[ simName ].branch;
    }

    // make sure the repos passed in validates
    for ( const key in repos ) {
      if ( repos.hasOwnProperty( key ) ) {
        winston.log( 'info', 'Validating repo: ' + key );

        // make sure all keys in repos object are valid sim names
        if ( !simNameRegex.test( key ) ) {
          return abortBuild( 'invalid simName in repos: ' + simName );
        }

        const value = repos[ key ];
        if ( key === 'comment' ) {
          if ( typeof value !== 'string' ) {
            return abortBuild( 'invalid comment in repos: should be a string' );
          }
        }
        else if ( value instanceof Object && value.hasOwnProperty( 'sha' ) ) {
          if ( !/^[a-f0-9]{40}$/.test( value.sha ) ) {
            return abortBuild( 'invalid sha in repos. key: ' + key + ' value: ' + value + ' sha: ' + value.sha );
          }
        }
        else {
          return abortBuild( 'invalid item in repos. key: ' + key + ' value: ' + value );
        }
      }
    }

    // validate simName
    if ( !simNameRegex.test( simName ) ) {
      return abortBuild( 'invalid simName ' + simName );
    }

    // Infer brand from version string and keep unstripped version for phet-io
    const originalVersion = version;
    if ( api === '1.0' ) {
      // validate version and strip suffixes since just the numbers are used in the directory name on dev and production servers
      const versionMatch = version.match( /^(\d+\.\d+\.\d+)(?:-.*)?$/ );
      if ( versionMatch && versionMatch.length === 2 ) {

        if ( servers.includes( 'dev' ) ) {
          // if deploying an rc version use the -rc.[number] suffix
          version = versionMatch[ 0 ];
        }
        else {
          // otherwise strip any suffix
          version = versionMatch[ 1 ];
        }
        winston.log( 'info', 'detecting version number: ' + version );
      }
      else {
        return abortBuild( 'invalid version number: ' + version );
      }
    }

    const simDir = `../${simName}`;
    winston.log( 'info', 'building sim ' + simName );

    // Create the temporary build dir, removing the existing dir if it exists.
    await new Promise( ( resolve, reject ) => {
      fs.mkdir( buildDir, async err => {
        // If there is an error, try to remove the directory and contents and try again
        if ( err ) {
          try {
            await execute( 'rm', [ '-rf', buildDir ], '.' );
          }
          catch ( e ) {
            reject( e );
          }
          fs.mkdir( buildDir, err => {
            if ( err ) {
              winston.error( 'Error creating new build dir: ' );
              winston.error( err );
              reject( err );
            }
            else {
              winston.info( 'successfully created build dir' );
              resolve();
            }
          } );

        }
        else {
          winston.info( 'successfully created build dir' );
          resolve();
        }
      } );
    } );

    await writeFile( buildDir + '/dependencies.json', JSON.stringify( repos ) );
    winston.log( 'info', 'wrote file ' + buildDir + '/dependencies.json' );

    await execute( 'git', [ 'pull' ], constants.PERENNIAL );
    await execute( 'npm', [ 'prune' ], constants.PERENNIAL );
    await execute( 'npm', [ 'update' ], constants.PERENNIAL );
    await execute( './perennial/bin/clone-missing-repos.sh', [], '..' );
    await pullMaster( repos );
    await execute( 'grunt', [ 'checkout-shas', '--buildServer=true', '--repo=' + simName ], constants.PERENNIAL );
    await execute( 'git', [ 'checkout', repos[ simName ].sha ], simDir );
    await execute( 'npm', [ 'prune' ], '../chipper' );
    await execute( 'npm', [ 'update' ], '../chipper' );
    await execute( 'npm', [ 'prune' ], simDir );
    await execute( 'npm', [ 'update' ], simDir );

    if ( api === '1.0' ) {
      locales = await getLocales( locales, simName );
    }

    const brandLocales = ( brands.indexOf( constants.PHET_BRAND ) >= 0 ) ? locales : 'en';
    winston.log( 'info', 'building for brands: ' + brands + ' version: ' + version );

    const chipperVersion = ChipperVersion.getFromRepository();
    winston.debug( 'Chipper version detected: ' + chipperVersion.toString() );

    if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
      await execute( 'grunt', [ '--allHTML', '--debugHTML', '--brands=' + brands.join( ',' ), '--locales=' + brandLocales ], simDir );
    }
    else if ( chipperVersion.major === 0 && chipperVersion.minor === 0 ) {
      const args = [ 'build-for-server', '--brand=' + brands[ 0 ], '--locales=' + brandLocales ];
      if ( brands[ 0 ] === constants.PHET_BRAND ) {
        args.push( '--allHTML' );
      }
      await execute( 'grunt', args, simDir );
    }
    else {
      return abortBuild( 'Unsupported chipper version' );
    }

    winston.debug( 'deploying to servers: ' + JSON.stringify( servers ) );

    if ( servers.indexOf( constants.DEV_SERVER ) >= 0 ) {
      winston.info( 'deploying to dev' );
      if ( brands.indexOf( constants.PHET_IO_BRAND ) >= 0 ) {
        const htaccessLocation = ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) ?
                                 simDir + '/build/phet-io' :
                                 simDir + '/build';
        await writePhetioHtaccess( htaccessLocation );
      }
      await devDeploy( simDir, simName, version, chipperVersion, brands );
    }

    if ( servers.indexOf( constants.PRODUCTION_SERVER ) >= 0 ) {
      winston.info( 'deploying to production' );
      let targetVersionDir;
      let targetSimDir;
      // Loop over all brands
      for ( const i in brands ) {
        if ( brands.hasOwnProperty( i ) ) {
          const brand = brands[ i ];
          winston.info( 'deploying brand: ' + brand );

          // Pre-copy steps
          if ( brand === constants.PHET_BRAND ) {
            targetSimDir = constants.HTML_SIMS_DIRECTORY + simName;
            targetVersionDir = targetSimDir + '/' + version + '/';

            if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
              // Remove _phet from all filenames in the phet directory
              const files = fs.readdirSync( simDir + '/build/phet' );
              for ( const i in files ) {
                if ( files.hasOwnProperty( i ) ) {
                  const filename = files[ i ];
                  if ( filename.indexOf( '_phet' ) >= 0 ) {
                    const newFilename = filename.replace( '_phet', '' );
                    await execute( 'mv', [ filename, newFilename ], simDir + '/build/phet' );
                  }
                }
              }
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            targetSimDir = constants.PHET_IO_SIMS_DIRECTORY + simName;
            targetVersionDir = targetSimDir + '/' + originalVersion;
            if ( chipperVersion.major === 0 && !originalVersion.match( '-phetio' ) ) {
              targetVersionDir += '-phetio';
            }
            targetVersionDir += '/';
          }

          // Copy steps - allow EEXIST errors but reject anything else
          await new Promise( async ( resolve, reject ) => {
            // If this is a deploy of 1.0.0, create the sim directory
            if ( !fs.existsSync( targetSimDir ) ) {
              winston.debug( 'Creating sim dir: ' + targetSimDir );
              try {
                await new Promise( ( resolve, reject ) => {
                  fs.mkdir( targetSimDir, err => {
                    if ( err && err.code !== 'EEXIST' ) {
                      winston.debug( 'Failure creating sim dir' );
                      reject( err );
                    }
                    winston.debug( 'Success creating sim dir' );
                    resolve();
                  } );
                } );
              }
              catch ( e ) {
                reject( e );
              }
            }
            // Create the version directory
            winston.debug( 'Creating version dir: ' + targetVersionDir );
            fs.mkdir( targetVersionDir, err => {
              if ( err && err.code !== 'EEXIST' ) {
                winston.debug( 'Failure creating version dir' );
                reject( err );
              }
              winston.debug( 'Success creating sim dir' );
              resolve();
            } );
          } );
          let sourceDir = simDir + '/build';
          if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
            sourceDir += '/' + brand;
          }
          await new Promise( ( resolve, reject ) => {
            winston.debug( 'Copying recursive ' + sourceDir + ' to ' + targetVersionDir );
            new rsync()
              .flags( 'razpO' )
              .set( 'no-perms' )
              .set( 'exclude', '.rsync-filter' )
              .source( sourceDir + '/' )
              .destination( targetVersionDir )
              .output( stdout => { winston.debug( stdout.toString() ); },
                stderr => { winston.error( stderr.toString() ); } )
              .execute( ( err, code, cmd ) => {
                if ( err && code !== 23 ) {
                  winston.debug( code );
                  winston.debug( cmd );
                  reject( err );
                }
                else { resolve(); }
              } );
          } );

          winston.debug( 'Copy finished' );

          // Post-copy steps
          if ( brand === constants.PHET_BRAND ) {
            await writePhetHtaccess( simName, version );
            await createTranslationsXML( simName, version );
            await notifyServer( {
              simName: simName,
              email: email,
              brand: brand,
              locales: locales
            } );

            // if this build request comes from rosetta it will have a userId field and only one locale
            const localesArray = typeof ( locales ) === 'string' ? locales.split( ',' ) : locales;
            if ( userId && localesArray.length === 1 && localesArray[ 0 ] !== '*' ) {
              await addTranslator( localesArray[ 0 ], simName, userId );
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            const suffix = originalVersion.split( '-' ).length >= 2 ? originalVersion.split( '-' )[ 1 ] :
                           ( chipperVersion.major < 2 ? 'phetio' : '' );
            const parsedVersion = SimVersion.parse( version, '' );
            const simPackage = JSON.parse( fs.readFileSync( `${simDir}/package.json` ) );
            const ignoreForAutomatedMaintenanceReleases = !!( simPackage && simPackage.phet && simPackage.phet.ignoreForAutomatedMaintenanceReleases );
            await notifyServer( {
              simName: simName,
              email: email,
              brand: brand,
              phetioOptions: {
                branch: branch,
                suffix: suffix,
                version: parsedVersion,
                ignoreForAutomatedMaintenanceReleases: ignoreForAutomatedMaintenanceReleases
              }
            } );

            winston.debug( 'server notified' );
            await writePhetioHtaccess( targetVersionDir, {
              simName: simName,
              version: originalVersion,
              directory: constants.PHET_IO_SIMS_DIRECTORY
            } );
          }
        }
      }
    }
  }
  catch ( err ) {
    return abortBuild( err );
  }

  await deployImages( {
    branch: 'master', // chipper branch, always deploy images from master
    simulation: options.simName,
    brands: options.brands
  } );

  return afterDeploy();
}

module.exports = ( task, taskCallback ) => {
  taskWorker( task )
    .then( () => {
        taskCallback();
      }
    ).catch( reason => {
    taskCallback( reason );
  } );
};<commit_msg>image deployment only in production<commit_after>// Copyright 2017-2019, University of Colorado Boulder

'use strict';

const addTranslator = require( './addTranslator' );
const ChipperVersion = require( '../common/ChipperVersion' );
const constants = require( './constants' );
const createTranslationsXML = require( './createTranslationsXML' );
const devDeploy = require( './devDeploy' );
const execute = require( '../common/execute' );
const fs = require( 'graceful-fs' ); //eslint-disable-line
const gitCheckout = require( '../common/gitCheckout' );
const gitPull = require( '../common/gitPull' );
const getLocales = require( './getLocales' );
const notifyServer = require( './notifyServer' );
const pullMaster = require( './pullMaster' );
const rsync = require( 'rsync' );
const SimVersion = require( '../common/SimVersion' );
const winston = require( 'winston' );
const writeFile = require( '../common/writeFile' );
const writePhetHtaccess = require( './writePhetHtaccess' );
const writePhetioHtaccess = require( '../common/writePhetioHtaccess' );
const deployImages = require( './deployImages' );

const buildDir = './js/build-server/tmp';

/**
 * checkout master everywhere and abort build with err
 * @param {String|Error} err - error logged and sent via email
 */
const abortBuild = async err => {
  winston.log( 'error', 'BUILD ABORTED! ' + err );
  winston.log( 'info', 'build aborted: checking out master for every repo in case build shas are still checked out' );
  await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
  return Promise.reject( 'Build aborted,' + err );
};

/**
 * Clean up after deploy. Checkout master for every repo and remove tmp dir.
 */
const afterDeploy = async buildDir => {
  try {
    await execute( 'grunt', [ 'checkout-master-all' ], constants.PERENNIAL );
    await execute( 'rm', [ '-rf', buildDir ], '.' );
  }
  catch ( err ) {
    return abortBuild( err );
  }
};

/**
 * taskQueue ensures that only one build/deploy process will be happening at the same time.  The main build/deploy logic is here.
 *
 * @param {Object}
 * @property {JSON} repos
 * @property {String} api
 * @property {String} locales - comma separated list of locale codes
 * @property {String} simName - lower case simulation name used for creating files/directories
 * @property {String} version - sim version identifier string
 * @property {String} servers - deployment targets, subset of [ 'dev', 'production' ]
 * @property {String} brands - deployment brands
 * @property {String} email - used for sending notifications about success/failure
 * @property {String} translatorId - rosetta user id for adding translators to the website
 * @property {String} res - express response object
 * @property {winston} winston - logger
 */
async function taskWorker( options ) {
  if ( options.deployImages ) {
    try {
      await deployImages( options );
      await gitCheckout( 'chipper', 'master' );
      await gitPull( 'chipper' );
      winston.info( 'Deploy images completed successfully.' );
      return Promise.resolve();
    }
    catch ( e ) {
      winston.error( e );
      winston.error( 'Deploy images failed. See previous logs for details.' );
      return Promise.reject( e );
    }

  }
  try {
    //-------------------------------------------------------------------------------------
    // Parse and validate parameters
    //-------------------------------------------------------------------------------------
    const api = options.api;
    const repos = options.repos;
    let locales = options.locales;
    const simName = options.simName;
    let version = options.version;
    const email = options.email;
    const brands = options.brands;
    const servers = options.servers;
    const userId = options.userId;
    let branch = options.branch;

    if ( userId ) {
      winston.log( 'info', 'setting userId = ' + userId );
    }

    const simNameRegex = /^[a-z-]+$/;

    winston.debug( JSON.stringify( repos ) );

    if ( branch === null ) {
      branch = repos[ simName ].branch;
    }

    // make sure the repos passed in validates
    for ( const key in repos ) {
      if ( repos.hasOwnProperty( key ) ) {
        winston.log( 'info', 'Validating repo: ' + key );

        // make sure all keys in repos object are valid sim names
        if ( !simNameRegex.test( key ) ) {
          return abortBuild( 'invalid simName in repos: ' + simName );
        }

        const value = repos[ key ];
        if ( key === 'comment' ) {
          if ( typeof value !== 'string' ) {
            return abortBuild( 'invalid comment in repos: should be a string' );
          }
        }
        else if ( value instanceof Object && value.hasOwnProperty( 'sha' ) ) {
          if ( !/^[a-f0-9]{40}$/.test( value.sha ) ) {
            return abortBuild( 'invalid sha in repos. key: ' + key + ' value: ' + value + ' sha: ' + value.sha );
          }
        }
        else {
          return abortBuild( 'invalid item in repos. key: ' + key + ' value: ' + value );
        }
      }
    }

    // validate simName
    if ( !simNameRegex.test( simName ) ) {
      return abortBuild( 'invalid simName ' + simName );
    }

    // Infer brand from version string and keep unstripped version for phet-io
    const originalVersion = version;
    if ( api === '1.0' ) {
      // validate version and strip suffixes since just the numbers are used in the directory name on dev and production servers
      const versionMatch = version.match( /^(\d+\.\d+\.\d+)(?:-.*)?$/ );
      if ( versionMatch && versionMatch.length === 2 ) {

        if ( servers.includes( 'dev' ) ) {
          // if deploying an rc version use the -rc.[number] suffix
          version = versionMatch[ 0 ];
        }
        else {
          // otherwise strip any suffix
          version = versionMatch[ 1 ];
        }
        winston.log( 'info', 'detecting version number: ' + version );
      }
      else {
        return abortBuild( 'invalid version number: ' + version );
      }
    }

    const simDir = `../${simName}`;
    winston.log( 'info', 'building sim ' + simName );

    // Create the temporary build dir, removing the existing dir if it exists.
    await new Promise( ( resolve, reject ) => {
      fs.mkdir( buildDir, async err => {
        // If there is an error, try to remove the directory and contents and try again
        if ( err ) {
          try {
            await execute( 'rm', [ '-rf', buildDir ], '.' );
          }
          catch ( e ) {
            reject( e );
          }
          fs.mkdir( buildDir, err => {
            if ( err ) {
              winston.error( 'Error creating new build dir: ' );
              winston.error( err );
              reject( err );
            }
            else {
              winston.info( 'successfully created build dir' );
              resolve();
            }
          } );

        }
        else {
          winston.info( 'successfully created build dir' );
          resolve();
        }
      } );
    } );

    await writeFile( buildDir + '/dependencies.json', JSON.stringify( repos ) );
    winston.log( 'info', 'wrote file ' + buildDir + '/dependencies.json' );

    await execute( 'git', [ 'pull' ], constants.PERENNIAL );
    await execute( 'npm', [ 'prune' ], constants.PERENNIAL );
    await execute( 'npm', [ 'update' ], constants.PERENNIAL );
    await execute( './perennial/bin/clone-missing-repos.sh', [], '..' );
    await pullMaster( repos );
    await execute( 'grunt', [ 'checkout-shas', '--buildServer=true', '--repo=' + simName ], constants.PERENNIAL );
    await execute( 'git', [ 'checkout', repos[ simName ].sha ], simDir );
    await execute( 'npm', [ 'prune' ], '../chipper' );
    await execute( 'npm', [ 'update' ], '../chipper' );
    await execute( 'npm', [ 'prune' ], simDir );
    await execute( 'npm', [ 'update' ], simDir );

    if ( api === '1.0' ) {
      locales = await getLocales( locales, simName );
    }

    const brandLocales = ( brands.indexOf( constants.PHET_BRAND ) >= 0 ) ? locales : 'en';
    winston.log( 'info', 'building for brands: ' + brands + ' version: ' + version );

    const chipperVersion = ChipperVersion.getFromRepository();
    winston.debug( 'Chipper version detected: ' + chipperVersion.toString() );

    if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
      await execute( 'grunt', [ '--allHTML', '--debugHTML', '--brands=' + brands.join( ',' ), '--locales=' + brandLocales ], simDir );
    }
    else if ( chipperVersion.major === 0 && chipperVersion.minor === 0 ) {
      const args = [ 'build-for-server', '--brand=' + brands[ 0 ], '--locales=' + brandLocales ];
      if ( brands[ 0 ] === constants.PHET_BRAND ) {
        args.push( '--allHTML' );
      }
      await execute( 'grunt', args, simDir );
    }
    else {
      return abortBuild( 'Unsupported chipper version' );
    }

    winston.debug( 'deploying to servers: ' + JSON.stringify( servers ) );

    if ( servers.indexOf( constants.DEV_SERVER ) >= 0 ) {
      winston.info( 'deploying to dev' );
      if ( brands.indexOf( constants.PHET_IO_BRAND ) >= 0 ) {
        const htaccessLocation = ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) ?
                                 simDir + '/build/phet-io' :
                                 simDir + '/build';
        await writePhetioHtaccess( htaccessLocation );
      }
      await devDeploy( simDir, simName, version, chipperVersion, brands );
    }

    if ( servers.indexOf( constants.PRODUCTION_SERVER ) >= 0 ) {
      winston.info( 'deploying to production' );
      let targetVersionDir;
      let targetSimDir;
      // Loop over all brands
      for ( const i in brands ) {
        if ( brands.hasOwnProperty( i ) ) {
          const brand = brands[ i ];
          winston.info( 'deploying brand: ' + brand );

          // Pre-copy steps
          if ( brand === constants.PHET_BRAND ) {
            targetSimDir = constants.HTML_SIMS_DIRECTORY + simName;
            targetVersionDir = targetSimDir + '/' + version + '/';

            if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
              // Remove _phet from all filenames in the phet directory
              const files = fs.readdirSync( simDir + '/build/phet' );
              for ( const i in files ) {
                if ( files.hasOwnProperty( i ) ) {
                  const filename = files[ i ];
                  if ( filename.indexOf( '_phet' ) >= 0 ) {
                    const newFilename = filename.replace( '_phet', '' );
                    await execute( 'mv', [ filename, newFilename ], simDir + '/build/phet' );
                  }
                }
              }
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            targetSimDir = constants.PHET_IO_SIMS_DIRECTORY + simName;
            targetVersionDir = targetSimDir + '/' + originalVersion;
            if ( chipperVersion.major === 0 && !originalVersion.match( '-phetio' ) ) {
              targetVersionDir += '-phetio';
            }
            targetVersionDir += '/';
          }

          // Copy steps - allow EEXIST errors but reject anything else
          await new Promise( async ( resolve, reject ) => {
            // If this is a deploy of 1.0.0, create the sim directory
            if ( !fs.existsSync( targetSimDir ) ) {
              winston.debug( 'Creating sim dir: ' + targetSimDir );
              try {
                await new Promise( ( resolve, reject ) => {
                  fs.mkdir( targetSimDir, err => {
                    if ( err && err.code !== 'EEXIST' ) {
                      winston.debug( 'Failure creating sim dir' );
                      reject( err );
                    }
                    winston.debug( 'Success creating sim dir' );
                    resolve();
                  } );
                } );
              }
              catch ( e ) {
                reject( e );
              }
            }
            // Create the version directory
            winston.debug( 'Creating version dir: ' + targetVersionDir );
            fs.mkdir( targetVersionDir, err => {
              if ( err && err.code !== 'EEXIST' ) {
                winston.debug( 'Failure creating version dir' );
                reject( err );
              }
              winston.debug( 'Success creating sim dir' );
              resolve();
            } );
          } );
          let sourceDir = simDir + '/build';
          if ( chipperVersion.major === 2 && chipperVersion.minor === 0 ) {
            sourceDir += '/' + brand;
          }
          await new Promise( ( resolve, reject ) => {
            winston.debug( 'Copying recursive ' + sourceDir + ' to ' + targetVersionDir );
            new rsync()
              .flags( 'razpO' )
              .set( 'no-perms' )
              .set( 'exclude', '.rsync-filter' )
              .source( sourceDir + '/' )
              .destination( targetVersionDir )
              .output( stdout => { winston.debug( stdout.toString() ); },
                stderr => { winston.error( stderr.toString() ); } )
              .execute( ( err, code, cmd ) => {
                if ( err && code !== 23 ) {
                  winston.debug( code );
                  winston.debug( cmd );
                  reject( err );
                }
                else { resolve(); }
              } );
          } );

          winston.debug( 'Copy finished' );

          // Post-copy steps
          if ( brand === constants.PHET_BRAND ) {
            await writePhetHtaccess( simName, version );
            await createTranslationsXML( simName, version );
            await notifyServer( {
              simName: simName,
              email: email,
              brand: brand,
              locales: locales
            } );

            // if this build request comes from rosetta it will have a userId field and only one locale
            const localesArray = typeof ( locales ) === 'string' ? locales.split( ',' ) : locales;
            if ( userId && localesArray.length === 1 && localesArray[ 0 ] !== '*' ) {
              await addTranslator( localesArray[ 0 ], simName, userId );
            }
          }
          else if ( brand === constants.PHET_IO_BRAND ) {
            const suffix = originalVersion.split( '-' ).length >= 2 ? originalVersion.split( '-' )[ 1 ] :
                           ( chipperVersion.major < 2 ? 'phetio' : '' );
            const parsedVersion = SimVersion.parse( version, '' );
            const simPackage = JSON.parse( fs.readFileSync( `${simDir}/package.json` ) );
            const ignoreForAutomatedMaintenanceReleases = !!( simPackage && simPackage.phet && simPackage.phet.ignoreForAutomatedMaintenanceReleases );
            await notifyServer( {
              simName: simName,
              email: email,
              brand: brand,
              phetioOptions: {
                branch: branch,
                suffix: suffix,
                version: parsedVersion,
                ignoreForAutomatedMaintenanceReleases: ignoreForAutomatedMaintenanceReleases
              }
            } );

            winston.debug( 'server notified' );
            await writePhetioHtaccess( targetVersionDir, {
              simName: simName,
              version: originalVersion,
              directory: constants.PHET_IO_SIMS_DIRECTORY
            } );
          }
        }
      }

      await deployImages( {
        branch: 'master', // chipper branch, always deploy images from master
        simulation: options.simName,
        brands: options.brands
      } );
    }
  }
  catch ( err ) {
    return abortBuild( err );
  }

  return afterDeploy();
}

module.exports = ( task, taskCallback ) => {
  taskWorker( task )
    .then( () => {
        taskCallback();
      }
    ).catch( reason => {
    taskCallback( reason );
  } );
};<|endoftext|>


<commit_before>/**
 * Create a DepthRender that draws and receives a depth image.
 *
 * @param stream
 * @param reduce
 * @param renderEngine
 * @param rotation
 * @constructor
 */
function DepthRender(stream, reduce, renderEngine, rotation) {
  var self = this;
  this.stream = stream;
  this.renderEngine = renderEngine;

  this.particles = new THREE.BufferGeometry();
  this.positions = new Float32Array(640 / reduce * 480 / reduce * 3);
  this.particles.addAttribute('position', new THREE.BufferAttribute(this.positions, 3));

  this.material = new THREE.PointCloudMaterial({
    color: 0xFFFFFF,
    size: reduce / 2
  });

  this.system = new THREE.PointCloud(this.particles, this.material);
  this.system.rotation.y = rotation;

  this.renderEngine.scene.add(this.system);

  this.transformation = new DepthTransformation(reduce, this.particles);

  this.stream.on('data', function(data){
    var array = new Uint16Array(data);
    self.transformation.update(array);
    self.particles.verticesNeedUpdate = true;
  });
}

/**
 * Stop renderer.
 */
DepthRender.prototype.stop = function(){
  this.renderEngine.scene.remove(this.system);
};
<commit_msg>always render cloud<commit_after>/**
 * Create a DepthRender that draws and receives a depth image.
 *
 * @param stream
 * @param reduce
 * @param renderEngine
 * @param rotation
 * @constructor
 */
function DepthRender(stream, reduce, renderEngine, rotation) {
  var self = this;
  this.stream = stream;
  this.renderEngine = renderEngine;

  this.particles = new THREE.BufferGeometry();
  this.positions = new Float32Array(640 / reduce * 480 / reduce * 3);
  this.particles.addAttribute('position', new THREE.BufferAttribute(this.positions, 3));

  this.material = new THREE.PointCloudMaterial({
    color: 0xFFFFFF,
    size: reduce / 2
  });

  this.system = new THREE.PointCloud(this.particles, this.material);
  this.system.rotation.y = rotation;
  this.system.frustumCulled = false;

  this.renderEngine.scene.add(this.system);

  this.transformation = new DepthTransformation(reduce, this.particles);

  this.stream.on('data', function(data){
    var array = new Uint16Array(data);
    self.transformation.update(array);
    self.particles.verticesNeedUpdate = true;
  });
}

/**
 * Stop renderer.
 */
DepthRender.prototype.stop = function(){
  this.renderEngine.scene.remove(this.system);
};
<|endoftext|>


<commit_before>/** @jsx React.DOM */
define([
    'underscore', 'react',
    './RadioGroup',
    './UserPicker',
    '../ImmutableOptimizations'
], function (_, React, RadioGroup, UserPicker, ImmutableOptimizations) {
    'use strict';

    void UserPicker;
    /**
     * This is a user picker control that is similar to the UserPicker but differs by
     * 1) it's given a list of users for radio buttons,
     * 2) The last radio button allows a user to be selected using a standard picker control
     * 3) it has no multi-select feature (single only).
     */
    return React.createClass({

        displayName: 'UserPickerPlus',
        mixins: [ImmutableOptimizations],
        statics: { fieldClass: function () { return 'formFieldRadio'; } },

        getDefaultProps: function () {
            return {
                value: undefined,
                disabled: false,
                readonly: false
            };
        },

        componentWillMount: function () {
            console.assert(this.props.dataSource);

            this.elemIDPrefix = _.uniqueId('userPickerRadio');
        },

        /*jshint ignore:start */
        render: function () {
            var props = this.props,
                pickerValue = props.value,
                idPrefix = this.elemIDPrefix;

            function onChangeRadio(e) {
                var index = parseInt(e.target.value);
                props.onChange(props.userList[index]);
            }

            function makeRadio(user, index) {
                var elemID = idPrefix + index;
                var checked = user.isEqual(props.value);

                return [
                    (<input type="radio" name={props.name} id={elemID} value={index} checked={checked} onChange={onChangeRadio}/>),
                    ({user.fullName}),
                    (
)
                ];
            }

            // If the "value" is one of the users in the userList, we don't want to pass it to the picker.
            if (_.some(props.userList, function (user) { return user.isEqual(pickerValue); } )) {
                pickerValue = null;
            }

            return(
                <fieldset>
                    {_.flatten(_.map(props.userList, makeRadio))}
                    <input type="radio" name={props.name} id={idPrefix + 'Last'} checked={!!pickerValue} />

                        <UserPicker onChange={props.onChange} placeholder={props.placeholder}
                                    value={pickerValue} dataSource={props.dataSource} width={props.width} />

                </fieldset>
            );
        }
        /*jshint ignore:end */
    });
});
<commit_msg>Use _.isEqual because user.isEqual may not always be available<commit_after>/** @jsx React.DOM */
define([
    'underscore', 'react',
    './RadioGroup',
    './UserPicker',
    '../ImmutableOptimizations'
], function (_, React, RadioGroup, UserPicker, ImmutableOptimizations) {
    'use strict';

    void UserPicker;
    /**
     * This is a user picker control that is similar to the UserPicker but differs by
     * 1) it's given a list of users for radio buttons,
     * 2) The last radio button allows a user to be selected using a standard picker control
     * 3) it has no multi-select feature (single only).
     */
    return React.createClass({

        displayName: 'UserPickerPlus',
        mixins: [ImmutableOptimizations],
        statics: { fieldClass: function () { return 'formFieldRadio'; } },

        getDefaultProps: function () {
            return {
                value: undefined,
                disabled: false,
                readonly: false
            };
        },

        componentWillMount: function () {
            console.assert(this.props.dataSource);

            this.elemIDPrefix = _.uniqueId('userPickerRadio');
        },

        /*jshint ignore:start */
        render: function () {
            var props = this.props,
                pickerValue = props.value,
                idPrefix = this.elemIDPrefix;

            function onChangeRadio(e) {
                var index = parseInt(e.target.value);
                props.onChange(props.userList[index]);
            }

            function makeRadio(user, index) {
                var elemID = idPrefix + index;
                var checked = _.isEqual(user, props.value);

                return [
                    (<input type="radio" name={props.name} id={elemID} value={index} checked={checked} onChange={onChangeRadio}/>),
                    ({user.fullName}),
                    (
)
                ];
            }

            // If the "value" is one of the users in the userList, we don't want to pass it to the picker.
            if (_.some(props.userList, function (user) { return _.isEqual(user, pickerValue) } )) {
                pickerValue = null;
            }

            return(
                <fieldset>
                    {_.flatten(_.map(props.userList, makeRadio))}
                    <input type="radio" name={props.name} id={idPrefix + 'Last'} checked={!!pickerValue} />

                        <UserPicker onChange={props.onChange} placeholder={props.placeholder}
                                    value={pickerValue} dataSource={props.dataSource} width={props.width} />

                </fieldset>
            );
        }
        /*jshint ignore:end */
    });
});
<|endoftext|>


<commit_before>window.onload = function(){
		var labor_force_size = document.getElementById('labor_force_size')
		var labor_force_size_ref = firebase.database().ref('data_dashboard').child('labor_force_size');
		labor_force_size_ref.on('value', snap => labor_force_size.innerText = snap.val());

		var average_annual_wage = document.getElementById('average_annual_wage')
		var average_annual_wage_ref = firebase.database().ref('data_dashboard').child('average_annual_wage');
		average_annual_wage_ref.on('value', snap => average_annual_wage.innerText = snap.val());

		var average_annual_commute_time = document.getElementById('average_annual_commute_time')
		var average_annual_commute_time_ref = firebase.database().ref('data_dashboard').child('average_annual_commute_time');
		average_annual_commute_time_ref.on('value', snap => average_annual_commute_time.innerText = snap.val());

		var broadband_access = document.getElementById('broadband_access')
		var broadband_access_ref = firebase.database().ref('data_dashboard').child('broadband_access');
		broadband_access_ref.on('value', snap => broadband_access.innerText = snap.val());

		var churn_rate = document.getElementById('churn_rate')
		var churn_rate_ref = firebase.database().ref('data_dashboard').child('churn_rate');
		churn_rate_ref.on('value', snap => churn_rate.innerText = snap.val());

		var closings = document.getElementById('closings')
		var closings_ref = firebase.database().ref('data_dashboard').child('closings');
		closings_ref.on('value', snap => closings.innerText = snap.val());

		var cost_of_living = document.getElementById('cost_of_living')
		var cost_of_living_ref = firebase.database().ref('data_dashboard').child('cost_of_living');
		cost_of_living_ref.on('value', snap => cost_of_living.innerText = snap.val());

		var gross_domestic_product = document.getElementById('gross_domestic_product')
		var gross_domestic_product_ref = firebase.database().ref('data_dashboard').child('gross_domestic_product');
		gross_domestic_product_ref.on('value', snap => gross_domestic_product.innerText = snap.val());

		var jobs_employed_resident_ratio = document.getElementById('jobs_employed_resident_ratio')
		var jobs_employed_resident_ratio_ref = firebase.database().ref('data_dashboard').child('jobs_employed_resident_ratio');
		jobs_employed_resident_ratio_ref.on('value', snap => jobs_employed_resident_ratio.innerText = snap.val());

		var new_business_starts = document.getElementById('new_business_starts')
		var new_business_starts_ref = firebase.database().ref('data_dashboard').child('new_business_starts');
		new_business_starts_ref.on('value', snap => new_business_starts.innerText = snap.val());

		var new_housing_costs = document.getElementById('new_housing_costs')
		var new_housing_costs_ref = firebase.database().ref('data_dashboard').child('new_housing_costs');
		new_housing_costs_ref.on('value', snap => new_housing_costs.innerText = snap.val());

		var poverty_rate = document.getElementById('poverty_rate')
		var poverty_rate_ref = firebase.database().ref('data_dashboard').child('poverty_rate');
		poverty_rate_ref.on('value', snap => poverty_rate.innerText = snap.val());

		var sbir_grants = document.getElementById('sbir_grants')
		var sbir_grants_ref = firebase.database().ref('data_dashboard').child('sbir_grants');
		sbir_grants_ref.on('value', snap => sbir_grants.innerText = snap.val());

		var stage_ii_establishments = document.getElementById('stage_ii_establishments')
		var stage_ii_establishments_ref = firebase.database().ref('data_dashboard').child('stage_ii_establishments');
		stage_ii_establishments_ref.on('value', snap => stage_ii_establishments.innerText = snap.val());

		var stem_employment = document.getElementById('stem_employment')
		var stem_employment_ref = firebase.database().ref('data_dashboard').child('stem_employment');
		stem_employment_ref.on('value', snap => stem_employment.innerText = snap.val());

		var unemployment_rate = document.getElementById('unemployment_rate')
		var unemployment_rate_ref = firebase.database().ref('data_dashboard').child('unemployment_rate');
		unemployment_rate_ref.on('value', snap => unemployment_rate.innerText = snap.val());

		firebase.database().child("data_dashboard").child("unemployment_rate").on("value", snap => {
			var variable_one = snap.val();
			console.log(variable_one);
			var ctx = document.getElementById("myChart1");
		var myChart = new Chart(ctx, {
		    	type: 'bar',
		    	data: {
				labels: ["Business Startups", "Unemployment", "Average Commute", "Labor Force", "Broadband", "Churn Rate"],
				datasets: [{
			    		label: 'Test Bar Graph',
			    		data: [variable_one, 3, 1, 1, 5, 3],
					backgroundColor: [
						'rgba(255, 99, 132, 0.2)',
						'rgba(54, 162, 235, 0.2)',
						'rgba(255, 206, 86, 0.2)',
						'rgba(75, 192, 192, 0.2)',
						'rgba(153, 102, 255, 0.2)',
						'rgba(255, 159, 64, 0.2)'
			    		],
			    		borderColor: [
						'rgba(255,99,132,1)',
						'rgba(54, 162, 235, 1)',
						'rgba(255, 206, 86, 1)',
						'rgba(75, 192, 192, 1)',
						'rgba(153, 102, 255, 1)',
						'rgba(255, 159, 64, 1)'
			    		],
			    		borderWidth: 1
				}]
		    	},
		    	options: {
				scales: {
			    		yAxes: [{
						ticks: {
				    			beginAtZero:true
						}
			    		}]
				}
		    	}
		});
		});

	var dtx = document.getElementById("myChart2");
		var myChart = new Chart(dtx, {
		    type: 'line',
		    data: {
			labels: ["2015", "2013", "2014", "2015", "2016", "2017"],
			datasets: [{
			    label: 'Test line Graph',
			    data: [2, 3, 5, 3, 3.5, 2],
				backgroundColor: [
				'rgba(255, 99, 132, 0.2)',
				'rgba(54, 162, 235, 0.2)',
				'rgba(255, 206, 86, 0.2)',
				'rgba(75, 192, 192, 0.2)',
				'rgba(153, 102, 255, 0.2)',
				'rgba(255, 159, 64, 0.2)'
			    ],
			    borderColor: [
				'rgba(255,99,132,1)',
				'rgba(54, 162, 235, 1)',
				'rgba(255, 206, 86, 1)',
				'rgba(75, 192, 192, 1)',
				'rgba(153, 102, 255, 1)',
				'rgba(255, 159, 64, 1)'
			    ],
			    borderWidth: 1
			}]
		    },
		    options: {
			scales: {
			    yAxes: [{
				ticks: {
				    beginAtZero:true
				}
			    }]
			}
		    }
		});

	var pie_data = {
    		labels: [
        	"Variable One",
        	"Variable Two",
        	"Variable Three"
    		],
    		datasets: [
        	{
            	data: [300, 50, 100],
            	backgroundColor: [
                	"#FF6384",
                	"#36A2EB",
                	"#FFCE56"
            	],
           	 hoverBackgroundColor: [
                	"#FF6384",
                	"#36A2EB",
                	"#FFCE56"
            	]
        	}]
	};
	var etx = document.getElementById("myChart3");
	var myPieChart = new Chart(etx,{
    		type: 'pie',
    		data: pie_data,
    		options: {
        		animation:{
            			animateScale:true
        		}
    		}
	});

	var bubble_data = {
		datasets: [{
			label: 'First Dataset',
			data: [
				{
					x: 20,
					y: 30,
					r: 15
				},
				{
					x: 40,
					y: 10,
					r: 10
				}
			],
			backgroundColor:"#FF6384",
			hoverBackgroundColor: "#FF6384",
		},
		{
			label: 'Second Dataset',
			data: [
				{
					x: 5,
					y: 25,
					r: 30
				},
				{
					x: 15,
					y: 15,
					r: 20
				}
			],
			backgroundColor:"#36A2EB",
			hoverBackgroundColor: "#36A2EB",
		}]
	};

	var ftx = document.getElementById("myChart4");
	var myBubbleChart = new Chart(ftx,{
    		type: 'bubble',
    		data: bubble_data,
    		options: {
        		elements: {
           			 points: {
                			borderWidth: 1,
                			borderColor: 'rgb(0, 0, 0)'
            			}
        		}
    		}
	});
};
<commit_msg>Update firebase-data-dashboard.js<commit_after>window.onload = function(){
		var labor_force_size = document.getElementById('labor_force_size')
		var labor_force_size_ref = firebase.database().ref('data_dashboard').child('labor_force_size');
		labor_force_size_ref.on('value', snap => labor_force_size.innerText = snap.val());

		var average_annual_wage = document.getElementById('average_annual_wage')
		var average_annual_wage_ref = firebase.database().ref('data_dashboard').child('average_annual_wage');
		average_annual_wage_ref.on('value', snap => average_annual_wage.innerText = snap.val());

		var average_annual_commute_time = document.getElementById('average_annual_commute_time')
		var average_annual_commute_time_ref = firebase.database().ref('data_dashboard').child('average_annual_commute_time');
		average_annual_commute_time_ref.on('value', snap => average_annual_commute_time.innerText = snap.val());

		var broadband_access = document.getElementById('broadband_access')
		var broadband_access_ref = firebase.database().ref('data_dashboard').child('broadband_access');
		broadband_access_ref.on('value', snap => broadband_access.innerText = snap.val());

		var churn_rate = document.getElementById('churn_rate')
		var churn_rate_ref = firebase.database().ref('data_dashboard').child('churn_rate');
		churn_rate_ref.on('value', snap => churn_rate.innerText = snap.val());

		var closings = document.getElementById('closings')
		var closings_ref = firebase.database().ref('data_dashboard').child('closings');
		closings_ref.on('value', snap => closings.innerText = snap.val());

		var cost_of_living = document.getElementById('cost_of_living')
		var cost_of_living_ref = firebase.database().ref('data_dashboard').child('cost_of_living');
		cost_of_living_ref.on('value', snap => cost_of_living.innerText = snap.val());

		var gross_domestic_product = document.getElementById('gross_domestic_product')
		var gross_domestic_product_ref = firebase.database().ref('data_dashboard').child('gross_domestic_product');
		gross_domestic_product_ref.on('value', snap => gross_domestic_product.innerText = snap.val());

		var jobs_employed_resident_ratio = document.getElementById('jobs_employed_resident_ratio')
		var jobs_employed_resident_ratio_ref = firebase.database().ref('data_dashboard').child('jobs_employed_resident_ratio');
		jobs_employed_resident_ratio_ref.on('value', snap => jobs_employed_resident_ratio.innerText = snap.val());

		var new_business_starts = document.getElementById('new_business_starts')
		var new_business_starts_ref = firebase.database().ref('data_dashboard').child('new_business_starts');
		new_business_starts_ref.on('value', snap => new_business_starts.innerText = snap.val());

		var new_housing_costs = document.getElementById('new_housing_costs')
		var new_housing_costs_ref = firebase.database().ref('data_dashboard').child('new_housing_costs');
		new_housing_costs_ref.on('value', snap => new_housing_costs.innerText = snap.val());

		var poverty_rate = document.getElementById('poverty_rate')
		var poverty_rate_ref = firebase.database().ref('data_dashboard').child('poverty_rate');
		poverty_rate_ref.on('value', snap => poverty_rate.innerText = snap.val());

		var sbir_grants = document.getElementById('sbir_grants')
		var sbir_grants_ref = firebase.database().ref('data_dashboard').child('sbir_grants');
		sbir_grants_ref.on('value', snap => sbir_grants.innerText = snap.val());

		var stage_ii_establishments = document.getElementById('stage_ii_establishments')
		var stage_ii_establishments_ref = firebase.database().ref('data_dashboard').child('stage_ii_establishments');
		stage_ii_establishments_ref.on('value', snap => stage_ii_establishments.innerText = snap.val());

		var stem_employment = document.getElementById('stem_employment')
		var stem_employment_ref = firebase.database().ref('data_dashboard').child('stem_employment');
		stem_employment_ref.on('value', snap => stem_employment.innerText = snap.val());

		var unemployment_rate = document.getElementById('unemployment_rate')
		var unemployment_rate_ref = firebase.database().ref('data_dashboard').child('unemployment_rate');
		unemployment_rate_ref.on('value', snap => unemployment_rate.innerText = snap.val());

		firebase.database().ref("data_dashboard").child("unemployment_rate").on("value", snap => {
			var variable_one = snap.val();
			console.log(variable_one);
			var ctx = document.getElementById("myChart1");
		var myChart = new Chart(ctx, {
		    	type: 'bar',
		    	data: {
				labels: ["Business Startups", "Unemployment", "Average Commute", "Labor Force", "Broadband", "Churn Rate"],
				datasets: [{
			    		label: 'Test Bar Graph',
			    		data: [variable_one, 3, 1, 1, 5, 3],
					backgroundColor: [
						'rgba(255, 99, 132, 0.2)',
						'rgba(54, 162, 235, 0.2)',
						'rgba(255, 206, 86, 0.2)',
						'rgba(75, 192, 192, 0.2)',
						'rgba(153, 102, 255, 0.2)',
						'rgba(255, 159, 64, 0.2)'
			    		],
			    		borderColor: [
						'rgba(255,99,132,1)',
						'rgba(54, 162, 235, 1)',
						'rgba(255, 206, 86, 1)',
						'rgba(75, 192, 192, 1)',
						'rgba(153, 102, 255, 1)',
						'rgba(255, 159, 64, 1)'
			    		],
			    		borderWidth: 1
				}]
		    	},
		    	options: {
				scales: {
			    		yAxes: [{
						ticks: {
				    			beginAtZero:true
						}
			    		}]
				}
		    	}
		});
		});

	var dtx = document.getElementById("myChart2");
		var myChart = new Chart(dtx, {
		    type: 'line',
		    data: {
			labels: ["2015", "2013", "2014", "2015", "2016", "2017"],
			datasets: [{
			    label: 'Test line Graph',
			    data: [2, 3, 5, 3, 3.5, 2],
				backgroundColor: [
				'rgba(255, 99, 132, 0.2)',
				'rgba(54, 162, 235, 0.2)',
				'rgba(255, 206, 86, 0.2)',
				'rgba(75, 192, 192, 0.2)',
				'rgba(153, 102, 255, 0.2)',
				'rgba(255, 159, 64, 0.2)'
			    ],
			    borderColor: [
				'rgba(255,99,132,1)',
				'rgba(54, 162, 235, 1)',
				'rgba(255, 206, 86, 1)',
				'rgba(75, 192, 192, 1)',
				'rgba(153, 102, 255, 1)',
				'rgba(255, 159, 64, 1)'
			    ],
			    borderWidth: 1
			}]
		    },
		    options: {
			scales: {
			    yAxes: [{
				ticks: {
				    beginAtZero:true
				}
			    }]
			}
		    }
		});

	var pie_data = {
    		labels: [
        	"Variable One",
        	"Variable Two",
        	"Variable Three"
    		],
    		datasets: [
        	{
            	data: [300, 50, 100],
            	backgroundColor: [
                	"#FF6384",
                	"#36A2EB",
                	"#FFCE56"
            	],
           	 hoverBackgroundColor: [
                	"#FF6384",
                	"#36A2EB",
                	"#FFCE56"
            	]
        	}]
	};
	var etx = document.getElementById("myChart3");
	var myPieChart = new Chart(etx,{
    		type: 'pie',
    		data: pie_data,
    		options: {
        		animation:{
            			animateScale:true
        		}
    		}
	});

	var bubble_data = {
		datasets: [{
			label: 'First Dataset',
			data: [
				{
					x: 20,
					y: 30,
					r: 15
				},
				{
					x: 40,
					y: 10,
					r: 10
				}
			],
			backgroundColor:"#FF6384",
			hoverBackgroundColor: "#FF6384",
		},
		{
			label: 'Second Dataset',
			data: [
				{
					x: 5,
					y: 25,
					r: 30
				},
				{
					x: 15,
					y: 15,
					r: 20
				}
			],
			backgroundColor:"#36A2EB",
			hoverBackgroundColor: "#36A2EB",
		}]
	};

	var ftx = document.getElementById("myChart4");
	var myBubbleChart = new Chart(ftx,{
    		type: 'bubble',
    		data: bubble_data,
    		options: {
        		elements: {
           			 points: {
                			borderWidth: 1,
                			borderColor: 'rgb(0, 0, 0)'
            			}
        		}
    		}
	});
};
<|endoftext|>


<commit_before>// The Web Audio API does not offer an easy way to make a stereo balance
// control. This is an attempt to fill that void, using an API similar to
// [StereoPannerNode](https://developer.mozilla.org/en-US/docs/Web/API/StereoPannerNode)
export default function StereoBalanceNode(context) {
  let balance = 0;

  // ChannelSplitterNode cannot be told to use a `channelInterperatation` of
  // "speakers". This means that if we get a mono file, we will end up only
  // playing in the left speaker. So instead we use this dummy gain node to
  // convert whatever source we get (stereo, mono, or n channels) into a stereo
  // signal.
  // Idea credit: https://github.com/WebAudio/web-audio-api/issues/975#issue-177242377
  const upMixer = context.createGain();
  upMixer.channelCount = 2;
  upMixer.channelCountMode = "explicit";
  upMixer.channelInterpretation = "speakers";

  const splitter = context.createChannelSplitter(2);

  // Create the gains for left and right
  const leftGain = context.createGain();
  const rightGain = context.createGain();

  const merger = context.createChannelMerger(2);

  upMixer.connect(splitter);

  splitter.connect(leftGain, 0);
  splitter.connect(rightGain, 1);

  leftGain.connect(merger, 0, 0);
  rightGain.connect(merger, 0, 1);

  // -1 (left) to 1 (right)
  function set(rawValue) {
    const value = Number(rawValue);
    leftGain.gain.value = value > 0 ? 1 - value : 1;
    rightGain.gain.value = value > 0 ? 1 : 1 + value;
    balance = value;
  }

  function get() {
    return balance;
  }

  // Create our own version of an [AudioParam](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam).
  // We don't currently support any of the "over time" methods, but maybe some day
  // we'll want to.
  const audioParam = {};
  Object.defineProperties(audioParam, {
    value: { get, set, enumerable: true, configurable: true }
  });

  // The way the `.connect` API works, we can't actually construct our own
  // AudioNode. Instead we have to take an existing node and monkey patch it.
  Object.defineProperties(upMixer, {
    balance: {
      value: audioParam,
      enumerable: true,
      writable: false,
      configurable: true
    },
    connect: {
      value: AudioNode.prototype.connect.bind(merger),
      enumerable: false,
      writable: false,
      configurable: true
    },
    disconnect: {
      value: AudioNode.prototype.disconnect.bind(merger),
      enumerable: false,
      writable: false,
      configurable: true
    }
  });

  return upMixer;
}
<commit_msg>Allow consumers to initialize StereoBlanceNode value a la StereoPannerNode<commit_after>// The Web Audio API does not offer an easy way to make a stereo balance
// control. This is an attempt to fill that void, using an API similar to
// [StereoPannerNode](https://developer.mozilla.org/en-US/docs/Web/API/StereoPannerNode)
export default function StereoBalanceNode(context, options) {
  let balance = 0;

  // ChannelSplitterNode cannot be told to use a `channelInterperatation` of
  // "speakers". This means that if we get a mono file, we will end up only
  // playing in the left speaker. So instead we use this dummy gain node to
  // convert whatever source we get (stereo, mono, or n channels) into a stereo
  // signal.
  // Idea credit: https://github.com/WebAudio/web-audio-api/issues/975#issue-177242377
  const upMixer = context.createGain();
  upMixer.channelCount = 2;
  upMixer.channelCountMode = "explicit";
  upMixer.channelInterpretation = "speakers";

  const splitter = context.createChannelSplitter(2);

  // Create the gains for left and right
  const leftGain = context.createGain();
  const rightGain = context.createGain();

  const merger = context.createChannelMerger(2);

  upMixer.connect(splitter);

  splitter.connect(leftGain, 0);
  splitter.connect(rightGain, 1);

  leftGain.connect(merger, 0, 0);
  rightGain.connect(merger, 0, 1);

  // -1 (left) to 1 (right)
  function set(rawValue) {
    const value = Number(rawValue);
    leftGain.gain.value = value > 0 ? 1 - value : 1;
    rightGain.gain.value = value > 0 ? 1 : 1 + value;
    balance = value;
  }

  function get() {
    return balance;
  }

  // Create our own version of an [AudioParam](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam).
  // We don't currently support any of the "over time" methods, but maybe some day
  // we'll want to.
  const audioParam = {};
  Object.defineProperties(audioParam, {
    value: { get, set, enumerable: true, configurable: true }
  });

  // The way the `.connect` API works, we can't actually construct our own
  // AudioNode. Instead we have to take an existing node and monkey patch it.
  Object.defineProperties(upMixer, {
    balance: {
      value: audioParam,
      enumerable: true,
      writable: false,
      configurable: true
    },
    connect: {
      value: AudioNode.prototype.connect.bind(merger),
      enumerable: false,
      writable: false,
      configurable: true
    },
    disconnect: {
      value: AudioNode.prototype.disconnect.bind(merger),
      enumerable: false,
      writable: false,
      configurable: true
    }
  });

  if (balance !== options.balance) {
    set(options.balance);
  }

  return upMixer;
}
<|endoftext|>


<commit_before>/**
 * MLJLib
 * MeshLabJS Library
 *
 * Copyright(C) 2015
 * Paolo Cignoni
 * Visual Computing Lab
 * ISTI - CNR
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See theGNU General Public License
 * (http://www.gnu.org/licenses/gpl.txt) for more details.
 *
 */

/**
 * @file Defines the functions to manage and install plugins
 * @author Stefano Gabriele
 */

/**
 * MLJ.core.plugin.Manager
 * @author Stefano Gabriele
 */
MLJ.core.plugin.Manager = {
};

(function (widget, gui) {

    var _filters = new MLJ.util.AssociativeArray();
    var _rendering = new MLJ.util.AssociativeArray();

    /**
     * Installs a new plugin in MeshLabJS
     * @memberOf MLJ.core.plugin
     * @author Stefano Gabriele
     */
    this.install = function () {
        var plugin;
        var search = MLJ.gui.getWidget("SearchTool");
        for (var i = 0; i < arguments.length; i++) {
            plugin = arguments[i];
            if (plugin instanceof MLJ.core.plugin.Plugin) {
                if (plugin instanceof MLJ.core.plugin.Filter) {
                    _filters.set(plugin.getName(), plugin);
                    search.addItem(plugin.getName());
                    if(plugin.parameters.tooltip) {
                        search.addItem(plugin.parameters.tooltip);
                    }
                } else if (plugin instanceof MLJ.core.plugin.Rendering) {
                    _rendering.set(plugin.getName(), plugin);
                }
            } else {
                console.error("The parameter must be an instance of MLJ.core.Plugin");
            }
        }
    };

    /**
     * Executes the main entry point function for all installed plugins
     * It is called only in the index.html after loading all the js plugins
     * to perform all the proper initialization and GUI setup
     * (e.g. it will call for each plugin the _init())
     * @memberOf MLJ.core.plugin
     * @author Stefano Gabriele
     */
    this.run = function () {
        var ptr = _filters.iterator();
        while (ptr.hasNext()) {
            ptr.next()._main();
        }

        ptr = _rendering.iterator();
        while (ptr.hasNext()) {
            ptr.next()._main();
        }

    };

    this.getRenderingPlugins = function () {
        return _rendering;
    };

    this.getFilterPlugins = function () {
        return _filters;
    };

}).call(MLJ.core.plugin.Manager, MLJ.widget, MLJ.gui);//MLJ.widget contains GUI running widgets<commit_msg>Updated plugin framework to sort filters in alphabetical order<commit_after>/**
 * MLJLib
 * MeshLabJS Library
 *
 * Copyright(C) 2015
 * Paolo Cignoni
 * Visual Computing Lab
 * ISTI - CNR
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See theGNU General Public License
 * (http://www.gnu.org/licenses/gpl.txt) for more details.
 *
 */

/**
 * @file Defines the functions to manage and install plugins
 * @author Stefano Gabriele
 */

/**
 * MLJ.core.plugin.Manager
 * @author Stefano Gabriele
 */
MLJ.core.plugin.Manager = {
};

(function (widget, gui) {

    var _filters = new MLJ.util.AssociativeArray();
    var _rendering = new MLJ.util.AssociativeArray();

    /**
     * Installs a new plugin in MeshLabJS
     * @memberOf MLJ.core.plugin
     * @author Stefano Gabriele
     */
    this.install = function () {
        var plugin;
        var search = MLJ.gui.getWidget("SearchTool");
        for (var i = 0; i < arguments.length; i++) {
            plugin = arguments[i];
            if (plugin instanceof MLJ.core.plugin.Plugin) {
                if (plugin instanceof MLJ.core.plugin.Filter) {
                    _filters.set(plugin.getName(), plugin);
                    search.addItem(plugin.getName());
                    if(plugin.parameters.tooltip) {
                        search.addItem(plugin.parameters.tooltip);
                    }
                } else if (plugin instanceof MLJ.core.plugin.Rendering) {
                    _rendering.set(plugin.getName(), plugin);
                }
            } else {
                console.error("The parameter must be an instance of MLJ.core.Plugin");
            }
        }
    };

    /**
     * Executes the main entry point function for all installed plugins
     * It is called only in the index.html after loading all the js plugins
     * to perform all the proper initialization and GUI setup
     * (e.g. it will call for each plugin the _init())
     * @memberOf MLJ.core.plugin
     * @author Stefano Gabriele
     */
    this.run = function () {

        //sort filters in alphabetical order
        _filters.sortByKey();

        var ptr = _filters.iterator();
        while (ptr.hasNext()) {
            ptr.next()._main();
        }

        ptr = _rendering.iterator();
        while (ptr.hasNext()) {
            ptr.next()._main();
        }

    };

    this.getRenderingPlugins = function () {
        return _rendering;
    };

    this.getFilterPlugins = function () {
        return _filters;
    };

}).call(MLJ.core.plugin.Manager, MLJ.widget, MLJ.gui);//MLJ.widget contains GUI running widgets<|endoftext|>


<commit_before>/*  NETPIE Microgear Freeboard plugin                             */
/*  Developed by Chavee Issariyapat                               */
/*  More information about NETPIE please visit https://netpie.io  */

if (typeof microgear === "undefined") {
    microgear = {};
}

(function()
{
    freeboard.loadDatasourcePlugin({
        "type_name"   : "netpie_microgear",
        "display_name": "NETPIE Microgear",
        "description" : "Connect to NETPIE as a microgear to communicate real-time with other microgears in the same App ID. The microgear of this datasource is referenced by microgear[DATASOURCENAME]",
        "external_scripts" : [
            "https://cdn.netpie.io/microgear.js"
        ],
        "settings"    : [
            {
                "name"         : "appid",
                "display_name" : "App ID",
                "type"         : "text",
                "description"  : "NETPIE App ID obtained from https://netpie.io/app",
                "required" : true
            },
            {
                "name"         : "key",
                "display_name" : "Key",
                "type"         : "text",
                "description"  : "Key",
                "required"     : true
            },
            {
                "name"        : "secret",
                "display_name" : "Secret",
                "type"         : "text",
                "description"  : "Secret",
                "type"         : "text",
                "required"     : true
            },
/*
            {
                "name"         : "alias",
                "display_name" : "Microgear Alias",
                "type"         : "text",
                "description"  : "A nick name of this freeboard that other device can chat to",
                "type"         : "text",
                "default_value": "freeboard",
                "required"     : false
            },
            {
                "name"         : "microgearRef",
                "display_name" : "Microgear Reference",
                "type"         : "text",
                "description"  : "Define a reference for a microgear of this datasource. For example if you set this to 'mygear' you can access the microgear object by microgear['mygear']",
                "type"         : "text",
                "required"     : false
            },
*/
            {
                "name"         : "topics",
                "display_name" : "Subscribed Topics",
                "type"         : "text",
                "description"  : "Topics of the messages that this datasource will consume, the default is /# which means all messages in this app ID.",
                "default_value": "/#",
                "required"     : false
            },
            {
                "name"          : "onConnectedAction",
                "display_name"  : "onConnected Action",
                "type"          : "text",
                "description"   : "JS code to run after a microgear datasource is connected"
            }

        ],

        newInstance : function(settings, newInstanceCallback, updateCallback) {
            newInstanceCallback(new netpieDatasourcePlugin(settings, updateCallback));
        }
    });

    var netpieDatasourcePlugin = function(settings, updateCallback) {
        var self = this;
        var currentSettings = settings;
        var gconf = {
            key: settings.key,
            secret: settings.secret
        }
        if (settings.alias) gconf.alias = settings.alias;

        var data = {};
        var aliasList = {};

        function initSubscribe(toparr, toSub) {
            if (toparr && toparr.length>0) {
                for (var i=0; i< toparr.length; i++) {
                    if (toSub) {
                        self.mg.subscribe(toparr[i]);
                    }
                    else {
                        self.mg.unsubscribe(toparr[i]);
                    }
                }
            }
        }

        self.updateNow = function() {

        }

        self.onSettingsChanged = function(newSettings) {

            if (currentSettings.name && (currentSettings.name != newSettings.name)) {
                var modifiedname = newSettings.name.substring(0,16);

                if (newSettings.name != modifiedname) {
                    var text = "The datasource name should not be longer than 16 characters otherwise the associative id will be shorten i.e. now the microgear object is referenced by microgear[\""+modifiedname+"\"] and the microgear device alias is trimmed to \""+modifiedname+"\".";
                    newSettings.name = modifiedname;
                    freeboard.showDialog(text, "Warning", "I understand");
                }

                if (microgear[currentSettings.name]) {
                    delete(microgear[currentSettings.name]);
                }
                microgear[newSettings.name] = self.mg;

                self.mg.setAlias(newSettings.name);
            }

            if (currentSettings.topics != newSettings.topics) {
                initSubscribe(currentSettings.topics.trim().split(','), false);
                initSubscribe(newSettings.topics.trim().split(','), true);
            }

            if (currentSettings.appid != newSettings.appid || currentSettings.key != newSettings.key || currentSettings.secret != newSettings.secret) {
                freeboard.showDialog("Reconfigure AppID, Key or Secret needs a page reloading. Make sure you save the current configuration before processding.", "Warning", "OK", "CANCEL", function() {
                    location.reload(true);
                })
            }
            currentSettings = newSettings;
        }

        self.onDispose = function() {
            delete(self.mg);
        }

        self.mg = Microgear.create(gconf);

        if(settings.name !== undefined){
            settings.name = settings.name.replace(' ','_').substring(0,16);
        }

        microgear[settings.name] = self.mg;

        self.mg.on('message', function(topic,msg) {
            if (topic && msg) {
                data[topic] = msg;
                updateCallback(data);
            }
        });

        self.mg.on('present', function(m) {
            //console.log('p---> '+JSON.stringify(m));

            var mtoken = m.gear;
            var aobj = {
                token : mtoken
            };
            var found = false;
            if (typeof(aliasList[m.alias]) != 'undefined') {
                for (var k=0; k<aliasList[m.alias].length; k++) {
                    if (aliasList[m.alias][k].token == mtoken) {
                        found = true;
                        break;
                    }
                }
            }
            else {
                aliasList[m.alias] = [];
            }

            if (!found) {
                // if the alias changed, remove the old one located under the old alias name
                if (m.type=='alias') {
                    for (var _alias in aliasList) {
                        for (var k=0; k<aliasList[_alias].length; k++) {
                            if (aliasList[_alias][k].token == mtoken) {
                                aliasList[_alias].splice(k,1);
                            }
                        }
                    }
                }

                aliasList[m.alias].push(aobj);
            }
            data['alias'] = aliasList;
            updateCallback(data);
        });

        self.mg.on('absent', function(m) {
            var mtoken = m.gear;

            if (typeof(aliasList[m.alias]) != 'undefined') {
                for (var k=0; k<aliasList[m.alias].length; k++) {
                    if (aliasList[m.alias][k].token == mtoken) {
                        aliasList[m.alias].splice(k,1);
                        if (aliasList[m.alias].length == 0) delete aliasList[m.alias];
                        break;
                    }
                }
            }
            data['alias'] = aliasList;
            updateCallback(data);
        });

        self.mg.on('connected', function() {
            onlineList = {};

            initSubscribe(settings.topics.trim().split(','), true);
            if (gconf.alias) {
                self.mg.setAlias(gconf.alias);
            }
            else if (settings.name) {
                self.mg.setAlias(settings.name);
            }

            if (settings.onConnectedAction) {
                var timer = setInterval(function() {
                    if (Object.getOwnPropertyNames(microgear).length > 0) {
                        clearInterval(timer);
                        eval(settings.onConnectedAction);
                    }
                },200);
            }

            if (typeof(onConnectedHandler) != 'undefined') {
                //onConnectedHandler(settings.microgearRef);
                onConnectedHandler(settings.name);
            }
        })

        self.mg.connect(settings.appid, function(){

        });

    }
}());

<commit_msg>fix stuck token in alias list<commit_after>/*  NETPIE Microgear Freeboard plugin                             */
/*  Developed by Chavee Issariyapat                               */
/*  More information about NETPIE please visit https://netpie.io  */

if (typeof microgear === "undefined") {
    microgear = {};
}

(function()
{
    freeboard.loadDatasourcePlugin({
        "type_name"   : "netpie_microgear",
        "display_name": "NETPIE Microgear",
        "description" : "Connect to NETPIE as a microgear to communicate real-time with other microgears in the same App ID. The microgear of this datasource is referenced by microgear[DATASOURCENAME]",
        "external_scripts" : [
            "https://cdn.netpie.io/microgear.js"
        ],
        "settings"    : [
            {
                "name"         : "appid",
                "display_name" : "App ID",
                "type"         : "text",
                "description"  : "NETPIE App ID obtained from https://netpie.io/app",
                "required" : true
            },
            {
                "name"         : "key",
                "display_name" : "Key",
                "type"         : "text",
                "description"  : "Key",
                "required"     : true
            },
            {
                "name"        : "secret",
                "display_name" : "Secret",
                "type"         : "text",
                "description"  : "Secret",
                "type"         : "text",
                "required"     : true
            },
/*
            {
                "name"         : "alias",
                "display_name" : "Microgear Alias",
                "type"         : "text",
                "description"  : "A nick name of this freeboard that other device can chat to",
                "type"         : "text",
                "default_value": "freeboard",
                "required"     : false
            },
            {
                "name"         : "microgearRef",
                "display_name" : "Microgear Reference",
                "type"         : "text",
                "description"  : "Define a reference for a microgear of this datasource. For example if you set this to 'mygear' you can access the microgear object by microgear['mygear']",
                "type"         : "text",
                "required"     : false
            },
*/
            {
                "name"         : "topics",
                "display_name" : "Subscribed Topics",
                "type"         : "text",
                "description"  : "Topics of the messages that this datasource will consume, the default is /# which means all messages in this app ID.",
                "default_value": "/#",
                "required"     : false
            },
            {
                "name"          : "onConnectedAction",
                "display_name"  : "onConnected Action",
                "type"          : "text",
                "description"   : "JS code to run after a microgear datasource is connected"
            }

        ],

        newInstance : function(settings, newInstanceCallback, updateCallback) {
            newInstanceCallback(new netpieDatasourcePlugin(settings, updateCallback));
        }
    });

    var netpieDatasourcePlugin = function(settings, updateCallback) {
        var self = this;
        var currentSettings = settings;
        var gconf = {
            key: settings.key,
            secret: settings.secret
        }
        if (settings.alias) gconf.alias = settings.alias;

        var data = {};
        var aliasList = {};

        function initSubscribe(toparr, toSub) {
            if (toparr && toparr.length>0) {
                for (var i=0; i< toparr.length; i++) {
                    if (toSub) {
                        self.mg.subscribe(toparr[i]);
                    }
                    else {
                        self.mg.unsubscribe(toparr[i]);
                    }
                }
            }
        }

        self.updateNow = function() {

        }

        self.onSettingsChanged = function(newSettings) {

            if (currentSettings.name && (currentSettings.name != newSettings.name)) {
                var modifiedname = newSettings.name.substring(0,16);

                if (newSettings.name != modifiedname) {
                    var text = "The datasource name should not be longer than 16 characters otherwise the associative id will be shorten i.e. now the microgear object is referenced by microgear[\""+modifiedname+"\"] and the microgear device alias is trimmed to \""+modifiedname+"\".";
                    newSettings.name = modifiedname;
                    freeboard.showDialog(text, "Warning", "I understand");
                }

                if (microgear[currentSettings.name]) {
                    delete(microgear[currentSettings.name]);
                }
                microgear[newSettings.name] = self.mg;

                self.mg.setAlias(newSettings.name);
            }

            if (currentSettings.topics != newSettings.topics) {
                initSubscribe(currentSettings.topics.trim().split(','), false);
                initSubscribe(newSettings.topics.trim().split(','), true);
            }

            if (currentSettings.appid != newSettings.appid || currentSettings.key != newSettings.key || currentSettings.secret != newSettings.secret) {
                freeboard.showDialog("Reconfigure AppID, Key or Secret needs a page reloading. Make sure you save the current configuration before processding.", "Warning", "OK", "CANCEL", function() {
                    location.reload(true);
                })
            }
            currentSettings = newSettings;
        }

        self.onDispose = function() {
            delete(self.mg);
        }

        self.mg = Microgear.create(gconf);

        if(settings.name !== undefined){
            settings.name = settings.name.replace(' ','_').substring(0,16);
        }

        microgear[settings.name] = self.mg;

        self.mg.on('message', function(topic,msg) {
            if (topic && msg) {
                data[topic] = msg;
                updateCallback(data);
            }
        });

        self.mg.on('present', function(m) {
            //console.log('p---> '+JSON.stringify(m));

            var mtoken = m.gear;
            var aobj = {
                token : mtoken
            };
            var found = false;
            if (typeof(aliasList[m.alias]) != 'undefined') {
                for (var k=0; k<aliasList[m.alias].length; k++) {
                    if (aliasList[m.alias][k].token == mtoken) {
                        found = true;
                        break;
                    }
                }
            }
            else {
                aliasList[m.alias] = [];
            }
            if (!found) {
                // if the alias changed, remove the old one located under the old alias name
                if (m.type=='aliased') {
                    for (var _alias in aliasList) {
                        for (var k=0; k<aliasList[_alias].length; k++) {
                            if (aliasList[_alias][k].token == mtoken) {
                                aliasList[_alias].splice(k,1);
                            }
                        }
                    }
                }

                aliasList[m.alias].push(aobj);
            }

            for (var _alias in aliasList) {
                        if (aliasList[_alias].length == 0) {
                            console.log();
                            delete aliasList[_alias];
                        }
            }

            data['alias'] = aliasList;
            updateCallback(data);
        });

        self.mg.on('absent', function(m) {
            var mtoken = m.gear;

            if (typeof(aliasList[m.alias]) != 'undefined') {
                for (var k=0; k<aliasList[m.alias].length; k++) {
                    if (aliasList[m.alias][k].token == mtoken) {
                        aliasList[m.alias].splice(k,1);
                        if (aliasList[m.alias].length == 0) delete aliasList[m.alias];
                        break;
                    }
                }
            }
            data['alias'] = aliasList;
            updateCallback(data);
        });

        self.mg.on('connected', function() {
            onlineList = {};

            initSubscribe(settings.topics.trim().split(','), true);
            if (gconf.alias) {
                self.mg.setAlias(gconf.alias);
            }
            else if (settings.name) {
                self.mg.setAlias(settings.name);
            }

            if (settings.onConnectedAction) {
                var timer = setInterval(function() {
                    if (Object.getOwnPropertyNames(microgear).length > 0) {
                        clearInterval(timer);
                        eval(settings.onConnectedAction);
                    }
                },200);
            }

            if (typeof(onConnectedHandler) != 'undefined') {
                //onConnectedHandler(settings.microgearRef);
                onConnectedHandler(settings.name);
            }
        })

        self.mg.connect(settings.appid, function(){

        });

    }
}());

<|endoftext|>


<commit_before>var cmail = cmail || {};

cmail.mail = {

	get_all: function() {
		var self = this;
		ajax.asyncGet(cmail.api_url + "mailbox/?get", function(xhr) {
			var mailbox = JSON.parse(xhr.response);

			if (mailbox.status != "ok") {
				cmail.set_status(mailbox.status);
				return;
			}

			var body = gui.elem("mail_list");
			body.innerHTML = "";
			mailbox.mails.forEach(function(mail) {
				var tr = gui.create("tr");

				tr.appendChild(gui.createColumn(mail["mail_id"]));
				tr.appendChild(gui.createColumn(mail["mail_read"]));
				if (mail["mail_date"]) {
					tr.appendChild(gui.createColumn(mail["mail_date"]));
				} else {
					tr.appendChild(gui.createColumn(mail["mail_submission"]));
				}
				tr.appendChild(gui.createColumn(mail["mail_envelopefrom"]));
				tr.appendChild(gui.createColumn(mail["mail_envelopeto"]));
				tr.appendChild(gui.createColumn(mail["mail_subject"]));

				//options
				var options = gui.create("td");
				options.classList.add("align_left");
				options.appendChild(gui.createButton("read", self.read, [mail], self));
				options.appendChild(gui.createButton("delete", self.delete, [mail], self));

				var buttonName = "Mark as read";
				if (mail["mail_read"] == 1) {
					buttonName = "Unread";
				}

				options.appendChild(gui.createButton(buttonName, self.toggleread, [mail], self));
				tr.appendChild(options);

				body.appendChild(tr);
			});
		});
	},
	delete: function(mail) {
		var self = this;
		if (confirm("Do you really delete this mail?") == true) {
			ajax.asyncPost(cmail.api_url + "mailbox/?delete", JSON.stringify(mail), function(xhr) {
				cmail.set_status(JSON.parse(xhr.response).status);
				self.get_all();
			});
		}
	},
	toggleread: function(mail) {
		var self = this;

		var url = "mailbox/?read";
		if (mail["mail_read"] == 1) {
			url = "mailbox/?unread";
		}

		ajax.asyncPost(cmail.api_url + url, JSON.stringify(mail), function(xhr) {
			cmail.set_status(JSON.parse(xhr.response).status);
			self.get_all();
		});
	},
	read: function(mail) {

		var self = this;
		ajax.asyncPost(cmail.api_url + "mailbox/?read", JSON.stringify(mail), function(xhr) {
			var obj = JSON.parse(xhr.response);
			cmail.set_status(obj.status);

			var mail = obj.mail;

			self.currMail = mail;

			var splitted = mail["mail_data"].split("\r\n\r\n");

			gui.elem("mail_id").textContent = mail["mail_id"];
			gui.elem("mail_subject").textContent = mail["mail_subject"];
			gui.elem("mail_from").textContent = mail["mail_from"];
			gui.elem("mail_to").textContent = mail["mail_to"];
			gui.elem("mail_body").textContent = mail["mail_body"];

			cmail.switch_hash("#single");
		});
	},
	unread: function() {
		var self = this;
		ajax.asyncPost(cmail.api_url + "mailbox/?unread", JSON.stringify(self.currMail), function(xhr) {
			cmail.set_status(JSON.parse(xhr.response).status);
			self.back();
		});
	},
	back: function() {
		this.get_all();
		window.location.hash = "#list";
		cmail.switch_hash();
	}
}
<commit_msg>change textContent function to innerText for mail_body<commit_after>var cmail = cmail || {};

cmail.mail = {

	get_all: function() {
		var self = this;
		ajax.asyncGet(cmail.api_url + "mailbox/?get", function(xhr) {
			var mailbox = JSON.parse(xhr.response);

			if (mailbox.status != "ok") {
				cmail.set_status(mailbox.status);
				return;
			}

			var body = gui.elem("mail_list");
			body.innerHTML = "";
			mailbox.mails.forEach(function(mail) {
				var tr = gui.create("tr");

				tr.appendChild(gui.createColumn(mail["mail_id"]));
				tr.appendChild(gui.createColumn(mail["mail_read"]));
				if (mail["mail_date"]) {
					tr.appendChild(gui.createColumn(mail["mail_date"]));
				} else {
					tr.appendChild(gui.createColumn(mail["mail_submission"]));
				}
				tr.appendChild(gui.createColumn(mail["mail_envelopefrom"]));
				tr.appendChild(gui.createColumn(mail["mail_envelopeto"]));
				tr.appendChild(gui.createColumn(mail["mail_subject"]));

				//options
				var options = gui.create("td");
				options.classList.add("align_left");
				options.appendChild(gui.createButton("read", self.read, [mail], self));
				options.appendChild(gui.createButton("delete", self.delete, [mail], self));

				var buttonName = "Mark as read";
				if (mail["mail_read"] == 1) {
					buttonName = "Unread";
				}

				options.appendChild(gui.createButton(buttonName, self.toggleread, [mail], self));
				tr.appendChild(options);

				body.appendChild(tr);
			});
		});
	},
	delete: function(mail) {
		var self = this;
		if (confirm("Do you really delete this mail?") == true) {
			ajax.asyncPost(cmail.api_url + "mailbox/?delete", JSON.stringify(mail), function(xhr) {
				cmail.set_status(JSON.parse(xhr.response).status);
				self.get_all();
			});
		}
	},
	toggleread: function(mail) {
		var self = this;

		var url = "mailbox/?read";
		if (mail["mail_read"] == 1) {
			url = "mailbox/?unread";
		}

		ajax.asyncPost(cmail.api_url + url, JSON.stringify(mail), function(xhr) {
			cmail.set_status(JSON.parse(xhr.response).status);
			self.get_all();
		});
	},
	read: function(mail) {

		var self = this;
		ajax.asyncPost(cmail.api_url + "mailbox/?read", JSON.stringify(mail), function(xhr) {
			var obj = JSON.parse(xhr.response);
			cmail.set_status(obj.status);

			var mail = obj.mail;

			self.currMail = mail;

			var splitted = mail["mail_data"].split("\r\n\r\n");

			gui.elem("mail_id").textContent = mail["mail_id"];
			gui.elem("mail_subject").textContent = mail["mail_subject"];
			gui.elem("mail_from").textContent = mail["mail_from"];
			gui.elem("mail_to").textContent = mail["mail_to"];
			gui.elem("mail_body").innerText = mail["mail_body"];

			cmail.switch_hash("#single");
		});
	},
	unread: function() {
		var self = this;
		ajax.asyncPost(cmail.api_url + "mailbox/?unread", JSON.stringify(self.currMail), function(xhr) {
			cmail.set_status(JSON.parse(xhr.response).status);
			self.back();
		});
	},
	back: function() {
		this.get_all();
		window.location.hash = "#list";
		cmail.switch_hash();
	}
}
<|endoftext|>