<commit_before>/**
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * Common code to work OS or Disk image that implements vm disk * and vm image* commands
 */

var path = require('path');
var utils = require('../utils');
var blobUtils = require('../blobUtils');
var deleteImage = require('./deleteImage');
var uploadVMImage = require('./upload/uploadVMImage');

var DISK = exports.DISK = 0;
var OSIMAGE = exports.OSIMAGE = 1;

var whatAPI = ['Disk', 'OSImage'];
var whatLog = ['disk image', 'VM image'];
var whatLogs = ['disk images', 'VM images'];

exports.show = function(what, cli) {
  return function(name, options, callback) {
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var progress = cli.progress('Fetching ' + whatLog[what]);
    utils.doServiceManagementOperation(channel, 'get' + whatAPI[what], name, function(error, response) {
      progress.end();
      if (!error) {
        if (logger.format().json) {
          logger.json(response.body);
        } else {
          delete response.body['@']; // skip @ xmlns and @ xmlns:i
          utils.logLineFormat(response.body, logger.data);
        }
      }
      callback(error);
    });
  };
};

exports.list = function(what, cli) {
  function list(name, options, callback) {
    if (what === DISK) {
      if (name) {
        // list disks for a specific VM
        listDisks(cli, {
          subscription: options.subscription,
          name: name.toLowerCase(),
          dnsPrefix: options.dnsPrefix
        }, callback);
        return;
      }
    }
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var progress = cli.progress('Fetching ' + whatLogs[what]);
    utils.doServiceManagementOperation(channel, ['listDisks', 'listOSImage'][what], function(error, response) {
      progress.end();
      if (!error) {
        if (response.body.length > 0) {
          logger.table(response.body, function (row, item) {
            row.cell('Name', item.Name);
            if (what === DISK) {
              row.cell('OS', item.OS || '');
            } else { // OSIMAGE
              if (item.Category) {
                row.cell('Category', item.Category);
              }
              row.cell('OS', item.OS);
            }
          });
        } else {
          if (logger.format().json) {
            logger.json([]);
          } else {
            logger.info('No ' + whatLogs[what] + ' found');
          }
        }
      }
      callback(error);
    });
  }
  // return 2 or 3-arg version of the function
  return what === DISK ? list : function(options, callback) {return list(undefined, options, callback);};
};

exports.imageDelete = function(what, cli) {
  return function(name, options, callback) {
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    deleteImage.deleteImage(whatAPI[what], ['Disk', 'VM image'][what], logger, channel, name, null, cli.progress, options.blobDelete, callback);
  };
};

