<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM, {unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer} from 'react-dom'
import assign from 'object-assign'

class Card extends React.Component {
  static propTypes = {
    active: PropTypes.bool,
    position: PropTypes.oneOf([
      'top',
      'right',
      'bottom',
      'left'
    ]),
    arrow: PropTypes.oneOf([
      null,
      'center',
      'top',
      'right',
      'bottom',
      'left'
    ]),
    style: PropTypes.object
  }
  static defaultProps = {
    active: false,
    position: 'right',
    arrow: null,
    style: {style: {}, arrowStyle: {}}
  }
  state = {
    hover: false,
    transition: 'opacity',
    width: 0,
    height: 0
  }
  margin = 15
  defaultArrowStyle = {
    color: '#fff',
    borderColor: 'rgba(0,0,0,.4)'
  }
  getGlobalStyle() {
    if (!this.props.parentEl) {
      return {display: 'none'}
    }

    let style = {
      position: 'absolute',
      padding: '5px',
      background: '#fff',
      boxShadow: '0 0 8px rgba(0,0,0,.3)',
      borderRadius: '3px',
      transition: `${this.state.transition} .3s ease-in-out, visibility .3s ease-in-out`,
      opacity: this.state.hover || this.props.active ? 1 : 0,
      visibility: this.state.hover || this.props.active ? 'visible' : 'hidden',
      zIndex: 50
    }

    assign(style, this.getStyle(this.props.position, this.props.arrow))

    return this.mergeStyle(style, this.props.style.style)
  }
  getBaseArrowStyle() {
    return {
      position: 'absolute',
      content: '""',
      transition: 'all .3s ease-in-out'
    }
  }
  getArrowStyle() {
    let fgStyle = this.getBaseArrowStyle()
    let bgStyle = this.getBaseArrowStyle()
    fgStyle.zIndex = 60
    bgStyle.zIndex = 55

    let arrowStyle = assign(this.defaultArrowStyle, this.props.style.arrowStyle)
    let bgBorderColor = arrowStyle.borderColor ? arrowStyle.borderColor : 'transparent'

    let fgSize = 8
    let bgSize = 9
    let fgColorBorder = `10px solid ${arrowStyle.color}`
    let fgTransBorder = `${fgSize}px solid transparent`
    let bgColorBorder = `11px solid ${bgBorderColor}`
    let bgTransBorder = `${bgSize}px solid transparent`

    let {position, arrow} = this.props

    if (position === 'left' || position === 'right') {
      fgStyle.top = '50%'
      fgStyle.borderTop = fgTransBorder
      fgStyle.borderBottom = fgTransBorder
      fgStyle.marginTop = -7

      bgStyle.borderTop = bgTransBorder
      bgStyle.borderBottom = bgTransBorder
      bgStyle.top = '50%'
      bgStyle.marginTop = -8

      if (position === 'left') {
        fgStyle.right = -10
        fgStyle.borderLeft = fgColorBorder
        bgStyle.right = -11
        bgStyle.borderLeft = bgColorBorder
      }
      else {
        fgStyle.left = -10
        fgStyle.borderRight = fgColorBorder
        bgStyle.left = -11
        bgStyle.borderRight = bgColorBorder
      }

      if (arrow === 'top') {
        fgStyle.top = this.margin
        bgStyle.top = this.margin
      }
      if (arrow === 'bottom') {
        fgStyle.top = null
        fgStyle.bottom = this.margin - 7
        bgStyle.top = null
        bgStyle.bottom = this.margin - 8
      }
    }
    else {
      fgStyle.left = '50%'
      fgStyle.marginLeft = -10
      fgStyle.borderLeft = fgTransBorder
      fgStyle.borderRight = fgTransBorder
      bgStyle.left = '50%'
      bgStyle.marginLeft = -11
      bgStyle.borderLeft = bgTransBorder
      bgStyle.borderRight = bgTransBorder

      if (position === 'top') {
        fgStyle.bottom = -10
        fgStyle.borderTop = fgColorBorder
        bgStyle.bottom = -11
        bgStyle.borderTop = bgColorBorder
      }
      else {
        fgStyle.top = -10
        fgStyle.borderBottom = fgColorBorder
        bgStyle.top = -11
        bgStyle.borderBottom = bgColorBorder
      }

      if (arrow === 'right') {
        fgStyle.left = null
        fgStyle.right = this.margin + 1 - fgSize
        fgStyle.marginLeft = 0
        bgStyle.left = null
        bgStyle.right = this.margin - fgSize
        bgStyle.marginLeft = 0
      }
      if (arrow === 'left') {
        fgStyle.left = this.margin + 1 - fgSize
        fgStyle.marginLeft = 0
        bgStyle.left = this.margin - fgSize
        bgStyle.marginLeft = 0
      }
    }

    let {color, borderColor, ...propsArrowStyle} = this.props.style.arrowStyle

    return {
      fgStyle: this.mergeStyle(fgStyle, propsArrowStyle),
      bgStyle: this.mergeStyle(bgStyle, propsArrowStyle)
    }
  }
  mergeStyle(style, theme) {
    if (theme) {
      let {position, top, left, right, bottom, marginLeft, marginRight, ...validTheme} = theme

      return assign(style, validTheme)
    }

    return style
  }
  getStyle(position, arrow) {
    let parent = this.props.parentEl
    let tooltipPosition = parent.getBoundingClientRect()
    let scrollY = (window.scrollY !== undefined) ? window.scrollY : window.pageYOffset
    let scrollX = (window.scrollX !== undefined) ? window.scrollX : window.pageXOffset
    let top = scrollY + tooltipPosition.top
    let left = scrollX + tooltipPosition.left
    let style = {}

    const stylesFromPosition = {
      left: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height / 2
        style.left = left - this.state.width - this.margin
      },
      right: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height / 2
        style.left = left + parent.offsetWidth + this.margin
      },
      top: () => {
        style.left = left - this.state.width / 2 + parent.offsetWidth / 2
        style.top = top - this.state.height - this.margin
      },
      bottom: () => {
        style.left = left - this.state.width / 2 + parent.offsetWidth / 2
        style.top = top + parent.offsetHeight + this.margin
      },
    }

    const stylesFromArrow = {
      left: () => {
        style.left = left + parent.offsetWidth / 2 - this.margin
      },
      right: () => {
        style.left = left - this.state.width + parent.offsetWidth / 2 + this.margin
      },
      top: () => {
        style.top = top + parent.offsetHeight / 2 - this.margin
      },
      bottom: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height + this.margin
      },
    }

    executeFunctionIfExist(stylesFromPosition, position)
    executeFunctionIfExist(stylesFromArrow, arrow)

    return style
  }
  checkWindowPosition(style, arrowStyle) {
    if (this.props.position === 'top' || this.props.position === 'bottom') {
      if (style.left < 0) {
        const parent = this.props.parentEl
        if (parent && this.tooltipref) {
          const tooltipWidth = this.tooltipref.offsetWidth
          const parentWidth = parent.offsetWidth
          const newBgRight = Math.round(arrowStyle.bgStyle.right - style.left + this.margin)
          arrowStyle = Object.assign({}, arrowStyle, {
            bgStyle: Object.assign({}, arrowStyle.bgStyle, {right: newBgRight}),
            fgStyle: Object.assign({}, arrowStyle.fgStyle, {right: newBgRight + 1})
          })
        }
        style.left = this.margin
      }
      else {
        let rightOffset = style.left + this.state.width - window.innerWidth
        if (rightOffset > 0) {
          let originalLeft = style.left
          style.left = window.innerWidth - this.state.width - this.margin
          arrowStyle.fgStyle.marginLeft += originalLeft - style.left
          arrowStyle.bgStyle.marginLeft += originalLeft - style.left
        }
      }
    }
    return {style, arrowStyle}
  }
  handleMouseEnter() {
    this.props.active && this.setState({hover: true})
  }
  handleMouseLeave() {
    this.setState({hover: false})
  }
  componentDidMount() {
    this.updateSize()
  }
  componentWillReceiveProps() {
    this.setState({transition: this.state.hover || this.props.active ? 'all' : 'opacity'}, () => {
      this.updateSize()
    })
  }
  updateSize() {
    let self = ReactDOM.findDOMNode(this)
    this.setState({
      width: self.offsetWidth,
      height: self.offsetHeight
    })
  }
  render() {
    let {style, arrowStyle} = this.checkWindowPosition(this.getGlobalStyle(), this.getArrowStyle())

    return (

 this.tooltipref = node} style={style} onMouseEnter={::this.handleMouseEnter} onMouseLeave={::this.handleMouseLeave}>
        {this.props.arrow ? (

)
          : null
        }
        {this.props.children}

    )
  }
}

var portalNodes = {}

export default class ToolTip extends React.Component {
  static propTypes = {
    parent: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.object
    ]).isRequired,
    active: PropTypes.bool,
    group: PropTypes.string,
    tooltipTimeout: PropTypes.number
  }
  static defaultProps = {
    active: false,
    group: 'main',
    tooltipTimeout: 500
  }
  componentDidMount() {
    if (!this.props.active) {
      return
    }

    this.renderPortal(this.props)
  }
  componentWillReceiveProps(nextProps) {
    if ((!portalNodes[this.props.group] && !nextProps.active) ||
      (!this.props.active && !nextProps.active)) {
      return
    }

    let props = assign({}, nextProps)
    let newProps = assign({}, nextProps)

    if (portalNodes[this.props.group] && portalNodes[this.props.group].timeout) {
      clearTimeout(portalNodes[this.props.group].timeout)
    }

    if (this.props.active && !props.active) {
      newProps.active = true
      portalNodes[this.props.group].timeout = setTimeout(() => {
        props.active = false
        this.renderPortal(props)
      }, this.props.tooltipTimeout)
    }

    this.renderPortal(newProps)
  }
  componentWillUnmount() {
    if (portalNodes[this.props.group]) {
      ReactDOM.unmountComponentAtNode(portalNodes[this.props.group].node)
      clearTimeout(portalNodes[this.props.group].timeout)
    }
  }
  createPortal() {
    portalNodes[this.props.group] = {
      node: document.createElement('div'),
      timeout: false
    }
    portalNodes[this.props.group].node.className = 'ToolTipPortal'
    document.body.appendChild(portalNodes[this.props.group].node)
  }
  renderPortal(props) {
    if (!portalNodes[this.props.group]) {
      this.createPortal()
    }
    let {parent, ...other} = props
    let parentEl = typeof parent === 'string' ? document.querySelector(parent) : parent
    renderSubtreeIntoContainer(this, <Card parentEl={parentEl} {...other}/>, portalNodes[this.props.group].node)
  }
  shouldComponentUpdate() {
    return false
  }
  render() {
    return null
  }
}

