<commit_before>import _ from 'lodash';

import React from 'react';

import ActorClient from '../../utils/ActorClient';

import InviteUserActions from '../../actions/InviteUserActions';

import ContactStore from '../../stores/ContactStore';
import InviteUserStore from '../../stores/InviteUserStore';

import Modal from 'react-modal';

import ContactItem from './invite-user/ContactItem.react';

const getStateFromStores = function() {
  return ({
    contacts: ContactStore.getContacts(),
    group: InviteUserStore.getGroup(),
    inviteUrl: InviteUserStore.getInviteUrl(),
    isOpen: InviteUserStore.isModalOpen()
  });
};

const hasMember = (group, userId) =>
  undefined !== _.find(group.members, (c) => c.peerInfo.peer.id === userId);

export default React.createClass({
  getInitialState () {
    return getStateFromStores();
  },

  componentWillMount() {
    this.unsubscribe = InviteUserStore.listen(this._onChange);
    ContactStore.addChangeListener(this._onChange);
  },

  componentWillUnmount() {
    this.unsubscribe();
    ContactStore.removeChangeListener(this._onChange);
  },

  render () {
    let contacts = this.state.contacts;
    let isOpen = this.state.isOpen;

    if (isOpen) {
      let contactList = [];

      _.forEach(contacts, function(contact, i) {
        if (!hasMember(this.state.group, contact.uid)) {
          contactList.push(
            <ContactItem key={i} contact={contact} onSelect={this._onContactSelect}/>
          );
        }
      }, this);

      let inviteViaUrl = null;

      if (this.state.inviteUrl) {
        inviteViaUrl =
Or send a link: <input value={this.state.inviteUrl} onClick={this._onInviteUrlClick}/>
;
      }

      return (
        <Modal closeTimeoutMS={150}
               isOpen={isOpen} className="modal modal--invite contacts">

            clear

Select contact

              {inviteViaUrl}
              {contactList}

        </Modal>
      );
    } else {
      return (null);
    }
  },

  _onChange () {
    this.setState(getStateFromStores());
  },

  _onClose () {
    InviteUserActions.modalClose();
  },

  _onContactSelect (contact) {
    ActorClient.inviteMember(this.state.group.id, contact.uid)
      .then(() => InviteUserActions.modalClose());
  },

  _onInviteUrlClick (event) {
    event.target.select();
  }
});

<commit_msg>feat(web): style invite url<commit_after>import _ from 'lodash';

import React from 'react';

import ActorClient from '../../utils/ActorClient';

import InviteUserActions from '../../actions/InviteUserActions';

import ContactStore from '../../stores/ContactStore';
import InviteUserStore from '../../stores/InviteUserStore';

import Modal from 'react-modal';

import ContactItem from './invite-user/ContactItem.react';

const getStateFromStores = function () {
  return ({
    contacts: ContactStore.getContacts(),
    group: InviteUserStore.getGroup(),
    inviteUrl: InviteUserStore.getInviteUrl(),
    isOpen: InviteUserStore.isModalOpen()
  });
};

const hasMember = (group, userId) =>
undefined !== _.find(group.members, (c) => c.peerInfo.peer.id === userId);

export default React.createClass({
  getInitialState () {
    return getStateFromStores();
  },

  componentWillMount() {
    this.unsubscribe = InviteUserStore.listen(this._onChange);
    ContactStore.addChangeListener(this._onChange);
  },

  componentWillUnmount() {
    this.unsubscribe();
    ContactStore.removeChangeListener(this._onChange);
  },

  render () {
    let contacts = this.state.contacts;
    let isOpen = this.state.isOpen;

    if (isOpen) {
      let contactList = [];

      _.forEach(contacts, function (contact, i) {
        if (!hasMember(this.state.group, contact.uid)) {
          contactList.push(
            <ContactItem key={i} contact={contact} onSelect={this._onContactSelect}/>
          );
        }
      }, this);

      let inviteViaUrl = null;

      if (this.state.inviteUrl) {
        inviteViaUrl =

Or send a link:

                <input readOnly value={this.state.inviteUrl} onClick={this._onInviteUrlClick}/>

;
      }

      return (
        <Modal closeTimeoutMS={150}
               isOpen={isOpen} className="modal modal--invite contacts">

            clear

Select contact

              {inviteViaUrl}
              {contactList}

        </Modal>
      );
    } else {
      return (null);
    }
  },

  _onChange () {
    this.setState(getStateFromStores());
  },

  _onClose () {
    InviteUserActions.modalClose();
  },

  _onContactSelect (contact) {
    ActorClient.inviteMember(this.state.group.id, contact.uid)
      .then(() => InviteUserActions.modalClose());
  },

  _onInviteUrlClick (event) {
    event.target.select();
  }
});

<|endoftext|>


<commit_before>
class Parser{
  createNode(node){
    let newnode = Object.assign({},node.attribs || {});
    this.parseCoordinate(newnode);
    this.parseTags(node,newnode);
    this.parseOthers(node,newnode);
    return newnode;

  }
  parseCoordinate(newnode){
     var lat = parseFloat(newnode.lat);
     var lng = parseFloat(newnode.lon);
     if(lat && lng){
      newnode.loc={
        type:"Point",
        coordinates:[lng,lat]
      }
      newnode.lng = newnode.lon;
      delete newnode.lon;
     }
  }
  parseTags(node,newnode){
    if(node.children && node.children.tag){
      var tags = node.children.tag;
      let tag;
      newnode.tags = {};
      for(var i=0;i<tags.length;i++){
        tag = tags[i];
        if(tag.attribs){
          if(tag.attribs.k && tag.attribs.k.trim() === "name" ){
            newnode.name = tag.attribs.v.trim();
          }
        }
        else{
          newnode.tags[tag.attribs.k.trim()]=tag.attribs.v.trim();
        }
      }
    }
  }
  parseOthers(node,newnode){
    var childname,child;
    if(!node.children) return;
    for(childname in node.children){
      if(childname !== "tag"){
        child = node.children[childname];
        if(Array.isArray(child)){
          newnode[childname] = []
          child.forEach((child)=>{
            newnode[childname].push(child.attribs);
          })
        }
        else{
          newnode[childname] = child.attribs;
        }
      }

    }
  }
}
const nodeParser = new Parser();
exports.nodeParser = nodeParser;

<commit_msg>fixed tags parsing<commit_after>
class Parser{
  createNode(node){
    let newnode = Object.assign({},node.attribs || {});
    this.parseCoordinate(newnode);
    this.parseTags(node,newnode);
    this.parseOthers(node,newnode);
    return newnode;

  }
  parseCoordinate(newnode){
     var lat = parseFloat(newnode.lat);
     var lng = parseFloat(newnode.lon);
     if(lat && lng){
      newnode.loc={
        type:"Point",
        coordinates:[lng,lat]
      }
      newnode.lng = newnode.lon;
      delete newnode.lon;
     }
  }
  parseTags(node,newnode){
    if(node.children && node.children.tag){
      var tags = node.children.tag;
      let tag;
      newnode.tags = {};
      if(Array.isArray(tags)){
        for(var i=0;i<tags.length;i++){
          tag = tags[i];
          //console.log(tag.attribs.k)
          if(tag.attribs && tag.attribs.k){
            if(tag.attribs.k.trim() === "name"){
              newnode.name = tag.attribs.v.trim();
            }
            else{
              newnode.tags[tag.attribs.k.trim()]=tag.attribs.v.trim();
            }
          }
        }
      }
      else if(tags.attribs && tags.attribs.k){
          newnode.tags[tags.attribs.k.trim()]=tags.attribs.v.trim();
      }
    }
    //console.log(newnode)
  }
  parseOthers(node,newnode){
    var childname,child;
    if(!node.children) return;
    for(childname in node.children){
      if(childname !== "tag"){
        child = node.children[childname];
        if(Array.isArray(child)){
          newnode[childname] = []
          child.forEach((child)=>{
            newnode[childname].push(child.attribs);
          })
        }
        else{
          newnode[childname] = child.attribs;
        }
      }

    }
  }
}
const nodeParser = new Parser();
exports.nodeParser = nodeParser;

<|endoftext|>


