<commit_before>"use strict";

var _ = require('./lodash');
var Dom = require('./packages/dom');

var SelectionHandler = function(wrapper, mediator, editor) {
  this.wrapper = wrapper;
  this.mediator = mediator;
  this.editor = editor;
  this.options = editor.options;

  this.startIndex = undefined;
  this.endIndex = undefined;
  this.selecting = false;

  this._bindFunctions();
  this._bindMediatedEvents();

  this.initialize();
};

Object.assign(SelectionHandler.prototype, require('./function-bind'), require('./mediated-events'), {

  eventNamespace: 'selection',

  mediatedEvents: {
    'start': 'start',
    'render': 'render',
    'complete': 'complete',
    'all': 'all',
    'copy': 'copy',
    'update': 'update',
    'delete': 'delete',
    'cancel': 'cancel'
  },

  cancelSelectAll: function() {
    // Don't select if within an input field
    var editorEl = Dom.getClosest(document.activeElement, 'input');
    if (editorEl !== document.body) return true;
    // Don't select all if focused on element outside of the editor.
    if (this.options.selectionLimitToEditor) {
      var editorEl = Dom.getClosest(document.activeElement, '.st-outer');
      if (editorEl === document.body) return true;
    }
  },

  initialize: function() {
    window.addEventListener("keydown", (e) => {
      e = e || window.event;
      var ctrl = e.ctrlKey || e.metaKey;
      if (!ctrl) return;

      if (this.options.selectionCopy && e.key === "a") {
        if (this.cancelSelectAll()) return;
        e.preventDefault();
        this.mediator.trigger("selection:all");
      } else if (this.options.selectionCopy && e.key === "c") {
        if (!this.selecting) return;
        e.preventDefault();
        this.mediator.trigger("selection:copy");
      } else if (this.options.selectionDelete && e.key === "x") {
        this.mediator.trigger("selection:delete");
      }
    }, false);

    window.addEventListener("click", () => {
      this.mediator.trigger("selection:complete");
      this.mediator.trigger("selection:cancel");
    });
  },

  start: function(index) {
    this.startIndex = this.endIndex = index;
    window.mouseDown = true;
    this.mediator.trigger("selection:render");
    window.addEventListener("mousemove", this.onMouseMove);
  },

  onMouseMove: function() {},

  update: function(index) {
    this.endIndex = index;
    this.selecting = this.startIndex !== this.endIndex;
    this.mediator.trigger("selection:render");
  },

  complete: function() {
    this.selecting = false;
    window.removeEventListener("mousemove", this.onMouseMove);
  },

  all: function() {
    this.removeNativeSelection();

    var blocks = this.editor.getBlocks();
    this.selecting = true;
    this.startIndex = 0;
    this.endIndex = blocks.length;
    this.mediator.trigger("selection:render");
  },

  cancel: function() {
    this.mouseDown = false;
    this.selecting = false;
    this.render();
  },

  removeNativeSelection: function() {
    var sel = window.getSelection ? window.getSelection() : document.selection;
    if (sel) {
      if (sel.removeAllRanges) {
        sel.removeAllRanges();
      } else if (sel.empty) {
        sel.empty();
      }
    }
    document.activeElement && document.activeElement.blur();
  },

  render: function() {
    this.editor.getBlocks().forEach((block, idx) => {
      block.select(this.selecting && this.indexSelected(idx));
    });
  },

  getClipboardData: function() {
    this.editor.getData();

    var output = [];

    this.editor.getBlocks().forEach((block, idx) => {
      if (this.indexSelected(idx)) output.push(block.asClipboardHTML());
    });

    return output.join("\n");
  },

  copy: function() {
    var copyArea = document.body.querySelector(".st-copy-area");
    if (!copyArea) {
      copyArea = Dom.createElement("div", {
        contenteditable: true,
        class: 'st-copy-area st-utils__hidden',
      });
      document.body.appendChild(copyArea);
    }
    copyArea.innerHTML = this.getClipboardData();

    var selection = window.getSelection();
    var range = document.createRange();
    range.selectNodeContents(copyArea);
    selection.removeAllRanges();
    selection.addRange(range);

    try {
      document.execCommand('copy');
      copyArea.blur();
    }
    catch (err) {
      console.log("Copy could not be performed");
    }
  },

  delete: function() {
    this.editor.getBlocks().forEach((blockEl, idx) => {
      if (this.indexSelected(idx)) this.mediator.trigger("block:remove", block.blockID, { focusOnNext: true });
    });
  },

  indexSelected: function(index) {
    return index >= Math.min(this.startIndex, this.endIndex) && index <= Math.max(this.startIndex, this.endIndex);
  }

});

module.exports = SelectionHandler;
<commit_msg>Allow multiple editors to work on one page.<commit_after>"use strict";

var _ = require('./lodash');
var Dom = require('./packages/dom');

var SelectionHandler = function(wrapper, mediator, editor) {
  this.wrapper = wrapper;
  this.mediator = mediator;
  this.editor = editor;
  this.options = editor.options;

  this.startIndex = undefined;
  this.endIndex = undefined;
  this.selecting = false;

  this._bindFunctions();
  this._bindMediatedEvents();

  this.initialize();
};

Object.assign(SelectionHandler.prototype, require('./function-bind'), require('./mediated-events'), {

  eventNamespace: 'selection',

  mediatedEvents: {
    'start': 'start',
    'render': 'render',
    'complete': 'complete',
    'all': 'all',
    'copy': 'copy',
    'update': 'update',
    'delete': 'delete',
    'cancel': 'cancel'
  },

  cancelSelectAll: function() {
    // Don't select if within an input field
    var editorEl1 = Dom.getClosest(document.activeElement, 'input');
    if (editorEl1 !== document.body) return true;

    var editorEl2 = Dom.getClosest(document.activeElement, '.st-outer');

    // Don't select all if focused on element outside of the editor.
    if (this.options.selectionLimitToEditor) {
      if (editorEl2 !== this.wrapper) return true;
    }
  },

  initialize: function() {
    window.addEventListener("keydown", (e) => {
      e = e || window.event;
      var ctrl = e.ctrlKey || e.metaKey;
      if (!ctrl) return;

      if (this.options.selectionCopy && e.key === "a") {
        if (this.cancelSelectAll()) return;
        e.preventDefault();
        this.mediator.trigger("selection:all");
      } else if (this.options.selectionCopy && e.key === "c") {
        if (!this.selecting) return;
        e.preventDefault();
        this.mediator.trigger("selection:copy");
      } else if (this.options.selectionDelete && e.key === "x") {
        this.mediator.trigger("selection:delete");
      }
    }, false);

    window.addEventListener("click", () => {
      this.mediator.trigger("selection:complete");
      this.mediator.trigger("selection:cancel");
    });
  },

  start: function(index) {
    this.startIndex = this.endIndex = index;
    window.mouseDown = true;
    this.mediator.trigger("selection:render");
    window.addEventListener("mousemove", this.onMouseMove);
  },

  onMouseMove: function() {},

  update: function(index) {
    this.endIndex = index;
    this.selecting = this.startIndex !== this.endIndex;
    this.mediator.trigger("selection:render");
  },

  complete: function() {
    this.selecting = false;
    window.removeEventListener("mousemove", this.onMouseMove);
  },

  all: function() {
    this.removeNativeSelection();

    var blocks = this.editor.getBlocks();
    this.selecting = true;
    this.startIndex = 0;
    this.endIndex = blocks.length;
    this.mediator.trigger("selection:render");
  },

  cancel: function() {
    this.mouseDown = false;
    this.selecting = false;
    this.render();
  },

  removeNativeSelection: function() {
    var sel = window.getSelection ? window.getSelection() : document.selection;
    if (sel) {
      if (sel.removeAllRanges) {
        sel.removeAllRanges();
      } else if (sel.empty) {
        sel.empty();
      }
    }
    document.activeElement && document.activeElement.blur();
  },

  render: function() {
    this.editor.getBlocks().forEach((block, idx) => {
      block.select(this.selecting && this.indexSelected(idx));
    });
  },

  getClipboardData: function() {
    this.editor.getData();

    var output = [];

    this.editor.getBlocks().forEach((block, idx) => {
      if (this.indexSelected(idx)) output.push(block.asClipboardHTML());
    });

    return output.join("\n");
  },

  copy: function() {
    var copyArea = document.body.querySelector(".st-copy-area");
    if (!copyArea) {
      copyArea = Dom.createElement("div", {
        contenteditable: true,
        class: 'st-copy-area st-utils__hidden',
      });
      document.body.appendChild(copyArea);
    }
    copyArea.innerHTML = this.getClipboardData();

    var selection = window.getSelection();
    var range = document.createRange();
    range.selectNodeContents(copyArea);
    selection.removeAllRanges();
    selection.addRange(range);

    try {
      document.execCommand('copy');
      copyArea.blur();
    }
    catch (err) {
      console.log("Copy could not be performed");
    }
  },

  delete: function() {
    this.editor.getBlocks().forEach((blockEl, idx) => {
      if (this.indexSelected(idx)) this.mediator.trigger("block:remove", block.blockID, { focusOnNext: true });
    });
  },

  indexSelected: function(index) {
    return index >= Math.min(this.startIndex, this.endIndex) && index <= Math.max(this.startIndex, this.endIndex);
  }

});