const executeFunctionIfExist = (object, key) => {
  if (Object.prototype.hasOwnProperty.call(object, key)){
    object[key]()
  }
}
<commit_msg>fix arrow positioning with arrow center<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM, {unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer} from 'react-dom'
import assign from 'object-assign'

let FG_SIZE = 8
let BG_SIZE = 9

class Card extends React.Component {
  static propTypes = {
    active: PropTypes.bool,
    position: PropTypes.oneOf([
      'top',
      'right',
      'bottom',
      'left'
    ]),
    arrow: PropTypes.oneOf([
      null,
      'center',
      'top',
      'right',
      'bottom',
      'left'
    ]),
    style: PropTypes.object
  }
  static defaultProps = {
    active: false,
    position: 'right',
    arrow: null,
    style: {style: {}, arrowStyle: {}}
  }
  state = {
    hover: false,
    transition: 'opacity',
    width: 0,
    height: 0
  }
  margin = 15
  defaultArrowStyle = {
    color: '#fff',
    borderColor: 'rgba(0,0,0,.4)'
  }
  getGlobalStyle() {
    if (!this.props.parentEl) {
      return {display: 'none'}
    }

    let style = {
      position: 'absolute',
      padding: '5px',
      background: '#fff',
      boxShadow: '0 0 8px rgba(0,0,0,.3)',
      borderRadius: '3px',
      transition: `${this.state.transition} .3s ease-in-out, visibility .3s ease-in-out`,
      opacity: this.state.hover || this.props.active ? 1 : 0,
      visibility: this.state.hover || this.props.active ? 'visible' : 'hidden',
      zIndex: 50
    }

    assign(style, this.getStyle(this.props.position, this.props.arrow))

    return this.mergeStyle(style, this.props.style.style)
  }
  getBaseArrowStyle() {
    return {
      position: 'absolute',
      content: '""',
      transition: 'all .3s ease-in-out'
    }
  }
  getArrowStyle() {
    let fgStyle = this.getBaseArrowStyle()
    let bgStyle = this.getBaseArrowStyle()
    fgStyle.zIndex = 60
    bgStyle.zIndex = 55

    let arrowStyle = assign(this.defaultArrowStyle, this.props.style.arrowStyle)
    let bgBorderColor = arrowStyle.borderColor ? arrowStyle.borderColor : 'transparent'

    let fgColorBorder = `10px solid ${arrowStyle.color}`
    let fgTransBorder = `${FG_SIZE}px solid transparent`
    let bgColorBorder = `11px solid ${bgBorderColor}`
    let bgTransBorder = `${BG_SIZE}px solid transparent`

    let {position, arrow} = this.props

    if (position === 'left' || position === 'right') {
      fgStyle.top = '50%'
      fgStyle.borderTop = fgTransBorder
      fgStyle.borderBottom = fgTransBorder
      fgStyle.marginTop = -7

      bgStyle.borderTop = bgTransBorder
      bgStyle.borderBottom = bgTransBorder
      bgStyle.top = '50%'
      bgStyle.marginTop = -8

      if (position === 'left') {
        fgStyle.right = -10
        fgStyle.borderLeft = fgColorBorder
        bgStyle.right = -11
        bgStyle.borderLeft = bgColorBorder
      }
      else {
        fgStyle.left = -10
        fgStyle.borderRight = fgColorBorder
        bgStyle.left = -11
        bgStyle.borderRight = bgColorBorder
      }

      if (arrow === 'top') {
        fgStyle.top = this.margin
        bgStyle.top = this.margin
      }
      if (arrow === 'bottom') {
        fgStyle.top = null
        fgStyle.bottom = this.margin - 7
        bgStyle.top = null
        bgStyle.bottom = this.margin - 8
      }
    }
    else {
      fgStyle.left = '50%'
      fgStyle.marginLeft = -10
      fgStyle.borderLeft = fgTransBorder
      fgStyle.borderRight = fgTransBorder
      bgStyle.left = '50%'
      bgStyle.marginLeft = -11
      bgStyle.borderLeft = bgTransBorder
      bgStyle.borderRight = bgTransBorder

      if (position === 'top') {
        fgStyle.bottom = -10
        fgStyle.borderTop = fgColorBorder
        bgStyle.bottom = -11
        bgStyle.borderTop = bgColorBorder
      }
      else {
        fgStyle.top = -10
        fgStyle.borderBottom = fgColorBorder
        bgStyle.top = -11
        bgStyle.borderBottom = bgColorBorder
      }

      if (arrow === 'right') {
        fgStyle.left = null
        fgStyle.right = this.margin + 1 - FG_SIZE
        fgStyle.marginLeft = 0
        bgStyle.left = null
        bgStyle.right = this.margin - FG_SIZE
        bgStyle.marginLeft = 0
      }
      if (arrow === 'left') {
        fgStyle.left = this.margin + 1 - FG_SIZE
        fgStyle.marginLeft = 0
        bgStyle.left = this.margin - FG_SIZE
        bgStyle.marginLeft = 0
      }
    }

    let {color, borderColor, ...propsArrowStyle} = this.props.style.arrowStyle

    return {
      fgStyle: this.mergeStyle(fgStyle, propsArrowStyle),
      bgStyle: this.mergeStyle(bgStyle, propsArrowStyle)
    }
  }
  mergeStyle(style, theme) {
    if (theme) {
      let {position, top, left, right, bottom, marginLeft, marginRight, ...validTheme} = theme

      return assign(style, validTheme)
    }

    return style
  }
  getStyle(position, arrow) {
    let parent = this.props.parentEl
    let tooltipPosition = parent.getBoundingClientRect()
    let scrollY = (window.scrollY !== undefined) ? window.scrollY : window.pageYOffset
    let scrollX = (window.scrollX !== undefined) ? window.scrollX : window.pageXOffset
    let top = scrollY + tooltipPosition.top
    let left = scrollX + tooltipPosition.left
    let style = {}

    const stylesFromPosition = {
      left: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height / 2
        style.left = left - this.state.width - this.margin
      },
      right: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height / 2
        style.left = left + parent.offsetWidth + this.margin
      },
      top: () => {
        style.left = left - this.state.width / 2 + parent.offsetWidth / 2
        style.top = top - this.state.height - this.margin
      },
      bottom: () => {
        style.left = left - this.state.width / 2 + parent.offsetWidth / 2
        style.top = top + parent.offsetHeight + this.margin
      },
    }

    const stylesFromArrow = {
      left: () => {
        style.left = left + parent.offsetWidth / 2 - this.margin
      },
      right: () => {
        style.left = left - this.state.width + parent.offsetWidth / 2 + this.margin
      },
      top: () => {
        style.top = top + parent.offsetHeight / 2 - this.margin
      },
      bottom: () => {
        style.top = top + parent.offsetHeight / 2 - this.state.height + this.margin
      },
    }

    executeFunctionIfExist(stylesFromPosition, position)
    executeFunctionIfExist(stylesFromArrow, arrow)

    return style
  }
  checkWindowPosition(style, arrowStyle) {
    if (this.props.position === 'top' || this.props.position === 'bottom') {
      if (style.left < 0) {
        const parent = this.props.parentEl
        if (parent && this.tooltipref) {
          const tooltipWidth = this.tooltipref.offsetWidth
          let bgStyleRight = arrowStyle.bgStyle.right
          // If it's arrow center
          if (!bgStyleRight) {
            bgStyleRight = (tooltipWidth / 2) - (BG_SIZE / 2)
          }
          const newBgRight = Math.round(bgStyleRight - style.left + this.margin)
          arrowStyle = Object.assign({}, arrowStyle, {
            bgStyle: Object.assign({}, arrowStyle.bgStyle, {right: newBgRight, left: null}),
            fgStyle: Object.assign({}, arrowStyle.fgStyle, {right: newBgRight + 1, left: null})
          })
        }
        style.left = this.margin
      }
      else {
        let rightOffset = style.left + this.state.width - window.innerWidth
        if (rightOffset > 0) {
          let originalLeft = style.left
          style.left = window.innerWidth - this.state.width - this.margin
          arrowStyle.fgStyle.marginLeft += originalLeft - style.left
          arrowStyle.bgStyle.marginLeft += originalLeft - style.left
        }
      }
    }
    return {style, arrowStyle}
  }
  handleMouseEnter() {
    this.props.active && this.setState({hover: true})
  }
  handleMouseLeave() {
    this.setState({hover: false})
  }
  componentDidMount() {
    this.updateSize()
  }
  componentWillReceiveProps() {
    this.setState({transition: this.state.hover || this.props.active ? 'all' : 'opacity'}, () => {
      this.updateSize()
    })
  }
  updateSize() {
    let self = ReactDOM.findDOMNode(this)
    this.setState({
      width: self.offsetWidth,
      height: self.offsetHeight
    })
  }
  render() {
    let {style, arrowStyle} = this.checkWindowPosition(this.getGlobalStyle(), this.getArrowStyle())

    return (

 this.tooltipref = node} style={style} onMouseEnter={::this.handleMouseEnter} onMouseLeave={::this.handleMouseLeave}>
        {this.props.arrow ? (

)
          : null
        }
        {this.props.children}

    )
  }
}

var portalNodes = {}

export default class ToolTip extends React.Component {
  static propTypes = {
    parent: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.object
    ]).isRequired,
    active: PropTypes.bool,
    group: PropTypes.string,
    tooltipTimeout: PropTypes.number
  }
  static defaultProps = {
    active: false,
    group: 'main',
    tooltipTimeout: 500
  }
  componentDidMount() {
    if (!this.props.active) {
      return
    }

    this.renderPortal(this.props)
  }
  componentWillReceiveProps(nextProps) {
    if ((!portalNodes[this.props.group] && !nextProps.active) ||
      (!this.props.active && !nextProps.active)) {
      return
    }

    let props = assign({}, nextProps)
    let newProps = assign({}, nextProps)

    if (portalNodes[this.props.group] && portalNodes[this.props.group].timeout) {
      clearTimeout(portalNodes[this.props.group].timeout)
    }

    if (this.props.active && !props.active) {
      newProps.active = true
      portalNodes[this.props.group].timeout = setTimeout(() => {
        props.active = false
        this.renderPortal(props)
      }, this.props.tooltipTimeout)
    }

    this.renderPortal(newProps)
  }
  componentWillUnmount() {
    if (portalNodes[this.props.group]) {
      ReactDOM.unmountComponentAtNode(portalNodes[this.props.group].node)
      clearTimeout(portalNodes[this.props.group].timeout)
    }
  }
  createPortal() {
    portalNodes[this.props.group] = {
      node: document.createElement('div'),
      timeout: false
    }
    portalNodes[this.props.group].node.className = 'ToolTipPortal'
    document.body.appendChild(portalNodes[this.props.group].node)
  }
  renderPortal(props) {
    if (!portalNodes[this.props.group]) {
      this.createPortal()
    }
    let {parent, ...other} = props
    let parentEl = typeof parent === 'string' ? document.querySelector(parent) : parent
    renderSubtreeIntoContainer(this, <Card parentEl={parentEl} {...other}/>, portalNodes[this.props.group].node)
  }
  shouldComponentUpdate() {
    return false
  }
  render() {
    return null
  }
}