<commit_before>iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
    var way = context.entity(wayId),
        finished = false,
        annotation = t((way.isDegenerate() ?
            'operations.start.annotation.' :
            'operations.continue.annotation.') + context.geometry(wayId)),
        draw = iD.behavior.Draw(context);

    var node = iD.Node({loc: context.map().mouseCoordinates()}),
        nodeId = node.id;

    context[way.isDegenerate() ? 'replace' : 'perform'](
        iD.actions.AddEntity(node),
        iD.actions.AddVertex(wayId, node.id, index));

    function move(datum) {
        var loc = context.map().mouseCoordinates();

        if (datum.type === 'node' || datum.type === 'midpoint') {
            loc = datum.loc;
        } else if (datum.type === 'way') {
            loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
        }

        context.replace(iD.actions.MoveNode(nodeId, loc));
    }

    function undone() {
        context.enter(iD.modes.Browse(context));
    }

    var drawWay = function(surface) {
        draw.on('move', move)
            .on('click', drawWay.add)
            .on('clickWay', drawWay.addWay)
            .on('clickNode', drawWay.addNode)
            .on('clickMidpoint', drawWay.addMidpoint)
            .on('undo', context.undo)
            .on('cancel', drawWay.cancel)
            .on('finish', drawWay.finish);

        context.map()
            .fastEnable(false)
            .minzoom(16)
            .dblclickEnable(false);

        surface.call(draw)
          .selectAll('.way, .node')
            .filter(function (d) { return d.id === wayId || d.id === nodeId; })
            .classed('active', true);

        context.history()
            .on('undone.draw', undone);
    };

    drawWay.off = function(surface) {
        if (!finished)
            context.pop();

        context.map()
            .fastEnable(true)
            .minzoom(0)
            .tail(false);

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        surface.call(draw.off)
          .selectAll('.way, .node')
            .classed('active', false);

        context.history()
            .on('undone.draw', null);
    };

    function ReplaceTemporaryNode(newNode) {
        return function(graph) {
            return graph
                .replace(way.removeNode(nodeId).addNode(newNode.id, index))
                .remove(node);
        }
    }

    // Accept the current position of the temporary node and continue drawing.
    drawWay.add = function(loc) {
        var newNode = iD.Node({loc: loc});

        context.replace(
            iD.actions.AddEntity(newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing way.
    drawWay.addWay = function(way, loc, wayIndex) {
        var newNode = iD.Node({loc: loc});

        context.perform(
            iD.actions.AddEntity(newNode),
            iD.actions.AddVertex(way.id, newNode.id, wayIndex),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing node and continue drawing.
    drawWay.addNode = function(node) {
        context.perform(
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Add a midpoint, connect the way to it, and continue drawing.
    drawWay.addMidpoint = function(midpoint) {
        var node = iD.Node();

        context.perform(
            iD.actions.AddMidpoint(midpoint, node),
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Finish the draw operation, removing the temporary node. If the way has enough
    // nodes to be valid, it's selected. Otherwise, return to browse mode.
    drawWay.finish = function() {
        context.pop();
        finished = true;

        var way = context.entity(wayId);
        if (way) {
            context.enter(iD.modes.Select(context, [way.id], true));
        } else {
            context.enter(iD.modes.Browse(context));
        }
    };

    // Cancel the draw operation and return to browse, deleting everything drawn.
    drawWay.cancel = function() {
        context.perform(
            d3.functor(baseGraph),
            t('operations.cancel_draw.annotation'));

        finished = true;
        context.enter(iD.modes.Browse(context));
    };

    return d3.rebind(drawWay, event, 'on');
};
<commit_msg>Don't snap to midpoints, snap to their parent way<commit_after>iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
    var way = context.entity(wayId),
        finished = false,
        annotation = t((way.isDegenerate() ?
            'operations.start.annotation.' :
            'operations.continue.annotation.') + context.geometry(wayId)),
        draw = iD.behavior.Draw(context);

    var node = iD.Node({loc: context.map().mouseCoordinates()}),
        nodeId = node.id;

    context[way.isDegenerate() ? 'replace' : 'perform'](
        iD.actions.AddEntity(node),
        iD.actions.AddVertex(wayId, node.id, index));

    function move(datum) {
        var loc = context.map().mouseCoordinates();

        if (datum.type === 'node') {
            loc = datum.loc;
        } else if (datum.type === 'midpoint' || datum.type === 'way') {
            var way = datum.type === 'way' ?
                datum :
                baseGraph.entity(datum.ways[0].id);
            loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc;
        }

        context.replace(iD.actions.MoveNode(nodeId, loc));
    }

    function undone() {
        context.enter(iD.modes.Browse(context));
    }

    var drawWay = function(surface) {
        draw.on('move', move)
            .on('click', drawWay.add)
            .on('clickWay', drawWay.addWay)
            .on('clickNode', drawWay.addNode)
            .on('clickMidpoint', drawWay.addMidpoint)
            .on('undo', context.undo)
            .on('cancel', drawWay.cancel)
            .on('finish', drawWay.finish);

        context.map()
            .fastEnable(false)
            .minzoom(16)
            .dblclickEnable(false);

        surface.call(draw)
          .selectAll('.way, .node')
            .filter(function (d) { return d.id === wayId || d.id === nodeId; })
            .classed('active', true);

        context.history()
            .on('undone.draw', undone);
    };

    drawWay.off = function(surface) {
        if (!finished)
            context.pop();

        context.map()
            .fastEnable(true)
            .minzoom(0)
            .tail(false);

        window.setTimeout(function() {
            context.map().dblclickEnable(true);
        }, 1000);

        surface.call(draw.off)
          .selectAll('.way, .node')
            .classed('active', false);

        context.history()
            .on('undone.draw', null);
    };

    function ReplaceTemporaryNode(newNode) {
        return function(graph) {
            return graph
                .replace(way.removeNode(nodeId).addNode(newNode.id, index))
                .remove(node);
        }
    }

    // Accept the current position of the temporary node and continue drawing.
    drawWay.add = function(loc) {
        var newNode = iD.Node({loc: loc});

        context.replace(
            iD.actions.AddEntity(newNode),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing way.
    drawWay.addWay = function(way, loc, wayIndex) {
        var newNode = iD.Node({loc: loc});

        context.perform(
            iD.actions.AddEntity(newNode),
            iD.actions.AddVertex(way.id, newNode.id, wayIndex),
            ReplaceTemporaryNode(newNode),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Connect the way to an existing node and continue drawing.
    drawWay.addNode = function(node) {
        context.perform(
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Add a midpoint, connect the way to it, and continue drawing.
    drawWay.addMidpoint = function(midpoint) {
        var node = iD.Node();

        context.perform(
            iD.actions.AddMidpoint(midpoint, node),
            ReplaceTemporaryNode(node),
            annotation);

        finished = true;
        context.enter(mode);
    };

    // Finish the draw operation, removing the temporary node. If the way has enough
    // nodes to be valid, it's selected. Otherwise, return to browse mode.
    drawWay.finish = function() {
        context.pop();
        finished = true;

        var way = context.entity(wayId);
        if (way) {
            context.enter(iD.modes.Select(context, [way.id], true));
        } else {
            context.enter(iD.modes.Browse(context));
        }
    };

    // Cancel the draw operation and return to browse, deleting everything drawn.
    drawWay.cancel = function() {
        context.perform(
            d3.functor(baseGraph),
            t('operations.cancel_draw.annotation'));

        finished = true;
        context.enter(iD.modes.Browse(context));
    };

    return d3.rebind(drawWay, event, 'on');
};
<|endoftext|>


<commit_before>// ==========================================================================
//                            DG.PlottedAverageModel
//
//  Model for averages displayed as symbols in a dot plot.
//  The primary axis should be numeric, and the secondary axis one or
//  more cells (one mean per cell).
//
//  Author:   Craig D. Miller
//
//  Copyright ©2013 Scientific Reasoning Research Institute,
//                  University of Massachusetts Amherst
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/adornments/plot_adornment_model');

/**
 * @class  The model for a plotted average (mean or median).
 * @extends SC.Object
*/
DG.PlottedAverageModel = DG.PlotAdornmentModel.extend(
/** @scope DG.PlottedAverageModel.prototype */
{
  _values: null,       // [{ mean|median }], one element per cell on secondary axis

  values: function( iKey, iValue) {
    if( iValue) {
      this._values = iValue;
    }
    if( !this._values) {
      this.recomputeValue();
    }
    return this._values;
  }.property(),

  precision: 0,       // decimal precision of attribute being averaged

  /**
   * True if we need to compute new values to match new cells.
   * Note that this does not detect data changes where means need recomputing anyway.
   * @return { Boolean }
   */
  isComputingNeeded: function() {
    return this._needsComputing && this.isVisible;
  },

  /**
   * Note that our mean values are out of date, for lazy evaluation.
   * Dependencies, which will require a recompute
   *  - case-attribute-values added/deleted/changed for the primary and secondary axis attribute(s)
   *  - primary or secondary axis attributes changed (from one attribute to another)
   *  - axis models changed (must be up to date when we use them here)
   */
  setComputingNeeded: function() {
    this._needsComputing = true;
  },

  /**
    Use the bounds of the given axes to recompute slope and intercept.
  */
  recomputeValueIfNeeded: function() {
    if( this.isComputingNeeded())
      this.recomputeValue();
  },

  /** derived classes must implement */
  recomputeValue: function() {
  },

  /**
    Private cache.
    @property { Boolean }
  */
  _needsComputing: true

});

DG.PlottedMeanStDevModel = DG.PlottedAverageModel.extend(
/** @scope DG.PlottedMeanStDevModel.prototype */
{
  /**
   * Compute or re-compute the sums, counts, and means of each cell.
   * returns null or an array of values, one for each cell
   */
  computeSumCountMean: function( includeMAD) {

    var tCases = this.getPath('plotModel.cases'),
        tNumericVarID = this.getPath( 'plotModel.primaryVarID'),
        tNumericAxisModel = this.getPath('plotModel.primaryAxisModel'),
        tCategoricalVarID = this.getPath( 'plotModel.secondaryVarID'),
        tCategoricalAxisModel = this.getPath('plotModel.secondaryAxisModel');
    if( !( tCategoricalAxisModel && tNumericAxisModel )) {
      return null; // too early to recompute, caller must try again later.
    }

    var tNumCells = tCategoricalAxisModel.get('numberOfCells');
    var tValues = []; // new values to compute and save
    var i,j;

    // initialize the values
    for( i=0, j=tNumCells; i<j; ++i ) {
      tValues.push({ sum:0, sumOfSquares:0, count:0, mean:undefined, stdev:undefined, numericValues: [] });
    }

    // compute count and sum of cases in each cell, excluding missing values
    // take care to handle null VarIDs and null case values correctly
    tCases.forEach( function( iCase, iIndex ) {
      var tNumericValue = iCase.getNumValue( tNumericVarID),
          tCellValue = iCase.getStrValue( tCategoricalVarID),
          tCellNumber = tCategoricalAxisModel.cellNameToCellNumber( tCellValue);
      if( tCellNumber!==null && DG.MathUtilities.isInIntegerRange( tCellNumber, 0, tValues.length )) { // if Cell Number not missing
        var iValue = tValues[tCellNumber];
        if( isFinite( tNumericValue )) { // if numeric value not missing
          iValue.sum += tNumericValue;
          iValue.sumOfSquares +=( tNumericValue * tNumericValue );
          iValue.count += 1;
          if( includeMAD) {
            iValue.numericValues.push( tNumericValue);
          }
          //iValue.cellValue = tCellValue;
        }
      }
    });

    // compute mean of cases in each cell
    tValues.forEach( function( iValue ) { // TO-DO remove this.
      if( iValue.count > 0 ) {
        iValue.mean = iValue.sum / iValue.count;
        if( includeMAD) {
          iValue.mad = iValue.numericValues.reduce( function( iMad, iNumValue) {
            return iMad + Math.abs( iValue.mean - iNumValue);
          }, 0) / iValue.count;
          delete iValue.numericValues;
        }
      }
    });

    return tValues;
  }
});

DG.PlottedMeanModel = DG.PlottedMeanStDevModel.extend(
/** @scope DG.PlottedMeanModel.prototype */
{
  /**
   * Compute or re-compute the mean(s).
   */
  recomputeValue: function() {
    var tValues = this.computeSumCountMean();

    if( tValues ) {
      var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
      this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
      this.set( 'values', tValues ); // we expect view to observe this change
      this._needsComputing = false;
    }
  }
});
DG.PlotAdornmentModel.registry.plottedMean = DG.PlottedMeanModel;

DG.PlottedStDevModel = DG.PlottedMeanStDevModel.extend(
/** @scope DG.PlottedStDevModel.prototype */
{
  /**
   * Compute or re-compute the standard deviation(s).
   */
  recomputeValue: function() {
    var tValues = this.computeSumCountMean();
    if( !tValues)
      return;

    // compute st.dev. of cases in each cell
    tValues.forEach( function( iValue ) {
      if( iValue.count > 1 ) {
        iValue.stdev = Math.sqrt(( iValue.sumOfSquares -
                                  ( iValue.mean * iValue.mean ) * iValue.count) /
                                    (iValue.count - 1));
        iValue.centerMinus1Dev = iValue.mean - iValue.stdev;
      }
    });
    var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
    this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
    this.set( 'values', tValues ); // we expect view to observe this change
    this._needsComputing = false;
  }
});
DG.PlotAdornmentModel.registry.plottedStDev = DG.PlottedStDevModel;

DG.PlottedMadModel = DG.PlottedMeanStDevModel.extend(
    /** @scope DG.PlottedMadModel.prototype */
    {
      /**
       * Compute or re-compute the mean(s).
       */
      recomputeValue: function() {
        var tValues = this.computeSumCountMean( true /* include MAD */);
        if( !tValues)
          return;

        tValues.forEach( function( iValue ) {
          iValue.centerMinus1Dev = iValue.mean - iValue.mad;
        });
        var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
        this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
        this.set( 'values', tValues ); // we expect view to observe this change
        this._needsComputing = false;
      }
    });
DG.PlotAdornmentModel.registry.plottedMad = DG.PlottedMadModel;

DG.PlottedQuantileModel = DG.PlottedAverageModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute quantile(s) for cases in each cell.
   * Note: array of '.vals' is sorted ascending, which helps make further quantile computations more efficient.
   * returns null or an array of values, one for each cell.
   */
  collectCellValsAndMedian: function() {

    var tCases = this.getPath('plotModel.cases'),
        tNumericVarID = this.getPath( 'plotModel.primaryVarID'),
        tCategoricalVarID = this.getPath( 'plotModel.secondaryVarID'),
        tCategoricalAxisModel = this.getPath('plotModel.secondaryAxisModel');
    if( ! tCategoricalAxisModel ) {
      //DG.log("DG.PlottedMedianModel.recomputeValue() ignored, no plotModel.secondaryAxisModel yet");
      return; // too early to recompute, caller must try again later.
    }
    var tNumCells = tCategoricalAxisModel.get('numberOfCells'),
        tValues = [], // new values to compute and save
        i,j;

    // initialize the values
    for( i=0, j=tNumCells; i<j; ++i ) {
      tValues.push({ vals: [], cases: [], median: undefined });
    }

    // collect array of numeric cases in each cell, excluding missing/non-numeric/non-finite values
    // take care to handle null VarIDs and null case values correctly
    tCases.forEach( function( iCase, iIndex ) {
      var tNumericValue = iCase.getNumValue( tNumericVarID),
          tCellValue = iCase.getStrValue( tCategoricalVarID),
          tCellNumber = tCategoricalAxisModel.cellNameToCellNumber( tCellValue);
      if( tCellNumber!= null && DG.MathUtilities.isInIntegerRange( tCellNumber, 0, tValues.length )) { // if Cell Number not missing
        var iValue = tValues[tCellNumber];
        if( isFinite( tNumericValue )) { // if numeric value not missing
          iValue.vals.push( tNumericValue );
          iValue.cases.push( iCase);
        }
      }
    });

    // compute median of cases in each cell
    tValues.forEach( function( iValue ) {
      if( iValue.vals.length > 0 ) { // TO_DO remove this.
        iValue.median = DG.MathUtilities.medianOfNumericArray( iValue.vals ); // also sorts .vals array
      }
    });
    return tValues;
  }
});

DG.PlottedMedianModel = DG.PlottedQuantileModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute or re-compute the median(s).
   */
  recomputeValue: function() {
    var tValues = this.collectCellValsAndMedian();

    if( tValues) {
      this.set('values', tValues); // we expect view to observe this change
      this._needsComputing = false;
      //DG.log("DG.PlottedMedianModel.recomputeValue() done");
    }
  }
});
DG.PlotAdornmentModel.registry.plottedMedian = DG.PlottedMedianModel;

DG.PlottedIQRModel = DG.PlottedQuantileModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute or re-compute the Median, Q1, Q3, IQR for each cell.
   */
  recomputeValue: function() {
    var tValues = this.collectCellValsAndMedian();
    if( !tValues)
      return;

    // also compute IQR
    tValues.forEach( function( iValue ) {
      if( iValue.vals.length > 0 ) {
        iValue.Q1 = DG.MathUtilities.quantileOfSortedArray( iValue.vals, 0.25 );
        iValue.Q3 = DG.MathUtilities.quantileOfSortedArray( iValue.vals, 0.75 );
        iValue.IQR = (iValue.Q3 - iValue.Q1);
      }
    });

    this.set( 'values', tValues ); // we expect view to observe this change
    this._needsComputing = false;
    //DG.log("DG.PlottedIQRModel.recomputeValue() done");
  }
});
DG.PlotAdornmentModel.registry.plottedIQR = DG.PlottedIQRModel;

DG.PlottedBoxPlotModel = DG.PlottedIQRModel.extend(
/** @scope DG.PlottedBoxPlotModel.prototype */
{
  showOutliers: false,  // If true, then we draw a "modified" boxplot

  /**
   * Compute or re-compute the Median, Q1, Q3, lowerWhisker, upperWhisker for each cell.
   * My base class will have computed everything except the lower and upper whisker.
   *    A whisker extends at most 1.5 times the IQR beyond the appropriate percentile, but
   *    stops at the closest value that doesn't exceed it.
   */
  recomputeValue: function() {
    this.beginPropertyChanges();  // Postpone notification until after we compute whisker ends

    sc_super();

    var tValues = this.get('values'),
        tShowOutliers = this.get('showOutliers');
    if( tValues) {

      tValues.forEach(function (iValue) {
        iValue.upperOutliers = [];
        iValue.lowerOutliers = [];
        if (iValue.vals.length > 0) {
          if( tShowOutliers) {
            var tMaxWhiskerLength = 1.5 * iValue.IQR,
                tWhiskerCandidate, tIndex;
            tWhiskerCandidate = iValue.Q1 - tMaxWhiskerLength;
            tIndex = 0;
            while (iValue.vals[tIndex] < tWhiskerCandidate) {
              iValue.lowerOutliers.push(iValue.vals[tIndex]);
              tIndex++;
            }
            iValue.lowerWhisker = iValue.vals[tIndex];

            tWhiskerCandidate = iValue.Q3 + tMaxWhiskerLength;
            tIndex = iValue.vals.length - 1;
            while (iValue.vals[tIndex] > tWhiskerCandidate) {
              iValue.upperOutliers.push(iValue.vals[tIndex]);
              tIndex--;
            }
            iValue.upperWhisker = iValue.vals[tIndex];
          }
          else {
            iValue.lowerWhisker = iValue.vals[0];
            iValue.upperWhisker = iValue.vals[iValue.vals.length - 1];
          }
        }
      });
    }

    this.endPropertyChanges();
  },
  /**
   Returns an object which contains properties that should be written
   out with the document for archiving purposes.
   */
  createStorage: function() {
    var tStorage = sc_super();
    tStorage.showOutliers = this.get('showOutliers') || false;
    return tStorage;
  },

  /**
   Set the contents of the adornment model from the restored storage.
   */
  restoreStorage: function( iStorage) {
    sc_super();
    if( iStorage)
      this.set('showOutliers', iStorage.showOutliers || false);
  }

});
DG.PlotAdornmentModel.registry.plottedBoxPlot = DG.PlottedBoxPlotModel;
<commit_msg>[#168183559] Bug fix: CODAP crashes when categorical axis is changed while graph adornment is showing * Needed a more lenient test for null that includes undefined because we make a (spurious) pass through computing values as the categorical axis is going away.<commit_after>// ==========================================================================
//                            DG.PlottedAverageModel
//
//  Model for averages displayed as symbols in a dot plot.
//  The primary axis should be numeric, and the secondary axis one or
//  more cells (one mean per cell).
//
//  Author:   Craig D. Miller
//
//  Copyright ©2013 Scientific Reasoning Research Institute,
//                  University of Massachusetts Amherst
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/adornments/plot_adornment_model');

/**
 * @class  The model for a plotted average (mean or median).
 * @extends SC.Object
*/
DG.PlottedAverageModel = DG.PlotAdornmentModel.extend(
/** @scope DG.PlottedAverageModel.prototype */
{
  _values: null,       // [{ mean|median }], one element per cell on secondary axis

  values: function( iKey, iValue) {
    if( iValue) {
      this._values = iValue;
    }
    if( !this._values) {
      this.recomputeValue();
    }
    return this._values;
  }.property(),

  precision: 0,       // decimal precision of attribute being averaged

  /**
   * True if we need to compute new values to match new cells.
   * Note that this does not detect data changes where means need recomputing anyway.
   * @return { Boolean }
   */
  isComputingNeeded: function() {
    return this._needsComputing && this.isVisible;
  },

  /**
   * Note that our mean values are out of date, for lazy evaluation.
   * Dependencies, which will require a recompute
   *  - case-attribute-values added/deleted/changed for the primary and secondary axis attribute(s)
   *  - primary or secondary axis attributes changed (from one attribute to another)
   *  - axis models changed (must be up to date when we use them here)
   */
  setComputingNeeded: function() {
    this._needsComputing = true;
  },

  /**
    Use the bounds of the given axes to recompute slope and intercept.
  */
  recomputeValueIfNeeded: function() {
    if( this.isComputingNeeded())
      this.recomputeValue();
  },

  /** derived classes must implement */
  recomputeValue: function() {
  },

  /**
    Private cache.
    @property { Boolean }
  */
  _needsComputing: true

});

DG.PlottedMeanStDevModel = DG.PlottedAverageModel.extend(
/** @scope DG.PlottedMeanStDevModel.prototype */
{
  /**
   * Compute or re-compute the sums, counts, and means of each cell.
   * returns null or an array of values, one for each cell
   */
  computeSumCountMean: function( includeMAD) {

    var tCases = this.getPath('plotModel.cases'),
        tNumericVarID = this.getPath( 'plotModel.primaryVarID'),
        tNumericAxisModel = this.getPath('plotModel.primaryAxisModel'),
        tCategoricalVarID = this.getPath( 'plotModel.secondaryVarID'),
        tCategoricalAxisModel = this.getPath('plotModel.secondaryAxisModel');
    if( !( tCategoricalAxisModel && tNumericAxisModel )) {
      return null; // too early to recompute, caller must try again later.
    }

    var tNumCells = tCategoricalAxisModel.get('numberOfCells');
    var tValues = []; // new values to compute and save
    var i,j;

    // initialize the values
    for( i=0, j=tNumCells; i<j; ++i ) {
      tValues.push({ sum:0, sumOfSquares:0, count:0, mean:undefined, stdev:undefined, numericValues: [] });
    }

    // compute count and sum of cases in each cell, excluding missing values
    // take care to handle null VarIDs and null case values correctly
    tCases.forEach( function( iCase, iIndex ) {
      var tNumericValue = iCase.getNumValue( tNumericVarID),
          tCellValue = iCase.getStrValue( tCategoricalVarID),
          tCellNumber = tCategoricalAxisModel.cellNameToCellNumber( tCellValue);
      if( tCellNumber!=null && DG.MathUtilities.isInIntegerRange( tCellNumber, 0, tValues.length )) { // if Cell Number not missing
        var iValue = tValues[tCellNumber];
        if( isFinite( tNumericValue )) { // if numeric value not missing
          iValue.sum += tNumericValue;
          iValue.sumOfSquares +=( tNumericValue * tNumericValue );
          iValue.count += 1;
          if( includeMAD) {
            iValue.numericValues.push( tNumericValue);
          }
          //iValue.cellValue = tCellValue;
        }
      }
    });

    // compute mean of cases in each cell
    tValues.forEach( function( iValue ) { // TO-DO remove this.
      if( iValue.count > 0 ) {
        iValue.mean = iValue.sum / iValue.count;
        if( includeMAD) {
          iValue.mad = iValue.numericValues.reduce( function( iMad, iNumValue) {
            return iMad + Math.abs( iValue.mean - iNumValue);
          }, 0) / iValue.count;
          delete iValue.numericValues;
        }
      }
    });

    return tValues;
  }
});

DG.PlottedMeanModel = DG.PlottedMeanStDevModel.extend(
/** @scope DG.PlottedMeanModel.prototype */
{
  /**
   * Compute or re-compute the mean(s).
   */
  recomputeValue: function() {
    var tValues = this.computeSumCountMean();

    if( tValues ) {
      var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
      this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
      this.set( 'values', tValues ); // we expect view to observe this change
      this._needsComputing = false;
    }
  }
});
DG.PlotAdornmentModel.registry.plottedMean = DG.PlottedMeanModel;

DG.PlottedStDevModel = DG.PlottedMeanStDevModel.extend(
/** @scope DG.PlottedStDevModel.prototype */
{
  /**
   * Compute or re-compute the standard deviation(s).
   */
  recomputeValue: function() {
    var tValues = this.computeSumCountMean();
    if( !tValues)
      return;

    // compute st.dev. of cases in each cell
    tValues.forEach( function( iValue ) {
      if( iValue.count > 1 ) {
        iValue.stdev = Math.sqrt(( iValue.sumOfSquares -
                                  ( iValue.mean * iValue.mean ) * iValue.count) /
                                    (iValue.count - 1));
        iValue.centerMinus1Dev = iValue.mean - iValue.stdev;
      }
    });
    var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
    this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
    this.set( 'values', tValues ); // we expect view to observe this change
    this._needsComputing = false;
  }
});
DG.PlotAdornmentModel.registry.plottedStDev = DG.PlottedStDevModel;

DG.PlottedMadModel = DG.PlottedMeanStDevModel.extend(
    /** @scope DG.PlottedMadModel.prototype */
    {
      /**
       * Compute or re-compute the mean(s).
       */
      recomputeValue: function() {
        var tValues = this.computeSumCountMean( true /* include MAD */);
        if( !tValues)
          return;

        tValues.forEach( function( iValue ) {
          iValue.centerMinus1Dev = iValue.mean - iValue.mad;
        });
        var tNumericAxisModel = this.getPath('plotModel.primaryAxisModel');
        this.set( 'precision', tNumericAxisModel.getPath('attributeDescription.attribute.precision'));
        this.set( 'values', tValues ); // we expect view to observe this change
        this._needsComputing = false;
      }
    });
DG.PlotAdornmentModel.registry.plottedMad = DG.PlottedMadModel;

DG.PlottedQuantileModel = DG.PlottedAverageModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute quantile(s) for cases in each cell.
   * Note: array of '.vals' is sorted ascending, which helps make further quantile computations more efficient.
   * returns null or an array of values, one for each cell.
   */
  collectCellValsAndMedian: function() {

    var tCases = this.getPath('plotModel.cases'),
        tNumericVarID = this.getPath( 'plotModel.primaryVarID'),
        tCategoricalVarID = this.getPath( 'plotModel.secondaryVarID'),
        tCategoricalAxisModel = this.getPath('plotModel.secondaryAxisModel');
    if( ! tCategoricalAxisModel ) {
      //DG.log("DG.PlottedMedianModel.recomputeValue() ignored, no plotModel.secondaryAxisModel yet");
      return; // too early to recompute, caller must try again later.
    }
    var tNumCells = tCategoricalAxisModel.get('numberOfCells'),
        tValues = [], // new values to compute and save
        i,j;

    // initialize the values
    for( i=0, j=tNumCells; i<j; ++i ) {
      tValues.push({ vals: [], cases: [], median: undefined });
    }

    // collect array of numeric cases in each cell, excluding missing/non-numeric/non-finite values
    // take care to handle null VarIDs and null case values correctly
    tCases.forEach( function( iCase, iIndex ) {
      var tNumericValue = iCase.getNumValue( tNumericVarID),
          tCellValue = iCase.getStrValue( tCategoricalVarID),
          tCellNumber = tCategoricalAxisModel.cellNameToCellNumber( tCellValue);
      if( tCellNumber!= null && DG.MathUtilities.isInIntegerRange( tCellNumber, 0, tValues.length )) { // if Cell Number not missing
        var iValue = tValues[tCellNumber];
        if( isFinite( tNumericValue )) { // if numeric value not missing
          iValue.vals.push( tNumericValue );
          iValue.cases.push( iCase);
        }
      }
    });

    // compute median of cases in each cell
    tValues.forEach( function( iValue ) {
      if( iValue.vals.length > 0 ) { // TO_DO remove this.
        iValue.median = DG.MathUtilities.medianOfNumericArray( iValue.vals ); // also sorts .vals array
      }
    });
    return tValues;
  }
});

DG.PlottedMedianModel = DG.PlottedQuantileModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute or re-compute the median(s).
   */
  recomputeValue: function() {
    var tValues = this.collectCellValsAndMedian();

    if( tValues) {
      this.set('values', tValues); // we expect view to observe this change
      this._needsComputing = false;
      //DG.log("DG.PlottedMedianModel.recomputeValue() done");
    }
  }
});
DG.PlotAdornmentModel.registry.plottedMedian = DG.PlottedMedianModel;

DG.PlottedIQRModel = DG.PlottedQuantileModel.extend(
/** @scope DG.PlottedMedianModel.prototype */
{
  /**
   * Compute or re-compute the Median, Q1, Q3, IQR for each cell.
   */
  recomputeValue: function() {
    var tValues = this.collectCellValsAndMedian();
    if( !tValues)
      return;

    // also compute IQR
    tValues.forEach( function( iValue ) {
      if( iValue.vals.length > 0 ) {
        iValue.Q1 = DG.MathUtilities.quantileOfSortedArray( iValue.vals, 0.25 );
        iValue.Q3 = DG.MathUtilities.quantileOfSortedArray( iValue.vals, 0.75 );
        iValue.IQR = (iValue.Q3 - iValue.Q1);
      }
    });

    this.set( 'values', tValues ); // we expect view to observe this change
    this._needsComputing = false;
    //DG.log("DG.PlottedIQRModel.recomputeValue() done");
  }
});
DG.PlotAdornmentModel.registry.plottedIQR = DG.PlottedIQRModel;

DG.PlottedBoxPlotModel = DG.PlottedIQRModel.extend(
/** @scope DG.PlottedBoxPlotModel.prototype */
{
  showOutliers: false,  // If true, then we draw a "modified" boxplot

  /**
   * Compute or re-compute the Median, Q1, Q3, lowerWhisker, upperWhisker for each cell.
   * My base class will have computed everything except the lower and upper whisker.
   *    A whisker extends at most 1.5 times the IQR beyond the appropriate percentile, but
   *    stops at the closest value that doesn't exceed it.
   */
  recomputeValue: function() {
    this.beginPropertyChanges();  // Postpone notification until after we compute whisker ends

    sc_super();

    var tValues = this.get('values'),
        tShowOutliers = this.get('showOutliers');
    if( tValues) {

      tValues.forEach(function (iValue) {
        iValue.upperOutliers = [];
        iValue.lowerOutliers = [];
        if (iValue.vals.length > 0) {
          if( tShowOutliers) {
            var tMaxWhiskerLength = 1.5 * iValue.IQR,
                tWhiskerCandidate, tIndex;
            tWhiskerCandidate = iValue.Q1 - tMaxWhiskerLength;
            tIndex = 0;
            while (iValue.vals[tIndex] < tWhiskerCandidate) {
              iValue.lowerOutliers.push(iValue.vals[tIndex]);
              tIndex++;
            }
            iValue.lowerWhisker = iValue.vals[tIndex];

            tWhiskerCandidate = iValue.Q3 + tMaxWhiskerLength;
            tIndex = iValue.vals.length - 1;
            while (iValue.vals[tIndex] > tWhiskerCandidate) {
              iValue.upperOutliers.push(iValue.vals[tIndex]);
              tIndex--;
            }
            iValue.upperWhisker = iValue.vals[tIndex];
          }
          else {
            iValue.lowerWhisker = iValue.vals[0];
            iValue.upperWhisker = iValue.vals[iValue.vals.length - 1];
          }
        }
      });
    }

    this.endPropertyChanges();
  },
  /**
   Returns an object which contains properties that should be written
   out with the document for archiving purposes.
   */
  createStorage: function() {
    var tStorage = sc_super();
    tStorage.showOutliers = this.get('showOutliers') || false;
    return tStorage;
  },

  /**
   Set the contents of the adornment model from the restored storage.
   */
  restoreStorage: function( iStorage) {
    sc_super();
    if( iStorage)
      this.set('showOutliers', iStorage.showOutliers || false);
  }

});
DG.PlotAdornmentModel.registry.plottedBoxPlot = DG.PlottedBoxPlotModel;
<|endoftext|>


<commit_before>iD.Features = function(context) {
    var major_roads = {
        'motorway': true,
        'motorway_link': true,
        'trunk': true,
        'trunk_link': true,
        'primary': true,
        'primary_link': true,
        'secondary': true,
        'secondary_link': true,
        'tertiary': true,
        'tertiary_link': true,
        'residential': true
    };

    var minor_roads = {
        'service': true,
        'living_street': true,
        'road': true,
        'unclassified': true,
        'track': true
    };

    var paths = {
        'path': true,
        'footway': true,
        'cycleway': true,
        'bridleway': true,
        'steps': true,
        'pedestrian': true,
        'corridor': true
    };

    var past_futures = {
        'proposed': true,
        'construction': true,
        'abandoned': true,
        'dismantled': true,
        'disused': true,
        'razed': true,
        'demolished': true,
        'obliterated': true
    };

    var dispatch = d3.dispatch('change', 'redraw'),
        _cullFactor = 1,
        _cache = {},
        _features = {},
        _stats = {},
        _keys = [],
        _hidden = [];

    function update() {
        _hidden = features.hidden();
        dispatch.change();
        dispatch.redraw();
    }

    function defineFeature(k, filter, max) {
        _keys.push(k);
        _features[k] = {
            filter: filter,
            enabled: true,   // whether the user wants it enabled..
            count: 0,
            currentMax: (max || Infinity),
            defaultMax: (max || Infinity),
            enable: function() { this.enabled = true; this.currentMax = this.defaultMax; },
            disable: function() { this.enabled = false; this.currentMax = 0; },
            hidden: function() { return !context.editable() || this.count > this.currentMax * _cullFactor; },
            autoHidden: function() { return this.hidden() && this.currentMax > 0; }
        };
    }

    defineFeature('points', function isPoint(entity, resolver, geometry) {
        return geometry === 'point';
    }, 200);

    defineFeature('major_roads', function isMajorRoad(entity) {
        return major_roads[entity.tags.highway];
    });

    defineFeature('minor_roads', function isMinorRoad(entity) {
        return minor_roads[entity.tags.highway];
    });

    defineFeature('paths', function isPath(entity) {
        return paths[entity.tags.highway];
    });

    defineFeature('buildings', function isBuilding(entity) {
        return (
            !!entity.tags['building:part'] ||
            (!!entity.tags.building && entity.tags.building !== 'no') ||
            entity.tags.amenity === 'shelter' ||
            entity.tags.parking === 'multi-storey' ||
            entity.tags.parking === 'sheds' ||
            entity.tags.parking === 'carports' ||
            entity.tags.parking === 'garage_boxes'
        );
    }, 250);

    defineFeature('landuse', function isLanduse(entity, resolver, geometry) {
        return geometry === 'area' &&
            !_features.buildings.filter(entity) &&
            !_features.water.filter(entity);
    });

    defineFeature('boundaries', function isBoundary(entity) {
        return !!entity.tags.boundary;
    });

    defineFeature('water', function isWater(entity) {
        return (
            !!entity.tags.waterway ||
            entity.tags.natural === 'water' ||
            entity.tags.natural === 'coastline' ||
            entity.tags.natural === 'bay' ||
            entity.tags.landuse === 'pond' ||
            entity.tags.landuse === 'basin' ||
            entity.tags.landuse === 'reservoir' ||
            entity.tags.landuse === 'salt_pond'
        );
    });

    defineFeature('rail', function isRail(entity) {
        return (
            !!entity.tags.railway ||
            entity.tags.landuse === 'railway'
        ) && !(
            major_roads[entity.tags.highway] ||
            minor_roads[entity.tags.highway] ||
            paths[entity.tags.highway]
        );
    });

    defineFeature('power', function isPower(entity) {
        return !!entity.tags.power;
    });

    // contains a past/future tag, but not in active use as a road/path/cycleway/etc..
    defineFeature('past_future', function isPastFuture(entity) {
        if (
            major_roads[entity.tags.highway] ||
            minor_roads[entity.tags.highway] ||
            paths[entity.tags.highway]
        ) { return false; }

        var strings = Object.keys(entity.tags);

        for (var i = 0; i < strings.length; i++) {
            var s = strings[i];
            if (past_futures[s] || past_futures[entity.tags[s]]) { return true; }
        }
        return false;
    });

    // Lines or areas that don't match another feature filter.
    // IMPORTANT: The 'others' feature must be the last one defined,
    //   so that code in getMatches can skip this test if `hasMatch = true`
    defineFeature('others', function isOther(entity, resolver, geometry) {
        return (geometry === 'line' || geometry === 'area');
    });

    function features() {}

    features.features = function() {
        return _features;
    };

    features.keys = function() {
        return _keys;
    };

    features.enabled = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].enabled; });
        }
        return _features[k] && _features[k].enabled;
    };

    features.disabled = function(k) {
        if (!arguments.length) {
            return _.reject(_keys, function(k) { return _features[k].enabled; });
        }
        return _features[k] && !_features[k].enabled;
    };

    features.hidden = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].hidden(); });
        }
        return _features[k] && _features[k].hidden();
    };

    features.autoHidden = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].autoHidden(); });
        }
        return _features[k] && _features[k].autoHidden();
    };

    features.enable = function(k) {
        if (_features[k] && !_features[k].enabled) {
            _features[k].enable();
            update();
        }
    };

    features.disable = function(k) {
        if (_features[k] && _features[k].enabled) {
            _features[k].disable();
            update();
        }
    };

    features.toggle = function(k) {
        if (_features[k]) {
            (function(f) { return f.enabled ? f.disable() : f.enable(); }(_features[k]));
            update();
        }
    };

    features.resetStats = function() {
        _.each(_features, function(f) { f.count = 0; });
        dispatch.change();
    };

    features.gatherStats = function(d, resolver, dimensions) {
        var needsRedraw = false,
            type = _.groupBy(d, function(ent) { return ent.type; }),
            entities = [].concat(type.relation || [], type.way || [], type.node || []),
            currHidden, geometry, matches;

        _.each(_features, function(f) { f.count = 0; });

        // adjust the threshold for point/building culling based on viewport size..
        // a _cullFactor of 1 corresponds to a 1000x1000px viewport..
        _cullFactor = dimensions[0] * dimensions[1] / 1000000;

        for (var i = 0; i < entities.length; i++) {
            geometry = entities[i].geometry(resolver);
            if (!(geometry === 'vertex' || geometry === 'relation')) {
                matches = Object.keys(features.getMatches(entities[i], resolver, geometry));
                for (var j = 0; j < matches.length; j++) {
                    _features[matches[j]].count++;
                }
            }
        }

        currHidden = features.hidden();
        if (currHidden !== _hidden) {
            _hidden = currHidden;
            needsRedraw = true;
            dispatch.change();
        }

        return needsRedraw;
    };

    features.stats = function() {
        _.each(_keys, function(k) { _stats[k] = _features[k].count; });
        return _stats;
    };

    features.clear = function(d) {
        for (var i = 0; i < d.length; i++) {
            features.clearEntity(d[i]);
        }
    };

    features.clearEntity = function(entity) {
        delete _cache[iD.Entity.key(entity)];
    };

    features.reset = function() {
        _cache = {};
    };

    features.getMatches = function(entity, resolver, geometry) {
        var ent = iD.Entity.key(entity);

        if (!_cache[ent]) {
            _cache[ent] = {};
        }
        if (!_cache[ent].matches) {
            var matches = {},
                hasMatch = false;

            if (!(geometry === 'vertex' || geometry === 'relation')) {
                for (var i = 0; i < _keys.length; i++) {

                    if (_keys[i] === 'others') {
                        if (hasMatch) continue;

                        // If the entity is a way that has not matched any other
                        // feature type, see if it has a parent relation, and if so,
                        // match whatever feature types the parent has matched.
                        // (The way is a member of a multipolygon.)
                        //
                        // IMPORTANT:
                        // For this to work, getMatches must be called on relations before ways.
                        //
                        if (entity.type === 'way') {
                            var parents = features.getParents(entity, resolver, geometry);
                            if (parents.length === 1) {
                                var pkey = iD.Entity.key(parents[0]);
                                if (_cache[pkey] && _cache[pkey].matches) {
                                    matches = _.clone(_cache[pkey].matches);
                                    continue;
                                }
                            }
                        }
                    }

                    if (_features[_keys[i]].filter(entity, resolver, geometry)) {
                        matches[_keys[i]] = hasMatch = true;
                    }
                }
            }
            _cache[ent].matches = matches;
        }
        return _cache[ent].matches;
    };

    features.getParents = function(entity, resolver, geometry) {
        var ent = iD.Entity.key(entity);

        if (!_cache[ent]) {
            _cache[ent] = {};
        }
        if (!_cache[ent].parents) {
            var parents = [];

            if (geometry !== 'point') {
                if (geometry === 'vertex') {
                    parents = resolver.parentWays(entity);
                } else {   // 'line', 'area', 'relation'
                    parents = resolver.parentRelations(entity);
                }
            }
            _cache[ent].parents = parents;
        }
        return _cache[ent].parents;
    };

    features.isHiddenFeature = function(entity, resolver, geometry) {
        if (!entity.version) return false;

        var matches = features.getMatches(entity, resolver, geometry);

        for (var i = 0; i < _hidden.length; i++) {
            if (matches[_hidden[i]]) { return true; }
        }
        return false;
    };

    features.isHiddenChild = function(entity, resolver, geometry) {
        if (!entity.version || geometry === 'point') { return false; }

        var parents = features.getParents(entity, resolver, geometry);

        if (!parents.length) { return false; }

        for (var i = 0; i < parents.length; i++) {
            if (!features.isHidden(parents[i], resolver, parents[i].geometry(resolver))) {
                return false;
            }
        }
        return true;
    };

    features.hasHiddenConnections = function(entity, resolver) {
        var childNodes, connections;

        if (entity.type === 'midpoint') {
            childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
            connections = [];
        } else {
            childNodes = entity.nodes ? resolver.childNodes(entity) : [];
            connections = features.getParents(entity, resolver, entity.geometry(resolver));
        }

        // gather ways connected to child nodes..
        connections = _.reduce(childNodes, function(result, e) {
            return resolver.isShared(e) ? _.union(result, resolver.parentWays(e)) : result;
        }, connections);

        return connections.length ? _.any(connections, function(e) {
            return features.isHidden(e, resolver, e.geometry(resolver));
        }) : false;
    };

    features.isHidden = function(entity, resolver, geometry) {
        if (!entity.version) return false;

        if (geometry === 'vertex')
            return features.isHiddenChild(entity, resolver, geometry);
        if (geometry === 'point')
            return features.isHiddenFeature(entity, resolver, geometry);

        return features.isHiddenFeature(entity, resolver, geometry) ||
               features.isHiddenChild(entity, resolver, geometry);
    };

    features.filter = function(d, resolver) {
        if (!_hidden.length)
            return d;

        var result = [];
        for (var i = 0; i < d.length; i++) {
            var entity = d[i];
            if (!features.isHidden(entity, resolver, entity.geometry(resolver))) {
                result.push(entity);
            }
        }
        return result;
    };

    return d3.rebind(features, dispatch, 'on');
};
<commit_msg>WIP for 2887, and perf improvements (avoid unnecessary caching)<commit_after>iD.Features = function(context) {
    var major_roads = {
        'motorway': true,
        'motorway_link': true,
        'trunk': true,
        'trunk_link': true,
        'primary': true,
        'primary_link': true,
        'secondary': true,
        'secondary_link': true,
        'tertiary': true,
        'tertiary_link': true,
        'residential': true
    };

    var minor_roads = {
        'service': true,
        'living_street': true,
        'road': true,
        'unclassified': true,
        'track': true
    };

    var paths = {
        'path': true,
        'footway': true,
        'cycleway': true,
        'bridleway': true,
        'steps': true,
        'pedestrian': true,
        'corridor': true
    };

    var past_futures = {
        'proposed': true,
        'construction': true,
        'abandoned': true,
        'dismantled': true,
        'disused': true,
        'razed': true,
        'demolished': true,
        'obliterated': true
    };

    var dispatch = d3.dispatch('change', 'redraw'),
        _cullFactor = 1,
        _cache = {},
        _features = {},
        _stats = {},
        _keys = [],
        _hidden = [];

    function update() {
        _hidden = features.hidden();
        dispatch.change();
        dispatch.redraw();
    }

    function defineFeature(k, filter, max) {
        _keys.push(k);
        _features[k] = {
            filter: filter,
            enabled: true,   // whether the user wants it enabled..
            count: 0,
            currentMax: (max || Infinity),
            defaultMax: (max || Infinity),
            enable: function() { this.enabled = true; this.currentMax = this.defaultMax; },
            disable: function() { this.enabled = false; this.currentMax = 0; },
            hidden: function() { return !context.editable() || this.count > this.currentMax * _cullFactor; },
            autoHidden: function() { return this.hidden() && this.currentMax > 0; }
        };
    }

    defineFeature('points', function isPoint(entity, resolver, geometry) {
        return geometry === 'point';
    }, 200);

    defineFeature('major_roads', function isMajorRoad(entity) {
        return major_roads[entity.tags.highway];
    });

    defineFeature('minor_roads', function isMinorRoad(entity) {
        return minor_roads[entity.tags.highway];
    });

    defineFeature('paths', function isPath(entity) {
        return paths[entity.tags.highway];
    });

    defineFeature('buildings', function isBuilding(entity) {
        return (
            !!entity.tags['building:part'] ||
            (!!entity.tags.building && entity.tags.building !== 'no') ||
            entity.tags.amenity === 'shelter' ||
            entity.tags.parking === 'multi-storey' ||
            entity.tags.parking === 'sheds' ||
            entity.tags.parking === 'carports' ||
            entity.tags.parking === 'garage_boxes'
        );
    }, 250);

    defineFeature('landuse', function isLanduse(entity, resolver, geometry) {
        return geometry === 'area' &&
            !_features.buildings.filter(entity) &&
            !_features.water.filter(entity);
    });

    defineFeature('boundaries', function isBoundary(entity) {
        return !!entity.tags.boundary;
    });

    defineFeature('water', function isWater(entity) {
        return (
            !!entity.tags.waterway ||
            entity.tags.natural === 'water' ||
            entity.tags.natural === 'coastline' ||
            entity.tags.natural === 'bay' ||
            entity.tags.landuse === 'pond' ||
            entity.tags.landuse === 'basin' ||
            entity.tags.landuse === 'reservoir' ||
            entity.tags.landuse === 'salt_pond'
        );
    });

    defineFeature('rail', function isRail(entity) {
        return (
            !!entity.tags.railway ||
            entity.tags.landuse === 'railway'
        ) && !(
            major_roads[entity.tags.highway] ||
            minor_roads[entity.tags.highway] ||
            paths[entity.tags.highway]
        );
    });

    defineFeature('power', function isPower(entity) {
        return !!entity.tags.power;
    });

    // contains a past/future tag, but not in active use as a road/path/cycleway/etc..
    defineFeature('past_future', function isPastFuture(entity) {
        if (
            major_roads[entity.tags.highway] ||
            minor_roads[entity.tags.highway] ||
            paths[entity.tags.highway]
        ) { return false; }

        var strings = Object.keys(entity.tags);

        for (var i = 0; i < strings.length; i++) {
            var s = strings[i];
            if (past_futures[s] || past_futures[entity.tags[s]]) { return true; }
        }
        return false;
    });

    // Lines or areas that don't match another feature filter.
    // IMPORTANT: The 'others' feature must be the last one defined,
    //   so that code in getMatches can skip this test if `hasMatch = true`
    defineFeature('others', function isOther(entity, resolver, geometry) {
        return (geometry === 'line' || geometry === 'area');
    });

    function features() {}

    features.features = function() {
        return _features;
    };

    features.keys = function() {
        return _keys;
    };

    features.enabled = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].enabled; });
        }
        return _features[k] && _features[k].enabled;
    };

    features.disabled = function(k) {
        if (!arguments.length) {
            return _.reject(_keys, function(k) { return _features[k].enabled; });
        }
        return _features[k] && !_features[k].enabled;
    };

    features.hidden = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].hidden(); });
        }
        return _features[k] && _features[k].hidden();
    };

    features.autoHidden = function(k) {
        if (!arguments.length) {
            return _.filter(_keys, function(k) { return _features[k].autoHidden(); });
        }
        return _features[k] && _features[k].autoHidden();
    };

    features.enable = function(k) {
        if (_features[k] && !_features[k].enabled) {
            _features[k].enable();
            update();
        }
    };

    features.disable = function(k) {
        if (_features[k] && _features[k].enabled) {
            _features[k].disable();
            update();
        }
    };

    features.toggle = function(k) {
        if (_features[k]) {
            (function(f) { return f.enabled ? f.disable() : f.enable(); }(_features[k]));
            update();
        }
    };

    features.resetStats = function() {
        _.each(_features, function(f) { f.count = 0; });
        dispatch.change();
    };

    features.gatherStats = function(d, resolver, dimensions) {
        var needsRedraw = false,
            type = _.groupBy(d, function(ent) { return ent.type; }),
            entities = [].concat(type.relation || [], type.way || [], type.node || []),
            currHidden, geometry, matches;

        _.each(_features, function(f) { f.count = 0; });

        // adjust the threshold for point/building culling based on viewport size..
        // a _cullFactor of 1 corresponds to a 1000x1000px viewport..
        _cullFactor = dimensions[0] * dimensions[1] / 1000000;

        for (var i = 0; i < entities.length; i++) {
            geometry = entities[i].geometry(resolver);
            if (!(geometry === 'vertex' || geometry === 'relation')) {
                matches = Object.keys(features.getMatches(entities[i], resolver, geometry));
                for (var j = 0; j < matches.length; j++) {
                    _features[matches[j]].count++;
                }
            }
        }

        currHidden = features.hidden();
        if (currHidden !== _hidden) {
            _hidden = currHidden;
            needsRedraw = true;
            dispatch.change();
        }

        return needsRedraw;
    };

    features.stats = function() {
        _.each(_keys, function(k) { _stats[k] = _features[k].count; });
        return _stats;
    };

    features.clear = function(d) {
        for (var i = 0; i < d.length; i++) {
            features.clearEntity(d[i]);
        }
    };

    features.clearEntity = function(entity) {
        delete _cache[iD.Entity.key(entity)];
    };

    features.reset = function() {
        _cache = {};
    };

    features.getMatches = function(entity, resolver, geometry) {
        if (geometry === 'vertex' || geometry === 'relation') return {};

        var ent = iD.Entity.key(entity),
            matches = {},
            hasMatch = false;

        if (!_cache[ent]) {
            _cache[ent] = {};
        }
        if (!_cache[ent].matches) {
            if (_.isEmpty(entity.tags)) {
                matches.others = true;
            }
            else {
                for (var i = 0; i < _keys.length; i++) {
                    if (_keys[i] === 'others') {
                        if (hasMatch) continue;

                        // // If the entity is a way that has not matched any other
                        // // feature type, see if it has a parent relation, and if so,
                        // // match whatever feature types the parent has matched.
                        // // (The way is a member of a multipolygon.)
                        // //
                        // // IMPORTANT:
                        // // For this to work, getMatches must be called on relations before ways.
                        // //
                        // if (entity.type === 'way') {
                        //     var parents = features.getParents(entity, resolver, geometry);
                        //     if (parents.length === 1) {
                        //         var pkey = iD.Entity.key(parents[0]);
                        //         if (_cache[pkey] && _cache[pkey].matches) {
                        //             matches = _.clone(_cache[pkey].matches);
                        //             continue;
                        //         }
                        //     }
                        // }
                    }

                    if (_features[_keys[i]].filter(entity, resolver, geometry)) {
                        matches[_keys[i]] = hasMatch = true;
                    }
                }
            }
            _cache[ent].matches = matches;
        }

        return _cache[ent].matches;
    };

    features.getParents = function(entity, resolver, geometry) {
        var ent = iD.Entity.key(entity);

        if (!_cache[ent]) {
            _cache[ent] = {};
        }
        if (!_cache[ent].parents) {
            var parents = [];

            if (geometry !== 'point') {
                if (geometry === 'vertex') {
                    parents = resolver.parentWays(entity);
                } else {   // 'line', 'area', 'relation'
                    parents = resolver.parentRelations(entity);
                }
            }
            _cache[ent].parents = parents;
        }
        return _cache[ent].parents;
    };

    features.isHiddenFeature = function(entity, resolver, geometry) {
        if (!entity.version) return false;

        var matches = features.getMatches(entity, resolver, geometry);

        for (var i = 0; i < _hidden.length; i++) {
            if (matches[_hidden[i]]) { return true; }
        }
        return false;
    };

    features.isHiddenChild = function(entity, resolver, geometry) {
        if (!entity.version || geometry === 'point') { return false; }

        var parents = features.getParents(entity, resolver, geometry);

        if (!parents.length) { return false; }

        for (var i = 0; i < parents.length; i++) {
            if (!features.isHidden(parents[i], resolver, parents[i].geometry(resolver))) {
                return false;
            }
        }
        return true;
    };

    features.hasHiddenConnections = function(entity, resolver) {
        var childNodes, connections;

        if (entity.type === 'midpoint') {
            childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
            connections = [];
        } else {
            childNodes = entity.nodes ? resolver.childNodes(entity) : [];
            connections = features.getParents(entity, resolver, entity.geometry(resolver));
        }

        // gather ways connected to child nodes..
        connections = _.reduce(childNodes, function(result, e) {
            return resolver.isShared(e) ? _.union(result, resolver.parentWays(e)) : result;
        }, connections);

        return connections.length ? _.any(connections, function(e) {
            return features.isHidden(e, resolver, e.geometry(resolver));
        }) : false;
    };

    features.isHidden = function(entity, resolver, geometry) {
        if (!entity.version) return false;

        if (geometry === 'vertex')
            return features.isHiddenChild(entity, resolver, geometry);
        if (geometry === 'point')
            return features.isHiddenFeature(entity, resolver, geometry);

        return features.isHiddenFeature(entity, resolver, geometry) ||
               features.isHiddenChild(entity, resolver, geometry);
    };

    features.filter = function(d, resolver) {
        if (!_hidden.length)
            return d;

        var result = [];
        for (var i = 0; i < d.length; i++) {
            var entity = d[i];
            if (!features.isHidden(entity, resolver, entity.geometry(resolver))) {
                result.push(entity);
            }
        }
        return result;
    };

    return d3.rebind(features, dispatch, 'on');
};
<|endoftext|>


