<commit_before>// DIALOGS /////////////////////////////////////////////////////////
// Inspired by TES III: Morrowind. Long live House Telvanni. ///////
////////////////////////////////////////////////////////////////////

/* The global ID of onscreen dialogs.
 * Starts at 0.
 */
window.DIALOG_ID = 0;

/* All onscreen dialogs, keyed by their ID.
 */
window.DIALOGS = {};

/* The number of dialogs on screen.
 */
window.DIALOG_COUNT = 0;

/* The dialog that has focus.
 */
window.DIALOG_FOCUS = null;

/* Creates a dialog and puts it onscreen. Takes one argument: options, a JS object.
 * == Common options
 * (text|html): The text or HTML to display in the dialog. Text is auto-converted to HTML.
 * title: The dialog's title.
 * modal: Whether to open a modal dialog. Implies draggable=false; dialogClass='ui-dialog-modal'.
 *        Please note that modal dialogs hijack the entire screen and should only be used in very
 *        specific cases. (If IITC is running on mobile, modal will always be true).
 * id:   A unique ID for this dialog. If a dialog with id `id' is already open and dialog() is called
 *       again, it will be automatically closed.
 *
 * == Callbacks
 * closeCallback: A callback to run on close. Takes no arguments.
 * collapseCallback: A callback to run on dialog collapse.  Takes no arguments.
 * expandCallback:   A callback to run on dialog expansion. Takes no arguments.
 * collapseExpandCallback: A callback to run on both collapse and expand (overrides collapseCallback
 *                         and expandCallback, takes a boolean argument `collapsing' - true if collapsing;
 *                         false if expanding)
 * focusCallback: A callback to run when the dialog gains focus.
 * blurCallback:  A callback to run when the dialog loses focus.
 *
 * See http://docs.jquery.com/UI/API/1.8/Dialog for a list of all the options. If you previously
 * applied a class to your dialog after creating it with alert(), dialogClass may be particularly
 * useful.
 */
