<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Full PhET-iO build including phet version, phet-io version and accompanying material.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
var assert = require( 'assert' );
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var child_process = require( 'child_process' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt the grunt instance
 * @param {Object} buildConfig
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // Read a JS file and uglify
  var readUgly = function( filename ) {
    var text = grunt.file.read( filename );
    return uglify.minify( text, {
      mangle: true,
      fromString: true,
      output: {
        inline_script: true, // escape </script
        beautify: false
      },
      compress: {
        global_defs: {}
      }
    } ).code;
  };

  var skipBuild = grunt.option( 'skipBuild' );
  var packageJSON = grunt.file.readJSON( 'package.json' );

  var cwd = process.cwd();

  var phetioBuildDir = cwd + '/build-phet-io';
  var buildDir = cwd + '/build';

  // Clean
  if ( grunt.file.exists( phetioBuildDir ) ) {
    grunt.file.delete( phetioBuildDir );
  }
  grunt.file.mkdir( phetioBuildDir );

  var createOtherPhETIOFiles = function() {

    // create devguide/
    grunt.file.mkdir( 'build/phet-io/devguide' );
    copyDirectory( grunt, '../sherpa/lib/highlight.js-9.1.0', 'build/phet-io/devguide/lib/highlight.js-9.1.0' );
    copyDirectory( grunt, '../phet-io/doc/devguide', 'build/phet-io/devguide', function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {
        return ChipperStringUtils.replaceAll( contents, '../../../sherpa/lib/highlight.js-9.1.0', 'lib/highlight.js-9.1.0' );
      }
    } );

    // output the SimIFrameClient.js and SimWrapperUtils.js to the top level lib (not protected), see https://github.com/phetsims/phet-io/issues/353
    grunt.file.mkdir( 'build/phet-io/lib' );

    // create protected/
    grunt.file.mkdir( 'build/phet-io/protected' );
    grunt.file.mkdir( 'build/phet-io/protected/api' );
    grunt.file.mkdir( 'build/phet-io/protected/wrappers' );

    // Load the template for instance-proxies.html
    var entry = packageJSON.phet[ 'phet-io' ];
    var preload = [];
    if ( entry && entry.preload ) {
      preload = entry.preload;
    }

    var simAPIDeclaration = '';
    for ( var i = 0; i < preload.length; i++ ) {
      simAPIDeclaration = simAPIDeclaration + readUgly( preload[ i ] );
    }
    var templateText = grunt.file.read( '../phet-io/html/templates/load-state.html' );
    var lines = templateText.split( '\n' );
    var templateString = '[';
    lines.forEach( function( line ) {
      templateString = templateString + '\'' + line + '\',\n';
    } );
    // "<script>" does not parse in an HTML file (even if in JS), so we split it apart into separate substrings.
    templateString = ChipperStringUtils.replaceAll( templateString, '<script>', '<scr\'+\'ipt>' );
    templateString = ChipperStringUtils.replaceAll( templateString, '</script>', '</scr\'+\'ipt>' );
    templateString = templateString + '\'\'].join(\'\\n\')';

    var filterWrapper = function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {

        // RULES FOR INSTANCE_PROXIES
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/lodash-2.4.1.min.js"',
          '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/jquery-2.1.0.min.js"',
          '"https://code.jquery.com/jquery-2.2.3.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../js/api/PhETIOCommon.js"></script>',
          '<script>' + readUgly( '../phet-io/js/api/PhETIOCommon.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
          '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          'var isBuiltMode = false;',
          'var isBuiltMode = true;' );
        contents = ChipperStringUtils.replaceAll( contents,
          '<!--SIM_API_DECLARATION-->',
          '<script>' + simAPIDeclaration + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          'var templateText = null;',
          'var templateText = ' + templateString + ';'
        ); //template uses exclusively "
        // END RULES FOR INSTANCE_PROXIES

        contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_NAME$', buildConfig.name );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_VERSION$', buildConfig.version );
        return contents;
      }
    };
    copyDirectory( grunt, '../phet-io/html/wrappers', 'build/phet-io/protected/wrappers', filterWrapper );

    // TODO: what if something overwrites? These directories are being merged.
    var simSpecificPath = '../phet-io/html/' + buildConfig.name;
    if ( grunt.file.exists( simSpecificPath ) ) {
      var failOnExistingFiles = true;
      if ( skipBuild ) {
        failOnExistingFiles = false;
      }
      copyDirectory( grunt, simSpecificPath, 'build/phet-io/protected/wrappers', filterWrapper, { failOnExistingFiles: failOnExistingFiles } );
    }

    // Copy the API files
    for ( i = 0; i < preload.length; i++ ) {
      var lastSlash = preload[ i ].lastIndexOf( '/' );
      var filename = preload[ i ].substring( lastSlash + 1 );
      grunt.file.copy( preload[ i ], 'build/phet-io/protected/api/' + filename );
    }

    // The .htaccess file
    grunt.file.copy( '../phet-io/templates/.htaccess', 'build/phet-io/protected/.htaccess' );

    var wrapperHTML = grunt.file.read( '../phet-io/templates/wrappers.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM$', buildConfig.name );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$VERSION$', buildConfig.version );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$PHET_IO_HTML_SIM_FILENAME$', buildConfig.name + '_en-phetio.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM_NAME$', buildConfig.name );
    grunt.file.write( 'build/phet-io/protected/wrappers.html', wrapperHTML );

    var destinationPath = 'build/phet-io/lib';

    var minifyAndWrite = function( filename, precode ) {
      var minified = uglify.minify( [ '../phet-io/js/' + filename ], {
        mangle: true,
        output: {
          inline_script: true, // escape </script
          beautify: false
        },
        compress: {
          global_defs: {}
        }
      } ).code;

      var copyrightHeader = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                            '// This PhET-iO file requires a license\n' +
                            '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                            '// For licensing, please contact phethelp@colorado.edu';
      grunt.file.write( destinationPath + '/' + filename, copyrightHeader + '\n\n' + precode + minified );
    };

    minifyAndWrite( 'SimIFrameClient.js', '' );

    // Determine which sim versions will be pointed to by SimWrapperUtils
    minifyAndWrite( 'SimWrapperUtils.js', 'window.useRelativeSimPath=true;' );
  };

  if ( skipBuild ) {
    grunt.file.mkdir( 'build/phet-io' );
    createOtherPhETIOFiles();
  }
  else {
    var done = grunt.task.current.async();
    var command1 = 'grunt --brand=phet-io';
    var command2 = 'grunt --brand=phet';
    grunt.log.writeln( 'Starting command: ' + command1 + '...' );
    child_process.exec( command1, { cwd: cwd }, function( error1, stdout1, stderr1 ) {
      assert( !error1, 'error in ' + command1 + '\n' + error1 );
      grunt.log.writeln( stdout1 );
      grunt.log.writeln( stderr1 );
      grunt.log.writeln( 'Finished command: ' + command1 );
      copyDirectory( grunt, buildDir, phetioBuildDir );

      grunt.log.writeln( 'Starting command: ' + command2 + '...' );
      child_process.exec( command2, { cwd: cwd }, function( error2, stdout2, stderr2 ) {
        assert( !error2, 'error in ' + command2 + '\n' + error2 );
        grunt.log.writeln( stdout2 );
        grunt.log.writeln( stderr2 );
        grunt.log.writeln( 'Finished command: ' + command2 );
        copyDirectory( grunt, phetioBuildDir, buildDir + '/phet-io' );

        createOtherPhETIOFiles();
        done();
      } );
    } );
  }
};<commit_msg>Removed sim api declaration block, see #44<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Full PhET-iO build including phet version, phet-io version and accompanying material.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
var assert = require( 'assert' );
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var child_process = require( 'child_process' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt the grunt instance
 * @param {Object} buildConfig
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // Read a JS file and uglify
  var readUgly = function( filename ) {
    var text = grunt.file.read( filename );
    return uglify.minify( text, {
      mangle: true,
      fromString: true,
      output: {
        inline_script: true, // escape </script
        beautify: false
      },
      compress: {
        global_defs: {}
      }
    } ).code;
  };

  var skipBuild = grunt.option( 'skipBuild' );
  var packageJSON = grunt.file.readJSON( 'package.json' );

  var cwd = process.cwd();

  var phetioBuildDir = cwd + '/build-phet-io';
  var buildDir = cwd + '/build';

  // Clean
  if ( grunt.file.exists( phetioBuildDir ) ) {
    grunt.file.delete( phetioBuildDir );
  }
  grunt.file.mkdir( phetioBuildDir );

  var createOtherPhETIOFiles = function() {

    // create devguide/
    grunt.file.mkdir( 'build/phet-io/devguide' );
    copyDirectory( grunt, '../sherpa/lib/highlight.js-9.1.0', 'build/phet-io/devguide/lib/highlight.js-9.1.0' );
    copyDirectory( grunt, '../phet-io/doc/devguide', 'build/phet-io/devguide', function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {
        return ChipperStringUtils.replaceAll( contents, '../../../sherpa/lib/highlight.js-9.1.0', 'lib/highlight.js-9.1.0' );
      }
    } );

    // output the SimIFrameClient.js and SimWrapperUtils.js to the top level lib (not protected), see https://github.com/phetsims/phet-io/issues/353
    grunt.file.mkdir( 'build/phet-io/lib' );

    // create protected/
    grunt.file.mkdir( 'build/phet-io/protected' );
    grunt.file.mkdir( 'build/phet-io/protected/api' );
    grunt.file.mkdir( 'build/phet-io/protected/wrappers' );

    // Load the template for instance-proxies.html
    var entry = packageJSON.phet[ 'phet-io' ];
    var preload = [];
    if ( entry && entry.preload ) {
      preload = entry.preload;
    }

    var simAPIDeclaration = '';
    for ( var i = 0; i < preload.length; i++ ) {
      simAPIDeclaration = simAPIDeclaration + readUgly( preload[ i ] );
    }
    var templateText = grunt.file.read( '../phet-io/html/templates/load-state.html' );
    var lines = templateText.split( '\n' );
    var templateString = '[';
    lines.forEach( function( line ) {
      templateString = templateString + '\'' + line + '\',\n';
    } );
    // "<script>" does not parse in an HTML file (even if in JS), so we split it apart into separate substrings.
    templateString = ChipperStringUtils.replaceAll( templateString, '<script>', '<scr\'+\'ipt>' );
    templateString = ChipperStringUtils.replaceAll( templateString, '</script>', '</scr\'+\'ipt>' );
    templateString = templateString + '\'\'].join(\'\\n\')';

    var filterWrapper = function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {

        // RULES FOR INSTANCE_PROXIES
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/lodash-2.4.1.min.js"',
          '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/jquery-2.1.0.min.js"',
          '"https://code.jquery.com/jquery-2.2.3.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../js/api/PhETIOCommon.js"></script>',
          '<script>' + readUgly( '../phet-io/js/api/PhETIOCommon.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
          '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          'var isBuiltMode = false;',
          'var isBuiltMode = true;' );
        contents = ChipperStringUtils.replaceAll( contents,
          'var templateText = null;',
          'var templateText = ' + templateString + ';'
        ); //template uses exclusively "
        // END RULES FOR INSTANCE_PROXIES

        contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_NAME$', buildConfig.name );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_VERSION$', buildConfig.version );
        return contents;
      }
    };
    copyDirectory( grunt, '../phet-io/html/wrappers', 'build/phet-io/protected/wrappers', filterWrapper );

    // TODO: what if something overwrites? These directories are being merged.
    var simSpecificPath = '../phet-io/html/' + buildConfig.name;
    if ( grunt.file.exists( simSpecificPath ) ) {
      var failOnExistingFiles = true;
      if ( skipBuild ) {
        failOnExistingFiles = false;
      }
      copyDirectory( grunt, simSpecificPath, 'build/phet-io/protected/wrappers', filterWrapper, { failOnExistingFiles: failOnExistingFiles } );
    }

    // Copy the API files
    for ( i = 0; i < preload.length; i++ ) {
      var lastSlash = preload[ i ].lastIndexOf( '/' );
      var filename = preload[ i ].substring( lastSlash + 1 );
      grunt.file.copy( preload[ i ], 'build/phet-io/protected/api/' + filename );
    }

    // The .htaccess file
    grunt.file.copy( '../phet-io/templates/.htaccess', 'build/phet-io/protected/.htaccess' );

    var wrapperHTML = grunt.file.read( '../phet-io/templates/wrappers.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM$', buildConfig.name );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$VERSION$', buildConfig.version );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$PHET_IO_HTML_SIM_FILENAME$', buildConfig.name + '_en-phetio.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM_NAME$', buildConfig.name );
    grunt.file.write( 'build/phet-io/protected/wrappers.html', wrapperHTML );

    var destinationPath = 'build/phet-io/lib';

    var minifyAndWrite = function( filename, precode ) {
      var minified = uglify.minify( [ '../phet-io/js/' + filename ], {
        mangle: true,
        output: {
          inline_script: true, // escape </script
          beautify: false
        },
        compress: {
          global_defs: {}
        }
      } ).code;

      var copyrightHeader = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                            '// This PhET-iO file requires a license\n' +
                            '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                            '// For licensing, please contact phethelp@colorado.edu';
      grunt.file.write( destinationPath + '/' + filename, copyrightHeader + '\n\n' + precode + minified );
    };

    minifyAndWrite( 'SimIFrameClient.js', '' );

    // Determine which sim versions will be pointed to by SimWrapperUtils
    minifyAndWrite( 'SimWrapperUtils.js', 'window.useRelativeSimPath=true;' );
  };

  if ( skipBuild ) {
    grunt.file.mkdir( 'build/phet-io' );
    createOtherPhETIOFiles();
  }
  else {
    var done = grunt.task.current.async();
    var command1 = 'grunt --brand=phet-io';
    var command2 = 'grunt --brand=phet';
    grunt.log.writeln( 'Starting command: ' + command1 + '...' );
    child_process.exec( command1, { cwd: cwd }, function( error1, stdout1, stderr1 ) {
      assert( !error1, 'error in ' + command1 + '\n' + error1 );
      grunt.log.writeln( stdout1 );
      grunt.log.writeln( stderr1 );
      grunt.log.writeln( 'Finished command: ' + command1 );
      copyDirectory( grunt, buildDir, phetioBuildDir );

      grunt.log.writeln( 'Starting command: ' + command2 + '...' );
      child_process.exec( command2, { cwd: cwd }, function( error2, stdout2, stderr2 ) {
        assert( !error2, 'error in ' + command2 + '\n' + error2 );
        grunt.log.writeln( stdout2 );
        grunt.log.writeln( stderr2 );
        grunt.log.writeln( 'Finished command: ' + command2 );
        copyDirectory( grunt, phetioBuildDir, buildDir + '/phet-io' );

        createOtherPhETIOFiles();
        done();
      } );
    } );
  }
};<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Full PhET-iO build including phet version, phet-io version and accompanying material.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
var assert = require( 'assert' );
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var child_process = require( 'child_process' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt the grunt instance
 * @param {Object} buildConfig
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // Read a JS file and uglify
  var readUgly = function( filename ) {
    var text = grunt.file.read( filename );
    return uglify.minify( text, {
      mangle: true,
      fromString: true,
      output: {
        inline_script: true, // escape </script
        beautify: false
      },
      compress: {
        global_defs: {}
      }
    } ).code;
  };

  var skipBuild = grunt.option( 'skipBuild' );
  var packageJSON = grunt.file.readJSON( 'package.json' );

  var cwd = process.cwd();

  var phetioBuildDir = cwd + '/build-phet-io';
  var buildDir = cwd + '/build';

  // Clean
  if ( grunt.file.exists( phetioBuildDir ) ) {
    grunt.file.delete( phetioBuildDir );
  }
  grunt.file.mkdir( phetioBuildDir );

  var createOtherPhETIOFiles = function() {

    // create devguide/
    grunt.file.mkdir( 'build/phet-io/devguide' );
    copyDirectory( grunt, '../sherpa/lib/highlight.js-9.1.0', 'build/phet-io/devguide/lib/highlight.js-9.1.0' );
    copyDirectory( grunt, '../phet-io/doc/devguide', 'build/phet-io/devguide', function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {
        return ChipperStringUtils.replaceAll( contents, '../../../sherpa/lib/highlight.js-9.1.0', 'lib/highlight.js-9.1.0' );
      }
    } );

    // output the SimIFrameClient.js and SimWrapperUtils.js to the top level lib (not protected), see https://github.com/phetsims/phet-io/issues/353
    grunt.file.mkdir( 'build/phet-io/lib' );

    // create protected/
    grunt.file.mkdir( 'build/phet-io/protected' );
    grunt.file.mkdir( 'build/phet-io/protected/api' );
    grunt.file.mkdir( 'build/phet-io/protected/wrappers' );

    // Load the template for instance-proxies.html
    var entry = packageJSON.phet[ 'phet-io' ];
    var preload = [];
    if ( entry && entry.preload ) {
      preload = entry.preload;
    }

    var simAPIDeclaration = '';
    for ( var i = 0; i < preload.length; i++ ) {
      simAPIDeclaration = simAPIDeclaration + readUgly( preload[ i ] );
    }
    var templateText = grunt.file.read( '../phet-io/html/templates/load-state.html' );
    var lines = templateText.split( '\n' );
    var templateString = '[';
    lines.forEach( function( line ) {
      templateString = templateString + '\'' + line + '\',\n';
    } );
    // "<script>" does not parse in an HTML file (even if in JS), so we split it apart into separate substrings.
    templateString = ChipperStringUtils.replaceAll( templateString, '<script>', '<scr\'+\'ipt>' );
    templateString = ChipperStringUtils.replaceAll( templateString, '</script>', '</scr\'+\'ipt>' );
    templateString = templateString + '\'\'].join(\'\\n\')';

    var filterWrapper = function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {

        // RULES FOR INSTANCE_PROXIES
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/lodash-2.4.1.min.js"',
          '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/jquery-2.1.0.min.js"',
          '"https://code.jquery.com/jquery-2.2.3.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../js/PhETIOCommon.js"></script>',
          '<script>' + readUgly( '../phet-io/js/PhETIOCommon.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
          '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          'var isBuiltMode = false;',
          'var isBuiltMode = true;' );
        contents = ChipperStringUtils.replaceAll( contents,
          'var templateText = null;',
          'var templateText = ' + templateString + ';'
        ); //template uses exclusively "
        // END RULES FOR INSTANCE_PROXIES

        contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_NAME$', buildConfig.name );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_VERSION$', buildConfig.version );
        return contents;
      }
    };
    copyDirectory( grunt, '../phet-io/html/wrappers', 'build/phet-io/protected/wrappers', filterWrapper );

    // TODO: what if something overwrites? These directories are being merged.
    var simSpecificPath = '../phet-io/html/' + buildConfig.name;
    if ( grunt.file.exists( simSpecificPath ) ) {
      var failOnExistingFiles = true;
      if ( skipBuild ) {
        failOnExistingFiles = false;
      }
      copyDirectory( grunt, simSpecificPath, 'build/phet-io/protected/wrappers', filterWrapper, { failOnExistingFiles: failOnExistingFiles } );
    }

    // Copy the API files.  The file is located in e.g., phet-io/js/simulations/concentration/concentration-api.js
    var apiFilename = buildConfig.name + '-api.js';
    var apiFilePath = '../phet-io/js/simulations/' + buildConfig.name + '/' + apiFilename;
    grunt.file.copy( apiFilePath, 'build/phet-io/protected/api/' + apiFilename );
    // TODO: How to copy other dependencies, such as beers-law-lab?

    // The .htaccess file
    grunt.file.copy( '../phet-io/templates/.htaccess', 'build/phet-io/protected/.htaccess' );

    var wrapperHTML = grunt.file.read( '../phet-io/templates/wrappers.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM$', buildConfig.name );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$VERSION$', buildConfig.version );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$PHET_IO_HTML_SIM_FILENAME$', buildConfig.name + '_en-phetio.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM_NAME$', buildConfig.name );
    grunt.file.write( 'build/phet-io/protected/wrappers.html', wrapperHTML );

    var destinationPath = 'build/phet-io/lib';

    var minifyAndWrite = function( filename, precode ) {
      var minified = uglify.minify( [ '../phet-io/js/' + filename ], {
        mangle: true,
        output: {
          inline_script: true, // escape </script
          beautify: false
        },
        compress: {
          global_defs: {}
        }
      } ).code;

      var copyrightHeader = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                            '// This PhET-iO file requires a license\n' +
                            '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                            '// For licensing, please contact phethelp@colorado.edu';
      grunt.file.write( destinationPath + '/' + filename, copyrightHeader + '\n\n' + precode + minified );
    };

    minifyAndWrite( 'SimIFrameClient.js', '' );

    // Determine which sim versions will be pointed to by SimWrapperUtils
    minifyAndWrite( 'SimWrapperUtils.js', 'window.useRelativeSimPath=true;' );
  };

  if ( skipBuild ) {
    grunt.file.mkdir( 'build/phet-io' );
    createOtherPhETIOFiles();
  }
  else {
    var done = grunt.task.current.async();
    var command1 = 'grunt --brand=phet-io';
    var command2 = 'grunt --brand=phet';
    grunt.log.writeln( 'Starting command: ' + command1 + '...' );
    child_process.exec( command1, { cwd: cwd }, function( error1, stdout1, stderr1 ) {
      assert( !error1, 'error in ' + command1 + '\n' + error1 );
      grunt.log.writeln( stdout1 );
      grunt.log.writeln( stderr1 );
      grunt.log.writeln( 'Finished command: ' + command1 );
      copyDirectory( grunt, buildDir, phetioBuildDir );

      grunt.log.writeln( 'Starting command: ' + command2 + '...' );
      child_process.exec( command2, { cwd: cwd }, function( error2, stdout2, stderr2 ) {
        assert( !error2, 'error in ' + command2 + '\n' + error2 );
        grunt.log.writeln( stdout2 );
        grunt.log.writeln( stderr2 );
        grunt.log.writeln( 'Finished command: ' + command2 );
        copyDirectory( grunt, phetioBuildDir, buildDir + '/phet-io' );

        createOtherPhETIOFiles();
        done();
      } );
    } );
  }
};<commit_msg>Omit api file during build<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Full PhET-iO build including phet version, phet-io version and accompanying material.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// modules
var assert = require( 'assert' );
var copyDirectory = require( '../../../chipper/js/grunt/copyDirectory' );
var child_process = require( 'child_process' );
var ChipperStringUtils = require( '../../../chipper/js/common/ChipperStringUtils' );

