<commit_before>/**
* @license AGPL-3.0
* @author aAXEe (https://github.com/aAXEe)
* @author mojoaxel (https://github.com/mojoaxel
*/
'use strict'

import React, { PropTypes } from 'react'
import ol from 'openlayers'
import { positionsEqual } from './utils'

import MetaControl from './controls/metaControl/MetaControl'
import Sidebar from './controls/sidebar/Sidebar'

import controlIds from './controls/ol3/controls'
import OL3Attribution from './controls/ol3/OL3Attribution'
import OL3Fullscreen from './controls/ol3/OL3Fullscreen'
import OL3ScaleLine from './controls/ol3/OL3ScaleLine'
import OL3Zoom from './controls/ol3/OL3Zoom'

import { alwaysOnControls } from './SETTINGS'
import { Tabs } from './features/tabs'

class Ol3Map extends React.Component {

  constructor (props) {
    super(props)

    this._controls = new Map()
    this.forceSetViewPosition = false
  }

  componentDidMount () {
    let self = this
    var layers = []
    let interactiveLayers = []

    this.context.layers.forEach((layer) => {
      layer.layer.setVisible(!!(this.props.layerVisible[layer.id]))
      layers.push(layer.layer)

      if (layer.isInteractive) {
        interactiveLayers.push(layer.layer)
      }
    })

    var interactions = ol.interaction.defaults({
      altShiftDragRotate: false,
      pinchRotate: false
    })

    this.selector = new ol.interaction.Select({
      layers: interactiveLayers,
      toggleCondition: ol.events.condition.never,
      style: []
    })
    interactions.push(this.selector)

    this.hoverer = new ol.interaction.Select({
      layers: interactiveLayers,
      condition: ol.events.condition.pointerMove,
      toggleCondition: ol.events.condition.never,
      style: (feature, resolution) => {
        let featureStyleFunc = feature.getStyleFunction()
        if (featureStyleFunc) return featureStyleFunc(feature, resolution)
        let featureStyle = feature.getStyle()
        if (featureStyle) return featureStyle

        let layer = self.hoverer.getLayer(feature)
        if (!layer) return
        let layerStyleFunc = layer.getStyleFunction()
        if (layerStyleFunc) return layerStyleFunc(feature, resolution)
        return layer.getStyle()
      }
    })
    interactions.push(this.hoverer)

    this.ol3Map = new ol.Map({
      target: this._input,
      controls: [],
      layers: layers,
      interactions: interactions,
      loadTilesWhileAnimating: true,
      loadTilesWhileInteracting: true,
      view: new ol.View({
        center: ol.proj.fromLonLat([
          this.props.viewPosition.position.lon,
          this.props.viewPosition.position.lat
        ]),
        zoom: this.props.viewPosition.position.zoom
      })
    })

    let featureLayerMapSelected = {}
    this.selectFeature = (feature, layer) => {
      featureLayerMapSelected[ol.getUid(feature)] = layer

      const event = {
        type: 'selectFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      let f = this.selector.getFeatures().remove(feature)
      if (f) this.selector.getFeatures().push(f)
    }
    this.unselectFeature = (feature) => {
      let layer = featureLayerMapSelected[ol.getUid(feature)]
      if (!layer) return
      delete featureLayerMapSelected[ol.getUid(feature)]

      const event = {
        type: 'unselectFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      this.selector.getFeatures().remove(feature)
    }

    this.selector.on('select', function (e) {
      e.deselected.forEach((selectedFeature) => {
        self.unselectFeature(selectedFeature)
      })
      e.selected.forEach((selectedFeature) => {
        let layer = this.getLayer(selectedFeature)
        self.selectFeature(selectedFeature, layer)
      })
      return false
    })

    let featureLayerMapHovered = {}
    this.hoverFeature = (feature, layer) => {
      featureLayerMapHovered[ol.getUid(feature)] = layer

      const event = {
        type: 'hoverFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      let f = this.hoverer.getFeatures().remove(feature)
      if (f) this.hoverer.getFeatures().push(f)
    }
    this.unhoverFeature = (feature) => {
      let layer = featureLayerMapHovered[ol.getUid(feature)]
      if (!layer) return
      delete featureLayerMapHovered[ol.getUid(feature)]

      const event = {
        type: 'unhoverFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      this.hoverer.getFeatures().remove(feature)
    }

    this.hoverer.on('select', function (e) {
      e.deselected.forEach((selectedFeature) => {
        self.unhoverFeature(selectedFeature)
      })
      e.selected.forEach((selectedFeature) => {
        let layer = this.getLayer(selectedFeature)
        self.hoverFeature(selectedFeature, layer)
      })
      return false
    })

    /* add meta control to the map */
    this.ol3Map.addControl(new ol.control.Control({
      element: this._metaControl.getDomNode(),
      target: this.ol3Map.getTargetElement()
    }))

    this.ol3Map.on('moveend', () => {
      this.ol3Map.beforeRender()

      var centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
      let position = {
        lon: centre[0],
        lat: centre[1],
        zoom: this.ol3Map.getView().getZoom()
      }
      if (!this.forceSetViewPosition &&
        this.props.viewPosition.position &&
        positionsEqual(position, this.props.viewPosition.position)) {
        return
      }
      this.props.onViewPositionChange(position)
      this.forceSetViewPosition = false
    })

    this.updateLayerVisible(this.props)
  }

  componentWillReceiveProps (nextProps) {
    this.updateLayerVisible(nextProps)

    var centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
    let position = {
      lon: centre[0],
      lat: centre[1],
      zoom: this.ol3Map.getView().getZoom()
    }
    if (nextProps.viewPosition.position && !positionsEqual(nextProps.viewPosition.position, position)) {
      let view = this.ol3Map.getView()
      this.setupMoveAnimations()

      view.setCenter(ol.proj.fromLonLat([
        nextProps.viewPosition.position.lon,
        nextProps.viewPosition.position.lat
      ]))
      view.setZoom(nextProps.viewPosition.position.zoom)
    }

    if (nextProps.viewPosition.extent && nextProps.viewPosition.extent !== this.props.viewPosition.extent) {
      if (nextProps.viewPosition.extent && nextProps.viewPosition.extent.length === 4) {
        let mapSize = this.ol3Map.getSize()
        let width = mapSize[0]
        let height = mapSize[1]
        let options = {
          padding: [height / 4, width / 4, height / 4, width / 4],
          maxZoom: 18
        }
        this.setupMoveAnimations()
        this.forceSetViewPosition = true
        this.ol3Map.getView().fit(nextProps.viewPosition.extent, mapSize, options)
        // get view position after move
        centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
        const newPosition = {
          lon: centre[0],
          lat: centre[1],
          zoom: this.ol3Map.getView().getZoom()
        }
        if (positionsEqual(position, newPosition)) { // manually trigger the moveend
          this.ol3Map.dispatchEvent({type: 'moveend', target: this})
        }
      }
    }
  }

  addControlToMap (id, control) {
    this._controls.set(id, control)
  }

  setupMoveAnimations () {
    let view = this.ol3Map.getView()
    let start = +new Date()
    let pan = ol.animation.pan({
      duration: 1000,
      easing: ol.easing.inAndOut,
      source: view.getCenter(),
      start: start
    })
    var bounce = ol.animation.zoom({
      duration: 1000,
      easing: ol.easing.inAndOut,
      resolution: view.getResolution(),
      start: start
    })
    this.ol3Map.beforeRender(pan, bounce)
  }

  updateLayerVisible (nextProps) {
    let self = this
    let requestedControlsIds = new Set(alwaysOnControls)
    this.context.layers.forEach((layer) => {
      const layerVisibleNew = !!(nextProps.layerVisible[layer.id])
      const layerVisibleOld = layer.layer.getVisible()

      if (layerVisibleNew && layer.additionalControls) {
        layer.additionalControls.forEach((id) => {
          requestedControlsIds.add(id)
        })
      }

      if (layerVisibleOld === layerVisibleNew) return

      layer.layer.setVisible(layerVisibleNew)
      if (!layerVisibleNew && layer.isInteractive) {
        layer.layer.getSource().getFeatures().forEach((feature) => {
          self.unselectFeature(feature)
          self.unhoverFeature(feature)
        })
      }
    })

    for (var [id, control] of this._controls) {
      const map = requestedControlsIds.has(id) ? this.ol3Map : null
      control.setMap(map)
    }
  }

  render () {
    let names = new Set()
    let additionalTabs = []
    this.context.layers.forEach(layer => {
      if (!this.props.layerVisible[layer.id]) return
      if (!layer.additionalTab) return
      if (names.has(layer.additionalTab.name)) return
      additionalTabs.push(layer.additionalTab)
      names.add(layer.additionalTab.name)
    })
    return (
      <div
        className='sidebar-map'
        ref={(c) => { this._input = c }}>
        <MetaControl ref={(c) => { this._metaControl = c }}>
          <Sidebar
            id='sidebar'
            position='sidebar left'
            tabs={Tabs.concat(additionalTabs)} />
          <OL3Attribution
            id={controlIds.attribution}
            position='bottom right'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3Fullscreen
            id={controlIds.fullscreen}
            position='top right'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3Zoom
            id={controlIds.zoom}
            position='top left'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3ScaleLine
            id={controlIds.scaleline_metric}
            position='bottom left'
            units='metric'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3ScaleLine
            id={controlIds.scaleline_nautical}
            position='bottom left'
            units='nautical'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
        </MetaControl>
      </div>
    )
  }
}

Ol3Map.propTypes = {
  layerVisible: PropTypes.objectOf(PropTypes.bool).isRequired,
  onViewPositionChange: PropTypes.func.isRequired,
  viewPosition: PropTypes.shape({
    position: PropTypes.objectOf(PropTypes.number),
    extent: PropTypes.arrayOf(PropTypes.number)
  }).isRequired
}

import { LayerType } from './config/chartlayer'

Ol3Map.contextTypes = {
  layers: PropTypes.arrayOf(LayerType)
}

export default Ol3Map
<commit_msg>remove calls to private ol.getUid()<commit_after>/**
* @license AGPL-3.0
* @author aAXEe (https://github.com/aAXEe)
* @author mojoaxel (https://github.com/mojoaxel
*/
'use strict'

import React, { PropTypes } from 'react'
import ol from 'openlayers'
import { positionsEqual } from './utils'

import MetaControl from './controls/metaControl/MetaControl'
import Sidebar from './controls/sidebar/Sidebar'

import controlIds from './controls/ol3/controls'
import OL3Attribution from './controls/ol3/OL3Attribution'
import OL3Fullscreen from './controls/ol3/OL3Fullscreen'
import OL3ScaleLine from './controls/ol3/OL3ScaleLine'
import OL3Zoom from './controls/ol3/OL3Zoom'

import { alwaysOnControls } from './SETTINGS'
import { Tabs } from './features/tabs'

class Ol3Map extends React.Component {

  constructor (props) {
    super(props)

    this._controls = new Map()
    this.forceSetViewPosition = false
  }

  componentDidMount () {
    let self = this
    var layers = []
    let interactiveLayers = []

    this.context.layers.forEach((layer) => {
      layer.layer.setVisible(!!(this.props.layerVisible[layer.id]))
      layers.push(layer.layer)

      if (layer.isInteractive) {
        interactiveLayers.push(layer.layer)
      }
    })

    var interactions = ol.interaction.defaults({
      altShiftDragRotate: false,
      pinchRotate: false
    })

    this.selector = new ol.interaction.Select({
      layers: interactiveLayers,
      toggleCondition: ol.events.condition.never,
      style: []
    })
    interactions.push(this.selector)

    this.hoverer = new ol.interaction.Select({
      layers: interactiveLayers,
      condition: ol.events.condition.pointerMove,
      toggleCondition: ol.events.condition.never,
      style: (feature, resolution) => {
        let featureStyleFunc = feature.getStyleFunction()
        if (featureStyleFunc) return featureStyleFunc(feature, resolution)
        let featureStyle = feature.getStyle()
        if (featureStyle) return featureStyle

        let layer = self.hoverer.getLayer(feature)
        if (!layer) return
        let layerStyleFunc = layer.getStyleFunction()
        if (layerStyleFunc) return layerStyleFunc(feature, resolution)
        return layer.getStyle()
      }
    })
    interactions.push(this.hoverer)

    this.ol3Map = new ol.Map({
      target: this._input,
      controls: [],
      layers: layers,
      interactions: interactions,
      loadTilesWhileAnimating: true,
      loadTilesWhileInteracting: true,
      view: new ol.View({
        center: ol.proj.fromLonLat([
          this.props.viewPosition.position.lon,
          this.props.viewPosition.position.lat
        ]),
        zoom: this.props.viewPosition.position.zoom
      })
    })

    let featureLayerMapSelected = {}
    this.selectFeature = (feature, layer) => {
      featureLayerMapSelected[feature.getId()] = layer

      const event = {
        type: 'selectFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      let f = this.selector.getFeatures().remove(feature)
      if (f) this.selector.getFeatures().push(f)
    }
    this.unselectFeature = (feature) => {
      let layer = featureLayerMapSelected[feature.getId()]
      if (!layer) return
      delete featureLayerMapSelected[feature.getId()]

      const event = {
        type: 'unselectFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      this.selector.getFeatures().remove(feature)
    }

    this.selector.on('select', function (e) {
      e.deselected.forEach((selectedFeature) => {
        self.unselectFeature(selectedFeature)
      })
      e.selected.forEach((selectedFeature) => {
        let layer = this.getLayer(selectedFeature)
        self.selectFeature(selectedFeature, layer)
      })
      return false
    })

    let featureLayerMapHovered = {}
    this.hoverFeature = (feature, layer) => {
      featureLayerMapHovered[feature.getId()] = layer

      const event = {
        type: 'hoverFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      let f = this.hoverer.getFeatures().remove(feature)
      if (f) this.hoverer.getFeatures().push(f)
    }
    this.unhoverFeature = (feature) => {
      let layer = featureLayerMapHovered[feature.getId()]
      if (!layer) return
      delete featureLayerMapHovered[feature.getId()]

      const event = {
        type: 'unhoverFeature',
        feature: feature,
        layer: layer
      }
      layer.dispatchEvent(event)

      this.hoverer.getFeatures().remove(feature)
    }

    this.hoverer.on('select', function (e) {
      e.deselected.forEach((selectedFeature) => {
        self.unhoverFeature(selectedFeature)
      })
      e.selected.forEach((selectedFeature) => {
        let layer = this.getLayer(selectedFeature)
        self.hoverFeature(selectedFeature, layer)
      })
      return false
    })

    /* add meta control to the map */
    this.ol3Map.addControl(new ol.control.Control({
      element: this._metaControl.getDomNode(),
      target: this.ol3Map.getTargetElement()
    }))

    this.ol3Map.on('moveend', () => {
      this.ol3Map.beforeRender()

      var centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
      let position = {
        lon: centre[0],
        lat: centre[1],
        zoom: this.ol3Map.getView().getZoom()
      }
      if (!this.forceSetViewPosition &&
        this.props.viewPosition.position &&
        positionsEqual(position, this.props.viewPosition.position)) {
        return
      }
      this.props.onViewPositionChange(position)
      this.forceSetViewPosition = false
    })

    this.updateLayerVisible(this.props)
  }

  componentWillReceiveProps (nextProps) {
    this.updateLayerVisible(nextProps)

    var centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
    let position = {
      lon: centre[0],
      lat: centre[1],
      zoom: this.ol3Map.getView().getZoom()
    }
    if (nextProps.viewPosition.position && !positionsEqual(nextProps.viewPosition.position, position)) {
      let view = this.ol3Map.getView()
      this.setupMoveAnimations()

      view.setCenter(ol.proj.fromLonLat([
        nextProps.viewPosition.position.lon,
        nextProps.viewPosition.position.lat
      ]))
      view.setZoom(nextProps.viewPosition.position.zoom)
    }

    if (nextProps.viewPosition.extent && nextProps.viewPosition.extent !== this.props.viewPosition.extent) {
      if (nextProps.viewPosition.extent && nextProps.viewPosition.extent.length === 4) {
        let mapSize = this.ol3Map.getSize()
        let width = mapSize[0]
        let height = mapSize[1]
        let options = {
          padding: [height / 4, width / 4, height / 4, width / 4],
          maxZoom: 18
        }
        this.setupMoveAnimations()
        this.forceSetViewPosition = true
        this.ol3Map.getView().fit(nextProps.viewPosition.extent, mapSize, options)
        // get view position after move
        centre = ol.proj.transform(this.ol3Map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326')
        const newPosition = {
          lon: centre[0],
          lat: centre[1],
          zoom: this.ol3Map.getView().getZoom()
        }
        if (positionsEqual(position, newPosition)) { // manually trigger the moveend
          this.ol3Map.dispatchEvent({type: 'moveend', target: this})
        }
      }
    }
  }

  addControlToMap (id, control) {
    this._controls.set(id, control)
  }

  setupMoveAnimations () {
    let view = this.ol3Map.getView()
    let start = +new Date()
    let pan = ol.animation.pan({
      duration: 1000,
      easing: ol.easing.inAndOut,
      source: view.getCenter(),
      start: start
    })
    var bounce = ol.animation.zoom({
      duration: 1000,
      easing: ol.easing.inAndOut,
      resolution: view.getResolution(),
      start: start
    })
    this.ol3Map.beforeRender(pan, bounce)
  }

  updateLayerVisible (nextProps) {
    let self = this
    let requestedControlsIds = new Set(alwaysOnControls)
    this.context.layers.forEach((layer) => {
      const layerVisibleNew = !!(nextProps.layerVisible[layer.id])
      const layerVisibleOld = layer.layer.getVisible()

      if (layerVisibleNew && layer.additionalControls) {
        layer.additionalControls.forEach((id) => {
          requestedControlsIds.add(id)
        })
      }

      if (layerVisibleOld === layerVisibleNew) return

      layer.layer.setVisible(layerVisibleNew)
      if (!layerVisibleNew && layer.isInteractive) {
        layer.layer.getSource().getFeatures().forEach((feature) => {
          self.unselectFeature(feature)
          self.unhoverFeature(feature)
        })
      }
    })

    for (var [id, control] of this._controls) {
      const map = requestedControlsIds.has(id) ? this.ol3Map : null
      control.setMap(map)
    }
  }

  render () {
    let names = new Set()
    let additionalTabs = []
    this.context.layers.forEach(layer => {
      if (!this.props.layerVisible[layer.id]) return
      if (!layer.additionalTab) return
      if (names.has(layer.additionalTab.name)) return
      additionalTabs.push(layer.additionalTab)
      names.add(layer.additionalTab.name)
    })
    return (
      <div
        className='sidebar-map'
        ref={(c) => { this._input = c }}>
        <MetaControl ref={(c) => { this._metaControl = c }}>
          <Sidebar
            id='sidebar'
            position='sidebar left'
            tabs={Tabs.concat(additionalTabs)} />
          <OL3Attribution
            id={controlIds.attribution}
            position='bottom right'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3Fullscreen
            id={controlIds.fullscreen}
            position='top right'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3Zoom
            id={controlIds.zoom}
            position='top left'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3ScaleLine
            id={controlIds.scaleline_metric}
            position='bottom left'
            units='metric'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
          <OL3ScaleLine
            id={controlIds.scaleline_nautical}
            position='bottom left'
            units='nautical'
            addControlToMap={(id, c) => this.addControlToMap(id, c)} />
        </MetaControl>
      </div>
    )
  }
}

Ol3Map.propTypes = {
  layerVisible: PropTypes.objectOf(PropTypes.bool).isRequired,
  onViewPositionChange: PropTypes.func.isRequired,
  viewPosition: PropTypes.shape({
    position: PropTypes.objectOf(PropTypes.number),
    extent: PropTypes.arrayOf(PropTypes.number)
  }).isRequired
}

import { LayerType } from './config/chartlayer'

Ol3Map.contextTypes = {
  layers: PropTypes.arrayOf(LayerType)
}

export default Ol3Map
<|endoftext|>


<commit_before>import _ from 'lodash';

export function findProjectKey(jiraClient) {
  return jiraClient.listProjects()
    .then(projects => _.find(projects,
      project => project.name === jiraClient.projectName));
}

export function getEpicLinkField(jiraClient) {
  return jiraClient.listFields()
    .then(fields => {
      for(let i = 0; i < fields.length; i++) {
        if(fields[i].name === 'Epic Link') {
          return fields[i].id;
        }
      }

      return Promise.reject('Cannot find Epic Link Field.');
    });
}

function findIssueLinkParentKey(issue, linkDirection) {
  let index = _.findIndex(issue.fields.issuelinks,
    issueLink => issueLink[linkDirection].fields.issuetype.name === 'Initiative');

  return index >= 0 ? issue.fields.issuelinks[index][linkDirection].key : null;
}

export function findParent(issue, jiraClient) {
  switch(issue.fields.issuetype.name) {
    case 'Sub-task':
      return jiraClient.findIssue(issue.fields.parent.key);

    case 'Story':
      if(issue.fields.issuelinks) {
        let parentKey = findIssueLinkParentKey(issue, 'outwardIssue');

        if(parentKey) {
          return jiraClient.findIssue(parentKey);
        }
      }

      return getEpicLinkField(jiraClient)
        .then(linkField => jiraClient.findIssue(issue.fields[linkField]));

    case 'Epic':
      let parentKey = findIssueLinkParentKey(issue, 'inwardIssue');

      return parentKey ? jiraClient.findIssue(parentKey) : Promise.reject(`Cannot find parent from Epic ${issue.key}`);

    default:
        return Promise.reject(`${issue.fields.issuetype.name} should not have a parent.`);
  }
}
<commit_msg>Fix broken test<commit_after>import _ from 'lodash';

export function findProjectKey(jiraClient) {
  return jiraClient.listProjects()
    .then(projects => _.find(projects,
        project => project.name === jiraClient.projectName).key);
}

export function getEpicLinkField(jiraClient) {
  return jiraClient.listFields()
    .then(fields => {
      for(let i = 0; i < fields.length; i++) {
        if(fields[i].name === 'Epic Link') {
          return fields[i].id;
        }
      }

      return Promise.reject('Cannot find Epic Link Field.');
    });
}

function findIssueLinkParentKey(issue, linkDirection) {
  let index = _.findIndex(issue.fields.issuelinks,
    issueLink => issueLink[linkDirection].fields.issuetype.name === 'Initiative');

  return index >= 0 ? issue.fields.issuelinks[index][linkDirection].key : null;
}

export function findParent(issue, jiraClient) {
  switch(issue.fields.issuetype.name) {
    case 'Sub-task':
      return jiraClient.findIssue(issue.fields.parent.key);

    case 'Story':
      if(issue.fields.issuelinks) {
        let parentKey = findIssueLinkParentKey(issue, 'outwardIssue');

        if(parentKey) {
          return jiraClient.findIssue(parentKey);
        }
      }

      return getEpicLinkField(jiraClient)
        .then(linkField => jiraClient.findIssue(issue.fields[linkField]));

    case 'Epic':
      let parentKey = findIssueLinkParentKey(issue, 'inwardIssue');

      return parentKey ? jiraClient.findIssue(parentKey) : Promise.reject(`Cannot find parent from Epic ${issue.key}`);

    default:
        return Promise.reject(`${issue.fields.issuetype.name} should not have a parent.`);
  }
}
<|endoftext|>


<commit_before>var Train = Backbone.Model.extend({
    idAttribute: 'train_name',

    url: function() {
        return 'api/trains/' + this.get('train_name');
    },

    matchFilter: function(filter) {
        filter = filter.toLowerCase();
        return this.get("train_name").toLowerCase().indexOf(filter) >= 0 ||
            _.some(this.get("stations"), function(s) {
                return s.toLowerCase().indexOf(filter) >= 0
            });
    },

    parse: function(train) {
        if (train.stations) {
            train.activeStation = 1;
            train.firstIntermediateStation = 1;
            train.lastIntermediateStation = train.stations.length - 2;
        }
        return train;
    },

    nextStation: function() {
        var activeStation = this.get("activeStation");
        this.set("activeStation", activeStation + 1);
    },

    prevStation: function() {
        var activeStation = this.get("activeStation");
        this.set("activeStation", activeStation - 1);
    }
});

var Trains = Backbone.Collection.extend({
    model: Train,
    url: 'api/trains',

    parse: function(resp) {
        return resp.trains;
    }
});

var MainItemView = Backbone.View.extend({
    tagName: 'tr',
    template: _.template($('#main-item-template').html()),

    events: {
        'click .prev-station': 'prevStation',
        'click .next-station': 'nextStation'
    },

    initialize: function() {
        this.listenTo(this.model, 'change', this.render);
    },

    prevStation: function() {
        this.model.prevStation();
    },

    nextStation: function() {
        this.model.nextStation();
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

var MainView = Backbone.View.extend({
    el: '#page',
    template: _.template($('#main-template').html()),
    filterTimeout: 200,
    nameFilter: '',
    nameFilterMin: 2,
    lastFilterValue: '',
    page: 0,
    trainsPerPage: 100,

    events: {
        'click .next-page': 'nextPage',
        'click .prev-page': 'prevPage',
        'keyup #filter': 'filterChanged'
    },

    activate: function() {
        if (!this.allTrains) {
            var that = this;
            this.allTrains = new Trains();
            this.allTrains.fetch({
                success: function() { that.state = 'ok'; that.render(); },
                error: function() { that.state = 'error'; that.render(); }
            });
            this.state = 'waiting';
        }
        document.title = 'InfoPasażer Archiver - archiwum opóźnień pociągów';
        this.render();
    },

    nextPage: function() {
        this.page++;
        this.renderTrains();
    },

    prevPage: function() {
        this.page--;
        this.renderTrains();
    },

    filterChanged: function(e) {
        if (e.target.value != this.nameFilter) {
            var that = this;
            this.nameFilter = e.target.value;
            clearTimeout(this.timer);
            this.timer = setTimeout(function() {
                that.page = 0;
                that.renderTrains();
            }, this.filterTimeout);
        }
    },

    renderTrains: function() {
        var that = this;
        var trainListTemplate = _.template($('#main-table-template').html());
        var trains = [];
        var pageCount = 0;

        if (this.nameFilter != '') {
            trains = this.allTrains.filter(function (train) {
                return train.matchFilter(that.nameFilter);
            });
        } else {
            trains = this.allTrains.toArray();
        }

        if (trains.length > 0) {
            pageCount = Math.ceil(trains.length / this.trainsPerPage);
            trains = _.first(_.rest(trains, this.page * this.trainsPerPage), this.trainsPerPage);
        }

        $('#main-content').html(trainListTemplate({state: this.state, page: this.page + 1, pageCount: pageCount, size: trains.length}));

        _.each(trains, function(train) {
            var view = new MainItemView({model: train});
            $('#main-table').append(view.render().el);
        });
    },

    render: function() {
        this.$el.html(this.template());
        this.renderTrains();
        return this;
    }
});

var TrainView = Backbone.View.extend({
    el: '#page',

    activate: function(name) {
        var that = this;
        var train = new Train({train_name: name});
        train.fetch({
            success: function(train) { that.render(train); },
            error: function() { that.error(); }
        });
        document.title = name;
    },

    render: function(train) {
        var template = _.template($('#train-template').html());
        this.$el.html(template(train.toJSON()));
        return this;
    },

    error: function() {
        var template = _.template($('#error-template').html());
        this.$el.html(template({msg: 'Nie ma takiego pociągu'}));
        return this;
    }
});

var Router = Backbone.Router.extend({
    routes: {
        '': 'index',
        'train/*name': 'train'
    },

    initialize: function() {
        this.mainView = new MainView();
        this.trainView = new TrainView();
    },

    index: function() {
        this.mainView.activate();
    },

    train: function(name) {
        this.trainView.activate(name);
    }
});

var router = new Router();
Backbone.history.start();
<commit_msg>preserve filter<commit_after>var Train = Backbone.Model.extend({
    idAttribute: 'train_name',

    url: function() {
        return 'api/trains/' + this.get('train_name');
    },

    matchFilter: function(filter) {
        filter = filter.toLowerCase();
        return this.get("train_name").toLowerCase().indexOf(filter) >= 0 ||
            _.some(this.get("stations"), function(s) {
                return s.toLowerCase().indexOf(filter) >= 0
            });
    },

    parse: function(train) {
        if (train.stations) {
            train.activeStation = 1;
            train.firstIntermediateStation = 1;
            train.lastIntermediateStation = train.stations.length - 2;
        }
        return train;
    },

    nextStation: function() {
        var activeStation = this.get("activeStation");
        this.set("activeStation", activeStation + 1);
    },

    prevStation: function() {
        var activeStation = this.get("activeStation");
        this.set("activeStation", activeStation - 1);
    }
});

var Trains = Backbone.Collection.extend({
    model: Train,
    url: 'api/trains',

    parse: function(resp) {
        return resp.trains;
    }
});

var MainItemView = Backbone.View.extend({
    tagName: 'tr',
    template: _.template($('#main-item-template').html()),

    events: {
        'click .prev-station': 'prevStation',
        'click .next-station': 'nextStation'
    },

    initialize: function() {
        this.listenTo(this.model, 'change', this.render);
    },

    prevStation: function() {
        this.model.prevStation();
    },

    nextStation: function() {
        this.model.nextStation();
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

var MainView = Backbone.View.extend({
    el: '#page',
    template: _.template($('#main-template').html()),
    filterTimeout: 200,
    nameFilter: '',
    nameFilterMin: 2,
    lastFilterValue: '',
    page: 0,
    trainsPerPage: 100,

    events: {
        'click .next-page': 'nextPage',
        'click .prev-page': 'prevPage',
        'keyup #filter': 'filterChanged'
    },

    activate: function() {
        if (!this.allTrains) {
            var that = this;
            this.allTrains = new Trains();
            this.allTrains.fetch({
                success: function() { that.state = 'ok'; that.render(); },
                error: function() { that.state = 'error'; that.render(); }
            });
            this.state = 'waiting';
        }
        document.title = 'InfoPasażer Archiver - archiwum opóźnień pociągów';
        this.render();
    },

    nextPage: function() {
        this.page++;
        this.renderTrains();
    },

    prevPage: function() {
        this.page--;
        this.renderTrains();
    },

    filterChanged: function(e) {
        if (e.target.value != this.nameFilter) {
            var that = this;
            this.nameFilter = e.target.value;
            clearTimeout(this.timer);
            this.timer = setTimeout(function() {
                that.page = 0;
                that.renderTrains();
            }, this.filterTimeout);
        }
    },

    renderTrains: function() {
        var that = this;
        var trainListTemplate = _.template($('#main-table-template').html());
        var trains = [];
        var pageCount = 0;

        if (this.nameFilter != '') {
            trains = this.allTrains.filter(function (train) {
                return train.matchFilter(that.nameFilter);
            });
        } else {
            trains = this.allTrains.toArray();
        }

        if (trains.length > 0) {
            pageCount = Math.ceil(trains.length / this.trainsPerPage);
            trains = _.first(_.rest(trains, this.page * this.trainsPerPage), this.trainsPerPage);
        }

        $('#main-content').html(trainListTemplate({state: this.state, page: this.page + 1, pageCount: pageCount, size: trains.length}));

        _.each(trains, function(train) {
            var view = new MainItemView({model: train});
            $('#main-table').append(view.render().el);
        });
    },

    render: function() {
        this.$el.html(this.template());
        this.renderTrains();
        $('#filter').val(this.nameFilter)
        $('#filter').focus();
        return this;
    }
});

var TrainView = Backbone.View.extend({
    el: '#page',

    activate: function(name) {
        var that = this;
        var train = new Train({train_name: name});
        train.fetch({
            success: function(train) { that.render(train); },
            error: function() { that.error(); }
        });
        document.title = name;
    },

    render: function(train) {
        var template = _.template($('#train-template').html());
        this.$el.html(template(train.toJSON()));
        return this;
    },

    error: function() {
        var template = _.template($('#error-template').html());
        this.$el.html(template({msg: 'Nie ma takiego pociągu'}));
        return this;
    }
});

var Router = Backbone.Router.extend({
    routes: {
        '': 'index',
        'train/*name': 'train'
    },

    initialize: function() {
        this.mainView = new MainView();
        this.trainView = new TrainView();
    },

    index: function() {
        this.mainView.activate();
    },

    train: function(name) {
        this.trainView.activate(name);
    }
});

var router = new Router();
Backbone.history.start();
<|endoftext|>


<commit_before>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React from 'react';
import Tile from 'grommet/components/Tile';
import Header from 'grommet/components/Header';
import Heading from 'grommet/components/Heading';
import Footer from 'grommet/components/Footer';
import Menu from 'grommet/components/Menu';
import Button from 'grommet/components/Button';
import Box from 'grommet/components/Box';

export function renderRichTiles (options={}, onClick) {
  var richTiles = [];
  var index = 1;
  while (index <= (options.count || 8)) {
    var bottom;
    if ('button' === options.bottom) {
      bottom = <Button fill={true} label="Action" onClick={onClick} />;
    } else if ('menu' === options.bottom) {
      bottom = (

          <Menu inline={false} dropAlign={{bottom: 'bottom', right: 'right'}}>
            action 1
            action 2
            action 3
          </Menu>

      );
    }

    var colorIndex;
    if (options.colorIndex) {
      if ('random' === options.colorIndex) {
        colorIndex = 'neutral-' + index;
      } else {
        colorIndex = options.colorIndex;
      }
    }

    richTiles.push(
      <Tile key={index} align="start" basis="small" flex={false}
        separator={options.separator ? 'top' : undefined}
        colorIndex={colorIndex}>

          <Heading tag="h4">{"Tile " + index}</Heading>

        <Box pad="small">

{(options.alternateContent && index % 2 === 0) ?
            'Tile summary content. One or two lines.' :
            'Tile summary content. One or two lines. Tile summary content.\
            One or two lines.'}

        </Box>
        {bottom}
      </Tile>
    );
    index += 1;
  }
  return richTiles;
}
<commit_msg>Fix Tiles example menu to use Anchor instead of plain  element. Fixes GitHub issue #153.<commit_after>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React from 'react';
import Tile from 'grommet/components/Tile';
import Header from 'grommet/components/Header';
import Heading from 'grommet/components/Heading';
import Footer from 'grommet/components/Footer';
import Menu from 'grommet/components/Menu';
import Button from 'grommet/components/Button';
import Anchor from 'grommet/components/Anchor';
import Box from 'grommet/components/Box';

export function renderRichTiles (options={}, onClick) {
  var richTiles = [];
  var index = 1;
  while (index <= (options.count || 8)) {
    var bottom;
    if ('button' === options.bottom) {
      bottom = <Button fill={true} label="Action" onClick={onClick} />;
    } else if ('menu' === options.bottom) {
      bottom = (

          <Menu inline={false} dropAlign={{bottom: 'bottom', right: 'right'}}>
            <Anchor>action 1</Anchor>
            <Anchor>action 2</Anchor>
            <Anchor>action 3</Anchor>
          </Menu>

      );
    }

    var colorIndex;
    if (options.colorIndex) {
      if ('random' === options.colorIndex) {
        colorIndex = 'neutral-' + index;
      } else {
        colorIndex = options.colorIndex;
      }
    }

    richTiles.push(
      <Tile key={index} align="start" basis="small" flex={false}
        separator={options.separator ? 'top' : undefined}
        colorIndex={colorIndex}>

          <Heading tag="h4">{"Tile " + index}</Heading>

        <Box pad="small">

{(options.alternateContent && index % 2 === 0) ?
            'Tile summary content. One or two lines.' :
            'Tile summary content. One or two lines. Tile summary content.\
            One or two lines.'}

        </Box>
        {bottom}
      </Tile>
    );
    index += 1;
  }
  return richTiles;
}
<|endoftext|>


<commit_before>'use strict';

angular
  .module('noCAPTCHA', [])
  .provider('googleGrecaptcha', function googleGrecaptchaProvider(){
    var language;

    this.setLanguage = function (_language){
      language = _language;
    };

    this.$get = [
      '$q',
      '$window',
      '$document',
      '$rootScope',
      function googleGrecaptchaFactory($q, $window, $document, $rootScope){
        var deferred = $q.defer();

        $window.recaptchaOnloadCallback = function (){
          $rootScope.$apply(function (){
            deferred.resolve();
          });
        };

        var s = $document[0].createElement('script');
        var src = 'https://www.google.com/recaptcha/api.js?onload=recaptchaOnloadCallback&render=explicit';
        s.type = 'application/javascript';

        if (language) {
          src += '&hl=' + language;
        }
        s.src = src;
        $document[0].body.appendChild(s);

        return deferred.promise;
    }];
  })
  .provider('noCAPTCHA', [
    'googleGrecaptchaProvider',
    function noCaptchaProvider(googleGrecaptchaProvider){
      var siteKey,
        size,
        theme;

      this.setSiteKey = function (_siteKey){
        siteKey = _siteKey;
      };

      this.setSize = function (_size){
        size = _size;
      };

      this.setTheme = function (_theme){
        theme = _theme;
      };

      this.setLanguage = function (language){
        googleGrecaptchaProvider.setLanguage(language);
      };

      this.$get = function noCaptchaFactory(){
        return {
          theme: theme,
          siteKey: siteKey,
          size: size
        };
      };
  }])
  .directive('noCaptcha', [
    'noCAPTCHA',
    'googleGrecaptcha',
    '$document',
    '$window',
    function (noCaptcha, googleGrecaptcha, $document, $window){
      /**
       * Removes all .pls-container elements
       *
       * This is a dirty workaround for fixing reCAPTCHAs bug
       * which leaves obsolete elements that causes errors
       * when closing dialogues or changing pages
       *
       * This will be removed as soon as Google fixes the bug
       *
       * Discussion: https://groups.google.com/forum/#!topic/recaptcha/miaW9qdVIgA
       */
      var removePLSContainers = function(){
        // remove pls-containers
        var plsContainers = $document[0].getElementsByClassName('pls-container');
        for(var i = 0; i < plsContainers.length; i++){
          var parent = plsContainers[i].parentNode;
          while (parent.firstChild) {
            parent.removeChild(parent.firstChild);
          }
        }
      };

      return {
        restrict: 'EA',
        scope: {
          gRecaptchaResponse: '=',
          siteKey: '@',
          size: '@',
          theme: '@',
          control: '=?',
          expiredCallback: '=?'
        },
        replace: true,
        link: function (scope, element){
          scope.control = scope.control || {};

          var widgetId;
          var grecaptchaCreateParameters;
          var control = scope.control;

          grecaptchaCreateParameters = {
            sitekey: scope.siteKey || noCaptcha.siteKey,
            size: scope.size || noCaptcha.size,
            theme: scope.theme || noCaptcha.theme,
            callback: function (r){
              scope.$apply(function (){
                scope.gRecaptchaResponse = r;
              });
            },
            'expired-callback': function (){
              if(scope.expiredCallback && typeof (scope.expiredCallback) === 'function') {
                scope.$apply(function (){
                  scope.expiredCallback();
                });
              }
            }
          };

          if(!grecaptchaCreateParameters.sitekey) {
            throw new Error('Site Key is required');
          }

          googleGrecaptcha.then(function (){
            widgetId = $window.grecaptcha.render(
              element[0],
              grecaptchaCreateParameters
            );
            control.reset = function (){
              $window.grecaptcha.reset(widgetId);
              scope.gRecaptchaResponse = null;
            };
          });

          scope.$on('$destroy', function (){
            if($window.grecaptcha) {
              $window.grecaptcha.reset(widgetId);
            }
            removePLSContainers();
          });
        }
      };
    }]);
<commit_msg>Added support for stoken<commit_after>'use strict';

angular
  .module('noCAPTCHA', [])
  .provider('googleGrecaptcha', function googleGrecaptchaProvider(){
    var language;

    this.setLanguage = function (_language){
      language = _language;
    };

    this.$get = [
      '$q',
      '$window',
      '$document',
      '$rootScope',
      function googleGrecaptchaFactory($q, $window, $document, $rootScope){
        var deferred = $q.defer();

        $window.recaptchaOnloadCallback = function (){
          $rootScope.$apply(function (){
            deferred.resolve();
          });
        };

        var s = $document[0].createElement('script');
        var src = 'https://www.google.com/recaptcha/api.js?onload=recaptchaOnloadCallback&render=explicit';
        s.type = 'application/javascript';

        if (language) {
          src += '&hl=' + language;
        }
        s.src = src;
        $document[0].body.appendChild(s);

        return deferred.promise;
    }];
  })
  .provider('noCAPTCHA', [
    'googleGrecaptchaProvider',
    function noCaptchaProvider(googleGrecaptchaProvider){
      var siteKey,
        size,
        theme;

      this.setSiteKey = function (_siteKey){
        siteKey = _siteKey;
      };

      this.setSize = function (_size){
        size = _size;
      };

      this.setTheme = function (_theme){
        theme = _theme;
      };

      this.setLanguage = function (language){
        googleGrecaptchaProvider.setLanguage(language);
      };

      this.$get = function noCaptchaFactory(){
        return {
          theme: theme,
          siteKey: siteKey,
          size: size
        };
      };
  }])
  .directive('noCaptcha', [
    'noCAPTCHA',
    'googleGrecaptcha',
    '$document',
    '$window',
    function (noCaptcha, googleGrecaptcha, $document, $window){
      /**
       * Removes all .pls-container elements
       *
       * This is a dirty workaround for fixing reCAPTCHAs bug
       * which leaves obsolete elements that causes errors
       * when closing dialogues or changing pages
       *
       * This will be removed as soon as Google fixes the bug
       *
       * Discussion: https://groups.google.com/forum/#!topic/recaptcha/miaW9qdVIgA
       */
      var removePLSContainers = function(){
        // remove pls-containers
        var plsContainers = $document[0].getElementsByClassName('pls-container');
        for(var i = 0; i < plsContainers.length; i++){
          var parent = plsContainers[i].parentNode;
          while (parent.firstChild) {
            parent.removeChild(parent.firstChild);
          }
        }
      };

      return {
        restrict: 'EA',
        scope: {
          gRecaptchaResponse: '=',
          siteKey: '@',
          stoken: '@',
          size: '@',
          theme: '@',
          control: '=?',
          expiredCallback: '=?'
        },
        replace: true,
        link: function (scope, element){
          scope.control = scope.control || {};

          var widgetId;
          var grecaptchaCreateParameters;
          var control = scope.control;

          grecaptchaCreateParameters = {
            sitekey: scope.siteKey || noCaptcha.siteKey,
            size: scope.size || noCaptcha.size,
            theme: scope.theme || noCaptcha.theme,
            callback: function (r){
              scope.$apply(function (){
                scope.gRecaptchaResponse = r;
              });
            },
            'expired-callback': function (){
              if(scope.expiredCallback && typeof (scope.expiredCallback) === 'function') {
                scope.$apply(function (){
                  scope.expiredCallback();
                });
              }
            }
          };

          if (scope.stoken) {
            grecaptchaCreateParameters.stoken = scope.stoken;
          }

          if(!grecaptchaCreateParameters.sitekey) {
            throw new Error('Site Key is required');
          }

          googleGrecaptcha.then(function (){
            widgetId = $window.grecaptcha.render(
              element[0],
              grecaptchaCreateParameters
            );
            control.reset = function (){
              $window.grecaptcha.reset(widgetId);
              scope.gRecaptchaResponse = null;
            };
          });

          scope.$on('$destroy', function (){
            if($window.grecaptcha) {
              $window.grecaptcha.reset(widgetId);
            }
            removePLSContainers();
          });
        }
      };
    }]);
<|endoftext|>


<commit_before>const google = require('googleapis');
const generateJWT = require('../utilities/jwt');
const { getSyncToken, setSyncToken } = require('../utilities/token');

export default async function getDelta(options) {
  return new Promise(async (res, rej) => {
    const syncToken = await getSyncToken();
    const { calendarId } = options;

    const jwtClient = await generateJWT();
    const calendar = google.calendar({ version: 'v3', auth: jwtClient });
    calendar.events.list(
      {
        calendarId,
        singleEvents: true,
        syncToken,
      },
      async (err, response) => {
        if (err) {
          rej(err);
        } else {
          res(response);
        }
      },
    );
  });
}
<commit_msg>finding sync error<commit_after>const google = require('googleapis');
const generateJWT = require('../utilities/jwt');
const { getSyncToken, setSyncToken } = require('../utilities/token');

export default async function getDelta(options) {
  return new Promise(async (res, rej) => {
    const syncToken = await getSyncToken();
    const { calendarId } = options;

    const jwtClient = await generateJWT();
    const calendar = google.calendar({ version: 'v3', auth: jwtClient });
    calendar.events.list(
      {
        calendarId,
        singleEvents: true,
        syncToken,
        maxResults: 2500
      },
      async (err, response) => {
        if (err) {
          rej(err);
        } else {
          res(response);
        }
      },
    );
  });
}
<|endoftext|>


<commit_before>import {navigateAction} from 'fluxible-router';
import UserProfileStore from '../stores/UserProfileStore';
export default function loadAddDeck(context, payload, done) {
    //redirect to homepage if not logged in
    let store = context.getStore(UserProfileStore);
    if (!((store.username !== undefined && store.username !== null && store.username !== '')
      && (store.userid !== undefined && store.userid !== null && store.userid !== '')
      && (store.jwt !== undefined && store.jwt !== null && store.jwt !== ''))) {
        context.executeAction(navigateAction, {
            url: '/'
        });
    }
    done();
}
<commit_msg>[SWIK-501_redirect_on_add_deck] Now Login modal opens if addDeck was accessed by a not logged in user<commit_after>import {navigateAction} from 'fluxible-router';
import UserProfileStore from '../stores/UserProfileStore';
export default function loadAddDeck(context, payload, done) {
    //redirect to homepage if not logged in
    let store = context.getStore(UserProfileStore);
    if (!((store.username !== undefined && store.username !== null && store.username !== '')
      && (store.userid !== undefined && store.userid !== null && store.userid !== '')
      && (store.jwt !== undefined && store.jwt !== null && store.jwt !== ''))) {
        context.executeAction(navigateAction, {
            url: '/'
        });
        context.dispatch('TOGGLE_LOGIN_MODAL', null);
    }
    done();
}
<|endoftext|>


<commit_before>// Copyright, 2013-2014, by Tomas Korcak. <korczis@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

(function (global) {

    var deps = [
        'app'
    ];

    define(deps, function (App) {
        App.Router.map(function() {
            this.route("login", { path: "/login" });
        });

        return App;
    });
})(this);
<commit_msg>Added tos and privacy routes<commit_after>// Copyright, 2013-2014, by Tomas Korcak. <korczis@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

(function (global) {

    var deps = [
        'app'
    ];

    define(deps, function (App) {
        App.Router.map(function() {
            this.route("login", { path: "/login" });
            this.route("privacy", { path: "/privacy" });
            this.route("tos", { path: "/tos" });
        });

        return App;
    });
})(this);
<|endoftext|>


<commit_before>/* Information about this package */
Package.describe({
  // Short two-sentence summary.
  summary: "Draw polygons and rectangles on a HTML5 canvas reactively",
  // Version number.
  version: "0.0.20",
  // Optional.  Default is package directory name.
  name: "philippspo:reactive-canvas",
  // Optional github URL to your source repository.
  git: "https://github.com/PhilippSpo/reactive-canvas",
});

/* This defines your actual package */
Package.onUse(function (api) {
  // If no version is specified for an 'api.use' dependency, use the
  // one defined in Meteor 0.9.0.
  api.versionsFrom('0.9.0');
  // Use Underscore package, but only on the server.
  // Version not specified, so it will be as of Meteor 0.9.0.
  api.use('underscore', 'server');
  // Use application-configuration package, version 1.0.0 or newer.
  api.use('application-configuration@1.0.0');
  api.use(['session', 'reactive-var@1.0.3']);
  // Specify the source code for the package.
  api.addFiles('lib/CanvasFunctions.js', 'client');
  api.addFiles('lib/Shape.js', 'client');
  api.addFiles('lib/Poly.js', 'client');
  api.addFiles('lib/Rect.js', 'client');
  api.addFiles('lib/TextField.js', 'client');
  api.addFiles('lib/ReactiveCanvas.js', 'client');

  api.export('ReactiveCanvas');
  api.export('Polygon');
  api.export('Rectangle');
  api.export('CanvasFunctions');
});<commit_msg>version bump<commit_after>/* Information about this package */
Package.describe({
  // Short two-sentence summary.
  summary: "Draw polygons and rectangles on a HTML5 canvas reactively",
  // Version number.
  version: "0.0.21",
  // Optional.  Default is package directory name.
  name: "philippspo:reactive-canvas",
  // Optional github URL to your source repository.
  git: "https://github.com/PhilippSpo/reactive-canvas",
});

/* This defines your actual package */
Package.onUse(function (api) {
  // If no version is specified for an 'api.use' dependency, use the
  // one defined in Meteor 0.9.0.
  api.versionsFrom('0.9.0');
  // Use Underscore package, but only on the server.
  // Version not specified, so it will be as of Meteor 0.9.0.
  api.use('underscore', 'server');
  // Use application-configuration package, version 1.0.0 or newer.
  api.use('application-configuration@1.0.0');
  api.use(['session', 'reactive-var@1.0.3']);
  // Specify the source code for the package.
  api.addFiles('lib/CanvasFunctions.js', 'client');
  api.addFiles('lib/Shape.js', 'client');
  api.addFiles('lib/Poly.js', 'client');
  api.addFiles('lib/Rect.js', 'client');
  api.addFiles('lib/TextField.js', 'client');
  api.addFiles('lib/ReactiveCanvas.js', 'client');

  api.export('ReactiveCanvas');
  api.export('Polygon');
  api.export('Rectangle');
  api.export('CanvasFunctions');
});<|endoftext|>


<commit_before>const $ = require('jquery');
const { createApplicationForm } = require('js/shared/application-form');
const { createTeamForm } = require('js/shared/team-form');

/**
 * jQuery's .serializeArray does not give us file input values. While we can't get useful
 * information about files' content easily, we can get its name for validation easily.
 *
 * For inspiration, see their implementation: https://github.com/jquery/jquery/blob/master/src/serialize.js
 */
function serializeForm($formElement) {
  const serializedObject = { };

  $formElement.find('select, input, textarea').each((_, element) => {
    const $element = $(element);

    const key = $element.attr('name');
    const value = getFieldValue($element);

    if (($element.attr('type') === 'submit') || (($element.attr('type') === 'checkbox') && !element.checked)) {
      return;
    }

    if (Array.isArray(serializedObject[key])) {
      serializedObject[key].push(value);
    } else if (typeof serializedObject[key] !== 'undefined') {
      serializedObject[key] = [serializedObject[key], value];
    } else {
      serializedObject[key] = value;
    }
  });

  return serializedObject;
}

function getFieldValue($field) {
  if ($field.attr('type') === 'file') {
    const files = $field[0].files;

    if ((files == null) || (files.length == 0)) {
      return null;
    }

    return {
      originalname: files[0].name,
      mimetype: files[0].type,
      size: files[0].size,
      location: '',
    };
  } else {
    return $field.val();
  }
}

function uncheckElements($fieldElements) {
  $fieldElements.each(function () {
    this.checked = false;
  });
}

/**
 * Determines if user input could lead to ambiguous data, and corrects it automatically
 * based on context - e.g. what the user most recently selected.
 */
function disallowAmbiguousAnswersProactively($form) {
  const notSureAnswer = 'unknown';

  const $developmentStyleFields = $form.find('input[name=development]');

  $developmentStyleFields.change(function () {
    const $field = $(this);

    if ($field.attr('value') === notSureAnswer) {
      uncheckElements($developmentStyleFields.not(`[value=${notSureAnswer}]`));
    } else {
      uncheckElements($developmentStyleFields.filter(`[value=${notSureAnswer}]`));
    }
  });

  const $teamFields = $form.find('input[name=team_apply], input[name=team_placement]');

  $teamFields.change(function () {
    uncheckElements($teamFields.not(this));
  });

  const $memberFields = $form.find('input[name=memberB], input[name=memberC], input[name=memberD]');

  $memberFields.on('input', function () {
    $(this).val($(this).val().replace(/\s/g, ''));
  });
}

function processForm($form, createForm) {
  disallowAmbiguousAnswersProactively($form);

  const supportsFileApi = ($('<input type="file">').get(0).files) !== undefined;

  const addFeedbackToForm = (form) => {
    let firstErrorFound = false;
    Object.keys(form.fields).forEach((fieldName) => {
      const $row = $form.find(`[name=${fieldName}]`).closest('.form-row');
      const field = form.fields[fieldName];

      $row.removeClass('error');
      $row.find('.form-error-message').remove();

      if (field.error != null) {
        if (!firstErrorFound) {
          $('html, body').animate({
            scrollTop: $row.offset().top
          }, 500);
          firstErrorFound = true;
        }
        $row.find('.form-control-note').after(field.errorHTML());
        $row.addClass('error');
      }
    });
  };

  $form.submit((event, { valid } = { }) => {
    // Our form validation is asynchronous (but happens within a few ms).
    // This means that we have to re-submit the form after validation
    // with some kind of indicator that the validation has succeeded

    if (valid) {
      return true;
    }

    event.preventDefault();

    createForm(supportsFileApi).handle(
      serializeForm($form),
      {
        success: () => $form.trigger('submit', { valid: true }),
        error: addFeedbackToForm,
        empty: addFeedbackToForm,
      }
    );
  });
}

module.exports = function applyPage() {
  $('.apply-form').each(function () {
    processForm($(this), createApplicationForm);
  });
  $('.team-form').each(function () {
    processForm($(this), createTeamForm);
  });
};<commit_msg>Fix form error messages not being removed<commit_after>const $ = require('jquery');
const { createApplicationForm } = require('js/shared/application-form');
const { createTeamForm } = require('js/shared/team-form');

/**
 * jQuery's .serializeArray does not give us file input values. While we can't get useful
 * information about files' content easily, we can get its name for validation easily.
 *
 * For inspiration, see their implementation: https://github.com/jquery/jquery/blob/master/src/serialize.js
 */
function serializeForm($formElement) {
  const serializedObject = { };

  $formElement.find('select, input, textarea').each((_, element) => {
    const $element = $(element);

    const key = $element.attr('name');
    const value = getFieldValue($element);

    if (($element.attr('type') === 'submit') || (($element.attr('type') === 'checkbox') && !element.checked)) {
      return;
    }

    if (Array.isArray(serializedObject[key])) {
      serializedObject[key].push(value);
    } else if (typeof serializedObject[key] !== 'undefined') {
      serializedObject[key] = [serializedObject[key], value];
    } else {
      serializedObject[key] = value;
    }
  });

  return serializedObject;
}

function getFieldValue($field) {
  if ($field.attr('type') === 'file') {
    const files = $field[0].files;

    if ((files == null) || (files.length == 0)) {
      return null;
    }

    return {
      originalname: files[0].name,
      mimetype: files[0].type,
      size: files[0].size,
      location: '',
    };
  } else {
    return $field.val();
  }
}

function uncheckElements($fieldElements) {
  $fieldElements.each(function () {
    this.checked = false;
  });
}

/**
 * Determines if user input could lead to ambiguous data, and corrects it automatically
 * based on context - e.g. what the user most recently selected.
 */
function disallowAmbiguousAnswersProactively($form) {
  const notSureAnswer = 'unknown';

  const $developmentStyleFields = $form.find('input[name=development]');

  $developmentStyleFields.change(function () {
    const $field = $(this);

    if ($field.attr('value') === notSureAnswer) {
      uncheckElements($developmentStyleFields.not(`[value=${notSureAnswer}]`));
    } else {
      uncheckElements($developmentStyleFields.filter(`[value=${notSureAnswer}]`));
    }
  });

  const $teamFields = $form.find('input[name=team_apply], input[name=team_placement]');

  $teamFields.change(function () {
    uncheckElements($teamFields.not(this));
  });

  const $memberFields = $form.find('input[name=memberB], input[name=memberC], input[name=memberD]');

  $memberFields.on('input', function () {
    $(this).val($(this).val().replace(/\s/g, ''));
  });
}

function processForm($form, createForm) {
  disallowAmbiguousAnswersProactively($form);

  const supportsFileApi = ($('<input type="file">').get(0).files) !== undefined;

  const addFeedbackToForm = (form) => {
    let firstErrorFound = false;
    Object.keys(form.fields).forEach((fieldName) => {
      const $row = $form.find(`[name=${fieldName}]`).closest('.form-row');
      const field = form.fields[fieldName];

      $row.removeClass('error');
      $row.find('.error_msg').remove();

      if (field.error != null) {
        if (!firstErrorFound) {
          $('html, body').animate({
            scrollTop: $row.offset().top
          }, 500);
          firstErrorFound = true;
        }
        $row.find('.form-control-note').after(field.errorHTML());
        $row.addClass('error');
      }
    });
  };

  $form.submit((event, { valid } = { }) => {
    // Our form validation is asynchronous (but happens within a few ms).
    // This means that we have to re-submit the form after validation
    // with some kind of indicator that the validation has succeeded

    if (valid) {
      return true;
    }

    event.preventDefault();

    createForm(supportsFileApi).handle(
      serializeForm($form),
      {
        success: () => $form.trigger('submit', { valid: true }),
        error: addFeedbackToForm,
        empty: addFeedbackToForm,
      }
    );
  });
}

module.exports = function applyPage() {
  $('.apply-form').each(function () {
    processForm($(this), createApplicationForm);
  });
  $('.team-form').each(function () {
    processForm($(this), createTeamForm);
  });
};<|endoftext|>


<commit_before>import externalRequest from 'request';
import Rx from 'rxjs';
const externalRequest$ = Rx.Observable.bindNodeCallback(externalRequest);

import * as apiConfigCreators from './apiConfigCreators';
import { duotoneRef } from '../util/duotone';

const parseResponseFlags = ({ headers }) =>
	(headers['X-Meetup-Flags'] || '')
		.split(',')
		.map(pair => pair.split('='))
		.reduce((flags, [key, val]) => {
			flags[key] = val;
			return flags;
		}, {});

/**
 * Given the current request and API server host, proxy the request to the API
 * and return the responses corresponding to the provided queries.
 *
 * This module plugs in to any system that provides a `request` object with:
 * - headers
 * - method ('get'/'post')
 * - query string parameters parsed as a plain object (for GET requests)
 * - payload/body (for POST requests)
 *
 * @module ApiProxy
 */

/**
 * mostly error handling - any case where the API does not satisfy the
 * "api response" formatting requirement: plain object containing the requested
 * values
 *
 * This utility is specific to the response format of the API being consumed
 * @param response {String} the raw response body text from an API call
 * @return responseObj the JSON-parsed text, possibly with error info
 */
export const parseApiResponse = ([response, body]) => {
	let value;
	const flags = parseResponseFlags(response);

	if (!response.ok) {
		return {
			value: { error: response.statusText },
			flags,
		};
	}
	try {
		value = JSON.parse(body);
		if (value && value.problem) {
			value = { error: `API problem: ${value.problem}: ${value.details}` };
		}
		return { value, flags };
	} catch(err) {
		return {
			value: { error: err.message },
			flags
		};
	}
};

/**
 * Translate a query into an API `endpoint` + `params`. The translation is based
 * on the Meetup REST API.
 *
 * This function serves as an adapter between the structure of a query and the
 * API-specific config needed to get that data. Note that *each* required
 * endpoint needs to be manually configured
 *
 * {@link http://www.meetup.com/meetup_api/docs/batch/}
 *
 * @param {Object} query a query object from the application
 * @return {Object} the arguments for api request, including endpoint
 */
export function queryToApiConfig({ type, params, flags }) {
	const configCreator = apiConfigCreators[type];
	if (!configCreator) {
		throw new ReferenceError(`No API specified for query type ${type}`);
	}
	return configCreator(params);
}

/**
 * Join the key-value params object into a querystring-like
 * string. use `encodeURIComponent` _only_ if `doEncode` is provided,
 * otherwise the caller is responsible for encoding
 *
 * @param {Object} params plain object of keys and values to format
 * @return {String}
 */
function urlFormatParams(params, doEncode) {
	return Object.keys(params || {})
		.reduce((dataParams, paramKey) => {
			const paramValue = encodeURIComponent(params[paramKey]);
			dataParams.push(`${paramKey}=${paramValue}`);
			return dataParams;
		}, []).join('&');
}

/**
 * Transform each query into the arguments needed for a `request` call.
 *
 * Some request options are constant for all queries, and these are curried into
 * a function that can be called with a single query as part of the request
 * stream
 *
 * @see {@link https://www.npmjs.com/package/request}
 *
 * @param {Object} externalRequestOpts request options that will be applied to
 *   every query request
 * @param {Object} apiConfig { endpoint, params, flags }
 *   call)
 * @return {Object} externalRequestOptsQuery argument for the call to
 *   `externalRequest` for the query
 */
export const buildRequestArgs = externalRequestOpts =>
	({ endpoint, params, flags }) => {

		const opts = { ...externalRequestOpts };
		opts.url = `/${endpoint}`;

		const formattedParams = urlFormatParams(params, opts.method === 'get');

		if (flags) {
			opts.headers['X-Meetup-Request-Flags'] = flags.join(',');
		}

		switch (opts.method) {
		case 'get':
			opts.url += `?${formattedParams}`;
			opts.headers['X-Meta-Photo-Host'] = 'secure';
			break;
		case 'post':
			opts.body = formattedParams;
			opts.headers['content-type'] = 'application/x-www-form-urlencoded';
			break;
		}

		return opts;
	};

/**
 * Format apiResponse to match expected state structure
 *
 * @param {Object} apiResponse JSON-parsed api response data
 */
export const apiResponseToQueryResponse = query => ({ flags, value }) => ({
	[query.ref]: {
		type: query.type,
		value,
		flags
	}
});

/**
 * Parse request for queries and request options
 * @return {Object} { queries, externalRequestOpts }
 */
export function parseRequest(request, baseUrl) {
	const {
		headers,
		method,
		query,
		payload,
	} = request;

	const externalRequestOpts = {
		baseUrl,
		method,
		headers: { ...headers },  // make a copy to be immutable
		mode: 'no-cors',
		time: true,
		agentOptions: {
			rejectUnauthorized: baseUrl.indexOf('.dev') === -1
		}
	};

	// Forward the Hapi request headers from the client query
	// except for `host` and `accept-encoding`
	// which should be provided by the external api request
	delete externalRequestOpts.headers['host'];
	delete externalRequestOpts.headers['accept-encoding'];
	delete externalRequestOpts.headers['content-length'];  // original request content-length is irrelevant

	const queriesJSON = request.method === 'get' ? query.queries : payload.queries;
	const queries = JSON.parse(queriesJSON);
	return { queries, externalRequestOpts };
}

/**
 * From a provided set of signed duotone URLs, create a function that injects
 * the full duotone URL into a group object with the key `duotoneUrl`.
 *
 * @param {Object} duotoneUrls map of `[duotoneRef]: url template root`
 * @param {Object} group group object from API
 * @return {Object} the mutated group object
 */
export const groupDuotoneSetter = duotoneUrls => group => {
	const photo = group.key_photo || group.group_photo || {};
	const duotoneKey = group.photo_gradient && duotoneRef(
			group.photo_gradient.light_color,
			group.photo_gradient.dark_color
		);
	const duotoneUrlRoot = duotoneKey && duotoneUrls[duotoneKey];
	if (duotoneUrlRoot && photo.id) {
		group.duotoneUrl = `${duotoneUrlRoot}/${photo.id}.jpeg`;
	}
	return group;
};

/**
 * From a provided set of signed duotoneUrls, create a function that injects
 * the full duotone URL into an query response containing objects that support
 * duotoned images (anything containing group or event objects
 *
 * @param {Object} duotoneUrls map of `[duotoneRef]: url template root`
 * @param {Object} queryResponse { type: <type>, value: <API object> }
 * @return {Object} the modified queryResponse
 */
export const apiResponseDuotoneSetter = duotoneUrls => {
	const setGroupDuotone = groupDuotoneSetter(duotoneUrls);
	return queryResponse => {
		// inject duotone URLs into any group query response
		Object.keys(queryResponse)
			.forEach(key => {
				const { type, value } = queryResponse[key];
				let groups;
				switch (type) {
				case 'group':
					groups = value instanceof Array ? value : [value];
					groups.forEach(setGroupDuotone);
					break;
				case 'home':
					(value.rows || []).map(({ items }) => items)
						.forEach(items => items.filter(({ type }) => type === 'group')
							.forEach(({ group }) => setGroupDuotone(group))
						);
					break;
				}
			});
		return queryResponse;
	};
};

export const makeApiRequest = (request, API_TIMEOUT, duotoneUrls) => {
	const setApiResponseDuotones = apiResponseDuotoneSetter(duotoneUrls);

	return ([requestOpts, query]) => {
		request.log(['api'], JSON.stringify(requestOpts.url));
		return externalRequest$(requestOpts)
			.timeout(API_TIMEOUT, new Error('API response timeout'))
			.do(([response]) => request.log(['api'], `${response.elapsedTime}ms - ${response.request.uri.path}`)) // log api response time
			.map(parseApiResponse)             // parse into plain object
			.catch(error =>
				Rx.Observable.of({ value: { error: error.message } })
			)
			.map(apiResponseToQueryResponse(query))    // convert apiResponse to app-ready queryResponse
			.map(setApiResponseDuotones);        // special duotone prop
	};
};

/**
 * This function transforms a single request to the application server into a
 * parallel array of requests to the API server, and then re-assembles the
 * API responses into an array of 'query responses' - i.e. API responses that
 * are formatted with properties from their corresponding query (ref, type).
 *
 * Most of the `options` for the `externalRequest` are shared for all the API
 * requests, so these are initialized in `parseRequest`. `buildRequestArgs`
 * then curries those into a function that can accept a `query` to write the
 * query-specific options.
 *
 * @param {Request} request Hapi request object
 * @param {Object} baseUrl API server base URL for all API requests
 * @return Array$ contains all API responses corresponding to the provided queries
 */
const apiProxy$ = ({ API_TIMEOUT=5000, baseUrl, duotoneUrls }) => {

	return request => {

		// 1. get the queries and the 'universal' `externalRequestOpts` from the request
		const { queries, externalRequestOpts } = parseRequest(request, baseUrl);

		// 2. curry a function that uses `externalRequestOpts` as a base from which
		// to build the query-specific API request options object
		const apiConfigToRequestOptions = buildRequestArgs(externalRequestOpts);

		// 3. map the queries onto an array of api request observables
		const apiRequests$ = queries
			.map(queryToApiConfig)
			.map(apiConfigToRequestOptions)
			.map((opts, i) => ([opts, queries[i]]))  // zip the query back into the opts
			.map(makeApiRequest(request, API_TIMEOUT, duotoneUrls));

		// 4. zip them together to send them parallel and receive them in order
		return Rx.Observable.zip(...apiRequests$);
	};
};

export default apiProxy$;

<commit_msg>boolean<commit_after>import externalRequest from 'request';
import Rx from 'rxjs';
const externalRequest$ = Rx.Observable.bindNodeCallback(externalRequest);

import * as apiConfigCreators from './apiConfigCreators';
import { duotoneRef } from '../util/duotone';

const parseResponseFlags = ({ headers }) =>
	(headers['X-Meetup-Flags'] || '')
		.split(',')
		.map(pair => pair.split('='))
		.reduce((flags, [key, val]) => {
			flags[key] = val === 'true';
			return flags;
		}, {});

/**
 * Given the current request and API server host, proxy the request to the API
 * and return the responses corresponding to the provided queries.
 *
 * This module plugs in to any system that provides a `request` object with:
 * - headers
 * - method ('get'/'post')
 * - query string parameters parsed as a plain object (for GET requests)
 * - payload/body (for POST requests)
 *
 * @module ApiProxy
 */

/**
 * mostly error handling - any case where the API does not satisfy the
 * "api response" formatting requirement: plain object containing the requested
 * values
 *
 * This utility is specific to the response format of the API being consumed
 * @param response {String} the raw response body text from an API call
 * @return responseObj the JSON-parsed text, possibly with error info
 */
export const parseApiResponse = ([response, body]) => {
	let value;
	const flags = parseResponseFlags(response);

	if (!response.ok) {
		return {
			value: { error: response.statusText },
			flags,
		};
	}
	try {
		value = JSON.parse(body);
		if (value && value.problem) {
			value = { error: `API problem: ${value.problem}: ${value.details}` };
		}
		return { value, flags };
	} catch(err) {
		return {
			value: { error: err.message },
			flags
		};
	}
};

/**
 * Translate a query into an API `endpoint` + `params`. The translation is based
 * on the Meetup REST API.
 *
 * This function serves as an adapter between the structure of a query and the
 * API-specific config needed to get that data. Note that *each* required
 * endpoint needs to be manually configured
 *
 * {@link http://www.meetup.com/meetup_api/docs/batch/}
 *
 * @param {Object} query a query object from the application
 * @return {Object} the arguments for api request, including endpoint
 */
export function queryToApiConfig({ type, params, flags }) {
	const configCreator = apiConfigCreators[type];
	if (!configCreator) {
		throw new ReferenceError(`No API specified for query type ${type}`);
	}
	return configCreator(params);
}

/**
 * Join the key-value params object into a querystring-like
 * string. use `encodeURIComponent` _only_ if `doEncode` is provided,
 * otherwise the caller is responsible for encoding
 *
 * @param {Object} params plain object of keys and values to format
 * @return {String}
 */
function urlFormatParams(params, doEncode) {
	return Object.keys(params || {})
		.reduce((dataParams, paramKey) => {
			const paramValue = encodeURIComponent(params[paramKey]);
			dataParams.push(`${paramKey}=${paramValue}`);
			return dataParams;
		}, []).join('&');
}

/**
 * Transform each query into the arguments needed for a `request` call.
 *
 * Some request options are constant for all queries, and these are curried into
 * a function that can be called with a single query as part of the request
 * stream
 *
 * @see {@link https://www.npmjs.com/package/request}
 *
 * @param {Object} externalRequestOpts request options that will be applied to
 *   every query request
 * @param {Object} apiConfig { endpoint, params, flags }
 *   call)
 * @return {Object} externalRequestOptsQuery argument for the call to
 *   `externalRequest` for the query
 */
export const buildRequestArgs = externalRequestOpts =>
	({ endpoint, params, flags }) => {

		const opts = { ...externalRequestOpts };
		opts.url = `/${endpoint}`;

		const formattedParams = urlFormatParams(params, opts.method === 'get');

		if (flags) {
			opts.headers['X-Meetup-Request-Flags'] = flags.join(',');
		}

		switch (opts.method) {
		case 'get':
			opts.url += `?${formattedParams}`;
			opts.headers['X-Meta-Photo-Host'] = 'secure';
			break;
		case 'post':
			opts.body = formattedParams;
			opts.headers['content-type'] = 'application/x-www-form-urlencoded';
			break;
		}

		return opts;
	};

/**
 * Format apiResponse to match expected state structure
 *
 * @param {Object} apiResponse JSON-parsed api response data
 */
export const apiResponseToQueryResponse = query => ({ flags, value }) => ({
	[query.ref]: {
		type: query.type,
		value,
		flags
	}
});

/**
 * Parse request for queries and request options
 * @return {Object} { queries, externalRequestOpts }
 */
export function parseRequest(request, baseUrl) {
	const {
		headers,
		method,
		query,
		payload,
	} = request;

	const externalRequestOpts = {
		baseUrl,
		method,
		headers: { ...headers },  // make a copy to be immutable
		mode: 'no-cors',
		time: true,
		agentOptions: {
			rejectUnauthorized: baseUrl.indexOf('.dev') === -1
		}
	};

	// Forward the Hapi request headers from the client query
	// except for `host` and `accept-encoding`
	// which should be provided by the external api request
	delete externalRequestOpts.headers['host'];
	delete externalRequestOpts.headers['accept-encoding'];
	delete externalRequestOpts.headers['content-length'];  // original request content-length is irrelevant

	const queriesJSON = request.method === 'get' ? query.queries : payload.queries;
	const queries = JSON.parse(queriesJSON);
	return { queries, externalRequestOpts };
}

/**
 * From a provided set of signed duotone URLs, create a function that injects
 * the full duotone URL into a group object with the key `duotoneUrl`.
 *
 * @param {Object} duotoneUrls map of `[duotoneRef]: url template root`
 * @param {Object} group group object from API
 * @return {Object} the mutated group object
 */
export const groupDuotoneSetter = duotoneUrls => group => {
	const photo = group.key_photo || group.group_photo || {};
	const duotoneKey = group.photo_gradient && duotoneRef(
			group.photo_gradient.light_color,
			group.photo_gradient.dark_color
		);
	const duotoneUrlRoot = duotoneKey && duotoneUrls[duotoneKey];
	if (duotoneUrlRoot && photo.id) {
		group.duotoneUrl = `${duotoneUrlRoot}/${photo.id}.jpeg`;
	}
	return group;
};

/**
 * From a provided set of signed duotoneUrls, create a function that injects
 * the full duotone URL into an query response containing objects that support
 * duotoned images (anything containing group or event objects
 *
 * @param {Object} duotoneUrls map of `[duotoneRef]: url template root`
 * @param {Object} queryResponse { type: <type>, value: <API object> }
 * @return {Object} the modified queryResponse
 */
export const apiResponseDuotoneSetter = duotoneUrls => {
	const setGroupDuotone = groupDuotoneSetter(duotoneUrls);
	return queryResponse => {
		// inject duotone URLs into any group query response
		Object.keys(queryResponse)
			.forEach(key => {
				const { type, value } = queryResponse[key];
				let groups;
				switch (type) {
				case 'group':
					groups = value instanceof Array ? value : [value];
					groups.forEach(setGroupDuotone);
					break;
				case 'home':
					(value.rows || []).map(({ items }) => items)
						.forEach(items => items.filter(({ type }) => type === 'group')
							.forEach(({ group }) => setGroupDuotone(group))
						);
					break;
				}
			});
		return queryResponse;
	};
};

export const makeApiRequest = (request, API_TIMEOUT, duotoneUrls) => {
	const setApiResponseDuotones = apiResponseDuotoneSetter(duotoneUrls);

	return ([requestOpts, query]) => {
		request.log(['api'], JSON.stringify(requestOpts.url));
		return externalRequest$(requestOpts)
			.timeout(API_TIMEOUT, new Error('API response timeout'))
			.do(([response]) => request.log(['api'], `${response.elapsedTime}ms - ${response.request.uri.path}`)) // log api response time
			.map(parseApiResponse)             // parse into plain object
			.catch(error =>
				Rx.Observable.of({ value: { error: error.message } })
			)
			.map(apiResponseToQueryResponse(query))    // convert apiResponse to app-ready queryResponse
			.map(setApiResponseDuotones);        // special duotone prop
	};
};

/**
 * This function transforms a single request to the application server into a
 * parallel array of requests to the API server, and then re-assembles the
 * API responses into an array of 'query responses' - i.e. API responses that
 * are formatted with properties from their corresponding query (ref, type).
 *
 * Most of the `options` for the `externalRequest` are shared for all the API
 * requests, so these are initialized in `parseRequest`. `buildRequestArgs`
 * then curries those into a function that can accept a `query` to write the
 * query-specific options.
 *
 * @param {Request} request Hapi request object
 * @param {Object} baseUrl API server base URL for all API requests
 * @return Array$ contains all API responses corresponding to the provided queries
 */
const apiProxy$ = ({ API_TIMEOUT=5000, baseUrl, duotoneUrls }) => {

	return request => {

		// 1. get the queries and the 'universal' `externalRequestOpts` from the request
		const { queries, externalRequestOpts } = parseRequest(request, baseUrl);

		// 2. curry a function that uses `externalRequestOpts` as a base from which
		// to build the query-specific API request options object
		const apiConfigToRequestOptions = buildRequestArgs(externalRequestOpts);

		// 3. map the queries onto an array of api request observables
		const apiRequests$ = queries
			.map(queryToApiConfig)
			.map(apiConfigToRequestOptions)
			.map((opts, i) => ([opts, queries[i]]))  // zip the query back into the opts
			.map(makeApiRequest(request, API_TIMEOUT, duotoneUrls));

		// 4. zip them together to send them parallel and receive them in order
		return Rx.Observable.zip(...apiRequests$);
	};
};

export default apiProxy$;

<|endoftext|>


<commit_before>Package.describe({
  name: 'daoli:hallo',
  version: '1.1.1_6',
  summary: 'Homebrew Hallo.js for Meteor',
  git: 'https://github.com/daoli/hallo.git',
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0.5');

  api.use(['jquery'], 'client');
  api.use(['linto:jquery-ui'], 'client');
  api.use(['sewdn:rangy'], 'client');
  api.use(['twbs:bootstrap'], 'client');
  api.use(['fortawesome:fontawesome'], 'client');

  api.addFiles(['lib/hallo.js'], 'client');
});
<commit_msg>Specify versions.<commit_after>Package.describe({
  name: 'daoli:hallo',
  version: '1.1.1_6',
  summary: 'Homebrew Hallo.js for Meteor',
  git: 'https://github.com/daoli/hallo.git',
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.versionsFrom('1.0.5');

  api.use(['jquery@1.11.3'], 'client');
  api.use(['linto:jquery-ui@1.0.0'], 'client');
  api.use(['sewdn:rangy@1.3.0'], 'client');
  api.use(['twbs:bootstrap@3.3.4'], 'client');
  api.use(['fortawesome:fontawesome@4.2.0'], 'client');

  api.addFiles(['lib/hallo.js'], 'client');
});
<|endoftext|>


<commit_before>window.ConnectFour = { };

document.addEventListener("DOMContentLoaded", function() {
	(function(cf) {
		var board = new cf.Board(),
			match = new cf.Match(board),
			tableHead = document.getElementById('board-head'),
			tableBody = document.getElementById('board-body'),
			slotToElementMap = {};

		function doMove(eventArgs) {
			var col = eventArgs.srcElement.dataset.col;
			var move = new cf.Move(col, match.currentPlayer);
			match.doMove(move);
			redrawSlots(board);
		}

		function getKey(r, c) {
			return r.toString() + ";" + c.toString();
		}

		function redrawBoard(board) {
			tableBody.innerHTML = "";

			for (var r = 0; r < board.height; r++) {
				var tr = document.createElement("tr");
				for (var c = 0; c < board.width; c++) {
					var td = document.createElement("td");
					var slot = document.createElement("div");
					slot.className = "slot";
					td.appendChild(slot);
					tr.appendChild(td);
					slotToElementMap[getKey(r,c)] = td;
				}
				tableBody.insertBefore(tr, tableBody.firstChild);
			}

			var tr = document.createElement("tr");
			for (var c = 0; c < board.width; c++) {
				var th = document.createElement("th");
				var btn = document.createElement("button");
				btn.className = "do-move";
				btn.dataset.col = c;
				btn.addEventListener("click", doMove, false);
				th.appendChild(btn);
				tr.appendChild(th);
			}
			tableHead.appendChild(tr);
		}

		function redrawSlots(board) {
			for (var r = 0; r < board.height; r++) {
				for (var c = 0; c < board.width; c++) {
					var td = slotToElementMap[getKey(r,c)];
					var player = board.slots[r][c].getPlayer();

					if (!!player) {
						td.className = player.isFirstPlayer ? "player-one" : "player-two";
					} else {
						td.className = "empty";
					}
				}
			}
		}

		redrawBoard(board);
		redrawSlots(board);

	}(window.ConnectFour))
});
<commit_msg>Fixes variable hiding in bootstrapper<commit_after>window.ConnectFour = { };

document.addEventListener("DOMContentLoaded", function() {
	(function(cf) {
		var board = new cf.Board(),
			match = new cf.Match(board),
			tableHead = document.getElementById('board-head'),
			tableBody = document.getElementById('board-body'),
			slotToElementMap = {};

		function doMove(eventArgs) {
			var col = eventArgs.srcElement.dataset.col;
			var move = new cf.Move(col, match.currentPlayer);
			match.doMove(move);
			redrawSlots(board);
		}

		function getKey(r, c) {
			return r.toString() + ";" + c.toString();
		}

		function redrawBoard() {
			tableBody.innerHTML = "";

			for (var r = 0; r < board.height; r++) {
				var tr = document.createElement("tr");
				for (var c = 0; c < board.width; c++) {
					var td = document.createElement("td");
					var slot = document.createElement("div");
					slot.className = "slot";
					td.appendChild(slot);
					tr.appendChild(td);
					slotToElementMap[getKey(r,c)] = td;
				}
				tableBody.insertBefore(tr, tableBody.firstChild);
			}

			var tr = document.createElement("tr");
			for (var c = 0; c < board.width; c++) {
				var th = document.createElement("th");
				var btn = document.createElement("button");
				btn.className = "do-move";
				btn.dataset.col = c;
				btn.addEventListener("click", doMove, false);
				th.appendChild(btn);
				tr.appendChild(th);
			}
			tableHead.appendChild(tr);
		}

		function redrawSlots() {
			for (var r = 0; r < board.height; r++) {
				for (var c = 0; c < board.width; c++) {
					var td = slotToElementMap[getKey(r,c)];
					var player = board.slots[r][c].getPlayer();

					if (!!player) {
						td.className = player.isFirstPlayer ? "player-one" : "player-two";
					} else {
						td.className = "empty";
					}
				}
			}
		}

		redrawBoard();
		redrawSlots();

	}(window.ConnectFour))
});
<|endoftext|>