const executeFunctionIfExist = (object, key) => {
  if (Object.prototype.hasOwnProperty.call(object, key)){
    object[key]()
  }
}
<|endoftext|>


<commit_before>/*
* @Author: Mike Reich
* @Date:   2016-02-05 07:45:34
* @Last Modified 2016-02-12
*/

'use strict';

import Promise from 'bluebird'
import _ from 'underscore'

export default class Pipeliner {
  constructor(app) {
    this.app = app
    this._pipelines = {}

    this.app.get('pipeliner').use(this)
    .gather('pipeline')
    .gather('task')
    .respond('getPipelines')
    .respond('getPipeline')
    .respond('run')
    .respond('stages')

    this.app.log.debug('Pipeliner setting up')
  }

  pipeline(pipeline) {
    this.app.log.debug('Creating pipeline')
    if(this._pipelines[pipeline]) return
    this._pipelines[pipeline] = {
      'collect': [],
      'process': [],
      'generate': []
    }
  }

  stages(stages, pipeline) {
    if(!this._pipelines[pipeline]) this.pipeline(pipeline)
    stages.each(stage => this._pipelines[pipeline][stage] = [])
  }

  task(pipeline, stage, job) {
    this.app.log.debug('Registering job', pipeline, stage)
    if(!this._pipelines[pipeline]) this.pipeline(pipeline)
    if(!this._pipelines[pipeline][stage]) this._pipelines[pipeline][stage] = []
    this._pipelines[pipeline][stage].push(job)
  }

  getPipelines() {
    return this._pipelines
  }

  getPipeline(pipeline) {
    return this._pipelines[pipeline]
  }

  run(pipeline, ...args) {
    this.app.log.debug('Running pipeline', pipeline)
    if(!this._pipelines[pipeline]) throw new Error('The specified pipeline \''+pipeline+'\' doesn\'t exist.')
    let _jobs = []
    for (let stage in this._pipelines[pipeline]) {
      _jobs = _jobs.concat(this._pipelines[pipeline][stage])
    }
    return Promise.mapSeries(_jobs, (job) => {return Promise.resolve(job(...args))})
  }
} <commit_msg>Added documentation<commit_after>/*
* @Author: Mike Reich
* @Date:   2016-02-05 07:45:34
* @Last Modified 2016-02-12
*/

'use strict';

import Promise from 'bluebird'
import _ from 'underscore'

/**
 * @class The Pipeliner class is a Nxus module for creating and running data pipelines.
 * Pipelines can have any number of stages, and stages can have any number of tasks.
 *
 * @example
 * let myTask = (data) => {
 *   data.word.toUpperCase()
 * }
 * let data = {word: 'hello'}
 *
 * let pipeliner = app.get('pipeliner')
 *
 * pipeliner.pipeline('capitalize')
 * pipeliner.task('capitalize', 'process', myTask)
 * pipeliner.run('capitalize', data).then(() => {
 *   console.log('data') // {word: 'HELLO'}
 * })
 *
 */
export default class Pipeliner {
  constructor(app) {
    this.app = app
    this._pipelines = {}

    this.app.get('pipeliner').use(this)
    .gather('pipeline')
    .gather('task')
    .respond('getPipelines')
    .respond('getPipeline')
    .respond('run')
    .respond('stages')

    this.app.log.debug('Pipeliner setting up')
  }

  /**
   * Create a new pipeline.
   * @param  {string} pipeline The name of the pipeline to create
   */
  pipeline(pipeline) {
    this.app.log.debug('Creating pipeline')
    if(this._pipelines[pipeline]) return
    this._pipelines[pipeline] = {
      'collect': [],
      'process': [],
      'generate': []
    }
  }

  /**
   * Define stages for a pipeline
   * @param  {array} stages   An array of strings, each string being the name of a stage. Stages are executed in the order in the array.
   * @param  {string} pipeline The name of the pipeline to use the stages.
   * @return {[type]}          [description]
   */
  stages(stages, pipeline) {
    if(!this._pipelines[pipeline]) this.pipeline(pipeline)
    stages.each(stage => this._pipelines[pipeline][stage] = [])
  }

  /**
   * Defintes a task for a pipeline and a stage.
   * @param  {string} pipeline The name of the pipeline
   * @param  {string} stage    The name of the string
   * @param  {function} job      A function which accepts data
   */
  task(pipeline, stage, job) {
    this.app.log.debug('Registering job', pipeline, stage)
    if(!this._pipelines[pipeline]) this.pipeline(pipeline)
    if(!this._pipelines[pipeline][stage]) this._pipelines[pipeline][stage] = []
    this._pipelines[pipeline][stage].push(job)
  }

  /**
   * Returns all pipelines which have been defined
   * @return {object} A hash of the pipelines.
   */
  getPipelines() {
    return this._pipelines
  }

  /**
   * Returns a specific pipeline
   * @param  {string} pipeline The name of a pipeline to return.
   * @return {object}          The pipeline object.
   */
  getPipeline(pipeline) {
    return this._pipelines[pipeline]
  }

  /**
   * Runs the specified pipeline, passing the arguments to each task.
   * @param  {string}    pipeline the pipeline to run
   * @param  {...object} args     Arguments to pass to the pipeline tasks
   * @return {Promise}             A promise that is executed when the pipeline completes.
   */
  run(pipeline, ...args) {
    this.app.log.debug('Running pipeline', pipeline)
    if(!this._pipelines[pipeline]) throw new Error('The specified pipeline \''+pipeline+'\' doesn\'t exist.')
    let _jobs = []
    for (let stage in this._pipelines[pipeline]) {
      _jobs = _jobs.concat(this._pipelines[pipeline][stage])
    }
    return Promise.mapSeries(_jobs, (job) => {return Promise.resolve(job(...args))})
  }
} <|endoftext|>


<commit_before>'use strict';

var assert = require('assert');
var _ = require('lodash');
var URI = require('URIjs');
var mimeDB = require('mime-db');
var api = require('json-schema-compatibility');
var jp = require('jsonpath');
var traverse = require('traverse');

exports.convert = function (data) {
  assert.equal(data.discoveryVersion, 'v1');
  assert.equal(data.protocol, 'rest');

  //fields that doesn't map to anything:
  //	id
  //	name
  //	revision
  //	icons
  //	batchPath
  //	labels
  //	features

  //deprecated:
  //	baseUrl
  //	basePath

  var rootUrl = URI(data.rootUrl);
  var srGlobalRefParameters = [];
  var srGlobalParameters = processGlobalParameters(data.parameters, srGlobalRefParameters);

  var swagger = {
    swagger: '2.0',
    info: {
      title: data.title,
      description: data.description,
      contact: {
        name: data.ownerName,
        url: 'https://' + data.ownerDomain,
      },
      version: data.version,
    },
    host: rootUrl.host(),
    basePath: '/' + data.servicePath.replace(/^\/|\/$/, ''),
    schemes: [rootUrl.scheme()],
    paths: processResource(data, srGlobalRefParameters),
    definitions: processDefinitions(data.schemas),
    parameters: srGlobalParameters,
    securityDefinitions: processAuth(data.auth)
  };

  if (data.documentationLink)
    swagger.externalDocs = { url: data.documentationLink };

  removeUndefined(swagger);
  return swagger;
};

function processAuth(auth) {
  if (auth === undefined)
    return undefined;

  //For now Google use only Oauth2.0
  assert(Object.keys(auth).length === 1);
  assert(Object.keys(auth)[0] === 'oauth2');

  var scopes = auth.oauth2.scopes;
  var srScopes = {};
  for (var name in scopes)
    srScopes[name] = scopes[name].description;

  return {
    Oauth2: {
      type: 'oauth2',
      description: 'Oauth 2.0 authentication',
      flow: 'implicit',
      authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
      scopes: srScopes
    }
  };
}

function processGlobalParameters(parameters, srGlobalRefParameters) {
  var srGlobalParameters = {};
  for (var name in parameters) {
    srGlobalParameters[name] = processParameter(name, parameters[name]);
    srGlobalRefParameters.push({$ref: '#/parameters/' + name});
  }
  return srGlobalParameters;
}

function fixRef(ref) {
  return '#/definitions/' + ref;
}

function applyOnProperty(schema, name, type, cb) {
  var path = '$..*["' + name + '"]';
  jp.apply(schema, path , function (value) {
    if (typeof value !== type)
      return value;
    return cb(value);
  });
}

function processDefinitions(schemas) {
  if (schemas === undefined)
    return undefined;

  schemas = api.v4(schemas);
  applyOnProperty(schemas, '$ref', 'string', fixRef);

  //HACK: Swagger doesn't support full JSON Schema
  applyOnProperty(schemas, 'id', 'string', _.noop);
  applyOnProperty(schemas, 'enumDescriptions', 'object', function (value) {
    if (_.isArray(value))
      return undefined;
    return value;
  });
  applyOnProperty(schemas, 'annotations', 'object', function (value) {
    var keys = _.keys(value);
    if (_.isEqual(keys, ['required']) && _.isArray(value.required))
      return undefined;
    return value;
  });

  //TODO: delete, after PR will be acepted
  applyOnProperty(schemas, 'type', 'string', function (value) {
    if (value === 'any')
      return undefined;
    return value;
  });

  //Google for some reason code minimum/maximum as strings
  function convertInt(value) {
    if (typeof value === 'string')
      return parseInt(value);
    return value;
  }
  jp.apply(schemas, '$..*.minimum' , convertInt);
  jp.apply(schemas, '$..*.maximum' , convertInt);

  return schemas;
}

function processResource(data, srGlobalRefParameters) {
  var srPaths = processMethodList(data);

  if ('resources' in data) {
    _.each(data.resources, function (subResource, name) {
      var srSubPaths = processSubResource(data.resources[name]);

      //Add top-level resource name as tag to all sub-methods.
      _.each(srSubPaths, function (srPath) {
        _.each(srPath, function (srOperation) {
          srOperation.tags = [name];
        });
      });

      srPaths = _.merge(srPaths, srSubPaths);
    });
  }

  //Add reference to global parameters
  _.each(srPaths, function (srPath) {
    srPath.parameters = srGlobalRefParameters;
  });
  return srPaths;
}