module.exports = SelectionHandler;
<|endoftext|>


<commit_before>/*
  Prologue 1.2 by HTML5 UP
  html5up.net | @n33co
  Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/

/*********************************************************************************/
/* Settings                                                                      */
/*********************************************************************************/

var prologue_settings = {

  // skelJS (probably don't need to change anything here unless you know what you're doing)
  skelJS: {
    prefix: 'css/style',
    resetCSS: true,
    boxModel: 'border',
    useOrientation: true,
    breakpoints: {
      'widest':  { range: '1881-', hasStyleSheet: false, containers: 1400, grid: { gutters: 40 } },
      'wide':  { range: '961-1880', containers: 1200, grid: { gutters: 40 } },
      'normal':  { range: '961-1620', containers: 960, grid: { gutters: 40 } },
      'narrow':  { range: '961-1320', containers: 'fluid', grid: { gutters: 20 } },
      'narrower':  { range: '-960', containers: 'fluid', grid: { gutters: 15 } },
      'mobile':  { range: '-640', containers: 'fluid', lockViewport: true, grid: { gutters: 15, collapse: true } }
    }
  },

  // skelJS Plugins (ditto; don't change unless you know what you're doing)
  skelJSPlugins: {
    panels: {
      panels: {
        sidePanel: {
          breakpoints: 'narrower',
          position: 'left',
          size: 240,
          html: '

'
        }
      },
      overlays: {
        sidePanelToggle: {
          breakpoints: 'narrower',
          position: 'top-left',
          width: '3.5em',
          height: '2.25em',
          html: '

'
        }
      }
    }
  }
};

/*********************************************************************************/
/* Don't modify beyond this point unless you know what you're doing!             */
/*********************************************************************************/

// Initialize skelJS
skel.init(prologue_settings.skelJS, prologue_settings.skelJSPlugins);
<commit_msg>fix relative path directory for skeljs<commit_after>/*
  Prologue 1.2 by HTML5 UP
  html5up.net | @n33co
  Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/

/*********************************************************************************/
/* Settings                                                                      */
/*********************************************************************************/

var prologue_settings = {

  // skelJS (probably don't need to change anything here unless you know what you're doing)
  skelJS: {
    prefix: '/css/style',
    resetCSS: true,
    boxModel: 'border',
    useOrientation: true,
    breakpoints: {
      'widest':  { range: '1881-', hasStyleSheet: false, containers: 1400, grid: { gutters: 40 } },
      'wide':  { range: '961-1880', containers: 1200, grid: { gutters: 40 } },
      'normal':  { range: '961-1620', containers: 960, grid: { gutters: 40 } },
      'narrow':  { range: '961-1320', containers: 'fluid', grid: { gutters: 20 } },
      'narrower':  { range: '-960', containers: 'fluid', grid: { gutters: 15 } },
      'mobile':  { range: '-640', containers: 'fluid', lockViewport: true, grid: { gutters: 15, collapse: true } }
    }
  },

  // skelJS Plugins (ditto; don't change unless you know what you're doing)
  skelJSPlugins: {
    panels: {
      panels: {
        sidePanel: {
          breakpoints: 'narrower',
          position: 'left',
          size: 240,
          html: '

'
        }
      },
      overlays: {
        sidePanelToggle: {
          breakpoints: 'narrower',
          position: 'top-left',
          width: '3.5em',
          height: '2.25em',
          html: '

'
        }
      }
    }
  }
};

/*********************************************************************************/
/* Don't modify beyond this point unless you know what you're doing!             */
/*********************************************************************************/

// Initialize skelJS
skel.init(prologue_settings.skelJS, prologue_settings.skelJSPlugins);
<|endoftext|>


<commit_before>'use strict'
var ButtonNavigator = require('./ButtonNavigator')
var Slider = require('./Slider')
var EventManager = require('../lib/eventManager')
var yo = require('yo-yo')
var utils = require('../helpers/util.js')

function StepManager (_parent, _traceManager) {
  this.event = new EventManager()
  this.parent = _parent
  this.traceManager = _traceManager
  this.sourceMapByAddress = {}
  this.solidityMode = false

  var self = this
  this.parent.event.register('newTraceLoaded', this, function () {
    self.traceManager.getLength(function (error, length) {
      if (error) {
        console.log(error)
      } else {
        self.slider.init(length)
        self.init()
      }
    })
  })

  this.slider = new Slider(this.traceManager, (step) => {
    return this.solidityMode ? this.resolveToReducedTrace(step, 0) : step
  })
  this.slider.event.register('moved', this, function (step) {
    self.sliderMoved(step)
  })

  this.parent.callTree.event.register('callTreeReady', () => {
    this.solidityMode = true
    this.parent.vmDebugger.asmCode.event.register('hide', () => {
      this.solidityMode = this.parent.callTree.reducedTrace.length !== 0
    })
    this.parent.vmDebugger.asmCode.event.register('show', () => {
      this.solidityMode = false
    })
  })

  this.buttonNavigator = new ButtonNavigator(_parent, this.traceManager)
  this.buttonNavigator.event.register('stepIntoBack', this, function () {
    self.stepIntoBack()
  })
  this.buttonNavigator.event.register('stepIntoForward', this, function () {
    self.stepIntoForward()
  })
  this.buttonNavigator.event.register('stepOverBack', this, function () {
    self.stepOverBack()
  })
  this.buttonNavigator.event.register('stepOverForward', this, function () {
    self.stepOverForward()
  })
  this.buttonNavigator.event.register('jumpOut', this, function () {
    self.jumpOut()
  })
  this.buttonNavigator.event.register('jumpToException', this, function (exceptionIndex) {
    self.jumpTo(exceptionIndex)
  })
}

StepManager.prototype.resolveToReducedTrace = function (value, incr) {
  if (this.parent.callTree.reducedTrace.length) {
    var nextSource = utils.findLowerClosestBound(value, this.parent.callTree.reducedTrace)
    nextSource = nextSource < this.parent.callTree.reducedTrace.length - 1 ? nextSource + incr : nextSource
    return this.parent.callTree.reducedTrace[nextSource]
  }
  return value
}

StepManager.prototype.render = function () {
  return (
  yo`

        ${this.slider.render()}
        ${this.buttonNavigator.render()}

`
  )
}

StepManager.prototype.reset = function () {
  this.slider.setValue(0)
  this.currentStepIndex = 0
  this.buttonNavigator.reset()
}

StepManager.prototype.init = function () {
  this.slider.setValue(0)
  this.changeState(0)
}

StepManager.prototype.newTraceAvailable = function () {
  this.init()
}

StepManager.prototype.jumpTo = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.sliderMoved = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.changeState(step)
}

StepManager.prototype.stepIntoForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 1)
  } else {
    step += 1
  }
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepIntoBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, -1)
  } else {
    step -= 1
  }
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverForward(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 1)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverBack(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, -1)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpOut = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOut(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 0)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.changeState = function (step) {
  this.currentStepIndex = step
  this.buttonNavigator.stepChanged(step)
  this.event.trigger('stepChanged', [step])
}

module.exports = StepManager
<commit_msg>modify resolveToReducedTrace<commit_after>'use strict'
var ButtonNavigator = require('./ButtonNavigator')
var Slider = require('./Slider')
var EventManager = require('../lib/eventManager')
var yo = require('yo-yo')
var utils = require('../helpers/util.js')

