<commit_before>var miio = require('./lib');

const crypto = require('crypto');
// var vacuum = python.import('mirobot/protocol');

class MiHome {

  constructor (name, ip, port, token, model) {
    this.ip = ip;
    this.port = port
    this.token = token;
    this.name = name
    this.model = model
    this.device = null;
  }

  load () {
    var that = this;

    return that.loading = new Promise(function(resolve, reject){

      if (that.model == 'vacuum') {
        that.device = miio.createDevice({
          address: that.ip,
          token: that.token,
          model: 'rockrobo.vacuum.v1'
        });

        that.device.init()
        .then(() => {
          console.log(that.model + " init success");
          resolve(that);
        })
        .catch(err => {
          console.error(that.model + " init error: " + err);
          reject(err);
        });
      }
      else {

        if (that.token == null) {
          var para = { address: that.ip };
        }
        else {
          var para = { address: that.ip, token: that.token };
        };

        miio.device(para)
          .then(device => {
              that.device = device;
              console.log(that.name + " " + that.device.type + " init success");
              resolve(that);
            })
          .catch(err => {
          console.error(that.name + " init error: " + err);
          reject(err);
        });
      }
    });
  }

  exec(cmd) {
    var that = this;

    if (cmd == 'Stop') {
      cmd = 'Charge';
    }

    switch (cmd) {
      case 'Start':

        if (this.device.type == 'vacuum') {
          this.device.start()
            .then(res => console.log(this.name + " start result : " + res))
            .catch(err => console.error(this.name + " start error: " + err));
        }

        break;

      case 'Charge':

        if (this.device.type == 'vacuum') {
          this.device.charge()
            .then(res => console.log(this.name + " charge result : " + res))
            .catch(err => console.error(this.name + " charge error: " + err));
        }

        break;

      case 'Pause':

        if (this.device.type == 'vacuum') {
          this.device.pause()
            .then(res => console.log(this.name + " pause result : " + res))
            .catch(err => console.error(this.name + " pause error: " + err));
        }

        break;

      case 'List':
        if (this.device.type == 'gateway') {
          this.list();
        }

        break;

      case 'PowerOff':
        if (this.device.type == 'light') {
          this.device.setPower(false)
            .then(res => console.log(this.name + " PowerOff result : " + res))
            .catch(err => console.error(this.name + " PowerOff error: " + err));
        }
        else {
          this.powerSet(false);
        }

        break;
      case 'PowerOn':

        if (this.device.type == 'light') {
          this.device.setPower(true)
            .then(res => console.log(this.name + " PowerOn result : " + res))
            .catch(err => console.error(this.name + " PowerOn error: " + err));
        }
        else {
          this.powerSet(true);
        }
        break;

      case 'PowerStatus':
        this.powerStatus();
        break;

      case 'LightStatus':
        if (this.device.type == 'light') {
          console.log(this.name + " Color Temp: " + this.device.colorTemperature);
        }
        break;

      default:
        console.log('Invalid type:' + cmd);
    }

    return;

  }

  powerOn() {
    var that = this;

    if(this.device.hasCapability('power')) {
      this.device.setPower(true)
        .then(console.log(this.name + ":Power On"))
        .catch(console.error(this.name + "Failed to Power On"));
    }

  }

  _deviecPowerSet(inputDevice, cmd) {
    var that = this;

    for (var i=0; i<inputDevice.powerChannels.length; i++) {
      console.log("Chan " + i);
      inputDevice.setPower(i, cmd)
      .then(power => console.log(inputDevice.type + " Power " + power))
      .catch(err => console.error(inputDevice.type + " Failed: " + err));
    }
  }

  _deviecPowerStatus(inputDevice) {
    var that = this;

    for (var i=0; i<inputDevice.powerChannels.length; i++) {
      console.log(inputDevice.type + " power " + inputDevice.power(JSON.stringify(i)));
    }
  }

  powerSet(cmd) {
    var that = this;

    if(that.device.type == 'gateway') {
      that.device.on('deviceAvailable', subDevice => {
        if (subDevice.type == 'power-plug') {

          that._deviecPowerSet(subDevice,cmd);
        }

        return;
      });
    }
    else {
      that._deviecPowerSet(that.device,cmd);
    }
  }

  powerStatus() {
    var that = this;

    if(that.device.type == 'gateway') {
      that.device.on('deviceAvailable', subDevice => {
        if (subDevice.type == 'power-plug') {

          that._deviecPowerStatus(subDevice);
        }

        return;
      });
    }
    else {
      that._deviecPowerStatus(that.device);
    }
  }

  encryption (key, iv, data) {
    var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
    var crypted = cipher.update(data, 'utf8', 'binary');
    crypted += cipher.final('binary');
    crypted = new Buffer(crypted, 'binary');
    return crypted;
  }

  list() {
    var that = this;
    console.log('All SubDevice:');

    that.device.on('deviceAvailable', subDevice => {
      console.log('ID: ' + subDevice.id + ' Model:' + subDevice.model + ' Type:' + subDevice.type );

      if (subDevice.model == 'lumi.switch') {
          console.log('Actions: ' + subDevice.actions );
          debugger;

          var key = that.device._developerKey;
          var token = that.device._lastToken;

          console.log('key ' + key + ' token ' + token)

          var temp = that.device.packet._tokenKey.toString('hex');
           console.log('temp ' + temp );

          console.log('tokenkey ' + that.device.packet._tokenKey + ' IV ' + that.device.packet._tokenIV)

          var base64str = that.encryption(that.device.packet._tokenKey, that.device.packet._tokenIV, token.toString());

          console.log(' ciphertext: ' + base64str.toString('hex'));

          var finalKey = base64str.toString('hex');

          var data = {"cmd":"write",
                "model":"switch",
                "sid":subDevice.id,
                "short_id":2014,
                "data":"{\"status\":\"click\",\"key\":\"" + finalKey + "\"}" }
          that.device.devApi.send(data);
                 // 0: data = {"cmd":"read_ack","model":"switch","sid":"158d000155e11d","short_id":2014,"data":"{\"voltage\":3062}"}

        }

      return;

    });

    if(that.device.devices) {

      that.device.devices.forEach(function(item){
        console.log('-' + item);
      })
    }

  }

};

module.exports = MiHome;<commit_msg>update test<commit_after>var miio = require('./lib');

const crypto = require('crypto');
// var vacuum = python.import('mirobot/protocol');

class MiHome {

  constructor (name, ip, port, token, model) {
    this.ip = ip;
    this.port = port
    this.token = token;
    this.name = name
    this.model = model
    this.device = null;
  }

  load () {
    var that = this;

    return that.loading = new Promise(function(resolve, reject){

      if (that.model == 'vacuum') {
        that.device = miio.createDevice({
          address: that.ip,
          token: that.token,
          model: 'rockrobo.vacuum.v1'
        });

        that.device.init()
        .then(() => {
          console.log(that.model + " init success");
          resolve(that);
        })
        .catch(err => {
          console.error(that.model + " init error: " + err);
          reject(err);
        });
      }
      else {

        if (that.token == null) {
          var para = { address: that.ip };
        }
        else {
          var para = { address: that.ip, token: that.token };
        };

        miio.device(para)
          .then(device => {
              that.device = device;
              console.log(that.name + " " + that.device.type + " init success");
              resolve(that);
            })
          .catch(err => {
          console.error(that.name + " init error: " + err);
          reject(err);
        });
      }
    });
  }

  exec(cmd) {
    var that = this;

    if (cmd == 'Stop') {
      cmd = 'Charge';
    }

    switch (cmd) {
      case 'Start':

        if (this.device.type == 'vacuum') {
          this.device.start()
            .then(res => console.log(this.name + " start result : " + res))
            .catch(err => console.error(this.name + " start error: " + err));
        }

        break;

      case 'Charge':

        if (this.device.type == 'vacuum') {
          this.device.charge()
            .then(res => console.log(this.name + " charge result : " + res))
            .catch(err => console.error(this.name + " charge error: " + err));
        }

        break;

      case 'Pause':

        if (this.device.type == 'vacuum') {
          this.device.pause()
            .then(res => console.log(this.name + " pause result : " + res))
            .catch(err => console.error(this.name + " pause error: " + err));
        }

        break;

      case 'List':
        if (this.device.type == 'gateway') {
          this.list();
        }

        break;

      case 'PowerOff':
        if (this.device.type == 'light') {
          this.device.setPower(false)
            .then(res => console.log(this.name + " PowerOff result : " + res))
            .catch(err => console.error(this.name + " PowerOff error: " + err));
        }
        else {
          this.powerSet(false);
        }

        break;
      case 'PowerOn':

        if (this.device.type == 'light') {
          this.device.setPower(true)
            .then(res => console.log(this.name + " PowerOn result : " + res))
            .catch(err => console.error(this.name + " PowerOn error: " + err));
        }
        else {
          this.powerSet(true);
        }
        break;

      case 'PowerStatus':
        this.powerStatus();
        break;

      case 'LightStatus':
        if (this.device.type == 'light') {
          console.log(this.name + " Color Temp: " + this.device.colorTemperature);
        }
        break;

      default:
        console.log('Invalid type:' + cmd);
    }

    return;

  }

  powerOn() {
    var that = this;

    if(this.device.hasCapability('power')) {
      this.device.setPower(true)
        .then(console.log(this.name + ":Power On"))
        .catch(console.error(this.name + "Failed to Power On"));
    }

  }

  _deviecPowerSet(inputDevice, cmd) {
    var that = this;

    for (var i=0; i<inputDevice.powerChannels.length; i++) {
      console.log("Chan " + i);
      inputDevice.setPower(i, cmd)
      .then(power => console.log(inputDevice.type + " Power " + power))
      .catch(err => console.error(inputDevice.type + " Failed: " + err));
    }
  }

  _deviecPowerStatus(inputDevice) {
    var that = this;

    for (var i=0; i<inputDevice.powerChannels.length; i++) {
      console.log(inputDevice.type + " power " + inputDevice.power(JSON.stringify(i)));
    }
  }

  powerSet(cmd) {
    var that = this;

    if(that.device.type == 'gateway') {
      that.device.on('deviceAvailable', subDevice => {
        if (subDevice.type == 'power-plug') {

          that._deviecPowerSet(subDevice,cmd);
        }

        return;
      });
    }
    else {
      that._deviecPowerSet(that.device,cmd);
    }
  }