function processMethodList(data) {
  if (!('methods' in data))
    return {};

  var srPaths = {};
  for (var key in data.methods) {
    var method = data.methods[key];
    var httpMethod = method.httpMethod.toLowerCase();
    var path = method.path;
    if (path[0] !== '/')
      path = '/' + path;

    if (!(path in srPaths))
      srPaths[path] = { };
    srPaths[path][httpMethod] = processMethod(method);
  }
  return srPaths;
}

function processSubResource(data) {
  var srPaths = processMethodList(data);

  if (!('resources' in data))
    return srPaths;

  for (var name in data.resources) {
    var srSubPaths = processSubResource(data.resources[name]);
    srPaths = _.merge(srPaths, srSubPaths);
  }
  return srPaths;
}

function globMime(pattern) {
  if (pattern === '*/*')
    return ['application/octet-stream'];

  var slashIdx = pattern.indexOf('/');
  if (slashIdx === -1 || pattern.slice(slashIdx + 1) !== '*')
    return [pattern];

  var prefix = pattern.slice(0,slashIdx+1);
  var result = [];
  _.each(mimeDB, function (dummy, name) {
    if (name.slice(0, slashIdx+1) === prefix)
      result.push(name);
  })
  return result;
}

function convertMime(list) {
  var result = [];
  _.each(list, function (pattern) {
    _.each(globMime(pattern), function (name) {
      //skip duplicates
      if (result.indexOf(name) !== -1)
        return;
      result.push(name);
    });
  });
  return result;
}

function processMethod(method) {
  var srResponse = {
    description: 'Successful response',
  };

  var srMethod = {
    description: method.description,
    operationId: method.id,
    responses: {
      200 : srResponse
    },
  };

  //TODO: implement file upload/download
  //  * rest of fields in 'mediaUpload'
  //  * 'supportsMediaDownload' https://code.google.com/p/google-api-go-client/issues/detail?id=16
  if (method.supportsMediaUpload)
    srMethod.consumes = convertMime(method.mediaUpload.accept);

  //TODO: convert data.supportsSubscription

  if ('parameters' in method)
    srMethod.parameters = processParameterList(method);

  //TODO: convert data.request

  if ('response' in method) {
    assert('$ref' in method.response);
    srResponse.schema = {
      $ref: fixRef(method.response.$ref)
    };
  }

  if ('scopes' in method)
    srMethod.security = [{ Oauth2: method.scopes}];

  return srMethod;
}

function processParameterList(method) {
  var parameters = method.parameters;
  var paramOrder = method.paramOreder || [];

  var srParameters = paramOrder.map(function (name) {
    return processParameter(name, parameters[name]);
  });

  for (var name in parameters) {
    if (paramOrder.indexOf(name) !== -1)
      continue;
    var srParam = processParameter(name, parameters[name]);
    srParameters.push(srParam);
  }

  return srParameters;
}

function processParameter(name, param) {
  assert(!('$ref' in param));
  assert(param.location === 'query' || param.location === 'path');
  assert(['string', 'number', 'integer', 'boolean'].indexOf(param.type) >= 0);
  assert(!('properties' in param));
  assert(!('additionalProperties' in param));
  assert(!('annotations' in param));

  fixDefault(param);

  var srParam = {
    name: name,
    in: param.location,
    description: param.description,
    required: param.required,
    default: param.default
  };

  if (param.repeated) {
    _.extend(srParam, {
      type: 'array',
      items: processType(param),
      collectionFormat: ((srParam.in === 'path') ? 'csv' : 'multi')
    });
  }
  else
    _.extend(srParam, processType(param));

  return srParam;
}

function processType(type) {
  var srType = {
    type: type.type,
    enum: type.enum,
    minimum: (type.minimum ? parseInt(type.minimum) : undefined),
    maximum: (type.maximum ? parseInt(type.maximum) : undefined)
  };

  //TODO: convert format.
  //if ('format' in type) {

  //TODO: use strings from type.enumDescriptions
  return srType;
}

function fixDefault(param) {
  //Google for some reason encode default values for enums like that
  //SOME_PREFIX_VALUE
  //That mean we need convert to lower case and strip prefix.
  if ('enum' in param && typeof param.default === 'string' &&
     param.enum.indexOf(param.default) === -1)
  {
    var lower = param.default.toLowerCase();
    var candidate;
    _.each(param.enum, function (value) {
      if (lower.slice(-value.length) === value) {
         assert(candidate === undefined);
         candidate = value;
      }
    });
    assert(candidate !== undefined);
    param.default = candidate;
  }
}

function removeUndefined(obj) {
  traverse(obj).forEach(function (value) {
    if (value === undefined)
      this.remove();
  });
}
<commit_msg>If param's default can't be fixed remove it.<commit_after>'use strict';

var assert = require('assert');
var _ = require('lodash');
var URI = require('URIjs');
var mimeDB = require('mime-db');
var jsonCompat = require('json-schema-compatibility');
var jp = require('jsonpath');
var traverse = require('traverse');

exports.convert = function (data) {
  assert.equal(data.discoveryVersion, 'v1');
  assert.equal(data.protocol, 'rest');

  //fields that doesn't map to anything:
  //	id
  //	name
  //	revision
  //	icons
  //	batchPath
  //	labels
  //	features

  //deprecated:
  //	baseUrl
  //	basePath

  var rootUrl = URI(data.rootUrl);
  var srGlobalRefParameters = [];
  var srGlobalParameters = processGlobalParameters(data.parameters, srGlobalRefParameters);

  var swagger = {
    swagger: '2.0',
    info: {
      title: data.title,
      description: data.description,
      contact: {
        name: data.ownerName,
        url: 'https://' + data.ownerDomain,
      },
      version: data.version,
    },
    host: rootUrl.host(),
    basePath: '/' + data.servicePath.replace(/^\/|\/$/, ''),
    schemes: [rootUrl.scheme()],
    paths: processResource(data, srGlobalRefParameters),
    definitions: processDefinitions(data.schemas),
    parameters: srGlobalParameters,
    securityDefinitions: processAuth(data.auth)
  };

  if (data.documentationLink)
    swagger.externalDocs = { url: data.documentationLink };

  removeUndefined(swagger);
  return swagger;
};

function processAuth(auth) {
  if (auth === undefined)
    return undefined;

  //For now Google use only Oauth2.0
  assert(Object.keys(auth).length === 1);
  assert(Object.keys(auth)[0] === 'oauth2');

  var scopes = auth.oauth2.scopes;
  var srScopes = {};
  for (var name in scopes)
    srScopes[name] = scopes[name].description;

  return {
    Oauth2: {
      type: 'oauth2',
      description: 'Oauth 2.0 authentication',
      flow: 'implicit',
      authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
      scopes: srScopes
    }
  };
}

function processGlobalParameters(parameters, srGlobalRefParameters) {
  var srGlobalParameters = {};
  for (var name in parameters) {
    srGlobalParameters[name] = processParameter(name, parameters[name]);
    srGlobalRefParameters.push({$ref: '#/parameters/' + name});
  }
  return srGlobalParameters;
}

function fixRef(ref) {
  return '#/definitions/' + ref;
}

function applyOnProperty(schema, name, type, cb) {
  var path = '$..*["' + name + '"]';
  jp.apply(schema, path , function (value) {
    if (typeof value !== type)
      return value;
    return cb(value);
  });
}

function processDefinitions(schemas) {
  if (schemas === undefined)
    return undefined;

  schemas = jsonCompat.v4(schemas);
  applyOnProperty(schemas, '$ref', 'string', fixRef);

  //HACK: Swagger doesn't support full JSON Schema
  applyOnProperty(schemas, 'id', 'string', _.noop);
  applyOnProperty(schemas, 'enumDescriptions', 'object', function (value) {
    if (_.isArray(value))
      return undefined;
    return value;
  });
  applyOnProperty(schemas, 'annotations', 'object', function (value) {
    var keys = _.keys(value);
    if (_.isEqual(keys, ['required']) && _.isArray(value.required))
      return undefined;
    return value;
  });

  //TODO: delete, after PR will be acepted
  applyOnProperty(schemas, 'type', 'string', function (value) {
    if (value === 'any')
      return undefined;
    return value;
  });

  //Google for some reason code minimum/maximum as strings
  function convertInt(value) {
    if (typeof value === 'string')
      return parseInt(value);
    return value;
  }
  jp.apply(schemas, '$..*.minimum' , convertInt);
  jp.apply(schemas, '$..*.maximum' , convertInt);

  return schemas;
}

function processResource(data, srGlobalRefParameters) {
  var srPaths = processMethodList(data);

  if ('resources' in data) {
    _.each(data.resources, function (subResource, name) {
      var srSubPaths = processSubResource(data.resources[name]);

      //Add top-level resource name as tag to all sub-methods.
      _.each(srSubPaths, function (srPath) {
        _.each(srPath, function (srOperation) {
          srOperation.tags = [name];
        });
      });

      srPaths = _.merge(srPaths, srSubPaths);
    });
  }

  //Add reference to global parameters
  _.each(srPaths, function (srPath) {
    srPath.parameters = srGlobalRefParameters;
  });
  return srPaths;
}

function processMethodList(data) {
  if (!('methods' in data))
    return {};

  var srPaths = {};
  for (var key in data.methods) {
    var method = data.methods[key];
    var httpMethod = method.httpMethod.toLowerCase();
    var path = method.path;
    if (path[0] !== '/')
      path = '/' + path;

    if (!(path in srPaths))
      srPaths[path] = { };
    srPaths[path][httpMethod] = processMethod(method);
  }
  return srPaths;
}

function processSubResource(data) {
  var srPaths = processMethodList(data);

  if (!('resources' in data))
    return srPaths;

  for (var name in data.resources) {
    var srSubPaths = processSubResource(data.resources[name]);
    srPaths = _.merge(srPaths, srSubPaths);
  }
  return srPaths;
}

function globMime(pattern) {
  if (pattern === '*/*')
    return ['application/octet-stream'];

  var slashIdx = pattern.indexOf('/');
  if (slashIdx === -1 || pattern.slice(slashIdx + 1) !== '*')
    return [pattern];

  var prefix = pattern.slice(0,slashIdx+1);
  var result = [];
  _.each(mimeDB, function (dummy, name) {
    if (name.slice(0, slashIdx+1) === prefix)
      result.push(name);
  })
  return result;
}

function convertMime(list) {
  var result = [];
  _.each(list, function (pattern) {
    _.each(globMime(pattern), function (name) {
      //skip duplicates
      if (result.indexOf(name) !== -1)
        return;
      result.push(name);
    });
  });
  return result;
}