window.dialog = function(options) {
  // Override for smartphones. Preserve default behavior and create a modal dialog.
  options = options || {};
  if(isSmartphone()) {
    options.modal = true;
  }

  // Build an identifier for this dialog
  var id = 'dialog-' + (options.modal ? 'modal' : (options.id ? options.id : 'anon-' + window.DIALOG_ID++));
  var jqID = '#' + id;
  var html = '';

  // Convert text to HTML if necessary
  if(options.text) {
    html = window.convertTextToTableMagic(options.text);
  } else if(options.html) {
    html = options.html;
  } else {
    console.log('window.dialog: warning: no text in dialog');
    html = window.convertTextToTableMagic('');
  }

  // Modal dialogs should not be draggable
  if(options.modal) {
    options.dialogClass = 'ui-dialog-modal';
    options.draggable = false;
  }

  // Close out existing dialogs.
  if(window.DIALOGS[id]) {
    try {
      $(window.DIALOGS[id]).dialog('close');
    } catch(err) {
      console.log('window.dialog: Tried to close nonexistent dialog ' + id);
    }
  }

  // Create the window, appending a div to the body
  $('body').append('

');
  var dialog = $(jqID).dialog($.extend(true, {
    autoOpen: false,
    modal: false,
    draggable: true,
    closeText: ' ',
    title: '',
    buttons: {
      'OK': function() {
        $(this).dialog('close');
      }
    },
    open: function() {
      var titlebar = $(this).closest('.ui-dialog').find('.ui-dialog-titlebar');
      titlebar.find('.ui-dialog-title').addClass('ui-dialog-title-active');
      var close = titlebar.find('.ui-dialog-titlebar-close');

      // Title should not show up on mouseover
      close.removeAttr('title').addClass('ui-dialog-titlebar-button');

      if(!$(this).dialog('option', 'modal')) {
        // Start out with a cloned version of the close button
        var collapse = close.clone();

        // Change it into a collapse button and set the click handler
        collapse.addClass('ui-dialog-titlebar-button-collapse ui-dialog-titlebar-button-collapse-expanded');
        collapse.click($.proxy(function() {
          var collapsed = ($(this).data('collapsed') === true);

          // Run callbacks if we have them
          if($(this).data('collapseExpandCallback')) {
            $.proxy($(this).data('collapseExpandCallback'), this)(!collapsed);
          } else {
            if(collapsed && $(this).data('collapseCallback')) {
              $.proxy($(this).data('collapseCallback'), this)();
            } else if (!collapsed && $(this).data('expandCallback')) {
              $.proxy($(this).data('expandCallback'), this)();
            }
          }

          // Find the button pane and content dialog in this ui-dialog, and add or remove the 'hidden' class.
          var dialog   = $(this).closest('.ui-dialog');
          var selector = dialog.find('.ui-dialog-content,.ui-dialog-buttonpane');
          var button   = dialog.find('.ui-dialog-titlebar-button-collapse');
          if (collapsed) {
            $(selector).removeClass('ui-dialog-content-hidden');
            $(button).removeClass('ui-dialog-titlebar-button-collapse-collapsed');
            $(button).addClass('ui-dialog-titlebar-button-collapse-expanded');
          } else {
            $(selector).addClass('ui-dialog-content-hidden');
            $(button).removeClass('ui-dialog-titlebar-button-collapse-expanded');
            $(button).addClass('ui-dialog-titlebar-button-collapse-collapsed');
          }

          // Toggle collapsed state
          $(this).data('collapsed', !collapsed);
        }, this));

        // Put it into the titlebar
        titlebar.prepend(collapse);
        close.addClass('ui-dialog-titlebar-button-close');
      }

      window.DIALOGS[$(this).data('id')] = this;
      window.DIALOG_COUNT++;

      console.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') opened. ' + window.DIALOG_COUNT + ' remain.');
    },
    close:  function() {
      // Run the close callback if we have one
      if($(this).data('closeCallback')) {
        $.proxy($(this).data('closeCallback'), this)();
      }

      // Make sure that we don't keep a dead dialog in focus
      if(window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') === $(this).data('id')) {
        window.DIALOG_FOCUS = null;
      }

      // Finalize
      delete window.DIALOGS[$(this).data('id')];

      window.DIALOG_COUNT--;
      console.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') closed. ' + window.DIALOG_COUNT + ' remain.');
    },
    focus: function() {
      if($(this).data('focusCallback')) {
        $.proxy($(this).data('focusCallback'), this)();
      }

      // Blur the window currently in focus unless we're gaining focus
      if(window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') !== $(this).data('id')) {
        $.proxy(function(event, ui) {
          if($(this).data('blurCallback')) {
            $.proxy($(this).data('blurCallback'), this)();
          }

          $(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-active').addClass('ui-dialog-title-inactive');
        }, window.DIALOG_FOCUS)();
      }

      // This dialog is now in focus
      window.DIALOG_FOCUS = this;
      $(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-inactive').addClass('ui-dialog-title-active');
    }
  }, options));

  // Set HTML and IDs
  dialog.html(html);
  dialog.data('id', id);
  dialog.data('jqID', jqID);

  // Set callbacks
  dialog.data('closeCallback', options.closeCallback);
  dialog.data('collapseCallback', options.collapseCallback);
  dialog.data('expandCallback', options.expandCallback);
  dialog.data('collapseExpandCallback', options.collapseExpandCallback);
  dialog.data('focusCallback', options.focusCallback);
  dialog.data('blurCallback', options.blurCallback);

  // ui-modal includes overrides for modal dialogs
  if (options.modal) {
    dialog.parent().addClass('ui-modal');
  }

  // Enable snapping
  dialog.dialog().parents('.ui-dialog').draggable('option', 'snap', true);

  // Run it
  dialog.dialog('open');

  return dialog;
}

/* Creates an alert dialog with default settings.
 * If you want more configurability, use window.dialog instead.
 */
window.alert = function(text, isHTML, closeCallback) {
  var obj = {closeCallback: closeCallback};
  if(isHTML) {
    obj.html = text;
  } else {
    obj.text = text;
  }

  return dialog(obj);
}

window.setupDialogs = function() {
  window.DIALOG_ID = 0;
  window.DIALOGS   = {}
  window.DIALOG_COUNT = 0;
  window.DIALOG_FOCUS = null;
}
<commit_msg>* Fix condition that would rarely cause dialogs to not open properly (dialog wasn't getting destroyed before being replaced)<commit_after>// DIALOGS /////////////////////////////////////////////////////////
// Inspired by TES III: Morrowind. Long live House Telvanni. ///////
////////////////////////////////////////////////////////////////////

/* The global ID of onscreen dialogs.
 * Starts at 0.
 */
window.DIALOG_ID = 0;

/* All onscreen dialogs, keyed by their ID.
 */
window.DIALOGS = {};

/* The number of dialogs on screen.
 */
window.DIALOG_COUNT = 0;

/* The dialog that has focus.
 */
window.DIALOG_FOCUS = null;

/* Creates a dialog and puts it onscreen. Takes one argument: options, a JS object.
 * == Common options
 * (text|html): The text or HTML to display in the dialog. Text is auto-converted to HTML.
 * title: The dialog's title.
 * modal: Whether to open a modal dialog. Implies draggable=false; dialogClass='ui-dialog-modal'.
 *        Please note that modal dialogs hijack the entire screen and should only be used in very
 *        specific cases. (If IITC is running on mobile, modal will always be true).
 * id:   A unique ID for this dialog. If a dialog with id `id' is already open and dialog() is called
 *       again, it will be automatically closed.
 *
 * == Callbacks
 * closeCallback: A callback to run on close. Takes no arguments.
 * collapseCallback: A callback to run on dialog collapse.  Takes no arguments.
 * expandCallback:   A callback to run on dialog expansion. Takes no arguments.
 * collapseExpandCallback: A callback to run on both collapse and expand (overrides collapseCallback
 *                         and expandCallback, takes a boolean argument `collapsing' - true if collapsing;
 *                         false if expanding)
 * focusCallback: A callback to run when the dialog gains focus.
 * blurCallback:  A callback to run when the dialog loses focus.
 *
 * See http://docs.jquery.com/UI/API/1.8/Dialog for a list of all the options. If you previously
 * applied a class to your dialog after creating it with alert(), dialogClass may be particularly
 * useful.
 */
window.dialog = function(options) {
  // Override for smartphones. Preserve default behavior and create a modal dialog.
  options = options || {};
  if(isSmartphone()) {
    options.modal = true;
  }

  // Build an identifier for this dialog
  var id = 'dialog-' + (options.modal ? 'modal' : (options.id ? options.id : 'anon-' + window.DIALOG_ID++));
  var jqID = '#' + id;
  var html = '';

  // Convert text to HTML if necessary
  if(options.text) {
    html = window.convertTextToTableMagic(options.text);
  } else if(options.html) {
    html = options.html;
  } else {
    console.log('window.dialog: warning: no text in dialog');
    html = window.convertTextToTableMagic('');
  }

  // Modal dialogs should not be draggable
  if(options.modal) {
    options.dialogClass = 'ui-dialog-modal';
    options.draggable = false;
  }

  // Close out existing dialogs.
  if(window.DIALOGS[id]) {
    try {
      var selector = $(window.DIALOGS[id]);
      selector.dialog('close');
      selector.remove();
    } catch(err) {
      console.log('window.dialog: Tried to close nonexistent dialog ' + id);
    }
  }

  // Create the window, appending a div to the body
  $('body').append('

');
  var dialog = $(jqID).dialog($.extend(true, {
    autoOpen: false,
    modal: false,
    draggable: true,
    closeText: ' ',
    title: '',
    buttons: {
      'OK': function() {
        $(this).dialog('close');
      }
    },
    open: function() {
      var titlebar = $(this).closest('.ui-dialog').find('.ui-dialog-titlebar');
      titlebar.find('.ui-dialog-title').addClass('ui-dialog-title-active');
      var close = titlebar.find('.ui-dialog-titlebar-close');

      // Title should not show up on mouseover
      close.removeAttr('title').addClass('ui-dialog-titlebar-button');

      if(!$(this).dialog('option', 'modal')) {
        // Start out with a cloned version of the close button
        var collapse = close.clone();

        // Change it into a collapse button and set the click handler
        collapse.addClass('ui-dialog-titlebar-button-collapse ui-dialog-titlebar-button-collapse-expanded');
        collapse.click($.proxy(function() {
          var collapsed = ($(this).data('collapsed') === true);

          // Run callbacks if we have them
          if($(this).data('collapseExpandCallback')) {
            $.proxy($(this).data('collapseExpandCallback'), this)(!collapsed);
          } else {
            if(collapsed && $(this).data('collapseCallback')) {
              $.proxy($(this).data('collapseCallback'), this)();
            } else if (!collapsed && $(this).data('expandCallback')) {
              $.proxy($(this).data('expandCallback'), this)();
            }
          }

          // Find the button pane and content dialog in this ui-dialog, and add or remove the 'hidden' class.
          var dialog   = $(this).closest('.ui-dialog');
          var selector = dialog.find('.ui-dialog-content,.ui-dialog-buttonpane');
          var button   = dialog.find('.ui-dialog-titlebar-button-collapse');
          if (collapsed) {
            $(selector).removeClass('ui-dialog-content-hidden');
            $(button).removeClass('ui-dialog-titlebar-button-collapse-collapsed');
            $(button).addClass('ui-dialog-titlebar-button-collapse-expanded');
          } else {
            $(selector).addClass('ui-dialog-content-hidden');
            $(button).removeClass('ui-dialog-titlebar-button-collapse-expanded');
            $(button).addClass('ui-dialog-titlebar-button-collapse-collapsed');
          }

          // Toggle collapsed state
          $(this).data('collapsed', !collapsed);
        }, this));

        // Put it into the titlebar
        titlebar.prepend(collapse);
        close.addClass('ui-dialog-titlebar-button-close');
      }

      window.DIALOGS[$(this).data('id')] = this;
      window.DIALOG_COUNT++;

      console.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') opened. ' + window.DIALOG_COUNT + ' remain.');
    },
    close:  function() {
      // Run the close callback if we have one
      if($(this).data('closeCallback')) {
        $.proxy($(this).data('closeCallback'), this)();
      }

      // Make sure that we don't keep a dead dialog in focus
      if(window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') === $(this).data('id')) {
        window.DIALOG_FOCUS = null;
      }

      // Finalize
      delete window.DIALOGS[$(this).data('id')];

      window.DIALOG_COUNT--;
      console.log('window.dialog: ' + $(this).data('id') + ' (' + $(this).dialog('option', 'title') + ') closed. ' + window.DIALOG_COUNT + ' remain.');
    },
    focus: function() {
      if($(this).data('focusCallback')) {
        $.proxy($(this).data('focusCallback'), this)();
      }

      // Blur the window currently in focus unless we're gaining focus
      if(window.DIALOG_FOCUS && $(window.DIALOG_FOCUS).data('id') !== $(this).data('id')) {
        $.proxy(function(event, ui) {
          if($(this).data('blurCallback')) {
            $.proxy($(this).data('blurCallback'), this)();
          }

          $(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-active').addClass('ui-dialog-title-inactive');
        }, window.DIALOG_FOCUS)();
      }

      // This dialog is now in focus
      window.DIALOG_FOCUS = this;
      $(this).closest('.ui-dialog').find('.ui-dialog-title').removeClass('ui-dialog-title-inactive').addClass('ui-dialog-title-active');
    }
  }, options));

  // Set HTML and IDs
  dialog.html(html);
  dialog.data('id', id);
  dialog.data('jqID', jqID);

  // Set callbacks
  dialog.data('closeCallback', options.closeCallback);
  dialog.data('collapseCallback', options.collapseCallback);
  dialog.data('expandCallback', options.expandCallback);
  dialog.data('collapseExpandCallback', options.collapseExpandCallback);
  dialog.data('focusCallback', options.focusCallback);
  dialog.data('blurCallback', options.blurCallback);

  // ui-modal includes overrides for modal dialogs
  if (options.modal) {
    dialog.parent().addClass('ui-modal');
  }

  // Enable snapping
  dialog.dialog().parents('.ui-dialog').draggable('option', 'snap', true);

  // Run it
  dialog.dialog('open');

  return dialog;
}

/* Creates an alert dialog with default settings.
 * If you want more configurability, use window.dialog instead.
 */
window.alert = function(text, isHTML, closeCallback) {
  var obj = {closeCallback: closeCallback};
  if(isHTML) {
    obj.html = text;
  } else {
    obj.text = text;
  }

  return dialog(obj);
}

window.setupDialogs = function() {
  window.DIALOG_ID = 0;
  window.DIALOGS   = {}
  window.DIALOG_COUNT = 0;
  window.DIALOG_FOCUS = null;
}
<|endoftext|>


<commit_before>/* global Module */

/* Magic Mirror
 * Module: MMM-DWD-WarnWeather
 *
 * By Luke Scheffler https://github.com/LukeSkywalker92
 * MIT Licensed.
 */

Module.register("MMM-DWD-WarnWeather", {

	// Default module config.
	defaults: {
		region: '',
		longversion: false,
		minutes: true,
		displayRegionName: true,
		width: 55,
		changeColor: true,
		interval: 10 * 60 * 1000, // every 10 minutes
		loadingText: 'Warnungen werden geladen...',
		noWarningText: 'Keine Warnungen'
	},
	// Required scrpits
	getScripts: function () {
		return ["moment.js"];
	},
	// Required styles
	getStyles: function () {
		return ["MMM-DWD-WarnWeather.css"]
	},
	// Define parameters at start
	start: function () {
		this.loaded = false;
		this.noWarnings = false;
		this.warnings = [];
		this.community = [];
		this.icons = {
			THUNDERSTORM: 'sprite-gewitter',
			WIND:         'sprite-sturm',
			TORNADO:      'sprite-sturm',
			RAIN:         'sprite-regen',
			HAIL:         'sprite-schnee',
			SNOWFALL:     'sprite-schnee',
			SNOWDRIFT:    'sprite-schnee',
			FOG:          'sprite-nebel',
			FROST:        'sprite-frost',
			GLAZE:        'sprite-glatteis',
			THAW:         'sprite-tauwetter',
			HEAT:         'sprite-hitze',
			UV:           'sprite-uv'
		}

		this.updateWarnings(this);
	},

	// Make node_helper to get new warning-data
	updateWarnings: function (self) {
		if (self.config.region) {
		  self.sendSocketNotification('GET_WARNINGS', self.config.region);
	  }
		else if (self.config.lat && self.config.lng) {
			var coords = {lat:self.config.lat, lng:self.config.lng};
			self.sendSocketNotification('GET_WARNINGS', coords);
		}
		setTimeout(self.updateWarnings, self.config.interval, self);
	},

	// Override dom generator.
	getDom: function () {
		var wrapper = document.createElement("div");
		wrapper.className = 'wrapper';

		var header = document.createElement("header");
		if (this.community.hasOwnProperty('properties')){
			header.innerHTML = 'Wetterwarnungen';
			if (this.config.displayRegionName && this.loaded) {
				header.innerHTML += ' für:
' + this.community.properties.NAME;
			}
		} else {
			header.innerHTML = 'Ort nicht gefunden';
		}
		wrapper.appendChild(header);

		// Check if warning data was loaded
		if (!this.loaded) {
			var loading = document.createElement("p");
			loading.className = 'status';
			loading.innerHTML = this.config.loadingText;
			wrapper.appendChild(loading);
			return wrapper;
		}

		// Check if there are warnings for defined region
		if (this.warnings.length < 1) {
			var noWarningWrapper = document.createElement("p");
			noWarningWrapper.className = 'status';
			noWarningWrapper.innerHTML = this.config.noWarningText;
			wrapper.appendChild(noWarningWrapper);
			return wrapper;
		}

		// Display warnings
		for (var i = 0; i < this.warnings.length; i++) {
			timeDisplayFormat = this.config.minutes ? "dd. HH:mm" : "dd. HH";
			var start = moment(this.warnings[i].properties.ONSET).format(timeDisplayFormat) + ' Uhr';
			var end = moment(this.warnings[i].properties.EXPIRES).format(timeDisplayFormat) + ' Uhr';
			var type = this.warnings[i].properties.EC_GROUP.split(';')[0];
			if (this.config.longversion) {
				var event = this.wordwrap(this.warnings[i].properties.DESCRIPTION, this.config.width, "
");
			} else {
				var event = this.warnings[i].properties.EVENT;
			}
			var warnWrapper = document.createElement("div");
			warnWrapper.className = 'warning';
			var icon = document.createElement("div");
			if (this.config.changeColor) {
				iconColor = this.warnings[i].properties.EC_AREA_COLOR.replace(/ /g, ',');
				icon.setAttribute('style', 'background-color: rgb(' + iconColor + ')');
				icon.classList.add('weathericon', this.icons[type], 'small-icon');
			} else {
				icon.setAttribute('style', 'background-color: white');
				icon.classList.add('weathericon', this.icons[type], 'small-icon');
			}
			var description = document.createElement("div");
			description.className = 'description';
			var headline = document.createElement("div");
			headline.innerHTML = event;
			var duration = document.createElement("div");
			duration.className = 'duration';
			duration.innerHTML = start + ' - ' + end;
			var newLine = document.createElement("br");
			description.appendChild(headline);
			description.appendChild(duration);
			warnWrapper.appendChild(icon);
			warnWrapper.appendChild(description);
			wrapper.appendChild(warnWrapper);
			wrapper.appendChild(newLine);

		}

		return wrapper;
	},

	wordwrap: function (str, width, brk) {

		brk = brk || "n";
		width = width || 75;

		if (!str) {
		  return str;
		}

		var re = new RegExp(".{1," + width +
		     "}(\\s|$)|\\ S+?(\\s|$)", "g");

	  return str.match(RegExp(re)).join(brk);
	},

	pointInPoly: function (point, vs) {
    // ray-casting algorithm based on
    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    var x = point[0], y = point[1];

    var inside = false;
    for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
      var xi = vs[i][0], yi = vs[i][1];
      var xj = vs[j][0], yj = vs[j][1];

      var intersect = ((yi > y) != (yj > y))
          && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
      if (intersect) inside = !inside;
    }

    return inside;
	},

	socketNotificationReceived: function (notification, payload) {
		Log.info(notification);

		if (notification === 'WARNINGS_DATA') {
			if(payload.community.hasOwnProperty('geometry')){
				if (payload.region == this.config.region || this.pointInPoly([this.config.lng, this.config.lat], payload.community.geometry.coordinates[0])){
					Log.info(payload);
					this.warnings = payload.warnings;
					this.community = payload.community;
					this.loaded = true;
					this.noWarnings = false;
					this.updateDom(1000);
				}
			}
		}
	}
});
<commit_msg>Update MMM-DWD-WarnWeather.js<commit_after>/* global Module */

/* Magic Mirror
 * Module: MMM-DWD-WarnWeather
 *
 * By Luke Scheffler https://github.com/LukeSkywalker92
 * MIT Licensed.
 */

Module.register("MMM-DWD-WarnWeather", {

	// Default module config.
	defaults: {
		region: '',
		longversion: false,
		minutes: true,
		displayRegionName: true,
		width: 55,
		changeColor: true,
		interval: 10 * 60 * 1000, // every 10 minutes
		loadingText: 'Warnungen werden geladen...',
		noWarningText: 'Keine Warnungen',
		severityThreshold: 2
	},
	// Required scrpits
	getScripts: function () {
		return ["moment.js"];
	},
	// Required styles
	getStyles: function () {
		return ["MMM-DWD-WarnWeather.css"]
	},
	// Define parameters at start
	start: function () {
		this.loaded = false;
		this.noWarnings = false;
		this.warnings = [];
		this.community = [];
		this.icons = {
			THUNDERSTORM: 'sprite-gewitter',
			WIND:         'sprite-sturm',
			TORNADO:      'sprite-sturm',
			RAIN:         'sprite-regen',
			HAIL:         'sprite-schnee',
			SNOWFALL:     'sprite-schnee',
			SNOWDRIFT:    'sprite-schnee',
			FOG:          'sprite-nebel',
			FROST:        'sprite-frost',
			GLAZE:        'sprite-glatteis',
			THAW:         'sprite-tauwetter',
			HEAT:         'sprite-hitze',
			UV:           'sprite-uv'
		}

		this.updateWarnings(this);
	},

	// Make node_helper to get new warning-data
	updateWarnings: function (self) {
		if (self.config.region) {
		  self.sendSocketNotification('GET_WARNINGS', self.config.region);
	  }
		else if (self.config.lat && self.config.lng) {
			var coords = {lat:self.config.lat, lng:self.config.lng};
			self.sendSocketNotification('GET_WARNINGS', coords);
		}
		setTimeout(self.updateWarnings, self.config.interval, self);
	},

	// Override dom generator.
	getDom: function () {
		var wrapper = document.createElement("div");
		wrapper.className = 'wrapper';

		var header = document.createElement("header");
		if (this.community.hasOwnProperty('properties')){
			header.innerHTML = 'Wetterwarnungen';
			if (this.config.displayRegionName && this.loaded) {
				header.innerHTML += ' für:
' + this.community.properties.NAME;
			}
		} else {
			header.innerHTML = 'Ort nicht gefunden';
		}
		wrapper.appendChild(header);

		// Check if warning data was loaded
		if (!this.loaded) {
			var loading = document.createElement("p");
			loading.className = 'status';
			loading.innerHTML = this.config.loadingText;
			wrapper.appendChild(loading);
			return wrapper;
		}

		// Check if there are warnings for defined region
		if (this.warnings.length < 1) {
			var noWarningWrapper = document.createElement("p");
			noWarningWrapper.className = 'status';
			noWarningWrapper.innerHTML = this.config.noWarningText;
			wrapper.appendChild(noWarningWrapper);
			return wrapper;
		}

		// Display warnings
		for (var i = 0; i < this.warnings.length; i++) {
			timeDisplayFormat = this.config.minutes ? "dd. HH:mm" : "dd. HH";
			var start = moment(this.warnings[i].properties.ONSET).format(timeDisplayFormat) + ' Uhr';
			var end = moment(this.warnings[i].properties.EXPIRES).format(timeDisplayFormat) + ' Uhr';
			var type = this.warnings[i].properties.EC_GROUP.split(';')[0];
			if (this.config.longversion) {
				var event = this.wordwrap(this.warnings[i].properties.DESCRIPTION, this.config.width, "
");
			} else {
				var event = this.warnings[i].properties.EVENT;
			}
			var warnWrapper = document.createElement("div");
			warnWrapper.className = 'warning';
			var icon = document.createElement("div");
			if (this.config.changeColor) {
				iconColor = this.warnings[i].properties.EC_AREA_COLOR.replace(/ /g, ',');
				icon.setAttribute('style', 'background-color: rgb(' + iconColor + ')');
				icon.classList.add('weathericon', this.icons[type], 'small-icon');
			} else {
				icon.setAttribute('style', 'background-color: white');
				icon.classList.add('weathericon', this.icons[type], 'small-icon');
			}
			var description = document.createElement("div");
			description.className = 'description';
			var headline = document.createElement("div");
			headline.innerHTML = event;
			var duration = document.createElement("div");
			duration.className = 'duration';
			duration.innerHTML = start + ' - ' + end;
			var newLine = document.createElement("br");
			description.appendChild(headline);
			description.appendChild(duration);
			warnWrapper.appendChild(icon);
			warnWrapper.appendChild(description);
			wrapper.appendChild(warnWrapper);
			wrapper.appendChild(newLine);

		}

		return wrapper;
	},

	wordwrap: function (str, width, brk) {

		brk = brk || "n";
		width = width || 75;

		if (!str) {
		  return str;
		}

		var re = new RegExp(".{1," + width +
		     "}(\\s|$)|\\ S+?(\\s|$)", "g");

	  return str.match(RegExp(re)).join(brk);
	},

	pointInPoly: function (point, vs) {
    // ray-casting algorithm based on
    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    var x = point[0], y = point[1];

    var inside = false;
    for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
      var xi = vs[i][0], yi = vs[i][1];
      var xj = vs[j][0], yj = vs[j][1];

      var intersect = ((yi > y) != (yj > y))
          && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
      if (intersect) inside = !inside;
    }

    return inside;
	},

	socketNotificationReceived: function (notification, payload) {
		Log.info(notification);

		if (notification === 'WARNINGS_DATA') {
			if(payload.community.hasOwnProperty('geometry')){
				if (payload.region == this.config.region || this.pointInPoly([this.config.lng, this.config.lat], payload.community.geometry.coordinates[0])){
					Log.info(payload);
					this.warnings = payload.warnings;
					this.community = payload.community;
					this.loaded = true;
					this.noWarnings = false;
					this.updateDom(1000);
				}
			}
		}
	}
});
<|endoftext|>


<commit_before>'use strict';

require('./block_arduino');
require('./block_sensorboard');
require('./block_joystick');
require('./block_dplay');
require('./block_nemoino');
require('./block_ardublock');
require('./block_arduino_nano');
require('./block_roboid');
require('./block_mkboard');
require('./block_memaker');
require('./block_smartboard');
require('./block_rokoboard');
require('./block_albert');
require('./block_altino');
require('./block_arduino_ext');
require('./block_coding_box');
require('./block_bitbrick');
require('./block_blacksmith');
require('./block_byrobot_dronefighter_controller');
require('./block_byrobot_dronefighter_drive');
require('./block_byrobot_dronefighter_flight');
require('./block_byrobot_petrone_v2_controller');
require('./block_byrobot_petrone_v2_drive');
require('./block_byrobot_petrone_v2_flight');
require('./block_chocopi');
require('./block_cobl');
require('./block_coconut');
require('./block_codeino');
require('./block_codestar');
require('./block_dadublock');
require('./block_edumaker');
require('./block_ev3');
require('./block_hamster');
require('./block_hummingbird');
require('./block_iboard');
require('./block_jdkit');
require('./block_codingmachine');
require('./block_mechatronics_4d');
require('./block_modi');
require('./block_neobot');
require('./block_neobot_sensor_theme');
require('./block_roborobo');
require('./block_robotis');
require('./block_robotori');
require('./block_truerobot');
require('./block_turtle');
require('./block_xbot');
require('./block_playcode');
require('./block_mechatro');
require('./block_creamo');
require('./block_funboard');
require('./block_microbit');
require('./block_mrt');
require('./block_dash');
require('./block_bingles');

Entry.HARDWARE_LIST = {
    '1.1': Entry.Arduino,
    '1.2': Entry.SensorBoard,
    '1.3': Entry.CODEino,
    '1.4': Entry.joystick,
    '1.5': Entry.dplay,
    '1.6': Entry.nemoino,
    '1.7': Entry.Xbot,
    '1.8': Entry.ardublock,
    '1.9': Entry.ArduinoExt,
    '1.10': Entry.ArduinoNano,
    '1.A': Entry.Cobl,
    '1.B': Entry.Blacksmith,
    '1.C': Entry.CodingBox,
    '2.4': Entry.Hamster,
    '2.5': Entry.Albert,
    '2.9': Entry.Turtle,
    '2.FF': Entry.Roboid,
    '3.1': Entry.Bitbrick,
    '4.2': Entry.Arduino,
    '5.1': Entry.Neobot,
    '5.2': Entry.NeobotSensorTheme,
    '6.1': Entry.mkboard,
    '6.2': Entry.memaker,
    '7.1': Entry.Robotis_carCont,
    '7.2': Entry.Robotis_openCM70,
    '8.1': Entry.Arduino,
    '9.1': Entry.iboard,
    'A.1': Entry.SmartBoard,
    'B.1': Entry.Codestar,
    'C.1': Entry.DaduBlock,
    'C.2': Entry.DaduBlock_Car,
    'D.1': Entry.robotori,
    'F.1': Entry.byrobot_dronefighter_controller,
    'F.2': Entry.byrobot_dronefighter_drive,
    'F.3': Entry.byrobot_dronefighter_flight,
    'F.4': Entry.byrobot_petrone_v2_controller,
    'F.5': Entry.byrobot_petrone_v2_drive,
    'F.6': Entry.byrobot_petrone_v2_flight,
    '10.1': Entry.Roborobo_Roduino,
    '10.2': Entry.Roborobo_SchoolKit,
    '12.1': Entry.EV3,
    '13.1': Entry.rokoboard,
    '14.1': Entry.Chocopi,
    '15.1': Entry.coconut,
    '16.1': Entry.MODI,
    '18.1': Entry.Altino,
    '19.1': Entry.trueRobot,
    '1A.1': Entry.ArduinoNano,
    '1B.1': Entry.EduMaker,
    '1C.1': Entry.hummingbird,
    '1D.1': Entry.JDKit,
    '1E.1': Entry.playcode,
    '1F.1': Entry.mechatro,
    '20.1': Entry.Creamo,
    '21.1': Entry.FunBoard,
    '22.1': Entry.Microbit,
    '23.1': Entry.MRT,
    '24.1': Entry.Dash,
    '1.D': Entry.BINGLES,
    'FF.FF': Entry.Codingmachine

};

const startBlock = require('./block_start');
const flowBlock = require('./block_flow');
const movingBlock = require('./block_moving');
const looksBlock = require('./block_looks');
const brushBlock = require('./block_brush');
const textBlock = require('./block_text');
const soundBlock = require('./block_sound');
const judgementBlock = require('./block_judgement');
const calcBlock = require('./block_calc');
const variableBlock = require('./block_variable');
const funcBlock = require('./block_func');

require('./block_expansion_weather');
Entry.EXPANSION_BLOCK = {};
Entry.EXPANSION_BLOCK_LIST = {
    'weather' : Entry.Expansion_Weather
};

function getBlockObject(items) {
    const blockObject = {};
    items.forEach((item)=> {
        if('getBlocks' in item) {
            Object.assign(blockObject, item.getBlocks());
        }
    });
    return blockObject;
}

module.exports = {
    getBlocks() {
        const basicBlockList = [
            startBlock,
            flowBlock,
            movingBlock,
            looksBlock,
            brushBlock,
            textBlock,
            soundBlock,
            judgementBlock,
            calcBlock,
            variableBlock,
            funcBlock
        ];
        const expansionBlockList = Object.values(Entry.EXPANSION_BLOCK_LIST);
        const hardwareList = Object.values(Entry.HARDWARE_LIST);
        return getBlockObject(basicBlockList.concat(hardwareList).concat(expansionBlockList));
    }
}
<commit_msg>Delete index.js<commit_after><|endoftext|>


<commit_before>document.getElementById("id_business_level_version").innerHTML = "Business level version: 2017.12.10.0";

navigator.geolocation.watchPosition(on_gps_success, on_gps_error);

var marker;
//--------------------------------------
function on_gps_success(p)
{
	document.getElementById("id_p").innerHTML = "lat = " +
	p.coords.latitude + "
long = " + p.coords.longitude +
	"
accuracy = " + p.coords.accuracy + "m" +
	"
altitude = " + p.coords.altitude + "m"+
	"
speed = " + p.coords.speed + "m/s";

	var map_obj = new google.maps.Map(document.getElementById("id_map"));
	map_obj.setCenter({lat:p.coords.latitude, lng:p.coords.longitude});
	map_obj.setZoom(10);

	marker = new google.maps.Marker({
    position: {lat:p.coords.latitude, lng:p.coords.longitude},
    title:"LMM!",
	map:map_obj,
	animation: google.maps.Animation.DROP
	});
	marker.addListener("click", on_marker_click);
}
//--------------------------------------
function on_marker_click()
{
	if (marker.getAnimation() !== null) {
		marker.setAnimation(null);
	} else {
		marker.setAnimation(google.maps.Animation.BOUNCE);
	}
}
//--------------------------------------
function on_gps_error(e)
{
	alert(e.code);
}
//--------------------------------------<commit_msg>formular<commit_after>document.getElementById("id_business_level_version").innerHTML = "Business level version: 2018.01.11.0";

navigator.geolocation.watchPosition(on_gps_success, on_gps_error);

var marker;
//--------------------------------------
function on_gps_success(p)
{
	document.getElementById("id_p").innerHTML = "lat = " +
	p.coords.latitude + "
long = " + p.coords.longitude +
	"
accuracy = " + p.coords.accuracy + "m" +
	"
altitude = " + p.coords.altitude + "m"+
	"
speed = " + p.coords.speed + "m/s";

	var map_obj = new google.maps.Map(document.getElementById("id_map"));
	map_obj.setCenter({lat:p.coords.latitude, lng:p.coords.longitude});
	map_obj.setZoom(10);

	marker = new google.maps.Marker({
    position: {lat:p.coords.latitude, lng:p.coords.longitude},
    title:"LMM!",
	map:map_obj,
	animation: google.maps.Animation.DROP
	});
	marker.addListener("click", on_marker_click);
}
//--------------------------------------
function on_marker_click()
{
	if (marker.getAnimation() !== null) {
		marker.setAnimation(null);
	} else {
		marker.setAnimation(google.maps.Animation.BOUNCE);
	}
}
//--------------------------------------
function on_gps_error(e)
{
	alert(e.code);
}
//--------------------------------------<|endoftext|>


<commit_before>/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var setConvertCartesian = require('../cartesian/set_convert');

var deg2rad = Lib.deg2rad;
var rad2deg = Lib.rad2deg;
var isFullCircle = Lib.isFullCircle;

/**
 * setConvert for polar axes!
 *
 * @param {object} ax
 *   axis in question (works for both radial and angular axes)
 * @param {object} polarLayout
 *   full polar layout of the subplot associated with 'ax'
 * @param {object} fullLayout
 *   full layout
 *
 * Here, reuse some of the Cartesian setConvert logic,
 * but we must extend some of it, as both radial and angular axes
 * don't have domains and angular axes don't have _true_ ranges.
 *
 * Moreover, we introduce two new coordinate systems:
 * - 'g' for geometric coordinates and
 * - 't' for angular ticks
 *
 * Radial axis coordinate systems flow:
 * - d2c (in calc) just like for cartesian axes
 * - c2g (in plot) translates calcdata about `radialaxis.range[0]`
 *
 * Angular axis coordinate systems flow:
 * + for linear axes:
 *   - d2c (in calc) angles -> 'data' radians
 *   - c2g (in plot) 'data' -> 'geometric' radians (fn of ax rotation & direction)
 *   - t2g (in updateAngularAxis) 'tick' value (in degrees) -> 'geometric' radians
 * + for category axes:
 *   - d2c (in calc) just like for cartesian axes
 *   - c2g (in plot) category indices -> 'geometric' radians
 *   - t2g (in updateAngularAxis) 'tick' value (as category indices) -> 'geometric' radians
 *
 * Then, 'g'eometric data is ready to be converted to (x,y).
 */
module.exports = function setConvert(ax, polarLayout, fullLayout) {
    setConvertCartesian(ax, fullLayout);

    switch(ax._id) {
        case 'x':
        case 'radialaxis':
            setConvertRadial(ax);
            break;
        case 'angularaxis':
            setConvertAngular(ax, polarLayout);
            break;
    }
};

function setConvertRadial(ax) {
    ax.setGeometry = function() {
        var rng = ax.range;

        var rFilter = rng[0] > rng[1] ?
            function(v) { return v <= 0; } :
            function(v) { return v >= 0; };

        ax.c2g = function(v) {
            var r = ax.c2r(v) - rng[0];
            return rFilter(r) ? r : 0;
        };

        ax.g2c = function(v) {
            return ax.r2c(v + rng[0]);
        };
    };
}

function toRadians(v, unit) {
    return unit === 'degrees' ? deg2rad(v) : v;
}

function fromRadians(v, unit) {
    return unit === 'degrees' ? rad2deg(v) : v;
}

function setConvertAngular(ax, polarLayout) {
    var axType = ax.type;

    if(axType === 'linear') {
        var _d2c = ax.d2c;
        var _c2d = ax.c2d;

        ax.d2c = function(v, unit) { return toRadians(_d2c(v), unit); };
        ax.c2d = function(v, unit) { return _c2d(fromRadians(v, unit)); };
    }

    // override makeCalcdata to handle thetaunit and special theta0/dtheta logic
    ax.makeCalcdata = function(trace, coord) {
        var arrayIn = trace[coord];
        var len = trace._length;
        var arrayOut, i;

        var _d2c = function(v) { return ax.d2c(v, trace.thetaunit); };

        if(arrayIn) {
            if(Lib.isTypedArray(arrayIn) && axType === 'linear') {
                if(len === arrayIn.length) {
                    return arrayIn;
                } else if(arrayIn.subarray) {
                    return arrayIn.subarray(0, len);
                }
            }

            arrayOut = new Array(len);
            for(i = 0; i < len; i++) {
                arrayOut[i] = _d2c(arrayIn[i]);
            }
        } else {
            var coord0 = coord + '0';
            var dcoord = 'd' + coord;
            var v0 = (coord0 in trace) ? _d2c(trace[coord0]) : 0;
            var dv = (trace[dcoord]) ? _d2c(trace[dcoord]) : (ax.period || 2 * Math.PI) / len;

            arrayOut = new Array(len);
            for(i = 0; i < len; i++) {
                arrayOut[i] = v0 + i * dv;
            }
        }

        return arrayOut;
    };

    // N.B. we mock the axis 'range' here
    ax.setGeometry = function() {
        var sector = polarLayout.sector;
        var dir = {clockwise: -1, counterclockwise: 1}[ax.direction];
        var rot = deg2rad(ax.rotation);

        var rad2g = function(v) { return dir * v + rot; };
        var g2rad = function(v) { return (v - rot) / dir; };

        var rad2c, c2rad;
        var rad2t, t2rad;

        switch(axType) {
            case 'linear':
                c2rad = rad2c = Lib.identity;
                t2rad = deg2rad;
                rad2t = rad2deg;

                // Set the angular range in degrees to make auto-tick computation cleaner,
                // changing rotation/direction should not affect the angular tick value.
                ax.range = isFullCircle(sector) ?
                    sector.slice() :
                    sector.map(deg2rad).map(g2rad).map(rad2deg);
                break;

            case 'category':
                var catLen = ax._categories.length;
                var _period = ax.period ? Math.max(ax.period, catLen) : catLen;

                c2rad = t2rad = function(v) { return v * 2 * Math.PI / _period; };
                rad2c = rad2t = function(v) { return v * _period / Math.PI / 2; };

                ax.range = [0, _period];
                break;
        }

        ax.c2g = function(v) { return rad2g(c2rad(v)); };
        ax.g2c = function(v) { return rad2c(g2rad(v)); };

        ax.t2g = function(v) { return rad2g(t2rad(v)); };
        ax.g2t = function(v) { return rad2t(g2rad(v)); };
    };
}
<commit_msg>improve polar setConvert docstring<commit_after>/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var setConvertCartesian = require('../cartesian/set_convert');

var deg2rad = Lib.deg2rad;
var rad2deg = Lib.rad2deg;
var isFullCircle = Lib.isFullCircle;

/**
 * setConvert for polar axes!
 *
 * @param {object} ax
 *   axis in question (works for both radial and angular axes)
 * @param {object} polarLayout
 *   full polar layout of the subplot associated with 'ax'
 * @param {object} fullLayout
 *   full layout
 *
 * Here, reuse some of the Cartesian setConvert logic,
 * but we must extend some of it, as both radial and angular axes
 * don't have domains and angular axes don't have _true_ ranges.
 *
 * Moreover, we introduce two new coordinate systems:
 * - 'g' for geometric coordinates and
 * - 't' for angular ticks
 *
 * Radial axis coordinate systems:
 * - d, c and l: same as for cartesian axes
 * - g: like calcdata but translated about `radialaxis.range[0]`
 *
 * Angular axis coordinate systems:
 * - d: data, in whatever form it's provided
 * - c: calcdata, turned into radians (for linear axes)
 *      or category indices (category axes)
 * - t: tick calcdata, just like 'c' but in degrees for linear axes
 * - g: geometric calcdata, radians coordinates that take into account
 *      axis rotation and direction
 *
 * Then, 'g'eometric data is ready to be converted to (x,y).
 */
module.exports = function setConvert(ax, polarLayout, fullLayout) {
    setConvertCartesian(ax, fullLayout);

    switch(ax._id) {
        case 'x':
        case 'radialaxis':
            setConvertRadial(ax);
            break;
        case 'angularaxis':
            setConvertAngular(ax, polarLayout);
            break;
    }
};

function setConvertRadial(ax) {
    ax.setGeometry = function() {
        var rng = ax.range;

        var rFilter = rng[0] > rng[1] ?
            function(v) { return v <= 0; } :
            function(v) { return v >= 0; };

        ax.c2g = function(v) {
            var r = ax.c2r(v) - rng[0];
            return rFilter(r) ? r : 0;
        };

        ax.g2c = function(v) {
            return ax.r2c(v + rng[0]);
        };
    };
}

function toRadians(v, unit) {
    return unit === 'degrees' ? deg2rad(v) : v;
}

function fromRadians(v, unit) {
    return unit === 'degrees' ? rad2deg(v) : v;
}

function setConvertAngular(ax, polarLayout) {
    var axType = ax.type;

    if(axType === 'linear') {
        var _d2c = ax.d2c;
        var _c2d = ax.c2d;

        ax.d2c = function(v, unit) { return toRadians(_d2c(v), unit); };
        ax.c2d = function(v, unit) { return _c2d(fromRadians(v, unit)); };
    }

    // override makeCalcdata to handle thetaunit and special theta0/dtheta logic
    ax.makeCalcdata = function(trace, coord) {
        var arrayIn = trace[coord];
        var len = trace._length;
        var arrayOut, i;

        var _d2c = function(v) { return ax.d2c(v, trace.thetaunit); };

        if(arrayIn) {
            if(Lib.isTypedArray(arrayIn) && axType === 'linear') {
                if(len === arrayIn.length) {
                    return arrayIn;
                } else if(arrayIn.subarray) {
                    return arrayIn.subarray(0, len);
                }
            }

            arrayOut = new Array(len);
            for(i = 0; i < len; i++) {
                arrayOut[i] = _d2c(arrayIn[i]);
            }
        } else {
            var coord0 = coord + '0';
            var dcoord = 'd' + coord;
            var v0 = (coord0 in trace) ? _d2c(trace[coord0]) : 0;
            var dv = (trace[dcoord]) ? _d2c(trace[dcoord]) : (ax.period || 2 * Math.PI) / len;

            arrayOut = new Array(len);
            for(i = 0; i < len; i++) {
                arrayOut[i] = v0 + i * dv;
            }
        }

        return arrayOut;
    };

    // N.B. we mock the axis 'range' here
    ax.setGeometry = function() {
        var sector = polarLayout.sector;
        var dir = {clockwise: -1, counterclockwise: 1}[ax.direction];
        var rot = deg2rad(ax.rotation);

        var rad2g = function(v) { return dir * v + rot; };
        var g2rad = function(v) { return (v - rot) / dir; };

        var rad2c, c2rad;
        var rad2t, t2rad;

        switch(axType) {
            case 'linear':
                c2rad = rad2c = Lib.identity;
                t2rad = deg2rad;
                rad2t = rad2deg;

                // Set the angular range in degrees to make auto-tick computation cleaner,
                // changing rotation/direction should not affect the angular tick value.
                ax.range = isFullCircle(sector) ?
                    sector.slice() :
                    sector.map(deg2rad).map(g2rad).map(rad2deg);
                break;

            case 'category':
                var catLen = ax._categories.length;
                var _period = ax.period ? Math.max(ax.period, catLen) : catLen;

                c2rad = t2rad = function(v) { return v * 2 * Math.PI / _period; };
                rad2c = rad2t = function(v) { return v * _period / Math.PI / 2; };

                ax.range = [0, _period];
                break;
        }

        ax.c2g = function(v) { return rad2g(c2rad(v)); };
        ax.g2c = function(v) { return rad2c(g2rad(v)); };

        ax.t2g = function(v) { return rad2g(t2rad(v)); };
        ax.g2t = function(v) { return rad2t(g2rad(v)); };
    };
}
<|endoftext|>


<commit_before>const Plugin = require('../../core/Plugin')
const { limitPromises } = require('../../core/Utils')
const Uploader = require('./MultipartUploader')

function handleResponse (response) {
  if (response.ok) return response.json()
  return response.json().then((err) => {
    throw err
  })
}

module.exports = class AwsS3Multipart extends Plugin {
  constructor (uppy, opts) {
    super(uppy, opts)
    this.type = 'uploader'
    this.id = 'AwsS3Multipart'
    this.title = 'AWS S3 Multipart'

    const defaultOptions = {
      timeout: 30 * 1000,
      limit: 0,
      createMultipartUpload: this.createMultipartUpload.bind(this),
      listParts: this.listParts.bind(this),
      prepareUploadPart: this.prepareUploadPart.bind(this),
      abortMultipartUpload: this.abortMultipartUpload.bind(this),
      completeMultipartUpload: this.completeMultipartUpload.bind(this)
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.upload = this.upload.bind(this)

    if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) {
      this.limitRequests = limitPromises(this.opts.limit)
    } else {
      this.limitRequests = (fn) => fn
    }
  }

  assertHost () {
    if (!this.opts.host) {
      throw new Error('Expected a `host` option containing an uppy-server address.')
    }
  }

  createMultipartUpload (file) {
    this.assertHost()

    const filename = encodeURIComponent(file.name)
    const type = encodeURIComponent(file.type)
    return fetch(`${this.opts.host}/s3/multipart?filename=${filename}&type=${type}`, {
      method: 'post',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  listParts (file, { key, uploadId }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    return fetch(`${this.opts.host}/s3/multipart/${uploadId}?key=${filename}`, {
      method: 'get',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  prepareUploadPart (file, { key, uploadId, number }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    return fetch(`${this.opts.host}/s3/multipart/${uploadId}/${number}?key=${filename}`, {
      method: 'get',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  completeMultipartUpload (file, { key, uploadId, parts }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    const uploadIdEnc = encodeURIComponent(uploadId)
    return fetch(`${this.opts.host}/s3/multipart/${uploadIdEnc}/complete?key=${filename}`, {
      method: 'post',
      headers: {
        accept: 'application/json',
        'content-type': 'application/json'
      },
      body: JSON.stringify({ parts })
    }).then(handleResponse)
  }

  abortMultipartUpload (file, { key, uploadId }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    const uploadIdEnc = encodeURIComponent(uploadId)
    return fetch(`${this.opts.host}/s3/multipart/${uploadIdEnc}?key=${filename}`, {
      method: 'delete',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  uploadFile (file) {
    return new Promise((resolve, reject) => {
      const upload = new Uploader(file.data, Object.assign({
        // .bind to pass the file object to each handler.
        createMultipartUpload: this.limitRequests(this.opts.createMultipartUpload.bind(this, file)),
        listParts: this.limitRequests(this.opts.listParts.bind(this, file)),
        prepareUploadPart: this.opts.prepareUploadPart.bind(this, file),
        completeMultipartUpload: this.limitRequests(this.opts.completeMultipartUpload.bind(this, file)),
        abortMultipartUpload: this.limitRequests(this.opts.abortMultipartUpload.bind(this, file)),

        limit: this.opts.limit || 5,
        onStart: (data) => {
          const cFile = this.uppy.getFile(file.id)
          this.uppy.setFileState(file.id, {
            s3Multipart: Object.assign({}, cFile.s3Multipart, {
              key: data.key,
              uploadId: data.uploadId,
              parts: []
            })
          })
        },
        onProgress: (bytesUploaded, bytesTotal) => {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: bytesUploaded,
            bytesTotal: bytesTotal
          })
        },
        onError: (err) => {
          this.uppy.log(err)
          this.uppy.emit('upload-error', file, err)
          err.message = `Failed because: ${err.message}`
          reject(err)
        },
        onSuccess: (result) => {
          this.uppy.emit('upload-success', file, upload, result.location)

          if (result.location) {
            this.uppy.log('Download ' + upload.file.name + ' from ' + result.location)
          }

          resolve(upload)
        },
        onPartComplete: (part) => {
          // Store completed parts in state.
          const cFile = this.uppy.getFile(file.id)
          this.uppy.setFileState(file.id, {
            s3Multipart: Object.assign({}, cFile.s3Multipart, {
              parts: [
                ...cFile.s3Multipart.parts,
                part
              ]
            })
          })

          this.uppy.emit('s3-multipart:part-uploaded', cFile, part)
        }
      }, file.s3Multipart))

      this.uppy.on('file-removed', (removed) => {
        if (file.id !== removed.id) return
        upload.abort({ really: true })
        resolve(`upload ${removed.id} was removed`)
      })

      this.uppy.on('upload-pause', (pausee, isPaused) => {
        if (pausee.id !== file.id) return
        if (isPaused) {
          upload.pause()
        } else {
          upload.start()
        }
      })

      this.uppy.on('pause-all', () => {
        upload.pause()
      })

      this.uppy.on('cancel-all', () => {
        upload.abort({ really: true })
      })

      this.uppy.on('resume-all', () => {
        upload.start()
      })

      if (!file.isPaused) {
        upload.start()
      }

      if (!file.isRestored) {
        this.uppy.emit('upload-started', file, upload)
      }
    })
  }

  upload (fileIDs) {
    if (fileIDs.length === 0) return Promise.resolve()

    const promises = fileIDs.map((id) => {
      const file = this.uppy.getFile(id)
      if (file.isRemote) {
        return Promise.reject(new Error('AwsS3/Multipart: Remote file uploads are not currently supported.'))
      }
      return this.uploadFile(file)
    })

    return Promise.all(promises)
  }

  addResumableUploadsCapabilityFlag () {
    this.uppy.setState({
      capabilities: Object.assign({}, this.uppy.getState().capabilities, {
        resumableUploads: true
      })
    })
  }

  install () {
    this.addResumableUploadsCapabilityFlag()
    this.uppy.addUploader(this.upload)
  }

  uninstall () {
    this.uppy.removeUploader(this.upload)
  }
}
<commit_msg>s3: Begin work on remote Multipart uploads<commit_after>const Plugin = require('../../core/Plugin')
const UppySocket = require('../../core/UppySocket')
const {
  emitSocketProgress,
  getSocketHost,
  limitPromises
} = require('../../core/Utils')
const Uploader = require('./MultipartUploader')

function handleResponse (response) {
  if (response.ok) return response.json()
  return response.json().then((err) => {
    throw err
  })
}

/**
 * Create a wrapper around an event emitter with a `remove` method to remove
 * all events that were added using the wrapped emitter.
 */
function createEventTracker (emitter) {
  const events = []
  return {
    on (event, fn) {
      events.push([ event, fn ])
      return emitter.on(event, fn)
    },
    remove () {
      events.forEach(([ event, fn ]) => {
        emitter.off(event, fn)
      })
    }
  }
}

module.exports = class AwsS3Multipart extends Plugin {
  constructor (uppy, opts) {
    super(uppy, opts)
    this.type = 'uploader'
    this.id = 'AwsS3Multipart'
    this.title = 'AWS S3 Multipart'

    const defaultOptions = {
      timeout: 30 * 1000,
      limit: 0,
      createMultipartUpload: this.createMultipartUpload.bind(this),
      listParts: this.listParts.bind(this),
      prepareUploadPart: this.prepareUploadPart.bind(this),
      abortMultipartUpload: this.abortMultipartUpload.bind(this),
      completeMultipartUpload: this.completeMultipartUpload.bind(this)
    }

    this.opts = Object.assign({}, defaultOptions, opts)

    this.upload = this.upload.bind(this)

    if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) {
      this.limitRequests = limitPromises(this.opts.limit)
    } else {
      this.limitRequests = (fn) => fn
    }

    this.uploaders = Object.create(null)
    this.uploaderEvents = Object.create(null)
    this.uploaderSockets = Object.create(null)
  }

  /**
   * Clean up all references for a file's upload: the MultipartUploader instance,
   * any events related to the file, and the uppy-server WebSocket connection.
   */
  resetUploaderReferences (fileID) {
    if (this.uploaders[fileID]) {
      this.uploaders[fileID].abort()
      this.uploaders[fileID] = null
    }
    if (this.uploaderEvents[fileID]) {
      this.uploaderEvents[fileID].remove()
      this.uploaderEvents[fileID] = null
    }
    if (this.uploaderSockets[fileID]) {
      this.uploaderSockets[fileID].close()
      this.uploaderSockets[fileID] = null
    }
  }

  assertHost () {
    if (!this.opts.host) {
      throw new Error('Expected a `host` option containing an uppy-server address.')
    }
  }

  createMultipartUpload (file) {
    this.assertHost()

    const filename = encodeURIComponent(file.name)
    const type = encodeURIComponent(file.type)
    return fetch(`${this.opts.host}/s3/multipart?filename=${filename}&type=${type}`, {
      method: 'post',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  listParts (file, { key, uploadId }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    return fetch(`${this.opts.host}/s3/multipart/${uploadId}?key=${filename}`, {
      method: 'get',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  prepareUploadPart (file, { key, uploadId, number }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    return fetch(`${this.opts.host}/s3/multipart/${uploadId}/${number}?key=${filename}`, {
      method: 'get',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  completeMultipartUpload (file, { key, uploadId, parts }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    const uploadIdEnc = encodeURIComponent(uploadId)
    return fetch(`${this.opts.host}/s3/multipart/${uploadIdEnc}/complete?key=${filename}`, {
      method: 'post',
      headers: {
        accept: 'application/json',
        'content-type': 'application/json'
      },
      body: JSON.stringify({ parts })
    }).then(handleResponse)
  }

  abortMultipartUpload (file, { key, uploadId }) {
    this.assertHost()

    const filename = encodeURIComponent(key)
    const uploadIdEnc = encodeURIComponent(uploadId)
    return fetch(`${this.opts.host}/s3/multipart/${uploadIdEnc}?key=${filename}`, {
      method: 'delete',
      headers: { accept: 'application/json' }
    }).then(handleResponse)
  }

  uploadFile (file) {
    return new Promise((resolve, reject) => {
      const upload = new Uploader(file.data, Object.assign({
        // .bind to pass the file object to each handler.
        createMultipartUpload: this.limitRequests(this.opts.createMultipartUpload.bind(this, file)),
        listParts: this.limitRequests(this.opts.listParts.bind(this, file)),
        prepareUploadPart: this.opts.prepareUploadPart.bind(this, file),
        completeMultipartUpload: this.limitRequests(this.opts.completeMultipartUpload.bind(this, file)),
        abortMultipartUpload: this.limitRequests(this.opts.abortMultipartUpload.bind(this, file)),

        limit: this.opts.limit || 5,
        onStart: (data) => {
          const cFile = this.uppy.getFile(file.id)
          this.uppy.setFileState(file.id, {
            s3Multipart: Object.assign({}, cFile.s3Multipart, {
              key: data.key,
              uploadId: data.uploadId,
              parts: []
            })
          })
        },
        onProgress: (bytesUploaded, bytesTotal) => {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: bytesUploaded,
            bytesTotal: bytesTotal
          })
        },
        onError: (err) => {
          this.uppy.log(err)
          this.uppy.emit('upload-error', file, err)
          err.message = `Failed because: ${err.message}`

          this.resetUploaderReferences(file.id)
          reject(err)
        },
        onSuccess: (result) => {
          this.uppy.emit('upload-success', file, upload, result.location)

          if (result.location) {
            this.uppy.log('Download ' + upload.file.name + ' from ' + result.location)
          }

          this.resetUploaderReferences(file.id)
          resolve(upload)
        },
        onPartComplete: (part) => {
          // Store completed parts in state.
          const cFile = this.uppy.getFile(file.id)
          this.uppy.setFileState(file.id, {
            s3Multipart: Object.assign({}, cFile.s3Multipart, {
              parts: [
                ...cFile.s3Multipart.parts,
                part
              ]
            })
          })

          this.uppy.emit('s3-multipart:part-uploaded', cFile, part)
        }
      }, file.s3Multipart))

      this.uploaders[file.id] = upload
      this.uploaderEvents[file.id] = createEventTracker(this.uppy)

      this.onFileRemove(file.id, (removed) => {
        this.resetUploaderReferences(file.id)
        resolve(`upload ${removed.id} was removed`)
      })

      this.onFilePause(file.id, (isPaused) => {
        if (isPaused) {
          upload.pause()
        } else {
          upload.start()
        }
      })

      this.onPauseAll(file.id, () => {
        upload.pause()
      })

      this.onCancelAll(file.id, () => {
        upload.abort({ really: true })
      })

      this.onResumeAll(file.id, () => {
        upload.start()
      })

      if (!file.isPaused) {
        upload.start()
      }

      if (!file.isRestored) {
        this.uppy.emit('upload-started', file, upload)
      }
    })
  }

  uploadRemote (file) {
    this.resetUploaderReferences(file.id)

    return new Promise((resolve, reject) => {
      if (file.serverToken) {
        return this.connectToServerSocket(file)
          .then(() => resolve())
          .catch(reject)
      }

      this.uppy.emit('upload-started', file)

      fetch(file.remote.url, {
        method: 'post',
        credentials: 'include',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(Object.assign({}, file.remote.body, {
          protocol: 's3-multipart',
          size: file.data.size,
          metadata: file.meta
        }))
      })
      .then((res) => {
        if (res.status < 200 || res.status > 300) {
          return reject(res.statusText)
        }

        return res.json().then((data) => {
          this.uppy.setFileState(file.id, { serverToken: data.token })
          return this.uppy.getFile(file.id)
        })
      })
      .then((file) => {
        return this.connectToServerSocket(file)
      })
      .then(() => {
        resolve()
      })
      .catch((err) => {
        reject(new Error(err))
      })
    })
  }

  connectToServerSocket (file) {
    return new Promise((resolve, reject) => {
      const token = file.serverToken
      const host = getSocketHost(file.remote.host)
      const socket = new UppySocket({ target: `${host}/api/${token}` })
      this.uploaderSockets[socket] = socket
      this.uploaderEvents[file.id] = createEventTracker(this.uppy)

      this.onFileRemove(file.id, (removed) => {
        socket.send('pause', {})
        resolve(`upload ${file.id} was removed`)
      })

      this.onFilePause(file.id, (isPaused) => {
        socket.send(isPaused ? 'pause' : 'resume', {})
      })

      this.onPauseAll(file.id, () => socket.send('pause', {}))

      this.onCancelAll(file.id, () => socket.send('pause', {}))

      this.onResumeAll(file.id, () => {
        if (file.error) {
          socket.send('pause', {})
        }
        socket.send('resume', {})
      })

      this.onRetry(file.id, () => {
        socket.send('pause', {})
        socket.send('resume', {})
      })

      this.onRetryAll(file.id, () => {
        socket.send('pause', {})
        socket.send('resume', {})
      })

      if (file.isPaused) {
        socket.send('pause', {})
      }

      socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))

      socket.on('error', (errData) => {
        this.uppy.emit('upload-error', file, new Error(errData.error))
        reject(new Error(errData.error))
      })

      socket.on('success', (data) => {
        this.uppy.emit('upload-success', file, data, data.url)
        resolve()
      })
    })
  }

  upload (fileIDs) {
    if (fileIDs.length === 0) return Promise.resolve()

    const promises = fileIDs.map((id) => {
      const file = this.uppy.getFile(id)
      if (file.isRemote) {
        return this.uploadRemote(file)
      }
      return this.uploadFile(file)
    })

    return Promise.all(promises)
  }

  addResumableUploadsCapabilityFlag () {
    this.uppy.setState({
      capabilities: Object.assign({}, this.uppy.getState().capabilities, {
        resumableUploads: true
      })
    })
  }

  onFileRemove (fileID, cb) {
    this.uploaderEvents[fileID].on('file-removed', (file) => {
      if (fileID === file.id) cb(file.id)
    })
  }

  onFilePause (fileID, cb) {
    this.uploaderEvents[fileID].on('upload-pause', (targetFileID, isPaused) => {
      if (fileID === targetFileID) {
        // const isPaused = this.uppy.pauseResume(fileID)
        cb(isPaused)
      }
    })
  }

  onRetry (fileID, cb) {
    this.uploaderEvents[fileID].on('upload-retry', (targetFileID) => {
      if (fileID === targetFileID) {
        cb()
      }
    })
  }

  onRetryAll (fileID, cb) {
    this.uploaderEvents[fileID].on('retry-all', (filesToRetry) => {
      if (!this.uppy.getFile(fileID)) return
      cb()
    })
  }

  onPauseAll (fileID, cb) {
    this.uploaderEvents[fileID].on('pause-all', () => {
      if (!this.uppy.getFile(fileID)) return
      cb()
    })
  }

  onCancelAll (fileID, cb) {
    this.uploaderEvents[fileID].on('cancel-all', () => {
      if (!this.uppy.getFile(fileID)) return
      cb()
    })
  }

  onResumeAll (fileID, cb) {
    this.uploaderEvents[fileID].on('resume-all', () => {
      if (!this.uppy.getFile(fileID)) return
      cb()
    })
  }

  install () {
    this.addResumableUploadsCapabilityFlag()
    this.uppy.addUploader(this.upload)
  }

  uninstall () {
    this.uppy.removeUploader(this.upload)
  }
}
<|endoftext|>


<commit_before>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
//import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as NeuralWorker_Proxy } from "./NeuralWorker_Proxy.js";
import { Mode as NeuralWorker_Mode } from "./NeuralWorker_Mode.js";

//!!! ...unfinished... (2022/05/25)
// Perhaps, Using SharedArrayBuffer to pass input image between different web worker.
//

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 *
 * 1. For NeuralWorker_Mode.Singleton.Ids.ONE_WORKER__Xxx
 *
 * Since it is slow to transfer data between CPU and GPU (for WebGL), it seems not
 * feasible to use multiple workers (which will transfer data between CPU and GPU
 * multiple times) to process both competition side of an diffential evolution
 * iteration.
 *
 * So these ONE_WORKER__Xxx mode combine two neural networks of both competition
 * sides in one web worker. Although they compute serially (not parallelly), they
 * still may be faster than compute them in two worker because the reduced memory
 * transferring between CPU and GPU.
 *
 *
 * 2. For NeuralWorker_Mode.Singleton.Ids.TWO_WORKER__Xxx
 *
 * Every worker handles one neural network. When .ImageData_process_async() is called,
 * the input (usually a large memory block) will be transffered to the 1st worker to
 * start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same time. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' results in a promise. The
 * promise will resolve with an array of Float32Array. Every Float32Array is the
 * output of one neural network.
 *
 *
 *
 * @member {string} weightsSpreadsheetId
 *   The Google Sheets spreadsheetId of neural network weights. Every worker will
 * load weights from the spreadsheet to initialize one neural network.
 *
 * @member {string} weightsAPIKey
 *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
 *   - If null, Google Visualization Table Query API will be used.
 *   - If not null, Google Sheets API v4 will be used.
 *
 * @member {numner} nNeuralWorker_ModeId
 *   The numeric identifier of neural worker mode (i.e.
 * NeuralWorker.Mode.Singleton.Ids.Xxx).
 *
 * @member {number} neuralNetCount
 *   There are how many neural networks created. It is always 2 (because of differential
 * evolution) no matter how totalWorkerCount is.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusSummary} evolutionVersusSummary
 *   The range list of all differential evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    if ( this.evolutionVersusSummary ) {
      this.evolutionVersusSummary.disposeResources_and_recycleToPool();
      this.evolutionVersusSummary = null;
    }

    this.disposeWorkers();

    this.hardwareConcurrency = undefined;
    this.neuralNetCount = undefined;

    this.nNeuralWorker_ModeId = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   *
   */
   disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, nNeuralWorker_ModeId ) {

    // 0.
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.nNeuralWorker_ModeId = nNeuralWorker_ModeId;

    this.neuralNetCount = 2; // Always two neural network (for differential evolution).
    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //
    // Note1: How could the two neural networks determine all the actions of so many
    //        game objects? The method is to let the output of one neural network
    //        contains all actions of all game objects.
    //
    // Note2: For NeuralWorker_Mode whose ( bFill == false ), only half of the output
    //        actions (i.e. actions of one alignment in the game world) will be used
    //        in one time.
    //
    let totalWorkerCount = NeuralWorker_Mode.workerCount_get( nNeuralWorker_ModeId );

    // 1. Create web workers.
    let initOkArray;
    {
      // Prepare container of all worker proxy.
      {
        if ( this.workerProxyArray )
          this.workerProxyArray.clear();
        else
          this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();

        this.workerProxyArray.length = totalWorkerCount;
      }

      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2. Summary.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    // 3.
    NeuralWorker_Proxies.setup_apply.call( this );

    return initOk;
  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    if ( weightArrayBufferArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

  /**
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. It will be scaled to the correct shape
   * before passed into the neural network.
   *
   * @return {Promise}
   *   Return a promise resolved to an array of Float32Array representing the neural
   * networks' result.
   */
  ImageData_process_async( sourceImageData ) {
    return this.apply_async( sourceImageData );
  }

  /**
   * Setup .apply_async according to .nNeuralWorker_ModeId.
   *
   * @param {NeuralWorker_Proxies} this
   */
  static setup_apply() {
    switch ( this.nNeuralWorker_ModeId ) {
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__FILL: // (0)
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__NO_FILL: // (1)
        this.apply_async
          = NeuralWorker_Proxies.apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__FILL: // (2)
      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__NO_FILL: // (3)
        this.apply_async
          = NeuralWorker_Proxies.apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__TWO_SCALE__NO_FILL: // (4)
        this.apply_async = NeuralWorker_Proxies.apply__TWO_WORKER__TWO_SCALE__NO_FILL;
        break;

      default:
        throw Error( `NeuralWorker_Proxies.setup_apply(): `
          + `Unknown nNeuralWorker_ModeId ( ${nNeuralWorker_ModeId} ).`
        );
        break;
    }
  }

  /** */
  static async apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_promise = this.workerProxyArray[ 0 ]
      .ImageData_scale_once_process_multiple_async( sourceImageData, bFill );

    let worker0_value_Float32ArrayArray = await worker0_promise;
    return worker0_value_Float32ArrayArray;
  }

  /** */
  static async apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_fork_fillable_process_asyncGenerator( sourceImageData, bFill );

    let { done: worker0_done_false, value: worker0_value_Int32Array }
      = await worker0_resulter.next();

    let worker1_promise = this.workerProxyArray[ 1 ]
      .Int32Array_fillable_process_async( worker0_value_Int32Array, bFill );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      worker1_value_Float32Array
    ] = await Promise.all( [ worker0_resulter.next(), worker1_promise ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  /** */
  static async apply__TWO_WORKER__TWO_SCALE__NO_FILL( sourceImageData ) {
    const worker0_bFork = true;
    const worker1_bFork = false;

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        sourceImageData, worker0_bFork );

    let { done: worker0_done_false, value: worker0_value_ImageData }
      = await worker0_resulter.next();

    let worker1_resulter = this.workerProxyArray[ 1 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        worker0_value_ImageData, worker1_bFork );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      { done: worker1_done_true, value: worker1_value_Float32Array },
    ] = await Promise.all( [ worker0_resulter.next(), worker1_resulter.next() ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

//!!! ...unfinished... (2022/09/21)

  /** Load all differential evolution versus weights ranges. */
  async evolutionVersusSummary_load_async() {

    if ( !this.evolutionVersusSummary ) {
      this.evolutionVersusSummary = DEvolution.VersusSummary.Pool.get_or_create_by(
        this.weightsSpreadsheetId, this.weightsAPIKey );
    }

    this.evolutionVersusSummary.rangeArray_load_async();

//!!! ...unfinished... (2022/08/27)

  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}

<commit_msg>evolutionVersusSummary_load_async<commit_after>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
//import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as NeuralWorker_Proxy } from "./NeuralWorker_Proxy.js";
import { Mode as NeuralWorker_Mode } from "./NeuralWorker_Mode.js";

//!!! ...unfinished... (2022/05/25)
// Perhaps, Using SharedArrayBuffer to pass input image between different web worker.
//

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 *
 * 1. For NeuralWorker_Mode.Singleton.Ids.ONE_WORKER__Xxx
 *
 * Since it is slow to transfer data between CPU and GPU (for WebGL), it seems not
 * feasible to use multiple workers (which will transfer data between CPU and GPU
 * multiple times) to process both competition side of an diffential evolution
 * iteration.
 *
 * So these ONE_WORKER__Xxx mode combine two neural networks of both competition
 * sides in one web worker. Although they compute serially (not parallelly), they
 * still may be faster than compute them in two worker because the reduced memory
 * transferring between CPU and GPU.
 *
 *
 * 2. For NeuralWorker_Mode.Singleton.Ids.TWO_WORKER__Xxx
 *
 * Every worker handles one neural network. When .ImageData_process_async() is called,
 * the input (usually a large memory block) will be transffered to the 1st worker to
 * start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same time. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' results in a promise. The
 * promise will resolve with an array of Float32Array. Every Float32Array is the
 * output of one neural network.
 *
 *
 *
 * @member {string} weightsSpreadsheetId
 *   The Google Sheets spreadsheetId of neural network weights. Every worker will
 * load weights from the spreadsheet to initialize one neural network.
 *
 * @member {string} weightsAPIKey
 *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
 *   - If null, Google Visualization Table Query API will be used.
 *   - If not null, Google Sheets API v4 will be used.
 *
 * @member {numner} nNeuralWorker_ModeId
 *   The numeric identifier of neural worker mode (i.e.
 * NeuralWorker.Mode.Singleton.Ids.Xxx).
 *
 * @member {number} neuralNetCount
 *   There are how many neural networks created. It is always 2 (because of differential
 * evolution) no matter how totalWorkerCount is.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusSummary} evolutionVersusSummary
 *   The range list of all differential evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    if ( this.evolutionVersusSummary ) {
      this.evolutionVersusSummary.disposeResources_and_recycleToPool();
      this.evolutionVersusSummary = null;
    }

    this.disposeWorkers();

    this.hardwareConcurrency = undefined;
    this.neuralNetCount = undefined;

    this.nNeuralWorker_ModeId = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   *
   */
   disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, nNeuralWorker_ModeId ) {

    // 0.
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.nNeuralWorker_ModeId = nNeuralWorker_ModeId;

    this.neuralNetCount = 2; // Always two neural network (for differential evolution).
    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //
    // Note1: How could the two neural networks determine all the actions of so many
    //        game objects? The method is to let the output of one neural network
    //        contains all actions of all game objects.
    //
    // Note2: For NeuralWorker_Mode whose ( bFill == false ), only half of the output
    //        actions (i.e. actions of one alignment in the game world) will be used
    //        in one time.
    //
    let totalWorkerCount = NeuralWorker_Mode.workerCount_get( nNeuralWorker_ModeId );

    // 1. Create web workers.
    let initOkArray;
    {
      // Prepare container of all worker proxy.
      {
        if ( this.workerProxyArray )
          this.workerProxyArray.clear();
        else
          this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();

        this.workerProxyArray.length = totalWorkerCount;
      }

      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2. Summary.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    // 3.
    NeuralWorker_Proxies.setup_apply.call( this );

    return initOk;
  }

//!!! ...unfinished... (2022/09/21)

  /** Load all differential evolution versus weights ranges. */
  async evolutionVersusSummary_load_async() {

    if ( !this.evolutionVersusSummary ) {
      this.evolutionVersusSummary = DEvolution.VersusSummary.Pool.get_or_create_by(
        this.weightsSpreadsheetId, this.weightsAPIKey );
    }

    this.evolutionVersusSummary.rangeArray_load_async();

//!!! ...unfinished... (2022/08/27)

  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    if ( weightArrayBufferArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

  /**
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. It will be scaled to the correct shape
   * before passed into the neural network.
   *
   * @return {Promise}
   *   Return a promise resolved to an array of Float32Array representing the neural
   * networks' result.
   */
  ImageData_process_async( sourceImageData ) {
    return this.apply_async( sourceImageData );
  }

  /**
   * Setup .apply_async according to .nNeuralWorker_ModeId.
   *
   * @param {NeuralWorker_Proxies} this
   */
  static setup_apply() {
    switch ( this.nNeuralWorker_ModeId ) {
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__FILL: // (0)
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__NO_FILL: // (1)
        this.apply_async
          = NeuralWorker_Proxies.apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__FILL: // (2)
      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__NO_FILL: // (3)
        this.apply_async
          = NeuralWorker_Proxies.apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__TWO_SCALE__NO_FILL: // (4)
        this.apply_async = NeuralWorker_Proxies.apply__TWO_WORKER__TWO_SCALE__NO_FILL;
        break;

      default:
        throw Error( `NeuralWorker_Proxies.setup_apply(): `
          + `Unknown nNeuralWorker_ModeId ( ${nNeuralWorker_ModeId} ).`
        );
        break;
    }
  }

  /** */
  static async apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_promise = this.workerProxyArray[ 0 ]
      .ImageData_scale_once_process_multiple_async( sourceImageData, bFill );

    let worker0_value_Float32ArrayArray = await worker0_promise;
    return worker0_value_Float32ArrayArray;
  }

  /** */
  static async apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_fork_fillable_process_asyncGenerator( sourceImageData, bFill );

    let { done: worker0_done_false, value: worker0_value_Int32Array }
      = await worker0_resulter.next();

    let worker1_promise = this.workerProxyArray[ 1 ]
      .Int32Array_fillable_process_async( worker0_value_Int32Array, bFill );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      worker1_value_Float32Array
    ] = await Promise.all( [ worker0_resulter.next(), worker1_promise ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  /** */
  static async apply__TWO_WORKER__TWO_SCALE__NO_FILL( sourceImageData ) {
    const worker0_bFork = true;
    const worker1_bFork = false;

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        sourceImageData, worker0_bFork );

    let { done: worker0_done_false, value: worker0_value_ImageData }
      = await worker0_resulter.next();

    let worker1_resulter = this.workerProxyArray[ 1 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        worker0_value_ImageData, worker1_bFork );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      { done: worker1_done_true, value: worker1_value_Float32Array },
    ] = await Promise.all( [ worker0_resulter.next(), worker1_resulter.next() ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}

<|endoftext|>


<commit_before>import { isSSR, hasWebStorage } from './platform.js'
import { getEmptyStorage, getStorage } from '../utils/web-storage.js'

export default {
  install ({ $q }) {
    const storage = isSSR || !hasWebStorage
      ? getEmptyStorage()
      : getStorage('session')

    $q.localStorage = storage
    Object.assign(this, storage)
  }
}
<commit_msg>SessionStorage: fix wrong name (#2584)<commit_after>import { isSSR, hasWebStorage } from './platform.js'
import { getEmptyStorage, getStorage } from '../utils/web-storage.js'

export default {
  install ({ $q }) {
    const storage = isSSR || !hasWebStorage
      ? getEmptyStorage()
      : getStorage('session')

    $q.sessionStorage = storage
    Object.assign(this, storage)
  }
}
<|endoftext|>


<commit_before>"use strict";
var winnerPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("this is winner");
        resolve("this is winner");
    }, 4);
});
var loserPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("this is loser");
        resolve("this is loser");
    }, 1000);
});

// 一番最初のものがresolveされた時点で終了
Promise.race([winnerPromise, loserPromise]).then(function (value) {
    console.log(value); // => 1
});
<commit_msg>fix(promise-race): 評価される値のコメントがおかしいのを修正<commit_after>"use strict";
var winnerPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("this is winner");
        resolve("this is winner");
    }, 4);
});
var loserPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("this is loser");
        resolve("this is loser");
    }, 1000);
});

// 一番最初のものがresolveされた時点で終了
Promise.race([winnerPromise, loserPromise]).then(function (value) {
    console.log(value); // => 'this is winner'
});
<|endoftext|>