function StepManager (_parent, _traceManager) {
  this.event = new EventManager()
  this.parent = _parent
  this.traceManager = _traceManager
  this.sourceMapByAddress = {}
  this.solidityMode = false

  var self = this
  this.parent.event.register('newTraceLoaded', this, function () {
    self.traceManager.getLength(function (error, length) {
      if (error) {
        console.log(error)
      } else {
        self.slider.init(length)
        self.init()
      }
    })
  })

  this.slider = new Slider(this.traceManager, (step) => {
    return this.solidityMode ? this.resolveToReducedTrace(step, 0) : step
  })
  this.slider.event.register('moved', this, function (step) {
    self.sliderMoved(step)
  })

  this.parent.callTree.event.register('callTreeReady', () => {
    this.solidityMode = true
    this.parent.vmDebugger.asmCode.event.register('hide', () => {
      this.solidityMode = this.parent.callTree.reducedTrace.length !== 0
    })
    this.parent.vmDebugger.asmCode.event.register('show', () => {
      this.solidityMode = false
    })
  })

  this.buttonNavigator = new ButtonNavigator(_parent, this.traceManager)
  this.buttonNavigator.event.register('stepIntoBack', this, function () {
    self.stepIntoBack()
  })
  this.buttonNavigator.event.register('stepIntoForward', this, function () {
    self.stepIntoForward()
  })
  this.buttonNavigator.event.register('stepOverBack', this, function () {
    self.stepOverBack()
  })
  this.buttonNavigator.event.register('stepOverForward', this, function () {
    self.stepOverForward()
  })
  this.buttonNavigator.event.register('jumpOut', this, function () {
    self.jumpOut()
  })
  this.buttonNavigator.event.register('jumpToException', this, function (exceptionIndex) {
    self.jumpTo(exceptionIndex)
  })
}

StepManager.prototype.resolveToReducedTrace = function (value, incr) {
  if (this.parent.callTree.reducedTrace.length) {
    var nextSource = utils.findClosestIndex(value, this.parent.callTree.reducedTrace)
    nextSource = nextSource + incr
    if (nextSource <= 0) {
      nextSource = 0
    } else if (nextSource > this.parent.callTree.reducedTrace.length) {
      nextSource = this.parent.callTree.reducedTrace.length - 1
    }
    return this.parent.callTree.reducedTrace[nextSource]
  }
  return value
}

StepManager.prototype.render = function () {
  return (
  yo`

        ${this.slider.render()}
        ${this.buttonNavigator.render()}

`
  )
}

StepManager.prototype.reset = function () {
  this.slider.setValue(0)
  this.currentStepIndex = 0
  this.buttonNavigator.reset()
}

StepManager.prototype.init = function () {
  this.slider.setValue(0)
  this.changeState(0)
}

StepManager.prototype.newTraceAvailable = function () {
  this.init()
}

StepManager.prototype.jumpTo = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.sliderMoved = function (step) {
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.changeState(step)
}