/**
 * @param grunt the grunt instance
 * @param {Object} buildConfig
 */
module.exports = function( grunt, buildConfig ) {
  'use strict';

  // TODO: chipper#101 eek, this is scary! we are importing from the repository dir. ideally we should just have uglify-js installed once in chipper?
  var uglify = require( '../../../' + buildConfig.name + '/node_modules/uglify-js' );

  // Read a JS file and uglify
  var readUgly = function( filename ) {
    var text = grunt.file.read( filename );
    return uglify.minify( text, {
      mangle: true,
      fromString: true,
      output: {
        inline_script: true, // escape </script
        beautify: false
      },
      compress: {
        global_defs: {}
      }
    } ).code;
  };

  var skipBuild = grunt.option( 'skipBuild' );
  var packageJSON = grunt.file.readJSON( 'package.json' );

  var cwd = process.cwd();

  var phetioBuildDir = cwd + '/build-phet-io';
  var buildDir = cwd + '/build';

  // Clean
  if ( grunt.file.exists( phetioBuildDir ) ) {
    grunt.file.delete( phetioBuildDir );
  }
  grunt.file.mkdir( phetioBuildDir );

  var createOtherPhETIOFiles = function() {

    // create devguide/
    grunt.file.mkdir( 'build/phet-io/devguide' );
    copyDirectory( grunt, '../sherpa/lib/highlight.js-9.1.0', 'build/phet-io/devguide/lib/highlight.js-9.1.0' );
    copyDirectory( grunt, '../phet-io/doc/devguide', 'build/phet-io/devguide', function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {
        return ChipperStringUtils.replaceAll( contents, '../../../sherpa/lib/highlight.js-9.1.0', 'lib/highlight.js-9.1.0' );
      }
    } );

    // output the SimIFrameClient.js and SimWrapperUtils.js to the top level lib (not protected), see https://github.com/phetsims/phet-io/issues/353
    grunt.file.mkdir( 'build/phet-io/lib' );

    // create protected/
    grunt.file.mkdir( 'build/phet-io/protected' );
    grunt.file.mkdir( 'build/phet-io/protected/api' );
    grunt.file.mkdir( 'build/phet-io/protected/wrappers' );

    // Load the template for instance-proxies.html
    var entry = packageJSON.phet[ 'phet-io' ];
    var preload = [];
    if ( entry && entry.preload ) {
      preload = entry.preload;
    }

    var simAPIDeclaration = '';
    for ( var i = 0; i < preload.length; i++ ) {
      simAPIDeclaration = simAPIDeclaration + readUgly( preload[ i ] );
    }
    var templateText = grunt.file.read( '../phet-io/html/templates/load-state.html' );
    var lines = templateText.split( '\n' );
    var templateString = '[';
    lines.forEach( function( line ) {
      templateString = templateString + '\'' + line + '\',\n';
    } );
    // "<script>" does not parse in an HTML file (even if in JS), so we split it apart into separate substrings.
    templateString = ChipperStringUtils.replaceAll( templateString, '<script>', '<scr\'+\'ipt>' );
    templateString = ChipperStringUtils.replaceAll( templateString, '</script>', '</scr\'+\'ipt>' );
    templateString = templateString + '\'\'].join(\'\\n\')';

    var filterWrapper = function( abspath, contents ) {
      if ( abspath.indexOf( '.html' ) >= 0 ) {

        // RULES FOR INSTANCE_PROXIES
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/lodash-2.4.1.min.js"',
          '"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '"../../../sherpa/lib/jquery-2.1.0.min.js"',
          '"https://code.jquery.com/jquery-2.2.3.min.js"'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../js/PhETIOCommon.js"></script>',
          '<script>' + readUgly( '../phet-io/js/PhETIOCommon.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          '<script type="text/javascript" src="../../../assert/js/assert.js"></script>',
          '<script>' + grunt.file.read( '../assert/js/assert.js' ) + '</script>'
        );
        contents = ChipperStringUtils.replaceAll( contents,
          'var isBuiltMode = false;',
          'var isBuiltMode = true;' );
        contents = ChipperStringUtils.replaceAll( contents,
          'var templateText = null;',
          'var templateText = ' + templateString + ';'
        ); //template uses exclusively "
        // END RULES FOR INSTANCE_PROXIES

        contents = ChipperStringUtils.replaceAll( contents, '../../js/', '../../lib/' );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_NAME$', buildConfig.name );
        contents = ChipperStringUtils.replaceAll( contents, '$SIMULATION_VERSION$', buildConfig.version );
        return contents;
      }
    };
    copyDirectory( grunt, '../phet-io/html/wrappers', 'build/phet-io/protected/wrappers', filterWrapper );

    // TODO: what if something overwrites? These directories are being merged.
    var simSpecificPath = '../phet-io/html/' + buildConfig.name;
    if ( grunt.file.exists( simSpecificPath ) ) {
      var failOnExistingFiles = true;
      if ( skipBuild ) {
        failOnExistingFiles = false;
      }
      copyDirectory( grunt, simSpecificPath, 'build/phet-io/protected/wrappers', filterWrapper, { failOnExistingFiles: failOnExistingFiles } );
    }

    // The .htaccess file
    grunt.file.copy( '../phet-io/templates/.htaccess', 'build/phet-io/protected/.htaccess' );

    var wrapperHTML = grunt.file.read( '../phet-io/templates/wrappers.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM$', buildConfig.name );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$VERSION$', buildConfig.version );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$PHET_IO_HTML_SIM_FILENAME$', buildConfig.name + '_en-phetio.html' );
    wrapperHTML = ChipperStringUtils.replaceAll( wrapperHTML, '$SIM_NAME$', buildConfig.name );
    grunt.file.write( 'build/phet-io/protected/wrappers.html', wrapperHTML );

    var destinationPath = 'build/phet-io/lib';

    var minifyAndWrite = function( filename, precode ) {
      var minified = uglify.minify( [ '../phet-io/js/' + filename ], {
        mangle: true,
        output: {
          inline_script: true, // escape </script
          beautify: false
        },
        compress: {
          global_defs: {}
        }
      } ).code;

      var copyrightHeader = '// Copyright 2002-2016, University of Colorado Boulder\n' +
                            '// This PhET-iO file requires a license\n' +
                            '// USE WITHOUT A LICENSE AGREEMENT IS STRICTLY PROHIBITED.\n' +
                            '// For licensing, please contact phethelp@colorado.edu';
      grunt.file.write( destinationPath + '/' + filename, copyrightHeader + '\n\n' + precode + minified );
    };

    minifyAndWrite( 'SimIFrameClient.js', '' );

    // Determine which sim versions will be pointed to by SimWrapperUtils
    minifyAndWrite( 'SimWrapperUtils.js', 'window.useRelativeSimPath=true;' );
  };

  if ( skipBuild ) {
    grunt.file.mkdir( 'build/phet-io' );
    createOtherPhETIOFiles();
  }
  else {
    var done = grunt.task.current.async();
    var command1 = 'grunt --brand=phet-io';
    var command2 = 'grunt --brand=phet';
    grunt.log.writeln( 'Starting command: ' + command1 + '...' );
    child_process.exec( command1, { cwd: cwd }, function( error1, stdout1, stderr1 ) {
      assert( !error1, 'error in ' + command1 + '\n' + error1 );
      grunt.log.writeln( stdout1 );
      grunt.log.writeln( stderr1 );
      grunt.log.writeln( 'Finished command: ' + command1 );
      copyDirectory( grunt, buildDir, phetioBuildDir );

      grunt.log.writeln( 'Starting command: ' + command2 + '...' );
      child_process.exec( command2, { cwd: cwd }, function( error2, stdout2, stderr2 ) {
        assert( !error2, 'error in ' + command2 + '\n' + error2 );
        grunt.log.writeln( stdout2 );
        grunt.log.writeln( stderr2 );
        grunt.log.writeln( 'Finished command: ' + command2 );
        copyDirectory( grunt, phetioBuildDir, buildDir + '/phet-io' );

        createOtherPhETIOFiles();
        done();
      } );
    } );
  }
};<|endoftext|>