  powerStatus() {
    var that = this;

    if(that.device.type == 'gateway') {
      that.device.on('deviceAvailable', subDevice => {
        if (subDevice.type == 'power-plug') {

          that._deviecPowerStatus(subDevice);
        }

        return;
      });
    }
    else {
      that._deviecPowerStatus(that.device);
    }
  }

  encryption (key, iv, data) {
    var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
    var crypted = cipher.update(data, 'utf8', 'binary');
    crypted += cipher.final('binary');
    crypted = new Buffer(crypted, 'binary');
    return crypted;
  }

  list() {
    var that = this;
    console.log('All SubDevice:');

    that.device.on('deviceAvailable', subDevice => {
      console.log('ID: ' + subDevice.id + ' Model:' + subDevice.model + ' Type:' + subDevice.type );

      if (subDevice.model == 'lumi.switch') {
          console.log('Actions: ' + subDevice.actions );
          debugger;

          var key = that.device._developerKey;
          var token = that.device._lastToken;

          console.log('key ' + key + ' token ' + token)

          var temp = that.device.packet._tokenKey.toString('base64');
           console.log('temp ' + temp );

          console.log('tokenkey ' + that.device.packet._tokenKey + ' IV ' + that.device.packet._tokenIV)

          var base64str = that.encryption(that.device.packet._tokenKey, that.device.packet._tokenIV, token.toString());

          console.log(' ciphertext: ' + base64str.toString('hex'));

          var finalKey = base64str.toString('hex');

          var data = {"cmd":"write",
                "model":"switch",
                "sid":subDevice.id,
                "short_id":2014,
                "data":"{\"status\":\"click\",\"key\":\"" + finalKey + "\"}" }
          that.device.devApi.send(data);
                 // 0: data = {"cmd":"read_ack","model":"switch","sid":"158d000155e11d","short_id":2014,"data":"{\"voltage\":3062}"}

        }

      return;

    });

    if(that.device.devices) {

      that.device.devices.forEach(function(item){
        console.log('-' + item);
      })
    }

  }

};

module.exports = MiHome;<|endoftext|>


<commit_before>/**
 * Warning: if editing this file, be sure to keep the VERSION variable set to 'X.X.X'
 *           without modifying formatting for automation scripts
 * @type {{VERSION: string}}
 */
let VersionInfo = { VERSION : '0.0.9730' };
export default VersionInfo;
<commit_msg>vinfo<commit_after>/**
 * Warning: if editing this file, be sure to keep the VERSION variable set to 'X.X.X'
 *           without modifying formatting for automation scripts
 * @type {{VERSION: string}}
 */
let VersionInfo = { VERSION : '0.0.9731' };
export default VersionInfo;
<|endoftext|>


<commit_before>var miio = require('./lib');

// var vacuum = python.import('mirobot/protocol');