StepManager.prototype.stepIntoForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 1)
  } else {
    step += 1
  }
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepIntoBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.currentStepIndex
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, -1)
  } else {
    step -= 1
  }
  if (!this.traceManager.inRange(step)) {
    return
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverForward = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverForward(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 1)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.stepOverBack = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOverBack(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, -1)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.jumpOut = function () {
  if (!this.traceManager.isLoaded()) {
    return
  }
  var step = this.traceManager.findStepOut(this.currentStepIndex)
  if (this.solidityMode) {
    step = this.resolveToReducedTrace(step, 0)
  }
  this.slider.setValue(step)
  this.changeState(step)
}

StepManager.prototype.changeState = function (step) {
  this.currentStepIndex = step
  this.buttonNavigator.stepChanged(step)
  this.event.trigger('stepChanged', [step])
}

module.exports = StepManager
<|endoftext|>


<commit_before>goog.provide('recoil.ui.layout.Card');

/**
 * @constructor
 * @param {!recoil.ui.WidgetScope} scope
 * @implements {recoil.ui.Widget}
 */
recoil.ui.layout.Card = function(scope) {
    this.container_ = new goog.ui.Container();
    this.curControl_ = null;
    this.helper_ = new recoil.ui.ComponentWidgetHelper(scope, this.container_, this, this.updateState_);

};

/**
 * gets the component of the widget that should be placed in the
 * dom
 * @return {!goog.ui.Component}
 */
recoil.ui.layout.Card.prototype.getComponent = function() {
    return this.container_;
};

/**
 * @param {!recoil.frp.Behaviour<!recoil.ui.Widget>} subComponent
 */

recoil.ui.layout.Card.prototype.attach = function(subComponent) {
    this.helper_.attach(subComponent);
};

/**
 * @private
 * @param {recoil.ui.ComponentWidgetHelper} helper
 * @param {recoil.frp.Behaviour<recoil.ui.Widget>} widgetB
 */
recoil.ui.layout.Card.prototype.updateState_ = function(helper, widgetB) {
    if (helper.isGood()) {
        var newControl = widgetB.get().getComponent();
        if (newControl !== this.curControl_) {
            if (this.curControl_) {
                this.container_.removeChild(this.curControl_, true);
            }
            this.container_.addChild(newControl, true);
            this.curControl_ = newControl;
        }
    }
    else {
        if (this.curControl_) {
            this.container_.removeChild(this.curControl_, true);
            this.curControl_ = undefined;
        }
    }
};

/**
 * all widgets should not allow themselves to be flatterned
 *
 * @type {!Object}
 */

recoil.ui.layout.Card.prototype.flatten = recoil.frp.struct.NO_FLATTEN;
<commit_msg>card layout missing requires<commit_after>goog.provide('recoil.ui.layout.Card');

goog.require('recoil.frp.struct');
goog.require('recoil.ui.WidgetScope');

/**
 * @constructor
 * @param {!recoil.ui.WidgetScope} scope
 * @implements {recoil.ui.Widget}
 */
recoil.ui.layout.Card = function(scope) {
    this.container_ = new goog.ui.Container();
    this.curControl_ = null;
    this.helper_ = new recoil.ui.ComponentWidgetHelper(scope, this.container_, this, this.updateState_);

};

/**
 * gets the component of the widget that should be placed in the
 * dom
 * @return {!goog.ui.Component}
 */
recoil.ui.layout.Card.prototype.getComponent = function() {
    return this.container_;
};

/**
 * @param {!recoil.frp.Behaviour<!recoil.ui.Widget>} subComponent
 */

recoil.ui.layout.Card.prototype.attach = function(subComponent) {
    this.helper_.attach(subComponent);
};

/**
 * @private
 * @param {recoil.ui.ComponentWidgetHelper} helper
 * @param {recoil.frp.Behaviour<recoil.ui.Widget>} widgetB
 */
recoil.ui.layout.Card.prototype.updateState_ = function(helper, widgetB) {
    if (helper.isGood()) {
        var newControl = widgetB.get().getComponent();
        if (newControl !== this.curControl_) {
            if (this.curControl_) {
                this.container_.removeChild(this.curControl_, true);
            }
            this.container_.addChild(newControl, true);
            this.curControl_ = newControl;
        }
    }
    else {
        if (this.curControl_) {
            this.container_.removeChild(this.curControl_, true);
            this.curControl_ = undefined;
        }
    }
};

/**
 * all widgets should not allow themselves to be flatterned
 *
 * @type {!Object}
 */

recoil.ui.layout.Card.prototype.flatten = recoil.frp.struct.NO_FLATTEN;
<|endoftext|>


<commit_before>/* eslint "no-console": 0 */
// @flow

import config from "config";
import moment from "moment";
import nodemailer from "nodemailer";

import Event from "./models/Event";
import OrganizationEventGroupResponsibility from "./models/OrganizationEventGroupResponsibility";
import OrganizationEventPersonResponsibility from "./models/OrganizationEventPersonResponsibility";
import Group from "./models/Group";
import User from "./models/User";
import PasswordCode from "./models/PasswordCode";

type ContactEmailType = {
  name: string,
  email: string,
  text: string,
  organization: any,
};

export function sendContactEmail({
  name,
  email,
  text,
  organization,
}: ContactEmailType) {
  if (config && config.auth && config.auth.smtp && config.auth.smtp.host) {
    const transporter = nodemailer.createTransport(config.auth.smtp);
    const data = {
      from: `${name} <${email}>`,
      to: organization.email,
      subject: `Melding fra ${name} via nidarholm.no`,
      text: text.replace("\n", "\r\n"),
    };
    transporter.sendMail(data, (emailErr, emailInfo) => {
      console.info("EMAIL", emailErr, emailInfo, data);
      if (!emailErr) {
        const receipt = {
          from: organization.email,
          to: `${name} <${email}>`,
          subject: "Melding sendt",
          text:
            "Takk!\r\n\r\nMeldingen du sendte til styret via nidarholm.no har blitt mottatt.",
        };
        transporter.sendMail(receipt, (receiptErr, receiptInfo) => {
          console.info("RECEIPT:", receiptErr, receiptInfo, receipt);
        });
      }
    });
  } else {
    console.info("EMAIL", name, email, text);
  }
}

export function sendPasswordEmail(organization: any, user: any) {
  const code = new PasswordCode();
  code.user = user._id;
  return code.save().then((newCode) => {
    const message = `Hei ${user.name}\r\n\r\nDet kan se ut som du holder på å sette nytt passord. Hvis du ikke prøver på dette, ber vi deg se bort fra denne eposten. For å sette nytt passord, må du gå til lenka:\r\n${config.site.protocol}://${config.site.domain}/login/reset/${newCode._id}`;
    if (config.auth && config.auth.smtp && config.auth.smtp.host) {
      const transporter = nodemailer.createTransport(config.auth.smtp);
      const mailOptions = {
        from: config.auth.smtp.noreplyAddress,
        to: `${user.name} <${user.email}>`,
        subject: "Nytt passord",
        text: message,
      };
      return transporter.sendMail(mailOptions).then((info) => {
        console.info("Email info:", info);
        return organization;
      });
    }
    console.info("No email config, this was the intended message:\n", message);
    return organization;
  });
}

export function sendReminderEmails() {
  const senderName = "Infoansvarlig, Nidarholm";
  const senderEmail = "informasjon@nidarholm.no";
  const now = moment();
  OrganizationEventPersonResponsibility.find({
    reminderAtHour: now.hour(),
    reminderText: { $exists: true },
  }).then((responsibilities) => {
    responsibilities.forEach((responsibility) => {
      const eventMoment = moment()
        .add(responsibility.reminderDaysBefore, "days")
        .hour(responsibility.reminderAtHour)
        .startOf("hour");
      Event.find({
        "contributors.role": responsibility.id,
        start: {
          $lt: moment(eventMoment).endOf("day"),
          $gte: moment(eventMoment).startOf("day"),
        },
      }).then((events) => {
        events.forEach((event) => {
          const users = event.contributors
            .map((contributor) => {
              if (contributor.role !== responsibility.id) {
                return null;
              }
              return User.findOne({ _id: contributor.user }).then((user) => {
                const { name, email } = user;
                return {
                  name,
                  email,
                };
              });
            })
            .filter((user) => {
              return user;
            });
          Promise.all(users).then((resolvedUsers) => {
            const recipients = resolvedUsers.map((user) => {
              return {
                name: user.name,
                address: user.email,
              };
            });
            if (
              config &&
              config.auth &&
              config.auth.smtp &&
              config.auth.smtp.host
            ) {
              const transporter = nodemailer.createTransport(config.auth.smtp);
              const data = {
                from: `${senderName} <${senderEmail}>`,
                to: recipients,
                subject: responsibility.name,
                text: responsibility.reminderText.replace("\n", "\r\n"),
              };
              transporter.sendMail(data, (emailErr, emailInfo) => {
                console.info("SENT:", emailErr, emailInfo);
              });
            } else {
              console.info(
                "EMAIL",
                senderName,
                senderEmail,
                recipients,
                responsibility.reminderText,
              );
            }
          });
        });
      });
    });
  });
  OrganizationEventGroupResponsibility.find({
    reminderAtHour: now.hour(),
    reminderText: { $exists: true },
  }).then((responsibilities) => {
    responsibilities.forEach((responsibility) => {
      const eventMoment = moment()
        .add(responsibility.reminderDaysBefore, "days")
        .hour(responsibility.reminderAtHour)
        .startOf("hour");
      Event.find({
        "contributorGroups.role": responsibility.id,
        start: {
          $lt: moment(eventMoment).endOf("day"),
          $gte: moment(eventMoment).startOf("day"),
        },
      }).then((events) => {
        events.forEach((event) => {
          const groups = event.contributorGroups
            .map((contributorGroup) => {
              if (contributorGroup.role !== responsibility.id) {
                return null;
              }
              return Group.findOne({ _id: contributorGroup.group }).then(
                (group) => {
                  return {
                    name: group.name,
                    groupEmail: group.email,
                  };
                },
              );
            })
            .filter((group) => {
              return group;
            });
          Promise.all(groups).then((resolvedGroups) => {
            const recipients = resolvedGroups.map((group) => {
              return {
                name: group.name,
                address: group.groupEmail,
              };
            });
            if (
              config &&
              config.auth &&
              config.auth.smtp &&
              config.auth.smtp.host
            ) {
              const transporter = nodemailer.createTransport(config.auth.smtp);
              const data = {
                from: `${senderName} <${senderEmail}>`,
                to: recipients,
                subject: responsibility.name,
                text: responsibility.reminderText.replace("\n", "\r\n"),
              };
              transporter.sendMail(data, (emailErr, emailInfo) => {
                console.info("SENT:", data, emailErr, emailInfo);
              });
            } else {
              console.info(
                "EMAIL",
                senderName,
                senderEmail,
                recipients,
                responsibility.reminderText,
              );
            }
          });
        });
      });
    });
  });
}
<commit_msg>Get group email correctly<commit_after>/* eslint "no-console": 0 */
// @flow

import config from "config";
import moment from "moment";
import nodemailer from "nodemailer";

import Event from "./models/Event";
import OrganizationEventGroupResponsibility from "./models/OrganizationEventGroupResponsibility";
import OrganizationEventPersonResponsibility from "./models/OrganizationEventPersonResponsibility";
import Group from "./models/Group";
import User from "./models/User";
import PasswordCode from "./models/PasswordCode";

type ContactEmailType = {
  name: string,
  email: string,
  text: string,
  organization: any,
};

export function sendContactEmail({
  name,
  email,
  text,
  organization,
}: ContactEmailType) {
  if (config && config.auth && config.auth.smtp && config.auth.smtp.host) {
    const transporter = nodemailer.createTransport(config.auth.smtp);
    const data = {
      from: `${name} <${email}>`,
      to: organization.email,
      subject: `Melding fra ${name} via nidarholm.no`,
      text: text.replace("\n", "\r\n"),
    };
    transporter.sendMail(data, (emailErr, emailInfo) => {
      console.info("EMAIL", emailErr, emailInfo, data);
      if (!emailErr) {
        const receipt = {
          from: organization.email,
          to: `${name} <${email}>`,
          subject: "Melding sendt",
          text:
            "Takk!\r\n\r\nMeldingen du sendte til styret via nidarholm.no har blitt mottatt.",
        };
        transporter.sendMail(receipt, (receiptErr, receiptInfo) => {
          console.info("RECEIPT:", receiptErr, receiptInfo, receipt);
        });
      }
    });
  } else {
    console.info("EMAIL", name, email, text);
  }
}

export function sendPasswordEmail(organization: any, user: any) {
  const code = new PasswordCode();
  code.user = user._id;
  return code.save().then((newCode) => {
    const message = `Hei ${user.name}\r\n\r\nDet kan se ut som du holder på å sette nytt passord. Hvis du ikke prøver på dette, ber vi deg se bort fra denne eposten. For å sette nytt passord, må du gå til lenka:\r\n${config.site.protocol}://${config.site.domain}/login/reset/${newCode._id}`;
    if (config.auth && config.auth.smtp && config.auth.smtp.host) {
      const transporter = nodemailer.createTransport(config.auth.smtp);
      const mailOptions = {
        from: config.auth.smtp.noreplyAddress,
        to: `${user.name} <${user.email}>`,
        subject: "Nytt passord",
        text: message,
      };
      return transporter.sendMail(mailOptions).then((info) => {
        console.info("Email info:", info);
        return organization;
      });
    }
    console.info("No email config, this was the intended message:\n", message);
    return organization;
  });
}

export function sendReminderEmails() {
  const senderName = "Infoansvarlig, Nidarholm";
  const senderEmail = "informasjon@nidarholm.no";
  const now = moment();
  OrganizationEventPersonResponsibility.find({
    reminderAtHour: now.hour(),
    reminderText: { $exists: true },
  }).then((responsibilities) => {
    responsibilities.forEach((responsibility) => {
      const eventMoment = moment()
        .add(responsibility.reminderDaysBefore, "days")
        .hour(responsibility.reminderAtHour)
        .startOf("hour");
      Event.find({
        "contributors.role": responsibility.id,
        start: {
          $lt: moment(eventMoment).endOf("day"),
          $gte: moment(eventMoment).startOf("day"),
        },
      }).then((events) => {
        events.forEach((event) => {
          const users = event.contributors
            .map((contributor) => {
              if (contributor.role !== responsibility.id) {
                return null;
              }
              return User.findOne({ _id: contributor.user }).then((user) => {
                const { name, email } = user;
                return {
                  name,
                  email,
                };
              });
            })
            .filter((user) => {
              return user;
            });
          Promise.all(users).then((resolvedUsers) => {
            const recipients = resolvedUsers.map((user) => {
              return {
                name: user.name,
                address: user.email,
              };
            });
            if (
              config &&
              config.auth &&
              config.auth.smtp &&
              config.auth.smtp.host
            ) {
              const transporter = nodemailer.createTransport(config.auth.smtp);
              const data = {
                from: `${senderName} <${senderEmail}>`,
                to: recipients,
                subject: responsibility.name,
                text: responsibility.reminderText.replace("\n", "\r\n"),
              };
              transporter.sendMail(data, (emailErr, emailInfo) => {
                console.info("SENT:", emailErr, emailInfo);
              });
            } else {
              console.info(
                "EMAIL",
                senderName,
                senderEmail,
                recipients,
                responsibility.reminderText,
              );
            }
          });
        });
      });
    });
  });
  OrganizationEventGroupResponsibility.find({
    reminderAtHour: now.hour(),
    reminderText: { $exists: true },
  }).then((responsibilities) => {
    responsibilities.forEach((responsibility) => {
      const eventMoment = moment()
        .add(responsibility.reminderDaysBefore, "days")
        .hour(responsibility.reminderAtHour)
        .startOf("hour");
      Event.find({
        "contributorGroups.role": responsibility.id,
        start: {
          $lt: moment(eventMoment).endOf("day"),
          $gte: moment(eventMoment).startOf("day"),
        },
      }).then((events) => {
        events.forEach((event) => {
          const groups = event.contributorGroups
            .map((contributorGroup) => {
              if (contributorGroup.role !== responsibility.id) {
                return null;
              }
              return Group.findOne({ _id: contributorGroup.group }).then(
                (group) => {
                  return {
                    name: group.name,
                    email: group.group_email,
                  };
                },
              );
            })
            .filter((group) => {
              return group;
            });
          Promise.all(groups).then((resolvedGroups) => {
            const recipients = resolvedGroups.map((group) => {
              return {
                name: group.name,
                address: group.email,
              };
            });
            if (
              config &&
              config.auth &&
              config.auth.smtp &&
              config.auth.smtp.host
            ) {
              const transporter = nodemailer.createTransport(config.auth.smtp);
              const data = {
                from: `${senderName} <${senderEmail}>`,
                to: recipients,
                subject: responsibility.name,
                text: responsibility.reminderText.replace("\n", "\r\n"),
              };
              transporter.sendMail(data, (emailErr, emailInfo) => {
                console.info("SENT:", data, emailErr, emailInfo);
              });
            } else {
              console.info(
                "EMAIL",
                senderName,
                senderEmail,
                recipients,
                responsibility.reminderText,
              );
            }
          });
        });
      });
    });
  });
}
<|endoftext|>


<commit_before>/**
 * Display account settings
 */
ReactiveTemplates.request('accounts.index');
ReactiveTemplates.request('accounts.update.roles');

/**
 * Register the route
 */
Router.route('/admin/accounts', function () {
  this.layout(ReactiveTemplates.get('layout'));
  this.render(ReactiveTemplates.get('accounts.index'));
}, { name: 'accounts.index' });
orion.accounts.addProtectedRoute('accounts.index');

/**
 * Register the link
 */
orion.addLink({
  section: 'bottom',
  title: 'Accounts',
  routeName: 'accounts.index',
  activeRouteRegex: 'accounts',
});

/**
 * Edit the roles of the user
 */
Router.route('/admin/accounts/:_id/update/roles', function () {
  this.layout(ReactiveTemplates.get('layout'));
  this.render(ReactiveTemplates.get('accounts.update.roles'));
}, { name: 'accounts.update.roles' });
orion.accounts.addProtectedRoute('accounts.update.roles');

orion.accounts.addAdminUsersButton({
  title: 'Edit Roles',
  route: 'accounts.update.roles',
  shouldShow: function() {
    return Roles.userHasPermission(Meteor.userId(), 'accounts.update.roles');
  }
});

<commit_msg>Added permissions to view accounts-tab<commit_after>/**
 * Display account settings
 */
ReactiveTemplates.request('accounts.index');
ReactiveTemplates.request('accounts.update.roles');

/**
 * Register the route
 */
Router.route('/admin/accounts', function () {
  this.layout(ReactiveTemplates.get('layout'));
  this.render(ReactiveTemplates.get('accounts.index'));
}, { name: 'accounts.index' });
orion.accounts.addProtectedRoute('accounts.index');

/**
 * Register the link
 */
orion.addLink({
  section: 'bottom',
  title: 'Accounts',
  routeName: 'accounts.index',
  activeRouteRegex: 'accounts',
  permission: 'accounts.index'
});

/**
 * Edit the roles of the user
 */
Router.route('/admin/accounts/:_id/update/roles', function () {
  this.layout(ReactiveTemplates.get('layout'));
  this.render(ReactiveTemplates.get('accounts.update.roles'));
}, { name: 'accounts.update.roles' });
orion.accounts.addProtectedRoute('accounts.update.roles');

orion.accounts.addAdminUsersButton({
  title: 'Edit Roles',
  route: 'accounts.update.roles',
  shouldShow: function() {
    return Roles.userHasPermission(Meteor.userId(), 'accounts.update.roles');
  }
});

<|endoftext|>


<commit_before>/**
 * This file contains some re-usable parts for websocket audio communication.
 *
 * @module api/utils/audio-over-socket
 */

import autobahn from 'autobahn';
import {
  getWebsocketConnection,
  makeWebsocketCall,
} from '../communication/websocket';
import broadcaster from '../broadcaster';
import { dataToBase64, asyncBlobToArrayBuffer } from './index';

/**
 * This class allows us to stream audio from the recorder to the backend.
 * @private
 */
class StreamRecorderAudio {
  /**
   * @param {MediaRecorder} recorder - Recorder to use to capture data from.
   * @param {string} rpcName - Name of the registered RPC function.
   */
  constructor(recorder, rpcName, websocketConnection) {
    /**
     * MediaRecorder to process the stream from.
     * @type {MediaRecorder}
     */
    this.recorder = recorder;

    /**
     * Name of the RPC registered.
     * This name will be prepended with 'nl.itslanguage' for better consistency.
     * @type {string}
     */
    this.rpcName = `nl.itslanguage.${rpcName}`;

    /**
     * Store a reference to the websocket connection.
     * @type {autobahn.Connection}
     */
    this.websocketConnection = websocketConnection;

    /**
     * The autobahn.Registration object. This is returned when you register
     * a function through Session.register.
     * @type {null|autobahn.Registration}
     */
    this.registration = null;

    this.sendAudioChunks = this.sendAudioChunks.bind(this);
    this.register = this.register.bind(this);
    this.unregister = this.unregister.bind(this);
  }

  /**
   * This is the function that will be registered to the autobahn realm that the backend will call
   * to receive audio on.
   *
   * Once called, it will prepare the recorder to allow data transmission trough the progressive
   * results meganism.
   *
   * @see https://github.com/crossbario/autobahn-js/blob/master/doc/reference.md#register
   * @see https://github.com/crossbario/autobahn-js/blob/master/doc/reference.md#progressive-results
   *
   * @private
   * @param {Array} args - Argument list.
   * @param {Object} kwargs - Key-valued argument list.
   * @param {Object} details - Details, just as the progress function.
   * @returns {Promise} - A promise that can be resolved to end the asynchronous behaviour of this
   * registered RCP.
   */
  sendAudioChunks(args, kwargs, details) {
    const defer = new autobahn.when.defer(); // eslint-disable-line new-cap
    let lastChunk = false;

    this.recorder.addEventListener('dataavailable', ({ data }) => {
      asyncBlobToArrayBuffer(data).then(audioData => {
        if (details.progress) {
          const dataToSend = Array.from(new Uint8Array(audioData));
          details.progress([dataToSend]);

          // If the last one ends, closing time!
          if (lastChunk) {
            defer.resolve();
            this.unregister();
          }
        } else {
          defer.reject('no progress function registered');
        }
      });
    });

    this.recorder.addEventListener('stop', () => {
      // When stopped, the dataavailableevent will be triggered
      // one final time, so make sure it will cleanup afterwards
      lastChunk = true;
    });

    // Notify listeners that we are ready to process audio;
    this.recorder.dispatchEvent(new Event('recorderready'));

    return defer.promise;
  }

  /**
   * register the RPC to the autobahn realm.
   * @returns {Promise}
   */
  register() {
    return new Promise((resolve, reject) => {
      this.websocketConnection.session
        .register(this.rpcName, this.sendAudioChunks)
        .then(registration => {
          this.registration = registration;
          resolve(registration);
        })
        .catch(reject);
    });
  }

  /**
   * unregister the RPC from the autobahn realm.
   */
  unregister() {
    return new Promise((resolve, reject) => {
      this.websocketConnection.session
        .unregister(this.registration)
        .then(() => {
          this.registration = null;
          resolve();
        })
        .catch(reject);
    });
  }
}

/**
 * Register a RPC call to the current websocket connection. The backend will call this registered
 * function once, an then we can send progressive results (the details.progress call) to send audio
 * chunks to the backend. We will send those chunks as soon as we got audio from the recorder.
 *
 * When the recording ends we un-register the rpc.
 *
 * @param {MediaRecorder} recorder - Audio recorder instance.
 * @param {string} rpcName - Name of the RPC to register. This name will be prepended with
 * nl.itslanguage for better consistency.
 * @fires broadcaster#websocketserverreadyforaudio
 * @returns {Promise} - It returns a promise with the service registration as result.
 */
export function registerStreamForRecorder(recorder, rpcName) {
  // Start registering a RPC call. As a result, this function will return a promise with the
  // registration of the RPC as result.
  return new Promise((resolve, reject) => {
    getWebsocketConnection().then(websocketConnection => {
      const streamingSession = new StreamRecorderAudio(
        recorder,
        rpcName,
        websocketConnection,
      );
      streamingSession
        .register()
        .then(registration => {
          /**
           * Notify that we are ready to process audio.
           * @event broadcaster#websocketserverreadyforaudio
           * @deprecated will be removed in a future version
           */
          broadcaster.emit('websocketserverreadyforaudio');
          recorder.dispatchEvent(new Event('websocketserverreadyforaudio'));
          resolve(registration);
        })
        .catch(reject);
    });
  });
}

/**
 * Encode the audio as base64 and send it to the websocket server.
 *
 * @param {string} id - The reserved ID for the audio.
 * @param {MediaRecorder} recorder - The recorder to use to get the recording.
 * @param {string} rpc - The RPC to use to store the data.
 *
 * @returns {Promise<*>} - The response of the given RPC.
 */
export function encodeAndSendAudioOnDataAvailable(id, recorder, rpc) {
  return new Promise((resolve, reject) => {
    let lastChunk = false;

    // When data is received from the recorder, it will be in Blob format.
    // When we read the data from the Blob element, base64 it and send it to
    // the websocket server and continue with the chain.
    recorder.addEventListener('dataavailable', ({ data }) => {
      asyncBlobToArrayBuffer(data).then(audioData => {
        const encoded = dataToBase64(audioData);
        // Send the audio
        makeWebsocketCall(rpc, { args: [id, encoded, 'base64'] })
          .then(result => {
            if (lastChunk) {
              resolve(result);
            }
          })
          .catch(error => {
            reject(error);
          });
      });
    });

    recorder.addEventListener('stop', () => {
      // When stopped, the dataavailableevent will be triggered
      // one final time, so make sure it will cleanup afterwards
      lastChunk = true;
    });

    // Notify listeners that we are ready to process audio;
    recorder.dispatchEvent(new Event('recorderready'));
  });
}

/**
 * Send the recorder settings to the websocket server to initialize it.
 *
 * The reserved ID (passed in the parameters) is returned once the promise is resolved.
 *
 * @param {string} id - The reserved ID for the audio.
 * @param {MediaRecorder} recorder - The recorder which has been set up to record.
 * @param {string} rpc - The RPC to use to initialize the websocket server.
 *
 * @emits {websocketserverreadyforaudio} - When the websocket server has been prepared for and is
 * ready to receive the audio.
 *
 * @returns {Promise} - The promise which resolves when the websocket server is ready for the audio.
 */
export function prepareServerForAudio(id, recorder, rpc) {
  const { audioFormat, audioParameters } = recorder.getAudioSpecs();
  return makeWebsocketCall(rpc, {
    args: [id, audioFormat],
    kwargs: audioParameters,
  }).then(() => {
    // We've prepped the websocket server, now it can receive audio. Broadcast
    // that it is allowed to record.
    // This call is deprecated and will be removed in a future version, the
    // event on the recorder will stay.
    broadcaster.emit('websocketserverreadyforaudio');
    recorder.dispatchEvent(new Event('websocketserverreadyforaudio'));
    return id;
  });
}
<commit_msg>refactor: remove event listeners before adding<commit_after>/**
 * This file contains some re-usable parts for websocket audio communication.
 *
 * @module api/utils/audio-over-socket
 */

import autobahn from 'autobahn';
import {
  getWebsocketConnection,
  makeWebsocketCall,
} from '../communication/websocket';
import broadcaster from '../broadcaster';
import { dataToBase64, asyncBlobToArrayBuffer } from './index';

/**
 * This class allows us to stream audio from the recorder to the backend.
 * @private
 */
class StreamRecorderAudio {
  /**
   * @param {MediaRecorder} recorder - Recorder to use to capture data from.
   * @param {string} rpcName - Name of the registered RPC function.
   */
  constructor(recorder, rpcName, websocketConnection) {
    /**
     * MediaRecorder to process the stream from.
     * @type {MediaRecorder}
     */
    this.recorder = recorder;

    /**
     * Name of the RPC registered.
     * This name will be prepended with 'nl.itslanguage' for better consistency.
     * @type {string}
     */
    this.rpcName = `nl.itslanguage.${rpcName}`;

    /**
     * Store a reference to the websocket connection.
     * @type {autobahn.Connection}
     */
    this.websocketConnection = websocketConnection;

    /**
     * The autobahn.Registration object. This is returned when you register
     * a function through Session.register.
     * @type {null|autobahn.Registration}
     */
    this.registration = null;

    this.sendAudioChunks = this.sendAudioChunks.bind(this);
    this.register = this.register.bind(this);
    this.unregister = this.unregister.bind(this);
  }

  /**
   * This is the function that will be registered to the autobahn realm that the backend will call
   * to receive audio on.
   *
   * Once called, it will prepare the recorder to allow data transmission trough the progressive
   * results meganism.
   *
   * @see https://github.com/crossbario/autobahn-js/blob/master/doc/reference.md#register
   * @see https://github.com/crossbario/autobahn-js/blob/master/doc/reference.md#progressive-results
   *
   * @private
   * @param {Array} args - Argument list.
   * @param {Object} kwargs - Key-valued argument list.
   * @param {Object} details - Details, just as the progress function.
   * @returns {Promise} - A promise that can be resolved to end the asynchronous behaviour of this
   * registered RCP.
   */
  sendAudioChunks(args, kwargs, details) {
    const defer = new autobahn.when.defer(); // eslint-disable-line new-cap
    let lastChunk = false;

    const processData = ({ data }) => {
      asyncBlobToArrayBuffer(data).then(audioData => {
        if (details.progress) {
          const dataToSend = Array.from(new Uint8Array(audioData));
          details.progress([dataToSend]);

          // If the last one ends, closing time!
          if (lastChunk) {
            defer.resolve();
            this.unregister();
          }
        } else {
          defer.reject('no progress function registered');
        }
      });
    };

    const recorderStopped = () => {
      // When stopped, the dataavailableevent will be triggered
      // one final time, so make sure it will cleanup afterwards
      lastChunk = true;
    };

    // Before adding the events, let's make sure they have not been added
    // on a previous session;
    this.recorder.removeEventListener('dataavailable', processData);
    this.recorder.removeEventListener('stop', recorderStopped);

    // Now add the event listeners!
    this.recorder.addEventListener('dataavailable', processData);
    this.recorder.addEventListener('stop', recorderStopped);

    // Notify listeners that we are ready to process audio;
    this.recorder.dispatchEvent(new Event('recorderready'));

    return defer.promise;
  }

  /**
   * register the RPC to the autobahn realm.
   * @returns {Promise}
   */
  register() {
    return new Promise((resolve, reject) => {
      this.websocketConnection.session
        .register(this.rpcName, this.sendAudioChunks)
        .then(registration => {
          this.registration = registration;
          resolve(registration);
        })
        .catch(reject);
    });
  }

  /**
   * unregister the RPC from the autobahn realm.
   */
  unregister() {
    return new Promise((resolve, reject) => {
      this.websocketConnection.session
        .unregister(this.registration)
        .then(() => {
          this.registration = null;
          resolve();
        })
        .catch(reject);
    });
  }
}

/**
 * Register a RPC call to the current websocket connection. The backend will call this registered
 * function once, an then we can send progressive results (the details.progress call) to send audio
 * chunks to the backend. We will send those chunks as soon as we got audio from the recorder.
 *
 * When the recording ends we un-register the rpc.
 *
 * @param {MediaRecorder} recorder - Audio recorder instance.
 * @param {string} rpcName - Name of the RPC to register. This name will be prepended with
 * nl.itslanguage for better consistency.
 * @fires broadcaster#websocketserverreadyforaudio
 * @returns {Promise} - It returns a promise with the service registration as result.
 */
export function registerStreamForRecorder(recorder, rpcName) {
  // Start registering a RPC call. As a result, this function will return a promise with the
  // registration of the RPC as result.
  return new Promise((resolve, reject) => {
    getWebsocketConnection().then(websocketConnection => {
      const streamingSession = new StreamRecorderAudio(
        recorder,
        rpcName,
        websocketConnection,
      );
      streamingSession
        .register()
        .then(registration => {
          /**
           * Notify that we are ready to process audio.
           * @event broadcaster#websocketserverreadyforaudio
           * @deprecated will be removed in a future version
           */
          broadcaster.emit('websocketserverreadyforaudio');
          recorder.dispatchEvent(new Event('websocketserverreadyforaudio'));
          resolve(registration);
        })
        .catch(reject);
    });
  });
}

/**
 * Encode the audio as base64 and send it to the websocket server.
 *
 * @param {string} id - The reserved ID for the audio.
 * @param {MediaRecorder} recorder - The recorder to use to get the recording.
 * @param {string} rpc - The RPC to use to store the data.
 *
 * @returns {Promise<*>} - The response of the given RPC.
 */
export function encodeAndSendAudioOnDataAvailable(id, recorder, rpc) {
  return new Promise((resolve, reject) => {
    let lastChunk = false;

    // When data is received from the recorder, it will be in Blob format.
    // When we read the data from the Blob element, base64 it and send it to
    // the websocket server and continue with the chain.
    const processData = ({ data }) => {
      asyncBlobToArrayBuffer(data).then(audioData => {
        const encoded = dataToBase64(audioData);
        // Send the audio
        makeWebsocketCall(rpc, { args: [id, encoded, 'base64'] })
          .then(result => {
            if (lastChunk) {
              resolve(result);
            }
          })
          .catch(error => {
            reject(error);
          });
      });
    };

    const recorderStopped = () => {
      // When stopped, the dataavailable event will be triggered
      // one final time, so make sure it will cleanup afterwards
      lastChunk = true;
    };

    // Before adding the events, let's make sure they have not been added
    // on a previous session;
    recorder.removeEventListener('dataavailable', processData);
    recorder.removeEventListener('stop', recorderStopped);

    // Now add the event listeners!
    recorder.addEventListener('dataavailable', processData);
    recorder.addEventListener('stop', recorderStopped);

    // Notify listeners that we are ready to process audio;
    recorder.dispatchEvent(new Event('recorderready'));
  });
}

/**
 * Send the recorder settings to the websocket server to initialize it.
 *
 * The reserved ID (passed in the parameters) is returned once the promise is resolved.
 *
 * @param {string} id - The reserved ID for the audio.
 * @param {MediaRecorder} recorder - The recorder which has been set up to record.
 * @param {string} rpc - The RPC to use to initialize the websocket server.
 *
 * @emits {websocketserverreadyforaudio} - When the websocket server has been prepared for and is
 * ready to receive the audio.
 *
 * @returns {Promise} - The promise which resolves when the websocket server is ready for the audio.
 */
export function prepareServerForAudio(id, recorder, rpc) {
  const { audioFormat, audioParameters } = recorder.getAudioSpecs();
  return makeWebsocketCall(rpc, {
    args: [id, audioFormat],
    kwargs: audioParameters,
  }).then(() => {
    // We've prepped the websocket server, now it can receive audio. Broadcast
    // that it is allowed to record.
    // This call is deprecated and will be removed in a future version, the
    // event on the recorder will stay.
    broadcaster.emit('websocketserverreadyforaudio');
    recorder.dispatchEvent(new Event('websocketserverreadyforaudio'));
    return id;
  });
}
<|endoftext|>


<commit_before>/**
 * Game
 *
 * @param {Room} room
 */
function Game(room)
{
    BaseGame.call(this, room);

    this.world       = new World(this.size);
    this.deaths      = new Collection([], 'id');
    this.controller  = new GameController(this);
    this.bonusStack  = new GameBonusStack(this);
    this.roundWinner = null;
    this.gameWinner  = null;

    this.addPoint = this.addPoint.bind(this);
    this.onDie    = this.onDie.bind(this);

    var avatar, i;

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        avatar.clear();
        avatar.on('point', this.addPoint);
        avatar.on('die', this.onDie);
    }
}