function processMethod(method) {
  var srResponse = {
    description: 'Successful response',
  };

  var srMethod = {
    description: method.description,
    operationId: method.id,
    responses: {
      200 : srResponse
    },
  };

  //TODO: implement file upload/download
  //  * rest of fields in 'mediaUpload'
  //  * 'supportsMediaDownload' https://code.google.com/p/google-api-go-client/issues/detail?id=16
  if (method.supportsMediaUpload)
    srMethod.consumes = convertMime(method.mediaUpload.accept);

  //TODO: convert data.supportsSubscription

  if ('parameters' in method)
    srMethod.parameters = processParameterList(method);

  //TODO: convert data.request

  if ('response' in method) {
    assert('$ref' in method.response);
    srResponse.schema = {
      $ref: fixRef(method.response.$ref)
    };
  }

  if ('scopes' in method)
    srMethod.security = [{ Oauth2: method.scopes}];

  return srMethod;
}

function processParameterList(method) {
  var parameters = method.parameters;
  var paramOrder = method.paramOreder || [];

  var srParameters = paramOrder.map(function (name) {
    return processParameter(name, parameters[name]);
  });

  for (var name in parameters) {
    if (paramOrder.indexOf(name) !== -1)
      continue;
    var srParam = processParameter(name, parameters[name]);
    srParameters.push(srParam);
  }

  return srParameters;
}

function processParameter(name, param) {
  assert(!('$ref' in param));
  assert(param.location === 'query' || param.location === 'path');
  assert(['string', 'number', 'integer', 'boolean'].indexOf(param.type) >= 0);
  assert(!('properties' in param));
  assert(!('additionalProperties' in param));
  assert(!('annotations' in param));

  fixDefault(param);

  var srParam = {
    name: name,
    in: param.location,
    description: param.description,
    required: param.required,
    default: param.default
  };

  if (param.repeated) {
    _.extend(srParam, {
      type: 'array',
      items: processType(param),
      collectionFormat: ((srParam.in === 'path') ? 'csv' : 'multi')
    });
  }
  else
    _.extend(srParam, processType(param));

  return srParam;
}

function processType(type) {
  var srType = {
    type: type.type,
    enum: type.enum,
    minimum: (type.minimum ? parseInt(type.minimum) : undefined),
    maximum: (type.maximum ? parseInt(type.maximum) : undefined)
  };

  //TODO: convert format.
  //if ('format' in type) {

  //TODO: use strings from type.enumDescriptions
  return srType;
}

function fixDefault(param) {
  //Google for some reason encode default values for enums like that
  //SOME_PREFIX_VALUE
  //That mean we need convert to lower case and strip prefix.
  if ('enum' in param && typeof param.default === 'string' &&
     param.enum.indexOf(param.default) === -1)
  {
    var lower = param.default.toLowerCase();
    var candidate;
    _.each(param.enum, function (value) {
      if (lower.slice(-value.length) === value) {
         assert(candidate === undefined);
         candidate = value;
      }
    });
    //If we can't fix default when remove it.
    delete param.default;
    if (candidate !== undefined)
      param.default = candidate;
  }
}

function removeUndefined(obj) {
  traverse(obj).forEach(function (value) {
    if (value === undefined)
      this.remove();
  });
}
<|endoftext|>


<commit_before>'use strict';

var AlexaSkill = require('./AlexaSkill'),
    // recipes = require('./recipes');

var APP_ID = undefined; //OPTIONAL: replace with 'amzn1.echo-sdk-ams.app.[your-unique-value-here]';

/**
 * MinecraftHelper is a child of AlexaSkill.
 * To read more about inheritance in JavaScript, see the link below.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Inheritance
 */
var sousChef = function () {
    AlexaSkill.call(this, APP_ID);
};

// Extend AlexaSkill
sousChef.prototype = Object.create(AlexaSkill.prototype);
sousChef.prototype.constructor = sousChef;

sousChef.prototype.eventHandlers.onLaunch = function (launchRequest, session, response) {
    var speechText = "Welcome to Amazon Alexa sousChef, you can tell me what ingredients you have in your refrigerator. See what can I do for you. Please remind that you can only name maximum 3 items.";
    // If the user either does not reply to the welcome message or says something that is not
    // understood, they will be prompted again with this text.
    var repromptText = "For instructions on what you can say, please say help me.";
    response.ask(speechText, repromptText);
};

sousChef.prototype.intentHandlers = {
    // Custom Intent

    "RecipeIntent": function (intent, session, response) {
        var itemSlot = intent.slots.Item,
            itemName;
        if (itemSlot && itemSlot.value){
            itemName = itemSlot.value.toLowerCase();
        }

        var cardTitle = "Recipe for " + itemName,
            recipe = recipes[itemName],
            speechOutput,
            repromptOutput;
        if (recipe) {
            speechOutput = {
                speech: recipe,
                type: AlexaSkill.speechOutputType.PLAIN_TEXT
            };
            response.tellWithCard(speechOutput, cardTitle, recipe);
        } else {
            var speech;
            if (itemName) {
                speech = "I'm sorry, I currently do not know the recipe for " + itemName + ". What else can I help with?";
            } else {
                speech = "I'm sorry, I currently do not know that recipe. What else can I help with?";
            }
            speechOutput = {
                speech: speech,
                type: AlexaSkill.speechOutputType.PLAIN_TEXT
            };
            repromptOutput = {
                speech: "What else can I help with?",
                type: AlexaSkill.speechOutputType.PLAIN_TEXT
            };
            response.ask(speechOutput, repromptOutput);
        }
    },

    "AMAZON.StopIntent": function (intent, session, response) {
        var speechOutput = "Goodbye";
        response.tell(speechOutput);
    },

    "AMAZON.CancelIntent": function (intent, session, response) {
        var speechOutput = "Goodbye";
        response.tell(speechOutput);
    },

    "AMAZON.HelpIntent": function (intent, session, response) {
        var speechText = "you can tell me what ingredients you have in your refrigerator. Please remind that you can only name maximum 3 items... Now, Tell me what do you have in your refrigerator?";
        var repromptText = "You can say things like, I want to cook but only have certain things in my refrigerator, or you can say exit... Now, what can I help you with?";
        var speechOutput = {
            speech: speechText,
            type: AlexaSkill.speechOutputType.PLAIN_TEXT
        };
        var repromptOutput = {
            speech: repromptText,
            type: AlexaSkill.speechOutputType.PLAIN_TEXT
        };
        response.ask(speechOutput, repromptOutput);
    }
};

exports.handler = function (event, context) {
    var sousChef = new sousChef();
    sousChef.execute(event, context);
};
<commit_msg>add comments<commit_after>'use strict';

var AlexaSkill = require('./AlexaSkill'),
    // recipes = require('./recipes');

var APP_ID = undefined; //OPTIONAL: replace with 'amzn1.echo-sdk-ams.app.[your-unique-value-here]';

/**
 * MinecraftHelper is a child of AlexaSkill.
 * To read more about inheritance in JavaScript, see the link below.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Inheritance
 */
var sousChef = function () {
    AlexaSkill.call(this, APP_ID);
};

// Extend AlexaSkill
sousChef.prototype = Object.create(AlexaSkill.prototype);
sousChef.prototype.constructor = sousChef;

sousChef.prototype.eventHandlers.onLaunch = function (launchRequest, session, response) {
    var speechText = "Welcome to Amazon Alexa sousChef, you can tell me what ingredients you have in your refrigerator. See what can I do for you. Please remind that you can only name maximum 3 items.";
    // If the user either does not reply to the welcome message or says something that is not
    // understood, they will be prompted again with this text.
    var repromptText = "For instructions on what you can say, please say help me.";
    response.ask(speechText, repromptText);
};

sousChef.prototype.intentHandlers = {
    // Custom Intent
    "IngredientIntent": function (intent, session, response) { },
    "RecipeIntent": function (intent, session, response) {
        // Find by ingredient API => return the id of the dish
        // getAnlyzedRecipeInstruction API => return JSON we need to parse it
    },

    // "RecipeIntent": function (intent, session, response) {
    //     var itemSlot = intent.slots.Item,
    //         itemName;
    //     if (itemSlot && itemSlot.value){
    //         itemName = itemSlot.value.toLowerCase();
    //     }

    //     var cardTitle = "Recipe for " + itemName,
    //         recipe = recipes[itemName],
    //         speechOutput,
    //         repromptOutput;
    //     if (recipe) {
    //         speechOutput = {
    //             speech: recipe,
    //             type: AlexaSkill.speechOutputType.PLAIN_TEXT
    //         };
    //         response.tellWithCard(speechOutput, cardTitle, recipe);
    //     } else {
    //         var speech;
    //         if (itemName) {
    //             speech = "I'm sorry, I currently do not know the recipe for " + itemName + ". What else can I help with?";
    //         } else {
    //             speech = "I'm sorry, I currently do not know that recipe. What else can I help with?";
    //         }
    //         speechOutput = {
    //             speech: speech,
    //             type: AlexaSkill.speechOutputType.PLAIN_TEXT
    //         };
    //         repromptOutput = {
    //             speech: "What else can I help with?",
    //             type: AlexaSkill.speechOutputType.PLAIN_TEXT
    //         };
    //         response.ask(speechOutput, repromptOutput);
    //     }
    // },

    "AMAZON.StopIntent": function (intent, session, response) {
        var speechOutput = "Goodbye";
        response.tell(speechOutput);
    },

    "AMAZON.CancelIntent": function (intent, session, response) {
        var speechOutput = "Goodbye";
        response.tell(speechOutput);
    },

    "AMAZON.HelpIntent": function (intent, session, response) {
        var speechText = "you can tell me what ingredients you have in your refrigerator. Please remind that you can only name maximum 3 items... Now, Tell me what do you have in your refrigerator?";
        var repromptText = "You can say things like, I want to cook but only have certain things in my refrigerator, or you can say exit... Now, what can I help you with?";
        var speechOutput = {
            speech: speechText,
            type: AlexaSkill.speechOutputType.PLAIN_TEXT
        };
        var repromptOutput = {
            speech: repromptText,
            type: AlexaSkill.speechOutputType.PLAIN_TEXT
        };
        response.ask(speechOutput, repromptOutput);
    }
};

exports.handler = function (event, context) {
    var sousChef = new sousChef();
    sousChef.execute(event, context);
};
<|endoftext|>


<commit_before>import {
  app,
  BrowserWindow,
  shell,
  ipcMain,
} from 'electron';
import isDevMode from 'electron-is-dev';
import fs from 'fs-extra';
import path from 'path';
import windowStateKeeper from 'electron-window-state';
import { enforceMacOSAppLocation } from 'electron-util';