var MiHome = function(name, ip, port, token, model) {

  var that = this;

  this.ip = ip;
  this.port = port
  this.token = token;
  this.name = name
  this.model = model
  this.device = null;

// initialize the vacuum independently, and general device will create ether with token or without token
  if (model == 'vacuum') {
    this.device = miio.createDevice({
      address: this.ip,
      token: this.token,
      model: 'rockrobo.vacuum.v1'
    });

    this.device.init()
    .then(() => {
        console.log(this.model + " init success");
        return this;
    })
    .catch(err => console.error(this.model + " init error: " + err));
  }
  else {

    if (token == null) {
      var para = { address: ip };
    }
    else {
      var para = { address: ip, token: token };
    };

    return new Promise(function(resolve, reject){
       miio.device(para)
        .then(device => {
            this.device = device;
            debugger;
            console.log(this.name + " " + this.device.type + " init success");
            resolve(this);
          })
        .catch(err => {
          console.error(this.name + " init error: " + err);
          reject(err);
        });

    // miio.device(para)
    //   .then(device => {
    //       this.device = device;
    //       debugger;
    //       console.log(this.name + " " + this.device.type + " init success");
    //       return this;
    //     })
    //   .catch(err => console.error(this.name + " init error: " + err));

};

MiHome.prototype.exec = function(cmd) {

  var that = this;

  if (cmd == 'Stop') {
    cmd = 'Charge';
  }

  switch (cmd) {
    case 'Start':

      if (this.device.type == 'vacuum') {
        this.device.start()
          .then(res => console.log(this.name + " start result : " + res))
          .catch(err => console.error(this.name + " start error: " + err));
      }

      break;

    case 'Charge':

      if (this.device.type == 'vacuum') {
        this.device.charge()
          .then(res => console.log(this.name + " charge result : " + res))
          .catch(err => console.error(this.name + " charge error: " + err));
      }

      break;

    case 'Pause':

      if (this.device.type == 'vacuum') {
        this.device.pause()
          .then(res => console.log(this.name + " pause result : " + res))
          .catch(err => console.error(this.name + " pause error: " + err));
      }

      break;

    case 'List':
      if (this.device.type == 'gateway') {
        console.log(this.device.devices);
      }

      break;

    default:
      console.log('Invalid type:' + cmd);
  }

  return;

};

MiHome.prototype.powerOn = function() {

  var that = this;

  if(this.device.hasCapability('power')) {
    this.device.setPower(true)
      .then(console.log(this.name + ":Power On"))
      .catch(console.error(this.name + "Failed to Power On"));
  }

};

module.exports = function(name, ip, port, token, model) {
  return new MiHome(name, ip, port, token, model);
};

<commit_msg>update mihome index<commit_after>var miio = require('./lib');

// var vacuum = python.import('mirobot/protocol');

var MiHome = function(name, ip, port, token, model) {

  var that = this;

  this.ip = ip;
  this.port = port
  this.token = token;
  this.name = name
  this.model = model
  this.device = null;

// initialize the vacuum independently, and general device will create ether with token or without token
  if (model == 'vacuum') {
    this.device = miio.createDevice({
      address: this.ip,
      token: this.token,
      model: 'rockrobo.vacuum.v1'
    });

    this.device.init()
    .then(() => {
        console.log(this.model + " init success");
        return this;
    })
    .catch(err => console.error(this.model + " init error: " + err));
  }
  else {

    if (token == null) {
      var para = { address: ip };
    }
    else {
      var para = { address: ip, token: token };
    };

    return new Promise(function(resolve, reject){
       miio.device(para)
        .then(device => {
            this.device = device;
            debugger;
            console.log(this.name + " " + this.device.type + " init success");
            resolve(this);
          })
        .catch(err => {
          console.error(this.name + " init error: " + err);
          reject(err);
        });
      }

    // miio.device(para)
    //   .then(device => {
    //       this.device = device;
    //       debugger;
    //       console.log(this.name + " " + this.device.type + " init success");
    //       return this;
    //     })
    //   .catch(err => console.error(this.name + " init error: " + err));
  };
};

MiHome.prototype.exec = function(cmd) {

  var that = this;

  if (cmd == 'Stop') {
    cmd = 'Charge';
  }

  switch (cmd) {
    case 'Start':

      if (this.device.type == 'vacuum') {
        this.device.start()
          .then(res => console.log(this.name + " start result : " + res))
          .catch(err => console.error(this.name + " start error: " + err));
      }

      break;

    case 'Charge':

      if (this.device.type == 'vacuum') {
        this.device.charge()
          .then(res => console.log(this.name + " charge result : " + res))
          .catch(err => console.error(this.name + " charge error: " + err));
      }

      break;

    case 'Pause':

      if (this.device.type == 'vacuum') {
        this.device.pause()
          .then(res => console.log(this.name + " pause result : " + res))
          .catch(err => console.error(this.name + " pause error: " + err));
      }

      break;

    case 'List':
      if (this.device.type == 'gateway') {
        console.log(this.device.devices);
      }

      break;

    default:
      console.log('Invalid type:' + cmd);
  }

  return;

};

MiHome.prototype.powerOn = function() {

  var that = this;

  if(this.device.hasCapability('power')) {
    this.device.setPower(true)
      .then(console.log(this.name + ":Power On"))
      .catch(console.error(this.name + "Failed to Power On"));
  }

};

module.exports = function(name, ip, port, token, model) {
  return new MiHome(name, ip, port, token, model);
};

<|endoftext|>


<commit_before>/*jshint jquery: true, browser: true*/
/*global _: false, Backbone: false, ActionQueue: true, LogType: true*/
(function(L) {
    'use strict';

    L.views.NoteView = Backbone.View.extend({
        tagName: 'li',
        className: 'note hbox justified_box',
        initialize: function(options) {
            var that = this;
            $(window).one('beforeunload', function() {
                that.undelegateEvents();
                that.stopListening();
            });
            this.template = L.templates["note"];
            this.listenTo(this.model, 'change:contents', _.mask(this.updateContents, 2));
            this.listenTo(this.model, 'change:meta', _.mask(this.updateMeta, 2));
        },
        remove : function(options) {
            if (this._rendered) {
                var el = this.$el;
                if (options && options.animate) {
                    el.stop().fadeOut({queue: false}, 200).slideUp(300, function() {
                        el.remove();
                    });
                } else {
                    el.remove();
                }
            }
        },
        isVisible: function() {
            // This assumes that el having a parent means that it is visible. Much
            // faster than checking $el.is(':visible') and speed is important here.
            return this.el && this.el.parentNode;
        },
        render: function(options) {
            var that = this;
            this.$el.on('DOMNodeRemoved', function(evt) {
              if (evt.srcElement === that.el) {
                that.cleanupEditor()
              }
            });
            this.$el.attr("id", "note-"+this.model.id);

            if (this._rendered) {
                this.cleanupEditor();
                this.undelegateEvents();
                this.delegateEvents();
            } else {
                this._rendered = true;

                this.updateContents(options);
                this.updateMeta();
            }

            return this;
        },
        cleanupEditor: function() {
            if (this.editor) {
                this.editor.remove();
                delete this.editor;
            }
        },
        updateMeta: function(options) {
            this.$el.attr('class', this.className);
            if (this.model.get('meta').pinned) {
                this.$el.addClass('pinned');
            }
        },
        updateContents: function(options) {
            this.$el.html(this.template(this.model.toJSON()));
        },
        events: {
            'click .close-btn': 'onRemoveClicked',
            'click .contents': 'onClick',
            'click .contents a': 'onLinkOpen',
            'keyup .contents': 'onKeyUp',
            'blur .editor': 'onBlur'
        },
        getNoteText: function() {
            return this.$('.contents').html();
        },
        onLinkOpen: function(event) {
          this.model.trigger('user:open-bookmark', this.model, this, event.target.href);
        },
        onRemoveClicked: function() {
          L.notebook.trashNote(this.model);
          return false;
        },
        expand: function() {
            this.$el.css('height', 'auto');
        },
        collapse: function() {
            this.$el.css('height', '');
        },
        openEditor: function() {
            // I know that this is unreadable. But it makes a point.
            // (it's complicated)

            var $contentsEl = this.$('.contents'),
                $editorEl = this.$('.editor-container');
            if (!this.editor) {
              this.editor = new ListIt.views.Editor({
                text: $contentsEl.html()
              });
              $editorEl.html(this.editor.render().el);
            }
            $editorEl.show();
            $contentsEl.hide();
            this.editor.focus();
            this.model.trigger('user:edit', this.model, this);
        },
        onBlur: function() {
            var that = this;
            if (!this.editor || this.editor.isShowingDialog()) {
                return;
            }
            _.defer(function() {
                var focusedEditor = $(document.activeElement).closest('.editor').get(0),
                    myEditor = that.$('.editor').get(0);

                if (focusedEditor !== myEditor) {
                    that.closeEditor();
                }
            });
        },
        closeEditor: function() {
            this.model.changeContents(this.editor.getText(), window);
            this.model.trigger('user:save', this.model, this);
            this.collapse();
            this.$('.editor-container').hide();
            this.$('.contents').show();
        },
        onClick: function(e) {
            if (!e.target.href) {
                this.expand();
                this.openEditor();
            }
        },
        /**
        * Scroll List so note selected with tab is at top.
        */
        onKeyUp: function(event) {
            if (event.keyCode === 9) { // TAB
                event.target.scrollIntoView();
                $('#notes')[0].scrollTop -= 4;
            }
        },
        openLink: function() {
            window.debug('READY');
            L.openLinkTimer = setTimeout(function () {
                window.debug('FIRE');
                window.open(this.model.get('meta').fullURL);
            }, 50);
        }
    });

    L.views.NoteCollectionView = Backbone.View.extend({
        defaults: {
            noteHeight: '1.3em', //L.base.oneLineHeight, //'1.2em',
            lastFocusedNote: null
        },
        initialize: function() {
            var that = this;
            this.subViews = {}; // Note views
            this.renderNext = 0;
            this.listenTo(this.collection, 'add', _.mask(this.addNote, 0, 2));
            this.listenTo(this.collection, 'remove', function(note, col, options) {
              that.removeNote(note, _.defaults({}, options));
            });
            this.listenTo(this.collection, 'reset', _.mask(this.reset, 1));
            this.listenTo(this.collection, 'sort', _.mask(this.sort));
            this.listenTo(L.preferences, 'change:shrinkNotes', this.updateNoteShrinkState);
            $(window).one('beforeunload', function() {
                that.undelegateEvents();
                that.stopListening();
            });
            // Load more if needed on window resize.
            $(window).on("resize", function() {
              if (that._rendered) {
                that.lazyLoadMore();
              }
            });
        },
        events: {
            'scroll': 'onScroll'
        },
        updateNoteShrinkState : function(model, state) {
            if (!this._rendered) {
                return;
            }

            if (state) {
                this.$el.addClass('shrink');
            } else {
                this.$el.removeClass('shrink');
            }
        },
        checkLoadMore: function() {
            var scrollTop = this.$el.scrollTop(),
                containerHeight = this.$el.parent().height(),
                listHeight = this.$el.children('#notes-container').height();

            return (scrollTop + 2*containerHeight) > listHeight;
        },
        onScroll: function() {
          // Load on scroll.
          this.loadMore();
        },
        loadMore: function() {
          var note;
          var view;
          while (this.collection.size() > this.renderNext && this.shouldRenderAt(this.renderNext)) {
            note = this.collection.at(this.renderNext);
            view = this.subViews[note.id];
            if (!view) {
              view = this.subViews[note.id] = new L.views.NoteView({model: note});
            }
            if (!view.isVisible()) {
              this.insertAt(this.renderNext, view);
            }
            this.renderNext += 1;
          }
        },
        lazyLoadMore: _.debounce(function() {
          return this.loadMore();
        }, 100),
        shouldRenderAt: function(index) {
            var scrollTop = this.$el.scrollTop(),
                containerHeight = this.$el.parent().height(),
                notes = this.$('#notes-container .note'),
                noteOffset;
            if (notes.length > index) {
              noteOffset = notes.eq(index).position().top;
            } else if (notes.length > 0) {
              noteOffset = notes.last().position().top;
            } else {
              noteOffset = 0;
            }
            return (scrollTop + 2*containerHeight) > noteOffset;
        },
        addNote: function(note, options) {
          var view = this.subViews[note.id];

          // Ignore already visible/unrendered
          if (!this._rendered || view && view.isVisible()) {
              return;
          }

          // XXX: BUG: options.index incorrect. Calculate manually.
          var index = this.collection.indexOf(note, options && options.index);

          if (this.shouldRenderAt(index)) {
            if (!view) {
              view = this.subViews[note.id] = new L.views.NoteView({model: note});
            }
            this.insertAt(index, view);
            if (index < this.renderNext) {
              this.renderNext += 1;
            }
          } else if (index < this.renderNext) {
            // If I choose not to render, fix the render next index.
            this.renderNext = index;
          }
          this.fixHeight();
        },
        insertAt: function(index, view) {
            var otherEl = this.$el.children('#notes-container').find('.note').eq(index);
            if (otherEl.length === 0) {
                this.$el.children('#notes-container').append(view.render().$el);
            } else {
                otherEl.before(view.render().$el);
            }
        },
        // Delay removes just in case we are re-adding the note.
        removeNote: function(note, options) {
            var id = note.id || note,
                view = this.subViews[id];

            this.fixHeight();
            if (!view || !this._rendered) {
                return;
            }

            if (0 < this.$('#notes-container .note').index(view.$el) < this.renderNext) {
              this.renderNext--;
            }

            view.remove(options);
            this.lazyLoadMore();
        },
        fixHeight: _.throttle(function() {
          this.$('#notes-container').css("min-height", 36*this.collection.size());
        }, 100),
        sort: _.debounce(function() {
          var that = this;
          this.collection.each(function(note, i) {
            var view = that.subViews[note.id];
            if (view) {
              view.remove();
              that.insertAt(i, view);
            }
          });
        }, 100),
        reset: function() {
          this.renderNext = 0;
            if (this._rendered) {
                this.$el.children('#notes-container').empty();
                this.collection.each(_.mask(_.bind(this.addNote, this), 0));
            }
        },
        render: function() {
            if (!this._rendered) {
                var ul = $('<ul>');
                ul.attr({
                  'class': 'notelist', 'id': 'notes-container'
                });
                this.$el.html(ul);

                this._rendered = true;
                this.updateNoteShrinkState(L.preferences, L.preferences.get('shrinkNotes'));
            }
            _.defer(_.bind(this.reset, this));
            return this;
        }
    });

})(ListIt);
<commit_msg>Cancel note edit on escape.<commit_after>/*jshint jquery: true, browser: true*/
/*global _: false, Backbone: false, ActionQueue: true, LogType: true*/
(function(L) {
    'use strict';

    L.views.NoteView = Backbone.View.extend({
        tagName: 'li',
        className: 'note hbox justified_box',
        initialize: function(options) {
            var that = this;
            $(window).one('beforeunload', function() {
                that.undelegateEvents();
                that.stopListening();
            });
            this.template = L.templates["note"];
            this.listenTo(this.model, 'change:contents', _.mask(this.updateContents, 2));
            this.listenTo(this.model, 'change:meta', _.mask(this.updateMeta, 2));
        },
        remove : function(options) {
            if (this._rendered) {
                var el = this.$el;
                if (options && options.animate) {
                    el.stop().fadeOut({queue: false}, 200).slideUp(300, function() {
                        el.remove();
                    });
                } else {
                    el.remove();
                }
            }
        },
        isVisible: function() {
            // This assumes that el having a parent means that it is visible. Much
            // faster than checking $el.is(':visible') and speed is important here.
            return this.el && this.el.parentNode;
        },
        render: function(options) {
            var that = this;
            this.$el.on('DOMNodeRemovedFromDocument', function(evt) {
              if (evt.srcElement === that.el) {
                that.cleanupEditor()
              }
            });
            this.$el.attr("id", "note-"+this.model.id);

            if (this._rendered) {
                this.cleanupEditor();
                this.undelegateEvents();
                this.delegateEvents();
            } else {
                this._rendered = true;

                this.updateContents(options);
                this.updateMeta();
            }

            return this;
        },
        cleanupEditor: function() {
            if (this.editor) {
                this.editor.remove();
                delete this.editor;
            }
        },
        updateMeta: function(options) {
            this.$el.attr('class', this.className);
            if (this.model.get('meta').pinned) {
                this.$el.addClass('pinned');
            }
        },
        updateContents: function(options) {
            this.$el.html(this.template(this.model.toJSON()));
        },
        events: {
            'click   .close-btn':   'onRemoveClicked',
            'click   .contents':    'onClick',
            'click   .contents a':  'onLinkOpen',
            'keyup   .contents':    'onKeyUp',
            'blur    .editor':      'onBlur',
            'keydown .editor':      'onKeyDown'
        },
        getNoteText: function() {
            return this.$('.contents').html();
        },
        onLinkOpen: function(event) {
          this.model.trigger('user:open-bookmark', this.model, this, event.target.href);
        },
        onRemoveClicked: function() {
          L.notebook.trashNote(this.model);
          return false;
        },
        expand: function() {
            this.$el.css('height', 'auto');
        },
        collapse: function() {
            this.$el.css('height', '');
        },
        openEditor: function() {
            // I know that this is unreadable. But it makes a point.
            // (it's complicated)

            var $contentsEl = this.$('.contents'),
                $editorEl = this.$('.editor-container');
            if (!this.editor) {
              this.editor = new ListIt.views.Editor({
                text: $contentsEl.html()
              });
              $editorEl.html(this.editor.render().el);
            }
            $editorEl.show();
            $contentsEl.hide();
            this.editor.focus();
            this.model.trigger('user:edit', this.model, this);
        },
        onBlur: function() {
            var that = this;
            if (!this.editor || this.editor.isShowingDialog()) {
                return;
            }
            _.defer(function() {
                var focusedEditor = $(document.activeElement).closest('.editor').get(0),
                    myEditor = that.$('.editor').get(0);

                if (focusedEditor !== myEditor) {
                    that.closeEditor();
                }
            });
        },
        closeEditor: function() {
            this.model.changeContents(this.editor.getText(), window);
            this.model.trigger('user:save', this.model, this);
            this.collapse();
            this.$('.editor-container').hide();
            this.$('.contents').show();
        },
        onClick: function(e) {
            if (!e.target.href) {
                this.expand();
                this.openEditor();
            }
        },
        /**
        * Scroll List so note selected with tab is at top.
        */
        onKeyUp: function(event) {
            if (event.keyCode === KeyCode.TAB) {
                event.target.scrollIntoView();
                $('#notes')[0].scrollTop -= 4;
            }
        },
        onKeyDown: function(event) {
          if (event.keyCode === KeyCode.ESC && this.editor && !this.editor.isShowingDialog()) {
            this.closeEditor();
          }
        },
        openLink: function() {
            window.debug('READY');
            L.openLinkTimer = setTimeout(function () {
                window.debug('FIRE');
                window.open(this.model.get('meta').fullURL);
            }, 50);
        }
    });

    L.views.NoteCollectionView = Backbone.View.extend({
        defaults: {
            noteHeight: '1.3em', //L.base.oneLineHeight, //'1.2em',
            lastFocusedNote: null
        },
        initialize: function() {
            var that = this;
            this.subViews = {}; // Note views
            this.renderNext = 0;
            this.listenTo(this.collection, 'add', _.mask(this.addNote, 0, 2));
            this.listenTo(this.collection, 'remove', function(note, col, options) {
              that.removeNote(note, _.defaults({}, options));
            });
            this.listenTo(this.collection, 'reset', _.mask(this.reset, 1));
            this.listenTo(this.collection, 'sort', _.mask(this.sort));
            this.listenTo(L.preferences, 'change:shrinkNotes', this.updateNoteShrinkState);
            $(window).one('beforeunload', function() {
                that.undelegateEvents();
                that.stopListening();
            });
            // Load more if needed on window resize.
            $(window).on("resize", function() {
              if (that._rendered) {
                that.lazyLoadMore();
              }
            });
        },
        events: {
            'scroll': 'onScroll'
        },
        updateNoteShrinkState : function(model, state) {
            if (!this._rendered) {
                return;
            }

            if (state) {
                this.$el.addClass('shrink');
            } else {
                this.$el.removeClass('shrink');
            }
        },
        checkLoadMore: function() {
            var scrollTop = this.$el.scrollTop(),
                containerHeight = this.$el.parent().height(),
                listHeight = this.$el.children('#notes-container').height();

            return (scrollTop + 2*containerHeight) > listHeight;
        },
        onScroll: function() {
          // Load on scroll.
          this.loadMore();
        },
        loadMore: function() {
          var note;
          var view;
          while (this.collection.size() > this.renderNext && this.shouldRenderAt(this.renderNext)) {
            note = this.collection.at(this.renderNext);
            view = this.subViews[note.id];
            if (!view) {
              view = this.subViews[note.id] = new L.views.NoteView({model: note});
            }
            if (!view.isVisible()) {
              this.insertAt(this.renderNext, view);
            }
            this.renderNext += 1;
          }
        },
        lazyLoadMore: _.debounce(function() {
          return this.loadMore();
        }, 100),
        shouldRenderAt: function(index) {
            var scrollTop = this.$el.scrollTop(),
                containerHeight = this.$el.parent().height(),
                notes = this.$('#notes-container .note'),
                noteOffset;
            if (notes.length > index) {
              noteOffset = notes.eq(index).position().top;
            } else if (notes.length > 0) {
              noteOffset = notes.last().position().top;
            } else {
              noteOffset = 0;
            }
            return (scrollTop + 2*containerHeight) > noteOffset;
        },
        addNote: function(note, options) {
          var view = this.subViews[note.id];

          // Ignore already visible/unrendered
          if (!this._rendered || view && view.isVisible()) {
              return;
          }

          // XXX: BUG: options.index incorrect. Calculate manually.
          var index = this.collection.indexOf(note, options && options.index);

          if (this.shouldRenderAt(index)) {
            if (!view) {
              view = this.subViews[note.id] = new L.views.NoteView({model: note});
            }
            this.insertAt(index, view);
            if (index < this.renderNext) {
              this.renderNext += 1;
            }
          } else if (index < this.renderNext) {
            // If I choose not to render, fix the render next index.
            this.renderNext = index;
          }
          this.fixHeight();
        },
        insertAt: function(index, view) {
            var otherEl = this.$el.children('#notes-container').find('.note').eq(index);
            if (otherEl.length === 0) {
                this.$el.children('#notes-container').append(view.render().$el);
            } else {
                otherEl.before(view.render().$el);
            }
        },
        // Delay removes just in case we are re-adding the note.
        removeNote: function(note, options) {
            var id = note.id || note,
                view = this.subViews[id];

            this.fixHeight();
            if (!view || !this._rendered) {
                return;
            }

            if (0 < this.$('#notes-container .note').index(view.$el) < this.renderNext) {
              this.renderNext--;
            }

            view.remove(options);
            this.lazyLoadMore();
        },
        fixHeight: _.throttle(function() {
          this.$('#notes-container').css("min-height", 36*this.collection.size());
        }, 100),
        sort: _.debounce(function() {
          var that = this;
          this.collection.each(function(note, i) {
            var view = that.subViews[note.id];
            if (view) {
              view.remove();
              that.insertAt(i, view);
            }
          });
        }, 100),
        reset: function() {
          this.renderNext = 0;
            if (this._rendered) {
                this.$el.children('#notes-container').empty();
                this.collection.each(_.mask(_.bind(this.addNote, this), 0));
            }
        },
        render: function() {
            if (!this._rendered) {
                var ul = $('<ul>');
                ul.attr({
                  'class': 'notelist', 'id': 'notes-container'
                });
                this.$el.html(ul);

                this._rendered = true;
                this.updateNoteShrinkState(L.preferences, L.preferences.get('shrinkNotes'));
            }
            _.defer(_.bind(this.reset, this));
            return this;
        }
    });

})(ListIt);
<|endoftext|>


<commit_before>var templates = [
    "root/externallib/text!root/plugins/events/events.html",
    "root/externallib/text!root/plugins/events/event.html"
];

define(templates, function (eventsTpl, eventTpl) {
    var plugin = {
        settings: {
            name: "events",
            type: "general",
            icon: "plugins/events/icon.png",
            menuURL: "#events/90",
            lang: {
                component: "core"
            }
        },

        storage: {
            "event": {type: "model"},
            "eventsDisabled": {type: "collection", model: "event"},
            "events": {type: "collection", model: "event"}
        },

        routes: [
            ["events/:days", "show_events", "showEvents"],
            ["events/show/:id", "show_event", "showEvent"]
        ],

        // This is like a static variable where we store the last Events retrieved in JSON format.
        lastEvents: null,

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            return MM.util.wsAvailable('core_calendar_get_calendar_events') ||
                    MM.util.wsAvailable('local_mobile_core_calendar_get_calendar_events');
        },

        _getCalendarEventsSucces: function(response, days) {
            var daysIncrement = 90;
            var pageTitle = MM.lang.s("events") + "  " + days + " " + MM.lang.s("days");

            MM.plugins.events.lastEvents = typeof(response.events !== "undefined")? response.events : [];

            var d;
            // Formatting.
            for (var el in MM.plugins.events.lastEvents) {
                var event = MM.plugins.events.lastEvents[el];

                d = new Date(event.timestart * 1000);
                event.startdate = d.toLocaleDateString();
                event.starttime = d.toLocaleTimeString(MM.lang.current, {hour: '2-digit', minute:'2-digit'});

                if (event.timeduration) {
                    d = new Date((event.timestart + event.timeduration) * 1000);
                    event.enddate = d.toLocaleDateString();
                    event.endtime = d.toLocaleTimeString(MM.lang.current, {hour: '2-digit', minute:'2-digit'});
                } else {
                    event.enddate = 0;
                    event.endtime = 0;
                }

                MM.plugins.events.lastEvents[el] = event;
            }

            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');

            var tpl = {events: MM.plugins.events.lastEvents};

            var html = MM.tpl.render(MM.plugins.events.templates.events.html, tpl);

            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                MM.panels.show('center', html, {title: pageTitle});
            } else  {
                if (MM.deviceType == "tablet") {
                    MM.panels.show('center', html, {title: pageTitle, hideRight: true});
                } else {
                    MM.panels.show('center', html, {title: pageTitle});
                }
            }

            $("#events-showmore").on(MM.clickType, function(e) {
                MM.plugins.events.showEvents(days + daysIncrement);
            });
            // Load the first event.
            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                $("#panel-center li:eq(0)").addClass("selected-row");
                MM.plugins.events.showEvent(0);
                $("#panel-center li:eq(0)").addClass("selected-row");
            }

        },

        _getCalendarEventsFailure: function(m) {
            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');
            if (typeof(m) !== "undefined" && m) {
                MM.popErrorMessage(m);
            }
        },

        /**
         * Display global and course events for all the user courses
         * TODO: Support groups events also
         *
         * @param  {integer} days The number of days for displaying events starting today
         */
        showEvents: function(days) {
            MM.panels.showLoading('center');

            days = parseInt(days, 10);

            if (MM.deviceType == "tablet") {
                MM.panels.showLoading('right');
            }
            // Adding loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').addClass('loading-row');

            MM.plugins.events._getEvents(
                days,
                null,
                function(r) {
                    MM.plugins.events._getCalendarEventsSucces(r, days);
                },
                MM.plugins.events._getCalendarEventsFailure
            );
        },

        /**
         * Displays a single event information
         *
         * @param  {integer} eventId The index position in the original events array retrieved.
         */
        showEvent: function(eventId) {
            var pageTitle = MM.lang.s("events");

            if (typeof(MM.plugins.events.lastEvents[eventId]) != "undefined") {
                var fullEvent = MM.plugins.events.lastEvents[eventId];
                var course = MM.db.get("courses", MM.config.current_site.id + "-" + fullEvent.courseid);
                if (course) {
                    fullEvent.courseName = MM.util.formatText(course.get("fullname"));
                }

                var localEventId = MM.plugins.events._getLocalEventUniqueId(fullEvent);
                var disabled = MM.db.get("eventsDisabled", localEventId);
                var checked = "";
                if (disabled) {
                    checked = "checked";
                }

                var tpl = {
                    "event": fullEvent,
                    "checked": checked
                };
                var html = MM.tpl.render(MM.plugins.events.templates.event.html, tpl);

                var title = '

';
                title += '
' + MM.util.formatText(fullEvent.name) + '

';

                MM.panels.show('right', html, {title: title});

                $("#disable-event").bind("change", function() {

                    if (window.plugin && window.plugin.notification && window.plugin.notification.local) {
                        var disable = $(this).is(':checked');
                        if (disable) {
                            window.plugin.notification.local.cancel(localEventId);
                            MM.db.remove("events", localEventId);
                            MM.db.insert("eventsDisabled", {id: localEventId});
                        } else {
                            var d = new Date(fullEvent.timestart * 1000);

                            window.plugin.notification.local.add(
                                {
                                    id: localEventId,
                                    date: d,
                                    title: MM.lang.s("events"),
                                    message: fullEvent.name,
                                    badge: 1
                                }
                            );
                            MM.db.insert("events", {id: localEventId});
                            MM.db.remove("eventsDisabled", localEventId);
                        }
                    }
                });
            }
        },

        _getEvents: function(days, settings, successCallback, errorCallback) {
            settings = settings || null;
            // The core_calendar_get_calendar_events needs all the current user courses and groups.
            var params = {
                "options[userevents]": 1,
                "options[siteevents]": 1,
                "options[timestart]": MM.util.timestamp(),
                "options[timeend]": MM.util.timestamp() + (MM.util.SECONDS_DAY * days)
            };

            var courses = MM.db.where("courses", {siteid: MM.config.current_site.id});
            $.each(courses, function(index, course) {
                params["events[courseids][" + index + "]"] = course.get("courseid");
            });

            var wsFunction = "core_calendar_get_calendar_events";
            if (!MM.util.wsAvailable(wsFunction)) {
                wsFunction = 'local_mobile_core_calendar_get_calendar_events';
            }

            MM.moodleWSCall(wsFunction,
                params,
                function(r) {
                    successCallback(r);
                },
                settings,
                errorCallback
            );
        },

        /**
         * We create an event Id, note that we try to make this id unique between sites but colissions may exists.
         * It must be an integer convertible value (Android limitation).
         * @param  {object} event An event object
         * @return {string}       A string convertible to number
         */
        _getLocalEventUniqueId: function(event) {
            var siteCode = MM.config.current_site.id.charCodeAt(0) + "";
            siteCode += "" + MM.config.current_site.id.charCodeAt(1);
            siteCode += "" + MM.config.current_site.id.charCodeAt(2);
            siteCode += "" + MM.config.current_site.id.charCodeAt(3);

            return siteCode + "" + event.id;
        },

        checkLocalNotifications: function() {
            // Check if the plugin is loaded.
            var enabled = MM.getConfig("event_notif_on", false);

            if (!enabled) {
                return false;
            }

            if (window.plugin && window.plugin.notification && window.plugin.notification.local) {
                // Cancell all to resync.
                var events = MM.db.where("events", {'site': MM.config.current_site.id});
                console.log(events);
                _.each(events, function(e) {
                    MM.db.remove("events", e.get("id"));
                    window.plugin.notification.local.cancel(e.get("id"));
                });

                MM.plugins.events._getEvents(
                30,
                {
                    getFromCache: false,
                    saveToCache: true
                },
                function(events) {
                    if (events.events) {
                        _.each(events.events, function(event) {
                            var eventId = MM.plugins.events._getLocalEventUniqueId(event);

                            var disabled = MM.db.get("eventsDisabled", eventId);

                            if (!disabled) {
                                // We insert the event allways, if already exists it will be updated.
                                var d = new Date(event.timestart * 1000);

                                window.plugin.notification.local.add(
                                    {
                                        id: eventId,
                                        date: d,
                                        title: MM.lang.s("events"),
                                        message: event.name,
                                        badge: 1
                                    }
                                );
                                MM.db.insert("events", {id: eventId});
                            }
                        });
                    }
                },
                function() {}
            );
            }
        },

        templates: {
            "event": {
                html: eventTpl
            },
            "events": {
                html: eventsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<commit_msg>MOBILE-899 notifications: Add reminders<commit_after>var templates = [
    "root/externallib/text!root/plugins/events/events.html",
    "root/externallib/text!root/plugins/events/event.html"
];

define(templates, function (eventsTpl, eventTpl) {
    var plugin = {
        settings: {
            name: "events",
            type: "general",
            icon: "plugins/events/icon.png",
            menuURL: "#events/90",
            lang: {
                component: "core"
            }
        },

        storage: {
            "event": {type: "model"},
            "events": {type: "collection", model: "event"}
        },

        routes: [
            ["events/:days", "show_events", "showEvents"],
            ["events/show/:id", "show_event", "showEvent"]
        ],

        // This is like a static variable where we store the last Events retrieved in JSON format.
        lastEvents: null,

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            return MM.util.wsAvailable('core_calendar_get_calendar_events') ||
                    MM.util.wsAvailable('local_mobile_core_calendar_get_calendar_events');
        },

        _getCalendarEventsSucces: function(response, days) {
            var daysIncrement = 90;
            var pageTitle = MM.lang.s("events") + "  " + days + " " + MM.lang.s("days");

            MM.plugins.events.lastEvents = typeof(response.events !== "undefined")? response.events : [];

            var d;
            // Formatting.
            for (var el in MM.plugins.events.lastEvents) {
                var event = MM.plugins.events.lastEvents[el];

                d = new Date(event.timestart * 1000);
                event.startdate = d.toLocaleDateString();
                event.starttime = d.toLocaleTimeString(MM.lang.current, {hour: '2-digit', minute:'2-digit'});

                if (event.timeduration) {
                    d = new Date((event.timestart + event.timeduration) * 1000);
                    event.enddate = d.toLocaleDateString();
                    event.endtime = d.toLocaleTimeString(MM.lang.current, {hour: '2-digit', minute:'2-digit'});
                } else {
                    event.enddate = 0;
                    event.endtime = 0;
                }

                MM.plugins.events.lastEvents[el] = event;
            }

            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');

            var tpl = {events: MM.plugins.events.lastEvents};

            var html = MM.tpl.render(MM.plugins.events.templates.events.html, tpl);

            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                MM.panels.show('center', html, {title: pageTitle});
            } else  {
                if (MM.deviceType == "tablet") {
                    MM.panels.show('center', html, {title: pageTitle, hideRight: true});
                } else {
                    MM.panels.show('center', html, {title: pageTitle});
                }
            }

            $("#events-showmore").on(MM.clickType, function(e) {
                MM.plugins.events.showEvents(days + daysIncrement);
            });
            // Load the first event.
            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                $("#panel-center li:eq(0)").addClass("selected-row");
                MM.plugins.events.showEvent(0);
                $("#panel-center li:eq(0)").addClass("selected-row");
            }

        },

        _getCalendarEventsFailure: function(m) {
            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');
            if (typeof(m) !== "undefined" && m) {
                MM.popErrorMessage(m);
            }
        },

        /**
         * Display global and course events for all the user courses
         * TODO: Support groups events also
         *
         * @param  {integer} days The number of days for displaying events starting today
         */
        showEvents: function(days) {
            MM.panels.showLoading('center');

            days = parseInt(days, 10);

            if (MM.deviceType == "tablet") {
                MM.panels.showLoading('right');
            }
            // Adding loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').addClass('loading-row');

            MM.plugins.events._getEvents(
                days,
                null,
                function(r) {
                    MM.plugins.events._getCalendarEventsSucces(r, days);
                },
                MM.plugins.events._getCalendarEventsFailure
            );
        },

        /**
         * Displays a single event information
         *
         * @param  {integer} eventId The index position in the original events array retrieved.
         */
        showEvent: function(eventId) {
            var pageTitle = MM.lang.s("events");

            if (typeof(MM.plugins.events.lastEvents[eventId]) != "undefined") {
                var fullEvent = MM.plugins.events.lastEvents[eventId];
                var course = MM.db.get("courses", MM.config.current_site.id + "-" + fullEvent.courseid);
                if (course) {
                    fullEvent.courseName = MM.util.formatText(course.get("fullname"));
                }

                var localEventId = MM.plugins.events._getLocalEventUniqueId(fullEvent);

                var checked = "";
                if (MM.plugins.events._eventIsDisabled(localEventId)) {
                    checked = "checked";
                }

                var tpl = {
                    "event": fullEvent,
                    "checked": checked
                };
                var html = MM.tpl.render(MM.plugins.events.templates.event.html, tpl);

                var title = '

';
                title += '
' + MM.util.formatText(fullEvent.name) + '

';

                MM.panels.show('right', html, {title: title});

                $("#disable-event").bind("change", function() {

                    if (window.plugin && window.plugin.notification && window.plugin.notification.local) {
                        var disable = $(this).is(':checked');
                        if (disable) {
                            window.plugin.notification.local.cancel(localEventId);
                            MM.plugins.events._setEnabled(localEventId, false);
                        } else {
                            MM.plugins.events._createLocalEvent(fullEvent);
                        }
                    }
                });
            }
        },

        _getEvents: function(days, settings, successCallback, errorCallback) {
            settings = settings || null;
            // The core_calendar_get_calendar_events needs all the current user courses and groups.
            var params = {
                "options[userevents]": 1,
                "options[siteevents]": 1,
                "options[timestart]": MM.util.timestamp(),
                "options[timeend]": MM.util.timestamp() + (MM.util.SECONDS_DAY * days)
            };

            var courses = MM.db.where("courses", {siteid: MM.config.current_site.id});
            $.each(courses, function(index, course) {
                params["events[courseids][" + index + "]"] = course.get("courseid");
            });

            var wsFunction = "core_calendar_get_calendar_events";
            if (!MM.util.wsAvailable(wsFunction)) {
                wsFunction = 'local_mobile_core_calendar_get_calendar_events';
            }

            MM.moodleWSCall(wsFunction,
                params,
                function(r) {
                    successCallback(r);
                },
                settings,
                errorCallback
            );
        },

        /**
         * We create an event Id, note that we try to make this id unique between sites but colissions may exists.
         * It must be an integer convertible value (Android limitation).
         * @param  {object} event An event object
         * @return {string}       A string convertible to number
         */
        _getLocalEventUniqueId: function(event) {
            var siteCode = MM.config.current_site.id.charCodeAt(0) + "";
            siteCode += "" + MM.config.current_site.id.charCodeAt(1);
            siteCode += "" + MM.config.current_site.id.charCodeAt(2);
            siteCode += "" + MM.config.current_site.id.charCodeAt(3);

            return siteCode + "" + event.id;
        },

        checkLocalNotifications: function() {
            // Check if the plugin is loaded.
            var enabled = MM.getConfig("event_notif_on", false);

            if (!enabled) {
                return false;
            }

            if (window.plugin && window.plugin.notification && window.plugin.notification.local) {
                // Cancell all to resync.
                var events = MM.db.where("events", {'site': MM.config.current_site.id});
                _.each(events, function(e) {
                    if (e.get("enabled")) {
                        MM.db.remove("events", e.get("id"));
                        window.plugin.notification.local.cancel(e.get("id"));
                    }
                });

                MM.plugins.events._getEvents(
                30,
                {
                    getFromCache: false,
                    saveToCache: true
                },
                function(events) {
                    if (events.events) {
                        _.each(events.events, function(event) {
                            var eventId = MM.plugins.events._getLocalEventUniqueId(event);

                            if (!MM.plugins.events._eventIsDisabled(eventId)) {
                                MM.plugins.events._createLocalEvent(event);
                            }
                        });
                    }
                },
                function() {}
            );
            }
        },

        _eventIsDisabled: function(localEventId) {
            var event = MM.db.get("events", localEventId);

            if (!event) {
                return false;
            }

            if (event.get("enabled")) {
                return false;
            }
            return true;
        },

        _setEnabled: function(localEventId, status) {
            var event = MM.db.get("events", localEventId);
            if (event) {
                event.set("enabled", status);
                MM.db.insert("events", event);
            }
        },

        _createLocalEvent: function(event) {
            var eventId = MM.plugins.events._getLocalEventUniqueId(event);

            // We insert the event allways, if already exists it will be updated.
            var d = new Date(event.timestart * 1000);

            window.plugin.notification.local.add(
                {
                    id: eventId,
                    date: d,
                    title: MM.lang.s("events"),
                    message: event.name,
                    badge: 1
                }
            );
            MM.db.insert("events", {id: eventId, enabled: true});

        },

        templates: {
            "event": {
                html: eventTpl
            },
            "events": {
                html: eventsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<|endoftext|>


<commit_before>const stylelint = require('stylelint')

const ruleName = 'primer/no-override'
const CLASS_PATTERN = /(\.[-\w]+)/g

let primerMeta = {}
let availableBundles = []
try {
  primerMeta = require('@primer/css/dist/meta.json')
} catch (error) {
  throw new Error(
    `[${ruleName}] Unable to require('@primer/css/dist/meta.json')! Did you install it as a peerDependency?`
  )
}

availableBundles = Object.keys(primerMeta.bundles)

const messages = stylelint.utils.ruleMessages(ruleName, {
  rejected: selector => `The selector "${selector}" should not be overridden.`
})

module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}) => {
  const {bundles = ['utilities']} = options

  const immutableSelectors = new Set()
  const immutableClassSelectors = new Set()
  for (const bundle of bundles) {
    if (!availableBundles.includes(bundle)) {
      throw new Error(`Unknown @primer/css bundle: "${bundle}"!`)
    }
    const {cssstats} = require(`@primer/css/dist/${bundle}`)
    for (const selector of cssstats.selectors.values) {
      immutableSelectors.add(selector)
      for (const classSelector of getClassSelectors(selector)) {
        immutableClassSelectors.add(classSelector)
      }
    }
  }

  // console.warn(`Got ${immutableSelectors.size} immutable selectors from: "${bundles.join('", "')}"`)

  return (root, result) => {
    const validOptions = stylelint.utils.validateOptions(result, ruleName, {
      actual: enabled,
      possible: [true, false]
    })

    if (!validOptions) {
      return
    }

    const report = (rule, selector) =>
      stylelint.utils.report({
        message: messages.rejected(selector),
        node: rule,
        result,
        ruleName
      })

    root.walkRules(rule => {
      if (immutableSelectors.has(rule.selector)) {
        report(rule, rule.selector)
      } else {
        for (const classSelector of getClassSelectors(rule.selector)) {
          if (immutableClassSelectors.has(classSelector)) {
            report(rule, classSelector)
          }
        }
      }
    })
  }
})

function getClassSelectors(selector) {
  const match = selector.match(CLASS_PATTERN)
  return match ? [...match] : []
}
<commit_msg>fix(no-override): fix how invalid bundle names are reported<commit_after>const stylelint = require('stylelint')

const ruleName = 'primer/no-override'
const CLASS_PATTERN = /(\.[-\w]+)/g

let primerMeta = {}
let availableBundles = []
try {
  primerMeta = require('@primer/css/dist/meta.json')
} catch (error) {
  throw new Error(
    `[${ruleName}] Unable to require('@primer/css/dist/meta.json')! Did you install it as a peerDependency?`
  )
}

availableBundles = Object.keys(primerMeta.bundles)

const messages = stylelint.utils.ruleMessages(ruleName, {
  rejected: selector => `The selector "${selector}" should not be overridden.`
})

module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}) => {
  const {bundles = ['utilities']} = options

  const immutableSelectors = new Set()
  const immutableClassSelectors = new Set()
  for (const bundle of bundles) {
    if (!availableBundles.includes(bundle)) {
      continue
    }
    const stats = require(`@primer/css/dist/stats/${bundle}.json`)
    for (const selector of stats.selectors.values) {
      immutableSelectors.add(selector)
      for (const classSelector of getClassSelectors(selector)) {
        immutableClassSelectors.add(classSelector)
      }
    }
  }

  // console.warn(`Got ${immutableSelectors.size} immutable selectors from: "${bundles.join('", "')}"`)

  return (root, result) => {
    if (!Array.isArray(bundles) || bundles.some(bundle => !availableBundles.includes(bundle))) {
      const invalidBundles = Array.isArray(bundles)
        ? `"${bundles.filter(bundle => !availableBundles.includes(bundle)).join('", "')}"`
        : '(not an array)'
      result.warn(
        `The "bundles" option must be an array of valid bundles; got: ${invalidBundles}`,
        {
          stylelintType: 'invalidOption',
          stylelintReference: 'https://github.com/primer/stylelint-config-primer#options'
        }
      )
    }

    if (!enabled) {
      return
    }

    const report = (rule, selector) =>
      stylelint.utils.report({
        message: messages.rejected(selector),
        node: rule,
        result,
        ruleName
      })

    root.walkRules(rule => {
      if (immutableSelectors.has(rule.selector)) {
        report(rule, rule.selector)
      } else {
        for (const classSelector of getClassSelectors(rule.selector)) {
          if (immutableClassSelectors.has(classSelector)) {
            report(rule, classSelector)
          }
        }
      }
    })
  }
})

function getClassSelectors(selector) {
  const match = selector.match(CLASS_PATTERN)
  return match ? [...match] : []
}
<|endoftext|>


<commit_before>class Inode {
  constructor(config = {}) {
    this.links = 0;
    // Merge inode config with `this` to override anything
    Object.assign( this, config );
  }
}

class Disk {
  constructor() {
    this.uuid = genUUID();
    this.inodes = [
      new Inode({
        links: 1,
        id: 0,
        type: "d",
        files: {
          ".": 0,
          "..": 0
        }
      })
    ];
  }

  // Add a new inode to the disk
  // Defaults to just adding an inode, but if you pass
  // `parentInode` as an Inode instance, it will add the name to the parent file list
  addInode(type, name=null, parentInode=null) {
    // Reject if name contains a "/"
    if ( name.match("/") ) {
      console.warn("No '/' allowed in a name");
      return -1;
    }
    const id = this.inodes.length;
    this.inodes[id] = new Inode({
      links: 1,
      type: type,
      id: id
    });
    // Check if inode and directory
    if (parentInode instanceof Inode && parentInode.type === "d") {
      parentInode.files[name] = id;
    }
    return this.inodes[id];
  }

  // Add a new file to the disk
  mkFile(name, parentInode) {
    const inode = this.addInode("f", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    inode.data = "";
    return inode;
  }

  // Add a new directory Inode to the disk
  mkDir(name, parentInode) {
    const inode = this.addInode("d", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    inode.files = {
      ".": inode.id,
      "..": parentInode.id
    }
    return inode;
  }

  // Make a hard link inode
  mkLink(name, parentInode, targetInode) {
    // Same as in addInode, not very DRY I know...
    if ( name.match("/") ) {
      console.warn("No '/' allowed in a name");
      return -1;
    }
    parentInode.files[name] = targetInode.id;
    return targetInode;
  }

  // Make a symbolic link inode
  mkSymLink(name, parentInode, targetPath) {
    const inode = this.addInode("sl", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    const path = new Pathname(targetPath).clean;
    inode.redirect = path;
    return inode;
  }
}<commit_msg>remove console.warn<commit_after>class Inode {
  constructor(config = {}) {
    this.links = 0;
    // Merge inode config with `this` to override anything
    Object.assign( this, config );
  }
}

class Disk {
  constructor() {
    this.uuid = genUUID();
    this.inodes = [
      new Inode({
        links: 1,
        id: 0,
        type: "d",
        files: {
          ".": 0,
          "..": 0
        }
      })
    ];
  }

  // Add a new inode to the disk
  // Defaults to just adding an inode, but if you pass
  // `parentInode` as an Inode instance, it will add the name to the parent file list
  addInode(type, name=null, parentInode=null) {
    // Reject if name contains a "/"
    if ( name.match("/") ) {
      return -1;
    }
    const id = this.inodes.length;
    this.inodes[id] = new Inode({
      links: 1,
      type: type,
      id: id
    });
    // Check if inode and directory
    if (parentInode instanceof Inode && parentInode.type === "d") {
      parentInode.files[name] = id;
    }
    return this.inodes[id];
  }

  // Add a new file to the disk
  mkFile(name, parentInode) {
    const inode = this.addInode("f", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    inode.data = "";
    return inode;
  }

  // Add a new directory Inode to the disk
  mkDir(name, parentInode) {
    const inode = this.addInode("d", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    inode.files = {
      ".": inode.id,
      "..": parentInode.id
    }
    return inode;
  }

  // Make a hard link inode
  mkLink(name, parentInode, targetInode) {
    // Same as in addInode, not very DRY I know...
    if ( name.match("/") ) {
      return -1;
    }
    parentInode.files[name] = targetInode.id;
    return targetInode;
  }

  // Make a symbolic link inode
  mkSymLink(name, parentInode, targetPath) {
    const inode = this.addInode("sl", name, parentInode);
    if (inode < 0) {
      return -1;
    }
    const path = new Pathname(targetPath).clean;
    inode.redirect = path;
    return inode;
  }
}
<|endoftext|>


<commit_before>module.exports = {

  name: 'utils',
  version: require('../../package.json').version,

  init: function(app, done) {
    app.utils = require('util');
    app.utils.async = require('async');
    app.utils.glob = require('glob');
    app.utils.clone = require('clone');

    /**
     * Extend an object with defaults.
     *
     * @param [deep] {Boolean} If true, performs a deep (recursive) merge.
     * @param obj {Object} The object to extend.
     * @param defaults {Object} An object containing the defaults.
     */
    app.utils.defaults = function (deep, obj, defaults) {
      var copy;

      if (arguments.length === 2) {
        defaults = obj;
        obj = deep;
        deep = false;
      }

      // Clone the defaults so we dont transfer any properties by reference.
      if (deep) {
        copy = app.utils.clone(defaults);
      }
      else {
        copy = defaults;
      }

      Object.keys(copy).forEach(function(key) {
        if (deep && (typeof obj[key] === 'object') && (typeof copy[key] === 'object')) {
          module.exports(deep, obj[key], copy[key]);
        }
        else if (!obj.hasOwnProperty(key)) {
          obj[key] = copy[key];
        }
      });
    };

    done();
  }
};
<commit_msg>Fixes error in app.utils.defaults.<commit_after>module.exports = {

  name: 'utils',
  version: require('../../package.json').version,

  init: function(app, done) {
    app.utils = require('util');
    app.utils.async = require('async');
    app.utils.glob = require('glob');
    app.utils.clone = require('clone');

    /**
     * Extend an object with defaults.
     *
     * @param [deep] {Boolean} If true, performs a deep (recursive) merge.
     * @param obj {Object} The object to extend.
     * @param defaults {Object} An object containing the defaults.
     */
    app.utils.defaults = function (deep, obj, defaults) {
      var copy;

      if (arguments.length === 2) {
        defaults = obj;
        obj = deep;
        deep = false;
      }

      // Clone the defaults so we dont transfer any properties by reference.
      if (deep) {
        copy = app.utils.clone(defaults);
      }
      else {
        copy = defaults;
      }

      Object.keys(copy).forEach(function(key) {
        if (deep && (typeof obj[key] === 'object') && (typeof copy[key] === 'object')) {
          app.utils.defaults(deep, obj[key], copy[key]);
        }
        else if (!obj.hasOwnProperty(key)) {
          obj[key] = copy[key];
        }
      });
    };

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


<commit_before>/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
//     NoFlo - Flow-Based Programming for JavaScript
//     (c) 2017-2018 Flowhub UG
//     NoFlo may be freely distributed under the MIT license
const {
  ComponentLoader
} = require('./ComponentLoader');
const {
  Network
} = require('./Network');
const IP = require('./IP');
const internalSocket = require('./InternalSocket');
const {
  Graph
} = require('fbp-graph');

// ## asCallback embedding API
//
// asCallback is a helper for embedding NoFlo components or
// graphs in other JavaScript programs.
//
// By using the `noflo.asCallback` function, you can turn any
// NoFlo component or NoFlo Graph instance into a regular,
// Node.js-style JavaScript function.
//
// Each call to that function starts a new NoFlo network where
// the given input arguments are sent as IP objects to matching
// inports. Once the network finishes, the IP objects received
// from the network will be sent to the callback function.
//
// If there was anything sent to an `error` outport, this will
// be provided as the error argument to the callback.

// ### Option normalization
//
// Here we handle the input valus given to the `asCallback`
// function. This allows passing things like a pre-initialized
// NoFlo ComponentLoader, or giving the component loading
// baseDir context.
const normalizeOptions = function(options, component) {
  if (!options) { options = {}; }
  if (!options.name) { options.name = component; }
  if (options.loader) {
    options.baseDir = options.loader.baseDir;
  }
  if (!options.baseDir && process && process.cwd) {
    options.baseDir = process.cwd();
  }
  if (!options.loader) {
    options.loader = new ComponentLoader(options.baseDir);
  }
  if (!options.raw) { options.raw = false; }
  return options;
};

// ### Network preparation
//
// Each invocation of the asCallback-wrapped NoFlo graph
// creates a new network. This way we can isolate multiple
// executions of the function in their own contexts.
const prepareNetwork = function(component, options, callback) {
  // If we were given a graph instance, then just create a network
  let network;
  if (typeof component === 'object') {
    component.componentLoader = options.loader;

    network = new Network(component, options);
    // Wire the network up
    network.connect(function(err) {
      if (err) {
        callback(err);
        return;
      }
      callback(null, network);
    });
    return;
  }

  // Start by loading the component
  options.loader.load(component, function(err, instance) {
    let def, port;
    if (err) {
      callback(err);
      return;
    }
    // Prepare a graph wrapping the component
    const graph = new Graph(options.name);
    const nodeName = options.name;
    graph.addNode(nodeName, component);
    // Expose ports
    const inPorts = instance.inPorts.ports;
    const outPorts = instance.outPorts.ports;
    for (port in inPorts) {
      def = inPorts[port];
      graph.addInport(port, nodeName, port);
    }
    for (port in outPorts) {
      def = outPorts[port];
      graph.addOutport(port, nodeName, port);
    }
    // Prepare network
    graph.componentLoader = options.loader;
    network = new Network(graph, options);
    // Wire the network up and start execution
    network.connect(function(err) {
      if (err) {
        callback(err);
        return;
      }
      callback(null, network);
    });
  });
};

// ### Network execution
//
// Once network is ready, we connect to all of its exported
// in and outports and start the network.
//
// Input data is sent to the inports, and we collect IP
// packets received on the outports.
//
// Once the network finishes, we send the resulting IP
// objects to the callback.
const runNetwork = function(network, inputs, options, callback) {
  // Prepare inports
  const inPorts = Object.keys(network.graph.inports);
  let inSockets = {};
  // Subscribe outports
  const received = [];
  const outPorts = Object.keys(network.graph.outports);
  let outSockets = {};
  outPorts.forEach(function(outport) {
    const portDef = network.graph.outports[outport];
    const process = network.getNode(portDef.process);
    outSockets[outport] = internalSocket.createSocket();
    process.component.outPorts[portDef.port].attach(outSockets[outport]);
    outSockets[outport].from = {
      process,
      port: portDef.port
    };
    outSockets[outport].on('ip', function(ip) {
      const res = {};
      res[outport] = ip;
      received.push(res);
    });
  });
  // Subscribe to process errors
  const onError = function(err) {
    callback(err.error);
    network.removeListener('end', onEnd);
  };
  network.once('process-error', onError);
  // Subscribe network finish
  var onEnd = function() {
    // Clear listeners
    for (let port in outSockets) {
      const socket = outSockets[port];
      socket.from.process.component.outPorts[socket.from.port].detach(socket);
    }
    outSockets = {};
    inSockets = {};
    callback(null, received);
    network.removeListener('process-error', onError);
  };
  network.once('end', onEnd);
  // Start network
  network.start(function(err) {
    if (err) {
      callback(err);
      return;
    }
    // Send inputs
    for (let inputMap of Array.from(inputs)) {
      for (let port in inputMap) {
        const value = inputMap[port];
        if (!inSockets[port]) {
          const portDef = network.graph.inports[port];
          const process = network.getNode(portDef.process);
          inSockets[port] = internalSocket.createSocket();
          process.component.inPorts[portDef.port].attach(inSockets[port]);
        }
        try {
          if (IP.isIP(value)) {
            inSockets[port].post(value);
            continue;
          }
          inSockets[port].post(new IP('data', value));
        } catch (e) {
          callback(e);
          network.removeListener('process-error', onError);
          network.removeListener('end', onEnd);
          return;
        }
      }
    }
  });
};

var getType = function(inputs, network) {
  // Scalar values are always simple inputs
  if (typeof inputs !== 'object') { return 'simple'; }

  if (Array.isArray(inputs)) {
    const maps = inputs.filter(entry => getType(entry, network) === 'map');
    // If each member if the array is an input map, this is a sequence
    if (maps.length === inputs.length) { return 'sequence'; }
    // Otherwise arrays must be simple inputs
    return 'simple';
  }

  // Empty objects can't be maps
  if (!Object.keys(inputs).length) { return 'simple'; }
  for (let key in inputs) {
    const value = inputs[key];
    if (!network.graph.inports[key]) { return 'simple'; }
  }
  return 'map';
};

const prepareInputMap = function(inputs, inputType, network) {
  // Sequence we can use as-is
  if (inputType === 'sequence') { return inputs; }
  // We can turn a map to a sequence by wrapping it in an array
  if (inputType === 'map') { return [inputs]; }
  // Simple inputs need to be converted to a sequence
  let inPort = Object.keys(network.graph.inports)[0];
  // If we have a port named "IN", send to that
  if (network.graph.inports.in) { inPort = 'in'; }
  const map = {};
  map[inPort] = inputs;
  return [map];
};

const normalizeOutput = function(values, options) {
  if (options.raw) { return values; }
  const result = [];
  let previous = null;
  let current = result;
  for (let packet of Array.from(values)) {
    if (packet.type === 'openBracket') {
      previous = current;
      current = [];
      previous.push(current);
    }
    if (packet.type === 'data') {
      current.push(packet.data);
    }
    if (packet.type === 'closeBracket') {
      current = previous;
    }
  }
  if (result.length === 1) {
    return result[0];
  }
  return result;
};

const sendOutputMap = function(outputs, resultType, options, callback) {
  // First check if the output sequence contains errors
  const errors = outputs.filter(map => map.error != null).map(map => map.error);
  if (errors.length) {
    callback(normalizeOutput(errors, options));
    return;
  }

  if (resultType === 'sequence') {
    callback(null, outputs.map(function(map) {
      const res = {};
      for (let key in map) {
        const val = map[key];
        if (options.raw) {
          res[key] = val;
          continue;
        }
        res[key] = normalizeOutput([val], options);
      }
      return res;
    })
    );
    return;
  }

  // Flatten the sequence
  const mappedOutputs = {};
  for (let map of Array.from(outputs)) {
    for (let key in map) {
      const val = map[key];
      if (!mappedOutputs[key]) { mappedOutputs[key] = []; }
      mappedOutputs[key].push(val);
    }
  }

  const outputKeys = Object.keys(mappedOutputs);
  const withValue = outputKeys.filter(outport => mappedOutputs[outport].length > 0);
  if (withValue.length === 0) {
    // No output
    callback(null);
    return;
  }
  if ((withValue.length === 1) && (resultType === 'simple')) {
    // Single outport
    callback(null, normalizeOutput(mappedOutputs[withValue[0]], options));
    return;
  }
  const result = {};
  for (let port in mappedOutputs) {
    const packets = mappedOutputs[port];
    result[port] = normalizeOutput(packets, options);
  }
  callback(null, result);
};

exports.asCallback = function(component, options) {
  options = normalizeOptions(options, component);
  return function(inputs, callback) {
    prepareNetwork(component, options, function(err, network) {
      if (err) {
        callback(err);
        return;
      }
      const resultType = getType(inputs, network);
      const inputMap = prepareInputMap(inputs, resultType, network);
      runNetwork(network, inputMap, options, function(err, outputMap) {
        if (err) {
          callback(err);
          return;
        }
        sendOutputMap(outputMap, resultType, options, callback);
      });
    });
  };
};
<commit_msg>decaffeinate: Run post-processing cleanups on AsCallback.coffee<commit_after>/* eslint-disable
    func-names,
    guard-for-in,
    no-continue,
    no-param-reassign,
    no-restricted-syntax,
    no-shadow,
    no-unused-vars,
    no-use-before-define,
    no-var,
    vars-on-top,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
//     NoFlo - Flow-Based Programming for JavaScript
//     (c) 2017-2018 Flowhub UG
//     NoFlo may be freely distributed under the MIT license
const {
  Graph,
} = require('fbp-graph');
const {
  ComponentLoader,
} = require('./ComponentLoader');
const {
  Network,
} = require('./Network');
const IP = require('./IP');
const internalSocket = require('./InternalSocket');

// ## asCallback embedding API
//
// asCallback is a helper for embedding NoFlo components or
// graphs in other JavaScript programs.
//
// By using the `noflo.asCallback` function, you can turn any
// NoFlo component or NoFlo Graph instance into a regular,
// Node.js-style JavaScript function.
//
// Each call to that function starts a new NoFlo network where
// the given input arguments are sent as IP objects to matching
// inports. Once the network finishes, the IP objects received
// from the network will be sent to the callback function.
//
// If there was anything sent to an `error` outport, this will
// be provided as the error argument to the callback.

// ### Option normalization
//
// Here we handle the input valus given to the `asCallback`
// function. This allows passing things like a pre-initialized
// NoFlo ComponentLoader, or giving the component loading
// baseDir context.
const normalizeOptions = function (options, component) {
  if (!options) { options = {}; }
  if (!options.name) { options.name = component; }
  if (options.loader) {
    options.baseDir = options.loader.baseDir;
  }
  if (!options.baseDir && process && process.cwd) {
    options.baseDir = process.cwd();
  }
  if (!options.loader) {
    options.loader = new ComponentLoader(options.baseDir);
  }
  if (!options.raw) { options.raw = false; }
  return options;
};

// ### Network preparation
//
// Each invocation of the asCallback-wrapped NoFlo graph
// creates a new network. This way we can isolate multiple
// executions of the function in their own contexts.
const prepareNetwork = function (component, options, callback) {
  // If we were given a graph instance, then just create a network
  let network;
  if (typeof component === 'object') {
    component.componentLoader = options.loader;

    network = new Network(component, options);
    // Wire the network up
    network.connect((err) => {
      if (err) {
        callback(err);
        return;
      }
      callback(null, network);
    });
    return;
  }

  // Start by loading the component
  options.loader.load(component, (err, instance) => {
    let def; let
      port;
    if (err) {
      callback(err);
      return;
    }
    // Prepare a graph wrapping the component
    const graph = new Graph(options.name);
    const nodeName = options.name;
    graph.addNode(nodeName, component);
    // Expose ports
    const inPorts = instance.inPorts.ports;
    const outPorts = instance.outPorts.ports;
    for (port in inPorts) {
      def = inPorts[port];
      graph.addInport(port, nodeName, port);
    }
    for (port in outPorts) {
      def = outPorts[port];
      graph.addOutport(port, nodeName, port);
    }
    // Prepare network
    graph.componentLoader = options.loader;
    network = new Network(graph, options);
    // Wire the network up and start execution
    network.connect((err) => {
      if (err) {
        callback(err);
        return;
      }
      callback(null, network);
    });
  });
};

// ### Network execution
//
// Once network is ready, we connect to all of its exported
// in and outports and start the network.
//
// Input data is sent to the inports, and we collect IP
// packets received on the outports.
//
// Once the network finishes, we send the resulting IP
// objects to the callback.
const runNetwork = function (network, inputs, options, callback) {
  // Prepare inports
  const inPorts = Object.keys(network.graph.inports);
  let inSockets = {};
  // Subscribe outports
  const received = [];
  const outPorts = Object.keys(network.graph.outports);
  let outSockets = {};
  outPorts.forEach((outport) => {
    const portDef = network.graph.outports[outport];
    const process = network.getNode(portDef.process);
    outSockets[outport] = internalSocket.createSocket();
    process.component.outPorts[portDef.port].attach(outSockets[outport]);
    outSockets[outport].from = {
      process,
      port: portDef.port,
    };
    outSockets[outport].on('ip', (ip) => {
      const res = {};
      res[outport] = ip;
      received.push(res);
    });
  });
  // Subscribe to process errors
  const onError = function (err) {
    callback(err.error);
    network.removeListener('end', onEnd);
  };
  network.once('process-error', onError);
  // Subscribe network finish
  var onEnd = function () {
    // Clear listeners
    for (const port in outSockets) {
      const socket = outSockets[port];
      socket.from.process.component.outPorts[socket.from.port].detach(socket);
    }
    outSockets = {};
    inSockets = {};
    callback(null, received);
    network.removeListener('process-error', onError);
  };
  network.once('end', onEnd);
  // Start network
  network.start((err) => {
    if (err) {
      callback(err);
      return;
    }
    // Send inputs
    for (const inputMap of Array.from(inputs)) {
      for (const port in inputMap) {
        const value = inputMap[port];
        if (!inSockets[port]) {
          const portDef = network.graph.inports[port];
          const process = network.getNode(portDef.process);
          inSockets[port] = internalSocket.createSocket();
          process.component.inPorts[portDef.port].attach(inSockets[port]);
        }
        try {
          if (IP.isIP(value)) {
            inSockets[port].post(value);
            continue;
          }
          inSockets[port].post(new IP('data', value));
        } catch (e) {
          callback(e);
          network.removeListener('process-error', onError);
          network.removeListener('end', onEnd);
          return;
        }
      }
    }
  });
};

var getType = function (inputs, network) {
  // Scalar values are always simple inputs
  if (typeof inputs !== 'object') { return 'simple'; }

  if (Array.isArray(inputs)) {
    const maps = inputs.filter((entry) => getType(entry, network) === 'map');
    // If each member if the array is an input map, this is a sequence
    if (maps.length === inputs.length) { return 'sequence'; }
    // Otherwise arrays must be simple inputs
    return 'simple';
  }

  // Empty objects can't be maps
  if (!Object.keys(inputs).length) { return 'simple'; }
  for (const key in inputs) {
    const value = inputs[key];
    if (!network.graph.inports[key]) { return 'simple'; }
  }
  return 'map';
};

const prepareInputMap = function (inputs, inputType, network) {
  // Sequence we can use as-is
  if (inputType === 'sequence') { return inputs; }
  // We can turn a map to a sequence by wrapping it in an array
  if (inputType === 'map') { return [inputs]; }
  // Simple inputs need to be converted to a sequence
  let inPort = Object.keys(network.graph.inports)[0];
  // If we have a port named "IN", send to that
  if (network.graph.inports.in) { inPort = 'in'; }
  const map = {};
  map[inPort] = inputs;
  return [map];
};

const normalizeOutput = function (values, options) {
  if (options.raw) { return values; }
  const result = [];
  let previous = null;
  let current = result;
  for (const packet of Array.from(values)) {
    if (packet.type === 'openBracket') {
      previous = current;
      current = [];
      previous.push(current);
    }
    if (packet.type === 'data') {
      current.push(packet.data);
    }
    if (packet.type === 'closeBracket') {
      current = previous;
    }
  }
  if (result.length === 1) {
    return result[0];
  }
  return result;
};

const sendOutputMap = function (outputs, resultType, options, callback) {
  // First check if the output sequence contains errors
  const errors = outputs.filter((map) => map.error != null).map((map) => map.error);
  if (errors.length) {
    callback(normalizeOutput(errors, options));
    return;
  }

  if (resultType === 'sequence') {
    callback(null, outputs.map((map) => {
      const res = {};
      for (const key in map) {
        const val = map[key];
        if (options.raw) {
          res[key] = val;
          continue;
        }
        res[key] = normalizeOutput([val], options);
      }
      return res;
    }));
    return;
  }

  // Flatten the sequence
  const mappedOutputs = {};
  for (const map of Array.from(outputs)) {
    for (const key in map) {
      const val = map[key];
      if (!mappedOutputs[key]) { mappedOutputs[key] = []; }
      mappedOutputs[key].push(val);
    }
  }

  const outputKeys = Object.keys(mappedOutputs);
  const withValue = outputKeys.filter((outport) => mappedOutputs[outport].length > 0);
  if (withValue.length === 0) {
    // No output
    callback(null);
    return;
  }
  if ((withValue.length === 1) && (resultType === 'simple')) {
    // Single outport
    callback(null, normalizeOutput(mappedOutputs[withValue[0]], options));
    return;
  }
  const result = {};
  for (const port in mappedOutputs) {
    const packets = mappedOutputs[port];
    result[port] = normalizeOutput(packets, options);
  }
  callback(null, result);
};

exports.asCallback = function (component, options) {
  options = normalizeOptions(options, component);
  return function (inputs, callback) {
    prepareNetwork(component, options, (err, network) => {
      if (err) {
        callback(err);
        return;
      }
      const resultType = getType(inputs, network);
      const inputMap = prepareInputMap(inputs, resultType, network);
      runNetwork(network, inputMap, options, (err, outputMap) => {
        if (err) {
          callback(err);
          return;
        }
        sendOutputMap(outputMap, resultType, options, callback);
      });
    });
  };
};
<|endoftext|>