Game.prototype = Object.create(BaseGame.prototype);
Game.prototype.constructor = Game;

/**
 * Update
 *
 * @param {Number} step
 */
Game.prototype.update = function(step)
{
    BaseGame.prototype.update.call(this, step);

    var avatar, border, i;

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];

        if (avatar.alive) {
            avatar.update(step);

            border = this.world.getBoundIntersect(avatar.body, this.borderless ? 0 : avatar.radius);

            if (border) {
                if (this.borderless) {
                    if (this.testBorder(avatar.head, avatar.velocities, border)) {
                        avatar.setPosition(this.world.getOposite(border));
                    }
                } else {
                    avatar.die(null);
                }
            } else if (!avatar.invincible) {
                var killer = this.world.getBody(avatar.body);

                if (null !== killer) {
                    avatar.die(killer);
                }
            }

            if (avatar.alive) {
                avatar.printManager.test();
                this.bonusManager.testCatch(avatar);
            }
        }
    }
};

/**
 * Remove a avatar from the game
 *
 * @param {Avatar} avatar
 */
Game.prototype.removeAvatar = function(avatar)
{
    BaseGame.prototype.removeAvatar.call(this, avatar);
    this.emit('player:leave', {player: avatar.player});
    this.checkRoundEnd();
};

/**
 * Add point
 *
 * @param {Object} data
 */