// Set app directory before loading user modules
if (process.env.FRANZ_APPDATA_DIR != null) {
  app.setPath('appData', process.env.FRANZ_APPDATA_DIR);
  app.setPath('userData', path.join(app.getPath('appData')));
} else if (process.platform === 'win32') {
  app.setPath('appData', process.env.APPDATA);
  app.setPath('userData', path.join(app.getPath('appData'), app.getName()));
}

if (isDevMode) {
  app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev'));
}

/* eslint-disable import/first */
import {
  isMac,
  isWindows,
  isLinux,
} from './environment';
import { mainIpcHandler as basicAuthHandler } from './features/basicAuth';
import ipcApi from './electron/ipc-api';
import Tray from './lib/Tray';
import Settings from './electron/Settings';
import handleDeepLink from './electron/deepLinking';
import { isPositionValid } from './electron/windowUtils';
import askFormacOSPermissions from './electron/macOSPermissions';
import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
import './electron/exception';

import {
  DEFAULT_APP_SETTINGS,
  DEFAULT_WINDOW_OPTIONS,
} from './config';
import { asarPath } from './helpers/asar-helpers';
import { isValidExternalURL } from './helpers/url-helpers';
import userAgent from './helpers/userAgent-helpers';

/* eslint-enable import/first */
const debug = require('debug')('Franz:App');

// Globally set useragent to fix user agent override in service workers
debug('Set userAgent to ', userAgent());
app.userAgentFallback = userAgent();

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
let willQuitApp = false;

// Register methods to be called once the window has been loaded.
let onDidLoadFns = [];

function onDidLoad(fn) {
  if (onDidLoadFns) {
    onDidLoadFns.push(fn);
  } else if (mainWindow) {
    fn(mainWindow);
  }
}

// Ensure that the recipe directory exists
fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp'));
fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json'));

// Set App ID for Windows
if (isWindows) {
  app.setAppUserModelId(appId);
}

// Initialize Settings
const settings = new Settings('app', DEFAULT_APP_SETTINGS);
const proxySettings = new Settings('proxy');

// add `liftSingleInstanceLock` to settings.json to override the single instance lock
const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false;

// Force single window
const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock();
if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (event, argv) => {
    // Someone tried to run a second instance, we should focus our window.
    if (mainWindow) {
      mainWindow.show();
      if (mainWindow.isMinimized()) {
        mainWindow.restore();
      }
      mainWindow.focus();

      if (isWindows) {
        onDidLoad((window) => {
          // Keep only command line / deep linked arguments
          const url = argv.slice(1);
          if (url) {
            handleDeepLink(window, url.toString());
          }

          if (argv.includes('--reset-window')) {
            // Needs to be delayed to not interfere with mainWindow.restore();
            setTimeout(() => {
              debug('Resetting windows via Task');
              window.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100);
              window.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height);
            }, 1);
          } else if (argv.includes('--quit')) {
            // Needs to be delayed to not interfere with mainWindow.restore();
            setTimeout(() => {
              debug('Quitting Franz via Task');
              app.quit();
            }, 1);
          }
        });
      }
    }
  });
}

// Fix Unity indicator issue
// https://github.com/electron/electron/issues/9046
if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) {
  process.env.XDG_CURRENT_DESKTOP = 'Unity';
}

// Disable GPU acceleration
if (!settings.get('enableGPUAcceleration')) {
  debug('Disable GPU Acceleration');
  app.disableHardwareAcceleration();
}

const createWindow = () => {
  // Remember window size
  const mainWindowState = windowStateKeeper({
    defaultWidth: DEFAULT_WINDOW_OPTIONS.width,
    defaultHeight: DEFAULT_WINDOW_OPTIONS.height,
  });

  let posX = mainWindowState.x || DEFAULT_WINDOW_OPTIONS.x;
  let posY = mainWindowState.y || DEFAULT_WINDOW_OPTIONS.y;

  if (!isPositionValid({ x: posX, y: posY })) {
    debug('Window is out of screen bounds, resetting window');
    posX = DEFAULT_WINDOW_OPTIONS.x;
    posY = DEFAULT_WINDOW_OPTIONS.y;
  }

  // Create the browser window.
  mainWindow = new BrowserWindow({
    x: posX,
    y: posY,
    width: mainWindowState.width,
    height: mainWindowState.height,
    minWidth: 600,
    minHeight: 500,
    titleBarStyle: isMac ? 'hidden' : '',
    frame: isLinux,
    backgroundColor: !settings.get('darkMode') ? '#3498db' : '#1E1E1E',
    webPreferences: {
      nodeIntegration: true,
      webviewTag: true,
      enableRemoteModule: true,
    },
  });

  mainWindow.webContents.on('did-finish-load', () => {
    const fns = onDidLoadFns;
    onDidLoadFns = null;
    for (const fn of fns) {
      fn(mainWindow);
    }
  });

  // Initialize System Tray
  const trayIcon = new Tray();

  // Initialize ipcApi
  ipcApi({
    mainWindow,
    settings: {
      app: settings,
      proxy: proxySettings,
    },
    trayIcon,
  });

  // Manage Window State
  mainWindowState.manage(mainWindow);

  // and load the index.html of the app.
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // Open the DevTools.
  if (isDevMode || process.argv.includes('--devtools')) {
    mainWindow.webContents.openDevTools();
  }

  // Windows deep linking handling on app launch
  if (isWindows) {
    onDidLoad((window) => {
      const url = process.argv.slice(1);
      if (url) {
        handleDeepLink(window, url.toString());
      }
    });
  }

  // Emitted when the window is closed.
  mainWindow.on('close', (e) => {
    debug('Window: close window');
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) {
      e.preventDefault();
      if (isWindows) {
        debug('Window: minimize');
        mainWindow.minimize();

        if (settings.get('minimizeToSystemTray')) {
          debug('Skip taskbar: true');
          mainWindow.setSkipTaskbar(true);
        }
      } else {
        debug('Window: hide');
        mainWindow.hide();
      }
    } else {
      app.quit();
    }
  });

  // For Windows we need to store a flag to properly restore the window
  // if the window was maximized before minimizing it so system tray
  mainWindow.on('minimize', () => {
    app.wasMaximized = app.isMaximized;

    if (settings.get('minimizeToSystemTray')) {
      debug('Skip taskbar: true');
      mainWindow.setSkipTaskbar(true);
      trayIcon.show();
    }
  });

  mainWindow.on('maximize', () => {
    debug('Window: maximize');
    app.isMaximized = true;
  });

  mainWindow.on('unmaximize', () => {
    debug('Window: unmaximize');
    app.isMaximized = false;
  });

  mainWindow.on('restore', () => {
    debug('Window: restore');
    mainWindow.setSkipTaskbar(false);

    if (app.wasMaximized) {
      debug('Window: was maximized before, maximize window');
      mainWindow.maximize();
    }

    if (!settings.get('enableSystemTray')) {
      debug('Tray: hiding tray icon');
      trayIcon.hide();
    }
  });

  if (isMac) {
    askFormacOSPermissions();
  }

  mainWindow.on('show', () => {
    debug('Skip taskbar: false');
    mainWindow.setSkipTaskbar(false);
  });

  app.mainWindow = mainWindow;
  app.isMaximized = mainWindow.isMaximized();

  mainWindow.webContents.on('new-window', (e, url) => {
    debug('Open url', url);
    e.preventDefault();

    if (isValidExternalURL(url)) {
      shell.openExternal(url);
    }
  });
};

// Allow passing command line parameters/switches to electron
// https://electronjs.org/docs/api/chrome-command-line-switches
// used for Kerberos support
// Usage e.g. MACOS
// $ Franz.app/Contents/MacOS/Franz --auth-server-whitelist *.mydomain.com --auth-negotiate-delegate-whitelist *.mydomain.com
const argv = require('minimist')(process.argv.slice(1));

if (argv['auth-server-whitelist']) {
  app.commandLine.appendSwitch('auth-server-whitelist', argv['auth-server-whitelist']);
}
if (argv['auth-negotiate-delegate-whitelist']) {
  app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', argv['auth-negotiate-delegate-whitelist']);
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
  // Register App URL
  app.setAsDefaultProtocolClient('franz');

  if (isDevMode) {
    app.setAsDefaultProtocolClient('franz-dev');
  }

  if (process.platform === 'win32') {
    app.setUserTasks([{
      program: process.execPath,
      arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`,
      iconPath: asarPath(path.join(isDevMode ? `${__dirname}../src/` : __dirname, 'assets/images/taskbar/win32/display.ico')),
      iconIndex: 0,
      title: 'Move Franz to Current Display',
      description: 'Restore the position and size of Franz',
    }, {
      program: process.execPath,
      arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`,
      iconIndex: 0,
      title: 'Quit Franz',
    }]);
  }

  createWindow();
});

// This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕
// TODO: rewrite to handle multiple login calls
const noop = () => null;
let authCallback = noop;

app.on('login', (event, webContents, request, authInfo, callback) => {
  authCallback = callback;
  debug('browser login event', authInfo);
  event.preventDefault();

  if (!authInfo.isProxy && authInfo.scheme === 'basic') {
    debug('basic auth handler', authInfo);
    basicAuthHandler(mainWindow, authInfo);
  }
});

// TODO: evaluate if we need to store the authCallback for every service
ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => {
  debug('Received basic auth credentials', user, '********');

  authCallback(user, password);
  authCallback = noop;
});

ipcMain.on('feature-basic-auth-cancel', () => {
  debug('Cancel basic auth');

  authCallback(null);
  authCallback = noop;
});

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (settings.get('runInBackground') === undefined
    || settings.get('runInBackground')) {
    debug('Window: all windows closed, quit app');
    app.quit();
  } else {
    debug('Window: don\'t quit app');
  }
});

app.on('before-quit', () => {
  willQuitApp = true;
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  } else {
    mainWindow.show();
  }
});

app.on('will-finish-launching', () => {
  // Protocol handler for macOS
  app.on('open-url', (event, url) => {
    event.preventDefault();

    onDidLoad((window) => {
      debug('open-url event', url);
      handleDeepLink(window, url);
    });
  });
});
<commit_msg>fix(macOS): Ask the user to move Franz to the `/Applications` Folder<commit_after>import {
  app,
  BrowserWindow,
  shell,
  ipcMain,
} from 'electron';
import isDevMode from 'electron-is-dev';
import fs from 'fs-extra';
import path from 'path';
import windowStateKeeper from 'electron-window-state';
import { enforceMacOSAppLocation } from 'electron-util';