<commit_before>const Clip = require('../../../entities/Clip').Clip;

const AppContext = require('../../../AppContext');
const ClipPublisher = require('../../../publishers/ClipPublisher');

const AWS = require('aws-sdk');
const execSync = require('child_process').execSync;
const fs = require('fs');
const {URL} = require('url');
const path = require('path');
const md5 = require('md5');
const uuid = require('uuid/v1');

fs.readFileAsync = (filename) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
};

class Raa1ClipPublisher extends ClipPublisher {
    constructor(credentialsConf) {
        super();
        // Initiate AWS connection
        if (credentialsConf.AWS) {
            this._asyncS3 = new AsyncS3(
                AppContext.getInstance().CWD + '/' + credentialsConf.AWS,
                'vod.raa.media'
            );
        } else {
            throw Error(
                'AWS config not found! Cannot proceed' + ' with ClipPublisher constructor'
            );
        }
    }

    /**
     * Merges clips to a single one and posts it somewhere available to public
     * (e.g. Public S3)
     * @param {Clip[]} clips clips to be mereged
     * @param {String} publicClipNamingStrategy states how the public clip should
     *  be named. Options are 'MainClip', 'AllClips'
     * @return {Clip} the public clip object
     */
    getPublicClip(clips, publicClipNamingStrategy = 'MainClip') {
        let wrappedClip = new WrappedClip(clips, publicClipNamingStrategy);

        AppContext.getInstance().Logger.debug(
            'S3 upload key is: ' + wrappedClip.RelativePath
        );
        if (
            !(
                AppContext.getInstance('LineupGenerator').GeneratorOptions.TestMode ||
                AppContext.getInstance('LineupGenerator').GeneratorOptions.NoVODUpload
            )
        ) {
            wrappedClip.wrap();
            try {
                let self = this;
                fs.readFileAsync(wrappedClip.AbsolutePath).then(
                    // We want to block this part only, so we create surraounding closure
                    ((w) => {
                        let uploadClosure = async function(clipData) {
                            await self._asyncS3.putObject(
                                w.RelativePath,
                                clipData
                            );
                            // Remove the temp file
                            if (w.IsWrapped) {
                                fs.unlinkSync(w.AbsolutePath);
                            }
                        };
                        return uploadClosure;
                    })(wrappedClip)
                );
            } catch (e) {
                throw Error('Error while uploading public clip. Inner exception is ' + e);
            }
        }

        return wrappedClip.PublicClip;
    }
}