Game.prototype.addPoint = function(data)
{
    if (this.started && this.world.active) {
        this.world.addBody(new AvatarBody(data.point, data.avatar));
    }
};

/**
 * Is done
 *
 * @return {Boolean}
 */
Game.prototype.isWon = function()
{
    var present = this.getPresentAvatars().count();

    if (present <= 0) { return true; }
    if (this.avatars.count() > 1 && present <= 1) { return true; }

    var maxScore = this.maxScore,
        players = this.avatars.filter(function () { return this.present && this.score >= maxScore; });

    if (players.count() === 0) {
        return null;
    }

    if (players.count() === 1) {
        return players.getFirst();
    }

    this.sortAvatars(players);

    return players.items[0].score === players.items[1].score ? null : players.getFirst();
};

/**
 * On die
 *
 * @param {Object} data
 */
Game.prototype.onDie = function(data)
{
    this.deaths.add(data.avatar);
    data.avatar.addScore(this.deaths.count() - 1);
    this.checkRoundEnd();
};

/**
 * Check if the round should end
 */
Game.prototype.checkRoundEnd = function()
{
    if (this.inRound && this.getAliveAvatars().count() <= 1) {
        this.endRound();
    }
};

/**
 * Resolve scores
 */
Game.prototype.resolveScores = function()
{
    var winner;

    if (this.avatars.count() === 1) {
        winner = this.avatars.getFirst();
    } else {
        winner = this.avatars.match(function () { return this.alive; });
    }

    if (winner) {
        this.roundWinner = winner;
        winner.addScore(Math.max(this.avatars.count() - 1, 1));
        this.emit('round:winner', {game: this, winner: winner});
    }

    for (var i = this.avatars.items.length - 1; i >= 0; i--) {
        this.avatars.items[i].resolveScore();
    }
};

