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

  function lifxConfig(n) {
    RED.nodes.createNode(this, n);
    this.name = n.name;
    this.token = n.token;
  }
  RED.nodes.registerType("lifx-config", lifxConfig);

  function defaultTo(val, def) {
    return typeof val != "undefined" ? val : def != "" ? def : null;
  }

  function defaultToNumber(val, def) {
    return Number(defaultTo(val, def))
  }

  function defaultToBoolean(val, def) {
    var tmp = defaultTo(val, def)
    return typeof tmp === "boolean"
      ? tmp
      : typeof tmp === "string"
        ? tmp.toLowerCase() === "true"
        : null
  }

  function togglePower(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      lifx.togglePower(selector, 0, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-toggle-power", togglePower);

  function pulseEffect(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;
    this.color = config.color;
    this.from_color = config.from_color;
    this.period = config.period;
    this.cycles = config.cycles;
    this.persist = config.persist;
    this.power_on = config.power_on;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        color: defaultTo(msg.payload.color, this.color),
        from_color: defaultTo(msg.payload.from_color, this.from_color),
        period: defaultToNumber(msg.payload.period, this.period),
        cycles: defaultToNumber(msg.payload.cycles, this.cycles),
        persist: defaultToBoolean(msg.payload.persist, this.persist),
        power_on: defaultToBoolean(msg.payload.power_on, this.power_on)
      };
      lifx.pulse(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-pulse-effect", pulseEffect);

  function breatheEffect(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;
    this.color = config.color;
    this.from_color = config.from_color;
    this.period = config.period;
    this.cycles = config.cycles;
    this.persist = config.persist;
    this.power_on = config.power_on;
    this.peak = config.peak;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        color: defaultTo(msg.payload.color, this.color),
        from_color: defaultTo(msg.payload.from_color, this.from_color),
        period: defaultToNumber(msg.payload.period, this.period),
        cycles: defaultToNumber(msg.payload.cycles, this.cycles),
        persist: defaultToBoolean(msg.payload.persist, this.persist),
        power_on: defaultToBoolean(msg.payload.power_on, this.power_on),
        peak: defaultToNumber(msg.payload.peak, this.peak)
      };
      lifx.breathe(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-breathe-effect", breatheEffect);

  function listLights(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      lifx.listLights(selector, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-list-lights", listLights);

  function setState(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.power = config.power;
    this.color = config.color;
    this.brightness = config.brightness;
    this.duration = config.duration;
    this.infrared = config.infrared;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        power: defaultTo(msg.payload.power, this.power),
        color: defaultTo(msg.payload.color, this.color),
        brightness: defaultToNumber(msg.payload.brightness, this.brightness),
        duration: defaultToNumber(msg.payload.duration, this.duration),
        infrared: defaultToNumber(msg.payload.infrared, this.infrared)
      };
      lifx.setState(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-set-state", setState);

  function setStates(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      var states = defaultTo(msg.payload, {});
      lifx.setStates(states, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-set-states", setStates);

  function cycle(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.selector = config.selector;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var states = msg.payload;
      lifx.cycle(selector, states, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-cycle", cycle);

  function listScenes(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      lifx.listScenes(function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-list-scenes", listScenes);

  function activateScene(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.duration = config.duration;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var duration = defaultTo(msg.payload.duration, this.duration);
      lifx.setState(selector, duration, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-activate-scene", activateScene);

  function validateColor(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.color = config.color;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var color = defaultTo(msg.payload.color, this.color);
      lifx.validateColor(color, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-validate-color", validateColor);
};
<commit_msg>Refactor of activateScene<commit_after>/**
 * Copyright 2016 Alisdair Smyth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
module.exports = function(RED) {
  "use strict";
  var lifxObj = require("lifx-http-api");

  function lifxConfig(n) {
    RED.nodes.createNode(this, n);
    this.name = n.name;
    this.token = n.token;
  }
  RED.nodes.registerType("lifx-config", lifxConfig);

  function defaultTo(val, def) {
    return typeof val != "undefined" ? val : def != "" ? def : null;
  }

  function defaultToNumber(val, def) {
    return Number(defaultTo(val, def))
  }

  function defaultToBoolean(val, def) {
    var tmp = defaultTo(val, def)
    return typeof tmp === "boolean"
      ? tmp
      : typeof tmp === "string"
        ? tmp.toLowerCase() === "true"
        : null
  }

  function togglePower(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      lifx.togglePower(selector, 0, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-toggle-power", togglePower);

  function pulseEffect(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;
    this.color = config.color;
    this.from_color = config.from_color;
    this.period = config.period;
    this.cycles = config.cycles;
    this.persist = config.persist;
    this.power_on = config.power_on;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        color: defaultTo(msg.payload.color, this.color),
        from_color: defaultTo(msg.payload.from_color, this.from_color),
        period: defaultToNumber(msg.payload.period, this.period),
        cycles: defaultToNumber(msg.payload.cycles, this.cycles),
        persist: defaultToBoolean(msg.payload.persist, this.persist),
        power_on: defaultToBoolean(msg.payload.power_on, this.power_on)
      };
      lifx.pulse(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-pulse-effect", pulseEffect);

  function breatheEffect(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;
    this.color = config.color;
    this.from_color = config.from_color;
    this.period = config.period;
    this.cycles = config.cycles;
    this.persist = config.persist;
    this.power_on = config.power_on;
    this.peak = config.peak;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        color: defaultTo(msg.payload.color, this.color),
        from_color: defaultTo(msg.payload.from_color, this.from_color),
        period: defaultToNumber(msg.payload.period, this.period),
        cycles: defaultToNumber(msg.payload.cycles, this.cycles),
        persist: defaultToBoolean(msg.payload.persist, this.persist),
        power_on: defaultToBoolean(msg.payload.power_on, this.power_on),
        peak: defaultToNumber(msg.payload.peak, this.peak)
      };
      lifx.breathe(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-breathe-effect", breatheEffect);

  function listLights(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      lifx.listLights(selector, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-list-lights", listLights);

  function setState(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.power = config.power;
    this.color = config.color;
    this.brightness = config.brightness;
    this.duration = config.duration;
    this.infrared = config.infrared;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var settings = {
        power: defaultTo(msg.payload.power, this.power),
        color: defaultTo(msg.payload.color, this.color),
        brightness: defaultToNumber(msg.payload.brightness, this.brightness),
        duration: defaultToNumber(msg.payload.duration, this.duration),
        infrared: defaultToNumber(msg.payload.infrared, this.infrared)
      };
      lifx.setState(selector, settings, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-set-state", setState);

  function setStates(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      var states = defaultTo(msg.payload, {});
      lifx.setStates(states, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-set-states", setStates);

  function cycle(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.selector = config.selector;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var states = msg.payload;
      lifx.cycle(selector, states, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
      });
    });
  }
  RED.nodes.registerType("lifx-cycle", cycle);

  function listScenes(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      lifx.listScenes(function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-list-scenes", listScenes);

  function activateScene(config) {
    RED.nodes.createNode(this, config);
    this.selector = config.selector;

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.duration = config.duration;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var selector = defaultTo(msg.payload.selector, this.selector);
      var duration = defaultToNumber(msg.payload.duration, this.duration);
      lifx.setState(selector, duration, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-activate-scene", activateScene);

  function validateColor(config) {
    RED.nodes.createNode(this, config);

    // Retrieve the config node
    this.api = RED.nodes.getNode(config.api);
    this.color = config.color;

    var lifx = new lifxObj({ bearerToken: this.api.token });
    var node = this;
    this.on("input", function(msg) {
      msg.payload = defaultTo(msg.payload, {});
      var color = defaultTo(msg.payload.color, this.color);
      lifx.validateColor(color, function(err, data) {
        if (err) {
          node.error(err);
          return;
        }
        msg.payload = data;
        node.send(msg);
      });
    });
  }
  RED.nodes.registerType("lifx-validate-color", validateColor);
};
<|endoftext|>


<commit_before>var renameDialog = {
  _dialog_skel: undefined,

  initialize: function() {
    $.get(chrome.extension.getURL("templates/rename-dialog.html"), function(skeleton) {
      renameDialog._dialog_skel = skeleton;
    });
  },

  populate: function(listobj, callback) {
    $dialog = $(this._dialog_skel);
    this._set_dialog_events($dialog);
    this.refresh_pattern($dialog);

    trello_find_list_by_obj(listobj, function () {
      selection = this.cards.map(function (card) {
        return mk_option(card.name, card.id);
      });

      preview = this.cards.map(function(card) {
        return mk_option(card.name, card.id, "card-" + card.id);
      });
      $dialog.find('#rd-selection').html(selection);
      $dialog.find('#rd-preview').html(preview);

      callback.apply($dialog);
    });
  },

  update_preview: function(regex, replace) {
    $('#rd-selection option').each(function(i) {
      $pcard   = $($('#card-' + $(this).val()))
      $pcard.removeClass("ma-bold");
      $pcard.text($(this).text());
    });

    try {
      var re = new RegExp($(regex).val());
    } catch(e) {
      $(regex).css("background-color", "#de9a8f");
      $(replace).css("background-color", "#de9a8f");
      return false;
    }

    $(replace).css("background-color", "#96d48a");
    $(regex).css("background-color", "#96d48a");

    $('#rd-selection option:checked').each(function(i) {
      pcard   = $('#card-' + $(this).val())
      preview = renameDialog._replace(i, $(this).text(), re, $(replace).val());

      pcard.addClass("ma-bold");
      pcard.text(preview);
    });
  },

  apply_changes: function(regex, replace, callback) {
    $('#rd-selection option:checked').each(function(i) {
      re       = RegExp(regex);
      new_name = renameDialog._replace(i, $(this).text(), re, replace);

      Trello.put("cards/" + $(this).val() + "/name", {value: new_name});
    }).promise().done(function () {
      callback();
    });
  },

  show: function(list) {
    this.populate(list, function() {
      $('.pop-over').html(this);
      $('.pop-over').css({
        left: "10%",
        top: "10%",
        width: "80%"
      });
    });
  },

  hide: function() {
    $('.pop-over').empty().toggleClass('is-shown');
  },

  refresh_pattern: function($dialog) {
    patterns = patternDialog.patterns();

    $select = $dialog.find('#rd-select-pattern');
    $select.find('option').remove();

    options = Object.keys(patterns).map(function(name) {
      text = name + ' <i>(' + patterns[name].regex + ')';
      return mk_option(text, name);
    });
    $select.html(options);
  },

  _replace: function(index, text, regex, replace) {
      repl = replace.replace(/\$i/, index + 1);
      return text.replace(RegExp(regex), repl);
  },

  _set_dialog_events: function($dialog) {
    // make scrollbars synchron
    $dialog.find('#rd-selection').scroll(function(){
      $('#rd-preview').scrollTop($(this).scrollTop());
    });
    $dialog.find('#rd-preview').scroll(function(){
      $('#rd-selection').scrollTop($(this).scrollTop());
    });

    // apply changes
    $dialog.find(".rd-apply").click(function () {
      replace = $dialog.find('#rd-replace').val();
      regex   = $dialog.find('#rd-regex').val();
      renameDialog.apply_changes(regex, replace, function() {
        renameDialog.hide();
      });
    });

    // update preview
    $dialog.find('#rd-selection').change(function() {
      $dialog.find('#rd-replace').trigger('keyup');
    });
    $dialog.find('#rd-regex').keyup(function() {
      $dialog.find('#rd-replace').trigger('keyup');
    });

    $dialog.find('#rd-replace').keyup(function() {
      replace = $dialog.find('#rd-replace');
      regex   = $dialog.find('#rd-regex');
      renameDialog.update_preview(regex, replace);
    });

    $(document).on('click', '#rd-select-pattern', function() {
      name = $(this).val();
      pattern = patternDialog.pattern(name);

      if (pattern) {
        $dialog.find('#rd-replace').val(pattern.replace);
        $dialog.find('#rd-regex').val(pattern.regex);
        $dialog.find('#rd-replace').trigger('keyup');
      }
    });

    $(document).on('click', '.pd-delete', function() {
      name = $dialog.find('#rd-select-pattern').val();
      patternDialog.delete(name);
      renameDialog.refresh_pattern($dialog);
    });
  }
}
<commit_msg>clean rename dialog (adding const and strict)<commit_after>"use strict";

const renameDialog = {
  _dialog_skel: undefined,

  initialize: function() {
    $.get(chrome.extension.getURL("templates/rename-dialog.html"), function(skeleton) {
      renameDialog._dialog_skel = skeleton;
    });
  },

  populate: function(listobj, callback) {
    const $dialog = $(this._dialog_skel);
    this._set_dialog_events($dialog);
    this.refresh_pattern($dialog);

    trello_find_list_by_obj(listobj, function () {
      const selection = this.cards.map(function (card) {
        return mk_option(card.name, card.id);
      });

      const preview = this.cards.map(function(card) {
        return mk_option(card.name, card.id, "card-" + card.id);
      });
      $dialog.find('#rd-selection').html(selection);
      $dialog.find('#rd-preview').html(preview);

      callback.apply($dialog);
    });
  },

  update_preview: function(regex, replace) {
    let re;
    $('#rd-selection option').each(function(i) {
      const $pcard   = $($('#card-' + $(this).val()))
      $pcard.removeClass("ma-bold");
      $pcard.text($(this).text());
    });

    try {
      re = new RegExp($(regex).val());
    } catch(e) {
      $(regex).css("background-color", "#de9a8f");
      $(replace).css("background-color", "#de9a8f");
      return false;
    }

    $(replace).css("background-color", "#96d48a");
    $(regex).css("background-color", "#96d48a");

    $('#rd-selection option:checked').each(function(i) {
      const pcard   = $('#card-' + $(this).val())
      const preview = renameDialog._replace(i, $(this).text(), re, $(replace).val());

      pcard.addClass("ma-bold");
      pcard.text(preview);
    });
  },

  apply_changes: function(regex, replace, callback) {
    $('#rd-selection option:checked').each(function(i) {
      const re       = RegExp(regex);
      const new_name = renameDialog._replace(i, $(this).text(), re, replace);

      Trello.put("cards/" + $(this).val() + "/name", {value: new_name});
    }).promise().done(function () {
      callback();
    });
  },

  show: function(list) {
    this.populate(list, function() {
      $('.pop-over').html(this);
      $('.pop-over').css({
        left: "10%",
        top: "10%",
        width: "80%"
      });
    });
  },

  hide: function() {
    $('.pop-over').empty().toggleClass('is-shown');
  },

  refresh_pattern: function($dialog) {
    const patterns = patternDialog.patterns();

    const $select = $dialog.find('#rd-select-pattern');
    $select.find('option').remove();

    const options = Object.keys(patterns).map(function(name) {
      const text = name + ' <i>(' + patterns[name].regex + ')';
      return mk_option(text, name);
    });
    $select.html(options);
  },

  _replace: function(index, text, regex, replace) {
      const repl = replace.replace(/\$i/, index + 1);
      return text.replace(RegExp(regex), repl);
  },

  _set_dialog_events: function($dialog) {
    // make scrollbars synchron
    $dialog.find('#rd-selection').scroll(function(){
      $('#rd-preview').scrollTop($(this).scrollTop());
    });
    $dialog.find('#rd-preview').scroll(function(){
      $('#rd-selection').scrollTop($(this).scrollTop());
    });

    // apply changes
    $dialog.find(".rd-apply").click(function () {
      const replace = $dialog.find('#rd-replace').val();
      const regex   = $dialog.find('#rd-regex').val();
      renameDialog.apply_changes(regex, replace, function() {
        renameDialog.hide();
      });
    });

    // update preview
    $dialog.find('#rd-selection').change(function() {
      $dialog.find('#rd-replace').trigger('keyup');
    });
    $dialog.find('#rd-regex').keyup(function() {
      $dialog.find('#rd-replace').trigger('keyup');
    });

    $dialog.find('#rd-replace').keyup(function() {
      const replace = $dialog.find('#rd-replace');
      const regex   = $dialog.find('#rd-regex');
      renameDialog.update_preview(regex, replace);
    });

    $(document).on('click', '#rd-select-pattern', function() {
      const name = $(this).val();
      const pattern = patternDialog.pattern(name);

      if (pattern) {
        $dialog.find('#rd-replace').val(pattern.replace);
        $dialog.find('#rd-regex').val(pattern.regex);
        $dialog.find('#rd-replace').trigger('keyup');
      }
    });

    $(document).on('click', '.pd-delete', function() {
      const name = $dialog.find('#rd-select-pattern').val();
      patternDialog.delete(name);
      renameDialog.refresh_pattern($dialog);
    });
  }
}
<|endoftext|>


<commit_before>module.exports = {
  //---------------------------------------------------------------------
  // Action Name
  //
  // This is the name of the action displayed in the editor.
  //---------------------------------------------------------------------

  name: "Set Bot Activity",

  //---------------------------------------------------------------------
  // Action Section
  //
  // This is the section the action will fall into.
  //---------------------------------------------------------------------

  section: "Bot Client Control",

  //---------------------------------------------------------------------
  // Action Subtitle
  //
  // This function generates the subtitle displayed next to the name.
  //---------------------------------------------------------------------

  subtitle: function(data) {
    const activities = [
      "Playing",
      "Listening to",
      "Watching",
      "Streaming Twitch"
    ];

    const stats = ["Online", "Idle", "Invisible", "Do Not Disturb"];

    if (activities[data.activity] !== "Streaming Twitch") {
      return `${stats[data.stat]}, ${activities[data.activity]} ${
        data.nameText
      }`;
    } else {
      return `${stats[data.stat]}, ${activities[data.activity]} ${
        data.nameText
      } (${data.url})`;
    }
  },

  //---------------------------------------------------------------------
  // Action Fields
  //
  // These are the fields for the action. These fields are customized
  // by creating elements with corresponding IDs in the HTML. These
  // are also the names of the fields stored in the action's JSON data.
  //---------------------------------------------------------------------

  fields: ["activity", "nameText", "url", "stat"],

  //---------------------------------------------------------------------
  // Command HTML
  //
  // This function returns a string containing the HTML used for
  // editting actions.
  //
  // The "isEvent" parameter will be true if this action is being used
  // for an event. Due to their nature, events lack certain information,
  // so edit the HTML to reflect this.
  //
  // The "data" parameter stores constants for select elements to use.
  // Each is an array: index 0 for commands, index 1 for events.
  // The names are: sendTargets, members, roles, channels,
  //                messages, servers, variables
  //---------------------------------------------------------------------

  html: function(isEvent, data) {
    return `

								Mod Info:

								Created by Lasse!

								Edited by General Wrex and SeikiMatt

								Activity:

								<select id="activity" class="round" style="width: 100%;">
									<option value="0">Playing</option>
									<option value="1">Listening</option>
									<option value="2">Watching</option>
									<option value="3">Streaming Twitch</option>
								</select>

								Status:

								<select id="stat" class="round" style="width: 100%;">
									<option value="0">Online</option>
									<option value="1">Idle</option>
									<option value="2">Invisible</option>
									<option value="3">Do Not Disturb</option>
								</select>

						Activity Name:

						<input id="nameText" class="round" type="text" style="width: 100%;">

							Twitch Stream URL:

							<input id="url" class="round" type="text" autofocus="autofocus" placeholder='Only works with http://twitch.tv/ URLs'
							 style="width: 100%;">

						This mod requires the latest version of Discord.js

						Installation tutorial: https://youtu.be/mrrtj5nlV58

				`;
  },

  //---------------------------------------------------------------------
  // Action Editor Init Code
  //
  // When the HTML is first applied to the action editor, this code
  // is also run. This helps add modifications or setup reactionary
  // functions for the DOM elements.
  //---------------------------------------------------------------------

  init: function() {
    const { glob, document } = this;

    let selector = document.getElementById("activity");
    let targetfield = document.getElementById("urlArea");

    if (selector[selector.selectedIndex].value === "3") {
      targetfield.classList.remove("hidden");
      alert("bengis");
    } else {
      targetfield.classList.add("hidden");
    }

    function showUrl() {
      if (selector[selector.selectedIndex].value === "3") {
        targetfield.classList.remove("hidden");
      } else {
        targetfield.classList.add("hidden");
      }
    }

    selector.onclick = () => showUrl();
  },

  //---------------------------------------------------------------------
  // Action Bot Function
  //
  // This is the function for the action within the Bot's Action class.
  // Keep in mind event calls won't have access to the "msg" parameter,
  // so be sure to provide checks for variable existance.
  //---------------------------------------------------------------------

  action: function(cache) {
    const botClient = this.getDBM().Bot.bot.user;
    const data = cache.actions[cache.index];

    const nameText = this.evalMessage(data.nameText, cache);
    const url = this.evalMessage(data.url, cache);

    const activity = parseInt(data.activity);
    const stat = parseInt(data.stat);

    let obj;

    let target;
    if (activity >= 0) {
      switch (activity) {
        case 0:
          target = "PLAYING";
          break;
        case 1:
          target = "LISTENING";
          break;
        case 2:
          target = "WATCHING";
          break;
        case 3:
          target = "STREAMING";
          break;
      }
    }

    let statustarget;
    if (stat >= 0) {
      switch (stat) {
        case 0:
          statustarget = "online";
          break;
        case 1:
          statustarget = "idle";
          break;
        case 2:
          statustarget = "invisible";
          break;
        case 3:
          statustarget = "dnd";
          break;
      }
    }

    if (botClient) {
      if (nameText) {
        if (target === "STREAMING") {
          obj = {
            game: {
              name: nameText,
              type: target,
              url: url
            },
            status: statustarget
          };
        } else {
          obj = {
            game: { name: nameText, type: target },
            status: statustarget
          };
        }
      }

      console.log(obj);

      botClient
        .setPresence(obj)
        .then(
          function() {
            this.callNextAction(cache);
          }.bind(this)
        )
        .catch(err => console.log(err));
    } else {
      this.callNextAction(cache);
    }
  },

  //---------------------------------------------------------------------
  // Action Bot Mod
  //
  // Upon initialization of the bot, this code is run. Using the bot's
  // DBM namespace, one can add/modify existing functions if necessary.
  // In order to reduce conflictions between mods, be sure to alias
  // functions you wish to overwrite.
  //---------------------------------------------------------------------

  mod: function(DBM) {}
}; // End of module
<commit_msg>Beautified and added mod installer info to Set Bot Activity<commit_after>module.exports = {
  //---------------------------------------------------------------------
  // Action Name
  //
  // This is the name of the action displayed in the editor.
  //---------------------------------------------------------------------

  name: "Set Bot Activity",

  //---------------------------------------------------------------------
  // Action Section
  //
  // This is the section the action will fall into.
  //---------------------------------------------------------------------

  section: "Bot Client Control",

  //---------------------------------------------------------------------
	// DBM Mods Manager Variables (Optional but nice to have!)
	//
	// These are variables that DBM Mods Manager uses to show information
	// about the mods for people to see in the list.
	//---------------------------------------------------------------------

	// Who made the mod (If not set, defaults to "DBM Mods")
	author: "Lasse",

	// The version of the mod (Defaults to 1.0.0)
	version: "1.9", //Added in 1.8.9

	// A short description to show on the mod line for this mod (Must be on a single line)
	short_description: "Set activity and status for our bot!!",

  // If it depends on any other mods by name, ex: WrexMODS if the mod uses something from WrexMods

  //---------------------------------------------------------------------
  // Action Subtitle
  //
  // This function generates the subtitle displayed next to the name.
  //---------------------------------------------------------------------

  subtitle: function(data) {
    const activities = [
      "Playing",
      "Listening to",
      "Watching",
      "Streaming Twitch"
    ];

    const stats = ["Online", "Idle", "Invisible", "Do Not Disturb"];

    if (activities[data.activity] !== "Streaming Twitch") {
      return `${stats[data.stat]}, ${activities[data.activity]} ${
        data.nameText
      }`;
    } else {
      return `${stats[data.stat]}, ${activities[data.activity]} ${
        data.nameText
      } (${data.url})`;
    }
  },

  //---------------------------------------------------------------------
  // Action Fields
  //
  // These are the fields for the action. These fields are customized
  // by creating elements with corresponding IDs in the HTML. These
  // are also the names of the fields stored in the action's JSON data.
  //---------------------------------------------------------------------

  fields: ["activity", "nameText", "url", "stat"],

  //---------------------------------------------------------------------
  // Command HTML
  //
  // This function returns a string containing the HTML used for
  // editting actions.
  //
  // The "isEvent" parameter will be true if this action is being used
  // for an event. Due to their nature, events lack certain information,
  // so edit the HTML to reflect this.
  //
  // The "data" parameter stores constants for select elements to use.
  // Each is an array: index 0 for commands, index 1 for events.
  // The names are: sendTargets, members, roles, channels,
  //                messages, servers, variables
  //---------------------------------------------------------------------

  html: function(isEvent, data) {
    return `

								Mod Info:

								Created by Lasse!

								Edited by General Wrex and SeikiMatt

								Activity:

								<select id="activity" class="round" style="width: 100%;">
									<option value="0">Playing</option>
									<option value="1">Listening</option>
									<option value="2">Watching</option>
									<option value="3">Streaming Twitch</option>
								</select>

								Status:

								<select id="stat" class="round" style="width: 100%;">
									<option value="0">Online</option>
									<option value="1">Idle</option>
									<option value="2">Invisible</option>
									<option value="3">Do Not Disturb</option>
								</select>

						Activity Name:

						<input id="nameText" class="round" type="text" style="width: 100%;">

							Twitch Stream URL:

							<input id="url" class="round" type="text" autofocus="autofocus" placeholder='Only works with http://twitch.tv/ URLs'
							 style="width: 100%;">

						This mod requires the latest version of Discord.js

						Installation tutorial: https://youtu.be/mrrtj5nlV58

				`;
  },

  //---------------------------------------------------------------------
  // Action Editor Init Code
  //
  // When the HTML is first applied to the action editor, this code
  // is also run. This helps add modifications or setup reactionary
  // functions for the DOM elements.
  //---------------------------------------------------------------------

  init: function() {
    const { glob, document } = this;

    let selector = document.getElementById("activity");
    let targetfield = document.getElementById("urlArea");

    if (selector[selector.selectedIndex].value === "3") {
      targetfield.classList.remove("hidden");
      alert("bengis");
    } else {
      targetfield.classList.add("hidden");
    }

    function showUrl() {
      if (selector[selector.selectedIndex].value === "3") {
        targetfield.classList.remove("hidden");
      } else {
        targetfield.classList.add("hidden");
      }
    }

    selector.onclick = () => showUrl();
  },

  //---------------------------------------------------------------------
  // Action Bot Function
  //
  // This is the function for the action within the Bot's Action class.
  // Keep in mind event calls won't have access to the "msg" parameter,
  // so be sure to provide checks for variable existance.
  //---------------------------------------------------------------------

  action: function(cache) {
    const botClient = this.getDBM().Bot.bot.user;
    const data = cache.actions[cache.index];

    const nameText = this.evalMessage(data.nameText, cache);
    const url = this.evalMessage(data.url, cache);

    const activity = parseInt(data.activity);
    const stat = parseInt(data.stat);

    let obj;

    let target;
    if (activity >= 0) {
      switch (activity) {
        case 0:
          target = "PLAYING";
          break;
        case 1:
          target = "LISTENING";
          break;
        case 2:
          target = "WATCHING";
          break;
        case 3:
          target = "STREAMING";
          break;
      }
    }

    let statustarget;
    if (stat >= 0) {
      switch (stat) {
        case 0:
          statustarget = "online";
          break;
        case 1:
          statustarget = "idle";
          break;
        case 2:
          statustarget = "invisible";
          break;
        case 3:
          statustarget = "dnd";
          break;
      }
    }

    if (botClient) {
      if (nameText) {
        if (target === "STREAMING") {
          obj = {
            game: {
              name: nameText,
              type: target,
              url: url
            },
            status: statustarget
          };
        } else {
          obj = {
            game: { name: nameText, type: target },
            status: statustarget
          };
        }
      }

      botClient
        .setPresence(obj)
        .then(
          function() {
            this.callNextAction(cache);
          }.bind(this)
        )
        .catch(err => console.log(err));
    } else {
      this.callNextAction(cache);
    }
  },

  //---------------------------------------------------------------------
  // Action Bot Mod
  //
  // Upon initialization of the bot, this code is run. Using the bot's
  // DBM namespace, one can add/modify existing functions if necessary.
  // In order to reduce conflictions between mods, be sure to alias
  // functions you wish to overwrite.
  //---------------------------------------------------------------------

  mod: function(DBM) {}
}; // End of module
<|endoftext|>


<commit_before>'use strict';

var aFrom             = require('es5-ext/array/from')
  , flatten           = require('es5-ext/array/#/flatten')
  , last              = require('es5-ext/array/#/last')
  , format            = require('es5-ext/date/#/format')
  , forEach           = require('es5-ext/object/for-each')
  , normalizeOptions  = require('es5-ext/object/normalize-options')
  , ensureString      = require('es5-ext/object/validate-stringifiable-value')
  , uncapitalize      = require('es5-ext/string/#/uncapitalize')
  , Set               = require('es6-set')
  , Map               = require('es6-map')
  , deferred          = require('deferred')
  , unserializeEvent  = require('dbjs/_setup/unserialize/event')
  , resolveKeyPath    = require('dbjs/_setup/utils/resolve-key-path')
  , mano              = require('mano')
  , isOwnerId         = require('eregistrations/utils/is-object-id')
  , idToStorage       = require('eregistrations/server/utils/any-id-to-storage')
  , fs                = require('fs2')
  , path              = require('path')
  , debug             = require('debug-ext')('db-data-sync')
  , microtimeNow      = require('microtime-x')
  , Client            = require('../lib/server/client')
  , resolveUserAccess = require('../lib/server/resolve-user-access')
  , clientErrorFilter = require('./utils/client-error-logger-filter')

  , byStamp = function (a, b) { return a.stamp - b.stamp; }
  , isProtoId = RegExp.prototype.test.bind(/^7[A-Z][a-z0-9A-Z]+#$/)
  , dbDriver = mano.dbDriver, dbDriverGlobal = mano.dbDriverGlobal
  , create = Object.create, keys = Object.keys, stringify = JSON.stringify;

var resolveStorage = function (protoId) {
	var storageName = uncapitalize.call(protoId.slice(1, -1));
	return dbDriver.hasStorage(storageName)(function (result) {
		if (result) return dbDriver.getStorage(storageName);
		if (!dbDriverGlobal) return;
		return dbDriverGlobal.hasStorage(storageName)(function (result) {
			if (result) return dbDriverGlobal.getStorage(storageName);
		});
	})(function (storage) {
		if (!storage) throw new Error("No " + stringify(storageName) + " storage intialized on driver");
		return storage;
	});
};

exports['confirm-update'] = {
	validate: function (data) { return ensureString(data.id); },
	submit: function (id) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var promise = client.sseWaiting[id];
			debug("%s client -> server: confirmed db-update reception (%s)", clientWithSessionId, id);
			if (promise) promise.resolve();
			return client.isSync ? client.stateHash : true;
		});
	}
};

exports.dbupdate = {
	validate: function (data) { return ensureString(data.data); },
	submit: function (data) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var objIds = new Set(), initializedIds = create(null), records = new Map()
			  , events, timeDiff, lastStamp, tmpState = {};
			data = data.trim().split('\n');
			debug("%s client -> server", clientWithSessionId);
			console.log(data);
			events = flatten.call(data.map(function (data) {
				if (!data) return;
				return unserializeEvent(data, client._id_);
			})).sort(byStamp);
			lastStamp = events.length ?  last.call(events).stamp : 0;
			timeDiff = lastStamp - microtimeNow();
			if (timeDiff > 0) {
				debug("%s Events from future! %s", clientWithSessionId,
					(timeDiff / 1000000).toFixed(2) + "s");
				events.forEach(function (event) { event.stamp -= timeDiff; });
			} else {
				debug("%s event time diff %s", clientWithSessionId, (timeDiff / 1000000).toFixed(2) + "s");
			}
			return deferred.map(events, function (event) {
				var ownerId = event.id.split('/', 1)[0], valueOwnerId;
				if (event.value === '') {
					delete client.clientDataMap[event.id];
				} else {
					client.clientDataMap[event.id] = event.stamp;
					tmpState[event.id] = event.stamp;
				}
				if (ownerId === 'globalPrimitives') {
					// Special case, distribute per record id and not ownerId
					ownerId = 'globalPrimitives/' + resolveKeyPath(event.id);
				} else if (isOwnerId(ownerId)) {
					objIds.add(ownerId);
					if (event.value[0] === '7') {
						valueOwnerId = event.value.split('/', 1)[0].slice(1);
						if (isOwnerId(valueOwnerId)) objIds.add(valueOwnerId);
					}
					if ((event.id === ownerId) && isProtoId(event.value)) {
						initializedIds[ownerId] = resolveStorage(event.value);
					}
				}
				return deferred(initializedIds[ownerId] || idToStorage(ownerId)(function (storage) {
					if (!storage) {
						console.error("\nUpdate for unknown object " + stringify(ownerId) + "\n");
						delete client.clientDataMap[event.id];
					}
					return storage;
				}))(function (storage) {
					if (!storage) return;
					if (initializedIds[ownerId]) idToStorage.setStorage(ownerId, storage);
					if (!records.has(storage)) records.set(storage, []);
					records.get(storage).push({
						id: event.id,
						data: { value: event.value, stamp: event.stamp }
					});
				});
			})(function () {
				return resolveUserAccess(objIds)(function () {
					return deferred.map(aFrom(records), function (data) {
						return data[0].storeMany(data[1]);
					})(function () {
						mano.slaveAccessFragment.flush();
						return deferred(mano.registerUserPromise)(function () {
							client.save();
							return deferred(client.appFragmentPromise)(function () {
								var result;
								forEach(tmpState, function (stamp, id) {
									if (client.clientDataMap[id] !== stamp) return;
									if (!client.dataFragment.dataMap[id]) {
										debug("%s unexpected record came from client (%s)", clientWithSessionId, id);
										client.sync.remove(id);
									}
								});
								result = client.isSync ? client.stateHash : true;
								debug("%s server respond to /dbupdate/ %s", clientWithSessionId, result);
								return result;
							});
						});
					});
				});
			});
		}.bind(this));
	}
};

exports['data-initialization'] = {
	validate: Function.prototype,
	submit: function (data) {
		return Client.get(this.req)(function (client) {
			client.reinitialize();
			return client.isSync ? client.stateHash : true;
		});
	}
};

exports['sync-confirm'] = {
	validate: function (data) { return ensureString(data.data).trim(); },
	submit: function (data) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var self = create(null), obsolete = [], missing = [], unsync = [], events = {}, result;

			// console.log("Client", client._id_, "confirm against\n" + client.stateArray.join('\n'));

			forEach(client.dataFragment.dataMap, function (event, id) {
				if (event.value === '') return;
				self[id] = event.stamp;
				events[id] = event;
			});

			if (data) {
				data.split('\n').forEach(function (recordData) {
					var index = recordData.indexOf('.'), id, clientStamp, serverStamp;
					if (index === -1) {
						console.log("Record", recordData);
						console.log("Data", data);
						throw new Error('Unrecognized sync data');
					}
					id = recordData.slice(index + 1);
					if (self[id] == null) {
						obsolete.push(id);
						return;
					}
					clientStamp = Number(recordData.slice(0, index));
					serverStamp = self[id];
					delete self[id];
					if (serverStamp === clientStamp) return;
					if (Number(serverStamp) < Number(clientStamp)) unsync.push(id);
					else missing.push(id);
				});
			}
			missing = missing.concat(keys(self));

			if (!obsolete.length && !missing.length && !unsync.length) {
				debug("%s sync confirm OK", clientWithSessionId);
				return true;
			}

			result = {};
			debug("%s sync confirm not OK", clientWithSessionId);
			if (obsolete.length) {
				console.log("Obsolete on Client", obsolete);
				result.obsolete = obsolete;
			}

			if (missing.length) {
				console.log("Missing on Client", missing);
				result.missing = missing;
				missing.forEach(function (id) { this.update(id, events[id]); }, client.sync);
			}

			if (unsync.length) {
				console.log("Not in sync on Client", unsync);
				result.unsync = unsync;
				unsync.forEach(function (id) { this.update(id, events[id]); }, client.sync);
			}

			return result;
		});
	}
};

var errorProps = ['location', 'buildStamp', 'message', 'source', 'line', 'column', 'errorMessage',
	'errorStack'];

exports['log-client-error'] = {
	validate: function (data) {
		var normalizedData = {}, parsedData;
		parsedData = JSON.parse(data.data);
		errorProps.forEach(function (key) { normalizedData[key] = parsedData[key]; });

		return normalizedData;
	},
	submit: function (normalizedData) {
		var now = new Date(), content = [], logFileName, messages, data;
		data = normalizeOptions({
			clientWithSessionId: this.req.$clientWithSessionId,
			agent: this.req.headers['user-agent'],
			utcDateTime: now.toISOString(),
			serverDateTime: format.call(now, '%Y-%m-%d %H:%M:%S'),
			referer: this.req.headers.referer,
			appName: this.req.$appName,
			ip: this.req.headers['x-real-ip']
		}, normalizedData);
		if (!clientErrorFilter.call(this, data)) return;

		messages = {
			clientWithSessionId: "Client & Session Id",
			agent: "User Agent",
			utcDateTime: "UTC Time",
			serverDateTime: "Server Time",
			referer: "Referer",
			ip: "IP",
			appName: "Application Name",
			location: "Location",
			buildStamp: "Build Stamp",
			message: "Message",
			source: "Source",
			line: "Line",
			column: "Column",
			errorMessage: "Error Message",
			errorStack: "Error Stack"
		};

		Object.keys(data).forEach(function (key) {
			content.push(messages[key] + ':\n------------------------------------', data[key] + '\n\n');
		});
		content = content.join('\n');
		logFileName = (now.toISOString() + '-' + data.clientWithSessionId).replace(/:/g, '_');

		// Meant to be used only in context of a child of some process manager
		if (process.send) {
			process.send({
				type: 'client-error-msg',
				text: content,
				data: data
			});
		}
		fs.writeFile(path.resolve(mano.env.root, 'log/client-errors/' + logFileName),
			content, { intermediate: true }).done();
	}
};
<commit_msg>Update mano package<commit_after>'use strict';

var aFrom             = require('es5-ext/array/from')
  , flatten           = require('es5-ext/array/#/flatten')
  , last              = require('es5-ext/array/#/last')
  , format            = require('es5-ext/date/#/format')
  , forEach           = require('es5-ext/object/for-each')
  , normalizeOptions  = require('es5-ext/object/normalize-options')
  , ensureString      = require('es5-ext/object/validate-stringifiable-value')
  , uncapitalize      = require('es5-ext/string/#/uncapitalize')
  , Set               = require('es6-set')
  , Map               = require('es6-map')
  , deferred          = require('deferred')
  , unserializeEvent  = require('dbjs/_setup/unserialize/event')
  , resolveKeyPath    = require('dbjs/_setup/utils/resolve-key-path')
  , mano              = require('mano')
  , isOwnerId         = require('eregistrations/utils/is-object-id')
  , idToStorage       = require('eregistrations/server/utils/any-id-to-storage')
  , fs                = require('fs2')
  , path              = require('path')
  , debug             = require('debug-ext')('db-data-sync')
  , microtimeNow      = require('microtime-x')
  , Client            = require('../lib/server/client')
  , resolveUserAccess = require('../lib/server/resolve-user-access')
  , clientErrorFilter = require('./utils/client-error-logger-filter')

  , byStamp = function (a, b) { return a.stamp - b.stamp; }
  , isProtoId = RegExp.prototype.test.bind(/^7[A-Z][a-z0-9A-Z]+#$/)
  , isModelId = RegExp.prototype.test.bind(/^[A-Z]/)
  , dbDriver = mano.dbDriver, dbDriverGlobal = mano.dbDriverGlobal
  , create = Object.create, keys = Object.keys, stringify = JSON.stringify;

var resolveStorage = function (protoId) {
	var storageName = uncapitalize.call(protoId.slice(1, -1));
	return dbDriver.hasStorage(storageName)(function (result) {
		if (result) return dbDriver.getStorage(storageName);
		if (!dbDriverGlobal) return;
		return dbDriverGlobal.hasStorage(storageName)(function (result) {
			if (result) return dbDriverGlobal.getStorage(storageName);
		});
	})(function (storage) {
		if (!storage) throw new Error("No " + stringify(storageName) + " storage intialized on driver");
		return storage;
	});
};

exports['confirm-update'] = {
	validate: function (data) { return ensureString(data.id); },
	submit: function (id) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var promise = client.sseWaiting[id];
			debug("%s client -> server: confirmed db-update reception (%s)", clientWithSessionId, id);
			if (promise) promise.resolve();
			return client.isSync ? client.stateHash : true;
		});
	}
};

exports.dbupdate = {
	validate: function (data) { return ensureString(data.data); },
	submit: function (data) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var objIds = new Set(), initializedIds = create(null), records = new Map()
			  , events, timeDiff, lastStamp, tmpState = {};
			data = data.trim().split('\n');
			debug("%s client -> server", clientWithSessionId);
			console.log(data);
			events = flatten.call(data.map(function (data) {
				if (!data) return;
				return unserializeEvent(data, client._id_);
			})).sort(byStamp);
			lastStamp = events.length ?  last.call(events).stamp : 0;
			timeDiff = lastStamp - microtimeNow();
			if (timeDiff > 0) {
				debug("%s Events from future! %s", clientWithSessionId,
					(timeDiff / 1000000).toFixed(2) + "s");
				events.forEach(function (event) { event.stamp -= timeDiff; });
			} else {
				debug("%s event time diff %s", clientWithSessionId, (timeDiff / 1000000).toFixed(2) + "s");
			}
			return deferred.map(events, function (event) {
				var ownerId = event.id.split('/', 1)[0], valueOwnerId;
				if (event.value === '') {
					delete client.clientDataMap[event.id];
				} else {
					client.clientDataMap[event.id] = event.stamp;
					tmpState[event.id] = event.stamp;
				}
				if (ownerId === 'globalPrimitives') {
					// Special case, distribute per record id and not ownerId
					ownerId = 'globalPrimitives/' + resolveKeyPath(event.id);
				} else if (isOwnerId(ownerId)) {
					objIds.add(ownerId);
					if (event.value[0] === '7') {
						valueOwnerId = event.value.split('/', 1)[0].slice(1);
						if (isOwnerId(valueOwnerId)) objIds.add(valueOwnerId);
					}
					if ((event.id === ownerId) && isProtoId(event.value)) {
						initializedIds[ownerId] = resolveStorage(event.value);
					}
				} else if (isModelId(ownerId)) {
					// Note: This case should naturally be handled below with `idToStorage`, but
					// due to this flaw: https://github.com/medikoo/dbjs-persistence/blob/
					// df1dc68f81eddf224d0f8a9c941654a9926e9bbe/storage.js#L101
					// it isn't
					console.error("\nUpdate for unknown object " + stringify(ownerId) + "\n");
					delete client.clientDataMap[event.id];
					return;
				}
				return deferred(initializedIds[ownerId] || idToStorage(ownerId)(function (storage) {
					if (!storage) {
						console.error("\nUpdate for unknown object " + stringify(ownerId) + "\n");
						delete client.clientDataMap[event.id];
					}
					return storage;
				}))(function (storage) {
					if (!storage) return;
					if (initializedIds[ownerId]) idToStorage.setStorage(ownerId, storage);
					if (!records.has(storage)) records.set(storage, []);
					records.get(storage).push({
						id: event.id,
						data: { value: event.value, stamp: event.stamp }
					});
				});
			})(function () {
				return resolveUserAccess(objIds)(function () {
					return deferred.map(aFrom(records), function (data) {
						return data[0].storeMany(data[1]);
					})(function () {
						mano.slaveAccessFragment.flush();
						return deferred(mano.registerUserPromise)(function () {
							client.save();
							return deferred(client.appFragmentPromise)(function () {
								var result;
								forEach(tmpState, function (stamp, id) {
									if (client.clientDataMap[id] !== stamp) return;
									if (!client.dataFragment.dataMap[id]) {
										debug("%s unexpected record came from client (%s)", clientWithSessionId, id);
										client.sync.remove(id);
									}
								});
								result = client.isSync ? client.stateHash : true;
								debug("%s server respond to /dbupdate/ %s", clientWithSessionId, result);
								return result;
							});
						});
					});
				});
			});
		}.bind(this));
	}
};

exports['data-initialization'] = {
	validate: Function.prototype,
	submit: function (data) {
		return Client.get(this.req)(function (client) {
			client.reinitialize();
			return client.isSync ? client.stateHash : true;
		});
	}
};

exports['sync-confirm'] = {
	validate: function (data) { return ensureString(data.data).trim(); },
	submit: function (data) {
		var clientWithSessionId = this.req.$clientWithSessionId;
		return Client.get(this.req)(function (client) {
			var self = create(null), obsolete = [], missing = [], unsync = [], events = {}, result;

			// console.log("Client", client._id_, "confirm against\n" + client.stateArray.join('\n'));

			forEach(client.dataFragment.dataMap, function (event, id) {
				if (event.value === '') return;
				self[id] = event.stamp;
				events[id] = event;
			});

			if (data) {
				data.split('\n').forEach(function (recordData) {
					var index = recordData.indexOf('.'), id, clientStamp, serverStamp;
					if (index === -1) {
						console.log("Record", recordData);
						console.log("Data", data);
						throw new Error('Unrecognized sync data');
					}
					id = recordData.slice(index + 1);
					if (self[id] == null) {
						obsolete.push(id);
						return;
					}
					clientStamp = Number(recordData.slice(0, index));
					serverStamp = self[id];
					delete self[id];
					if (serverStamp === clientStamp) return;
					if (Number(serverStamp) < Number(clientStamp)) unsync.push(id);
					else missing.push(id);
				});
			}
			missing = missing.concat(keys(self));

			if (!obsolete.length && !missing.length && !unsync.length) {
				debug("%s sync confirm OK", clientWithSessionId);
				return true;
			}

			result = {};
			debug("%s sync confirm not OK", clientWithSessionId);
			if (obsolete.length) {
				console.log("Obsolete on Client", obsolete);
				result.obsolete = obsolete;
			}

			if (missing.length) {
				console.log("Missing on Client", missing);
				result.missing = missing;
				missing.forEach(function (id) { this.update(id, events[id]); }, client.sync);
			}

			if (unsync.length) {
				console.log("Not in sync on Client", unsync);
				result.unsync = unsync;
				unsync.forEach(function (id) { this.update(id, events[id]); }, client.sync);
			}

			return result;
		});
	}
};

var errorProps = ['location', 'buildStamp', 'message', 'source', 'line', 'column', 'errorMessage',
	'errorStack'];

exports['log-client-error'] = {
	validate: function (data) {
		var normalizedData = {}, parsedData;
		parsedData = JSON.parse(data.data);
		errorProps.forEach(function (key) { normalizedData[key] = parsedData[key]; });

		return normalizedData;
	},
	submit: function (normalizedData) {
		var now = new Date(), content = [], logFileName, messages, data;
		data = normalizeOptions({
			clientWithSessionId: this.req.$clientWithSessionId,
			agent: this.req.headers['user-agent'],
			utcDateTime: now.toISOString(),
			serverDateTime: format.call(now, '%Y-%m-%d %H:%M:%S'),
			referer: this.req.headers.referer,
			appName: this.req.$appName,
			ip: this.req.headers['x-real-ip']
		}, normalizedData);
		if (!clientErrorFilter.call(this, data)) return;

		messages = {
			clientWithSessionId: "Client & Session Id",
			agent: "User Agent",
			utcDateTime: "UTC Time",
			serverDateTime: "Server Time",
			referer: "Referer",
			ip: "IP",
			appName: "Application Name",
			location: "Location",
			buildStamp: "Build Stamp",
			message: "Message",
			source: "Source",
			line: "Line",
			column: "Column",
			errorMessage: "Error Message",
			errorStack: "Error Stack"
		};

		Object.keys(data).forEach(function (key) {
			content.push(messages[key] + ':\n------------------------------------', data[key] + '\n\n');
		});
		content = content.join('\n');
		logFileName = (now.toISOString() + '-' + data.clientWithSessionId).replace(/:/g, '_');

		// Meant to be used only in context of a child of some process manager
		if (process.send) {
			process.send({
				type: 'client-error-msg',
				text: content,
				data: data
			});
		}
		fs.writeFile(path.resolve(mano.env.root, 'log/client-errors/' + logFileName),
			content, { intermediate: true }).done();
	}
};
<|endoftext|>


<commit_before>class System {
  constructor(p_xSizeAndPos){
    this.sizeAndPosition = p_xSizeAndPos;
  }

  init(p_xSettings){
    if (p_xSettings){
      this.settings = p_xSettings;
    } else {
      this.optionsSet = new OptionsSet(this.optionsMetadata());
      this.settings = this.optionsSet.settings;
    }
    P5JsSettings.setSeed(this.settings.seed);
    this.scale =  this.settings.scale;
    this.baseImage = this.settings.baseImage;
    this.cellWidth = this.settings.cellWidth;

    this.internalTicksPerFrame = 1;
    this.speedIdx = 1;
    this.paused = false;
    this.tickCount = 0;
    this.initFireRisks();
    this.fireRiskThreshold = 0.1;
    this.resources = new Resources();
    this.cellViewer = new CellViewer();
    this.grid = new CellGrid(this.sizeAndPosition,
                             this,
                             this.settings.cellWidth,
                             this.cellViewer
                             );
    this.grid.initCells();

    this.firePropagationMatrix = System.BASE_INFLUENCE_MATRIX;
    this.initialLightning();
  }

  restart(){
    this.init(this.settings);
  }

  randomize(){
    this.settings.seed = Math.round(random(1000000));
    this.init(this.settings);
  }

  // Return a list of Options, specific to this sketch,
  // that can be tweaked via the URL parameters
  // separate from the default P5JsSettings that are supported
  //
  // supported types: integer, float, string
  optionsMetadata(){
    return [
      { name: "cellWidth", type: "integer", default: 20},
      { name: "scale", type: "float", default: 0.02},
      { name: "lightning_at", type: "array:string", default: [], delimiter: "|"},
      { name: "seed", type: "integer", default: P5JsSettings.getSeed() }
      // { name: "varname3", type: "float", default: 0.6}
    ];
  }

  createCell(tmpRow, tmpCol, i){
    const tmpCell = new Cell(tmpRow, tmpCol, i, this);
    tmpCell.setType(this.terrainTypeForPos(tmpRow, tmpCol));
    if (tmpCell.terrainType == System.TERRAIN_FOLLIAGE){
      tmpCell.fuelAmount = 100;
    }
    return tmpCell;
  }

  get x(){ return this.sizeAndPosition.x; }
  get y(){ return this.sizeAndPosition.y; }
  containsXY(x, y){ return this.sizeAndPosition.containsXY(x, y); }

  static get TERRAIN_SOIL(){ return 0; }
  static get TERRAIN_WATER(){ return 1; }
  static get TERRAIN_FOLLIAGE(){ return 2; }
  static get TERRAIN_BURNING(){ return 3; }
  static get TERRAIN_ENGULFED(){ return 4; }
  static get TERRAIN_SMOLDERING(){ return 5; }
  static get TERRAIN_BURNT(){ return 6; }
  static get TERRAIN_PARTIAL_BURN(){ return 7; }

  static get BASE_INFLUENCE_MATRIX(){
    return [
        0.02,
        0.05,
        0.02,

        0.05,
        // cell
        0.05,

        0.02,
        0.05,
        0.02
      ];
  }

  initFireRisks(){
    this.terrainFireRisks = [];
    this.terrainFireRisks[System.TERRAIN_SOIL]       = 0;
    this.terrainFireRisks[System.TERRAIN_WATER]      = -0.1;
    this.terrainFireRisks[System.TERRAIN_FOLLIAGE]   = 0;
    this.terrainFireRisks[System.TERRAIN_BURNING]    = 1.0;
    this.terrainFireRisks[System.TERRAIN_ENGULFED]   = 1.5;
    this.terrainFireRisks[System.TERRAIN_SMOLDERING] = 0.3;
    this.terrainFireRisks[System.TERRAIN_BURNT]      = 0.05;
    this.terrainFireRisks[System.TERRAIN_PARTIAL_BURN] = 0;
  }

  togglePause(){
    this.paused = !this.paused;
    this.setSpeed(max(1, this.speedIdx));
  }

  slowDown(){ this.setSpeed(this.speedIdx - 1) }
  speedUp(){ this.setSpeed(this.speedIdx + 1) }

  setSpeed(newIndex){
    const speedLookup = [0,
      0.25,
      0.5,
      1,
      2,
      4
    ];

    this.speedIdx = constrain(newIndex, 0, speedLookup.length - 1);
    this.internalTicksPerFrame = speedLookup[this.speedIdx];
  }

  terrainTypeForPos(x, y){
    if (this.settings.baseImage){
      return this.getTerrainTypeFromImage(x, y);
    }
    return this.getTerrainTypeFromNoise(x, y);
  }

  getTerrainTypeFromNoise(x, y){
    const waterOrLand = noise(this.scale * x, this.scale * y);

    if (waterOrLand < 0.5){
      return System.TERRAIN_WATER;

    } else {
      const largeOffset = 100000;
      const landOrFolliage = noise(this.scale * (x + largeOffset),
                                   this.scale * (y + largeOffset));
      if (landOrFolliage < 0.6) {
        return System.TERRAIN_SOIL;
      } else {
        return System.TERRAIN_FOLLIAGE;
      }
    }
  }

  getTerrainTypeFromImage(row, col){
    const colorAtXY = P5JsUtils.colorAtInPixels((col * this.cellWidth) % this.baseImage.width,
                                                (row * this.cellWidth) % this.baseImage.height,
                                                this.baseImage.width,
                                                this.baseImage.pixels);
    return this.cellViewer.terrainForColor(colorAtXY);
    return System.TERRAIN_FOLLIAGE;
  }

  setTerrainType(x, y, type){
    const cell = this.grid.cellForXY(x, y);
    if (!cell){
      return;
    }
    cell.setType(type);
  }

  initializeFromImage(image){
    let pausedState = this.paused;
    this.paused = true;
    this.settings.baseImage = image;
    this.init(this.settings);
    this.paused = pausedState;
  }

  initialLightning(){
    this.settings.lightning_at.forEach(coord => {
      let xyCoord = coord.split(",");
      this.lightningAt(xyCoord[0], xyCoord[1]);
    });
  }

  knockDownAt(x, y){
    const cell = this.grid.cellForXY(x, y);
    if (!cell.isBurning()){
      return;
    }
    cell.fireIntensity = 0;
    cell.setType(System.TERRAIN_SMOLDERING);
  }

  fireBreakAt(x, y){
    if (!this.resources.has(Resources.RES_FIRE_BREAK)){
      console.log("Out of: fire_break");
      return;
    }

    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.terrainType == System.TERRAIN_FOLLIAGE){
      cell.setType(System.TERRAIN_SOIL);
      cell.fuelAmount = 0;
      this.resources.use(Resources.RES_FIRE_BREAK);
    }
  }

  lightningAt(x, y){
    logMessage(LOG_LEVEL_INFO, `lightningAt: ${x},${y}`);
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.fuelAmount > 0){
      cell.startBurning();
    }
  }

  lightningStrike(){
    let firstFolliage = this.grid.cells.find(c => c.terrainType == System.TERRAIN_FOLLIAGE);
    if (firstFolliage) {
      firstFolliage.startBurning();
    }
  }

  tick(){
    // console.log("tock");
    if (this.paused){
      return;
    }

    if (this.internalTicksPerFrame < 1) {
      const onlyRunOnModulo = 1 / this.internalTicksPerFrame;
      if (frameCount % onlyRunOnModulo == 0){
        this.internalTick();
      }
    } else {
      for (var i = 0; i < this.internalTicksPerFrame; i++){
        this.internalTick();
      }
    }
  }

  internalTick(){
    this.tickCount += 1;

    if (this.tickCount % 5 == 0) {
      this.calcNextCellTypes();
      this.assignNextTypes();
    }
    this.grid.cells.forEach(cell => cell.tick());
    this.resources.tick();
  }

  assignNextTypes(){
    this.grid.cells.filter(c => c.nextFrameType != undefined)
                   .forEach((c) => {
                      c.setType(c.nextFrameType);
                      c.nextFrameType = undefined;
                    });
  }

  calcNextCellTypes(){
    this.grid.cells.filter(c => !c.isBurning() && c.fuelAmount > 0)
                   .forEach(c => this.setNextTypeForCell(c));
  }

  setNextTypeForCell(cell){
    if(this.fireRiskFromNeighbors(cell) > this.fireRiskThreshold){
      cell.nextFrameType = System.TERRAIN_BURNING;
    }
  }

  fireRiskFromNeighbors(cell){
    const neighbors = this.grid.cellNeighborsOfIdx(cell._idx);

    return  neighbors.map(this.fireRisk, this)
                     .reduce((total, riskFactor) => total + riskFactor, 0);
  }

  fireRisk(cell, neighborIdx){
    if (cell == undefined) {
      return 0;
    }

    return this.terrainFireRisks[cell.terrainType]
              * this.firePropagationMatrix[neighborIdx];
  }

  requestFullRedraw(){
    this.grid.cells.forEach(c => {c._needsRender = true; });
  }

  render(){
    noStroke();
    this.grid.renderViewsAsNeeded();
  }
}
<commit_msg>p5js/forest-fires-02 - Clear out the 'lightning_at' when randomizing<commit_after>class System {
  constructor(p_xSizeAndPos){
    this.sizeAndPosition = p_xSizeAndPos;
  }

  init(p_xSettings){
    if (p_xSettings){
      this.settings = p_xSettings;
    } else {
      this.optionsSet = new OptionsSet(this.optionsMetadata());
      this.settings = this.optionsSet.settings;
    }
    P5JsSettings.setSeed(this.settings.seed);
    this.scale =  this.settings.scale;
    this.baseImage = this.settings.baseImage;
    this.cellWidth = this.settings.cellWidth;

    this.internalTicksPerFrame = 1;
    this.speedIdx = 1;
    this.paused = false;
    this.tickCount = 0;
    this.initFireRisks();
    this.fireRiskThreshold = 0.1;
    this.resources = new Resources();
    this.cellViewer = new CellViewer();
    this.grid = new CellGrid(this.sizeAndPosition,
                             this,
                             this.settings.cellWidth,
                             this.cellViewer
                             );
    this.grid.initCells();

    this.firePropagationMatrix = System.BASE_INFLUENCE_MATRIX;
    this.initialLightning();
  }

  restart(){
    this.init(this.settings);
  }

  randomize(){
    this.settings.seed = Math.round(random(1000000));
    this.settings.lightning_at = [];
    this.init(this.settings);
  }

  // Return a list of Options, specific to this sketch,
  // that can be tweaked via the URL parameters
  // separate from the default P5JsSettings that are supported
  //
  // supported types: integer, float, string
  optionsMetadata(){
    return [
      { name: "cellWidth", type: "integer", default: 20},
      { name: "scale", type: "float", default: 0.02},
      { name: "lightning_at", type: "array:string", default: [], delimiter: "|"},
      { name: "seed", type: "integer", default: P5JsSettings.getSeed() }
      // { name: "varname3", type: "float", default: 0.6}
    ];
  }

  createCell(tmpRow, tmpCol, i){
    const tmpCell = new Cell(tmpRow, tmpCol, i, this);
    tmpCell.setType(this.terrainTypeForPos(tmpRow, tmpCol));
    if (tmpCell.terrainType == System.TERRAIN_FOLLIAGE){
      tmpCell.fuelAmount = 100;
    }
    return tmpCell;
  }

  get x(){ return this.sizeAndPosition.x; }
  get y(){ return this.sizeAndPosition.y; }
  containsXY(x, y){ return this.sizeAndPosition.containsXY(x, y); }

  static get TERRAIN_SOIL(){ return 0; }
  static get TERRAIN_WATER(){ return 1; }
  static get TERRAIN_FOLLIAGE(){ return 2; }
  static get TERRAIN_BURNING(){ return 3; }
  static get TERRAIN_ENGULFED(){ return 4; }
  static get TERRAIN_SMOLDERING(){ return 5; }
  static get TERRAIN_BURNT(){ return 6; }
  static get TERRAIN_PARTIAL_BURN(){ return 7; }

  static get BASE_INFLUENCE_MATRIX(){
    return [
        0.02,
        0.05,
        0.02,

        0.05,
        // cell
        0.05,

        0.02,
        0.05,
        0.02
      ];
  }

  initFireRisks(){
    this.terrainFireRisks = [];
    this.terrainFireRisks[System.TERRAIN_SOIL]       = 0;
    this.terrainFireRisks[System.TERRAIN_WATER]      = -0.1;
    this.terrainFireRisks[System.TERRAIN_FOLLIAGE]   = 0;
    this.terrainFireRisks[System.TERRAIN_BURNING]    = 1.0;
    this.terrainFireRisks[System.TERRAIN_ENGULFED]   = 1.5;
    this.terrainFireRisks[System.TERRAIN_SMOLDERING] = 0.3;
    this.terrainFireRisks[System.TERRAIN_BURNT]      = 0.05;
    this.terrainFireRisks[System.TERRAIN_PARTIAL_BURN] = 0;
  }

  togglePause(){
    this.paused = !this.paused;
    this.setSpeed(max(1, this.speedIdx));
  }

  slowDown(){ this.setSpeed(this.speedIdx - 1) }
  speedUp(){ this.setSpeed(this.speedIdx + 1) }

  setSpeed(newIndex){
    const speedLookup = [0,
      0.25,
      0.5,
      1,
      2,
      4
    ];

    this.speedIdx = constrain(newIndex, 0, speedLookup.length - 1);
    this.internalTicksPerFrame = speedLookup[this.speedIdx];
  }

  terrainTypeForPos(x, y){
    if (this.settings.baseImage){
      return this.getTerrainTypeFromImage(x, y);
    }
    return this.getTerrainTypeFromNoise(x, y);
  }

  getTerrainTypeFromNoise(x, y){
    const waterOrLand = noise(this.scale * x, this.scale * y);

    if (waterOrLand < 0.5){
      return System.TERRAIN_WATER;

    } else {
      const largeOffset = 100000;
      const landOrFolliage = noise(this.scale * (x + largeOffset),
                                   this.scale * (y + largeOffset));
      if (landOrFolliage < 0.6) {
        return System.TERRAIN_SOIL;
      } else {
        return System.TERRAIN_FOLLIAGE;
      }
    }
  }

  getTerrainTypeFromImage(row, col){
    const colorAtXY = P5JsUtils.colorAtInPixels((col * this.cellWidth) % this.baseImage.width,
                                                (row * this.cellWidth) % this.baseImage.height,
                                                this.baseImage.width,
                                                this.baseImage.pixels);
    return this.cellViewer.terrainForColor(colorAtXY);
    return System.TERRAIN_FOLLIAGE;
  }

  setTerrainType(x, y, type){
    const cell = this.grid.cellForXY(x, y);
    if (!cell){
      return;
    }
    cell.setType(type);
  }

  initializeFromImage(image){
    let pausedState = this.paused;
    this.paused = true;
    this.settings.baseImage = image;
    this.init(this.settings);
    this.paused = pausedState;
  }

  initialLightning(){
    this.settings.lightning_at.forEach(coord => {
      let xyCoord = coord.split(",");
      this.lightningAt(xyCoord[0], xyCoord[1]);
    });
  }

  knockDownAt(x, y){
    const cell = this.grid.cellForXY(x, y);
    if (!cell.isBurning()){
      return;
    }
    cell.fireIntensity = 0;
    cell.setType(System.TERRAIN_SMOLDERING);
  }

  fireBreakAt(x, y){
    if (!this.resources.has(Resources.RES_FIRE_BREAK)){
      console.log("Out of: fire_break");
      return;
    }

    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.terrainType == System.TERRAIN_FOLLIAGE){
      cell.setType(System.TERRAIN_SOIL);
      cell.fuelAmount = 0;
      this.resources.use(Resources.RES_FIRE_BREAK);
    }
  }

  lightningAt(x, y){
    logMessage(LOG_LEVEL_INFO, `lightningAt: ${x},${y}`);
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.fuelAmount > 0){
      cell.startBurning();
    }
  }

  lightningStrike(){
    let firstFolliage = this.grid.cells.find(c => c.terrainType == System.TERRAIN_FOLLIAGE);
    if (firstFolliage) {
      firstFolliage.startBurning();
    }
  }

  tick(){
    // console.log("tock");
    if (this.paused){
      return;
    }

    if (this.internalTicksPerFrame < 1) {
      const onlyRunOnModulo = 1 / this.internalTicksPerFrame;
      if (frameCount % onlyRunOnModulo == 0){
        this.internalTick();
      }
    } else {
      for (var i = 0; i < this.internalTicksPerFrame; i++){
        this.internalTick();
      }
    }
  }

  internalTick(){
    this.tickCount += 1;

    if (this.tickCount % 5 == 0) {
      this.calcNextCellTypes();
      this.assignNextTypes();
    }
    this.grid.cells.forEach(cell => cell.tick());
    this.resources.tick();
  }

  assignNextTypes(){
    this.grid.cells.filter(c => c.nextFrameType != undefined)
                   .forEach((c) => {
                      c.setType(c.nextFrameType);
                      c.nextFrameType = undefined;
                    });
  }

  calcNextCellTypes(){
    this.grid.cells.filter(c => !c.isBurning() && c.fuelAmount > 0)
                   .forEach(c => this.setNextTypeForCell(c));
  }

  setNextTypeForCell(cell){
    if(this.fireRiskFromNeighbors(cell) > this.fireRiskThreshold){
      cell.nextFrameType = System.TERRAIN_BURNING;
    }
  }

  fireRiskFromNeighbors(cell){
    const neighbors = this.grid.cellNeighborsOfIdx(cell._idx);

    return  neighbors.map(this.fireRisk, this)
                     .reduce((total, riskFactor) => total + riskFactor, 0);
  }

  fireRisk(cell, neighborIdx){
    if (cell == undefined) {
      return 0;
    }

    return this.terrainFireRisks[cell.terrainType]
              * this.firePropagationMatrix[neighborIdx];
  }

  requestFullRedraw(){
    this.grid.cells.forEach(c => {c._needsRender = true; });
  }

  render(){
    noStroke();
    this.grid.renderViewsAsNeeded();
  }
}
<|endoftext|>


<commit_before>import type Position from "./position";
import repeat from "lodash/repeat";
import trimEnd from "lodash/trimEnd";

/**
 * Buffer for collecting generated output.
 */

export default class Buffer {
  constructor(position: Position, format: Object) {
    this.printedCommentStarts = {};
    this.parenPushNewlineState = null;
    this.position = position;
    this._indent = format.indent.base;
    this.format = format;
    this.buf = "";

    // Maintaining a reference to the last char in the buffer is an optimization
    // to make sure that v8 doesn't "flatten" the string more often than needed
    // see https://github.com/babel/babel/pull/3283 for details.
    this.last = "";

    this.map = null;
    this._sourcePosition = {
      line: null,
      column: null,
      filename: null,
    };
  }

  printedCommentStarts: Object;
  parenPushNewlineState: ?Object;
  position: Position;
  _indent: number;
  format: Object;
  buf: string;
  last: string;

  /**
   * Description
   */

  catchUp(node: Object) {
    // catch up to this nodes newline if we're behind
    if (node.loc && this.format.retainLines && this.buf) {
      while (this.position.line < node.loc.start.line) {
        this.push("\n");
      }
    }
  }

  /**
   * Get the current trimmed buffer.
   */

  get(): string {
    return trimEnd(this.buf);
  }

  /**
   * Get the current indent.
   */

  getIndent(): string {
    if (this.format.compact || this.format.concise) {
      return "";
    } else {
      return repeat(this.format.indent.style, this._indent);
    }
  }

  /**
   * Get the current indent size.
   */

  indentSize(): number {
    return this.getIndent().length;
  }

  /**
   * Increment indent size.
   */

  indent() {
    this._indent++;
  }

  /**
   * Decrement indent size.
   */

  dedent() {
    this._indent--;
  }

  /**
   * Add a semicolon to the buffer.
   */

  semicolon() {
    this.token(";");
  }

  /**
   * Add a right brace to the buffer.
   */

  rightBrace() {
    this.newline(true);
    if (this.format.minified && !this._lastPrintedIsEmptyStatement) {
      this._removeLast(";");
    }
    this.token("}");
  }

  /**
   * Add a keyword to the buffer.
   */

  keyword(name: string) {
    this.word(name);
    this.space();
  }

  /**
   * Add a space to the buffer unless it is compact.
   */

  space() {
    if (this.format.compact) return;

    if (this.buf && !this.endsWith(" ") && !this.endsWith("\n")) {
      this.push(" ");
    }
  }

  /**
   * Writes a token that can't be safely parsed without taking whitespace into account.
   */

  word(str: string) {
    this.push(str);
  }

  /**
   * Writes a simple token.
   */

  token(str: string) {
    this.push(str);
  }

  /**
   * Remove the last character.
   */

  removeLast(cha: string) {
    if (this.format.compact) return;
    return this._removeLast(cha);
  }

  _removeLast(cha: string) {
    if (!this.endsWith(cha)) return;
    this.buf = this.buf.slice(0, -1);
    this.last = this.buf[this.buf.length - 1];
    this.position.unshift(cha);
  }

  /**
   * Set some state that will be modified if a newline has been inserted before any
   * non-space characters.
   *
   * This is to prevent breaking semantics for terminatorless separator nodes. eg:
   *
   *    return foo;
   *
   * returns `foo`. But if we do:
   *
   *   return
   *   foo;
   *
   *  `undefined` will be returned and not `foo` due to the terminator.
   */

  startTerminatorless(): Object {
    return this.parenPushNewlineState = {
      printed: false
    };
  }

  /**
   * Print an ending parentheses if a starting one has been printed.
   */

  endTerminatorless(state: Object) {
    if (state.printed) {
      this.dedent();
      this.newline();
      this.token(")");
    }
  }

  /**
   * Add a newline (or many newlines), maintaining formatting.
   * Strips multiple newlines if removeLast is true.
   */

  newline(i?: boolean | number, removeLast?: boolean) {
    if (this.format.retainLines || this.format.compact) return;

    if (this.format.concise) {
      this.space();
      return;
    }

    // never allow more than two lines
    if (this.endsWith("\n\n")) return;

    if (typeof i === "boolean") removeLast = i;
    if (typeof i !== "number") i = 1;

    i = Math.min(2, i);
    if (this.endsWith("{\n") || this.endsWith(":\n")) i--;
    if (i <= 0) return;

    // remove the last newline
    if (removeLast) {
      this.removeLast("\n");
    }

    this.removeLast(" ");
    this._removeSpacesAfterLastNewline();
    for (let j = 0; j < i; j++) {
      this.push("\n");
    }
  }

  /**
   * If buffer ends with a newline and some spaces after it, trim those spaces.
   */

  _removeSpacesAfterLastNewline() {
    let lastNewlineIndex = this.buf.lastIndexOf("\n");
    if (lastNewlineIndex >= 0 && this.get().length <= lastNewlineIndex) {
      let toRemove = this.buf.slice(lastNewlineIndex + 1);
      this.buf = this.buf.substring(0, lastNewlineIndex + 1);
      this.last = "\n";
      this.position.unshift(toRemove);
    }
  }

  /**
   * Sets a given position as the current source location so generated code after this call
   * will be given this position in the sourcemap.
   */

  source(prop: string, loc: Location) {
    if (prop && !loc) return;

    let pos = loc ? loc[prop] : null;

    this._sourcePosition.line = pos ? pos.line : null;
    this._sourcePosition.column = pos ? pos.column : null;
    this._sourcePosition.filename = loc && loc.filename || null;
  }

  /**
   * Call a callback with a specific source location and restore on completion.
   */

  withSource(prop: string, loc: Location, cb: () => void) {
    if (!this.opts.sourceMaps) return cb();

    // Use the call stack to manage a stack of "source location" data.
    let originalLine = this._sourcePosition.line;
    let originalColumn = this._sourcePosition.column;
    let originalFilename = this._sourcePosition.filename;

    this.source(prop, loc);

    cb();

    this._sourcePosition.line = originalLine;
    this._sourcePosition.column = originalColumn;
    this._sourcePosition.filename = originalFilename;
  }

  /**
   * Push a string to the buffer, maintaining indentation and newlines.
   */

  push(str: string) {
    if (!this.format.compact && this._indent && str[0] !== "\n") {
      // we've got a newline before us so prepend on the indentation
      if (this.endsWith("\n")) str = this.getIndent() + str;
    }

    // see startTerminatorless() instance method
    let parenPushNewlineState = this.parenPushNewlineState;
    if (parenPushNewlineState) {
      for (let i = 0; i < str.length; i++) {
        let cha = str[i];

        // we can ignore spaces since they wont interupt a terminatorless separator
        if (cha === " ") continue;

        this.parenPushNewlineState = null;

        if (cha === "\n" || cha === "/") {
          // we're going to break this terminator expression so we need to add a parentheses
          str = "(" + str;
          this.indent();
          parenPushNewlineState.printed = true;
        }

        break;
      }
    }

    // If there the line is ending, adding a new mapping marker is redundant
    if (this.opts.sourceMaps && str[0] !== "\n") this.map.mark(this._sourcePosition);

    //
    this.position.push(str);
    this.buf += str;
    this.last = str[str.length - 1];
  }

  /**
   * Test if the buffer ends with a string.
   */

  endsWith(str: string): boolean {
    if (Array.isArray(str)) return str.some((s) => this.endsWith(s));

    if (str.length === 1) {
      return this.last === str;
    } else {
      return this.buf.slice(-str.length) === str;
    }
  }

}
<commit_msg>Automatically prepend a space when writing a word immediately after a word.<commit_after>import type Position from "./position";
import repeat from "lodash/repeat";
import trimEnd from "lodash/trimEnd";

/**
 * Buffer for collecting generated output.
 */

export default class Buffer {
  constructor(position: Position, format: Object) {
    this.printedCommentStarts = {};
    this.parenPushNewlineState = null;
    this.position = position;
    this._indent = format.indent.base;
    this.format = format;
    this.buf = "";

    // Maintaining a reference to the last char in the buffer is an optimization
    // to make sure that v8 doesn't "flatten" the string more often than needed
    // see https://github.com/babel/babel/pull/3283 for details.
    this.last = "";

    this.map = null;
    this._sourcePosition = {
      line: null,
      column: null,
      filename: null,
    };
    this._endsWithWord = false;
  }

  printedCommentStarts: Object;
  parenPushNewlineState: ?Object;
  position: Position;
  _indent: number;
  format: Object;
  buf: string;
  last: string;

  /**
   * Description
   */

  catchUp(node: Object) {
    // catch up to this nodes newline if we're behind
    if (node.loc && this.format.retainLines && this.buf) {
      while (this.position.line < node.loc.start.line) {
        this.push("\n");
      }
    }
  }

  /**
   * Get the current trimmed buffer.
   */

  get(): string {
    return trimEnd(this.buf);
  }

  /**
   * Get the current indent.
   */

  getIndent(): string {
    if (this.format.compact || this.format.concise) {
      return "";
    } else {
      return repeat(this.format.indent.style, this._indent);
    }
  }

  /**
   * Get the current indent size.
   */

  indentSize(): number {
    return this.getIndent().length;
  }

  /**
   * Increment indent size.
   */

  indent() {
    this._indent++;
  }

  /**
   * Decrement indent size.
   */

  dedent() {
    this._indent--;
  }

  /**
   * Add a semicolon to the buffer.
   */

  semicolon() {
    this.token(";");
  }

  /**
   * Add a right brace to the buffer.
   */

  rightBrace() {
    this.newline(true);
    if (this.format.minified && !this._lastPrintedIsEmptyStatement) {
      this._removeLast(";");
    }
    this.token("}");
  }

  /**
   * Add a keyword to the buffer.
   */

  keyword(name: string) {
    this.word(name);
    this.space();
  }

  /**
   * Add a space to the buffer unless it is compact.
   */

  space() {
    if (this.format.compact) return;

    if (this.buf && !this.endsWith(" ") && !this.endsWith("\n")) {
      this.push(" ");
    }
  }

  /**
   * Writes a token that can't be safely parsed without taking whitespace into account.
   */

  word(str: string) {
    if (this._endsWithWord) this.push(" ");

    this.push(str);
    this._endsWithWord = true;
  }

  /**
   * Writes a simple token.
   */

  token(str: string) {
    this.push(str);
  }

  /**
   * Remove the last character.
   */

  removeLast(cha: string) {
    if (this.format.compact) return;
    return this._removeLast(cha);
  }

  _removeLast(cha: string) {
    if (!this.endsWith(cha)) return;
    this.buf = this.buf.slice(0, -1);
    this.last = this.buf[this.buf.length - 1];
    this.position.unshift(cha);
  }

  /**
   * Set some state that will be modified if a newline has been inserted before any
   * non-space characters.
   *
   * This is to prevent breaking semantics for terminatorless separator nodes. eg:
   *
   *    return foo;
   *
   * returns `foo`. But if we do:
   *
   *   return
   *   foo;
   *
   *  `undefined` will be returned and not `foo` due to the terminator.
   */

  startTerminatorless(): Object {
    return this.parenPushNewlineState = {
      printed: false
    };
  }

  /**
   * Print an ending parentheses if a starting one has been printed.
   */

  endTerminatorless(state: Object) {
    if (state.printed) {
      this.dedent();
      this.newline();
      this.token(")");
    }
  }

  /**
   * Add a newline (or many newlines), maintaining formatting.
   * Strips multiple newlines if removeLast is true.
   */

  newline(i?: boolean | number, removeLast?: boolean) {
    if (this.format.retainLines || this.format.compact) return;

    if (this.format.concise) {
      this.space();
      return;
    }

    // never allow more than two lines
    if (this.endsWith("\n\n")) return;

    if (typeof i === "boolean") removeLast = i;
    if (typeof i !== "number") i = 1;

    i = Math.min(2, i);
    if (this.endsWith("{\n") || this.endsWith(":\n")) i--;
    if (i <= 0) return;

    // remove the last newline
    if (removeLast) {
      this.removeLast("\n");
    }

    this.removeLast(" ");
    this._removeSpacesAfterLastNewline();
    for (let j = 0; j < i; j++) {
      this.push("\n");
    }
  }

  /**
   * If buffer ends with a newline and some spaces after it, trim those spaces.
   */

  _removeSpacesAfterLastNewline() {
    let lastNewlineIndex = this.buf.lastIndexOf("\n");
    if (lastNewlineIndex >= 0 && this.get().length <= lastNewlineIndex) {
      let toRemove = this.buf.slice(lastNewlineIndex + 1);
      this.buf = this.buf.substring(0, lastNewlineIndex + 1);
      this.last = "\n";
      this.position.unshift(toRemove);
    }
  }

  /**
   * Sets a given position as the current source location so generated code after this call
   * will be given this position in the sourcemap.
   */

  source(prop: string, loc: Location) {
    if (prop && !loc) return;

    let pos = loc ? loc[prop] : null;

    this._sourcePosition.line = pos ? pos.line : null;
    this._sourcePosition.column = pos ? pos.column : null;
    this._sourcePosition.filename = loc && loc.filename || null;
  }

  /**
   * Call a callback with a specific source location and restore on completion.
   */

  withSource(prop: string, loc: Location, cb: () => void) {
    if (!this.opts.sourceMaps) return cb();

    // Use the call stack to manage a stack of "source location" data.
    let originalLine = this._sourcePosition.line;
    let originalColumn = this._sourcePosition.column;
    let originalFilename = this._sourcePosition.filename;

    this.source(prop, loc);

    cb();

    this._sourcePosition.line = originalLine;
    this._sourcePosition.column = originalColumn;
    this._sourcePosition.filename = originalFilename;
  }

  /**
   * Push a string to the buffer, maintaining indentation and newlines.
   */

  push(str: string) {
    if (!this.format.compact && this._indent && str[0] !== "\n") {
      // we've got a newline before us so prepend on the indentation
      if (this.endsWith("\n")) str = this.getIndent() + str;
    }

    // see startTerminatorless() instance method
    let parenPushNewlineState = this.parenPushNewlineState;
    if (parenPushNewlineState) {
      for (let i = 0; i < str.length; i++) {
        let cha = str[i];

        // we can ignore spaces since they wont interupt a terminatorless separator
        if (cha === " ") continue;

        this.parenPushNewlineState = null;

        if (cha === "\n" || cha === "/") {
          // we're going to break this terminator expression so we need to add a parentheses
          str = "(" + str;
          this.indent();
          parenPushNewlineState.printed = true;
        }

        break;
      }
    }

    // If there the line is ending, adding a new mapping marker is redundant
    if (this.opts.sourceMaps && str[0] !== "\n") this.map.mark(this._sourcePosition);

    //
    this.position.push(str);
    this.buf += str;
    this.last = str[str.length - 1];

    // Clear any state-tracking flags that may have been set.
    this._endsWithWord = false;
  }

  /**
   * Test if the buffer ends with a string.
   */

  endsWith(str: string): boolean {
    if (Array.isArray(str)) return str.some((s) => this.endsWith(s));

    if (str.length === 1) {
      return this.last === str;
    } else {
      return this.buf.slice(-str.length) === str;
    }
  }

}
<|endoftext|>