class WrappedClip {
    constructor(clips, publicClipNamingStrategy) {
        this._clips = clips;
        this._publicClipNamingStrategy = publicClipNamingStrategy;

        this._absolutePath = null;
        this._relativePath = null;
        this._duration = 0;
        this._name = '';

        this._publicClip = null;

        this.initPaths();
    }

    initPaths() {
        if (this.IsWrapped) {
            this._allMediaPath = '';

            for (let clip of this._clips) {
                this._allMediaPath = this._allMediaPath + clip.Media.Path + '|';
                this._duration += clip.Media.Duration;

                if (clip.IsMainClip) {
                    this._relativePath = path
                        .dirname(clip.Media.Path)
                        .replace(
                            AppContext.getInstance('LineupGenerator').LineupManager
                                .MediaDirectory.BaseDir,
                            ''
                        )
                        .replace(/^\/+/g, ''); // also remove any '/' at the beginning
                    if (this._publicClipNamingStrategy == 'MainClip') {
                        this._name = this._relativePath.substring(
                            this._relativePath.lastIndexOf('/') + 1
                        ) + '.mp3';
                    }
                    this._publicClip = new Clip(clip);
                }
            }

            if (this._publicClipNamingStrategy == 'AllClips') {
                this._name = md5(this._allMediaPath) + '.mp3';
            }

            this._relativePath = path.join(this._relativePath, this._name);

            // tmp filename is a uuid to prevent name clashing
            this._absolutePath = AppContext.getInstance().CWD + '/run/tmp/' + uuid();
        } else {
            this._absolutePath = this._clips[0].Media.Path;
            this._relativePath = this._clips[0].Media.Path.replace(
                AppContext.getInstance('LineupGenerator').LineupManager.MediaDirectory
                    .BaseDir,
                ''
            ).replace(/^\/+/g, ''); // also remove any '/' at the beginning
            this._name = this._relativePath.substring(
                this._relativePath.lastIndexOf('/') + 1
            );
            this._duration = this._clips[0].Media.Duration;

            this._publicClip = new Clip(this._clips[0]);
        }

        let vodUrl = new URL(this._relativePath, 'http://vod.raa.media/');
        vodUrl =
            'https://api.raa.media/linkgenerator/podcast.mp3?src=' +
            Buffer.from(vodUrl.toString()).toString('base64');
        // Return the clip
        this._publicClip.Media.Path = vodUrl;
        this._publicClip.Media.Duration = this._duration;
    }