/**
 * Test border
 *
 * @param {Array} position
 * @param {Array} velocities
 * @param {Array} border
 *
 * @return {Boolean}
 */
Game.prototype.testBorder = function(position, velocities, border)
{
    var axis = border[2];

    return (position[axis] > border[axis]) === (velocities[axis] < 0);
};

/**
 * Clear trails
 */
Game.prototype.clearTrails = function()
{
    this.world.clear();
    this.world.activate();
    this.emit('clear', {game: this});
};

/**
 * Update size
 */
Game.prototype.setSize = function()
{
    BaseGame.prototype.setSize.call(this);

    this.world.clear();
    this.world = new World(this.size);

    this.bonusManager.setSize();
};

/**
 * Check end of round
 */
Game.prototype.onRoundEnd = function()
{
    this.emit('round:end', {game: this});
    BaseGame.prototype.onRoundEnd.call(this);
    this.resolveScores();
};

/**
 * New round
 */
Game.prototype.onRoundNew = function()
{
    this.emit('round:new', {game: this});
    BaseGame.prototype.onRoundNew.call(this);

    var avatar, i;

    this.roundWinner = null;
    this.world.clear();
    this.deaths.clear();
    this.bonusStack.clear();

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        if (avatar.present) {
            avatar.setPosition(this.world.getRandomPosition(avatar.radius, this.spawnMargin));
            avatar.setAngle(this.world.getRandomDirection(avatar.head, this.spawnAngleMargin));
        } else {
            this.deaths.add(avatar);
        }
    }
};

