<commit_before>/* globals jQuery */

(function ($) {
  // Selector to select only not already processed elements
  $.expr[":"].notmdproc = function (obj) {
    if ($(obj).data("mdproc")) {
      return false;
    } else {
      return true;
    }
  };

  function _isChar(evt) {
    if (typeof evt.which == "undefined") {
      return true;
    } else if (typeof evt.which == "number" && evt.which > 0) {
      return (
        !evt.ctrlKey
        && !evt.metaKey
        && !evt.altKey
        && evt.which != 8  // backspace
        && evt.which != 9  // tab
        && evt.which != 13 // enter
        && evt.which != 16 // shift
        && evt.which != 17 // ctrl
        && evt.which != 20 // caps lock
        && evt.which != 27 // escape
      );
    }
    return false;
  }

  function _addFormGroupFocus(element) {
    var $element = $(element);
    if (!$element.prop('disabled')) {  // this is showing as undefined on chrome but works fine on firefox??
      $element.closest(".form-group").addClass("is-focused");
    }
  }

  function _toggleTypeFocus($input) {
    $input.closest('label').hover(function () {
      var $i = $(this).find('input');
      if (!$i.prop('disabled')) { // hack because the _addFormGroupFocus() wasn't identifying the property on chrome
        _addFormGroupFocus($i);     // need to find the input so we can check disablement
      }
    }, function () {
      _removeFormGroupFocus($(this).find('input'));
    });
  }

  function _removeFormGroupFocus(element) {
    $(element).closest(".form-group").removeClass("is-focused"); // remove class from form-group
  }

  $.material = {
    "options": {
      // These options set what will be started by $.material.init()
      "validate": true,
      "input": true,
      "ripples": true,
      "checkbox": true,
      "togglebutton": true,
      "radio": true,
      "arrive": true,
      "autofill": false,

      "withRipples": [
        ".btn:not(.btn-link)",
        ".card-image",
        ".navbar a:not(.withoutripple)",
        ".dropdown-menu a",
        ".nav-tabs a:not(.withoutripple)",
        ".withripple",
        ".pagination li:not(.active):not(.disabled) a:not(.withoutripple)"
      ].join(","),
      "inputElements": "input.form-control, textarea.form-control, select.form-control",
      "checkboxElements": ".checkbox > label > input[type=checkbox]",
      "togglebuttonElements": ".togglebutton > label > input[type=checkbox]",
      "radioElements": ".radio > label > input[type=radio]"
    },
    "checkbox": function (selector) {
      // Add fake-checkbox to material checkboxes
      var $input = $((selector) ? selector : this.options.checkboxElements)
        .filter(":notmdproc")
        .data("mdproc", true)
        .after("");

      _toggleTypeFocus($input);
    },
    "togglebutton": function (selector) {
      // Add fake-checkbox to material checkboxes
      var $input = $((selector) ? selector : this.options.togglebuttonElements)
        .filter(":notmdproc")
        .data("mdproc", true)
        .after("");

      _toggleTypeFocus($input);
    },
    "radio": function (selector) {
      // Add fake-radio to material radios
      var $input = $((selector) ? selector : this.options.radioElements)
        .filter(":notmdproc")
        .data("mdproc", true)
        .after("");

      _toggleTypeFocus($input);
    },
    "input": function (selector) {
      $((selector) ? selector : this.options.inputElements)
        .filter(":notmdproc")
        .data("mdproc", true)
        .each(function () {
          var $input = $(this);

          // Requires form-group standard markup (will add it if necessary)
          var $formGroup = $input.closest(".form-group"); // note that form-group may be grandparent in the case of an input-group
          if ($formGroup.length === 0) {
            $input.wrap("

");
            $formGroup = $input.closest(".form-group"); // find node after attached (otherwise additional attachments don't work)
          }

          // Legacy - Add hint label if using the old shorthand data-hint attribute on the input
          if ($input.attr("data-hint")) {
            $input.after("
" + $input.attr("data-hint") + "
");
            $input.removeAttr("data-hint");
          }

          // Legacy - Change input-sm/lg to form-group-sm/lg instead (preferred standard and simpler css/less variants)
          var legacySizes = {
            "input-lg": "form-group-lg",
            "input-sm": "form-group-sm"
          };
          $.each(legacySizes, function (legacySize, standardSize) {
            if ($input.hasClass(legacySize)) {
              $input.removeClass(legacySize);
              $formGroup.addClass(standardSize);
            }
          });

          // Legacy - Add label-floating if using old shorthand <input class="floating-label" placeholder="foo">
          if ($input.hasClass("floating-label")) {
            var placeholder = $input.attr("placeholder");
            $input.attr("placeholder", null).removeClass("floating-label");
            var id = $input.attr("id");
            var forAttribute = "";
            if (id) {
              forAttribute = "for='" + id + "'";
            }
            $formGroup.addClass("label-floating");
            $input.after("" + placeholder + "");
          }

          // Set as empty if is empty (damn I must improve this...)
          if ($input.val() === null || $input.val() == "undefined" || $input.val() === "") {
            $formGroup.addClass("is-empty");
          }

          // Add at the end of the form-group
          $formGroup.append("");

          // Support for file input
          if ($formGroup.find("input[type=file]").length > 0) {
            $formGroup.addClass("is-fileinput");
          }
        });
    },
    "attachInputEventHandlers": function () {
      var validate = this.options.validate;

      $(document)
        .on("change", ".checkbox input[type=checkbox]", function () {
          $(this).blur();
        })
        .on("keydown paste", ".form-control", function (e) {
          if (_isChar(e)) {
            $(this).closest(".form-group").removeClass("is-empty");
          }
        })
        .on("keyup change", ".form-control", function () {
          var $input = $(this);
          var $formGroup = $input.closest(".form-group");
          var isValid = (typeof $input[0].checkValidity === "undefined" || $input[0].checkValidity());

          if ($input.val() === "") {
            $formGroup.addClass("is-empty");
          }
          else {
            $formGroup.removeClass("is-empty");
          }

          // Validation events do not bubble, so they must be attached directly to the input: http://jsfiddle.net/PEpRM/1/
          //  Further, even the bind method is being caught, but since we are already calling #checkValidity here, just alter
          //  the form-group on change.
          //
          // NOTE: I'm not sure we should be intervening regarding validation, this seems better as a README and snippet of code.
          //        BUT, I've left it here for backwards compatibility.
          if (validate) {
            if (isValid) {
              $formGroup.removeClass("has-error");
            }
            else {
              $formGroup.addClass("has-error");
            }
          }
        })
        .on("focus", ".form-control, .form-group.is-fileinput", function () {
          _addFormGroupFocus(this);
        })
        .on("blur", ".form-control, .form-group.is-fileinput", function () {
          _removeFormGroupFocus(this);
        })
        // make sure empty is added back when there is a programmatic value change.
        //  NOTE: programmatic changing of value using $.val() must trigger the change event i.e. $.val('x').trigger('change')
        .on("change", ".form-group input", function () {
          var $input = $(this);
          if ($input.attr("type") == "file") {
            return;
          }

          var $formGroup = $input.closest(".form-group");
          var value = $input.val();
          if (value) {
            $formGroup.removeClass("is-empty");
          } else {
            $formGroup.addClass("is-empty");
          }
        })
        // set the fileinput readonly field with the name of the file
        .on("change", ".form-group.is-fileinput input[type='file']", function () {
          var $input = $(this);
          var $formGroup = $input.closest(".form-group");
          var value = "";
          $.each(this.files, function (i, file) {
            value += file.name + ", ";
          });
          value = value.substring(0, value.length - 2);
          if (value) {
            $formGroup.removeClass("is-empty");
          } else {
            $formGroup.addClass("is-empty");
          }
          $formGroup.find("input.form-control[readonly]").val(value);
        });
    },
    "ripples": function (selector) {
      $((selector) ? selector : this.options.withRipples).ripples();
    },
    "autofill": function () {
      // This part of code will detect autofill when the page is loading (username and password inputs for example)
      var loading = setInterval(function () {
        $("input[type!=checkbox]").each(function () {
          var $this = $(this);
          if ($this.val() && $this.val() !== $this.attr("value")) {
            $this.trigger("change");
          }
        });
      }, 100);

      // After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them
      setTimeout(function () {
        clearInterval(loading);
      }, 10000);
    },
    "attachAutofillEventHandlers": function () {
      // Listen on inputs of the focused form (because user can select from the autofill dropdown only when the input has focus)
      var focused;
      $(document)
        .on("focus", "input", function () {
          var $inputs = $(this).parents("form").find("input").not("[type=file]");
          focused = setInterval(function () {
            $inputs.each(function () {
              var $this = $(this);
              if ($this.val() !== $this.attr("value")) {
                $this.trigger("change");
              }
            });
          }, 100);
        })
        .on("blur", ".form-group input", function () {
          clearInterval(focused);
        });
    },
    "init": function (options) {
      this.options = $.extend({}, this.options, options);
      var $document = $(document);

      if ($.fn.ripples && this.options.ripples) {
        this.ripples();
      }
      if (this.options.input) {
        this.input();
        this.attachInputEventHandlers();
      }
      if (this.options.checkbox) {
        this.checkbox();
      }
      if (this.options.togglebutton) {
        this.togglebutton();
      }
      if (this.options.radio) {
        this.radio();
      }
      if (this.options.autofill) {
        this.autofill();
        this.attachAutofillEventHandlers();
      }

      if (document.arrive && this.options.arrive) {
        if ($.fn.ripples && this.options.ripples) {
          $document.arrive(this.options.withRipples, function () {
            $.material.ripples($(this));
          });
        }
        if (this.options.input) {
          $document.arrive(this.options.inputElements, function () {
            $.material.input($(this));
          });
        }
        if (this.options.checkbox) {
          $document.arrive(this.options.checkboxElements, function () {
            $.material.checkbox($(this));
          });
        }
        if (this.options.radio) {
          $document.arrive(this.options.radioElements, function () {
            $.material.radio($(this));
          });
        }
        if (this.options.togglebutton) {
          $document.arrive(this.options.togglebuttonElements, function () {
            $.material.togglebutton($(this));
          });
        }

      }
    }
  };

})(jQuery);
<commit_msg>Delete material.js<commit_after><|endoftext|>


<commit_before>var bigAssApi = require("BigAssFansAPI");
bigAssApi.logging = false;

var Service, Characteristic;

module.exports = function(homebridge) {
  // Platform Accessory must be created from PlatformAccessory Constructor
  PlatformAccessory = homebridge.platformAccessory;
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  UUIDGen = homebridge.hap.uuid;

  homebridge.registerPlatform("homebridge-bigAssFans", "BigAssFans", BigAssFansPlatform, true);
  homebridge.registerAccessory("homebridge-bigAssFan", "BigAssFan", BigAssFanAccessory);
}

function BigAssFansPlatform(log, config, api) {
  log("Big Ass Fans Platform Init");
  var platform = this;
  this.log = log;
  this.config = config;
  this.accessories = [];
  this.accessoriesHashed = {};
  this.numberOfFans = config ? (config.fan_count || 1) : 1;
  this.fanMaster = new bigAssApi.FanMaster(this.numberOfFans);

  this.fanMaster.onFanFullyUpdated = function(myBigAss){
    platform.addAccessory(myBigAss);
  }

  if (api) {
      // Save the API object as plugin needs to register new accessory via this object.
      this.api = api;

      // Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories
      // Platform Plugin should only register new accessory that doesn't exist in homebridge after this event.
      // Or start discover new accessories
      this.api.on('didFinishLaunching', function() {
        platform.log("DidFinishLaunching");
      }.bind(this));
  }
}

// Function invoked when homebridge tries to restore cached accessory
// Developer can configure accessory at here (like setup event handler)
// Update current value
BigAssFansPlatform.prototype.configureAccessory = function(accessory) {
  this.log(accessory.displayName, "Configure Accessory", accessory.UUID);
  var platform = this;

  // set the accessory to reachable if plugin can currently process the accessory
  // otherwise set to false and update the reachability later by invoking
  // accessory.updateReachability()
  accessory.reachable = false;

  accessory.on('identify', function(paired, callback) {
    platform.log(accessory.displayName, "Identify!!!");
    callback();
  });

  this.accessoriesHashed[accessory.UUID] = accessory;
}

// Sample function to show how developer can add accessory dynamically from outside event
BigAssFansPlatform.prototype.addAccessory = function(theFan) {
  var platform = this;
  var newAccessory;
  var uuid;

  var doctoredConfig = {
    "name"               : theFan.name,
    "fan_name"           : theFan.name,
    "fan_id"             : theFan.id,
    "fan_ip_address"     : theFan.address,
    "light_on"           : platform.config.light_on,
    "fan_on"             : platform.config.fan_on,
    "homekit_fan_name"   : platform.config.homekit_fan_name,
    "homekit_light_name" : platform.config.homekit_light_name,
    "fan_master"         : theFan.master,
  }

  //Check if we have a cached accessory and link it to runtime fan instance, or create new service
  uuid = UUIDGen.generate(theFan.name);

  if (platform.accessoriesHashed[uuid]) {
    newAccessory = platform.accessoriesHashed[uuid];
  } else {
    newAccessory = new PlatformAccessory(theFan.name, uuid);
  }

  var newInnerFanAccessory = new BigAssFanAccessory(platform.log, doctoredConfig, newAccessory);

  // Adds "identify" functionality
  newAccessory.on('identify', function(paired, callback) {
    platform.log(newAccessory.displayName, "Identified fan (homekit setup)");
    callback();
  });

  // Plugin can save context on accessory
  // To help restore accessory in configureAccessory()
  newAccessory.context.doctoredConfig = this.fan_ip_address;

  this.accessories.push(newAccessory);
  if (!platform.accessoriesHashed[uuid]) {
    this.api.registerPlatformAccessories("homebridge-bigAssFans", "BigAssFans", [newAccessory]);
  }
}

// Sample function to show how developer can remove accessory dynamically from outside event
BigAssFansPlatform.prototype.removeAccessory = function() {
  this.log("Remove Accessory");
  this.api.unregisterPlatformAccessories("homebridge-bigAssFans", "BigAssFans", this.accessories);

  this.accessories = [];
}

function BigAssFanAccessory(log, config, existingAccessory) {
  this.log              = log;
  this.name             = config["name"];
  this.fanName          = config["fan_name"];        // TODO: Allow this to be null
  this.fanID            = config["fan_id"];
  this.fanIPAddress     = config["fan_ip_address"];  // Can be null - resorts to broadcasting
  this.lightOn          = config["light_on"];        // Can be null - default is below
  this.fanOn            = config["fan_on"];          // Can be null - default is below
  this.homekitFanName   = config["homekit_fan_name"]
  this.homekitLightName = config["homekit_light_name"]
  this.fanMaster        = config["fan_master"]       // Can NOT be entered by user

  // Set defaults
  var setDefault = function(property, value) {
    if (!this[property]) {this[property] = value}
  }.bind(this);

  setDefault("fanIPAddress", "255.255.255.255");
  setDefault("lightOn", 16);
  setDefault("fanOn", 3);

  setDefault("name", this.fanName);
  setDefault("homekitFanName", this.name + " Fan");
  setDefault("homekitLightName", this.name + " Fan Light");
  setDefault("homekitOccupancyName", this.name + " Occupancy Sensor");
  // Don't scan for any fans since we know the exact address of the fan (faster!)
  // TODO: Make fan_id optional and do the scan for the user
  if (!this.fanMaster) {
    this.fanMaster = new bigAssApi.FanMaster(this.numberOfFans);
  }

  // Put in exact information for the fan you're trying to reach
  this.myBigAss = new bigAssApi.BigAssFan(this.fanName, this.fanID, this.fanIPAddress, this.fanMaster);

  // this.myBigAss.updateAll();

  var setCharacteristicOnService = function(service,
                                            characteristic,
                                            propertyToWrap,
                                            subProperty,
                                            getOutputMapping,
                                            setOutputMapping) {

    var thisChar = service.getCharacteristic(characteristic)

    if (getOutputMapping) {
      thisChar.on('get', this.getStateFactory(propertyToWrap,
                                              subProperty,
                                              getOutputMapping).bind(this));

      // Register for updates outside of the homekit system
      this.myBigAss[propertyToWrap].registerUpdateCallback(subProperty, function(newValue) {
        if (thisChar.emit) {
          // Emit this change to the homekit system
          // oldValue : Grabs the old cached value for this characterisitic
          // newValue : The value we just recieved
          // context  : Gives context so that whoever requested the update doesn't recieve it.
          //            In this case we need everyone to get the update
          thisChar.emit('change', {
              oldValue:thisChar.value,
              newValue:getOutputMapping(newValue),
              context:null });
        }
      });
    }

    if (setOutputMapping) {
      thisChar.on('set', this.setStateFactory(propertyToWrap,
                                              subProperty,
                                              setOutputMapping).bind(this));
    }

  }.bind(this);

  /********************************************
   * Wrappers for various get and set functions
   ********************************************/

  var passThroughWrapper = function(value) {
    return value;
  }

  var setScalingWrapper = function(maxValue) {
    return function(value) {
      return Math.round(value * maxValue / 100);
    }
  }

  var getScalingWrapper = function(maxValue) {
    return function(value) {
      return Math.round(value * 100 / maxValue);
    }
  }

  var boolGetWrapper = function(value) {
    return value > 0;
  }

  var lightSetWrapper = function(value) {
    // We define that returning null will ignore the command
    // For some reason homekit likes to send extraneous "On" commands
    if (value && this.myBigAss.light.brightness > 0) {
      return null;
    }
    return (value ? this.lightOn : 0);
  }.bind(this)

  var fanSetWrapper = function(value) {
    // We define that returning null will ignore the command
    // For some reason homekit likes to send extraneous "On" commands
    if (value && this.myBigAss.fan.speed > 0) {
      return null;
    }
    return (value ? this.fanOn : 0);
  }.bind(this)

  var fanRotationSetWrapper = function(value) {
    return value == Characteristic.RotationDirection.COUNTER_CLOCKWISE;
  }

  var fanRotationGetWrapper = function(value) {
    return (value ? Characteristic.RotationDirection.COUNTER_CLOCKWISE : Characteristic.RotationDirection.CLOCKWISE);
  }

  var occupancyGetWrapper = function(value) {
    return (value ? Characteristic.OccupancyDetected.OCCUPANCY_DETECTED : Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED);
  }

  var lightMaxBrightness = this.myBigAss.light.max ? this.myBigAss.light.max : 16;
  var fanMaxSpeed        = this.myBigAss.fan.max ? this.myBigAss.fan.max : 7;

  var existingLightBulbService;
  if (existingAccessory){
    existingLightBulbService = existingAccessory.getService(this.homekitLightName);
  }
  this.lightService = existingLightBulbService || new Service.Lightbulb(this.homekitLightName);

  setCharacteristicOnService(this.lightService, Characteristic.On,
                             "light", "brightness",
                             boolGetWrapper, lightSetWrapper)

  setCharacteristicOnService(this.lightService, Characteristic.Brightness,
                             "light", "brightness",
                             getScalingWrapper(lightMaxBrightness), setScalingWrapper(lightMaxBrightness))

  if (existingAccessory && !existingLightBulbService){
    existingAccessory.addService(this.lightService);
  }
  var existingFanService;
  if (existingAccessory){
    existingFanService = existingAccessory.getService(this.homekitFanName);
  }
  this.fanService = existingFanService || new Service.Fan(this.homekitFanName);

  setCharacteristicOnService(this.fanService, Characteristic.On,
                             "fan", "speed",
                             boolGetWrapper, fanSetWrapper)

  setCharacteristicOnService(this.fanService, Characteristic.RotationDirection,
                             "fan", "isSpinningForwards",
                             fanRotationGetWrapper, fanRotationSetWrapper)

  setCharacteristicOnService(this.fanService, Characteristic.RotationSpeed,
                             "fan", "speed",
                             getScalingWrapper(fanMaxSpeed), setScalingWrapper(fanMaxSpeed))
  if (existingAccessory && !existingFanService){
    existingAccessory.addService(this.fanService);
  }
/*
  var existingOccupancyService;
  if (existingAccessory){
    existingOccupancyService = existingAccessory.getService(this.homekitOccupancyName);
  }

  this.occupancyService = existingOccupancyService || new Service.OccupancySensor(this.homekitOccupancyName);

  setCharacteristicOnService(this.occupancyService, Characteristic.OccupancyDetected,
                              "room", "isOccupied",
                              occupancyGetWrapper, null)

  if (existingAccessory && !existingOccupancyService){
    existingAccessory.addService(this.occupancyService);
  }
*/
  this.getServices = function() {
    return [this.lightService, this.fanService]; //this.occupancyService];
  }
  if (existingAccessory){
    existingAccessory.updateReachability(true);
  }
}

BigAssFanAccessory.prototype.getStateFactory = function(propertyToWrap, subProperty, outputMapping) {
  return function(callback) {
    this.myBigAss[propertyToWrap].update(subProperty, function(err, value) {
      var returnVal = outputMapping(value);
      callback(err, returnVal);
    });
  }
}

BigAssFanAccessory.prototype.setStateFactory = function(propertyToWrap, subProperty, outputMapping) {
  return function(state, callback) {
    var valToSet = outputMapping(state)
    if (valToSet != null) {
      this.myBigAss[propertyToWrap].setProperty(subProperty, valToSet, function(err) {
        callback(err);
      });
    } else {
      // If null is returned, ignore the command
      callback(null);
    }
  }
}

<commit_msg>Don't init Platform is no config is defined for it<commit_after>var bigAssApi = require("BigAssFansAPI");
bigAssApi.logging = false;

var Service, Characteristic;

module.exports = function(homebridge) {
  // Platform Accessory must be created from PlatformAccessory Constructor
  PlatformAccessory = homebridge.platformAccessory;
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  UUIDGen = homebridge.hap.uuid;

  homebridge.registerPlatform("homebridge-bigAssFans", "BigAssFans", BigAssFansPlatform, true);
  homebridge.registerAccessory("homebridge-bigAssFan", "BigAssFan", BigAssFanAccessory);
}

function BigAssFansPlatform(log, config, api) {
  // If config is not specified do not attempt to load platform
  // or else it will eat the port.
  if (!config) {
    return;
  }
  log("Big Ass Fans Platform Init");
  var platform = this;
  this.log = log;
  this.config = config;
  this.accessories = [];
  this.accessoriesHashed = {};
  this.numberOfFans = config ? (config.fan_count || 1) : 1;
  this.fanMaster = new bigAssApi.FanMaster(this.numberOfFans);

  this.fanMaster.onFanFullyUpdated = function(myBigAss){
    platform.addAccessory(myBigAss);
  }

  if (api) {
      // Save the API object as plugin needs to register new accessory via this object.
      this.api = api;

      // Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories
      // Platform Plugin should only register new accessory that doesn't exist in homebridge after this event.
      // Or start discover new accessories
      this.api.on('didFinishLaunching', function() {
        platform.log("DidFinishLaunching");
      }.bind(this));
  }
}

// Function invoked when homebridge tries to restore cached accessory
// Developer can configure accessory at here (like setup event handler)
// Update current value
BigAssFansPlatform.prototype.configureAccessory = function(accessory) {
  this.log(accessory.displayName, "Configure Accessory", accessory.UUID);
  var platform = this;

  // set the accessory to reachable if plugin can currently process the accessory
  // otherwise set to false and update the reachability later by invoking
  // accessory.updateReachability()
  accessory.reachable = false;

  accessory.on('identify', function(paired, callback) {
    platform.log(accessory.displayName, "Identify!!!");
    callback();
  });

  this.accessoriesHashed[accessory.UUID] = accessory;
}

// Sample function to show how developer can add accessory dynamically from outside event
BigAssFansPlatform.prototype.addAccessory = function(theFan) {
  var platform = this;
  var newAccessory;
  var uuid;

  var doctoredConfig = {
    "name"               : theFan.name,
    "fan_name"           : theFan.name,
    "fan_id"             : theFan.id,
    "fan_ip_address"     : theFan.address,
    "light_on"           : platform.config.light_on,
    "fan_on"             : platform.config.fan_on,
    "homekit_fan_name"   : platform.config.homekit_fan_name,
    "homekit_light_name" : platform.config.homekit_light_name,
    "fan_master"         : theFan.master,
  }

  //Check if we have a cached accessory and link it to runtime fan instance, or create new service
  uuid = UUIDGen.generate(theFan.name);

  if (platform.accessoriesHashed[uuid]) {
    newAccessory = platform.accessoriesHashed[uuid];
  } else {
    newAccessory = new PlatformAccessory(theFan.name, uuid);
  }

  var newInnerFanAccessory = new BigAssFanAccessory(platform.log, doctoredConfig, newAccessory);

  // Adds "identify" functionality
  newAccessory.on('identify', function(paired, callback) {
    platform.log(newAccessory.displayName, "Identified fan (homekit setup)");
    callback();
  });

  // Plugin can save context on accessory
  // To help restore accessory in configureAccessory()
  newAccessory.context.doctoredConfig = this.fan_ip_address;

  this.accessories.push(newAccessory);
  if (!platform.accessoriesHashed[uuid]) {
    this.api.registerPlatformAccessories("homebridge-bigAssFans", "BigAssFans", [newAccessory]);
  }
}

// Sample function to show how developer can remove accessory dynamically from outside event
BigAssFansPlatform.prototype.removeAccessory = function() {
  this.log("Remove Accessory");
  this.api.unregisterPlatformAccessories("homebridge-bigAssFans", "BigAssFans", this.accessories);

  this.accessories = [];
}

function BigAssFanAccessory(log, config, existingAccessory) {
  this.log              = log;
  this.name             = config["name"];
  this.fanName          = config["fan_name"];        // TODO: Allow this to be null
  this.fanID            = config["fan_id"];
  this.fanIPAddress     = config["fan_ip_address"];  // Can be null - resorts to broadcasting
  this.lightOn          = config["light_on"];        // Can be null - default is below
  this.fanOn            = config["fan_on"];          // Can be null - default is below
  this.homekitFanName   = config["homekit_fan_name"]
  this.homekitLightName = config["homekit_light_name"]
  this.fanMaster        = config["fan_master"]       // Can NOT be entered by user

  // Set defaults
  var setDefault = function(property, value) {
    if (!this[property]) {this[property] = value}
  }.bind(this);

  setDefault("fanIPAddress", "255.255.255.255");
  setDefault("lightOn", 16);
  setDefault("fanOn", 3);

  setDefault("name", this.fanName);
  setDefault("homekitFanName", this.name + " Fan");
  setDefault("homekitLightName", this.name + " Fan Light");
  setDefault("homekitOccupancyName", this.name + " Occupancy Sensor");
  // Don't scan for any fans since we know the exact address of the fan (faster!)
  // TODO: Make fan_id optional and do the scan for the user
  if (!this.fanMaster) {
    this.fanMaster = new bigAssApi.FanMaster(this.numberOfFans);
  }

  // Put in exact information for the fan you're trying to reach
  this.myBigAss = new bigAssApi.BigAssFan(this.fanName, this.fanID, this.fanIPAddress, this.fanMaster);

  // this.myBigAss.updateAll();

  var setCharacteristicOnService = function(service,
                                            characteristic,
                                            propertyToWrap,
                                            subProperty,
                                            getOutputMapping,
                                            setOutputMapping) {

    var thisChar = service.getCharacteristic(characteristic)

    if (getOutputMapping) {
      thisChar.on('get', this.getStateFactory(propertyToWrap,
                                              subProperty,
                                              getOutputMapping).bind(this));

      // Register for updates outside of the homekit system
      this.myBigAss[propertyToWrap].registerUpdateCallback(subProperty, function(newValue) {
        if (thisChar.emit) {
          // Emit this change to the homekit system
          // oldValue : Grabs the old cached value for this characterisitic
          // newValue : The value we just recieved
          // context  : Gives context so that whoever requested the update doesn't recieve it.
          //            In this case we need everyone to get the update
          thisChar.emit('change', {
              oldValue:thisChar.value,
              newValue:getOutputMapping(newValue),
              context:null });
        }
      });
    }

    if (setOutputMapping) {
      thisChar.on('set', this.setStateFactory(propertyToWrap,
                                              subProperty,
                                              setOutputMapping).bind(this));
    }

  }.bind(this);

  /********************************************
   * Wrappers for various get and set functions
   ********************************************/

  var passThroughWrapper = function(value) {
    return value;
  }

  var setScalingWrapper = function(maxValue) {
    return function(value) {
      return Math.round(value * maxValue / 100);
    }
  }

  var getScalingWrapper = function(maxValue) {
    return function(value) {
      return Math.round(value * 100 / maxValue);
    }
  }

  var boolGetWrapper = function(value) {
    return value > 0;
  }

  var lightSetWrapper = function(value) {
    // We define that returning null will ignore the command
    // For some reason homekit likes to send extraneous "On" commands
    if (value && this.myBigAss.light.brightness > 0) {
      return null;
    }
    return (value ? this.lightOn : 0);
  }.bind(this)

  var fanSetWrapper = function(value) {
    // We define that returning null will ignore the command
    // For some reason homekit likes to send extraneous "On" commands
    if (value && this.myBigAss.fan.speed > 0) {
      return null;
    }
    return (value ? this.fanOn : 0);
  }.bind(this)

  var fanRotationSetWrapper = function(value) {
    return value == Characteristic.RotationDirection.COUNTER_CLOCKWISE;
  }

  var fanRotationGetWrapper = function(value) {
    return (value ? Characteristic.RotationDirection.COUNTER_CLOCKWISE : Characteristic.RotationDirection.CLOCKWISE);
  }

  var occupancyGetWrapper = function(value) {
    return (value ? Characteristic.OccupancyDetected.OCCUPANCY_DETECTED : Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED);
  }

  var lightMaxBrightness = this.myBigAss.light.max ? this.myBigAss.light.max : 16;
  var fanMaxSpeed        = this.myBigAss.fan.max ? this.myBigAss.fan.max : 7;

  var existingLightBulbService;
  if (existingAccessory){
    existingLightBulbService = existingAccessory.getService(this.homekitLightName);
  }
  this.lightService = existingLightBulbService || new Service.Lightbulb(this.homekitLightName);

  setCharacteristicOnService(this.lightService, Characteristic.On,
                             "light", "brightness",
                             boolGetWrapper, lightSetWrapper)

  setCharacteristicOnService(this.lightService, Characteristic.Brightness,
                             "light", "brightness",
                             getScalingWrapper(lightMaxBrightness), setScalingWrapper(lightMaxBrightness))

  if (existingAccessory && !existingLightBulbService){
    existingAccessory.addService(this.lightService);
  }
  var existingFanService;
  if (existingAccessory){
    existingFanService = existingAccessory.getService(this.homekitFanName);
  }
  this.fanService = existingFanService || new Service.Fan(this.homekitFanName);

  setCharacteristicOnService(this.fanService, Characteristic.On,
                             "fan", "speed",
                             boolGetWrapper, fanSetWrapper)

  setCharacteristicOnService(this.fanService, Characteristic.RotationDirection,
                             "fan", "isSpinningForwards",
                             fanRotationGetWrapper, fanRotationSetWrapper)

  setCharacteristicOnService(this.fanService, Characteristic.RotationSpeed,
                             "fan", "speed",
                             getScalingWrapper(fanMaxSpeed), setScalingWrapper(fanMaxSpeed))
  if (existingAccessory && !existingFanService){
    existingAccessory.addService(this.fanService);
  }
/*
  var existingOccupancyService;
  if (existingAccessory){
    existingOccupancyService = existingAccessory.getService(this.homekitOccupancyName);
  }

  this.occupancyService = existingOccupancyService || new Service.OccupancySensor(this.homekitOccupancyName);

  setCharacteristicOnService(this.occupancyService, Characteristic.OccupancyDetected,
                              "room", "isOccupied",
                              occupancyGetWrapper, null)

  if (existingAccessory && !existingOccupancyService){
    existingAccessory.addService(this.occupancyService);
  }
*/
  this.getServices = function() {
    return [this.lightService, this.fanService]; //this.occupancyService];
  }
  if (existingAccessory){
    existingAccessory.updateReachability(true);
  }
}

BigAssFanAccessory.prototype.getStateFactory = function(propertyToWrap, subProperty, outputMapping) {
  return function(callback) {
    this.myBigAss[propertyToWrap].update(subProperty, function(err, value) {
      var returnVal = outputMapping(value);
      callback(err, returnVal);
    });
  }
}

BigAssFanAccessory.prototype.setStateFactory = function(propertyToWrap, subProperty, outputMapping) {
  return function(state, callback) {
    var valToSet = outputMapping(state)
    if (valToSet != null) {
      this.myBigAss[propertyToWrap].setProperty(subProperty, valToSet, function(err) {
        callback(err);
      });
    } else {
      // If null is returned, ignore the command
      callback(null);
    }
  }
}

<|endoftext|>


<commit_before>/*!
 * jqtimeline Plugin
 * http://goto.io/jqtimeline
 *
 * Copyright 2013 goto.io
 * Released under the MIT license
 *
 */
;
(function($) {
	var pluginName = 'myJqueryTimeline',
		defaults = {
			startYear : (new Date()).getFullYear() -1 , // Start with one less year by default
			numYears : 4,
			showToolTip : true,
			events : [],
			click : null //Handler for click event for event
		};
	months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	shortMonths = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];

	function myJqueryTimeline(element, options) {
		this.options = $.extend({}, defaults, options);
		this._name = pluginName;
		this._el=$(element);
		this._timelineArea;
		this._yearsEl;
		this._monthsEl;
		this._eventsEl=new Array();
		this._tooltipEl;
		//gapMonth= 100%/(12meses * Nr anos)
		this.options.gapMonth=100/(12*this.options.numYears);
		//console.log(this.options.gapMonth);
		this.init();

	}

//INIT DA TIMELINE
	myJqueryTimeline.prototype.init = function() {
		var _this=this;
		//Desenha timeline
		this.drawTimeline();
		//Desenha Years and Save elms in _yearsEl
		this._yearsEl=_this.drawYears();

		this._yearsEl=_this.drawYearsTags();

		this._yearsEl=_this.drawMonths();

		this._yearsEl=_this.drawMonthsTags();

		this._eventsEl=_this.drawEvents();

		if(_this.options.click){
			this._eventsEl.map(
			function( elm, ind ) {
				elm.obj.on('click',function(e){
					var $t = _this.getEventEl($(e.target));
					if($t.obj.hasClass('eventBar')){
						//console.log(_this.getEventEl($t));
						_this.options.click(e,_this.options.events[$t.obj.attr("evt")]);
					}
					});
	        	});
			}

		//this._el.on("change",function(){alert("aa");});

	};
//DESENHA A LINHA PRINCIPAL DA TIMELINE QUE OCUPA 100% DO ELEMENTO PAI
	myJqueryTimeline.prototype.drawTimeline=function(){
		var _this=this;
		//cria elemento da linha principal
		_this._timelineArea=$("

");
		// faz append no elemento enviado por parametro para o construtor
		_this._el.append(_this._timelineArea);
		//Desenha agora as barras correspondentes dos anos

	};

//DESENHA AS BARRAS CORRESPONDENTES AOS ANOS
	myJqueryTimeline.prototype.drawYears = function(){
		var _this=this;
		var _yearMark;
		//calcula espaço entre anos para vários tamanhos de display
		var _gap= 100/_this.options.numYears;
		var _yearsPos=new Array();
		//Para cada ano passados nas options é desenhada uma linha
		for(i=0;i<_this.options.numYears;i++){
			_yearMark=$("

");
			_yearMark.css("margin-left",(i*_gap)+"%");
			_yearMark.css("width",_gap+"%");
			//Push no array das posicoes das barras verticais dos anos
			//passa objs tipo {pos:posicao da linha  ,  obj: objectos jquery com linha do ano}
			_yearsPos.push({"pos":i*_gap,"obj":_yearMark});

			_this._timelineArea.append(_yearMark);
		}
		//desenha a ultima linha que termina a timeline
		_yearMark=$("

");
		_yearMark.css("margin-left",99.55+"%");

		//Push no array das posicoes das barras verticais dos anos
		//passa objs tipo {pos:posicao da linha  ,  obj: objectos jquery com linha do ano}
		_yearsPos.push({"pos":(_this._timelineArea.width()-3),"obj":_yearMark});

		_this._timelineArea.append(_yearMark);
		//retorna array com pos para colocar tags dos anos
		return _yearsPos;
	};

	myJqueryTimeline.prototype.drawYearsTags=function(){
		var _this=this;

		var _yearTag;
		for(i=0;i<_this.options.numYears+1;i++){
			_yearTag=$("
"+parseInt(_this.options.startYear+i)+"
");
			_this._yearsEl[i].obj.append(_yearTag);

		}
		return _this._yearsEl;
	};

	myJqueryTimeline.prototype.drawMonths= function(){
		var _this=this;
		var _monthMark;
		var _gap=100/12;
		var _monthsEl;

		for(i=0;i<_this.options.numYears;i++){
			_monthsEl=new Array();
			for(j=0;j<12;j++){
				j==0 ? _monthMark=$("

") : _monthMark=$("

");
				_monthMark.css("left",(j*_gap)+"%");

				_monthsEl.push({"pos":(j*_gap)+"%","obj":_monthMark});
				_this._yearsEl[i].obj.append(_monthMark);

			}
			_this._yearsEl[i].months=_monthsEl;
		}
		return _this._yearsEl;
	};

	myJqueryTimeline.prototype.drawMonthsTags=function(){
		var _this=this;
		$(_this._yearsEl).each(function(ind,it){
			$(it.months).each(function(ind1,it1){

				if(_this.options.gapMonth<1){
					_monthTag=$("
"+shortMonths[ind1]+"
");
					it1.obj.append(_monthTag);
				}else{
					_monthTag=$("
"+months[ind1]+"
");

					it1.obj.append(_monthTag);
				}

			});
		});
		return _this._yearsEl;

	};

	myJqueryTimeline.prototype.drawEvents=function(){
		var _this=this;
		//console.log(_this);
		$(_this.options.events).each(function(ind,it){
			//if(it.start.getFullYear()>_this.options.startYear && it.end.getFullYear()<_this.options.startYear+_this.options.numYears){
			if(_this.checkDate(it.start,it.end)){
				var mult=0.4;
				var _posWidth=_this.getEventPositionWidth(it.start,it.end);
				var _eventBar={};
				_eventBar.start=_posWidth.start;
				_eventBar.end=_posWidth.end;
				_eventBar.obj=$("

").css("left",(_posWidth.start*_this.options.gapMonth)+"%");
				_eventBar.obj.css("width",(_posWidth.end*_this.options.gapMonth)+"%");
				_eventBar.obj.css("margin-left","2px");
				//console.log(_eventBar.css("left").split("%")[0]);
				//_eventBar.css("margin-left",_eventBar.css("margin-left").split("%")[0]+2+"px");
				_this.addEventListner(_eventBar.obj,"click");
				_this.addEventListner(_eventBar.obj,"mouseover");
				_this.addEventListner(_eventBar.obj,"mouseleave");

				_this._eventsEl.push(_eventBar);

				_this._timelineArea.append(_eventBar.obj);
			}

					//if(it.end.next(it.start)){alert("depois")}
					//if(it.start.getMonth()<it.end.getMonth()){

				//	}

		});
		return _this._eventsEl;
	};

	myJqueryTimeline.prototype.getEventPositionWidth=function(start,end){
		var _this=this;
		var _ret={};
		for(i=0;i<_this.options.numYears;i++){
			if((start.getFullYear()==_this.options.startYear+i)){
				_ret.start=((i*12)+(start.getMonth()));
			}
			if((end.getFullYear()==_this.options.startYear+i)){
				_ret.end=((i*12)+(end.getMonth()))-_ret.start;
				//_ret.end=((i*12)+(start.getMonth()-1))-_ret.start;
			}

		}
		//console.log(_ret);
		return _ret;

	};

	myJqueryTimeline.prototype.addEventListner = function($retHtml,sEvent){
		var _this = this;
		if(sEvent == 'mouseover'){
			$retHtml.mouseover(
				function(e){
					//get id elemente

					if($(e.target).hasClass("eventBar")  ){

						//console.log(_evts);

						var _tgt=_this.getEventEl($(e.target));

						var _evts=_this.getNearEvents(_tgt);
						console.log(_evts);

						$(_evts).each(function(ind,it){

						});
						var evento=_this.options.events[_tgt.obj.attr("evt")];
						var offset=_tgt.obj.offset();

						if(_tgt.obj.find(".tooltipTime").length==0){
							var _tooltipEl=$("

");
							_tooltipEl.append("
"+evento.title+"
");
							_tooltipEl.append("

");
							_tooltipEl.css("left",e.pageX-offset.left-50);
							_tgt.obj.append(_tooltipEl);
							_tooltipEl.fadeIn();

						}

					}
					//get event from array

				}
			);
		}
		if(sEvent == 'mouseleave'){
			$retHtml.mouseleave(function(e){
				if($(e.target).hasClass("eventBar")){

						var _tgt=_this.getEventEl($(e.target));
						_tgt.obj.find(".tooltipTime").fadeOut();
						setTimeout(function(){
							_tgt.obj.find(".tooltipTime").remove();
						},300);
				}
			});
		}
		if(sEvent == 'click'){
		// Attach a click event handler to event objects
			$retHtml.click(function(e){
				var _tgt=_this.getEventEl($(e.target));
				//deselect actual

				if(_tgt.obj.hasClass("eventBar")){
					var _actTgt=_this.getEventElActive();
					if(_actTgt){
						_actTgt.obj.toggleClass("active");
					}

					//console.log(_this._eventsEl.find(".active"));
					_tgt.obj.toggleClass("active");

				}
				//console.log(_tgt);
			});
		}
	};

	myJqueryTimeline.prototype.getNearEvents=function(tgt){
		var _this=this;
		//console.log(tgt);
		var _ret=new Array();
		$(_this._eventsEl).each(function(i,it){

			if(tgt.obj!=it.obj){
				//console.log(it.start, (it.start+it.end),tgt.start, tgt.start+tgt.end);
				if(it.start>tgt.start && it.start<(tgt.start+tgt.end)){
					_ret.push(it);
				}
				if((it.start+it.end)>tgt.start && (it.start+it.end)<(tgt.start+tgt.end)){
					_ret.push(it);
				}
			}
		});
		return _ret;
	}

	myJqueryTimeline.prototype.getEventEl=function(tgt){
		var _this=this;
		var _ret=null;
		$(_this._eventsEl).each(function(ind,it){
			if(it.obj.attr("evt")===tgt.attr("evt")){
				_ret=it;
			}
		});
		return _ret;
	}
	myJqueryTimeline.prototype.getEventElActive=function(){
		var _this=this;
		var _ret=null;
		$(_this._eventsEl).each(function(ind,it){
			if(it.obj.hasClass("active")){
				_ret=it;
			}
		});
		return _ret;
	};

	myJqueryTimeline.prototype.drawToolTip=function(tgt,evento){
		var _this=this;

			var _tooltipEl=$("

");
			_tooltipEl.append("
"+evento.title+"
");
			_tooltipEl.append("

");

		//$(".tooltipTimeTitle").html(evento);

	};
	myJqueryTimeline.prototype.removeToolTip=function(){
		var _this=this;
		setTimeout(
			function(){}
		);
			_this._el.find(".tooltipTime").fadeOut();
			_this._el.find(".tooltipTime").remove();

	};

	myJqueryTimeline.prototype.checkDate=function(start,end){
		var _this=this;
		if(start.getFullYear()>_this.options.startYear && end.getFullYear()<_this.options.startYear+_this.options.numYears){
			if(start.getFullYear()<end.getFullYear()){
				return true;
			}
			if(start.getFullYear()==end.getFullYear()){
				if(start.getMonth()<end.getMonth()){
					//console.log(start.getMonth(),end.getMonth());
					return true;
				}
			}
		}
		return false;
	};

	$.fn.myJqueryTimeline = function(options) {
		return this.each(function() {
			var timeline = new myJqueryTimeline(this, options);
		});
	};

}(jQuery, window));

	<commit_msg>2 commit<commit_after>/*!
 * MyjqueryTimeline Plugin
 *
 */
;
(function($) {
	var pluginName = 'myJqueryTimeline',
		defaults = {
			startYear : (new Date()).getFullYear() -1 , // Start with one less year by default
			numYears : 4,
			showToolTip : true,
			events : [],
			click : null //Handler for click event for event
		};
	months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	shortMonths = ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];

	function myJqueryTimeline(element, options) {
		this.options = $.extend({}, defaults, options);
		this._name = pluginName;
		this._el=$(element);
		this._timelineArea;
		this._yearsEl;
		this._monthsEl;
		this._eventsEl=new Array();
		this._tooltipEl;
		//gapMonth= 100%/(12meses * Nr anos)
		this.options.gapMonth=100/(12*this.options.numYears);
		//console.log(this.options.gapMonth);
		this.init();

	}

//INIT DA TIMELINE
	myJqueryTimeline.prototype.init = function() {
		var _this=this;
		//Desenha timeline
		this.drawTimeline();
		//Desenha Years and Save elms in _yearsEl
		this._yearsEl=_this.drawYears();

		this._yearsEl=_this.drawYearsTags();

		this._yearsEl=_this.drawMonths();

		this._yearsEl=_this.drawMonthsTags();

		this._eventsEl=_this.drawEvents();

		if(_this.options.click){
			this._eventsEl.map(
			function( elm, ind ) {
				elm.obj.on('click',function(e){
					var $t = _this.getEventEl($(e.target));
					if($t.obj.hasClass('eventBar')){
						//console.log(_this.getEventEl($t));
						_this.options.click(e,_this.options.events[$t.obj.attr("evt")]);
					}
					});
	        	});
			}

		//this._el.on("change",function(){alert("aa");});

	};
//DESENHA A LINHA PRINCIPAL DA TIMELINE QUE OCUPA 100% DO ELEMENTO PAI
	myJqueryTimeline.prototype.drawTimeline=function(){
		var _this=this;
		//cria elemento da linha principal
		_this._timelineArea=$("

");
		// faz append no elemento enviado por parametro para o construtor
		_this._el.append(_this._timelineArea);
		//Desenha agora as barras correspondentes dos anos

	};

//DESENHA AS BARRAS CORRESPONDENTES AOS ANOS
	myJqueryTimeline.prototype.drawYears = function(){
		var _this=this;
		var _yearMark;
		//calcula espaço entre anos para vários tamanhos de display
		var _gap= 100/_this.options.numYears;
		var _yearsPos=new Array();
		//Para cada ano passados nas options é desenhada uma linha
		for(i=0;i<_this.options.numYears;i++){
			_yearMark=$("

");
			_yearMark.css("margin-left",(i*_gap)+"%");
			_yearMark.css("width",_gap+"%");
			//Push no array das posicoes das barras verticais dos anos
			//passa objs tipo {pos:posicao da linha  ,  obj: objectos jquery com linha do ano}
			_yearsPos.push({"pos":i*_gap,"obj":_yearMark});

			_this._timelineArea.append(_yearMark);
		}
		//desenha a ultima linha que termina a timeline
		_yearMark=$("

");
		_yearMark.css("margin-left",99.55+"%");

		//Push no array das posicoes das barras verticais dos anos
		//passa objs tipo {pos:posicao da linha  ,  obj: objectos jquery com linha do ano}
		_yearsPos.push({"pos":(_this._timelineArea.width()-3),"obj":_yearMark});

		_this._timelineArea.append(_yearMark);
		//retorna array com pos para colocar tags dos anos
		return _yearsPos;
	};

	myJqueryTimeline.prototype.drawYearsTags=function(){
		var _this=this;

		var _yearTag;
		for(i=0;i<_this.options.numYears+1;i++){
			_yearTag=$("
"+parseInt(_this.options.startYear+i)+"
");
			_this._yearsEl[i].obj.append(_yearTag);

		}
		return _this._yearsEl;
	};

	myJqueryTimeline.prototype.drawMonths= function(){
		var _this=this;
		var _monthMark;
		var _gap=100/12;
		var _monthsEl;

		for(i=0;i<_this.options.numYears;i++){
			_monthsEl=new Array();
			for(j=0;j<12;j++){
				j==0 ? _monthMark=$("

") : _monthMark=$("

");
				_monthMark.css("left",(j*_gap)+"%");

				_monthsEl.push({"pos":(j*_gap)+"%","obj":_monthMark});
				_this._yearsEl[i].obj.append(_monthMark);

			}
			_this._yearsEl[i].months=_monthsEl;
		}
		return _this._yearsEl;
	};

	myJqueryTimeline.prototype.drawMonthsTags=function(){
		var _this=this;
		$(_this._yearsEl).each(function(ind,it){
			$(it.months).each(function(ind1,it1){

				if(_this.options.gapMonth<1){
					_monthTag=$("
"+shortMonths[ind1]+"
");
					it1.obj.append(_monthTag);
				}else{
					_monthTag=$("
"+months[ind1]+"
");

					it1.obj.append(_monthTag);
				}

			});
		});
		return _this._yearsEl;

	};

	myJqueryTimeline.prototype.drawEvents=function(){
		var _this=this;
		//console.log(_this);
		$(_this.options.events).each(function(ind,it){
			//if(it.start.getFullYear()>_this.options.startYear && it.end.getFullYear()<_this.options.startYear+_this.options.numYears){
			if(_this.checkDate(it.start,it.end)){
				var mult=0.4;
				var _posWidth=_this.getEventPositionWidth(it.start,it.end);
				var _eventBar={};
				_eventBar.start=_posWidth.start;
				_eventBar.end=_posWidth.end;
				_eventBar.obj=$("

").css("left",(_posWidth.start*_this.options.gapMonth)+"%");
				_eventBar.obj.css("width",(_posWidth.end*_this.options.gapMonth)+"%");
				_eventBar.obj.css("margin-left","2px");
				//console.log(_eventBar.css("left").split("%")[0]);
				//_eventBar.css("margin-left",_eventBar.css("margin-left").split("%")[0]+2+"px");
				_this.addEventListner(_eventBar.obj,"click");
				_this.addEventListner(_eventBar.obj,"mouseover");
				_this.addEventListner(_eventBar.obj,"mouseleave");

				_this._eventsEl.push(_eventBar);

				_this._timelineArea.append(_eventBar.obj);
			}

					//if(it.end.next(it.start)){alert("depois")}
					//if(it.start.getMonth()<it.end.getMonth()){

				//	}

		});
		return _this._eventsEl;
	};

	myJqueryTimeline.prototype.getEventPositionWidth=function(start,end){
		var _this=this;
		var _ret={};
		for(i=0;i<_this.options.numYears;i++){
			if((start.getFullYear()==_this.options.startYear+i)){
				_ret.start=((i*12)+(start.getMonth()));
			}
			if((end.getFullYear()==_this.options.startYear+i)){
				_ret.end=((i*12)+(end.getMonth()))-_ret.start;
				//_ret.end=((i*12)+(start.getMonth()-1))-_ret.start;
			}

		}
		//console.log(_ret);
		return _ret;

	};

	myJqueryTimeline.prototype.addEventListner = function($retHtml,sEvent){
		var _this = this;
		if(sEvent == 'mouseover'){
			$retHtml.mouseover(
				function(e){
					//get id elemente

					if($(e.target).hasClass("eventBar")  ){

						//console.log(_evts);

						var _tgt=_this.getEventEl($(e.target));

						var _evts=_this.getNearEvents(_tgt);
						console.log(_evts);

						$(_evts).each(function(ind,it){

						});
						var evento=_this.options.events[_tgt.obj.attr("evt")];
						var offset=_tgt.obj.offset();

						if(_tgt.obj.find(".tooltipTime").length==0){
							var _tooltipEl=$("

");
							_tooltipEl.append("
"+evento.title+"
");
							_tooltipEl.append("

");
							_tooltipEl.css("left",e.pageX-offset.left-50);
							_tgt.obj.append(_tooltipEl);
							_tooltipEl.fadeIn();

						}

					}
					//get event from array

				}
			);
		}
		if(sEvent == 'mouseleave'){
			$retHtml.mouseleave(function(e){
				if($(e.target).hasClass("eventBar")){

						var _tgt=_this.getEventEl($(e.target));
						_tgt.obj.find(".tooltipTime").fadeOut();
						setTimeout(function(){
							_tgt.obj.find(".tooltipTime").remove();
						},300);
				}
			});
		}
		if(sEvent == 'click'){
		// Attach a click event handler to event objects
			$retHtml.click(function(e){
				var _tgt=_this.getEventEl($(e.target));
				//deselect actual

				if(_tgt.obj.hasClass("eventBar")){
					var _actTgt=_this.getEventElActive();
					if(_actTgt){
						_actTgt.obj.toggleClass("active");
					}

					//console.log(_this._eventsEl.find(".active"));
					_tgt.obj.toggleClass("active");

				}
				//console.log(_tgt);
			});
		}
	};

	myJqueryTimeline.prototype.getNearEvents=function(tgt){
		var _this=this;
		//console.log(tgt);
		var _ret=new Array();
		$(_this._eventsEl).each(function(i,it){

			if(tgt.obj!=it.obj){
				//console.log(it.start, (it.start+it.end),tgt.start, tgt.start+tgt.end);
				if(it.start>tgt.start && it.start<(tgt.start+tgt.end)){
					_ret.push(it);
				}
				if((it.start+it.end)>tgt.start && (it.start+it.end)<(tgt.start+tgt.end)){
					_ret.push(it);
				}
			}
		});
		return _ret;
	}

	myJqueryTimeline.prototype.getEventEl=function(tgt){
		var _this=this;
		var _ret=null;
		$(_this._eventsEl).each(function(ind,it){
			if(it.obj.attr("evt")===tgt.attr("evt")){
				_ret=it;
			}
		});
		return _ret;
	}
	myJqueryTimeline.prototype.getEventElActive=function(){
		var _this=this;
		var _ret=null;
		$(_this._eventsEl).each(function(ind,it){
			if(it.obj.hasClass("active")){
				_ret=it;
			}
		});
		return _ret;
	};

	myJqueryTimeline.prototype.drawToolTip=function(tgt,evento){
		var _this=this;

			var _tooltipEl=$("

");
			_tooltipEl.append("
"+evento.title+"
");
			_tooltipEl.append("

");

		//$(".tooltipTimeTitle").html(evento);

	};
	myJqueryTimeline.prototype.removeToolTip=function(){
		var _this=this;
		setTimeout(
			function(){}
		);
			_this._el.find(".tooltipTime").fadeOut();
			_this._el.find(".tooltipTime").remove();

	};

	myJqueryTimeline.prototype.checkDate=function(start,end){
		var _this=this;
		if(start.getFullYear()>_this.options.startYear && end.getFullYear()<_this.options.startYear+_this.options.numYears){
			if(start.getFullYear()<end.getFullYear()){
				return true;
			}
			if(start.getFullYear()==end.getFullYear()){
				if(start.getMonth()<end.getMonth()){
					//console.log(start.getMonth(),end.getMonth());
					return true;
				}
			}
		}
		return false;
	};

	$.fn.myJqueryTimeline = function(options) {
		return this.each(function() {
			var timeline = new myJqueryTimeline(this, options);
		});
	};

}(jQuery, window));

	<|endoftext|>


<commit_before>
import { compact, isEmpty, isObject, isArray, isNumber, isUndefined } from 'lodash'
import Expression from '../expression'

/**
 * @class BaseCompiler
 */
export default class Compiler {

  /**
   * BaseCompiler constructor
   *
   * @constructor
   */
  constructor() {
    this.bindings = []
  }

  /**
   * Default parameter placeholder
   *
   * @type {String}
   */
  get parameter() {
    return '?'
  }

  /**
   *
   * @returns {Array}
   */
  getBindings() {
    return this.bindings
  }

  /**
   *
   * @param {Object} query
   * @returns {String}
   */
  compileSelectComponents(query) {
    var sql = [
      this.compileSelectColumns(query.columns, query),
      this.compileTables(query.tables, query),
      this.compileJoins(query.joins, query),
      this.compileConditions(query.conditions, query),
      this.compileGroups(query.groups, query),
      this.compileHavingConditions(query.havingConditions, query),
      this.compileOrders(query.orders, query),
      this.compileLimit(query.limit, query),
      this.compileOffset(query.offset, query),
    ]

    return compact(sql).join(' ')
  }

  /**
   *
   * @param {Object} query
   * @returns {String}
   */
  compileUnionComponents(query) {
    var sql = [
      this.compileUnions(query.unions, query),
      this.compileOrders(query.unionOrders, query),
      this.compileLimit(query.unionLimit, query),
      this.compileOffset(query.unionOffset, query),
    ]

    return compact(sql).join(' ')
  }

  /**
   * Compile the query columns part
   *
   * @param {Array} columns
   * @param {Object} query
   * @returns {String}
   */
  compileSelectColumns(columns, query) {
    var select = 'select ' + (query.distinct ? 'distinct ' : '')

    return select + this.columnize(isEmpty(columns) ? ['*'] : columns)
  }

  /**
   *
   * @param {Array} tables
   * @param {Object} query
   * @returns {String}
   */
  compileTables(tables, query) {
    if ( isEmpty(tables) ) return

    return 'from ' + tables.map(table => this.escape(table)).join(', ')
  }

  /**
   *
   * @param {Array} joins
   * @param {Object} query
   * @returns {String}
   */
  compileJoins(joins, query) {
    if ( isEmpty(joins) ) return

    return joins.map(join => this.escape(join)).join(' ')
  }

  /**
   *
   * @param {Criteria} conditions
   * @param {Object} query
   * @returns {String}
   */
  compileConditions(conditions, query) {
    if ( conditions.isEmpty() ) return

    return 'where ' + conditions.compile(this)
  }

  /**
   *
   * @param {Array} groups
   * @param {Object} query
   * @returns {String}
   */
  compileGroups(groups, query) {
    if ( isEmpty(groups) ) return

    return 'group by ' + this.columnize(groups)
  }

  /**
   *
   * @param {Criteria} conditions
   * @param {Object} query
   * @returns {String}
   */
  compileHavingConditions(conditions, query) {
    if ( conditions.isEmpty() ) return

    return 'having ' + conditions.compile(this)
  }

  /**
   *
   * @param {Array} orders
   * @param {Object} query
   * @returns {String}
   */
  compileOrders(orders, query) {
    if ( isEmpty(orders) ) return

    return 'order by ' + this.columnize(orders)
  }

  /**
   *
   * @param {Number} limit
   * @param {Object} query
   * @returns {String}
   */
  compileLimit(limit, query) {
    if (! isNumber(limit) ) return

    return 'limit ' + this.parameterize(limit)
  }

  /**
   *
   * @param {Number} offset
   * @param {Object} query
   * @returns {String}
   */
  compileOffset(offset, query) {
    if (! isNumber(offset) ) return

    return 'offset ' + this.parameterize(offset)
  }

  /**
   *
   * @param {Array} unions
   * @param {Object} query
   * @returns {String}
   */
  compileUnions(unions, query) {
    if ( isEmpty(unions) ) return

    return unions.map(union => this.escape(union)).join(' ')
  }

  /**
   * Escape function name
   *
   * @param {String} name
   * @param {Array} args
   * @returns {String}
   */
  compileFunction(name, args = []) {
    return `${name}(${this.parameterize(args)})`
  }

  /**
   * Validate the query operator
   *
   * @param {String} value
   * @returns {String}
   */
  operator(value) {
    // TODO
    return value || '='
  }

  /**
   *
   * @param {Any} value
   * @returns {String}
   */
  parameterize(value) {
    if (! isArray(value) ) value = [value]

    if ( isEmpty(value) ) return ''

    return value.map(val => {
      // escape expressions
      if ( val instanceof Expression ) return this.escape(val)

      return this.addBinding(val).parameter
    }).join(', ')
  }

  /**
   * Add query binding value
   *
   * @param {Any} value
   * @returns {Compiler}
   */
  addBinding(value) {
    if ( isUndefined(value) || isObject(value) )
      throw new TypeError("Invalid parameter value")

    this.bindings.push(value)

    return this
  }

  /**
   *
   * @param {Array} columns
   * @returns {String}
   */
  columnize(columns) {
    if (! isArray(columns) ) columns = [columns]

    return columns.map(col => this.escape(col)).join(', ')
  }

  /**
   * Escape the given value
   *
   * @param {String|Expression} value
   * @returns {String}
   */
  escape(value) {
    if ( value === '*' )
      return value

    // escape expressions
    if ( value instanceof Expression )
      return value.compile(this)

    throw new TypeError("Invalid expression to escape")
  }

  /**
   * Escape the table or column name
   *
   * @param {String} value
   * @returns {String}
   */
  escapeIdentifier(value) {
    return (value === '*') ? value : `"${value.trim()}"`
  }

  /**
   * Join two identifiers by `AS` clause
   *
   * @param {String} first
   * @param {String} second
   * @returns {String}
   */
  alias(first, second = null) {
    return first + (second ? ' as ' + this.escapeIdentifier(second) : '')
  }

}
<commit_msg>Fix base compiler after builder changes<commit_after>
import { compact, isEmpty, isObject, isArray, isNumber, isUndefined } from 'lodash'
import Expression from '../expression'

/**
 * @class BaseCompiler
 */
export default class Compiler {

  /**
   * BaseCompiler constructor
   *
   * @constructor
   */
  constructor() {
    this.bindings = []
  }

  /**
   * Default parameter placeholder
   *
   * @type {String}
   */
  get parameter() {
    return '?'
  }

  /**
   *
   * @returns {Array}
   */
  getBindings() {
    return this.bindings
  }

  /**
   *
   * @param {Object} query
   * @returns {String}
   */
  compileSelectComponents(query) {
    var sql = [
      this.compileSelectColumns(query.columns, query),
      this.compileTables(query.tables, query),
      this.compileJoins(query.joins, query),
      this.compileConditions(query.conditions, query),
      this.compileGroups(query.groups, query),
      this.compileHavingConditions(query.havingConditions, query),
      this.compileOrders(query.orders, query),
      this.compileLimit(query.limit, query),
      this.compileOffset(query.offset, query),
    ]

    return compact(sql).join(' ')
  }

  /**
   *
   * @param {Object} query
   * @returns {String}
   */
  compileUnionComponents(query) {
    var sql = [
      this.compileUnions(query.unions, query),
      this.compileOrders(query.unionOrders, query),
      this.compileLimit(query.unionLimit, query),
      this.compileOffset(query.unionOffset, query),
    ]

    return compact(sql).join(' ')
  }

  /**
   * Compile the query columns part
   *
   * @param {Array} columns
   * @param {Object} query
   * @returns {String}
   */
  compileSelectColumns(columns, query) {
    var select = 'select ' + (query.distinct ? 'distinct ' : '')

    return select + this.columnize(isEmpty(columns) ? ['*'] : columns)
  }

  /**
   *
   * @param {Array} tables
   * @param {Object} query
   * @returns {String}
   */
  compileTables(tables, query) {
    if ( isEmpty(tables) ) return

    return 'from ' + tables.map(table => this.escape(table)).join(', ')
  }

  /**
   *
   * @param {Array} joins
   * @param {Object} query
   * @returns {String}
   */
  compileJoins(joins, query) {
    if ( isEmpty(joins) ) return

    return joins.map(join => this.escape(join)).join(' ')
  }

  /**
   *
   * @param {Criteria} conditions
   * @param {Object} query
   * @returns {String}
   */
  compileConditions(conditions, query) {
    if ( conditions == null || conditions.isEmpty() ) return

    return 'where ' + conditions.compile(this)
  }

  /**
   *
   * @param {Array} groups
   * @param {Object} query
   * @returns {String}
   */
  compileGroups(groups, query) {
    if ( isEmpty(groups) ) return

    return 'group by ' + this.columnize(groups)
  }

  /**
   *
   * @param {Criteria} conditions
   * @param {Object} query
   * @returns {String}
   */
  compileHavingConditions(conditions, query) {
    if ( conditions == null || conditions.isEmpty() ) return

    return 'having ' + conditions.compile(this)
  }

  /**
   *
   * @param {Array} orders
   * @param {Object} query
   * @returns {String}
   */
  compileOrders(orders, query) {
    if ( isEmpty(orders) ) return

    return 'order by ' + this.columnize(orders)
  }

  /**
   *
   * @param {Number} limit
   * @param {Object} query
   * @returns {String}
   */
  compileLimit(limit, query) {
    if (! isNumber(limit) ) return

    return 'limit ' + this.parameterize(limit)
  }

  /**
   *
   * @param {Number} offset
   * @param {Object} query
   * @returns {String}
   */
  compileOffset(offset, query) {
    if (! isNumber(offset) ) return

    return 'offset ' + this.parameterize(offset)
  }

  /**
   *
   * @param {Array} unions
   * @param {Object} query
   * @returns {String}
   */
  compileUnions(unions, query) {
    if ( isEmpty(unions) ) return

    return unions.map(union => this.escape(union)).join(' ')
  }

  /**
   * Escape function name
   *
   * @param {String} name
   * @param {Array} args
   * @returns {String}
   */
  compileFunction(name, args = []) {
    return `${name}(${this.parameterize(args)})`
  }

  /**
   * Validate the query operator
   *
   * @param {String} value
   * @returns {String}
   */
  operator(value) {
    // TODO
    return value || '='
  }

  /**
   *
   * @param {Any} value
   * @returns {String}
   */
  parameterize(value) {
    if (! isArray(value) ) value = [value]

    if ( isEmpty(value) ) return ''

    return value.map(val => {
      // escape expressions
      if ( val instanceof Expression ) return this.escape(val)

      return this.addBinding(val).parameter
    }).join(', ')
  }

  /**
   * Add query binding value
   *
   * @param {Any} value
   * @returns {Compiler}
   */
  addBinding(value) {
    if ( isUndefined(value) || isObject(value) )
      throw new TypeError("Invalid parameter value")

    this.bindings.push(value)

    return this
  }

  /**
   *
   * @param {Array} columns
   * @returns {String}
   */
  columnize(columns) {
    if (! isArray(columns) ) columns = [columns]

    return columns.map(col => this.escape(col)).join(', ')
  }

  /**
   * Escape the given value
   *
   * @param {String|Expression} value
   * @returns {String}
   */
  escape(value) {
    if ( value === '*' )
      return value

    // escape expressions
    if ( value instanceof Expression )
      return value.compile(this)

    throw new TypeError("Invalid expression to escape")
  }

  /**
   * Escape the table or column name
   *
   * @param {String} value
   * @returns {String}
   */
  escapeIdentifier(value) {
    return (value === '*') ? value : `"${value.trim()}"`
  }

  /**
   * Join two identifiers by `AS` clause
   *
   * @param {String} first
   * @param {String} second
   * @returns {String}
   */
  alias(first, second = null) {
    return first + (second ? ' as ' + this.escapeIdentifier(second) : '')
  }

}
<|endoftext|>


<commit_before>// not EXPIRE
// 当session为空时不生成文件。

var fs = require('fs');
var path = require('path');
var util = require('util');

var oneDayTime = 1000 * 60 * 60 * 24;

function Store(opts){
  this.dir = opts.dir;
  this.ttl = opts.ttl || oneDayTime;
  setInterval(()=> {
    this.clear();
  }, oneDayTime);

}
const cacheMaxAge = 1000 * 60 * 10;
const cache = Object.create(null);
global.SESSION_CACHE = cache;
setTimeout(function(){
  for(let i in cache){
    if(Date.now > cache[i]._expire){
      delete(cache[i]);
    }
  }
}, cacheMaxAge);

function isEqual(newSess, sid){
  const cacheSess = cache[sid];
  if(JSON.stringify(cacheSess.loginedMap) === JSON.stringify(newSess.loginedMap)){
    return true;
  }
  return false;
}
const geting = Object.create(null);

Store.prototype.get = function(sid, callback){
  if(cache[sid] && Date.now() < cache[sid]._expire){
    cache[sid]._expire = Date.now() + cacheMaxAge;
    callback(null, cache[sid]);
  }else {
    if(!geting[sid]){
      geting[sid] = [callback];
      fs.readFile(path.resolve(this.dir, sid), 'utf-8', function(err, result){
        if(err){
          if(err.code === "ENOENT"){
            // 不会自动创建 节省空间
            err = null;
            result = {cookie:{}};

          }
        }else{
          result = result ? JSON.parse(result) : {cookie:{}};
          cache[sid] = result;
          cache[sid]._expire = Date.now() + cacheMaxAge;
          console.log('get session by file.');
        }
        geting[sid].forEach(function(cb){
          cb(err, result);
        })
        delete(geting[sid]);
      })

    }else{
      geting[sid].push(callback);
    }

  }

};

Store.prototype.set = function(sid, data, callback){
  if(Object.keys(data).length === 1){ // 判断session为空，不存储。
    return callback();
  }
  if(cache[sid] && isEqual(data, sid)){
    cache[sid]._expire = Date.now() + cacheMaxAge;
    callback();
  }else{
    var fpath = path.resolve(this.dir, sid);
    fs.writeFile(fpath, JSON.stringify(data), {mode: 0o600},function(err){
      if(err){
        return callback(err);
      }
      cache[sid] = data;
      console.log('save session to file.');
      callback();
    });
  }
}

Store.prototype.destroy = function(sid, data, callback){
  delete(cache[sid]);
  fs.unlink(path.resolve(this.dir, sid), callback);
}

Store.prototype.clear = function(){
  var self = this;
  fs.readdir(self.dir, function(err, files){
    if(err) return console.error('fs session store clear readdir ' + self.dir + ' fail');
    let len = files.length, i = 0;
    var now = Date.now();
    for(; i < len; i++){
      var fpath = path.resolve(self.dir, files[i]);
      fs.stat(fpath, function(err, stat){
        if(err) return console.error('fs session store stat: ' + fpath + ' fail');
        var lastModifyTime = new Date(stat.mtime);
        lastModifyTime = lastModifyTime.getTime();

        if(now - lastModifyTime >= self.ttl){
          fs.unlink(fpath);
        }
      })
    }
  })
}

module.exports = function (session) {
  var defStore = session.Store;
  util.inherits(Store, defStore);
  return Store;
}
<commit_msg>session store ok<commit_after>// not EXPIRE
// 当session为空时不生成文件。

var fs = require('fs');
var path = require('path');
var util = require('util');

var oneDayTime = 1000 * 60 * 60 * 24;

function Store(opts){
  this.dir = opts.dir;
  this.ttl = opts.ttl || oneDayTime;
  setInterval(()=> {
    this._ttl();
  }, oneDayTime);

}
const cacheMaxAge = 1000 * 60 * 10;
const cache = Object.create(null);
global.SESSION_CACHE = cache;
setTimeout(function(){
  for(let i in cache){
    if(Date.now > cache[i]._expire){
      delete(cache[i]);
    }
  }
}, cacheMaxAge);

function isEqual(newSess, sid){
  const cacheSess = cache[sid];
  if(JSON.stringify(cacheSess.loginedMap) === JSON.stringify(newSess.loginedMap)){
    return true;
  }
  return false;
}
const geting = Object.create(null);

Store.prototype.get = function(sid, callback){
  if(cache[sid] && Date.now() < cache[sid]._expire){
    callback(null, cache[sid]);
  }else {
    if(!geting[sid]){
      geting[sid] = [callback];
      fs.readFile(path.resolve(this.dir, sid), 'utf-8', function(err, result){
        if(err){
          if(err.code === "ENOENT"){
            // 不会自动创建 节省空间
            err = null;
            result = {cookie:{}};

          }
        }else{
          result = result ? JSON.parse(result) : {cookie:{}};
          cache[sid] = result;
          cache[sid]._expire = Date.now() + cacheMaxAge;
          console.log('get session by file.');
        }
        geting[sid].forEach(function(cb){
          cb(err, result);
        })
        delete(geting[sid]);
      })

    }else{
      geting[sid].push(callback);
    }

  }

};

Store.prototype.set = function(sid, data, callback){
  if(Object.keys(data).length === 1){ // 判断session为空，不存储。
    return callback();
  }
  if(cache[sid] && isEqual(data, sid)){
    cache[sid]._expire = Date.now() + cacheMaxAge;
    callback();
  }else{
    var fpath = path.resolve(this.dir, sid);
    fs.writeFile(fpath, JSON.stringify(data), {mode: 0o600},function(err){
      if(err){
        return callback(err);
      }
      cache[sid] = data;
      console.log('save session to file.');
      callback();
    });
  }
}

Store.prototype.destroy = function(sid, data, callback){
  delete(cache[sid]);
  fs.unlink(path.resolve(this.dir, sid), callback);
}

Store.prototype._ttl = function(){
  var self = this;
  fs.readdir(self.dir, function(err, files){
    if(err) return console.error('fs session store clear readdir ' + self.dir + ' fail');
    let len = files.length, i = 0;
    var now = Date.now();
    for(; i < len; i++){
      var fpath = path.resolve(self.dir, files[i]);
      fs.stat(fpath, function(err, stat){
        if(err) return console.error('fs session store stat: ' + fpath + ' fail');
        var lastModifyTime = new Date(stat.atime); //通过 atime(文件被读取时间 fs.readFile 会更新该时间) 判定过没过期.
        lastModifyTime = lastModifyTime.getTime();

        if(now - lastModifyTime >= self.ttl){
          fs.unlink(fpath);
        }
      })
    }
  })
}

module.exports = function (session) {
  var defStore = session.Store;
  util.inherits(Store, defStore);
  return Store;
}
<|endoftext|>


<commit_before>const express = require('express');
const favicon = require('serve-favicon');
const app = express();

app.get('/', (req, res) => res.send('Hello World'));

app.use(favicon(__dirname + '/public/favicon.ico'));

app.listen(3000);
<commit_msg>Set default port to env variable<commit_after>const express = require('express');
const favicon = require('serve-favicon');
const app = express();

app.get('/', (req, res) => res.send('Hello World'));

app.use(favicon(__dirname + '/public/favicon.ico'));

app.listen(process.env.PORT || 3000);
<|endoftext|>