// Set app directory before loading user modules
if (process.env.FRANZ_APPDATA_DIR != null) {
  app.setPath('appData', process.env.FRANZ_APPDATA_DIR);
  app.setPath('userData', path.join(app.getPath('appData')));
} else if (process.platform === 'win32') {
  app.setPath('appData', process.env.APPDATA);
  app.setPath('userData', path.join(app.getPath('appData'), app.getName()));
}

if (isDevMode) {
  app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev'));
}

/* eslint-disable import/first */
import {
  isMac,
  isWindows,
  isLinux,
} from './environment';
import { mainIpcHandler as basicAuthHandler } from './features/basicAuth';
import ipcApi from './electron/ipc-api';
import Tray from './lib/Tray';
import Settings from './electron/Settings';
import handleDeepLink from './electron/deepLinking';
import { isPositionValid } from './electron/windowUtils';
import askFormacOSPermissions from './electron/macOSPermissions';
import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
import './electron/exception';

import {
  DEFAULT_APP_SETTINGS,
  DEFAULT_WINDOW_OPTIONS,
} from './config';
import { asarPath } from './helpers/asar-helpers';
import { isValidExternalURL } from './helpers/url-helpers';
import userAgent from './helpers/userAgent-helpers';

/* eslint-enable import/first */
const debug = require('debug')('Franz:App');

// Globally set useragent to fix user agent override in service workers
debug('Set userAgent to ', userAgent());
app.userAgentFallback = userAgent();

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
let willQuitApp = false;

// Register methods to be called once the window has been loaded.
let onDidLoadFns = [];

function onDidLoad(fn) {
  if (onDidLoadFns) {
    onDidLoadFns.push(fn);
  } else if (mainWindow) {
    fn(mainWindow);
  }
}

// Ensure that the recipe directory exists
fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp'));
fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json'));

// Set App ID for Windows
if (isWindows) {
  app.setAppUserModelId(appId);
}

// Initialize Settings
const settings = new Settings('app', DEFAULT_APP_SETTINGS);
const proxySettings = new Settings('proxy');

// add `liftSingleInstanceLock` to settings.json to override the single instance lock
const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false;

// Force single window
const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock();
if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (event, argv) => {
    // Someone tried to run a second instance, we should focus our window.
    if (mainWindow) {
      mainWindow.show();
      if (mainWindow.isMinimized()) {
        mainWindow.restore();
      }
      mainWindow.focus();

      if (isWindows) {
        onDidLoad((window) => {
          // Keep only command line / deep linked arguments
          const url = argv.slice(1);
          if (url) {
            handleDeepLink(window, url.toString());
          }

          if (argv.includes('--reset-window')) {
            // Needs to be delayed to not interfere with mainWindow.restore();
            setTimeout(() => {
              debug('Resetting windows via Task');
              window.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100);
              window.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height);
            }, 1);
          } else if (argv.includes('--quit')) {
            // Needs to be delayed to not interfere with mainWindow.restore();
            setTimeout(() => {
              debug('Quitting Franz via Task');
              app.quit();
            }, 1);
          }
        });
      }
    }
  });
}

// Fix Unity indicator issue
// https://github.com/electron/electron/issues/9046
if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) {
  process.env.XDG_CURRENT_DESKTOP = 'Unity';
}

// Disable GPU acceleration
if (!settings.get('enableGPUAcceleration')) {
  debug('Disable GPU Acceleration');
  app.disableHardwareAcceleration();
}

const createWindow = () => {
  // Remember window size
  const mainWindowState = windowStateKeeper({
    defaultWidth: DEFAULT_WINDOW_OPTIONS.width,
    defaultHeight: DEFAULT_WINDOW_OPTIONS.height,
  });

  let posX = mainWindowState.x || DEFAULT_WINDOW_OPTIONS.x;
  let posY = mainWindowState.y || DEFAULT_WINDOW_OPTIONS.y;

  if (!isPositionValid({ x: posX, y: posY })) {
    debug('Window is out of screen bounds, resetting window');
    posX = DEFAULT_WINDOW_OPTIONS.x;
    posY = DEFAULT_WINDOW_OPTIONS.y;
  }

  // Create the browser window.
  mainWindow = new BrowserWindow({
    x: posX,
    y: posY,
    width: mainWindowState.width,
    height: mainWindowState.height,
    minWidth: 600,
    minHeight: 500,
    titleBarStyle: isMac ? 'hidden' : '',
    frame: isLinux,
    backgroundColor: !settings.get('darkMode') ? '#3498db' : '#1E1E1E',
    webPreferences: {
      nodeIntegration: true,
      webviewTag: true,
      enableRemoteModule: true,
    },
  });

  mainWindow.webContents.on('did-finish-load', () => {
    const fns = onDidLoadFns;
    onDidLoadFns = null;
    for (const fn of fns) {
      fn(mainWindow);
    }
  });

  // Initialize System Tray
  const trayIcon = new Tray();

  // Initialize ipcApi
  ipcApi({
    mainWindow,
    settings: {
      app: settings,
      proxy: proxySettings,
    },
    trayIcon,
  });

  // Manage Window State
  mainWindowState.manage(mainWindow);

  // and load the index.html of the app.
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // Open the DevTools.
  if (isDevMode || process.argv.includes('--devtools')) {
    mainWindow.webContents.openDevTools();
  }

  // Windows deep linking handling on app launch
  if (isWindows) {
    onDidLoad((window) => {
      const url = process.argv.slice(1);
      if (url) {
        handleDeepLink(window, url.toString());
      }
    });
  }

  // Emitted when the window is closed.
  mainWindow.on('close', (e) => {
    debug('Window: close window');
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) {
      e.preventDefault();
      if (isWindows) {
        debug('Window: minimize');
        mainWindow.minimize();

        if (settings.get('minimizeToSystemTray')) {
          debug('Skip taskbar: true');
          mainWindow.setSkipTaskbar(true);
        }
      } else {
        debug('Window: hide');
        mainWindow.hide();
      }
    } else {
      app.quit();
    }
  });

  // For Windows we need to store a flag to properly restore the window
  // if the window was maximized before minimizing it so system tray
  mainWindow.on('minimize', () => {
    app.wasMaximized = app.isMaximized;

    if (settings.get('minimizeToSystemTray')) {
      debug('Skip taskbar: true');
      mainWindow.setSkipTaskbar(true);
      trayIcon.show();
    }
  });

  mainWindow.on('maximize', () => {
    debug('Window: maximize');
    app.isMaximized = true;
  });

  mainWindow.on('unmaximize', () => {
    debug('Window: unmaximize');
    app.isMaximized = false;
  });

  mainWindow.on('restore', () => {
    debug('Window: restore');
    mainWindow.setSkipTaskbar(false);

    if (app.wasMaximized) {
      debug('Window: was maximized before, maximize window');
      mainWindow.maximize();
    }

    if (!settings.get('enableSystemTray')) {
      debug('Tray: hiding tray icon');
      trayIcon.hide();
    }
  });

  if (isMac) {
    askFormacOSPermissions();
  }

  mainWindow.on('show', () => {
    debug('Skip taskbar: false');
    mainWindow.setSkipTaskbar(false);
  });

  app.mainWindow = mainWindow;
  app.isMaximized = mainWindow.isMaximized();

  mainWindow.webContents.on('new-window', (e, url) => {
    debug('Open url', url);
    e.preventDefault();

    if (isValidExternalURL(url)) {
      shell.openExternal(url);
    }
  });
};

// Allow passing command line parameters/switches to electron
// https://electronjs.org/docs/api/chrome-command-line-switches
// used for Kerberos support
// Usage e.g. MACOS
// $ Franz.app/Contents/MacOS/Franz --auth-server-whitelist *.mydomain.com --auth-negotiate-delegate-whitelist *.mydomain.com
const argv = require('minimist')(process.argv.slice(1));

if (argv['auth-server-whitelist']) {
  app.commandLine.appendSwitch('auth-server-whitelist', argv['auth-server-whitelist']);
}
if (argv['auth-negotiate-delegate-whitelist']) {
  app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', argv['auth-negotiate-delegate-whitelist']);
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
  // force app to live in /Applications
  enforceMacOSAppLocation();

  // Register App URL
  app.setAsDefaultProtocolClient('franz');

  if (isDevMode) {
    app.setAsDefaultProtocolClient('franz-dev');
  }

  if (process.platform === 'win32') {
    app.setUserTasks([{
      program: process.execPath,
      arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`,
      iconPath: asarPath(path.join(isDevMode ? `${__dirname}../src/` : __dirname, 'assets/images/taskbar/win32/display.ico')),
      iconIndex: 0,
      title: 'Move Franz to Current Display',
      description: 'Restore the position and size of Franz',
    }, {
      program: process.execPath,
      arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`,
      iconIndex: 0,
      title: 'Quit Franz',
    }]);
  }

  createWindow();
});

// This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕
// TODO: rewrite to handle multiple login calls
const noop = () => null;
let authCallback = noop;

app.on('login', (event, webContents, request, authInfo, callback) => {
  authCallback = callback;
  debug('browser login event', authInfo);
  event.preventDefault();

  if (!authInfo.isProxy && authInfo.scheme === 'basic') {
    debug('basic auth handler', authInfo);
    basicAuthHandler(mainWindow, authInfo);
  }
});

// TODO: evaluate if we need to store the authCallback for every service
ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => {
  debug('Received basic auth credentials', user, '********');

  authCallback(user, password);
  authCallback = noop;
});

ipcMain.on('feature-basic-auth-cancel', () => {
  debug('Cancel basic auth');

  authCallback(null);
  authCallback = noop;
});

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (settings.get('runInBackground') === undefined
    || settings.get('runInBackground')) {
    debug('Window: all windows closed, quit app');
    app.quit();
  } else {
    debug('Window: don\'t quit app');
  }
});

app.on('before-quit', () => {
  willQuitApp = true;
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow();
  } else {
    mainWindow.show();
  }
});

app.on('will-finish-launching', () => {
  // Protocol handler for macOS
  app.on('open-url', (event, url) => {
    event.preventDefault();

    onDidLoad((window) => {
      debug('open-url event', url);
      handleDeepLink(window, url);
    });
  });
});
<|endoftext|>


<commit_before>/*
 * React Since (MIT)
 * Tom Smith (https://github.com/tsmith123)
 */

import React from 'react'
import PropTypes from 'prop-types'

class Since extends React.Component {
  state = {
    timeoutId: null,
    since: null
  }

  tick = () => {
    const { seconds } = this.props

    const timeoutId = setTimeout(this.tick, 1000 * seconds)

    this.setState({ timeoutId })
  }

  componentDidMount () {
    const { live, date } = this.props

    // don't go any further if no date
    if (!date) return

    this.init(date)

    if (live) {
      this.tick()
    }
  }