/**
 * On start
 */
Game.prototype.onStart = function()
{
    this.emit('game:start', {game: this});

    for (var avatar, i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        setTimeout(avatar.printManager.start, 3000);
    }

    this.world.activate();

    BaseGame.prototype.onStart.call(this);
};

/**
 * On stop
 */
Game.prototype.onStop = function()
{
    this.emit('game:stop', {game: this});

    BaseGame.prototype.onStop.call(this);

    var won = this.isWon();

    if (won) {
        if (won instanceof Avatar) {
            this.gameWinner = won;
        }
        this.end();
    } else {
        this.newRound();
    }
};

/**
 * Set borderless
 *
 * @param {Boolean} borderless
 */
Game.prototype.setBorderless = function(borderless)
{
    BaseGame.prototype.setBorderless.call(this, borderless);
    this.emit('borderless', this.borderless);
};

/**
 * FIN DU GAME
 */
Game.prototype.end = function()
{
    BaseGame.prototype.end.call(this);

    this.world.clear();

    delete this.world;
    delete this.avatars;
    delete this.deaths;
    delete this.bonusManager;
    delete this.controller;
};
<commit_msg>Small optimisation<commit_after>/**
 * Game
 *
 * @param {Room} room
 */
function Game(room)
{
    BaseGame.call(this, room);

    this.world       = new World(this.size);
    this.deaths      = new Collection([], 'id');
    this.controller  = new GameController(this);
    this.bonusStack  = new GameBonusStack(this);
    this.roundWinner = null;
    this.gameWinner  = null;

    this.addPoint = this.addPoint.bind(this);
    this.onDie    = this.onDie.bind(this);

    var avatar, i;

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        avatar.clear();
        avatar.on('point', this.addPoint);
        avatar.on('die', this.onDie);
    }
}

Game.prototype = Object.create(BaseGame.prototype);
Game.prototype.constructor = Game;

/**
 * Update
 *
 * @param {Number} step
 */
Game.prototype.update = function(step)
{
    BaseGame.prototype.update.call(this, step);

    var avatar, border, i;

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];

        if (avatar.alive) {
            avatar.update(step);

            border = this.world.getBoundIntersect(avatar.body, this.borderless ? 0 : avatar.radius);

            if (border) {
                if (this.borderless) {
                    if (this.testBorder(avatar.head, avatar.velocities, border)) {
                        avatar.setPosition(this.world.getOposite(border));
                    }
                } else {
                    avatar.die(null);
                }
            } else if (!avatar.invincible) {
                var killer = this.world.getBody(avatar.body);

                if (null !== killer) {
                    avatar.die(killer);
                }
            }

            if (avatar.alive) {
                avatar.printManager.test();
                this.bonusManager.testCatch(avatar);
            }
        }
    }
};

/**
 * Remove a avatar from the game
 *
 * @param {Avatar} avatar
 */
Game.prototype.removeAvatar = function(avatar)
{
    BaseGame.prototype.removeAvatar.call(this, avatar);
    this.emit('player:leave', {player: avatar.player});
    this.checkRoundEnd();
};

/**
 * Add point
 *
 * @param {Object} data
 */
Game.prototype.addPoint = function(data)
{
    if (this.started && this.world.active) {
        this.world.addBody(new AvatarBody(data.point, data.avatar));
    }
};

/**
 * Is done
 *
 * @return {Boolean}
 */
Game.prototype.isWon = function()
{
    var present = this.getPresentAvatars().count();

    if (present <= 0) { return true; }
    if (this.avatars.count() > 1 && present <= 1) { return true; }

    var maxScore = this.maxScore,
        players = this.avatars.filter(function () { return this.present && this.score >= maxScore; });

    if (players.count() === 0) {
        return null;
    }

    if (players.count() === 1) {
        return players.getFirst();
    }

    this.sortAvatars(players);

    return players.items[0].score === players.items[1].score ? null : players.getFirst();
};

/**
 * On die
 *
 * @param {Object} data
 */
Game.prototype.onDie = function(data)
{
    this.deaths.add(data.avatar);
    data.avatar.addScore(this.deaths.count() - 1);
    this.checkRoundEnd();
};

/**
 * Check if the round should end
 */
Game.prototype.checkRoundEnd = function()
{
    if (this.inRound && this.getAliveAvatars().count() <= 1) {
        this.endRound();
    }
};

/**
 * Resolve scores
 */
Game.prototype.resolveScores = function()
{
    var winner;

    if (this.avatars.count() === 1) {
        winner = this.avatars.getFirst();
    } else {
        winner = this.avatars.match(function () { return this.alive; });
    }

    if (winner) {
        this.roundWinner = winner;
        winner.addScore(Math.max(this.avatars.count() - 1, 1));
        this.emit('round:winner', {game: this, winner: winner});
    }

    for (var i = this.avatars.items.length - 1; i >= 0; i--) {
        this.avatars.items[i].resolveScore();
    }
};

/**
 * Test border
 *
 * @param {Array} position
 * @param {Array} velocities
 * @param {Array} border
 *
 * @return {Boolean}
 */
Game.prototype.testBorder = function(position, velocities, border)
{
    var axis = border[2];

    return (position[axis] > border[axis]) === (velocities[axis] < 0);
};

/**
 * Clear trails
 */
Game.prototype.clearTrails = function()
{
    this.world.clear();
    this.world.activate();
    this.emit('clear', {game: this});
};

/**
 * Update size
 */
Game.prototype.setSize = function()
{
    BaseGame.prototype.setSize.call(this);

    this.world.clear();
    this.world = new World(this.size);

    this.bonusManager.setSize();
};

/**
 * Check end of round
 */
Game.prototype.onRoundEnd = function()
{
    this.emit('round:end', {game: this});
    BaseGame.prototype.onRoundEnd.call(this);
    this.resolveScores();
};

/**
 * New round
 */
Game.prototype.onRoundNew = function()
{
    this.emit('round:new', {game: this});
    BaseGame.prototype.onRoundNew.call(this);

    var avatar, i;

    this.roundWinner = null;
    this.world.clear();
    this.deaths.clear();
    this.bonusStack.clear();

    for (i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        if (avatar.present) {
            avatar.setPosition(this.world.getRandomPosition(avatar.radius, this.spawnMargin));
            avatar.setAngle(this.world.getRandomDirection(avatar.head, this.spawnAngleMargin));
        } else {
            this.deaths.add(avatar);
        }
    }
};

/**
 * On start
 */
Game.prototype.onStart = function()
{
    this.emit('game:start', {game: this});

    for (var avatar, i = this.avatars.items.length - 1; i >= 0; i--) {
        avatar = this.avatars.items[i];
        setTimeout(avatar.printManager.start, 3000);
    }

    this.world.activate();

    BaseGame.prototype.onStart.call(this);
};

/**
 * On stop
 */
Game.prototype.onStop = function()
{
    this.emit('game:stop', {game: this});

    BaseGame.prototype.onStop.call(this);

    var won = this.isWon();

    if (won) {
        if (won instanceof Avatar) {
            this.gameWinner = won;
        }
        this.end();
    } else {
        this.newRound();
    }
};

/**
 * Set borderless
 *
 * @param {Boolean} borderless
 */
Game.prototype.setBorderless = function(borderless)
{
    if (this.borderless !== borderless) {
        BaseGame.prototype.setBorderless.call(this, borderless);
        this.emit('borderless', this.borderless);
    }
};

/**
 * FIN DU GAME
 */
Game.prototype.end = function()
{
    BaseGame.prototype.end.call(this);

    this.world.clear();

    delete this.world;
    delete this.avatars;
    delete this.deaths;
    delete this.bonusManager;
    delete this.controller;
};
<|endoftext|>