    wrap() {
        if (this.IsWrapped) {
            let wrapCmd =
                'echo y | ffmpeg -i "concat:' +
                this._allMediaPath +
                '" -ac 2 ' +
                this._absolutePath +
                ' 2>&1 >/dev/null';

            try {
                if (AppContext.getInstance('LineupGenerator').GeneratorOptions.TestMode) {
                    AppContext.getInstance().Logger.debug(
                        'Clip wrapping cmd is: ' + wrapCmd
                    );
                } else {
                    AppContext.getInstance().Logger.info(
                        `Resource intensive process is starting: ${wrapCmd}`
                    );
                    execSync(wrapCmd);
                    AppContext.getInstance().Logger.info('External process finished.');
                }
            } catch (error) {
                throw Error(
                    'Merging clips was unsuccessful. Error was: ' + error.message
                );
            }
        }
    }

    get PublicClip() {
        return this._publicClip;
    }

    get AbsolutePath() {
        return this._absolutePath;
    }

    get RelativePath() {
        return this._relativePath;
    }

    get Name() {
        return this._name;
    }

    get Duration() {
        return this._duration;
    }

    get IsWrapped() {
        return this._clips.length > 1 ? true : false;
    }
}

class AsyncS3 {
    constructor(confPath, bucket) {
        AWS.config.loadFromPath(confPath);
        this._s3 = new AWS.S3();
        this._bucket = bucket;
    }