exports.create = function(what, cli) {
  return function(name, sourcePath, options, callback) {
    var logger = cli.output;
    var os = (undefined);
    if (typeof options.os === 'string') {
      var los = options.os.trim().toLowerCase();
      os = los[0].toUpperCase() + los.slice(1); // start with capital letter
    }

    if (what === DISK) {
      if (os && os !== 'Windows' && os !== 'Linux' && os !== 'None') {
        callback('--os [type] must specify linux, windows or none');
      }
      // @"^[a-zA-Z_][^\\\/\:\*\?\""\<\>\|\`\'\^%\#]*(?<![\.\s])$" in C# syntax
      if (!name.match(/^[a-zA-Z_][^\\\/\:\*\?\"\<\>\|\`\'\^%\#]*$/) || name.slice(-1).match(/[\.\s]/)) {
        callback('Invalid image name. Disk image name should start with a Latin letter or underscore (_), cannot contain any of the following characters:\n\\/:*?\"<>|`\'%#^\nIt cannot end with a period (.) or space character.');
      }
    } else {
      if (os !== 'Windows' && os !== 'Linux') {
        callback('--os <type> must specify linux or windows');
      }
      // @"^[A-Za-z0-9\-\.]{1,512}(?<!\-)$" in C# syntax
      if (!name.match(/^[A-Za-z0-9\-\.]{0,511}[A-Za-z0-9\.]$/)) {
        callback('Invalid image name. OS image name can only contain Latin letters, digits, \'.\' and \'-\'. It cannot end with \'-\' or be longer than 512 chars.');
      }
    }

    var genBlobUrl = '';
    var force = options.forceOverwrite;
    var blobUrl = options.blobUrl;
    var location = options.location;
    var affinityGroup = options.affinityGroup;

    if (sourcePath) {
      if (!blobUrl && !location && !affinityGroup) {
        logger.error('--blob-url, --location, or --affinity-group must be specified');
        logger.help('following commands show available locations and affinity groups:');
        logger.help('    azure account location list');
        logger.help('    azure account affinity-group list');
        callback(' ');
      }
    } else {
      // When source-path is not specified, the user is attempting to register an
      // already uploaded disk or OS blob.  In that case we need the blob-url.
      if (!blobUrl) {
        callback('--blob-url must be specified');
      }
    }

    if (blobUrl) {
      if (blobUrl[0] === '/') {
        // With partial urls, we need to know location/affinity group of the storage account.
        if (!location && !affinityGroup) {
          logger.error('location or affinity group is required if no full URL is specified');
          logger.help('following commands show available locations and affinity groups:');
          logger.help('    azure account location list');
          logger.help('    azure account affinity-group list');
          callback('--location, or --affinity-group must be specified');
        }
      } else {
        if (location) {
          logger.warn('--location option will be ignored');
        }

        if (affinityGroup) {
          logger.warn('--affinity-group option will be ignored');
        }
      }
    }

    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var options2 = {
      imageOptions : {
        Label : options.label
      },
      verbose : cli.verbose ||
        logger.format().level === 'verbose' ||
        logger.format().level === 'silly',
      skipMd5 : options.md5Skip,
      force : force,
      vhd : true,
      threads : options.parallel,
      parentBlob : options.baseVhd,
      exitWithError : callback,
      logger : logger
    };

    if (what === DISK && options.os) {
      var hasOS = os !== 'None';
      options2.imageOptions.HasOperatingSystem = hasOS;
      if (hasOS && os) {
        options2.imageOptions.OS = os;
      }
    }

    logger.silly('Options: ', options2.imageOptions);

    if (location) {
      logger.verbose('Resolving the location \'' + location + '\'');
      utils.resolveLocationName(channel, location, function(error, resolvedName) {
        if(!error) {
          location = resolvedName;
          logger.verbose('Location resolved to \'' + location + '\'');
          getBlobNameAndUpload();
        } else {
          callback(error);
        }
      });
    } else {
      getBlobNameAndUpload();
    }

    function getBlobNameAndUpload() {
      blobUtils.getBlobName(cli, channel, location, affinityGroup,
          path.basename(sourcePath), blobUrl, ['/disks/', '/vm-images/'][what],
          sourcePath, function(error, url) {
        if (error) {
          logger.error('Unable to retrieve storage account.');
          callback(error);
        } else {
          genBlobUrl = url;
          logger.verbose('Blob url: ' + genBlobUrl);
          upload();
        }
      });
    }

    function upload() {
      if (sourcePath) { // uploading
        uploadVMImage.uploadPageBlobFromIaasClient(genBlobUrl, channel, sourcePath,
            options2, function (error, finalBlobUrl, alreadyExisted) {
          if (error && !error.isSuccessful) {
            // do not delete incomplete blob
            logger.error('Couldn\'t upload blob ' + genBlobUrl);
            callback(error);
          }
          if (!error) { // final callback
            logger.info(finalBlobUrl + (alreadyExisted ? ' is already uploaded' : ' is uploaded successfully'));
            createImage(finalBlobUrl);
          }
        });
      } else {
        // not uploading
        createImage();
      }
    }

    function createImage(finalBlobUrl) {
      finalBlobUrl = finalBlobUrl || genBlobUrl;
      var normUrl = blobUtils.normalizeBlobUri(finalBlobUrl, true);
      if (normUrl !== genBlobUrl) {
        logger.verbose('Creating OS image from ' + normUrl);
      }

      if (what === DISK) {
        utils.doServiceManagementOperation(channel, 'addDisk', name,
            normUrl,
            options2.imageOptions, function(error, response) {
          callback(error);
        });
      } else {
        utils.doServiceManagementOperation(channel, 'createOSImage', os, name,
            normUrl,
            options2.imageOptions, function(error, response) {
          callback(error);
        });
      }
    }
  };
};

function listDisks(cli, options, cmdCallback) {
  var logger = cli.output;
  var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
    cli.category('account'), logger);

  var progress = cli.progress('Fetching VM');

  utils.enumDeployments(channel, options, function() {
    if (options.rsps.length === 0 && options.errs.length > 0) {
      cmdCallback(options.errs[0]);
    } else if (options.rsps.length > 0) {
      var found = null;
      var foundDisks = null;

      for (var i = 0; i < options.rsps.length; i++) {
        var roles = options.rsps[i].deploy.RoleList;
        if (roles) {
          for (var j = 0; j < roles.length; j++) {
            if (roles[j].RoleType === 'PersistentVMRole' &&
                roles[j].RoleName.toLowerCase() === options.name) {
              if (found) {
                // found duplicates
                progress.end();
                cmdCallback('VM name is not unique');
              }
              found = options.rsps[i];
              foundDisks = [roles[j].OSVirtualHardDisk].concat(roles[j].DataVirtualHardDisks);
            }
          }
        }
      }

      // got unique role under a deployment and service, list the endpoints
      if (found) {
        var osDiskName = foundDisks[0].DiskName;
        logger.verbose('Getting info for OS disk ' + osDiskName);
        utils.doServiceManagementOperation(channel, 'getDisk', osDiskName, function(error, response) {
          progress.end();
          foundDisks[0].LogicalDiskSizeInGB =
            error ? 'Error' : response.body.LogicalDiskSizeInGB;
          logger.table(foundDisks, function (row, item) {
            row.cell('Lun', (item === foundDisks[0]) ? '' : item.Lun || 0);
            row.cell('Size(GB)', item.LogicalDiskSizeInGB);
            var mediaLink = item.MediaLink.split('/');
            row.cell('Blob-Name', mediaLink[mediaLink.length - 1]);
          });
          cmdCallback(error);
        });
      } else {
        cmdCallback('No VMs found');
      }
    }
  });
}

<commit_msg>fix help info about vm location list<commit_after>/**
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
 * Common code to work OS or Disk image that implements vm disk * and vm image* commands
 */

var path = require('path');
var utils = require('../utils');
var blobUtils = require('../blobUtils');
var deleteImage = require('./deleteImage');
var uploadVMImage = require('./upload/uploadVMImage');

var DISK = exports.DISK = 0;
var OSIMAGE = exports.OSIMAGE = 1;

var whatAPI = ['Disk', 'OSImage'];
var whatLog = ['disk image', 'VM image'];
var whatLogs = ['disk images', 'VM images'];

exports.show = function(what, cli) {
  return function(name, options, callback) {
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var progress = cli.progress('Fetching ' + whatLog[what]);
    utils.doServiceManagementOperation(channel, 'get' + whatAPI[what], name, function(error, response) {
      progress.end();
      if (!error) {
        if (logger.format().json) {
          logger.json(response.body);
        } else {
          delete response.body['@']; // skip @ xmlns and @ xmlns:i
          utils.logLineFormat(response.body, logger.data);
        }
      }
      callback(error);
    });
  };
};

exports.list = function(what, cli) {
  function list(name, options, callback) {
    if (what === DISK) {
      if (name) {
        // list disks for a specific VM
        listDisks(cli, {
          subscription: options.subscription,
          name: name.toLowerCase(),
          dnsPrefix: options.dnsPrefix
        }, callback);
        return;
      }
    }
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var progress = cli.progress('Fetching ' + whatLogs[what]);
    utils.doServiceManagementOperation(channel, ['listDisks', 'listOSImage'][what], function(error, response) {
      progress.end();
      if (!error) {
        if (response.body.length > 0) {
          logger.table(response.body, function (row, item) {
            row.cell('Name', item.Name);
            if (what === DISK) {
              row.cell('OS', item.OS || '');
            } else { // OSIMAGE
              if (item.Category) {
                row.cell('Category', item.Category);
              }
              row.cell('OS', item.OS);
            }
          });
        } else {
          if (logger.format().json) {
            logger.json([]);
          } else {
            logger.info('No ' + whatLogs[what] + ' found');
          }
        }
      }
      callback(error);
    });
  }
  // return 2 or 3-arg version of the function
  return what === DISK ? list : function(options, callback) {return list(undefined, options, callback);};
};

exports.imageDelete = function(what, cli) {
  return function(name, options, callback) {
    var logger = cli.output;
    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    deleteImage.deleteImage(whatAPI[what], ['Disk', 'VM image'][what], logger, channel, name, null, cli.progress, options.blobDelete, callback);
  };
};

exports.create = function(what, cli) {
  return function(name, sourcePath, options, callback) {
    var logger = cli.output;
    var os = (undefined);
    if (typeof options.os === 'string') {
      var los = options.os.trim().toLowerCase();
      os = los[0].toUpperCase() + los.slice(1); // start with capital letter
    }

    if (what === DISK) {
      if (os && os !== 'Windows' && os !== 'Linux' && os !== 'None') {
        callback('--os [type] must specify linux, windows or none');
      }
      // @"^[a-zA-Z_][^\\\/\:\*\?\""\<\>\|\`\'\^%\#]*(?<![\.\s])$" in C# syntax
      if (!name.match(/^[a-zA-Z_][^\\\/\:\*\?\"\<\>\|\`\'\^%\#]*$/) || name.slice(-1).match(/[\.\s]/)) {
        callback('Invalid image name. Disk image name should start with a Latin letter or underscore (_), cannot contain any of the following characters:\n\\/:*?\"<>|`\'%#^\nIt cannot end with a period (.) or space character.');
      }
    } else {
      if (os !== 'Windows' && os !== 'Linux') {
        callback('--os <type> must specify linux or windows');
      }
      // @"^[A-Za-z0-9\-\.]{1,512}(?<!\-)$" in C# syntax
      if (!name.match(/^[A-Za-z0-9\-\.]{0,511}[A-Za-z0-9\.]$/)) {
        callback('Invalid image name. OS image name can only contain Latin letters, digits, \'.\' and \'-\'. It cannot end with \'-\' or be longer than 512 chars.');
      }
    }

    var genBlobUrl = '';
    var force = options.forceOverwrite;
    var blobUrl = options.blobUrl;
    var location = options.location;
    var affinityGroup = options.affinityGroup;

    if (sourcePath) {
      if (!blobUrl && !location && !affinityGroup) {
        logger.error('--blob-url, --location, or --affinity-group must be specified');
        logger.help('following commands show available locations and affinity groups:');
        logger.help('    azure vm location list');
        logger.help('    azure account affinity-group list');
        callback(' ');
      }
    } else {
      // When source-path is not specified, the user is attempting to register an
      // already uploaded disk or OS blob.  In that case we need the blob-url.
      if (!blobUrl) {
        callback('--blob-url must be specified');
      }
    }

    if (blobUrl) {
      if (blobUrl[0] === '/') {
        // With partial urls, we need to know location/affinity group of the storage account.
        if (!location && !affinityGroup) {
          logger.error('location or affinity group is required if no full URL is specified');
          logger.help('following commands show available locations and affinity groups:');
          logger.help('    azure vm location list');
          logger.help('    azure account affinity-group list');
          callback('--location, or --affinity-group must be specified');
        }
      } else {
        if (location) {
          logger.warn('--location option will be ignored');
        }

        if (affinityGroup) {
          logger.warn('--affinity-group option will be ignored');
        }
      }
    }

    var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
      cli.category('account'), logger);

    var options2 = {
      imageOptions : {
        Label : options.label
      },
      verbose : cli.verbose ||
        logger.format().level === 'verbose' ||
        logger.format().level === 'silly',
      skipMd5 : options.md5Skip,
      force : force,
      vhd : true,
      threads : options.parallel,
      parentBlob : options.baseVhd,
      exitWithError : callback,
      logger : logger
    };

    if (what === DISK && options.os) {
      var hasOS = os !== 'None';
      options2.imageOptions.HasOperatingSystem = hasOS;
      if (hasOS && os) {
        options2.imageOptions.OS = os;
      }
    }

    logger.silly('Options: ', options2.imageOptions);

    if (location) {
      logger.verbose('Resolving the location \'' + location + '\'');
      utils.resolveLocationName(channel, location, function(error, resolvedName) {
        if(!error) {
          location = resolvedName;
          logger.verbose('Location resolved to \'' + location + '\'');
          getBlobNameAndUpload();
        } else {
          callback(error);
        }
      });
    } else {
      getBlobNameAndUpload();
    }

    function getBlobNameAndUpload() {
      blobUtils.getBlobName(cli, channel, location, affinityGroup,
          path.basename(sourcePath), blobUrl, ['/disks/', '/vm-images/'][what],
          sourcePath, function(error, url) {
        if (error) {
          logger.error('Unable to retrieve storage account.');
          callback(error);
        } else {
          genBlobUrl = url;
          logger.verbose('Blob url: ' + genBlobUrl);
          upload();
        }
      });
    }

    function upload() {
      if (sourcePath) { // uploading
        uploadVMImage.uploadPageBlobFromIaasClient(genBlobUrl, channel, sourcePath,
            options2, function (error, finalBlobUrl, alreadyExisted) {
          if (error && !error.isSuccessful) {
            // do not delete incomplete blob
            logger.error('Couldn\'t upload blob ' + genBlobUrl);
            callback(error);
          }
          if (!error) { // final callback
            logger.info(finalBlobUrl + (alreadyExisted ? ' is already uploaded' : ' is uploaded successfully'));
            createImage(finalBlobUrl);
          }
        });
      } else {
        // not uploading
        createImage();
      }
    }

    function createImage(finalBlobUrl) {
      finalBlobUrl = finalBlobUrl || genBlobUrl;
      var normUrl = blobUtils.normalizeBlobUri(finalBlobUrl, true);
      if (normUrl !== genBlobUrl) {
        logger.verbose('Creating OS image from ' + normUrl);
      }

      if (what === DISK) {
        utils.doServiceManagementOperation(channel, 'addDisk', name,
            normUrl,
            options2.imageOptions, function(error, response) {
          callback(error);
        });
      } else {
        utils.doServiceManagementOperation(channel, 'createOSImage', os, name,
            normUrl,
            options2.imageOptions, function(error, response) {
          callback(error);
        });
      }
    }
  };
};

function listDisks(cli, options, cmdCallback) {
  var logger = cli.output;
  var channel = utils.createServiceManagementService(cli.category('account').lookupSubscriptionId(options.subscription),
    cli.category('account'), logger);

  var progress = cli.progress('Fetching VM');

  utils.enumDeployments(channel, options, function() {
    if (options.rsps.length === 0 && options.errs.length > 0) {
      cmdCallback(options.errs[0]);
    } else if (options.rsps.length > 0) {
      var found = null;
      var foundDisks = null;

      for (var i = 0; i < options.rsps.length; i++) {
        var roles = options.rsps[i].deploy.RoleList;
        if (roles) {
          for (var j = 0; j < roles.length; j++) {
            if (roles[j].RoleType === 'PersistentVMRole' &&
                roles[j].RoleName.toLowerCase() === options.name) {
              if (found) {
                // found duplicates
                progress.end();
                cmdCallback('VM name is not unique');
              }
              found = options.rsps[i];
              foundDisks = [roles[j].OSVirtualHardDisk].concat(roles[j].DataVirtualHardDisks);
            }
          }
        }
      }

      // got unique role under a deployment and service, list the endpoints
      if (found) {
        var osDiskName = foundDisks[0].DiskName;
        logger.verbose('Getting info for OS disk ' + osDiskName);
        utils.doServiceManagementOperation(channel, 'getDisk', osDiskName, function(error, response) {
          progress.end();
          foundDisks[0].LogicalDiskSizeInGB =
            error ? 'Error' : response.body.LogicalDiskSizeInGB;
          logger.table(foundDisks, function (row, item) {
            row.cell('Lun', (item === foundDisks[0]) ? '' : item.Lun || 0);
            row.cell('Size(GB)', item.LogicalDiskSizeInGB);
            var mediaLink = item.MediaLink.split('/');
            row.cell('Blob-Name', mediaLink[mediaLink.length - 1]);
          });
          cmdCallback(error);
        });
      } else {
        cmdCallback('No VMs found');
      }
    }
  });
}

<|endoftext|>


<commit_before>/* Network.js
KC3改 Network Listener

Must only be imported on devtools pages!
Listens to network history and triggers callback if game events happen
*/
(function(){
	"use strict";

	window.KC3Network = {
		hasOverlay: false,
		lastUrl: "",
		eventTypes : {
			GameStart: [],
			CatBomb: [],
			APIError: [],
			Bomb201: [],
			ModalBox: [],
			GameUpdate: [],
			DebuffNotify: [],
			HomeScreen: [],
			HQ: [],
			Consumables: [],
			ShipSlots: [],
			GearSlots: [],
			Timers: [],
			Quests: [],
			Fleet: [],
			Lbas: [],
			SortieStart: [],
			CompassResult: [],
			LandBaseAirRaid: [],
			BattleStart: [],
			BattleNight: [],
			BattleResult: [],
			CraftGear: [],
			CraftShip: [],
			Modernize: [],
			ClearedMap: [],
			PvPList: [],
			PvPFleet: [],
			PvPStart: [],
			PvPNight: [],
			PvPEnd: [],
			ExpeditionSelection: [],
			ExpeditionStart: [],
			ExpedResult: [],
			GunFit: [],
			GearRemodelList: [],
			GearRemodelDetail: [],
			GearRemodel: [],
		},
		delayedUpdate: {},
		deferredEvents: [],
		battleEvent: {
			entered: false,
			time: undefined,
			identifier: undefined,
		},
		submissionModuleNames: ["PoiDBSubmission", "OpenDBSubmission", "TsunDBSubmission"],
		submissionConfigs: {},

		/* ADD LISTENER
		All callback to an event
		------------------------------------------*/
		addListener : function( eventName, callback ){
			this.eventTypes[eventName].push(callback);
		},

		/* ADD GLOBAL LISTENER
		All callback to all events
		------------------------------------------*/
		addGlobalListener : function( callback ){
			$.each(this.eventTypes, function( eventType, eventCallbacks ){
				eventCallbacks.push( callback );
			});
		},

		/* TRIGGER
		Execute subscribed callbacks to an event
		------------------------------------------*/
		trigger : function( eventName, data ){
			if((this.delayedUpdate[eventName] || 0) <= 0) {
				$.each(this.eventTypes[eventName], function( index, callback ){
					callback( eventName, data || {});
				});
				this.delayedUpdate[eventName] = 0;
			} else {
				this.delayedUpdate[eventName] -= 1;
				console.log(`Prevented to call [${eventName}], delay ${this.delayedUpdate[eventName]} left`);
			}
		},

		/* DEFER TRIGGER
		Defers event performing after specified amount of API calls
		------------------------------------------*/
		deferTrigger : function(count, eventName, data){
			// Only allow `after` API call advice for now,
			// `before` and `around` seem not useful yet.
			const advice = "after";
			// +1 if not before since it will be handled at once after current API call
			const amount = count + (advice !== "before" ? 1 : 0);
			this.deferredEvents.push({
				advice: advice,
				amount: amount,
				name: eventName,
				data: data
			});
			//console.log(`Deferred to call [${eventName}] after ${count} time(s)`);
		},

		/* DELAY
		Prevents event performing
		------------------------------------------*/
		delay : function(){
			var self = this;
			var amount = Array.prototype.shift.apply(arguments);
			$.each(arguments, function( i,eventName ){
				self.delayedUpdate[eventName] = amount;
			});
		},

		/* LISTEN
		Start listening and define callback
		------------------------------------------*/
		listen :function(){
			// Call Chrome API to start listening
			chrome.devtools.network.onRequestFinished.addListener(this.received);
		},

		/* INIT CONFIGS
		Store and listen to the changes of used config values
		------------------------------------------*/
		initConfigs :function(){
			ConfigManager.loadIfNecessary();
			// Initial config values
			const configSuffix = "_enabled";
			this.submissionModuleNames.forEach(name => {
				this.submissionConfigs[name] = ConfigManager[name + configSuffix];
			});
			const configChangedListener = ({key, timeStamp, url}) => {
				if(key === ConfigManager.keyName()) {
					const newConfigs = localStorage.getObject(ConfigManager.keyName());
					this.submissionModuleNames.forEach(name => {
						if(this.submissionConfigs[name] !== newConfigs[name + configSuffix]) {
							const isToCleanup = !this.submissionConfigs[name] && !!newConfigs[name + configSuffix];
							this.submissionConfigs[name] = newConfigs[name + configSuffix];
							// Clean previous states if config is changed from disabled to enabled,
							// because it is buggy especially on config changed during sortie.
							const submission = window[name],
								cleanMethod = submission && submission.cleanup,
								isAbleToCleanup = isToCleanup && typeof cleanMethod === "function";
							console.log(`${name} enabled changed to ${this.submissionConfigs[name]}${isAbleToCleanup ? ", cleaning previous states..." : ""}`);
							if(isAbleToCleanup) {
								try {
									cleanMethod.call(submission);
								} catch (error) {
									console.warn("Uncaught states cleanup", error);
								}
							}
						}
					});
				}
			};
			// Do not try to call this multiple times, otherwise this is needed:
			//window.removeEventListener("storage", configChangeListener);
			// Listen to the changes of local storage configs
			window.addEventListener("storage", configChangedListener);
		},

		/* RECEIVED
		Fired when we receive network entry
		Inside, use "KC3Network" instead of "this"
		It's a callback so "this" is in the context of the chrome listener
		Argument HAR see: https://developer.chrome.com/extensions/devtools_network#type-Request
		------------------------------------------*/
		received : function(har){
			const requestUrl = har.request.url;
			// If request is an API Call
			if(requestUrl.indexOf("/kcsapi/") > -1){
				KC3Network.lastUrl = requestUrl;

				// Clear overlays before processing this new API call
				KC3Network.clearOverlays();

				// Create new request and process it
				// console.debug(request, request.request);
				var thisRequest = new KC3Request(har),
					message = {
						tabId: chrome.devtools.inspectedWindow.tabId,
						message_id: "goodResponses",
						tcp_status: har.response.status,
						api_status: undefined,
						api_result: "他のリクエストが成功！",
					};
				if(thisRequest.validateHeaders()){
					thisRequest.readResponse(har, function(){
						if(thisRequest.validateData()){
							// Invoke remote DB submission modules
							KC3Network.submissionModuleNames.forEach(name => {
								if(KC3Network.submissionConfigs[name]) {
									// Assume module already loaded globally
									KC3Network.asyncSubmit(window[name], thisRequest, name);
								}
							});

							// Trigger deferred events before this API call if there are some
							//KC3Network.handleDeferredEvents(thisRequest, "before");
							// Reset battle event before any valided API call
							KC3Network.setBattleEvent(false);

							thisRequest.process();

							// Trigger deferred events after this API call if there are some
							KC3Network.handleDeferredEvents(thisRequest, "after");
						}
					});
					har.getContent(function(x){
						try {
							var data = JSON.parse(/svdata=(.+)$/.exec(x)[1]);
							message.api_status = data.api_result;
							message.api_result = data.api_result_msg;
						} catch (e) {
							// Only prevent the data parsing error
							message.api_status = e.name;
							message.api_result = e.message;
							console.error("Prevented " + e.name, e.message, e);
						} finally {
							(new RMsg("service", "gameScreenChg", message)).execute();
						}
					});
				} else {
					message.api_status = false;
					message.api_result = har.response.statusText;
					(new RMsg("service", "gameScreenChg", message)).execute();
				}
			}

			// If request is a furniture asset
			if(requestUrl.includes("/img/interior/interior_parts")){
				// Clear overlays upon entering furniture menu,
				// not work everytime since Phase 2 caches assets
				KC3Network.clearOverlays();
			}
			// If request is a sound effect of closing shutter animation on battle ended,
			// to activiate and focus on game tab before battle result API call,
			// it only works if in-game SE is not completely off.
			if(ConfigManager.focus_game_tab && KC3Network.battleEvent.entered
				// Only after: daytime? day / night to day? night start?
				// seems no side effect if game tab already focused, so use any time for now
				//&& KC3Network.battleEvent.time === "day"
				&& requestUrl.includes("/resources/se/217.")){
				(new RMsg("service", "focusGameTab", {
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				// console.debug("Battle end SE detected, focus on game tab requested");
			}

			// Overlay subtitles
			KC3Network.showSubtitle(har);
		},

		/**
		 * Set some state attributes for tracking events of game battle.
		 */
		setBattleEvent :function(isEntered = false, time = undefined, identifier = undefined){
			this.battleEvent.entered = isEntered;
			this.battleEvent.time = time;
			this.battleEvent.identifier = identifier;
		},

		/**
		 * Call those events deferred by specified API call amount on specified advice.
		 */
		handleDeferredEvents :function(request, advice){
			if(!this.deferredEvents.length) { return false; }
			// Check the event queue if advice is matched
			const eventsToHandle = this.deferredEvents
				.filter(e => (e.advice === advice || e.advice === "around") && e.amount > 0);
			let eventTriggered = 0;
			eventsToHandle.forEach(e => {
				// Avoid to reduce API call count duplicatedly if advice is around
				if(e.advice !== "around" || (e.advice === "around" && advice === "before")) {
					e.amount -= 1;
				}
				if(e.amount <= 0) {
					// Remove the event already triggered from queue
					const index = this.deferredEvents.indexOf(e);
					if(index >= 0) { this.deferredEvents.splice(index, 1); }
					// Only trigger the event on first time countdown reaches
					if(e.amount === 0) {
						this.trigger(e.name, e.data);
						console.log(`Deferred event [${e.name}] called on [${request.call}]`, e);
						eventTriggered += 1;
					}
				} else {
					console.log(`Deferred event [${e.name}], ${e.amount} left on [${request.call}]`);
				}
			});
			// Indicate if there is an event triggered at least
			return eventTriggered > 0;
		},

		/**
		 * Asynchronously invoke a remote DB submission module to submit KCSAPI request data.
		 */
		asyncSubmit :function(submission, request, moduleName){
			// turns out "Request.process()" modifies the request, so clone the unmodified instance first.
			var clonedRequest = $.extend(true, new KC3Request(), request);
			// although submission processes should not be slow, still make them parallel async.
			setTimeout(function(){
				try {
					//console.log(`Processing data via ${moduleName}...`);
					submission.processData.call(submission, clonedRequest);
				} catch (error) {
					// suppose all exceptions thrown are caught already, should not reach here.
					console.warn(`Uncaught data submission to ${moduleName}`, error);
				}
			});
		},

		/**
		 * Send a message to content script (via background script service)
		 * to show subtitles at overlay for supported sound audio files.
		 */
		showSubtitle :function(har){
			// url sample: http://203.104.209.39/kcs/sound/kcdbtrdgatxdpl/178798.mp3?version=5
			//             http://203.104.209.39/kcs2/resources/voice/titlecall_1/050.mp3
			const requestUrl = har.request.url;
			const isV2Voice = requestUrl.includes("/kcs2/resources/voice/");
			if(!(isV2Voice || requestUrl.includes("/kcs/sound/"))) {
				return;
			}
			const soundPaths = requestUrl.split("/");
			const voiceType = soundPaths[isV2Voice ? 6 : 5];
			switch(voiceType) {
			case "titlecall":
				// console.debug("DETECTED titlecall sound");
				(new RMsg("service", "subtitle", {
					voicetype: "titlecall",
					fullurl: requestUrl,
					filename: soundPaths[6],
					voiceNum: soundPaths[7].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "titlecall_1":
			case "titlecall_2":
				// console.debug("DETECTED kcs2 titlecall sound");
				(new RMsg("service", "subtitle", {
					voicetype: "titlecall",
					fullurl: requestUrl,
					filename: voiceType.split("_")[1],
					voiceNum: soundPaths[7].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "tutorial":
				// Ignore this for now
				break;
			case "kc9997":
				// console.debug("DETECTED Event special sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "event",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					voiceSize: har.response.content.size || 0,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "kc9998":
				// console.debug("DETECTED Abyssal sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "abyssal",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					voiceSize: har.response.content.size || 0,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "kc9999":
				// console.debug("DETECTED NPC sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "npc",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			default:
				// console.debug("DETECTED shipgirl sound");
				const shipGirl = KC3Master.graph_file(soundPaths[5].substring(2));
				const voiceLine = KC3Meta.getVoiceLineByFilename(shipGirl, soundPaths[6].split(".")[0]);
				const audioFileSize = har.response.content.size || 0;
				(new RMsg("service", "subtitle", {
					voicetype: "shipgirl",
					fullurl: requestUrl,
					shipID: shipGirl,
					voiceNum: voiceLine,
					voiceSize: audioFileSize,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
			}
		},

		/* CLEAR OVERLAYS
		Ask background page to forward a message to play screen.
		Requests to remove existing HTML on-screen overlays
		------------------------------------------*/
		clearOverlays :function(){
			if(this.hasOverlay) {
				this.hasOverlay = false;
				(new RMsg("service", "clearOverlays", {
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
			}
		}

	};

})();
<commit_msg>Add chuuha/taiha damage cutin detection to focus<commit_after>/* Network.js
KC3改 Network Listener

Must only be imported on devtools pages!
Listens to network history and triggers callback if game events happen
*/
(function(){
	"use strict";

	window.KC3Network = {
		hasOverlay: false,
		lastUrl: "",
		eventTypes : {
			GameStart: [],
			CatBomb: [],
			APIError: [],
			Bomb201: [],
			ModalBox: [],
			GameUpdate: [],
			DebuffNotify: [],
			HomeScreen: [],
			HQ: [],
			Consumables: [],
			ShipSlots: [],
			GearSlots: [],
			Timers: [],
			Quests: [],
			Fleet: [],
			Lbas: [],
			SortieStart: [],
			CompassResult: [],
			LandBaseAirRaid: [],
			BattleStart: [],
			BattleNight: [],
			BattleResult: [],
			CraftGear: [],
			CraftShip: [],
			Modernize: [],
			ClearedMap: [],
			PvPList: [],
			PvPFleet: [],
			PvPStart: [],
			PvPNight: [],
			PvPEnd: [],
			ExpeditionSelection: [],
			ExpeditionStart: [],
			ExpedResult: [],
			GunFit: [],
			GearRemodelList: [],
			GearRemodelDetail: [],
			GearRemodel: [],
		},
		delayedUpdate: {},
		deferredEvents: [],
		battleEvent: {
			entered: false,
			time: undefined,
			identifier: undefined,
		},
		submissionModuleNames: ["PoiDBSubmission", "OpenDBSubmission", "TsunDBSubmission"],
		submissionConfigs: {},

		/* ADD LISTENER
		All callback to an event
		------------------------------------------*/
		addListener : function( eventName, callback ){
			this.eventTypes[eventName].push(callback);
		},

		/* ADD GLOBAL LISTENER
		All callback to all events
		------------------------------------------*/
		addGlobalListener : function( callback ){
			$.each(this.eventTypes, function( eventType, eventCallbacks ){
				eventCallbacks.push( callback );
			});
		},

		/* TRIGGER
		Execute subscribed callbacks to an event
		------------------------------------------*/
		trigger : function( eventName, data ){
			if((this.delayedUpdate[eventName] || 0) <= 0) {
				$.each(this.eventTypes[eventName], function( index, callback ){
					callback( eventName, data || {});
				});
				this.delayedUpdate[eventName] = 0;
			} else {
				this.delayedUpdate[eventName] -= 1;
				console.log(`Prevented to call [${eventName}], delay ${this.delayedUpdate[eventName]} left`);
			}
		},

		/* DEFER TRIGGER
		Defers event performing after specified amount of API calls
		------------------------------------------*/
		deferTrigger : function(count, eventName, data){
			// Only allow `after` API call advice for now,
			// `before` and `around` seem not useful yet.
			const advice = "after";
			// +1 if not before since it will be handled at once after current API call
			const amount = count + (advice !== "before" ? 1 : 0);
			this.deferredEvents.push({
				advice: advice,
				amount: amount,
				name: eventName,
				data: data
			});
			//console.log(`Deferred to call [${eventName}] after ${count} time(s)`);
		},

		/* DELAY
		Prevents event performing
		------------------------------------------*/
		delay : function(){
			var self = this;
			var amount = Array.prototype.shift.apply(arguments);
			$.each(arguments, function( i,eventName ){
				self.delayedUpdate[eventName] = amount;
			});
		},

		/* LISTEN
		Start listening and define callback
		------------------------------------------*/
		listen :function(){
			// Call Chrome API to start listening
			chrome.devtools.network.onRequestFinished.addListener(this.received);
		},

		/* INIT CONFIGS
		Store and listen to the changes of used config values
		------------------------------------------*/
		initConfigs :function(){
			ConfigManager.loadIfNecessary();
			// Initial config values
			const configSuffix = "_enabled";
			this.submissionModuleNames.forEach(name => {
				this.submissionConfigs[name] = ConfigManager[name + configSuffix];
			});
			const configChangedListener = ({key, timeStamp, url}) => {
				if(key === ConfigManager.keyName()) {
					const newConfigs = localStorage.getObject(ConfigManager.keyName());
					this.submissionModuleNames.forEach(name => {
						if(this.submissionConfigs[name] !== newConfigs[name + configSuffix]) {
							const isToCleanup = !this.submissionConfigs[name] && !!newConfigs[name + configSuffix];
							this.submissionConfigs[name] = newConfigs[name + configSuffix];
							// Clean previous states if config is changed from disabled to enabled,
							// because it is buggy especially on config changed during sortie.
							const submission = window[name],
								cleanMethod = submission && submission.cleanup,
								isAbleToCleanup = isToCleanup && typeof cleanMethod === "function";
							console.log(`${name} enabled changed to ${this.submissionConfigs[name]}${isAbleToCleanup ? ", cleaning previous states..." : ""}`);
							if(isAbleToCleanup) {
								try {
									cleanMethod.call(submission);
								} catch (error) {
									console.warn("Uncaught states cleanup", error);
								}
							}
						}
					});
				}
			};
			// Do not try to call this multiple times, otherwise this is needed:
			//window.removeEventListener("storage", configChangeListener);
			// Listen to the changes of local storage configs
			window.addEventListener("storage", configChangedListener);
		},

		/* RECEIVED
		Fired when we receive network entry
		Inside, use "KC3Network" instead of "this"
		It's a callback so "this" is in the context of the chrome listener
		Argument HAR see: https://developer.chrome.com/extensions/devtools_network#type-Request
		------------------------------------------*/
		received : function(har){
			const requestUrl = har.request.url;
			// If request is an API Call
			if(requestUrl.indexOf("/kcsapi/") > -1){
				KC3Network.lastUrl = requestUrl;

				// Clear overlays before processing this new API call
				KC3Network.clearOverlays();

				// Create new request and process it
				// console.debug(request, request.request);
				var thisRequest = new KC3Request(har),
					message = {
						tabId: chrome.devtools.inspectedWindow.tabId,
						message_id: "goodResponses",
						tcp_status: har.response.status,
						api_status: undefined,
						api_result: "他のリクエストが成功！",
					};
				if(thisRequest.validateHeaders()){
					thisRequest.readResponse(har, function(){
						if(thisRequest.validateData()){
							// Invoke remote DB submission modules
							KC3Network.submissionModuleNames.forEach(name => {
								if(KC3Network.submissionConfigs[name]) {
									// Assume module already loaded globally
									KC3Network.asyncSubmit(window[name], thisRequest, name);
								}
							});

							// Trigger deferred events before this API call if there are some
							//KC3Network.handleDeferredEvents(thisRequest, "before");
							// Reset battle event before any valided API call
							KC3Network.setBattleEvent(false);

							thisRequest.process();

							// Trigger deferred events after this API call if there are some
							KC3Network.handleDeferredEvents(thisRequest, "after");
						}
					});
					har.getContent(function(x){
						try {
							var data = JSON.parse(/svdata=(.+)$/.exec(x)[1]);
							message.api_status = data.api_result;
							message.api_result = data.api_result_msg;
						} catch (e) {
							// Only prevent the data parsing error
							message.api_status = e.name;
							message.api_result = e.message;
							console.error("Prevented " + e.name, e.message, e);
						} finally {
							(new RMsg("service", "gameScreenChg", message)).execute();
						}
					});
				} else {
					message.api_status = false;
					message.api_result = har.response.statusText;
					(new RMsg("service", "gameScreenChg", message)).execute();
				}
			}

			// If request is a furniture asset
			if(requestUrl.includes("/img/interior/interior_parts")){
				// Clear overlays upon entering furniture menu,
				// not work everytime since Phase 2 caches assets
				KC3Network.clearOverlays();
			}
			// If request is a sound effect of closing shutter animation on battle ended,
			// to activiate and focus on game tab before battle result API call,
			// it only works if in-game SE is not completely off.
			// Also, to trigger when first taiha/chuuha CG cutin occurs, as game may be paused if in background tab
			// battle_cutin_damage requested only once per battle (but not sortie)
			const focusPaths = ["/resources/se/217.", "img/battle/battle_cutin_damage."];
            if(ConfigManager.focus_game_tab && KC3Network.battleEvent.entered
                // Only after: daytime? day / night to day? night start?
                // seems no side effect if game tab already focused, so use any time for now
                //&& KC3Network.battleEvent.time === "day"
                && focusPaths.some(path => requestUrl.includes(path))){
				(new RMsg("service", "focusGameTab", {
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				// console.debug("Battle end SE detected, focus on game tab requested");
			}

			// Overlay subtitles
			KC3Network.showSubtitle(har);
		},

		/**
		 * Set some state attributes for tracking events of game battle.
		 */
		setBattleEvent :function(isEntered = false, time = undefined, identifier = undefined){
			this.battleEvent.entered = isEntered;
			this.battleEvent.time = time;
			this.battleEvent.identifier = identifier;
		},

		/**
		 * Call those events deferred by specified API call amount on specified advice.
		 */
		handleDeferredEvents :function(request, advice){
			if(!this.deferredEvents.length) { return false; }
			// Check the event queue if advice is matched
			const eventsToHandle = this.deferredEvents
				.filter(e => (e.advice === advice || e.advice === "around") && e.amount > 0);
			let eventTriggered = 0;
			eventsToHandle.forEach(e => {
				// Avoid to reduce API call count duplicatedly if advice is around
				if(e.advice !== "around" || (e.advice === "around" && advice === "before")) {
					e.amount -= 1;
				}
				if(e.amount <= 0) {
					// Remove the event already triggered from queue
					const index = this.deferredEvents.indexOf(e);
					if(index >= 0) { this.deferredEvents.splice(index, 1); }
					// Only trigger the event on first time countdown reaches
					if(e.amount === 0) {
						this.trigger(e.name, e.data);
						console.log(`Deferred event [${e.name}] called on [${request.call}]`, e);
						eventTriggered += 1;
					}
				} else {
					console.log(`Deferred event [${e.name}], ${e.amount} left on [${request.call}]`);
				}
			});
			// Indicate if there is an event triggered at least
			return eventTriggered > 0;
		},

		/**
		 * Asynchronously invoke a remote DB submission module to submit KCSAPI request data.
		 */
		asyncSubmit :function(submission, request, moduleName){
			// turns out "Request.process()" modifies the request, so clone the unmodified instance first.
			var clonedRequest = $.extend(true, new KC3Request(), request);
			// although submission processes should not be slow, still make them parallel async.
			setTimeout(function(){
				try {
					//console.log(`Processing data via ${moduleName}...`);
					submission.processData.call(submission, clonedRequest);
				} catch (error) {
					// suppose all exceptions thrown are caught already, should not reach here.
					console.warn(`Uncaught data submission to ${moduleName}`, error);
				}
			});
		},

		/**
		 * Send a message to content script (via background script service)
		 * to show subtitles at overlay for supported sound audio files.
		 */
		showSubtitle :function(har){
			// url sample: http://203.104.209.39/kcs/sound/kcdbtrdgatxdpl/178798.mp3?version=5
			//             http://203.104.209.39/kcs2/resources/voice/titlecall_1/050.mp3
			const requestUrl = har.request.url;
			const isV2Voice = requestUrl.includes("/kcs2/resources/voice/");
			if(!(isV2Voice || requestUrl.includes("/kcs/sound/"))) {
				return;
			}
			const soundPaths = requestUrl.split("/");
			const voiceType = soundPaths[isV2Voice ? 6 : 5];
			switch(voiceType) {
			case "titlecall":
				// console.debug("DETECTED titlecall sound");
				(new RMsg("service", "subtitle", {
					voicetype: "titlecall",
					fullurl: requestUrl,
					filename: soundPaths[6],
					voiceNum: soundPaths[7].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "titlecall_1":
			case "titlecall_2":
				// console.debug("DETECTED kcs2 titlecall sound");
				(new RMsg("service", "subtitle", {
					voicetype: "titlecall",
					fullurl: requestUrl,
					filename: voiceType.split("_")[1],
					voiceNum: soundPaths[7].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "tutorial":
				// Ignore this for now
				break;
			case "kc9997":
				// console.debug("DETECTED Event special sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "event",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					voiceSize: har.response.content.size || 0,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "kc9998":
				// console.debug("DETECTED Abyssal sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "abyssal",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					voiceSize: har.response.content.size || 0,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			case "kc9999":
				// console.debug("DETECTED NPC sound", soundPaths);
				(new RMsg("service", "subtitle", {
					voicetype: "npc",
					fullurl: requestUrl,
					filename: "",
					voiceNum: soundPaths[6].split(".")[0],
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
				break;
			default:
				// console.debug("DETECTED shipgirl sound");
				const shipGirl = KC3Master.graph_file(soundPaths[5].substring(2));
				const voiceLine = KC3Meta.getVoiceLineByFilename(shipGirl, soundPaths[6].split(".")[0]);
				const audioFileSize = har.response.content.size || 0;
				(new RMsg("service", "subtitle", {
					voicetype: "shipgirl",
					fullurl: requestUrl,
					shipID: shipGirl,
					voiceNum: voiceLine,
					voiceSize: audioFileSize,
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
			}
		},

		/* CLEAR OVERLAYS
		Ask background page to forward a message to play screen.
		Requests to remove existing HTML on-screen overlays
		------------------------------------------*/
		clearOverlays :function(){
			if(this.hasOverlay) {
				this.hasOverlay = false;
				(new RMsg("service", "clearOverlays", {
					tabId: chrome.devtools.inspectedWindow.tabId
				})).execute();
			}
		}

	};

})();
<|endoftext|>


<commit_before>const parse = text => {
  const languages = {};
  text
    .trim()
    .split(" ")
    .forEach(string => {
      const [key, value] = string.split(":");
      languages[key] = parseInt(value);
    });
  return languages;
};

const sum = array => {
  if (array.length === 0) {
    return 0;
  }
  return array.reduce((acum, val) => {
    return !isNaN(val) ? acum + val : 0;
  }, 0);
};

const saveChart = () => {
  const ctx = document.getElementById("myChart").getContext("2d");
  const tcanvas = document.createElement("canvas");
  const tctx = tcanvas.getContext("2d");
  tcanvas.width = ctx.canvas.width;
  tcanvas.height = ctx.canvas.height;
  tctx.fillStyle = "#0f229e";
  tctx.fillRect(0, 0, tcanvas.width, tcanvas.height);
  tctx.drawImage(document.getElementById("myChart"), 0, 0);

  var image = tcanvas
    .toDataURL("image/png")
    .replace("image/png", "image/octet-stream");
  window.location.href = image;
}

const chart = ({ limit, singleColor }) => {
    const canvasContainer = document.getElementById('canvas-container');
    const color = 'white';
    const username = canvasContainer.dataset.username;
    const languages = JSON.parse(canvasContainer.dataset.languages);
    const colors = singleColor ? 'white' : JSON.parse(canvasContainer.dataset.colors);
    const keys = Object.keys(languages);
    const values = Object.values(languages);
    if (typeof limit !== 'number') {
        limit = keys.length;
    }
    const labels = keys.slice(0, limit);
    const data = values.slice(0, limit);
    if (keys.length > 20) {
        const valueOthers = sum(values.slice(limit, keys.length));
        labels.push('Others');
        data.push(valueOthers);
    }
    const canvas = document.getElementById('myChart');
    const chart = window.chart = new Chart(canvas, {
        type: 'bar',
        data: {
            labels,
            datasets: [{
                label: 'Language Stats',
                backgroundColor: colors,
                borderColor: 'black',
                borderWidth: 1,
                data
            }]
        },
        options: {
            maintainAspectRatio: false,
            legend: {
                display: singleColor ? true : false,
                labels: {
                    fontColor: color
                },
                position: 'top'
            },
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: true,
                        fontColor: color
                    }
                }],
                xAxes: [{
                    ticks: {
                        fontColor: color,
                        maxRotation: 90,
                        minRotation: 90
                    }
                }]
            },
            tooltips: {
                callbacks: {
                    afterBody: () => {
                        return `Click to browse repos with the current language by ${username}`
                    }
                }
            }
        }
    });
    const onCanvasClick = (e) => {
        const activeElement = chart.getElementAtEvent(e)[0];
        if(activeElement){
            const language = chart.data.labels[activeElement._index];
            const value = chart.data.datasets[activeElement._datasetIndex].data[activeElement._index];
            const url = `https://github.com/${username}?tab=repositories&type=&language=${language}`;
            window.open(url, "_blank");
        }
    }
    canvas.addEventListener('click', onCanvasClick);
    return singleColor;
};

const toggleColor = () => {
    window.chart.destroy();
    singleColor = chart({ limit: 20, singleColor: !singleColor });
    const btn = document.getElementById('btn-toggle');
    if(!singleColor) {
        btn.classList.add('active').remove('inactive');
    } else {
        btn.classList.remove('active').add('inactive');
    }

};
<commit_msg>saving the image with the .jpg extension<commit_after>const parse = (text) => {
    const languages = {};
    text
        .trim()
        .split(' ')
        .forEach((string) => {
            const [key, value] = string.split(':');
            languages[key] = parseInt(value);
        });
    return languages;
};

const sum = (array) => {
    if (array.length === 0) {
        return 0;
    }
    return array.reduce((acum, val) => !isNaN(val) ? acum + val : 0, 0);
};

const saveChart = () => {
    const ctx = document.getElementById('myChart').getContext('2d');
    const tcanvas = document.createElement('canvas');
    const tctx = tcanvas.getContext('2d');
    tcanvas.width = ctx.canvas.width;
    tcanvas.height = ctx.canvas.height;
    tctx.fillStyle = '#0f229e';
    tctx.fillRect(0, 0, tcanvas.width, tcanvas.height);
    tctx.drawImage(document.getElementById('myChart'), 0, 0);

    const image = tcanvas
        .toDataURL('image/png')
        .replace('image/png', 'image/octet-stream');

    const link = document.createElement('a');
    link.download = 'chart.jpg';
    link.href = image;
    link.click();
};

const chart = ({ limit, singleColor }) => {
    const canvasContainer = document.getElementById('canvas-container');
    const color = 'white';
    const username = canvasContainer.dataset.username;
    const languages = JSON.parse(canvasContainer.dataset.languages);
    const colors = singleColor ? 'white' : JSON.parse(canvasContainer.dataset.colors);
    const keys = Object.keys(languages);
    const values = Object.values(languages);
    if (typeof limit !== 'number') {
        limit = keys.length;
    }
    const labels = keys.slice(0, limit);
    const data = values.slice(0, limit);
    if (keys.length > 20) {
        const valueOthers = sum(values.slice(limit, keys.length));
        labels.push('Others');
        data.push(valueOthers);
    }
    const canvas = document.getElementById('myChart');
    const chart = window.chart = new Chart(canvas, {
        type: 'bar',
        data: {
            labels,
            datasets: [{
                label: 'Language Stats',
                backgroundColor: colors,
                borderColor: 'black',
                borderWidth: 1,
                data
            }]
        },
        options: {
            maintainAspectRatio: false,
            legend: {
                display: singleColor ? true : false,
                labels: {
                    fontColor: color
                },
                position: 'top'
            },
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: true,
                        fontColor: color
                    }
                }],
                xAxes: [{
                    ticks: {
                        fontColor: color,
                        maxRotation: 90,
                        minRotation: 90
                    }
                }]
            },
            tooltips: {
                callbacks: {
                    afterBody: () => `Click to browse repos with the current language by ${username}`
                }
            }
        }
    });
    const onCanvasClick = (e) => {
        const activeElement = chart.getElementAtEvent(e)[0];
        if (activeElement) {
            const language = chart.data.labels[activeElement._index];
            const value = chart.data.datasets[activeElement._datasetIndex].data[activeElement._index];
            const url = `https://github.com/${username}?tab=repositories&type=&language=${language}`;
            window.open(url, '_blank');
        }
    };
    canvas.addEventListener('click', onCanvasClick);
    return singleColor;
};

const toggleColor = () => {
    window.chart.destroy();
    singleColor = chart({ limit: 20, singleColor: !singleColor });
    const btn = document.getElementById('btn-toggle');
    if (!singleColor) {
        btn.classList.add('active').remove('inactive');
    } else {
        btn.classList.remove('active').add('inactive');
    }

};
<|endoftext|>


<commit_before>import createStoreShape from '../utils/createStoreShape';
import getDisplayName from '../utils/getDisplayName';
import shallowEqualScalar from '../utils/shallowEqualScalar';
import shallowEqual from '../utils/shallowEqual';
import isPlainObject from '../utils/isPlainObject';
import wrapActionCreators from '../utils/wrapActionCreators';
import invariant from 'invariant';

const emptySelector = () => ({});

const emptyBinder = () => ({});

const identityMerge = (slice, actionsCreators, props) => ({...slice, ...actionsCreators, ...props});

export default function createConnectDecorator(React) {
  const { Component, PropTypes } = React;
  const storeShape = createStoreShape(PropTypes);

  return function connect(select, dispatchBinder = emptyBinder, mergeHandler = identityMerge) {

    const subscribing = select ? true : false;
    const selectState = select || emptySelector;
    const bindActionCreators = isPlainObject(dispatchBinder) ? wrapActionCreators(dispatchBinder) : dispatchBinder;
    const merge = mergeHandler;

    return DecoratedComponent => class ConnectDecorator extends Component {
      static displayName = `ConnectDecorator(${getDisplayName(DecoratedComponent)})`;
      static DecoratedComponent = DecoratedComponent;

      static contextTypes = {
        store: storeShape.isRequired
      };

      shouldComponentUpdate(nextProps, nextState) {
        return (this.subscribed && !this.isSliceEqual(this.state.slice, nextState.slice)) ||
               !shallowEqualScalar(this.props, nextProps);
      }

      isSliceEqual(slice, nextSlice) {
        const isRefEqual = slice === nextSlice;
        if (isRefEqual) {
          return true;
        } else if (typeof slice !== 'object' || typeof nextSlice !== 'object') {
          return isRefEqual;
        }
        return shallowEqual(slice, nextSlice);
      }

      constructor(props, context) {
        super(props, context);
        this.state = {
          ...this.selectState(props, context),
          ...this.bindActionCreators(context)
        };
      }

      componentDidMount() {
        if (subscribing) {
          this.subscribed = true;
          this.unsubscribe = this.context.store.subscribe(::this.handleChange);
        }
      }

      componentWillUnmount() {
        if (subscribing) {
          this.unsubscribe();
        }
      }

      handleChange(props = this.props) {
        const nextState = this.selectState(props, this.context);
        if (!this.isSliceEqual(this.state.slice, nextState.slice)) {
          this.setState(nextState);
        }
      }

      selectState(props = this.props, context = this.context) {
        const state = context.store.getState();
        const slice = selectState(state);

        invariant(
          isPlainObject(slice),
          'The return value of `select` prop must be an object. Instead received %s.',
          slice
        );

        return { slice };
      }

      bindActionCreators(context = this.context) {
        const { dispatch } = context.store;
        const actionCreators = bindActionCreators(dispatch);

        invariant(
          isPlainObject(actionCreators),
          'The return value of `bindActionCreators` prop must be an object. Instead received %s.',
          actionCreators
        );

        return { actionCreators };
      }

      merge(props = this.props, state = this.state) {
        const { slice, actionCreators } = state;
        const merged = merge(slice, actionCreators, props);

        invariant(
          isPlainObject(merged),
          'The return value of `merge` prop must be an object. Instead received %s.',
          merged
        );

        return merged;
      }

      render() {
        return <DecoratedComponent {...this.props} {...this.merge()} />;
      }
    };
  };
}
<commit_msg>move props in identityMerge to front to allow override by slice and actionCreators, remove from DecoratedComponent render<commit_after>import createStoreShape from '../utils/createStoreShape';
import getDisplayName from '../utils/getDisplayName';
import shallowEqualScalar from '../utils/shallowEqualScalar';
import shallowEqual from '../utils/shallowEqual';
import isPlainObject from '../utils/isPlainObject';
import wrapActionCreators from '../utils/wrapActionCreators';
import invariant from 'invariant';

const emptySelector = () => ({});

const emptyBinder = () => ({});

const identityMerge = (slice, actionsCreators, props) => ({ ...props, ...slice, ...actionsCreators});

export default function createConnectDecorator(React) {
  const { Component, PropTypes } = React;
  const storeShape = createStoreShape(PropTypes);

  return function connect(select, dispatchBinder = emptyBinder, mergeHandler = identityMerge) {

    const subscribing = select ? true : false;
    const selectState = select || emptySelector;
    const bindActionCreators = isPlainObject(dispatchBinder) ? wrapActionCreators(dispatchBinder) : dispatchBinder;
    const merge = mergeHandler;

    return DecoratedComponent => class ConnectDecorator extends Component {
      static displayName = `ConnectDecorator(${getDisplayName(DecoratedComponent)})`;
      static DecoratedComponent = DecoratedComponent;

      static contextTypes = {
        store: storeShape.isRequired
      };

      shouldComponentUpdate(nextProps, nextState) {
        return (this.subscribed && !this.isSliceEqual(this.state.slice, nextState.slice)) ||
               !shallowEqualScalar(this.props, nextProps);
      }

      isSliceEqual(slice, nextSlice) {
        const isRefEqual = slice === nextSlice;
        if (isRefEqual) {
          return true;
        } else if (typeof slice !== 'object' || typeof nextSlice !== 'object') {
          return isRefEqual;
        }
        return shallowEqual(slice, nextSlice);
      }

      constructor(props, context) {
        super(props, context);
        this.state = {
          ...this.selectState(props, context),
          ...this.bindActionCreators(context)
        };
      }

      componentDidMount() {
        if (subscribing) {
          this.subscribed = true;
          this.unsubscribe = this.context.store.subscribe(::this.handleChange);
        }
      }

      componentWillUnmount() {
        if (subscribing) {
          this.unsubscribe();
        }
      }

      handleChange(props = this.props) {
        const nextState = this.selectState(props, this.context);
        if (!this.isSliceEqual(this.state.slice, nextState.slice)) {
          this.setState(nextState);
        }
      }

      selectState(props = this.props, context = this.context) {
        const state = context.store.getState();
        const slice = selectState(state);

        invariant(
          isPlainObject(slice),
          'The return value of `select` prop must be an object. Instead received %s.',
          slice
        );

        return { slice };
      }

      bindActionCreators(context = this.context) {
        const { dispatch } = context.store;
        const actionCreators = bindActionCreators(dispatch);

        invariant(
          isPlainObject(actionCreators),
          'The return value of `bindActionCreators` prop must be an object. Instead received %s.',
          actionCreators
        );

        return { actionCreators };
      }

      merge(props = this.props, state = this.state) {
        const { slice, actionCreators } = state;
        const merged = merge(slice, actionCreators, props);

        invariant(
          isPlainObject(merged),
          'The return value of `merge` prop must be an object. Instead received %s.',
          merged
        );

        return merged;
      }

      render() {
        return <DecoratedComponent {...this.merge()} />;
      }
    };
  };
}
<|endoftext|>


<commit_before>$(document).ready(function() {
  function getLocation() {
    if (navigator.geolocation) {
      var spinner = ''
      $('button.geolocation').html(spinner)

      navigator.geolocation.getCurrentPosition(showPosition)
    } else {
      var error = " Not supported by your browser.";
      $('button.geolocation').html(error)
    }
  }

  function showPosition(position) {
    console.log(position)
  }

  $('button.geolocation').on('click', function() {
    getLocation()
  })
})
<commit_msg>Query with lat/lng on click of location button<commit_after>$(document).ready(function() {
  function getLocation() {
    if (navigator.geolocation) {
      var spinner = ''
      $('button.geolocation').html(spinner)

      navigator.geolocation.getCurrentPosition(showPosition)
    } else {
      var error = " Not supported by your browser.";
      $('button.geolocation').html(error)
    }
  }

  function showPosition(position) {
    console.log(position)
    if (position.coords) {
      var coords  = { lat: position.coords.latitude, lng: position.coords.longitude },
          query   = jQuery.param(coords);
      window.location = window.location + 'search?' + query;
    } else {
      var error = " Couldn't find your location.";
      $('button.geolocation').html(error);
    }
  }

  $('button.geolocation').on('click', function() {
    getLocation()
  })
})
<|endoftext|>


<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a SubmitButton
    should include a <Form.SubmitButton> as a child
    should be a button.btn

    should have the label specified by the `label` property
    should have the default label if no `label` property specified

    should have the text specified
    should have the child elements specified

    should be button.btn.btn-default when no style is provided
    should be button.btn.btn-danger when style=danger
    should be button.btn.btn-danger when style=error
    should be button.btn.btn-warning when style=warning
    should be button.btn.btn-warning when style=warn
    should be button.btn.btn-info when style=info
    should be button.btn.btn-success when style=success
    should be button.btn.btn-success when style=ok
*/
describe('a Form component containing a SubmitButton', () => {

    it('should include a <Form.SubmitButton> as a child', () => {
        const component = shallow(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find(Form.SubmitButton)).to.have.length(1);
    });

    it('should be a button.btn', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn')).to.have.length(1, 'button.btn.btn-default');
    });

    it('should have the label specified by the `label` property', () => {
        const label = 'my label';

        const component = render(
            <Form>
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').text()).to.equal(label);
    });

    it('should have the default label if no `label` property specified', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button.btn').text()).to.equal('Submit');
    });

    // it('should have the text specified', () => {});

    // it('should have the child elements specified', () => {});

    it('should be button.btn.btn-default when no style is provided', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-default'))
            .to.have.length(1, 'button.btn.btn-default');
    });

    it('should be button.btn.btn-danger when style=danger', () => {

        const style = 'danger';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-danger')).to.have.length(1, 'button.btn.btn-danger');
    });

    it('should be button.btn.btn-danger when style=error', () => {

        const style = 'error';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-danger')).to.have.length(1, 'button.btn.btn-danger');
    });

    it('should be button.btn.btn-warning when style=warning', () => {

        const style = 'warning';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-warning'))
            .to.have.length(1, 'button.btn.btn-warning');
    });

    it('should be button.btn.btn-warning when style=warn', () => {

        const style = 'warn';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-warning'))
            .to.have.length(1, 'button.btn.btn-warning');
    });

    it('should be button.btn.btn-info when style=info', () => {

        const style = 'info';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-info')).to.have.length(1, 'button.btn.btn-info');
    });

    it('should be button.btn.btn-success when style=success', () => {

        const style = 'success';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-success'))
            .to.have.length(1, 'button.btn.btn-success');
    });

    it('should be button.btn.btn-success when style=ok', () => {

        const style = 'ok';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-success'))
            .to.have.length(1, 'button.btn.btn-success');
    });
});

/* *****************************************************************************
given a Form containing a required TextInput and a SubmitButton
    after initialization with a valid value, the SubmitButton should be enabled
    after initialization with several valid values, the SubmitButton should be enabled
    after initialization with multiple invalid values, the SubmitButton should be disabled
    after initialization with a mix of valid & invalid values, the SubmitButton should be disabled
    after changing from invalid to valid, the SubmitButton should be enabled
    after changing from valid to invalid, the SubmitButton should be disabled
*/
describe('given a Form containing a required TextInput and a SubmitButton', () => {

    it('after initialization with a valid value, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const value = 'a valid value';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after initialization with several valid values, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const value = 'a valid value';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after initialization with multiple invalid values, the SubmitButton ' +
        'should be disabled', () => {
        const label = 'my label';

        const value = '';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

    it('after initialization with a mix of valid & invalid values, the ' +
        'SubmitButton should be disabled', () => {
        const label = 'my label';

        const invalid = '';

        const valid = 'triceratops';

        const component = mount(
            <Form>
                <Form.TextInput required value={invalid} />
                <Form.TextInput required value={invalid} />
                <Form.TextInput required value={valid} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

    it('after changing from invalid to valid, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const initialValue = '';

        const finalValue = 'I wish I were an Oscar Meyer weiner';

        const component = mount(
            <Form>
                <Form.TextInput required value={initialValue} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        component.find('input').simulate('blur');

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after changing from valid to invalid, the SubmitButton should be disabled', () => {
        const label = 'my label';

        const initialValue = 'I wish I were an Oscar Meyer weiner';

        const finalValue = '';

        const component = mount(
            <Form>
                <Form.TextInput required value={initialValue} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        component.find('input').simulate('blur');

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

});

/* *****************************************************************************
a SubmitButton
    should call onClick when the button in a form is clicked
*/
describe('a SubmitButton', () => {

    it('should call onClick when the button in a form is clicked', () => {

        const onClick = sinon.spy();

        const label = 'my label';

        const component = mount(
            <Form>
                <Form.SubmitButton label={label} onClick={onClick} />
            </Form>
        );

        component.find('button').simulate('click');

        expect(onClick.callCount).to.equal(1);
    });
});
<commit_msg>commented out old specs<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, render, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';

import Form from '../Form';

const expect = chai.expect;

/* eslint-disable no-unused-vars */
const debug = (component) => {
    console.info('------------------------------------------------------------');
    console.info(component.debug());
    console.info('------------------------------------------------------------');
};
/* eslint-enable no-unused-vars */

/* *****************************************************************************
a Form component containing a SubmitButton
    should include a <Form.SubmitButton> as a child
    should be a button.btn

    should have the label specified by the `label` property
    should have the default label if no `label` property specified

    should have the text specified
    should have the child elements specified

    should be button.btn.btn-default when no style is provided
    should be button.btn.btn-danger when style=danger
    should be button.btn.btn-danger when style=error
    should be button.btn.btn-warning when style=warning
    should be button.btn.btn-warning when style=warn
    should be button.btn.btn-info when style=info
    should be button.btn.btn-success when style=success
    should be button.btn.btn-success when style=ok
*/
describe('a Form component containing a SubmitButton', () => {

    it('should include a <Form.SubmitButton> as a child', () => {
        const component = shallow(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find(Form.SubmitButton)).to.have.length(1);
    });

    it('should be a button.btn', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn')).to.have.length(1, 'button.btn.btn-default');
    });

    /*
    it('should have the label specified by the `label` property', () => {
        const label = 'my label';

        const component = render(
            <Form>
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').text()).to.equal(label);
    });

    it('should have the default label if no `label` property specified', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button.btn').text()).to.equal('Submit');
    });
    */

    // it('should have the text specified', () => {});

    // it('should have the child elements specified', () => {});

    it('should be button.btn.btn-default when no style is provided', () => {
        const component = render(
            <Form>
                <Form.SubmitButton />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-default'))
            .to.have.length(1, 'button.btn.btn-default');
    });

    it('should be button.btn.btn-danger when style=danger', () => {

        const style = 'danger';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-danger')).to.have.length(1, 'button.btn.btn-danger');
    });

    it('should be button.btn.btn-danger when style=error', () => {

        const style = 'error';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-danger')).to.have.length(1, 'button.btn.btn-danger');
    });

    it('should be button.btn.btn-warning when style=warning', () => {

        const style = 'warning';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-warning'))
            .to.have.length(1, 'button.btn.btn-warning');
    });

    it('should be button.btn.btn-warning when style=warn', () => {

        const style = 'warn';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-warning'))
            .to.have.length(1, 'button.btn.btn-warning');
    });

    it('should be button.btn.btn-info when style=info', () => {

        const style = 'info';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-info')).to.have.length(1, 'button.btn.btn-info');
    });

    it('should be button.btn.btn-success when style=success', () => {

        const style = 'success';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-success'))
            .to.have.length(1, 'button.btn.btn-success');
    });

    it('should be button.btn.btn-success when style=ok', () => {

        const style = 'ok';

        const component = render(
            <Form>
                <Form.SubmitButton style={style} />
            </Form>
        );

        expect(component.find('button')).to.have.length(1, 'button');
        expect(component.find('button.btn.btn-success'))
            .to.have.length(1, 'button.btn.btn-success');
    });
});

/* *****************************************************************************
given a Form containing a required TextInput and a SubmitButton
    after initialization with a valid value, the SubmitButton should be enabled
    after initialization with several valid values, the SubmitButton should be enabled
    after initialization with multiple invalid values, the SubmitButton should be disabled
    after initialization with a mix of valid & invalid values, the SubmitButton should be disabled
    after changing from invalid to valid, the SubmitButton should be enabled
    after changing from valid to invalid, the SubmitButton should be disabled
*/
describe('given a Form containing a required TextInput and a SubmitButton', () => {

    it('after initialization with a valid value, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const value = 'a valid value';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after initialization with several valid values, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const value = 'a valid value';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after initialization with multiple invalid values, the SubmitButton ' +
        'should be disabled', () => {
        const label = 'my label';

        const value = '';

        const component = mount(
            <Form>
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.TextInput required value={value} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

    it('after initialization with a mix of valid & invalid values, the ' +
        'SubmitButton should be disabled', () => {
        const label = 'my label';

        const invalid = '';

        const valid = 'triceratops';

        const component = mount(
            <Form>
                <Form.TextInput required value={invalid} />
                <Form.TextInput required value={invalid} />
                <Form.TextInput required value={valid} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

    it('after changing from invalid to valid, the SubmitButton should be enabled', () => {
        const label = 'my label';

        const initialValue = '';

        const finalValue = 'I wish I were an Oscar Meyer weiner';

        const component = mount(
            <Form>
                <Form.TextInput required value={initialValue} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(true);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        component.find('input').simulate('blur');

        expect(component.find('button.btn').props().disabled).to.equal(false);
    });

    it('after changing from valid to invalid, the SubmitButton should be disabled', () => {
        const label = 'my label';

        const initialValue = 'I wish I were an Oscar Meyer weiner';

        const finalValue = '';

        const component = mount(
            <Form>
                <Form.TextInput required value={initialValue} />
                <Form.SubmitButton label={label} />
            </Form>
        );

        expect(component.find('button.btn').props().disabled).to.equal(false);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        component.find('input').simulate('blur');

        expect(component.find('button.btn').props().disabled).to.equal(true);
    });

});

/* *****************************************************************************
a SubmitButton
    should call onClick when the button in a form is clicked
*/
describe('a SubmitButton', () => {

    it('should call onClick when the button in a form is clicked', () => {

        const onClick = sinon.spy();

        const label = 'my label';

        const component = mount(
            <Form>
                <Form.SubmitButton label={label} onClick={onClick} />
            </Form>
        );

        component.find('button').simulate('click');

        expect(onClick.callCount).to.equal(1);
    });
});
<|endoftext|>