<commit_before>// A very light wrapper around jqplot that scrapes tables for data to plot

(function($) {
$.fn.tablechart = function(options) {
  var o = $.extend(true, {}, $.fn.tablechart.defaults, options);

  this.each(function(i) {
    chart = $(this).data('TableChart');
    if (chart == undefined) {
      tablechart = new TableChart(this, o);
      $(this).data('TableChart', tablechart);
    } else {
      chart.draw();
    }
  });

  return this;
};

// Defaults
$.fn.tablechart.defaults = {
  hideTables: false,
  hideChart: false,
  append: false,
  height: false,
  width: false,
  appendElement: null,
  plotOptions: {
    series: []
  }
};

function TableChart(el, options) {
  this.options = options;
  this.el = el;

  this.chartId = $.uuid('chart-');
  this.chartContainer = $('
').attr('id', this.chartId);

  if (options.height) { this.chartContainer.height(options.height); }
  if (options.width) { this.chartContainer.width(options.width); }

  // Attach chart
  $(el).prepend(this.chartContainer);

  this.draw();

  if (this.options.hideChart) {
    $('.tablechart').hide();
  }

}

TableChart.prototype.draw = function() {
  var tables;
  var series = [];
  var series_opts = [];
  var tablechart = this;

  // If element is not a table, look for them
  if (!$.nodeName(this.el, 'table')) {
    tables = $('table', this.el);
  } else {
    tables = $(this.el);
  }

  // Get each table, add the series
  tables.each(function(i) {
    series.push(tablechart.scrapeTable($(this)));
    // Extract
    if ($.metadata) {
      metadata = $(this).metadata();
      if (metadata) {
        series_opts[i] = metadata;
      }
    }
  });

  // Extend series options with metadata from table
  additional_opts = {series: series_opts}
  $.extend(true, this.options.plotOptions, additional_opts);

  // Hide tables
  if (this.options.hideTables) {
    tables.hide();
  }

  // Draw the chart
  if (series.length > 0) {
    this.chart = $.jqplot(this.chartId, series, this.options.plotOptions);
  }
}

/**
 * Scrape "2-d" table for series and data values.
 * - Table must have series labels as table header columns in the table header.
 * - Table must use th tag as first element in tbody rows to provide x axis
 *   values.
 *
 * Returns an associative array with 'labels' and 'data'.
 */
TableChart.prototype.scrapeTable = function(table) {
  var series = [];
  var labels = [];
  var tablechart = this;
  var j = 0;

  table.find('tbody tr').each(function(i) {
    var val = 0, xval = 0, data = [];
    $(this).find('th').each(function(j) {
      xval = tablechart.parseValue(this);
    });
    $(this).find('td').each(function(j) {
      series.push([xval, tablechart.parseValue(this)]);
    });
  });

  return series;
}

// Potentially vestigal function
TableChart.prototype.parseValue = function(element) {
  return parseFloat($(element).text());
}

})(jQuery);
<commit_msg>Commit before rolling in new version of tablechart plugin.<commit_after>/**
 * @file jquery.tablechart.js
 *
 * A light jqPlot wrapper that scrapes HTML tables to create charts
 *
 * Requires the jQuery UUID plugin (http://plugins.jquery.com/project/uuid)
 * Optionally uses the jQuery metadata plugin  (http://plugins.jquery.com/project/metadata)
 *
 */

(function($) {
$.fn.tablechart = function(options) {
  var o = $.extend(true, {}, $.fn.tablechart.defaults, options);

  this.each(function(i) {
    chart = $(this).data('TableChart');
    if (chart == undefined) {
      tablechart = new TableChart(this, o);
      $(this).data('TableChart', tablechart);
    } else {
      chart.draw();
    }
  });

  return this;
};

// Defaults
$.fn.tablechart.defaults = {
  hideTables: false,
  height: false,
  width: false,
  chartContainer: $('
'),
  appendElement: null,
  plotOptions: {
    series: []
  }
};

function TableChart(el, options) {
  this.options = options;
  this.el = el;

  this.chartId = $.uuid('chart-');
  this.chartContainer = options.chartContainer.attr('id', this.chartId);

  if (options.height) { this.chartContainer.height(options.height); }
  if (options.width) { this.chartContainer.width(options.width); }

  // Attach chart
  $(el).prepend(this.chartContainer);

  this.draw();

}

TableChart.prototype.draw = function() {
  var tables;
  var series = [];
  var series_opts = [];
  var tablechart = this;

  // If element is not a table, look for them
  if (!$.nodeName(this.el, 'table')) {
    tables = $('table', this.el);
  } else {
    tables = $(this.el);
  }

  // Get each table, add the series
  tables.each(function(i) {
    series.push(tablechart.scrapeTable($(this)));
    // Extract
    if ($.metadata) {
      metadata = $(this).metadata();
      if (metadata) {
        series_opts[i] = metadata;
      }
    }
  });

  // Extend series options with metadata from table
  additional_opts = {series: series_opts}
  $.extend(true, this.options.plotOptions, additional_opts);

  // Hide tables
  if (this.options.hideTables) {
    tables.hide();
  }

  // Draw the chart
  if (series.length > 0) {
    this.chart = $.jqplot(this.chartId, series, this.options.plotOptions);
  }
}

/**
 * Scrape "2-d" table for series and data values.
 * - Table must have series labels as table header columns in the table header.
 * - Table must use th tag as first element in tbody rows to provide x axis
 *   values.
 *
 * Returns an associative array with 'labels' and 'data'.
 */
TableChart.prototype.scrapeTable = function(table) {
  var series = [];
  var labels = [];
  var tablechart = this;
  var j = 0;

  table.find('tbody tr').each(function(i) {
    var val = 0, xval = 0, data = [];
    $(this).find('th').each(function(j) {
      xval = tablechart.parseValue(this);
    });
    $(this).find('td').each(function(j) {
      series.push([xval, tablechart.parseValue(this)]);
    });
  });

  return series;
}

// Potentially vestigal function
TableChart.prototype.parseValue = function(element) {
  return parseFloat($(element).text());
}

})(jQuery);
<|endoftext|>


<commit_before>/*
theadFixed plugin

Makes the body + foot of a table scrollable, while a "fixed" copy of the thead.
*/
;(function($, undefined) {
    /* css to copy from original col elements */
    var copy_col_css = [
        'display'
    ];
    /* css to copy from original th elements */
    var copy_th_css = [
        'display', 'position', 'color', 'background-color',
        'font-family', 'font-weight', 'font-size', 'font-style', 'text-align', 'vertical-align',
        'white-space', 'overflow-x',
        'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
        'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
        'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
        'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'
    ];
    /* css to copy from the original table */
    var copy_table_css = [
        'border-top-width', 'border-right-width', 'border-left-width',
        'border-top-style', 'border-right-style', 'border-left-style',
        'border-top-color', 'border-right-color', 'border-left-color',
        'border-collapse', 'border-spacing'
    ];

    // Find the element whose DOM location relative to otherRoot is the same as element's postion relative to root.
    // eg. If element is the 3rd child of the 5th child of root, find the 3rd child of the 5th child of otherRoot
    function treeMap(element, root, otherRoot) {
        if ( element.length == 0 ) {
            return $();
        } else if ( element.is(root) ) {
            return otherRoot;
        } else {
            var mappedParent = treeMap(element.parent(), root, otherRoot);
            return mappedParent.children().eq(element.index());
        }
    }

    $.widget('qcode.theadFixed', {
	options: {
	    'height': "500px",
            'fixedWidth': false,
            'initialScroll': "start"
	},
	_create: function() {
            this.table = this.element;
            this.theadCells = this.table.children('thead').children('tr').first().children('th');
            this.columns = this.table.children('colgroup').children('col');

            // Create the new thead as a separate table
            this.headClone = $('
')
                .append(this.table.children('colgroup').clone())
                .append(this.table.children('thead').clone())
                .addClass('thead-fixed-clone');
            this.headClone.children('tbody, tfoot').remove();
            var id = this.headClone.getID();
            qcode.style('#'+id, 'table-layout', "fixed");

            // Remove "name" attribute from original thead form elements
            this.table.children('thead').find(':input').removeAttr('name');

            // Generate and store column selectors
            var colSelectors = {};
            var thSelectors = {};
            this.theadCells.each(function(i, th) {
                colSelectors[i] = '#'+id+'>colgroup>col:nth-child('+(i+1)+')';
                thSelectors[i] = '#'+id+'>thead>tr>th:nth-child('+(i+1)+')';
            });
            this.colSelectors = colSelectors;
            this.thSelectors = thSelectors;

            // Create the wrappers
            this.table.wrap('
');
            this.scrollBox = this.table.parent().wrap('
');
            this.wrapper = this.scrollBox.parent().css({height: this.options.height});

            // Set the initial scroll position (wait for all other plugins to load)
            if ( this.options.initialScroll === "end" ) {
                var scrollBox = this.scrollBox;
                $('body').one('pluginsReady', function() {
                    var scrollTop = scrollBox[0].scrollHeight - scrollBox.height();
                    scrollBox.scrollTop(scrollTop);
                });
            }

            // Add the resize event listeners - only repaint when the table is resized
            // or the window width changes.
            var windowWidth = window.innerWidth;
            var widget = this;
            // On window resize, or when a resize bubbles to the window.
            this._on($(window), {
                'resize': function(event) {
                    if ( $(event.target).closest(this.element.add(this.headClone)).length > 0 ) {
                        this.repaint();

                    } else if ( windowWidth != window.innerWidth ) {
                        this.repaintStyles();
                        if ( ! this.options.fixedWidth ) {
                            this.repaintWidths();
                            windowWidth = window.innerWidth;
                        }
                    }
                }
            });

            // Copy click events back to the matching element in the original thead
            var handlers = {};
            var copy = function copyEvent(event) {
                var target = $(event.target);
                var eventProperties = {};
                $.each([
                    'pageX',
                    'pageY',
                    'which',
                    'data',
                    'metaKey',
                    'namespace',
                    'timeStamp',
                    'clientX',
                    'clientY'
                ], function(i, property) {
                    eventProperties[property] = event[property];
                });
                $.each(['relatedTarget'], function(i, property) {
                    if ( $(event[property]).closest(this.table).length > 0 ) {
                        eventProperties[property] = treeMap(event[property], this.headClone, this.table);
                    } else {
                        eventProperties[property] = event[property];
                    }
                });
                var originalElement = treeMap(target, this.headClone, this.table);
                var eventCopy = jQuery.Event(event.type, eventProperties);
                originalElement.trigger(eventCopy);
                if ( eventCopy.isDefaultPrevented() ) {
                    event.preventDefault();
                }
            };
            jQuery.each([
                'click',
                'mousedown',
                'mouseup',
                'mouseover',
                'mouseout',
                'mousemove',
                'contextmenu'
            ], function(i, eventName) {
                handlers[eventName] = copy;
            });
            this._on(this.headClone, handlers);

            // Where supported, MutationObserver allows us to listen for changes to the DOM
            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
            if ( MutationObserver ) {
                // When the contents or structure of the table change, repaint the widths.
                this.htmlObserver = new MutationObserver(this.repaintWidths.bind(this));
                this.htmlObserver.observe(
                    this.wrapper[0],
                    {
                        childList: true,
                        characterData: true,
                        subtree: true
                    }
                );

                // When the class or style of any element in the original thead or colgroup change,
                // or when an element in the original thead becomes disabled,
                // replicate this change to the thead copy.
                // If one or more classes changed, repaint the styles.
                this.styleObserver = new MutationObserver(function(mutations) {
                    var summary = mutationSummary(mutations);
                    var needsRepaint = false;
                    summary.forEach(function(change) {
                        var target = $(change.element);
                        var name = change.attribute;
                        if ( name === "class" ) {
                            needsRepaint = true;
                        }
                        if ( attrEqual(change.newValue, "") ) {
                            treeMap(target, widget.table, widget.headClone).removeAttr(name);
                        }  else {
                            treeMap(target, widget.table, widget.headClone).attr(name, change.newValue);
                        }
                    });
                    if ( needsRepaint ) {
                        widget.repaintStyles();
                    }
                });
                this.styleObserver.observe(
                    this.table.children('thead')[0],
                    {
                        attributes: true,
                        attributeFilter: ['class', 'style', 'disabled'],
                        attributeOldValue: true,
                        subtree: true
                    }
                );
                this.styleObserver.observe(
                    this.table.children('colgroup')[0],
                    {
                        attributes: true,
                        attributeFilter: ['class', 'style'],
                        attributeOldValue: true,
                        subtree: true
                    }
                );
            }

            // Call repaint once to set the widths and styles
            this.repaint();

            // Add the head to the page last, after widths and styles have been calculated
            // (this avoids a google chrome bug)
            this.wrapper.prepend(this.headClone);

            this.zoomFix();
            // end of _create;
	},
	repaint: function() {
            this.repaintStyles();
            this.repaintWidths();
        },
        zoomFix: function() {
            var cloneRow = this.headClone.children('thead').children('tr').first();
            var originalRow = this.table.children('thead').children('tr').first();
            var thSelectors = this.thSelectors;
            for ( var multiplier = 0.99 ; multiplier > 0 ; multiplier -= 0.01 ) {
                if ( cloneRow.height() <= originalRow.height() ) {
                    break
                }
                styles = {};
                this.theadCells.each(function(i, originalTH) {
                    styles[thSelectors[i]] = {
                        'font-size':
                        (
                            parseFloat( $(originalTH).css('font-size') ) * multiplier
                        ) + "px"
                    };
                });
                qcode.style(styles);
            }
        },
        repaintWidths: function() {
            // Measure and apply table and column widths
            var widget = this;
            var id = this.headClone.getID();
            var colSelectors = this.colSelectors;
            var thSelectors = this.thSelectors;

            var styles = {};
            styles['#' + id] = {
                'display': this.table.css('display'),
                'width': this.table.outerWidth() + "px"
            }

            this.theadCells.each(function(i, th) {
                var col = widget.columns.eq(i);
                var display = col.css('display');
                styles[colSelectors[i]] = {
                    'display': display
                }
                if ( display !== 'none' ) {
                    var width = $(th)[0].getBoundingClientRect().width + "px";
                    styles[colSelectors[i]]['width'] = width;
                }
                styles[thSelectors[i]] = {
                    'display': $(th).css('display')
                }
            });
            qcode.style(styles);
        },
        repaintStyles: function() {
            // Copy styles from the original table to the copied table,
            // and from the original th elements to the copied ones.
            var id = this.headClone.getID();
            var widget = this;
            var styles = {};
            selector = '#' + id;
            styles[selector] = {};

            $.each(copy_table_css, function(i, name) {
                var originalValue = widget.table.css(name);
                var cloneValue = widget.headClone.css(name);
                if ( cloneValue !== originalValue ) {
                    styles[selector][name] = originalValue;
                }
            });

            this.theadCells.each(function(i, th) {
                var thSelector = widget.thSelectors[i];
                styles[thSelector] = {};
                if ( $(th).css('display') === "table-cell" ) {
                    $.each(copy_th_css, function(j, name) {
                        var copy_th = widget.headClone.find('th:nth-child('+(i+1)+')');
                        if ( copy_th.css(name) !== $(th).css(name) ) {
                            styles[thSelector][name] = $(th).css(name);
                        }
                    });
                } else {
                    styles[thSelector].display = "none";
                }
            });

            this.columns.each(function(i, col) {
                var colSelector = widget.colSelectors[i];
                styles[colSelector] = {};
                $.each(copy_col_css, function(j, name) {
                    var copy_col = widget.headClone.find('col:nth-child('+(i+1)+')');
                    if ( ! attrEqual(copy_col.css(name), $(col).css(name)) ) {
                        styles[colSelector][name] = $(col).css(name);
                    }
                });
            });

            qcode.style(styles);
        },
        getWrapper: function() {
            return this.wrapper;
        }
    });
    function attrEqual(a, b) {
        // Compare two strings, treating null and undefined as ""
        // Returns true if the strings are equal
        if ( a === undefined || a === null ) {
            var a = "";
        }
        if ( b === undefined || b === null ) {
            var b = "";
        }
        return a === b;
    }
    function mutationSummary(mutations) {
        // Return a summary list of changes without intermediate changes.
        var summary = [];
        var done = [];
        mutations.forEach(function(mutation) {
            var target = mutation.target;
            var name = mutation.attributeName;
            var oldValue = mutation.oldValue;
            var newValue = $(target).attr(name);
            var found = done.some(function(member) {
                if ( member.element == target && member.attribute == name ) {
                    return true;
                }
                return false;
            });
            if ( !found ) {
                if ( !attrEqual(newValue, oldValue) ) {
                    summary.push({
                        element: target,
                        attribute: name,
                        oldValue: oldValue,
                        newValue: newValue
                    });
                }
                done.push({
                    element: target,
                    attribute: name
                });
            }
        });
        return summary;
    }
})(jQuery);
<commit_msg>make theadFixed listen for class changes<commit_after>/*
theadFixed plugin

Makes the body + foot of a table scrollable, while a "fixed" copy of the thead.
*/
;(function($, undefined) {
    /* css to copy from original col elements */
    var copy_col_css = [
        'display'
    ];
    /* css to copy from original th elements */
    var copy_th_css = [
        'display', 'position', 'color', 'background-color',
        'font-family', 'font-weight', 'font-size', 'font-style', 'text-align', 'vertical-align',
        'white-space', 'overflow-x',
        'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
        'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
        'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
        'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'
    ];
    /* css to copy from the original table */
    var copy_table_css = [
        'border-top-width', 'border-right-width', 'border-left-width',
        'border-top-style', 'border-right-style', 'border-left-style',
        'border-top-color', 'border-right-color', 'border-left-color',
        'border-collapse', 'border-spacing'
    ];

    // Find the element whose DOM location relative to otherRoot is the same as element's postion relative to root.
    // eg. If element is the 3rd child of the 5th child of root, find the 3rd child of the 5th child of otherRoot
    function treeMap(element, root, otherRoot) {
        if ( element.length == 0 ) {
            return $();
        } else if ( element.is(root) ) {
            return otherRoot;
        } else {
            var mappedParent = treeMap(element.parent(), root, otherRoot);
            return mappedParent.children().eq(element.index());
        }
    }

    $.widget('qcode.theadFixed', {
	options: {
	    'height': "500px",
            'fixedWidth': false,
            'initialScroll': "start"
	},
	_create: function() {
            this.table = this.element;
            this.theadCells = this.table.children('thead').children('tr').first().children('th');
            this.columns = this.table.children('colgroup').children('col');

            // Create the new thead as a separate table
            this.headClone = $('
')
                .append(this.table.children('colgroup').clone())
                .append(this.table.children('thead').clone())
                .addClass('thead-fixed-clone');
            this.headClone.children('tbody, tfoot').remove();
            var id = this.headClone.getID();
            qcode.style('#'+id, 'table-layout', "fixed");

            // Remove "name" attribute from original thead form elements
            this.table.children('thead').find(':input').removeAttr('name');

            // Generate and store column selectors
            var colSelectors = {};
            var thSelectors = {};
            this.theadCells.each(function(i, th) {
                colSelectors[i] = '#'+id+'>colgroup>col:nth-child('+(i+1)+')';
                thSelectors[i] = '#'+id+'>thead>tr>th:nth-child('+(i+1)+')';
            });
            this.colSelectors = colSelectors;
            this.thSelectors = thSelectors;

            // Create the wrappers
            this.table.wrap('
');
            this.scrollBox = this.table.parent().wrap('
');
            this.wrapper = this.scrollBox.parent().css({height: this.options.height});

            // Set the initial scroll position (wait for all other plugins to load)
            if ( this.options.initialScroll === "end" ) {
                var scrollBox = this.scrollBox;
                $('body').one('pluginsReady', function() {
                    var scrollTop = scrollBox[0].scrollHeight - scrollBox.height();
                    scrollBox.scrollTop(scrollTop);
                });
            }

            // Add the resize event listeners - only repaint when the table is resized
            // or the window width changes.
            var windowWidth = window.innerWidth;
            var widget = this;
            // On window resize, or when a resize bubbles to the window.
            this._on($(window), {
                'resize': function(event) {
                    if ( $(event.target).closest(this.element.add(this.headClone)).length > 0 ) {
                        this.repaint();

                    } else if ( windowWidth != window.innerWidth ) {
                        this.repaintStyles();
                        if ( ! this.options.fixedWidth ) {
                            this.repaintWidths();
                            windowWidth = window.innerWidth;
                        }
                    }
                }
            });

            // Copy click events back to the matching element in the original thead
            var handlers = {};
            var copy = function copyEvent(event) {
                var target = $(event.target);
                var eventProperties = {};
                $.each([
                    'pageX',
                    'pageY',
                    'which',
                    'data',
                    'metaKey',
                    'namespace',
                    'timeStamp',
                    'clientX',
                    'clientY'
                ], function(i, property) {
                    eventProperties[property] = event[property];
                });
                $.each(['relatedTarget'], function(i, property) {
                    if ( $(event[property]).closest(this.table).length > 0 ) {
                        eventProperties[property] = treeMap(event[property], this.headClone, this.table);
                    } else {
                        eventProperties[property] = event[property];
                    }
                });
                var originalElement = treeMap(target, this.headClone, this.table);
                var eventCopy = jQuery.Event(event.type, eventProperties);
                originalElement.trigger(eventCopy);
                if ( eventCopy.isDefaultPrevented() ) {
                    event.preventDefault();
                }
            };
            jQuery.each([
                'click',
                'mousedown',
                'mouseup',
                'mouseover',
                'mouseout',
                'mousemove',
                'contextmenu'
            ], function(i, eventName) {
                handlers[eventName] = copy;
            });
            this._on(this.headClone, handlers);

            // Where supported, MutationObserver allows us to listen for changes to the DOM
            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
            if ( MutationObserver ) {
                // When the contents or structure of the table change, repaint the widths.
                this.htmlObserver = new MutationObserver(this.repaintWidths.bind(this));
                this.htmlObserver.observe(
                    this.wrapper[0],
                    {
                        childList: true,
                        characterData: true,
                        subtree: true,
                        attributeFilter: ["class"]
                    }
                );

                // When the class or style of any element in the original thead or colgroup change,
                // or when an element in the original thead becomes disabled,
                // replicate this change to the thead copy.
                // If one or more classes changed, repaint the styles.
                this.styleObserver = new MutationObserver(function(mutations) {
                    var summary = mutationSummary(mutations);
                    var needsRepaint = false;
                    summary.forEach(function(change) {
                        var target = $(change.element);
                        var name = change.attribute;
                        if ( name === "class" ) {
                            needsRepaint = true;
                        }
                        if ( attrEqual(change.newValue, "") ) {
                            treeMap(target, widget.table, widget.headClone).removeAttr(name);
                        }  else {
                            treeMap(target, widget.table, widget.headClone).attr(name, change.newValue);
                        }
                    });
                    if ( needsRepaint ) {
                        widget.repaintStyles();
                    }
                });
                this.styleObserver.observe(
                    this.table.children('thead')[0],
                    {
                        attributes: true,
                        attributeFilter: ['class', 'style', 'disabled'],
                        attributeOldValue: true,
                        subtree: true
                    }
                );
                this.styleObserver.observe(
                    this.table.children('colgroup')[0],
                    {
                        attributes: true,
                        attributeFilter: ['class', 'style'],
                        attributeOldValue: true,
                        subtree: true
                    }
                );
            }

            // Call repaint once to set the widths and styles
            this.repaint();

            // Add the head to the page last, after widths and styles have been calculated
            // (this avoids a google chrome bug)
            this.wrapper.prepend(this.headClone);

            this.zoomFix();
            // end of _create;
	},
	repaint: function() {
            this.repaintStyles();
            this.repaintWidths();
        },
        zoomFix: function() {
            var cloneRow = this.headClone.children('thead').children('tr').first();
            var originalRow = this.table.children('thead').children('tr').first();
            var thSelectors = this.thSelectors;
            for ( var multiplier = 0.99 ; multiplier > 0 ; multiplier -= 0.01 ) {
                if ( cloneRow.height() <= originalRow.height() ) {
                    break
                }
                styles = {};
                this.theadCells.each(function(i, originalTH) {
                    styles[thSelectors[i]] = {
                        'font-size':
                        (
                            parseFloat( $(originalTH).css('font-size') ) * multiplier
                        ) + "px"
                    };
                });
                qcode.style(styles);
            }
        },
        repaintWidths: function() {
            // Measure and apply table and column widths
            var widget = this;
            var id = this.headClone.getID();
            var colSelectors = this.colSelectors;
            var thSelectors = this.thSelectors;

            var styles = {};
            styles['#' + id] = {
                'display': this.table.css('display'),
                'width': this.table.outerWidth() + "px"
            }

            this.theadCells.each(function(i, th) {
                var col = widget.columns.eq(i);
                var display = col.css('display');
                styles[colSelectors[i]] = {
                    'display': display
                }
                if ( display !== 'none' ) {
                    var width = $(th)[0].getBoundingClientRect().width + "px";
                    styles[colSelectors[i]]['width'] = width;
                }
                styles[thSelectors[i]] = {
                    'display': $(th).css('display')
                }
            });
            qcode.style(styles);
        },
        repaintStyles: function() {
            // Copy styles from the original table to the copied table,
            // and from the original th elements to the copied ones.
            var id = this.headClone.getID();
            var widget = this;
            var styles = {};
            selector = '#' + id;
            styles[selector] = {};

            $.each(copy_table_css, function(i, name) {
                var originalValue = widget.table.css(name);
                var cloneValue = widget.headClone.css(name);
                if ( cloneValue !== originalValue ) {
                    styles[selector][name] = originalValue;
                }
            });

            this.theadCells.each(function(i, th) {
                var thSelector = widget.thSelectors[i];
                styles[thSelector] = {};
                if ( $(th).css('display') === "table-cell" ) {
                    $.each(copy_th_css, function(j, name) {
                        var copy_th = widget.headClone.find('th:nth-child('+(i+1)+')');
                        if ( copy_th.css(name) !== $(th).css(name) ) {
                            styles[thSelector][name] = $(th).css(name);
                        }
                    });
                } else {
                    styles[thSelector].display = "none";
                }
            });

            this.columns.each(function(i, col) {
                var colSelector = widget.colSelectors[i];
                styles[colSelector] = {};
                $.each(copy_col_css, function(j, name) {
                    var copy_col = widget.headClone.find('col:nth-child('+(i+1)+')');
                    if ( ! attrEqual(copy_col.css(name), $(col).css(name)) ) {
                        styles[colSelector][name] = $(col).css(name);
                    }
                });
            });

            qcode.style(styles);
        },
        getWrapper: function() {
            return this.wrapper;
        }
    });
    function attrEqual(a, b) {
        // Compare two strings, treating null and undefined as ""
        // Returns true if the strings are equal
        if ( a === undefined || a === null ) {
            var a = "";
        }
        if ( b === undefined || b === null ) {
            var b = "";
        }
        return a === b;
    }
    function mutationSummary(mutations) {
        // Return a summary list of changes without intermediate changes.
        var summary = [];
        var done = [];
        mutations.forEach(function(mutation) {
            var target = mutation.target;
            var name = mutation.attributeName;
            var oldValue = mutation.oldValue;
            var newValue = $(target).attr(name);
            var found = done.some(function(member) {
                if ( member.element == target && member.attribute == name ) {
                    return true;
                }
                return false;
            });
            if ( !found ) {
                if ( !attrEqual(newValue, oldValue) ) {
                    summary.push({
                        element: target,
                        attribute: name,
                        oldValue: oldValue,
                        newValue: newValue
                    });
                }
                done.push({
                    element: target,
                    attribute: name
                });
            }
        });
        return summary;
    }
})(jQuery);
<|endoftext|>


<commit_before>/**
 * @module Environment
 * @submodule Environment
 * @for p5
 * @requires core
 */

import p5 from '../core/main';
let ingredients = {};
let preIngredients = {};
let txtOut = false;
let cnvConfig = {};

p5.prototype.textOutput = function() {
  txtOut = true;
  if (this.canvas !== undefined) {
    this._setDefaults();
    this._createOutput('textOutput');
  } else {
    throw new Error('textOutput() should be called after canvas is created');
  }
};

p5.prototype._addAccsOutput = function() {
  if (txtOut === true) {
    return true;
  } else {
    return false;
  }
};

//helper function that creates div for accessible outputs
p5.prototype._createOutput = function(type) {
  const cnvId = this.canvas.id;
  if (document.getElementById(cnvId + type) === null) {
    document
      .getElementById(cnvId)
      .insertAdjacentHTML(
        'afterend',
        '

'
      );
    this._updateOutput(type);
  }
};

//helper function that updates accessible outputs
p5.prototype._updateOutput = function(type) {
  if (txtOut === false) {
    return;
  }
  if (type === undefined) {
    type = 'textOutput';
  }
  let inner = this._buildOutput();
  const cnvId = this.canvas.id;
  document.getElementById(cnvId + type).innerHTML = inner;
};

//helper function that builds output content
p5.prototype._buildOutput = function() {
  if (ingredients === preIngredients) {
    return;
  }
  let el = 0;
  let elText = '';
  //go through shapes and add them:
  for (let x in ingredients) {
    for (let y in ingredients[x]) {
      el++;
      elText =
        elText +
        '
' +
        ingredients[x][y].color +
        ' ' +
        x +
        ', at ' +
        ingredients[x][y].pos +
        ', covering ' +
        ingredients[x][y].area +
        '% of the canvas.
';
    }
  }
  //create initial phrase
  let text =
    'Your output is a, ' +
    Math.round(this.width) +
    ' by ' +
    Math.round(this.height) +
    ' pixels, ' +
    cnvConfig.background +
    ' canvas. Containing the following ' +
    el +
    ' objects:

' +
    elText +
    '
';
  return text;
};

p5.prototype._accsBackground = function(args) {
  if (txtOut === false) {
    return;
  }
  preIngredients = ingredients;
  ingredients = {};
  if (cnvConfig.backgroundRGBA !== args) {
    cnvConfig.backgroundRGBA = args;
    cnvConfig.background = this._rgbColorName(args);
  }
};

p5.prototype._accscnvConfig = function(f, args) {
  if (txtOut === false) {
    return;
  }
  if (f === 'fill') {
    if (cnvConfig.fill !== args) {
      cnvConfig.fillRGBA = args;
      cnvConfig.fill = this._rgbColorName(args);
      this._updateOutput();
    }
  } else if (f === 'stroke') {
    if (cnvConfig.stroke !== args) {
      cnvConfig.strokeRGBA = args;
      cnvConfig.stroke = this._rgbColorName(args);
      this._updateOutput();
    }
  }
};

p5.prototype._setDefaults = function() {
  cnvConfig.background = 'white';
  cnvConfig.fill = 'white';
};

p5.prototype._accsOutput = function(f, args) {
  if (f === 'ellipse' && args[2] === args[3]) {
    f = 'circle';
  } else if (f === 'rectangle' && args[2] === args[3]) {
    f = 'square';
  }
  let include = {};
  if (f === 'line' || f === 'point') {
    include.color = cnvConfig.stroke;
  } else {
    include.color = cnvConfig.fill;
  }
  include.pos = this._getPos(args);
  include.area = this._getArea(f, args);
  include.args = args;
  if (ingredients[f] === undefined) {
    ingredients[f] = [include];
  } else if (ingredients[f] !== [include]) {
    for (var i = 0; i < ingredients[f].length; i++) {
      if (ingredients[f][i] !== include) {
        ingredients[f].push(include);
      }
    }
  }
  if (ingredients !== preIngredients) {
    this._updateOutput();
  }
};

p5.prototype._getPos = function(args) {
  let x = Math.round(args[0]);
  let y = Math.round(args[1]);

  if (x < 0.4 * this.width) {
    if (y < 0.4 * this.height) {
      return 'top left';
    } else if (yCoord > 0.6 * this.height) {
      return 'bottom left';
    } else {
      return 'mid left';
    }
  } else if (x > 0.6 * this.width) {
    if (y < 0.4 * this.height) {
      return 'top right';
    } else if (y > 0.6 * this.height) {
      return 'bottom right';
    } else {
      return 'mid right';
    }
  } else {
    if (y < 0.4 * this.height) {
      return 'top middle';
    } else if (y > 0.6 * this.height) {
      return 'bottom middle';
    } else {
      return 'middle';
    }
  }
};

p5.prototype._getArea = function(objectType, shapeArgs) {
  let objectArea = 0;
  if (objectType === 'arc') {
    // area of full ellipse = PI * horizontal radius * vertical radius.
    // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius.
    // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2
    const arcSizeInRadians =
      ((shapeArgs[5] - shapeArgs[4]) % (PI * 2) + PI * 2) % (PI * 2);
    objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8;
    if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') {
      // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc
      // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2
      const Ax = shapeArgs[0];
      const Ay = shapeArgs[1];
      const Bx = shapeArgs[0] + shapeArgs[2] / 2 * cos(shapeArgs[4]).toFixed(2);
      const By = shapeArgs[1] + shapeArgs[3] / 2 * sin(shapeArgs[4]).toFixed(2);
      const Cx = shapeArgs[0] + shapeArgs[2] / 2 * cos(shapeArgs[5]).toFixed(2);
      const Cy = shapeArgs[1] + shapeArgs[3] / 2 * sin(shapeArgs[5]).toFixed(2);
      const areaOfExtraTriangle =
        abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2;
      if (arcSizeInRadians > PI) {
        objectArea = objectArea + areaOfExtraTriangle;
      } else {
        objectArea = objectArea - areaOfExtraTriangle;
      }
    }
  } else if (objectType === 'ellipse' || objectType === 'circle') {
    objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2;
  } else if (objectType === 'line') {
    objectArea = 0;
  } else if (objectType === 'point') {
    objectArea = 0;
  } else if (objectType === 'quadrilateral') {
    // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2
    objectArea =
      abs(
        (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) +
          (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) +
          (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) +
          (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7])
      ) / 2;
  } else if (objectType === 'rectangle' || objectType === 'square') {
    objectArea = shapeArgs[2] * shapeArgs[3];
  } else if (objectType === 'triangle') {
    objectArea =
      abs(
        shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) +
          shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) +
          shapeArgs[4] * (shapeArgs[1] - shapeArgs[3])
      ) / 2;
    // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2
  }

  let percentage = Math.round(objectArea * 100 / (this.width * this.height));
  return percentage;
};

export default p5;
<commit_msg>add _getLineLenght<commit_after>/**
 * @module Environment
 * @submodule Environment
 * @for p5
 * @requires core
 */

import p5 from '../core/main';
let ingredients = {};
let preIngredients = {};
let txtOut = false;
let cnvConfig = {};

p5.prototype.textOutput = function() {
  txtOut = true;
  if (this.canvas !== undefined) {
    this._setDefaults();
    this._createOutput('textOutput');
  } else {
    throw new Error('textOutput() should be called after canvas is created');
  }
};

p5.prototype._addAccsOutput = function() {
  if (txtOut === true) {
    return true;
  } else {
    return false;
  }
};

//helper function that creates div for accessible outputs
p5.prototype._createOutput = function(type) {
  const cnvId = this.canvas.id;
  if (document.getElementById(cnvId + type) === null) {
    document
      .getElementById(cnvId)
      .insertAdjacentHTML(
        'afterend',
        '

'
      );
    this._updateOutput(type);
  }
};

//helper function that updates accessible outputs
p5.prototype._updateOutput = function(type) {
  if (txtOut === false) {
    return;
  }
  if (type === undefined) {
    type = 'textOutput';
  }
  let inner = this._buildOutput();
  const cnvId = this.canvas.id;
  document.getElementById(cnvId + type).innerHTML = inner;
};

//helper function that builds output content
p5.prototype._buildOutput = function() {
  if (ingredients === preIngredients) {
    return;
  }
  let el = 0;
  let elText = '';
  //go through shapes and add them:
  for (let x in ingredients) {
    for (let y in ingredients[x]) {
      el++;
      elText =
        elText +
        '
' +
        ingredients[x][y].color +
        ' ' +
        x +
        ', at ' +
        ingredients[x][y].pos +
        ', covering ' +
        ingredients[x][y].area +
        '% of the canvas.
';
    }
  }
  //create initial phrase
  let text =
    'Your output is a, ' +
    Math.round(this.width) +
    ' by ' +
    Math.round(this.height) +
    ' pixels, ' +
    cnvConfig.background +
    ' canvas. Containing the following ' +
    el +
    ' objects:

' +
    elText +
    '
';
  return text;
};

p5.prototype._accsBackground = function(args) {
  if (txtOut === false) {
    return;
  }
  preIngredients = ingredients;
  ingredients = {};
  if (cnvConfig.backgroundRGBA !== args) {
    cnvConfig.backgroundRGBA = args;
    cnvConfig.background = this._rgbColorName(args);
  }
};

p5.prototype._accscnvConfig = function(f, args) {
  if (txtOut === false) {
    return;
  }
  if (f === 'fill') {
    if (cnvConfig.fill !== args) {
      cnvConfig.fillRGBA = args;
      cnvConfig.fill = this._rgbColorName(args);
      this._updateOutput();
    }
  } else if (f === 'stroke') {
    if (cnvConfig.stroke !== args) {
      cnvConfig.strokeRGBA = args;
      cnvConfig.stroke = this._rgbColorName(args);
      this._updateOutput();
    }
  }
};

p5.prototype._setDefaults = function() {
  cnvConfig.background = 'white';
  cnvConfig.fill = 'white';
};

p5.prototype._accsOutput = function(f, args) {
  if (f === 'ellipse' && args[2] === args[3]) {
    f = 'circle';
  } else if (f === 'rectangle' && args[2] === args[3]) {
    f = 'square';
  }
  let include = {};
  if (f === 'line') {
    include.color = cnvConfig.stroke;
    include.length = _getLineLength(args);
    let p1 = this._getPos([args[0], [1]]);
    let p2 = this._getPos([args[2], [3]]);
    if (p1 === p2) {
      include.pos = 'at ' + p1;
    } else {
      include.pos = 'from ' + p1 + ' to ' + p2;
    }
  } else {
    if (f === 'point') {
      include.color = cnvConfig.stroke;
    } else {
      include.color = cnvConfig.fill;
      include.area = this._getArea(f, args);
    }
    include.pos = this._getPos(args);
  }
  include.args = args;
  if (ingredients[f] === undefined) {
    ingredients[f] = [include];
  } else if (ingredients[f] !== [include]) {
    for (var i = 0; i < ingredients[f].length; i++) {
      if (ingredients[f][i] !== include) {
        ingredients[f].push(include);
      }
    }
  }
  if (ingredients !== preIngredients) {
    this._updateOutput();
  }
};

p5.prototype._getPos = function(args) {
  let x = Math.round(args[0]);
  let y = Math.round(args[1]);

  if (x < 0.4 * this.width) {
    if (y < 0.4 * this.height) {
      return 'top left';
    } else if (yCoord > 0.6 * this.height) {
      return 'bottom left';
    } else {
      return 'mid left';
    }
  } else if (x > 0.6 * this.width) {
    if (y < 0.4 * this.height) {
      return 'top right';
    } else if (y > 0.6 * this.height) {
      return 'bottom right';
    } else {
      return 'mid right';
    }
  } else {
    if (y < 0.4 * this.height) {
      return 'top middle';
    } else if (y > 0.6 * this.height) {
      return 'bottom middle';
    } else {
      return 'middle';
    }
  }
};

// return length of lines
p5.prototype._getLineLength = function(args) {
  const lineLength = Math.round(
    Math.sqrt(Math.pow(args[2] - args[0], 2) + Math.pow(args[3] - args[1], 2))
  );
  return lineLength;
};

p5.prototype._getArea = function(objectType, shapeArgs) {
  let objectArea = 0;
  if (objectType === 'arc') {
    // area of full ellipse = PI * horizontal radius * vertical radius.
    // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius.
    // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2
    const arcSizeInRadians =
      ((shapeArgs[5] - shapeArgs[4]) % (PI * 2) + PI * 2) % (PI * 2);
    objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8;
    if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') {
      // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc
      // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2
      const Ax = shapeArgs[0];
      const Ay = shapeArgs[1];
      const Bx = shapeArgs[0] + shapeArgs[2] / 2 * cos(shapeArgs[4]).toFixed(2);
      const By = shapeArgs[1] + shapeArgs[3] / 2 * sin(shapeArgs[4]).toFixed(2);
      const Cx = shapeArgs[0] + shapeArgs[2] / 2 * cos(shapeArgs[5]).toFixed(2);
      const Cy = shapeArgs[1] + shapeArgs[3] / 2 * sin(shapeArgs[5]).toFixed(2);
      const areaOfExtraTriangle =
        abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2;
      if (arcSizeInRadians > PI) {
        objectArea = objectArea + areaOfExtraTriangle;
      } else {
        objectArea = objectArea - areaOfExtraTriangle;
      }
    }
  } else if (objectType === 'ellipse' || objectType === 'circle') {
    objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2;
  } else if (objectType === 'line') {
    objectArea = 0;
  } else if (objectType === 'point') {
    objectArea = 0;
  } else if (objectType === 'quadrilateral') {
    // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2
    objectArea =
      abs(
        (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) +
          (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) +
          (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) +
          (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7])
      ) / 2;
  } else if (objectType === 'rectangle' || objectType === 'square') {
    objectArea = shapeArgs[2] * shapeArgs[3];
  } else if (objectType === 'triangle') {
    objectArea =
      abs(
        shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) +
          shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) +
          shapeArgs[4] * (shapeArgs[1] - shapeArgs[3])
      ) / 2;
    // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2
  }

  let percentage = Math.round(objectArea * 100 / (this.width * this.height));
  return percentage;
};

export default p5;
<|endoftext|>