    exists(key) {
        let params = {
            Bucket: this._bucket,
            Key: key,
        };

        return new Promise((resolve, reject) => {
            this._s3.headObject(params, (err, metadata) => {
                if (err && err.code === 'NotFound') {
                    resolve(false); // Not found
                } else if (!err) {
                    resolve(true); // found
                } else {
                    reject(err); // error!
                }
            });
        });
    }

    putObject(key, data) {
        let params = {
            Bucket: this._bucket,
            Key: key,
        };

        params.Body = data;
        return new Promise((resolve, reject) => {
            this._s3.putObject(params, (err, data) => {
                if (err) {
                    AppContext.getInstance().Logger.error(
                        'Error uploading file to S3. Error is: ' + err
                    );
                    reject(err);
                } else {
                    resolve(data);
                }
            });
        });
    }
}

module.exports = Raa1ClipPublisher;
<commit_msg>bug fix<commit_after>const Clip = require('../../../entities/Clip').Clip;

const AppContext = require('../../../AppContext');
const ClipPublisher = require('../../../publishers/ClipPublisher');

const AWS = require('aws-sdk');
const execSync = require('child_process').execSync;
const fs = require('fs');
const {URL} = require('url');
const path = require('path');
const md5 = require('md5');
const uuid = require('uuid/v1');