  componentWillUnmount () {
    const { timeoutId } = this.state

    if (timeoutId) {
      clearTimeout(timeoutId)
    }
  }

  init = date => {
    // convert date to milliseconds
    const then = Date.parse(date)
    const now = Date.now()

    // get difference in seconds between two dates
    const seconds = Math.round(Math.abs(now - then) / 1000)
    const since = this.getSince(seconds)

    this.setState({ since })
  }

  // the logic - needs cleaning up
  getSince (seconds) {
    const minutes = Math.round(seconds / 60)
    if (minutes < 5) {
      return 'just now'
    }

    // round to nearest 5 minutes
    if (minutes < 45) {
      return (Math.round(minutes / 5) * 5) + ' minutes ago'
    }

    if (minutes < 90) {
      return 'an hour ago'
    }

    const hours = Math.round(minutes / 60)
    if (hours < 5) {
      return 'a few hours ago'
    }

    if (hours < 12) {
      return hours + ' hours ago'
    }

    const days = Math.round(hours / 24)
    if (days === 1) {
      return 'yesterday'
    }

    if (days < 7) {
      return days + ' days ago'
    }

    const weeks = Math.round(days / 7)
    if (weeks === 1) {
      return 'a week ago'
    }
    if (weeks < 4) {
      return weeks + ' weeks ago'
    }

    const months = Math.round(weeks / 4)
    if (months === 1) {
      return 'a month ago'
    }
    if (months < 12) {
      return months + ' months ago'
    }

    const years = Math.round(months / 12)
    if (years === 1) {
      return 'a year ago'
    }

    return years + ' years ago'
  }

  render () {
    const { since } = this.state

    return {since}
  }
}

Since.propTypes = {
  live: PropTypes.bool,
  date: PropTypes.string,
  seconds: PropTypes.number
}

Since.defaultProps = {
  live: true,
  seconds: 60
}

export default Since
<commit_msg>Update tick logic<commit_after>/*
 * React Since (MIT)
 * Tom Smith (https://github.com/tsmith123)
 */

import React from 'react'
import PropTypes from 'prop-types'

class Since extends React.Component {
  state = {
    timeoutId: null,
    since: null
  }

  tick = () => {
    const { date, seconds } = this.props

    const timeoutId = setTimeout(this.tick, 1000 * seconds)

    const since = this.getSince(date)

    this.setState({ since, timeoutId })
  }

  componentDidMount () {
    const { live, date } = this.props

    // don't go any further if no date
    if (!date) return

    const since = this.getSince(date)

    this.setState({ since })

    if (live) {
      this.tick()
    }
  }

  componentWillUnmount () {
    const { timeoutId } = this.state

    if (timeoutId) {
      clearTimeout(timeoutId)
    }
  }

  // the logic - needs cleaning up
  getSince = date => {
    // convert date to milliseconds
    const then = Date.parse(date)
    const now = Date.now()

    // get difference in seconds between two dates
    const seconds = Math.round(Math.abs(now - then) / 1000)

    const minutes = Math.round(seconds / 60)
    if (minutes < 5) {
      return 'just now'
    }

    // round to nearest 5 minutes
    if (minutes < 45) {
      return (Math.round(minutes / 5) * 5) + ' minutes ago'
    }

    if (minutes < 90) {
      return 'an hour ago'
    }

    const hours = Math.round(minutes / 60)
    if (hours < 5) {
      return 'a few hours ago'
    }

    if (hours < 12) {
      return hours + ' hours ago'
    }

    const days = Math.round(hours / 24)
    if (days === 1) {
      return 'yesterday'
    }

    if (days < 7) {
      return days + ' days ago'
    }

    const weeks = Math.round(days / 7)
    if (weeks === 1) {
      return 'a week ago'
    }
    if (weeks < 4) {
      return weeks + ' weeks ago'
    }

    const months = Math.round(weeks / 4)
    if (months === 1) {
      return 'a month ago'
    }
    if (months < 12) {
      return months + ' months ago'
    }

    const years = Math.round(months / 12)
    if (years === 1) {
      return 'a year ago'
    }

    return years + ' years ago'
  }

  render () {
    const { since } = this.state

    return {since}
  }
}

Since.propTypes = {
  live: PropTypes.bool,
  date: PropTypes.string,
  seconds: PropTypes.number
}

Since.defaultProps = {
  live: true,
  seconds: 60
}

export default Since
<|endoftext|>


<commit_before>import Client from './Client';

export default Client;
<commit_msg>[FEAT] Added export of client & connection states to avoid duplicating codes everywhere<commit_after>import Client from './Client';
import { CLIENT_STATES as CS, CONNECTION_STATES as CCS } from './Constants';

export const CLIENT_STATES = CS;
export const CONNECTION_STATES = CCS;

export default Client;
<|endoftext|>


<commit_before>import got from 'got';
import getArtistTitle, { fallBackToArtist } from 'get-artist-title';

function normalizeMedia(media) {
  const [artist, title] = getArtistTitle(media.title, [
    'base',
    fallBackToArtist(media.soundtrack_artist || media['owner.username'])
  ]);

  return {
    sourceID: media.id,
    artist, title,
    duration: media.duration,
    thumbnail: media.thumbnail_360_url,
    restricted: []
  };
}

export function getVideoID(url) {
  const match = /\/video\/([kx][a-zA-Z0-9]+)($|_)/.exec(url);
  if (match) {
    return match[1];
  }
  return null;
}

const defaultOptions = {
  api: 'https://api.dailymotion.com/',
  fields: [
    'id',
    'title',
    'owner.username',
    'allow_embed',
    'duration',
    'explicit',
    'soundtrack_artist',
    'thumbnail_360_url'
  ]
};

export default function dailymotionSource(uw, userOptions = {}) {
  const opts = {
    ...defaultOptions,
    ...userOptions
  };

  async function getOne(sourceID) {
    const { body } = await got(`${opts.api}video/${sourceID}`, {
      json: true,
      query: {
        fields: opts.fields.join(',')
      }
    });

    // Private videos return their x-ID ("x8yd34t"), but can only be accessed
    // using their k-ID ("k7dtszqBcKiDg952dNz"). We'll override the returned
    // x-ID with the original sourceID, so k-IDs will stay k-IDs.
    return Object.assign(normalizeMedia(body), {
      sourceID
    });
  }

  async function get(sourceIDs) {
    // Private videos don't show up in the /videos endpoint. They will normally
    // only be added one at a time, though, because the search can only find one
    // at a time. The /video/{id} endpoint _does_ return private videos, so
    // we'll use that instead when we're requesting just one video. This should
    // work most of the time, at least.
    // A more robust way would be to attempt to hit the /video/{id} endpoints
    // for any videos that were not returned from the /videos request below 👇,
    // but I'm not sure that it's necessary. We can change it if it turns out to
    // be a problem.
    if (sourceIDs.length === 1) {
      return [await getOne(sourceIDs[0])];
    }

    const { body } = await got(`${opts.api}videos`, {
      json: true,
      query: {
        limit: sourceIDs.length,
        ids: sourceIDs.join(','),
        fields: opts.fields.join(',')
      }
    });

    const videos = {};
    for (const video of body.list) {
      videos[video.id] = normalizeMedia(video);
    }

    return sourceIDs.map(id => videos[id]);
  }

  async function search(query, page = {}) {
    const id = getVideoID(query);
    if (id) {
      return [await getOne(id)];
    }

    const { body } = await got(`${opts.api}videos`, {
      json: true,
      query: {
        search: query,
        limit: 50,
        fields: opts.fields.join(',')
      }
    });
    return body.list.map(normalizeMedia);
  }

  return {
    get: get, // eslint-disable-line object-shorthand
    search
  };
}
<commit_msg>Style fixes<commit_after>import got from 'got';
import getArtistTitle, { fallBackToArtist } from 'get-artist-title';

function normalizeMedia(media) {
  const [artist, title] = getArtistTitle(media.title, [
    'base',
    fallBackToArtist(media.soundtrack_artist || media['owner.username'])
  ]);

  return {
    sourceID: media.id,
    artist,
    title,
    duration: media.duration,
    thumbnail: media.thumbnail_360_url,
    restricted: []
  };
}

export function getVideoID(url) {
  const match = /\/video\/([kx][a-zA-Z0-9]+)($|_)/.exec(url);
  if (match) {
    return match[1];
  }
  return null;
}

const defaultOptions = {
  api: 'https://api.dailymotion.com/',
  fields: [
    'id',
    'title',
    'owner.username',
    'allow_embed',
    'duration',
    'explicit',
    'soundtrack_artist',
    'thumbnail_360_url'
  ]
};

export default function dailymotionSource(uw, userOptions = {}) {
  const opts = {
    ...defaultOptions,
    ...userOptions
  };

  async function getOne(sourceID) {
    const { body } = await got(`${opts.api}video/${sourceID}`, {
      json: true,
      query: {
        fields: opts.fields.join(',')
      }
    });

    // Private videos return their x-ID ("x8yd34t"), but can only be accessed
    // using their k-ID ("k7dtszqBcKiDg952dNz"). We'll override the returned
    // x-ID with the original sourceID, so k-IDs will stay k-IDs.
    return Object.assign(normalizeMedia(body), {
      sourceID
    });
  }

  async function get(sourceIDs) {
    // Private videos don't show up in the /videos endpoint. They will normally
    // only be added one at a time, though, because the search can only find one
    // at a time. The /video/{id} endpoint _does_ return private videos, so
    // we'll use that instead when we're requesting just one video. This should
    // work most of the time, at least.
    // A more robust way would be to attempt to hit the /video/{id} endpoints
    // for any videos that were not returned from the /videos request below 👇,
    // but I'm not sure that it's necessary. We can change it if it turns out to
    // be a problem.
    if (sourceIDs.length === 1) {
      return [await getOne(sourceIDs[0])];
    }

    const { body } = await got(`${opts.api}videos`, {
      json: true,
      query: {
        limit: sourceIDs.length,
        ids: sourceIDs.join(','),
        fields: opts.fields.join(',')
      }
    });

    const videos = {};
    body.list.forEach((video) => {
      videos[video.id] = normalizeMedia(video);
    });

    return sourceIDs.map(id => videos[id]);
  }

  async function search(query) {
    const id = getVideoID(query);
    if (id) {
      return [await getOne(id)];
    }

    const { body } = await got(`${opts.api}videos`, {
      json: true,
      query: {
        search: query,
        limit: 50,
        fields: opts.fields.join(',')
      }
    });
    return body.list.map(normalizeMedia);
  }

  return {
    get: get, // eslint-disable-line object-shorthand
    search
  };
}
<|endoftext|>