fs.readFileAsync = (filename) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
};

class Raa1ClipPublisher extends ClipPublisher {
    constructor(credentialsConf) {
        super();
        // Initiate AWS connection
        if (credentialsConf.AWS) {
            this._asyncS3 = new AsyncS3(
                AppContext.getInstance().CWD + '/' + credentialsConf.AWS,
                'vod.raa.media'
            );
        } else {
            throw Error(
                'AWS config not found! Cannot proceed' + ' with ClipPublisher constructor'
            );
        }
    }

    /**
     * Merges clips to a single one and posts it somewhere available to public
     * (e.g. Public S3)
     * @param {Clip[]} clips clips to be mereged
     * @param {String} publicClipNamingStrategy states how the public clip should
     *  be named. Options are 'MainClip', 'AllClips'
     * @return {Clip} the public clip object
     */
    getPublicClip(clips, publicClipNamingStrategy = 'MainClip') {
        let wrappedClip = new WrappedClip(clips, publicClipNamingStrategy);

        AppContext.getInstance().Logger.debug(
            'S3 upload key is: ' + wrappedClip.RelativePath
        );
        if (
            !(
                AppContext.getInstance('LineupGenerator').GeneratorOptions.TestMode ||
                AppContext.getInstance('LineupGenerator').GeneratorOptions.NoVODUpload
            )
        ) {
            wrappedClip.wrap();
            try {
                let self = this;
                fs.readFileAsync(wrappedClip.AbsolutePath).then(
                    // We want to block this part only, so we create surraounding closure
                    ((w) => {
                        let uploadClosure = async function(clipData) {
                            await self._asyncS3.putObject(w.RelativePath, clipData);
                            // Remove the temp file
                            if (w.IsWrapped) {
                                fs.unlinkSync(w.AbsolutePath);
                            }
                        };
                        return uploadClosure;
                    })(wrappedClip)
                );
            } catch (e) {
                throw Error('Error while uploading public clip. Inner exception is ' + e);
            }
        }

        return wrappedClip.PublicClip;
    }
}

class WrappedClip {
    constructor(clips, publicClipNamingStrategy) {
        this._clips = clips;
        this._publicClipNamingStrategy = publicClipNamingStrategy;

        this._absolutePath = null;
        this._relativePath = null;
        this._duration = 0;
        this._name = '';

        this._publicClip = null;

        this.initPaths();
    }

    initPaths() {
        if (this.IsWrapped) {
            this._allMediaPath = '';

            for (let clip of this._clips) {
                this._allMediaPath = this._allMediaPath + clip.Media.Path + '|';
                this._duration += clip.Media.Duration;

                if (clip.IsMainClip) {
                    this._relativePath = path
                        .dirname(clip.Media.Path)
                        .replace(
                            AppContext.getInstance('LineupGenerator').LineupManager
                                .MediaDirectory.BaseDir,
                            ''
                        )
                        .replace(/^\/+/g, ''); // also remove any '/' at the beginning
                    if (this._publicClipNamingStrategy == 'MainClip') {
                        this._name =
                            this._relativePath.substring(
                                this._relativePath.lastIndexOf('/') + 1
                            ) + '.mp3';
                    }
                    this._publicClip = new Clip(clip);
                }
            }

            if (this._publicClipNamingStrategy == 'AllClips') {
                this._name = md5(this._allMediaPath) + '.mp3';
            }

            this._relativePath = path.join(this._relativePath, this._name);

            // tmp filename is a uuid to prevent name clashing
            this._absolutePath =
                AppContext.getInstance().CWD + '/run/tmp/' + uuid() + '.mp3';
        } else {
            this._absolutePath = this._clips[0].Media.Path;
            this._relativePath = this._clips[0].Media.Path.replace(
                AppContext.getInstance('LineupGenerator').LineupManager.MediaDirectory
                    .BaseDir,
                ''
            ).replace(/^\/+/g, ''); // also remove any '/' at the beginning
            this._name = this._relativePath.substring(
                this._relativePath.lastIndexOf('/') + 1
            );
            this._duration = this._clips[0].Media.Duration;

            this._publicClip = new Clip(this._clips[0]);
        }

        let vodUrl = new URL(this._relativePath, 'http://vod.raa.media/');
        vodUrl =
            'https://api.raa.media/linkgenerator/podcast.mp3?src=' +
            Buffer.from(vodUrl.toString()).toString('base64');
        // Return the clip
        this._publicClip.Media.Path = vodUrl;
        this._publicClip.Media.Duration = this._duration;
    }

    wrap() {
        if (this.IsWrapped) {
            let wrapCmd =
                'echo y | ffmpeg -i "concat:' +
                this._allMediaPath +
                '" -ac 2 ' +
                this._absolutePath +
                ' 2>&1 >/dev/null';

            try {
                if (AppContext.getInstance('LineupGenerator').GeneratorOptions.TestMode) {
                    AppContext.getInstance().Logger.debug(
                        'Clip wrapping cmd is: ' + wrapCmd
                    );
                } else {
                    AppContext.getInstance().Logger.info(
                        `Resource intensive process is starting: ${wrapCmd}`
                    );
                    execSync(wrapCmd);
                    AppContext.getInstance().Logger.info('External process finished.');
                }
            } catch (error) {
                throw Error(
                    'Merging clips was unsuccessful. Error was: ' + error.message
                );
            }
        }
    }

    get PublicClip() {
        return this._publicClip;
    }

    get AbsolutePath() {
        return this._absolutePath;
    }

    get RelativePath() {
        return this._relativePath;
    }

    get Name() {
        return this._name;
    }

    get Duration() {
        return this._duration;
    }

    get IsWrapped() {
        return this._clips.length > 1 ? true : false;
    }
}

class AsyncS3 {
    constructor(confPath, bucket) {
        AWS.config.loadFromPath(confPath);
        this._s3 = new AWS.S3();
        this._bucket = bucket;
    }

    exists(key) {
        let params = {
            Bucket: this._bucket,
            Key: key,
        };

        return new Promise((resolve, reject) => {
            this._s3.headObject(params, (err, metadata) => {
                if (err && err.code === 'NotFound') {
                    resolve(false); // Not found
                } else if (!err) {
                    resolve(true); // found
                } else {
                    reject(err); // error!
                }
            });
        });
    }

    putObject(key, data) {
        let params = {
            Bucket: this._bucket,
            Key: key,
        };

        params.Body = data;
        return new Promise((resolve, reject) => {
            this._s3.putObject(params, (err, data) => {
                if (err) {
                    AppContext.getInstance().Logger.error(
                        'Error uploading file to S3. Error is: ' + err
                    );
                    reject(err);
                } else {
                    resolve(data);
                }
            });
        });
    }
}

module.exports = Raa1ClipPublisher;
<|endoftext|>


<commit_before>"use strict";!function(t){t.fn.circliful=function(e,o){var r=t.extend({foregroundColor:"#3498DB",backgroundColor:"#ccc",pointColor:"none",fillColor:"none",foregroundBorderWidth:15,backgroundBorderWidth:15,pointSize:28.5,fontColor:"#aaa",percent:75,animation:1,animationStep:5,icon:"none",iconSize:"30",iconColor:"#ccc",iconPosition:"top",target:0,start:0,showPercent:1,percentageTextSize:22,textAdditionalCss:"",targetPercent:0,targetTextSize:17,targetColor:"#2980B9",text:null,textStyle:null,textColor:"#666",multiPercentage:0,percentages:null,textBelow:!1},e);return this.each(function(){var e,n,i,l=t(this),c=r.percent,a=83,s=100,d=110,x=100,g=r.backgroundBorderWidth;"bottom"==r.iconPosition?(a=124,d=95):"left"==r.iconPosition?(s=80,a=110,x=117):"middle"==r.iconPosition?1==r.multiPercentage?(console.log("object"==typeof r.percentages),"object"==typeof r.percentages?g=30:(a=110,n='<g stroke="'+("none"!=r.backgroundColor?r.backgroundColor:"#ccc")+'" ><line x1="133" y1="50" x2="140" y2="40" stroke-width="2"  /></g>',n+='<g stroke="'+("none"!=r.backgroundColor?r.backgroundColor:"#ccc")+'" ><line x1="140" y1="40" x2="200" y2="40" stroke-width="2"  /></g>',x=228,d=47)):(a=110,n='<g stroke="'+("none"!=r.backgroundColor?r.backgroundColor:"#ccc")+'" ><line x1="133" y1="50" x2="140" y2="40" stroke-width="2"  /></g>',n+='<g stroke="'+("none"!=r.backgroundColor?r.backgroundColor:"#ccc")+'" ><line x1="140" y1="40" x2="200" y2="40" stroke-width="2"  /></g>',x=175,d=35):"right"==r.iconPosition&&(s=120,a=110,x=80),r.targetPercent>0&&(d=95,n='<g stroke="'+("none"!=r.backgroundColor?r.backgroundColor:"#ccc")+'" ><line x1="75" y1="101" x2="125" y2="101" stroke-width="1"  /></g>',n+='<text text-anchor="middle" x="'+x+'" y="120" style="font-size: '+r.targetTextSize+'px;" fill="'+r.targetColor+'">'+r.targetPercent+"%</text>",n+='<circle cx="100" cy="100" r="69" fill="none" stroke="'+r.backgroundColor+'" stroke-width="3" stroke-dasharray="450" transform="rotate(-90,100,100)" />',n+='<circle cx="100" cy="100" r="69" fill="none" stroke="'+r.targetColor+'" stroke-width="3" stroke-dasharray="'+3.6*r.targetPercent+', 20000" transform="rotate(-90,100,100)" />'),null!=r.text&&(r.textBelow?n+='<text text-anchor="middle" x="100" y="190" style="'+r.textStyle+'" fill="'+r.textColor+'">'+r.text+"</text>":0==r.multiPercentage?n+='<text text-anchor="middle" x="100" y="125" style="'+r.textStyle+'" fill="'+r.textColor+'">'+r.text+"</text>":1==r.multiPercentage&&(n+='<text text-anchor="middle" x="228" y="65" style="'+r.textStyle+'" fill="'+r.textColor+'">'+r.text+"</text>")),"none"!=r.icon&&(i='<text text-anchor="middle" x="'+s+'" y="'+a+'" class="icon" style="font-size: '+r.iconSize+'px" fill="'+r.iconColor+'">&#x'+r.icon+"</text>"),l.addClass("svg-container").append(t('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 194 186" class="circliful">'+n+'<circle cx="100" cy="100" r="57" class="border" fill="'+r.fillColor+'" stroke="'+r.backgroundColor+'" stroke-width="'+g+'" stroke-dasharray="360" transform="rotate(-90,100,100)" /><circle class="circle" cx="100" cy="100" r="57" class="border" fill="none" stroke="'+r.foregroundColor+'" stroke-width="'+r.foregroundBorderWidth+'" stroke-dasharray="0,20000" transform="rotate(-90,100,100)" /><circle cx="100" cy="100" r="'+r.pointSize+'" fill="'+r.pointColor+'" />'+i+'<text class="timer" text-anchor="middle" x="'+x+'" y="'+d+'" style="font-size: '+r.percentageTextSize+"px; "+e+";"+r.textAdditionalCss+'" fill="'+r.fontColor+'">0%</text>'));var y=l.find(".circle"),f=l.find(".timer"),k=30,u=0,h=r.animationStep,C=0,m=0,p=0;if(r.start>0&&r.target>0&&(c=r.start/(r.target/100),p=r.target/100),1==r.animation)var w=window.setInterval(function(){u>=3.6*c?(window.clearInterval(w),C=1,o()):(u+=h,m+=p),u/3.6>=c&&1==C&&(u=3.6*c),m>r.target&&1==C&&(m=r.target),y.attr("stroke-dasharray",u+", 20000"),1==r.showPercent?f.text(parseInt(u/360*100)+"%"):f.text(m)}.bind(y),k);else y.attr("stroke-dasharray",3.6*c+", 20000"),1==r.showPercent?f.text(c+"%"):f.text(r.target)})}}(jQuery)
<commit_msg>I get an Uncaught TypeError with new version #95<commit_after>"use strict";!function(a){a.fn.circliful=function(b,c){var d=a.extend({foregroundColor:"#3498DB",backgroundColor:"#ccc",pointColor:"none",fillColor:"none",foregroundBorderWidth:15,backgroundBorderWidth:15,pointSize:28.5,fontColor:"#aaa",percent:75,animation:1,animationStep:5,icon:"none",iconSize:"30",iconColor:"#ccc",iconPosition:"top",target:0,start:0,showPercent:1,percentageTextSize:22,textAdditionalCss:"",targetPercent:0,targetTextSize:17,targetColor:"#2980B9",text:null,textStyle:null,textColor:"#666",multiPercentage:0,percentages:null,textBelow:!1},b);return this.each(function(){var j,k,l,b=a(this),e=d.percent,f=83,g=100,h=110,i=100,m=d.backgroundBorderWidth;"bottom"==d.iconPosition?(f=124,h=95):"left"==d.iconPosition?(g=80,f=110,i=117):"middle"==d.iconPosition?1==d.multiPercentage?(console.log("object"==typeof d.percentages),"object"==typeof d.percentages?m=30:(f=110,k='<g stroke="'+("none"!=d.backgroundColor?d.backgroundColor:"#ccc")+'" ><line x1="133" y1="50" x2="140" y2="40" stroke-width="2"  /></g>',k+='<g stroke="'+("none"!=d.backgroundColor?d.backgroundColor:"#ccc")+'" ><line x1="140" y1="40" x2="200" y2="40" stroke-width="2"  /></g>',i=228,h=47)):(f=110,k='<g stroke="'+("none"!=d.backgroundColor?d.backgroundColor:"#ccc")+'" ><line x1="133" y1="50" x2="140" y2="40" stroke-width="2"  /></g>',k+='<g stroke="'+("none"!=d.backgroundColor?d.backgroundColor:"#ccc")+'" ><line x1="140" y1="40" x2="200" y2="40" stroke-width="2"  /></g>',i=175,h=35):"right"==d.iconPosition&&(g=120,f=110,i=80),d.targetPercent>0&&(h=95,k='<g stroke="'+("none"!=d.backgroundColor?d.backgroundColor:"#ccc")+'" ><line x1="75" y1="101" x2="125" y2="101" stroke-width="1"  /></g>',k+='<text text-anchor="middle" x="'+i+'" y="120" style="font-size: '+d.targetTextSize+'px;" fill="'+d.targetColor+'">'+d.targetPercent+"%</text>",k+='<circle cx="100" cy="100" r="69" fill="none" stroke="'+d.backgroundColor+'" stroke-width="3" stroke-dasharray="450" transform="rotate(-90,100,100)" />',k+='<circle cx="100" cy="100" r="69" fill="none" stroke="'+d.targetColor+'" stroke-width="3" stroke-dasharray="'+3.6*d.targetPercent+', 20000" transform="rotate(-90,100,100)" />'),null!=d.text&&(d.textBelow?k+='<text text-anchor="middle" x="100" y="190" style="'+d.textStyle+'" fill="'+d.textColor+'">'+d.text+"</text>":0==d.multiPercentage?k+='<text text-anchor="middle" x="100" y="125" style="'+d.textStyle+'" fill="'+d.textColor+'">'+d.text+"</text>":1==d.multiPercentage&&(k+='<text text-anchor="middle" x="228" y="65" style="'+d.textStyle+'" fill="'+d.textColor+'">'+d.text+"</text>")),"none"!=d.icon&&(l='<text text-anchor="middle" x="'+g+'" y="'+f+'" class="icon" style="font-size: '+d.iconSize+'px" fill="'+d.iconColor+'">&#x'+d.icon+"</text>"),b.addClass("svg-container").append(a('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 194 186" class="circliful">'+k+'<circle cx="100" cy="100" r="57" class="border" fill="'+d.fillColor+'" stroke="'+d.backgroundColor+'" stroke-width="'+m+'" stroke-dasharray="360" transform="rotate(-90,100,100)" /><circle class="circle" cx="100" cy="100" r="57" class="border" fill="none" stroke="'+d.foregroundColor+'" stroke-width="'+d.foregroundBorderWidth+'" stroke-dasharray="0,20000" transform="rotate(-90,100,100)" /><circle cx="100" cy="100" r="'+d.pointSize+'" fill="'+d.pointColor+'" />'+l+'<text class="timer" text-anchor="middle" x="'+i+'" y="'+h+'" style="font-size: '+d.percentageTextSize+"px; "+j+";"+d.textAdditionalCss+'" fill="'+d.fontColor+'">0%</text>'));var n=b.find(".circle"),o=b.find(".timer"),p=30,q=0,r=d.animationStep,s=0,t=0,u=0;if(d.start>0&&d.target>0&&(e=d.start/(d.target/100),u=d.target/100),1==d.animation)var v=window.setInterval(function(){q>=3.6*e?(window.clearInterval(v),s=1,"function"==typeof c&&c.call(this)):(q+=r,t+=u),q/3.6>=e&&1==s&&(q=3.6*e),t>d.target&&1==s&&(t=d.target),n.attr("stroke-dasharray",q+", 20000"),1==d.showPercent?o.text(parseInt(q/360*100)+"%"):o.text(t)}.bind(n),p);else n.attr("stroke-dasharray",3.6*e+", 20000"),1==d.showPercent?o.text(e+"%"):o.text(d.target)})}}(jQuery);
<|endoftext|>