<commit_before>// @flow
/* global videojs: true */
/* global SETTINGS: false */
import React from "react"
import R from "ramda"
import _ from "lodash"
import type { Dispatch } from "redux"
import { makeVideoSubtitleUrl } from "../lib/urls"
import { videojs } from "../lib/video"
import type { Video, VideoSubtitle } from "../flow/videoTypes"
import { FULLSCREEN_API } from "../util/fullscreen_api"
import { CANVASES } from "../constants"
import { sendGAEvent, setCustomDimension } from "../util/google_analytics"
import { actions } from "../actions"
import { connect } from "react-redux"

const gaEvents = [
  "play",
  "pause",
  "seeked",
  "timeupdate",
  "fullscreen off",
  "fullscreen on",
  "ended"
]

const makeConfigForVideo = (
  video: Video,
  useYouTube: boolean,
  embedded: ?boolean
): Object => ({
  autoplay:    false,
  controls:    true,
  fluid:       embedded || false,
  playsinline: true,
  techOrder:   useYouTube ? ["youtube", "html5"] : ["html5"],
  html5:       {
    nativeTextTracks: false
  },
  playbackRates: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 4.0],
  sources:       useYouTube
    ? [
      {
        type: "video/youtube",
        src:  `https://www.youtube.com/watch?v=${video.youtube_id || ""}`
      }
    ]
    : video.sources,
  plugins: {
    hlsQualitySelector:        {},
    videoJsResolutionSwitcher: {
      default:      "high",
      dynamicLabel: true
    }
  }
})

const drawCanvasImage = function(canvas, videoNode, shiftX, shiftY) {
  const x = shiftX ? Math.floor(videoNode.videoWidth / 2) : 0
  const y = shiftY ? Math.floor(videoNode.videoHeight / 2) : 0
  const context = canvas.getContext("2d")
  context.drawImage(
    videoNode,
    x,
    y,
    Math.floor(videoNode.videoWidth / 2),
    Math.floor(videoNode.videoHeight / 2),
    0,
    0,
    canvas.width,
    canvas.height
  )
  setTimeout(drawCanvasImage, 20, canvas, videoNode, shiftX, shiftY)
}

const isFullscreen = function() {
  // $FlowFixMe
  return document[FULLSCREEN_API.fullscreenElement]
}

class VideoPlayer extends React.Component<*, void> {
  props: {
    dispatch: Dispatch,
    video: Video,
    selectedCorner: string,
    cornerFunc: (corner: string) => void,
    embed: ?boolean,
    videoPlayerRef?: (player: any) => void
  }

  player: Object
  videoNode: ?HTMLVideoElement
  videoContainer: ?HTMLDivElement
  cameras: ?HTMLDivElement
  lastMinuteTracked: ?number
  aspectRatio: number

  updateSubtitles() {
    const { video } = this.props
    if (this.player) {
      // Remove existing tracks for deleted subtitles
      const tracks = this.player.textTracks()
      const subtitleUrls = video.videosubtitle_set.map(
        (subtitle: VideoSubtitle) => makeVideoSubtitleUrl(subtitle)
      )
      const trackUrls = []
      for (let idx = 0; idx < tracks.length; idx++) {
        if (tracks[idx] && !subtitleUrls.includes(tracks[idx].src)) {
          this.player.removeRemoteTextTrack(tracks[idx])
        } else {
          trackUrls.push(tracks[idx].src)
        }
      }
      // Add tracks for any new subtitles associated with the video
      video.videosubtitle_set.forEach((subtitle: VideoSubtitle) => {
        const subUrl = makeVideoSubtitleUrl(subtitle)
        if (!trackUrls.includes(subUrl)) {
          this.player.addRemoteTextTrack(
            {
              kind:    "captions",
              src:     subUrl,
              srcLang: subtitle.language,
              label:   subtitle.language_name
            },
            true
          )
        }
        // Add listeners to each track
        const player = this.player
        for (let idx = 0; idx < this.player.textTracks().length; idx++) {
          if (!trackUrls.includes(tracks[idx].src)) {
            tracks[idx].addEventListener("modechange", function() {
              sendGAEvent(
                "video",
                `Subtitles ${this.label} ${this.mode}`,
                video.key,
                player.currentTime()
              )
            })
          }
        }
      })
    }
  }

  drawCanvas(canvas: HTMLCanvasElement, shiftX: boolean, shiftY: boolean) {
    if (!this.videoNode) {
      // make flow happy
      throw new Error("Missing videoNode")
    }
    const { offsetWidth, offsetHeight } = this.videoNode
    canvas.width = Math.floor(offsetWidth / 4) - 2
    canvas.height = Math.floor(offsetHeight / 4) - 2
    if (canvas && this.videoNode) {
      drawCanvasImage(canvas, this.videoNode, shiftX, shiftY)
    }
  }

  configureCameras() {
    if (this.cameras) {
      const canvasElements = this.cameras.getElementsByTagName("canvas")
      Object.keys(CANVASES).forEach(corner => {
        this.drawCanvas(
          // $FlowFixMe - corner does not have to be a number
          canvasElements[corner],
          CANVASES[corner].shiftX,
          CANVASES[corner].shiftY
        )
      })
    }
  }

  resizeYouTube = () => {
    const { embed } = this.props
    if (!isFullscreen() && !embed) {
      if (!this.aspectRatio) {
        this.aspectRatio =
          this.player.currentWidth() / this.player.currentHeight()
      }
      // $FlowFixMe videoContainer is not going to be null
      const maxWidth = this.videoContainer.clientWidth
      this.player.width(maxWidth)
      const maxHeight = window
        .getComputedStyle(this.videoContainer)
        .maxHeight.replace("px", "")
      this.player.height(Math.min(maxHeight, maxWidth / this.aspectRatio))
    }
  }

  cropVideo = () => {
    const { selectedCorner } = this.props
    const shiftX = CANVASES[selectedCorner].shiftX
    const shiftY = CANVASES[selectedCorner].shiftY
    const transformProps = [
      "transform",
      "WebkitTransform",
      "MozTransform",
      "msTransform",
      "OTransform"
    ]

    const prop =
      transformProps.find(
        property => this.player.el_.style[property] !== undefined
      ) || transformProps[0]
    const aspectRatio = this.player.videoWidth() / this.player.videoHeight()
    let videoWidth = Math.min(
      parseInt(window.getComputedStyle(this.videoNode).maxHeight) * aspectRatio,
      window.innerWidth,
      screen.width
    )
    if (isNaN(videoWidth) || isFullscreen()) {
      videoWidth = Math.min(window.innerWidth, screen.width)
    }
    const canvasWidth = Math.floor(videoWidth / 4)
    videoWidth = Math.floor(
      videoWidth - (canvasWidth - canvasWidth / aspectRatio / 3)
    )

    if (!this.videoContainer) {
      // Make flow happy
      throw new Error("Missing videoContainer")
    }
    this.videoContainer.style.maxWidth = `${videoWidth}px`
    // $FlowFixMe videoContainer.parentElement is not going to be null
    this.videoContainer.parentElement.style.width = `${videoWidth +
      canvasWidth}px`
    const left = Math.round(this.player.currentWidth() / (shiftX ? -2 : 2))
    const top = Math.round(this.player.currentHeight() / (shiftY ? -2 : 2))

    if (!this.videoNode) {
      // Make flow happy
      throw new Error("Missing videoNode")
    }

    this.videoNode.style.left = `${left}px`
    this.videoNode.style.top = `${top}px`
    // $FlowFixMe prop does not have to be a number
    this.videoNode.style[prop] = "scale(2)"
    this.configureCameras()
  }

  toggleFullscreen = () => {
    const fullscreen = isFullscreen()
    if (fullscreen) {
      // $FlowFixMe
      document[FULLSCREEN_API.exitFullscreen]()
    } else {
      // $FlowFixMe videoContainer.parentElement is not going to be null
      this.videoContainer.parentElement[FULLSCREEN_API.requestFullscreen]()
    }
    this.player.el_.dispatchEvent(
      new Event(`fullscreen ${fullscreen ? "off" : "on"}`)
    )
  }

  switchVideoSource = () => {
    const { video } = this.props
    if (video.sources.length > 0) {
      this.player
        .reset()
        .src(video.sources)
        .fluid(false)
    }
  }

  imageExists(url: string) {
    const img = new Image()
    img.onerror = this.switchVideoSource
    img.src = url
  }

  checkYouTube = async () => {
    const { video } = this.props
    // Try to load the YouTube video thumbnail image.  Assumes video availability == thumbnail availability
    const imgUrl = `https://img.youtube.com/vi/${video.youtube_id || ""}/0.jpg`
    this.imageExists(imgUrl)
  }

  sendEvent = (action: string, label: string) => {
    const { dispatch } = this.props
    if (action === "timeupdate") {
      // Track amount played in increments of 60 seconds
      const currentTime = this.player.currentTime()
      const nearestMinute = parseInt((currentTime - currentTime % 60) / 60)
      if (this.lastMinuteTracked !== nearestMinute) {
        sendGAEvent(
          "video",
          "T".concat(nearestMinute.toString().padStart(4, "0")),
          label,
          1
        )
        this.lastMinuteTracked = nearestMinute
      }
      dispatch(actions.videoUi.setVideoTime(Math.floor(currentTime)))
    } else {
      sendGAEvent("video", action, label, this.player.currentTime())
    }
  }

  createEventHandler = (action: string, label: string) => {
    const sendEvent = this.sendEvent
    this.player.on(action, function() {
      sendEvent(action, label)
    })
  }

  selectPlaylist = () => {
    const sortByBandwidth = R.sortBy(R.path(["attributes", "BANDWIDTH"]))
    const playlists = sortByBandwidth(
      this.player.tech_.hls.playlists.master.playlists
    )
    // Always start with highest bandwidth for first 10 seconds
    if (this.player.tech_.currentTime() < 10) {
      return _.last(playlists)
    }
    // Return active playlist with highest bandwidth <= system bandwidth,
    // or first active playlist otherwise.
    const activePlaylists = R.filter(rep => !rep.disabled, playlists)
    return (
      _.last(
        R.filter(rep => {
          return (
            rep.attributes.BANDWIDTH <=
            _.max([
              this.player.tech_.hls.systemBandwidth,
              playlists[0].attributes.BANDWIDTH
            ])
          )
        }, activePlaylists)
      ) || activePlaylists[0]
    )
  }

  componentDidMount() {
    const { video, selectedCorner, embed, videoPlayerRef } = this.props
    if (videoPlayerRef) {
      videoPlayerRef(this)
    }
    const cropVideo = this.cropVideo
    const resizeYouTube = this.resizeYouTube
    const createEventHandler = this.createEventHandler
    const toggleFullscreen = this.toggleFullscreen
    if (video.multiangle) {
      videojs.getComponent(
        "FullscreenToggle"
      ).prototype.handleClick = toggleFullscreen
    }
    const useYouTube = video.is_public && video.youtube_id !== null
    this.lastMinuteTracked = null
    const selectPlaylist = this.selectPlaylist.bind(this)
    const self = this
    this.player = videojs(
      this.videoNode,
      makeConfigForVideo(video, useYouTube, embed),
      function onPlayerReady() {
        this.enableTouchActivity()
        if (video.multiangle) {
          setCustomDimension(SETTINGS.ga_dimension_camera, selectedCorner)
          this.on("loadeddata", cropVideo)
          this.on(FULLSCREEN_API.fullscreenchange, cropVideo)
          window.addEventListener("resize", cropVideo)
        } else if (useYouTube) {
          this.on("loadedmetadata", resizeYouTube)
          window.addEventListener("resize", resizeYouTube)
        }
        this.on("loadedmetadata", function() {
          self.props.dispatch(
            actions.videoUi.setVideoDuration(self.player.duration())
          )
          gaEvents.forEach((event: string) => {
            createEventHandler(event, video.key)
          })
        })
        if (this.tech_.hls !== undefined) {
          this.tech_.hls.selectPlaylist = selectPlaylist
        }
        const params = new URLSearchParams(window.location.search)
        this.currentTime(parseInt(params.get("start")) || 0)
      }
    )
    if (useYouTube) {
      this.checkYouTube()
    }
    this.updateSubtitles()
  }

  componentDidUpdate() {
    this.updateSubtitles()
  }

  // destroy player on unmount
  componentWillUnmount() {
    if (this.player) {
      this.player.dispose()
    }
  }

  clickCamera = async (corner: string) => {
    const { cornerFunc, video } = this.props
    if (cornerFunc) {
      setCustomDimension(SETTINGS.ga_dimension_camera, corner)
      sendGAEvent(
        "video",
        "changeCameraView",
        video.key,
        this.player.currentTime()
      )
      await cornerFunc(corner)
      this.cropVideo()
    }
  }

  render() {
    const { video, selectedCorner, embed } = this.props
    return (

 (this.videoContainer = node)}
        >

            <video
              ref={node => (this.videoNode = node)}
              className={`video-js vjs-default-skin ${
                embed ? "video-odl-embed" : ""
              }`}
              crossOrigin="anonymous"
              controls
            />

        {video.multiangle && (

 (this.cameras = node)} className="camera-bar">
            {Object.keys(CANVASES).map(corner => (

                <canvas
                  id={corner}
                  key={corner}
                  onClick={this.clickCamera.bind(this, corner)}
                  className={`camera-box ${
                    corner === selectedCorner ? "camera-box-selected" : ""
                  }`}
                />

            ))}

        )}

    )
  }

  setCurrentTime(time) {
    this.player.currentTime(time)
  }
}

export default connect()(VideoPlayer)
<commit_msg>center play button in VideoPlayer<commit_after>// @flow
/* global videojs: true */
/* global SETTINGS: false */
import React from "react"
import R from "ramda"
import _ from "lodash"
import type { Dispatch } from "redux"
import { makeVideoSubtitleUrl } from "../lib/urls"
import { videojs } from "../lib/video"
import type { Video, VideoSubtitle } from "../flow/videoTypes"
import { FULLSCREEN_API } from "../util/fullscreen_api"
import { CANVASES } from "../constants"
import { sendGAEvent, setCustomDimension } from "../util/google_analytics"
import { actions } from "../actions"
import { connect } from "react-redux"

const gaEvents = [
  "play",
  "pause",
  "seeked",
  "timeupdate",
  "fullscreen off",
  "fullscreen on",
  "ended"
]

const makeConfigForVideo = (
  video: Video,
  useYouTube: boolean,
  embedded: ?boolean
): Object => ({
  autoplay:    false,
  controls:    true,
  fluid:       embedded || false,
  playsinline: true,
  techOrder:   useYouTube ? ["youtube", "html5"] : ["html5"],
  html5:       {
    nativeTextTracks: false
  },
  playbackRates: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 4.0],
  sources:       useYouTube
    ? [
      {
        type: "video/youtube",
        src:  `https://www.youtube.com/watch?v=${video.youtube_id || ""}`
      }
    ]
    : video.sources,
  plugins: {
    hlsQualitySelector:        {},
    videoJsResolutionSwitcher: {
      default:      "high",
      dynamicLabel: true
    }
  }
})

const drawCanvasImage = function(canvas, videoNode, shiftX, shiftY) {
  const x = shiftX ? Math.floor(videoNode.videoWidth / 2) : 0
  const y = shiftY ? Math.floor(videoNode.videoHeight / 2) : 0
  const context = canvas.getContext("2d")
  context.drawImage(
    videoNode,
    x,
    y,
    Math.floor(videoNode.videoWidth / 2),
    Math.floor(videoNode.videoHeight / 2),
    0,
    0,
    canvas.width,
    canvas.height
  )
  setTimeout(drawCanvasImage, 20, canvas, videoNode, shiftX, shiftY)
}

const isFullscreen = function() {
  // $FlowFixMe
  return document[FULLSCREEN_API.fullscreenElement]
}

class VideoPlayer extends React.Component<*, void> {
  props: {
    dispatch: Dispatch,
    video: Video,
    selectedCorner: string,
    cornerFunc: (corner: string) => void,
    embed: ?boolean,
    videoPlayerRef?: (player: any) => void
  }

  player: Object
  videoNode: ?HTMLVideoElement
  videoContainer: ?HTMLDivElement
  cameras: ?HTMLDivElement
  lastMinuteTracked: ?number
  aspectRatio: number

  updateSubtitles() {
    const { video } = this.props
    if (this.player) {
      // Remove existing tracks for deleted subtitles
      const tracks = this.player.textTracks()
      const subtitleUrls = video.videosubtitle_set.map(
        (subtitle: VideoSubtitle) => makeVideoSubtitleUrl(subtitle)
      )
      const trackUrls = []
      for (let idx = 0; idx < tracks.length; idx++) {
        if (tracks[idx] && !subtitleUrls.includes(tracks[idx].src)) {
          this.player.removeRemoteTextTrack(tracks[idx])
        } else {
          trackUrls.push(tracks[idx].src)
        }
      }
      // Add tracks for any new subtitles associated with the video
      video.videosubtitle_set.forEach((subtitle: VideoSubtitle) => {
        const subUrl = makeVideoSubtitleUrl(subtitle)
        if (!trackUrls.includes(subUrl)) {
          this.player.addRemoteTextTrack(
            {
              kind:    "captions",
              src:     subUrl,
              srcLang: subtitle.language,
              label:   subtitle.language_name
            },
            true
          )
        }
        // Add listeners to each track
        const player = this.player
        for (let idx = 0; idx < this.player.textTracks().length; idx++) {
          if (!trackUrls.includes(tracks[idx].src)) {
            tracks[idx].addEventListener("modechange", function() {
              sendGAEvent(
                "video",
                `Subtitles ${this.label} ${this.mode}`,
                video.key,
                player.currentTime()
              )
            })
          }
        }
      })
    }
  }

  drawCanvas(canvas: HTMLCanvasElement, shiftX: boolean, shiftY: boolean) {
    if (!this.videoNode) {
      // make flow happy
      throw new Error("Missing videoNode")
    }
    const { offsetWidth, offsetHeight } = this.videoNode
    canvas.width = Math.floor(offsetWidth / 4) - 2
    canvas.height = Math.floor(offsetHeight / 4) - 2
    if (canvas && this.videoNode) {
      drawCanvasImage(canvas, this.videoNode, shiftX, shiftY)
    }
  }

  configureCameras() {
    if (this.cameras) {
      const canvasElements = this.cameras.getElementsByTagName("canvas")
      Object.keys(CANVASES).forEach(corner => {
        this.drawCanvas(
          // $FlowFixMe - corner does not have to be a number
          canvasElements[corner],
          CANVASES[corner].shiftX,
          CANVASES[corner].shiftY
        )
      })
    }
  }

  resizeYouTube = () => {
    const { embed } = this.props
    if (!isFullscreen() && !embed) {
      if (!this.aspectRatio) {
        this.aspectRatio =
          this.player.currentWidth() / this.player.currentHeight()
      }
      // $FlowFixMe videoContainer is not going to be null
      const maxWidth = this.videoContainer.clientWidth
      this.player.width(maxWidth)
      const maxHeight = window
        .getComputedStyle(this.videoContainer)
        .maxHeight.replace("px", "")
      this.player.height(Math.min(maxHeight, maxWidth / this.aspectRatio))
    }
  }

  cropVideo = () => {
    const { selectedCorner } = this.props
    const shiftX = CANVASES[selectedCorner].shiftX
    const shiftY = CANVASES[selectedCorner].shiftY
    const transformProps = [
      "transform",
      "WebkitTransform",
      "MozTransform",
      "msTransform",
      "OTransform"
    ]

    const prop =
      transformProps.find(
        property => this.player.el_.style[property] !== undefined
      ) || transformProps[0]
    const aspectRatio = this.player.videoWidth() / this.player.videoHeight()
    let videoWidth = Math.min(
      parseInt(window.getComputedStyle(this.videoNode).maxHeight) * aspectRatio,
      window.innerWidth,
      screen.width
    )
    if (isNaN(videoWidth) || isFullscreen()) {
      videoWidth = Math.min(window.innerWidth, screen.width)
    }
    const canvasWidth = Math.floor(videoWidth / 4)
    videoWidth = Math.floor(
      videoWidth - (canvasWidth - canvasWidth / aspectRatio / 3)
    )

    if (!this.videoContainer) {
      // Make flow happy
      throw new Error("Missing videoContainer")
    }
    this.videoContainer.style.maxWidth = `${videoWidth}px`
    // $FlowFixMe videoContainer.parentElement is not going to be null
    this.videoContainer.parentElement.style.width = `${videoWidth +
      canvasWidth}px`
    const left = Math.round(this.player.currentWidth() / (shiftX ? -2 : 2))
    const top = Math.round(this.player.currentHeight() / (shiftY ? -2 : 2))

    if (!this.videoNode) {
      // Make flow happy
      throw new Error("Missing videoNode")
    }

    this.videoNode.style.left = `${left}px`
    this.videoNode.style.top = `${top}px`
    // $FlowFixMe prop does not have to be a number
    this.videoNode.style[prop] = "scale(2)"
    this.configureCameras()
  }

  toggleFullscreen = () => {
    const fullscreen = isFullscreen()
    if (fullscreen) {
      // $FlowFixMe
      document[FULLSCREEN_API.exitFullscreen]()
    } else {
      // $FlowFixMe videoContainer.parentElement is not going to be null
      this.videoContainer.parentElement[FULLSCREEN_API.requestFullscreen]()
    }
    this.player.el_.dispatchEvent(
      new Event(`fullscreen ${fullscreen ? "off" : "on"}`)
    )
  }

  switchVideoSource = () => {
    const { video } = this.props
    if (video.sources.length > 0) {
      this.player
        .reset()
        .src(video.sources)
        .fluid(false)
    }
  }

  imageExists(url: string) {
    const img = new Image()
    img.onerror = this.switchVideoSource
    img.src = url
  }

  checkYouTube = async () => {
    const { video } = this.props
    // Try to load the YouTube video thumbnail image.  Assumes video availability == thumbnail availability
    const imgUrl = `https://img.youtube.com/vi/${video.youtube_id || ""}/0.jpg`
    this.imageExists(imgUrl)
  }

  sendEvent = (action: string, label: string) => {
    const { dispatch } = this.props
    if (action === "timeupdate") {
      // Track amount played in increments of 60 seconds
      const currentTime = this.player.currentTime()
      const nearestMinute = parseInt((currentTime - currentTime % 60) / 60)
      if (this.lastMinuteTracked !== nearestMinute) {
        sendGAEvent(
          "video",
          "T".concat(nearestMinute.toString().padStart(4, "0")),
          label,
          1
        )
        this.lastMinuteTracked = nearestMinute
      }
      dispatch(actions.videoUi.setVideoTime(Math.floor(currentTime)))
    } else {
      sendGAEvent("video", action, label, this.player.currentTime())
    }
  }

  createEventHandler = (action: string, label: string) => {
    const sendEvent = this.sendEvent
    this.player.on(action, function() {
      sendEvent(action, label)
    })
  }

  selectPlaylist = () => {
    const sortByBandwidth = R.sortBy(R.path(["attributes", "BANDWIDTH"]))
    const playlists = sortByBandwidth(
      this.player.tech_.hls.playlists.master.playlists
    )
    // Always start with highest bandwidth for first 10 seconds
    if (this.player.tech_.currentTime() < 10) {
      return _.last(playlists)
    }
    // Return active playlist with highest bandwidth <= system bandwidth,
    // or first active playlist otherwise.
    const activePlaylists = R.filter(rep => !rep.disabled, playlists)
    return (
      _.last(
        R.filter(rep => {
          return (
            rep.attributes.BANDWIDTH <=
            _.max([
              this.player.tech_.hls.systemBandwidth,
              playlists[0].attributes.BANDWIDTH
            ])
          )
        }, activePlaylists)
      ) || activePlaylists[0]
    )
  }

  componentDidMount() {
    const { video, selectedCorner, embed, videoPlayerRef } = this.props
    if (videoPlayerRef) {
      videoPlayerRef(this)
    }
    const cropVideo = this.cropVideo
    const resizeYouTube = this.resizeYouTube
    const createEventHandler = this.createEventHandler
    const toggleFullscreen = this.toggleFullscreen
    if (video.multiangle) {
      videojs.getComponent(
        "FullscreenToggle"
      ).prototype.handleClick = toggleFullscreen
    }
    const useYouTube = video.is_public && video.youtube_id !== null
    this.lastMinuteTracked = null
    const selectPlaylist = this.selectPlaylist.bind(this)
    const self = this
    this.player = videojs(
      this.videoNode,
      makeConfigForVideo(video, useYouTube, embed),
      function onPlayerReady() {
        this.enableTouchActivity()
        if (video.multiangle) {
          setCustomDimension(SETTINGS.ga_dimension_camera, selectedCorner)
          this.on("loadeddata", cropVideo)
          this.on(FULLSCREEN_API.fullscreenchange, cropVideo)
          window.addEventListener("resize", cropVideo)
        } else if (useYouTube) {
          this.on("loadedmetadata", resizeYouTube)
          window.addEventListener("resize", resizeYouTube)
        }
        this.on("loadedmetadata", function() {
          self.props.dispatch(
            actions.videoUi.setVideoDuration(self.player.duration())
          )
          gaEvents.forEach((event: string) => {
            createEventHandler(event, video.key)
          })
        })
        if (this.tech_.hls !== undefined) {
          this.tech_.hls.selectPlaylist = selectPlaylist
        }
        const params = new URLSearchParams(window.location.search)
        this.currentTime(parseInt(params.get("start")) || 0)
      }
    )
    if (useYouTube) {
      this.checkYouTube()
    }
    this.updateSubtitles()
  }

  componentDidUpdate() {
    this.updateSubtitles()
  }

  // destroy player on unmount
  componentWillUnmount() {
    if (this.player) {
      this.player.dispose()
    }
  }

  clickCamera = async (corner: string) => {
    const { cornerFunc, video } = this.props
    if (cornerFunc) {
      setCustomDimension(SETTINGS.ga_dimension_camera, corner)
      sendGAEvent(
        "video",
        "changeCameraView",
        video.key,
        this.player.currentTime()
      )
      await cornerFunc(corner)
      this.cropVideo()
    }
  }

  render() {
    const { video, selectedCorner, embed } = this.props
    return (

 (this.videoContainer = node)}
        >

            <video
              ref={node => (this.videoNode = node)}
              className={`video-js vjs-default-skin ${
                embed ? "video-odl-embed" : ""
              }`}
              crossOrigin="anonymous"
              controls
            />

        {video.multiangle && (

 (this.cameras = node)} className="camera-bar">
            {Object.keys(CANVASES).map(corner => (

                <canvas
                  id={corner}
                  key={corner}
                  onClick={this.clickCamera.bind(this, corner)}
                  className={`camera-box ${
                    corner === selectedCorner ? "camera-box-selected" : ""
                  }`}
                />

            ))}

        )}

    )
  }

  setCurrentTime(time) {
    this.player.currentTime(time)
  }
}

export default connect()(VideoPlayer)
<|endoftext|>


<commit_before>'use strict'

const {app, BrowserWindow} = require('electron')
const binding = process.atomBinding('dialog')
const v8Util = process.atomBinding('v8_util')

const fileDialogProperties = {
  openFile: 1 << 0,
  openDirectory: 1 << 1,
  multiSelections: 1 << 2,
  createDirectory: 1 << 3,
  showHiddenFiles: 1 << 4
}

const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']

const messageBoxOptions = {
  noLink: 1 << 0
}

const parseArgs = function (window, options, callback, ...args) {
  if (window !== null && window.constructor !== BrowserWindow) {
    // Shift.
    [callback, options, window] = [options, window, null]
  }

  if ((callback == null) && typeof options === 'function') {
    // Shift.
    [callback, options] = [options, null]
  }

  // Fallback to using very last argument as the callback function
  const lastArgument = args[args.length - 1]
  if ((callback == null) && typeof lastArgument === 'function') {
    callback = lastArgument
  }

  return [window, options, callback]
}

const checkAppInitialized = function () {
  if (!app.isReady()) {
    throw new Error('dialog module can only be used after app is ready')
  }
}

module.exports = {
  showOpenDialog: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        title: 'Open',
        properties: ['openFile']
      }
    }

    let {buttonLabel, defaultPath, filters, properties, title} = options

    if (properties == null) {
      properties = ['openFile']
    } else if (!Array.isArray(properties)) {
      throw new TypeError('Properties must be an array')
    }

    let dialogProperties = 0
    for (const prop in fileDialogProperties) {
      if (properties.includes(prop)) {
        dialogProperties |= fileDialogProperties[prop]
      }
    }

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (buttonLabel == null) {
      buttonLabel = ''
    } else if (typeof buttonLabel !== 'string') {
      throw new TypeError('Button label must be a string')
    }

    if (defaultPath == null) {
      defaultPath = ''
    } else if (typeof defaultPath !== 'string') {
      throw new TypeError('Default path must be a string')
    }

    if (filters == null) {
      filters = []
    }

    const wrappedCallback = typeof callback === 'function' ? function (success, result) {
      return callback(success ? result : void 0)
    } : null
    return binding.showOpenDialog(title, buttonLabel, defaultPath, filters,
                                  dialogProperties, window, wrappedCallback)
  },

  showSaveDialog: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        title: 'Save'
      }
    }

    let {buttonLabel, defaultPath, filters, title} = options

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (buttonLabel == null) {
      buttonLabel = ''
    } else if (typeof buttonLabel !== 'string') {
      throw new TypeError('Button label must be a string')
    }

    if (defaultPath == null) {
      defaultPath = ''
    } else if (typeof defaultPath !== 'string') {
      throw new TypeError('Default path must be a string')
    }

    if (filters == null) {
      filters = []
    }

    const wrappedCallback = typeof callback === 'function' ? function (success, result) {
      return callback(success ? result : void 0)
    } : null
    return binding.showSaveDialog(title, buttonLabel, defaultPath, filters,
                                  window, wrappedCallback)
  },

  showMessageBox: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        type: 'none'
      }
    }

    let {buttons, cancelId, defaultId, detail, icon, message, title, type} = options

    if (type == null) {
      type = 'none'
    }

    const messageBoxType = messageBoxTypes.indexOf(type)
    if (messageBoxType === -1) {
      throw new TypeError('Invalid message box type')
    }

    if (buttons == null) {
      buttons = []
    } else if (!Array.isArray(buttons)) {
      throw new TypeError('Buttons must be an array')
    }

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (message == null) {
      message = ''
    } else if (typeof message !== 'string') {
      throw new TypeError('Message must be a string')
    }

    if (detail == null) {
      detail = ''
    } else if (typeof detail !== 'string') {
      throw new TypeError('Detail must be a string')
    }

    if (icon == null) {
      icon = null
    }

    if (defaultId == null) {
      defaultId = -1
    }

    // Choose a default button to get selected when dialog is cancelled.
    if (cancelId == null) {
      cancelId = 0
      for (let i = 0; i < buttons.length; i++) {
        const text = buttons[i].toLowerCase()
        if (text === 'cancel' || text === 'no') {
          cancelId = i
          break
        }
      }
    }

    const flags = options.noLink ? messageBoxOptions.noLink : 0
    return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
                                  flags, title, message, detail, icon, window,
                                  callback)
  },

  showErrorBox: function (...args) {
    return binding.showErrorBox(...args)
  }
}

// Mark standard asynchronous functions.
v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true)
<commit_msg>Use != null to handle undefined<commit_after>'use strict'

const {app, BrowserWindow} = require('electron')
const binding = process.atomBinding('dialog')
const v8Util = process.atomBinding('v8_util')

const fileDialogProperties = {
  openFile: 1 << 0,
  openDirectory: 1 << 1,
  multiSelections: 1 << 2,
  createDirectory: 1 << 3,
  showHiddenFiles: 1 << 4
}

const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']

const messageBoxOptions = {
  noLink: 1 << 0
}

const parseArgs = function (window, options, callback, ...args) {
  if (window != null && window.constructor !== BrowserWindow) {
    // Shift.
    [callback, options, window] = [options, window, null]
  }

  if ((callback == null) && typeof options === 'function') {
    // Shift.
    [callback, options] = [options, null]
  }

  // Fallback to using very last argument as the callback function
  const lastArgument = args[args.length - 1]
  if ((callback == null) && typeof lastArgument === 'function') {
    callback = lastArgument
  }

  return [window, options, callback]
}

const checkAppInitialized = function () {
  if (!app.isReady()) {
    throw new Error('dialog module can only be used after app is ready')
  }
}

module.exports = {
  showOpenDialog: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        title: 'Open',
        properties: ['openFile']
      }
    }

    let {buttonLabel, defaultPath, filters, properties, title} = options

    if (properties == null) {
      properties = ['openFile']
    } else if (!Array.isArray(properties)) {
      throw new TypeError('Properties must be an array')
    }

    let dialogProperties = 0
    for (const prop in fileDialogProperties) {
      if (properties.includes(prop)) {
        dialogProperties |= fileDialogProperties[prop]
      }
    }

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (buttonLabel == null) {
      buttonLabel = ''
    } else if (typeof buttonLabel !== 'string') {
      throw new TypeError('Button label must be a string')
    }

    if (defaultPath == null) {
      defaultPath = ''
    } else if (typeof defaultPath !== 'string') {
      throw new TypeError('Default path must be a string')
    }

    if (filters == null) {
      filters = []
    }

    const wrappedCallback = typeof callback === 'function' ? function (success, result) {
      return callback(success ? result : void 0)
    } : null
    return binding.showOpenDialog(title, buttonLabel, defaultPath, filters,
                                  dialogProperties, window, wrappedCallback)
  },

  showSaveDialog: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        title: 'Save'
      }
    }

    let {buttonLabel, defaultPath, filters, title} = options

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (buttonLabel == null) {
      buttonLabel = ''
    } else if (typeof buttonLabel !== 'string') {
      throw new TypeError('Button label must be a string')
    }

    if (defaultPath == null) {
      defaultPath = ''
    } else if (typeof defaultPath !== 'string') {
      throw new TypeError('Default path must be a string')
    }

    if (filters == null) {
      filters = []
    }

    const wrappedCallback = typeof callback === 'function' ? function (success, result) {
      return callback(success ? result : void 0)
    } : null
    return binding.showSaveDialog(title, buttonLabel, defaultPath, filters,
                                  window, wrappedCallback)
  },

  showMessageBox: function (...args) {
    checkAppInitialized()

    let [window, options, callback] = parseArgs(...args)

    if (options == null) {
      options = {
        type: 'none'
      }
    }

    let {buttons, cancelId, defaultId, detail, icon, message, title, type} = options

    if (type == null) {
      type = 'none'
    }

    const messageBoxType = messageBoxTypes.indexOf(type)
    if (messageBoxType === -1) {
      throw new TypeError('Invalid message box type')
    }

    if (buttons == null) {
      buttons = []
    } else if (!Array.isArray(buttons)) {
      throw new TypeError('Buttons must be an array')
    }

    if (title == null) {
      title = ''
    } else if (typeof title !== 'string') {
      throw new TypeError('Title must be a string')
    }

    if (message == null) {
      message = ''
    } else if (typeof message !== 'string') {
      throw new TypeError('Message must be a string')
    }

    if (detail == null) {
      detail = ''
    } else if (typeof detail !== 'string') {
      throw new TypeError('Detail must be a string')
    }

    if (icon == null) {
      icon = null
    }

    if (defaultId == null) {
      defaultId = -1
    }

    // Choose a default button to get selected when dialog is cancelled.
    if (cancelId == null) {
      cancelId = 0
      for (let i = 0; i < buttons.length; i++) {
        const text = buttons[i].toLowerCase()
        if (text === 'cancel' || text === 'no') {
          cancelId = i
          break
        }
      }
    }

    const flags = options.noLink ? messageBoxOptions.noLink : 0
    return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
                                  flags, title, message, detail, icon, window,
                                  callback)
  },

  showErrorBox: function (...args) {
    return binding.showErrorBox(...args)
  }
}

// Mark standard asynchronous functions.
v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showOpenDialog, 'asynchronous', true)
v8Util.setHiddenValue(module.exports.showSaveDialog, 'asynchronous', true)
<|endoftext|>


<commit_before>define(['backbone', 'marionette'], function(Backbone, Marionette) {
    return Backbone.Marionette.ItemView.extend({});
});
<commit_msg>Adds event view implementation<commit_after>define(['backbone', 'marionette'], function(Backbone, Marionette) {
    return Backbone.Marionette.ItemView.extend({
        template: '#event',
        initialize: function() {},
        render: function() {
            var rendered_template = _.template($(this.template).html())(this.model.attributes);
            this.$el.append(rendered_template);
            return this;
        },
    });
});
<|endoftext|>


<commit_before>var assert = require("assert")
  , log = require("npmlog")
  , addRemoteGit = require("./add-remote-git.js")

module.exports = function maybeGithub (p, cb) {
  assert(typeof p === "string", "must pass package name")
  assert(typeof cb === "function", "must pass callback")

  var u = "git://github.com/" + p
  log.info("maybeGithub", "Attempting %s from %s", p, u)

  return addRemoteGit(u, true, function (er, data) {
    if (er) {
      var upriv = "git+ssh://git@github.com:" + p
      log.info("maybeGithub", "Attempting %s from %s", p, upriv)

      return addRemoteGit(upriv, false, function (er, data) {
        if (er) return cb(er)

        success(upriv, data)
      })
    }

    success(u, data)
  })

  function success (u, data) {
    data._from = u
    data._fromGithub = true
    return cb(null, data)
  }
}
<commit_msg>cache: add-remote-git expects no 'git+' on URLs<commit_after>var assert = require("assert")
  , log = require("npmlog")
  , addRemoteGit = require("./add-remote-git.js")

module.exports = function maybeGithub (p, cb) {
  assert(typeof p === "string", "must pass package name")
  assert(typeof cb === "function", "must pass callback")

  var u = "git://github.com/" + p
  log.info("maybeGithub", "Attempting %s from %s", p, u)

  return addRemoteGit(u, true, function (er, data) {
    if (er) {
      var upriv = "ssh://git@github.com:" + p
      log.info("maybeGithub", "Attempting %s from %s", p, upriv)

      return addRemoteGit(upriv, false, function (er, data) {
        if (er) return cb(er)

        success(upriv, data)
      })
    }

    success(u, data)
  })

  function success (u, data) {
    data._from = u
    data._fromGithub = true
    return cb(null, data)
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import "../../commons/forms/select-token-filter.js";

export default class PhenotypeNameAutocomplete extends LitElement {

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            value: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    endpoint(resource) {
        return {
            INDIVIDUAL: this.opencgaSession.opencgaClient.individuals(),
            SAMPLE: this.opencgaSession.opencgaClient.samples(),
            FAMILY: this.opencgaSession.opencgaClient.families(),
        }[resource] || this.opencgaSession.opencgaClient.samples();
    }

    onFilterChange(key, value) {
        const event = new CustomEvent("filterChange", {
            detail: {
                value: value
            }
        });
        this.dispatchEvent(event);
    }

    getDefaultConfig() {
        return {
            limit: 10,
            /* fields: item => ({
                name: item
            }),*/
            // direct select2 configuration
            select2Config: {
                // enables free tokenisation as phenotypes field actually perform a full-text search (while the autocomplete works in IDs only)
                tags: true
            },
            source: (params, success, failure) => {
                const page = params?.data?.page || 1;
                const phenotypes = params?.data?.term ? {phenotypes: "~/" + params?.data?.term + "/i"} : null;
                const filters = {
                    study: this.opencgaSession.study.fqn,
                    limit: this._config.limit,
                    count: false,
                    skip: (page - 1) * this._config.limit,
                    // include: "id,proband",
                    ...phenotypes
                };
                this.endpoint(this._config.resource).distinct("phenotypes.name", filters)
                    .then(response => success(response))
                    .catch(error => failure(error));
            },
        };
    }

    render() {
        return html`
            <select-token-filter
                .opencgaSession="${this.opencgaSession}"
                .config="${this._config}"
                .value="${this.value}"
                @filterChange="${e => this.onFilterChange("id", e.detail.value)}">
            </select-token-filter>
        `;
    }

}

customElements.define("phenotype-name-autocomplete", PhenotypeNameAutocomplete);
<commit_msg>wc: fix phenotype autocomplete filter to restrict results #TASK-1502<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import "../../commons/forms/select-token-filter.js";

export default class PhenotypeNameAutocomplete extends LitElement {

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            value: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    endpoint(resource) {
        return {
            INDIVIDUAL: this.opencgaSession.opencgaClient.individuals(),
            SAMPLE: this.opencgaSession.opencgaClient.samples(),
            FAMILY: this.opencgaSession.opencgaClient.families(),
        }[resource] || this.opencgaSession.opencgaClient.samples();
    }

    onFilterChange(key, value) {
        const event = new CustomEvent("filterChange", {
            detail: {
                value: value
            }
        });
        this.dispatchEvent(event);
    }

    getDefaultConfig() {
        return {
            limit: 9999,
            /* fields: item => ({
                name: item
            }),*/
            // direct select2 configuration
            select2Config: {
                // enables free tokenisation as phenotypes field actually perform a full-text search (while the autocomplete works in IDs only)
                tags: true
            },
            source: (params, success, failure) => {
                // const page = params?.data?.page || 1;
                const phenotypes = params?.data?.term ? {phenotypes: "~/" + params?.data?.term + "/i"} : null;
                const filters = {
                    study: this.opencgaSession.study.fqn,
                    // limit: this._config.limit,
                    count: false,
                    // skip: (page - 1) * this._config.limit,
                    // include: "id,proband",
                    ...phenotypes
                };
                this.endpoint(this._config.resource).distinct("phenotypes.name", filters)
                    .then(response => {
                        if (params?.data?.term) {
                            const term = params.data.term.toUpperCase();
                            response.responses[0].results = response.responses[0].results.filter(item => {
                                return item.toUpperCase().includes(term);
                            });
                        }
                        success(response);
                    })
                    .catch(error => failure(error));
            },
        };
    }

    render() {
        return html`
            <select-token-filter
                .opencgaSession="${this.opencgaSession}"
                .config="${this._config}"
                .value="${this.value}"
                @filterChange="${e => this.onFilterChange("id", e.detail.value)}">
            </select-token-filter>
        `;
    }

}

customElements.define("phenotype-name-autocomplete", PhenotypeNameAutocomplete);
<|endoftext|>


<commit_before>#!/usr/bin/env node

/* eslint-disable no-await-in-loop, no-return-assign */

const crypto = require('crypto');
const prettyBytes = require('pretty-bytes');
const Table = require('cli-table');
const timeSpan = require('time-span');
const b64 = require('../');

const bytesToBenchmark = [10000, 100000, 1000000, 10000000];

const bench = noOfBytes => Promise.resolve().then(async () => {
	const results = {};
	let end;

	console.log(`Generating ${prettyBytes(noOfBytes)} of random binary data...`);
	const randomBytes = crypto.randomBytes(noOfBytes);

	console.log('Encoding sync...');
	end = timeSpan();
	const randomBytesBase64 = randomBytes.toString('base64');
	results.encodeSync = end();

	console.log('Decoding sync...');
	end = timeSpan();
	Buffer.from(randomBytesBase64, 'base64');
	results.decodeSync = end();

	console.log('Encoding async...');
	end = timeSpan();
	await b64(randomBytes);
	results.encodeAsync = end();

	console.log('Decoding async...');
	end = timeSpan();
	await b64(randomBytesBase64);
	results.decodeAsync = end();

	console.log();
	return results;
});

(async () => {
	const table = new Table({
		head: [
			'Bytes',
			'Encode Sync',
			'Decode Sync',
			'Encode Async',
			'Decode Async'
		]
	});

	for (const noOfBytes of bytesToBenchmark) {
		const results = await bench(noOfBytes);
		table.push([
			prettyBytes(noOfBytes),
			results.encodeSync + 'ms',
			results.decodeSync + 'ms',
			results.encodeAsync + 'ms',
			results.decodeAsync + 'ms'
		]);
	}

	console.log(table.toString());
})();
<commit_msg>Benchmark up to 100 MB<commit_after>#!/usr/bin/env node

/* eslint-disable no-await-in-loop, no-return-assign */

const crypto = require('crypto');
const prettyBytes = require('pretty-bytes');
const Table = require('cli-table');
const timeSpan = require('time-span');
const b64 = require('../');

const bytesToBenchmark = [10000, 100000, 1000000, 10000000, 100000000];

const bench = noOfBytes => Promise.resolve().then(async () => {
	const results = {};
	let end;

	console.log(`Generating ${prettyBytes(noOfBytes)} of random binary data...`);
	const randomBytes = crypto.randomBytes(noOfBytes);

	console.log('Encoding sync...');
	end = timeSpan();
	const randomBytesBase64 = randomBytes.toString('base64');
	results.encodeSync = end();

	console.log('Decoding sync...');
	end = timeSpan();
	Buffer.from(randomBytesBase64, 'base64');
	results.decodeSync = end();

	console.log('Encoding async...');
	end = timeSpan();
	await b64(randomBytes);
	results.encodeAsync = end();

	console.log('Decoding async...');
	end = timeSpan();
	await b64(randomBytesBase64);
	results.decodeAsync = end();

	console.log();
	return results;
});

(async () => {
	const table = new Table({
		head: [
			'Bytes',
			'Encode Sync',
			'Decode Sync',
			'Encode Async',
			'Decode Async'
		]
	});

	for (const noOfBytes of bytesToBenchmark) {
		const results = await bench(noOfBytes);
		table.push([
			prettyBytes(noOfBytes),
			results.encodeSync + 'ms',
			results.decodeSync + 'ms',
			results.encodeAsync + 'ms',
			results.decodeAsync + 'ms'
		]);
	}

	console.log(table.toString());
})();
<|endoftext|>


<commit_before>#!/usr/bin/env node

/*
  Determine Basal

  Released under MIT license. See the accompanying LICENSE.txt file for
  full terms and conditions

  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.
*/

/* istanbul ignore next */
if (!module.parent) {
    var determinebasal = init();

    var argv = require('yargs')
      .usage("$0 iob.json currenttemp.json glucose.json profile.json [[--auto-sens] autosens.json] [meal.json] [--reservoir reservoir.json]")
      .option('auto-sens', {
        alias: 'a',
        describe: "Auto-sensitivity configuration",
        default: true

      })
      .option('reservoir', {
        alias: 'r',
        describe: "Reservoir status file for SuperMicroBolus mode (oref1)",
        default: false

      })
      .option('meal', {
        describe: "json doc describing meals",
        default: true

      })
      .option('missing-auto-sens-ok', {
        describe: "If auto-sens data is missing, try anyway.",
        default: true

      })
      .option('missing-meal-ok', {
        describe: "If meal data is missing, try anyway.",
        default: true

      })
      .option('microbolus', {
        describe: "Enable SuperMicroBolus mode (oref1)",
        default: false

      })
      // error and show help if some other args given
      .strict(true)
      .help('help')
    ;
    function usage ( ) {
      argv.showHelp( );
    }

    var params = argv.argv;
    var errors = [ ];
    var warnings = [ ];

    var iob_input = params._.slice(0, 1).pop();
    if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) {

      usage( );
      process.exit(0)
    }
    var currenttemp_input = params._.slice(1, 2).pop();
    var glucose_input = params._.slice(2, 3).pop();
    var profile_input = params._.slice(3, 4).pop();
    var meal_input = params._.slice(4, 5).pop();
    var autosens_input = params.autoSens;
    if (params._.length > 5) {
      autosens_input = params.autoSens ? params._.slice(4, 5).pop() : false;
      meal_input = params._.slice(5, 6).pop();
    }
    if (params.meal && params.meal !== true && !meal_input) {
      meal_input = params.meal;
    }
    var reservoir_input = params.reservoir;

    if (!iob_input || !currenttemp_input || !glucose_input || !profile_input) {
        usage( );
        process.exit(1);
    }

    var fs = require('fs');
    try {
        var cwd = process.cwd();
        var glucose_data = require(cwd + '/' + glucose_input);
        var currenttemp = require(cwd + '/' + currenttemp_input);
        var iob_data = require(cwd + '/' + iob_input);
        var profile = require(cwd + '/' + profile_input);
        var glucose_status = determinebasal.getLastGlucose(glucose_data);
    } catch (e) {
        return console.error("Could not parse input data: ", e);
    }

    //console.log(carbratio_data);
    var meal_data = { };
    //console.error("meal_input",meal_input);
    if (meal_input && typeof meal_input != 'undefined') {
        try {
            meal_data = JSON.parse(fs.readFileSync(meal_input, 'utf8'));
            console.error(JSON.stringify(meal_data));
        } catch (e) {
            var msg = {
              msg: "Optional feature Meal Assist enabled, but could not read required meal data."
            , file: meal_input
            , error: e
            };
            console.error(msg.msg);
            // console.log(JSON.stringify(msg));
            if (!params['missing-meal-ok']) {
              warnings.push(msg);
            }
            // process.exit(1);
        }
    }
    //if (meal_input) { meal_data = require(cwd + '/' + meal_input); }

    //console.error(autosens_input);
    var autosens_data = null;
    if (autosens_input) {
      // { "ratio":1 };
      autosens_data = { "ratio": 1 };
      if (autosens_input !== true && autosens_input.length) {
        try {
            autosens_data = JSON.parse(fs.readFileSync(autosens_input, 'utf8'));
            //console.error(JSON.stringify(autosens_data));
        } catch (e) {
            var msg = {
              msg: "Optional feature Auto Sensitivity enabled.  Could not find specified auto-sens: " + autosens_input
            , error: e
            };
            console.error(msg.msg);
            console.error(e);
            // console.log(JSON.stringify(msg));
            if (!params['missing-auto-sens-ok']) {
              errors.push(msg);
            }
            // process.exit(1);
        }
      }
    }
    var reservoir_data = null;
    if (reservoir_input && typeof reservoir_input != 'undefined') {
        try {
            reservoir_data = fs.readFileSync(reservoir_input, 'utf8');
            //console.error(reservoir_data);
        } catch (e) {
            var msg = {
              msg: "Warning: Could not read required reservoir data from "+reservoir_input+"."
            , file: reservoir_input
            , error: e
            };
            console.error(msg.msg);
        }
    }

    //if old reading from Dexcom do nothing

    var systemTime = new Date();
    var bgTime;
    if (glucose_data[0].display_time) {
        bgTime = new Date(glucose_data[0].display_time.replace('T', ' '));
    } else if (glucose_data[0].dateString) {
        bgTime = new Date(glucose_data[0].dateString);
    } else { console.error("Could not determine last BG time"); }
    var minAgo = (systemTime - bgTime) / 60 / 1000;

    if (warnings.length) {
      console.error(JSON.stringify(warnings));
    }

    if (errors.length) {
      console.log(JSON.stringify(errors));
      process.exit(1);
    }

    if (minAgo > 10 || minAgo < -5) { // Dexcom data is too old, or way in the future
        var reason = "BG data is too old (it's probably this), or clock set incorrectly.  The last BG data was read at "+bgTime+" but your system time currently is "+systemTime;
        console.error(reason);
        var msg = {reason: reason }
	console.log(JSON.stringify(msg));
//        errors.push(msg);
        process.exit(1);
    }

    if (typeof(iob_data.length) && iob_data.length > 1) {
        console.error(JSON.stringify(iob_data[0]));
    } else {
        console.error(JSON.stringify(iob_data));
    }

    console.error(JSON.stringify(glucose_status));
    console.error(JSON.stringify(currenttemp));
    //console.error(JSON.stringify(profile));

    var tempBasalFunctions = require('oref0/lib/basal-set-temp');

    rT = determinebasal.determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, params['microbolus'], reservoir_data);

    if(typeof rT.error === 'undefined') {
        console.log(JSON.stringify(rT));
    } else {
        console.error(rT.error);
    }

}

function init() {

    var determinebasal = {
        name: 'determine-basal'
        , label: "OpenAPS Determine Basal"
    };

    determinebasal.getLastGlucose = require('oref0/lib/glucose-get-last');
    determinebasal.determine_basal = require('oref0/lib/determine-basal/determine-basal');
    return determinebasal;

}
module.exports = init;
<commit_msg>Adding a check for existing autotune folder. If it does not exist, warn user that microbolus-related features will be disabled.<commit_after>#!/usr/bin/env node

/*
  Determine Basal

  Released under MIT license. See the accompanying LICENSE.txt file for
  full terms and conditions

  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.
*/

/* istanbul ignore next */
if (!module.parent) {
    var determinebasal = init();

    var argv = require('yargs')
      .usage("$0 iob.json currenttemp.json glucose.json profile.json [[--auto-sens] autosens.json] [meal.json] [--reservoir reservoir.json]")
      .option('auto-sens', {
        alias: 'a',
        describe: "Auto-sensitivity configuration",
        default: true

      })
      .option('reservoir', {
        alias: 'r',
        describe: "Reservoir status file for SuperMicroBolus mode (oref1)",
        default: false

      })
      .option('meal', {
        describe: "json doc describing meals",
        default: true

      })
      .option('missing-auto-sens-ok', {
        describe: "If auto-sens data is missing, try anyway.",
        default: true

      })
      .option('missing-meal-ok', {
        describe: "If meal data is missing, try anyway.",
        default: true

      })
      .option('microbolus', {
        describe: "Enable SuperMicroBolus mode (oref1)",
        default: false

      })
      // error and show help if some other args given
      .strict(true)
      .help('help')
    ;
    function usage ( ) {
      argv.showHelp( );
    }

    var params = argv.argv;
    var errors = [ ];
    var warnings = [ ];

    var iob_input = params._.slice(0, 1).pop();
    if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) {

      usage( );
      process.exit(0)
    }
    var currenttemp_input = params._.slice(1, 2).pop();
    var glucose_input = params._.slice(2, 3).pop();
    var profile_input = params._.slice(3, 4).pop();
    var meal_input = params._.slice(4, 5).pop();
    var autosens_input = params.autoSens;
    if (params._.length > 5) {
      autosens_input = params.autoSens ? params._.slice(4, 5).pop() : false;
      meal_input = params._.slice(5, 6).pop();
    }
    if (params.meal && params.meal !== true && !meal_input) {
      meal_input = params.meal;
    }
    var reservoir_input = params.reservoir;

    if (!iob_input || !currenttemp_input || !glucose_input || !profile_input) {
        usage( );
        process.exit(1);
    }

    var fs = require('fs');
    try {
        var cwd = process.cwd();
        var glucose_data = require(cwd + '/' + glucose_input);
        var currenttemp = require(cwd + '/' + currenttemp_input);
        var iob_data = require(cwd + '/' + iob_input);
        var profile = require(cwd + '/' + profile_input);
        var glucose_status = determinebasal.getLastGlucose(glucose_data);
    } catch (e) {
        return console.error("Could not parse input data: ", e);
    }

//attempting to provide a check for autotune
//if autotune directory does not exist, SMB/oref1 should not be able to run

// console.error("Printing this so you know it's getting to the check for autotune.")

//printing microbolus before attempting check
console.error("Microbolus var is currently set to: ",params['microbolus']);

if (fs.existsSync("autotune")) {
    console.error("Autotune exists! Hoorah!")
    }
else {
    console.error("Warning: Autotune has not been run. All microboluses will be disabled until you manually run autotune or add it to run nightly in your loop.");
    params['microbolus'] = false;
    console.error("Microbolus var is currently set to: ",params['microbolus']);
    }

    //console.log(carbratio_data);
    var meal_data = { };
    //console.error("meal_input",meal_input);
    if (meal_input && typeof meal_input != 'undefined') {
        try {
            meal_data = JSON.parse(fs.readFileSync(meal_input, 'utf8'));
            console.error(JSON.stringify(meal_data));
        } catch (e) {
            var msg = {
              msg: "Optional feature Meal Assist enabled, but could not read required meal data."
            , file: meal_input
            , error: e
            };
            console.error(msg.msg);
            // console.log(JSON.stringify(msg));
            if (!params['missing-meal-ok']) {
              warnings.push(msg);
            }
            // process.exit(1);
        }
    }
    //if (meal_input) { meal_data = require(cwd + '/' + meal_input); }

    //console.error(autosens_input);
    var autosens_data = null;
    if (autosens_input) {
      // { "ratio":1 };
      autosens_data = { "ratio": 1 };
      if (autosens_input !== true && autosens_input.length) {
        try {
            autosens_data = JSON.parse(fs.readFileSync(autosens_input, 'utf8'));
            //console.error(JSON.stringify(autosens_data));
        } catch (e) {
            var msg = {
              msg: "Optional feature Auto Sensitivity enabled.  Could not find specified auto-sens: " + autosens_input
            , error: e
            };
            console.error(msg.msg);
            console.error(e);
            // console.log(JSON.stringify(msg));
            if (!params['missing-auto-sens-ok']) {
              errors.push(msg);
            }
            // process.exit(1);
        }
      }
    }
    var reservoir_data = null;
    if (reservoir_input && typeof reservoir_input != 'undefined') {
        try {
            reservoir_data = fs.readFileSync(reservoir_input, 'utf8');
            //console.error(reservoir_data);
        } catch (e) {
            var msg = {
              msg: "Warning: Could not read required reservoir data from "+reservoir_input+"."
            , file: reservoir_input
            , error: e
            };
            console.error(msg.msg);
        }
    }

    //if old reading from Dexcom do nothing

    var systemTime = new Date();
    var bgTime;
    if (glucose_data[0].display_time) {
        bgTime = new Date(glucose_data[0].display_time.replace('T', ' '));
    } else if (glucose_data[0].dateString) {
        bgTime = new Date(glucose_data[0].dateString);
    } else { console.error("Could not determine last BG time"); }
    var minAgo = (systemTime - bgTime) / 60 / 1000;

    if (warnings.length) {
      console.error(JSON.stringify(warnings));
    }

    if (errors.length) {
      console.log(JSON.stringify(errors));
      process.exit(1);
    }

    if (minAgo > 10 || minAgo < -5) { // Dexcom data is too old, or way in the future
        var reason = "BG data is too old (it's probably this), or clock set incorrectly.  The last BG data was read at "+bgTime+" but your system time currently is "+systemTime;
        console.error(reason);
        var msg = {reason: reason }
	console.log(JSON.stringify(msg));
//        errors.push(msg);
        process.exit(1);
    }

    if (typeof(iob_data.length) && iob_data.length > 1) {
        console.error(JSON.stringify(iob_data[0]));
    } else {
        console.error(JSON.stringify(iob_data));
    }

    console.error(JSON.stringify(glucose_status));
    console.error(JSON.stringify(currenttemp));
    //console.error(JSON.stringify(profile));

    var tempBasalFunctions = require('oref0/lib/basal-set-temp');

    rT = determinebasal.determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, params['microbolus'], reservoir_data);

    if(typeof rT.error === 'undefined') {
        console.log(JSON.stringify(rT));
    } else {
        console.error(rT.error);
    }

}

function init() {

    var determinebasal = {
        name: 'determine-basal'
        , label: "OpenAPS Determine Basal"
    };

    determinebasal.getLastGlucose = require('oref0/lib/glucose-get-last');
    determinebasal.determine_basal = require('oref0/lib/determine-basal/determine-basal');
    return determinebasal;

}
module.exports = init;
<|endoftext|>


<commit_before>/*
 * camin.io
 *
 * @author quaqua <quaqua@tastenwerk.com>
 * @date 01/2014
 * @copyright TASTENWERK http://tastenwerk.com
 * @license MIT
 *
 */

var _             = require('lodash')
  , fs            = require('fs')
  , join          = require('path').join;

module.exports = function( caminio ){

  if( !fs.existsSync( caminio.config.paths.config ) )
    throwMissing( caminio.config.paths.config, true );

  /**
   * database adapters
   */
  loadConfig( 'adapters' );

  /**
   * session settings
   */
  loadConfig( 'session' );

  /**
   * token settings
   */
  loadConfig( 'token' );

  /**
   * session settings
   */
  loadConfig( 'routes' );

  /**
   * environment settings
   */
  _.merge( caminio.config, require( join(caminio.config.paths.config, 'environments', caminio.env) ) );

  // errors
  caminio.config.errors = {};
  var errorsPath = join(caminio.config.paths.config,'errors');
  if( fs.existsSync( errorsPath ) ){
    fs
      .readdirSync( errorsPath )
      .forEach( function( file ){
        var name = file.replace('.js','');
        caminio.config.errors[name] = require( join(errorsPath,file) )( caminio );
      });
  }

  function loadConfig( str ){
    var adaptersPath = join( caminio.config.paths.config, str+'.js' );
    if( !fs.existsSync( adaptersPath ) )
      throwMissing( adaptersPath );
    caminio.config[str] = require( adaptersPath )[str];
  }

  function throwMissing( pth, directory ){
    if( directory )
      throw Error( 'missing directory '+pth );
    throw Error( 'missing file '+pth );
  }

}<commit_msg>removed adapter.js (is configured in environments)<commit_after>/*
 * camin.io
 *
 * @author quaqua <quaqua@tastenwerk.com>
 * @date 01/2014
 * @copyright TASTENWERK http://tastenwerk.com
 * @license MIT
 *
 */

var _             = require('lodash')
  , fs            = require('fs')
  , join          = require('path').join;

module.exports = function( caminio ){

  if( !fs.existsSync( caminio.config.paths.config ) )
    throwMissing( caminio.config.paths.config, true );

  /**
   * database adapters
   */
  //loadConfig( 'adapters' );

  /**
   * session settings
   */
  loadConfig( 'session' );

  /**
   * token settings
   */
  loadConfig( 'token' );

  /**
   * session settings
   */
  loadConfig( 'routes' );

  /**
   * environment settings
   */
  _.merge( caminio.config, require( join(caminio.config.paths.config, 'environments', caminio.env) ) );

  // errors
  caminio.config.errors = {};
  var errorsPath = join(caminio.config.paths.config,'errors');
  if( fs.existsSync( errorsPath ) ){
    fs
      .readdirSync( errorsPath )
      .forEach( function( file ){
        var name = file.replace('.js','');
        caminio.config.errors[name] = require( join(errorsPath,file) )( caminio );
      });
  }

  function loadConfig( str ){
    var adaptersPath = join( caminio.config.paths.config, str+'.js' );
    if( !fs.existsSync( adaptersPath ) )
      throwMissing( adaptersPath );
    caminio.config[str] = require( adaptersPath )[str];
  }

  function throwMissing( pth, directory ){
    if( directory )
      throw Error( 'missing directory '+pth );
    throw Error( 'missing file '+pth );
  }

}<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

/**
 * @private
 * @alias ConfigTransformRawMap
 * @typedef {Object<String,Function[]>}
 */

/**
 * @class
 * @alias ConfigTransformMap
 * @constructor
 */
function ConfigTransformMap() {
    this.map = {};
}

/**
 * @param {*} key
 * @param {...Function} value
 * @returns {ConfigTransformMap}
 */
ConfigTransformMap.prototype.set = _.restParam(function(key, value) {
    value = _.filter(value, _.isFunction);

    if (!_.isEmpty(value)) {
        var map = this.map[key];

        if (!_.isArray(map)) {
            map = [];
        }

        [].push.apply(map, value);

        this.map[key] = map;
    }

    return this;
});

/**
 * @param {*} key
 * @returns {Boolean}
 */
ConfigTransformMap.prototype.has = function(key) {
    return _.has(this.map, key);
};

/**
 * @param {*} key
 * @returns {Function}
 */
ConfigTransformMap.prototype.get = function(key) {
    return _.get(this.map, key);
};

/**
 * @param {*} key
 * @param {Function} value
 * @returns {Boolean}
 */
ConfigTransformMap.prototype.includes = function(key, value) {
    var map = this.get(key) || [];

    return _.includes(map, value);
};

/**
 * @returns {ConfigTransformRawMap}
 */
ConfigTransformMap.prototype.raw = function() {
    return this.map;
};

/**
 * @private
 * @module webpack-config/lib/configTransformMap
 * @returns {ConfigTransformMap}
 */
module.exports = ConfigTransformMap;
<commit_msg>fix(lodash): renames `_.restParam` to `_.rest`<commit_after>'use strict';

var _ = require('lodash');

/**
 * @private
 * @alias ConfigTransformRawMap
 * @typedef {Object<String,Function[]>}
 */

/**
 * @class
 * @alias ConfigTransformMap
 * @constructor
 */
function ConfigTransformMap() {
    this.map = {};
}

/**
 * @param {*} key
 * @param {...Function} value
 * @returns {ConfigTransformMap}
 */
ConfigTransformMap.prototype.set = _.rest(function(key, value) {
    value = _.filter(value, _.isFunction);

    if (!_.isEmpty(value)) {
        var map = this.map[key];

        if (!_.isArray(map)) {
            map = [];
        }

        [].push.apply(map, value);

        this.map[key] = map;
    }

    return this;
});

/**
 * @param {*} key
 * @returns {Boolean}
 */
ConfigTransformMap.prototype.has = function(key) {
    return _.has(this.map, key);
};

/**
 * @param {*} key
 * @returns {Function}
 */
ConfigTransformMap.prototype.get = function(key) {
    return _.get(this.map, key);
};

/**
 * @param {*} key
 * @param {Function} value
 * @returns {Boolean}
 */
ConfigTransformMap.prototype.includes = function(key, value) {
    var map = this.get(key) || [];

    return _.includes(map, value);
};

/**
 * @returns {ConfigTransformRawMap}
 */
ConfigTransformMap.prototype.raw = function() {
    return this.map;
};

/**
 * @private
 * @module webpack-config/lib/configTransformMap
 * @returns {ConfigTransformMap}
 */
module.exports = ConfigTransformMap;
<|endoftext|>


<commit_before>/****************************************************************************
 The MIT License (MIT)

 Copyright (c) 2015 Apigee Corporation

 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.
 ****************************************************************************/
'use strict';

var debug = require('debug')('swagger');
var Url = require('url');
var CORS = require('cors');
var YAML = require('yamljs');

module.exports = init;

function init(runner) {
  return new Middleware(runner);
}

function Middleware(runner) {
  this.runner = runner;
  this.config = runner.config.swaggerNode;

  this.metadata = function metadata() {

    return this.runner.swaggerTools.swaggerMetadata();
  };

  this.security = function security() {

    debug('swagger security config: %j', this.config.securityHandlers);
    return this.runner.swaggerTools.swaggerSecurity(this.config.securityHandlers);
  };

  this.validator = function validator() {

    var validatorConfig = {
      validateResponse: !!this.config.validateResponse
    };
    debug('validator config: %j', validatorConfig);
    return this.runner.swaggerTools.swaggerValidator(validatorConfig);
  };

  this.router = function router() {

    var controllers = this.config.mockMode ? this.config.mockControllersDirs : this.config.controllersDirs;

    var routerConfig = {
      useStubs: !!this.config.mockMode,
      controllers: controllers
    };
    debug('swaggerTools router config: %j', routerConfig);
    return this.runner.swaggerTools.swaggerRouter(routerConfig);
  };

  this.jsonErrorHandler = function() {

    debug('jsonErrorHandler configured');
    return function jsonErrorHandler(err, req, res, next) {
      debug('jsonErrorHandler: %s', err.message);
      if (err && typeof err === 'object') {
        Object.defineProperty(err, 'message', { enumerable: true }); // include message property in response
        try {
          return res.end(JSON.stringify(err));
        } catch (err2) {
          console.error(err2)
        }
      }
      next(err);
    };
  };

  this.expressCompatibilityMW = function expressCompatibilityMW() {
    debug('express compatibility middleware configured');
    return expressCompatibility;
  };

  this.swaggerDoc = function swaggerDoc() {

    debug('swagger doc path: %s', this.config.swaggerDocPath);

    // should this just be based on accept type?
    var swaggerDocPath = this.config.swaggerDocPath;
    var yaml = YAML.stringify(this.runner.swagger, 99, 2);
    var json = JSON.stringify(this.runner.swagger, null, 2);

    return function swaggerDoc(req, res, next) {
      if (swaggerDocPath) {
        try {
          if (!req.path) { req.path = Url.parse(req.url).path }
          if (swaggerDocPath.indexOf(req.path) != -1) {
            if (req.headers['accept'].indexOf('yaml') != -1) {
              res.setHeader('Content-Type', 'application/yaml');
              return res.end(yaml);
            } else {
              res.setHeader('Content-Type', 'application/json');
              return res.end(json);
            }
          }
        } catch (err) {
          return next(err);
        }
      }
      next();
    };

  };

  // enables request.swaggerNode.config
  this.helpers = function helpers() {

    debug('swaggerNode.config will be attached to requests');
    var swaggerNode = {
      config: this.runner.config
    };
    return function(req, res, next) {
      req.swaggerNode = swaggerNode;
      next();
    };
  };

  // config options: https://www.npmjs.com/package/cors
  this.cors = function cors() {

    debug('cors config: %j', this.config.corsOptions);
    return CORS(this.config.corsOptions);
  };

  this.register = function register(app) {
    this.stack().forEach(function(mw) { app.use(mw); });
  };

  this.stack = function stack(includeErrorHandler) {

    includeErrorHandler = !!includeErrorHandler;

    var middlewares = [
      this.metadata(),
      this.security(),
      this.validator(),
      this.helpers(),
      this.expressCompatibilityMW(),
      this.router()
    ];

    if (this.config.corsOptions) {
      middlewares.unshift(this.cors());
    }

    if (includeErrorHandler && this.config.mapErrorsToJson) {
      middlewares.unshift(this.jsonErrorHandler());
    }

    if (this.config.docEndpoints && this.config.docEndpoints.raw) {
      middlewares.push(this.swaggerDoc());
    }

    return middlewares;
  };
}

function expressCompatibility(req, res, next) {

  if (!req.query || !req.path) {
    var url = Url.parse(req.url, !req.query);
    req.path = url.path;
    req.query = url.query;
  }

  res.json = function(obj) {
    res.statusCode = 200;
    res.setHeader('content-type', 'application/json');
    res.end(JSON.stringify(obj));
  };

  if (!req.get) req.get = function(name) {
    return this.headers[name];
  };

  if (!res.set) { res.set = res.setHeader; }
  if (!res.get) { res.get = res.getHeader; }
  if (!res.status) {
    res.status = function(status) {
      res.statusCode = status;
    };
  }

  next();
}
<commit_msg>add filtering capability to swagger doc serving middleware<commit_after>/****************************************************************************
 The MIT License (MIT)

 Copyright (c) 2015 Apigee Corporation

 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.
 ****************************************************************************/
'use strict';

var debug = require('debug')('swagger');
var Url = require('url');
var CORS = require('cors');
var YAML = require('yamljs');
var _ = require('lodash');

// default filter just drops all the x- labels
var DROP_SWAGGER_EXTENSIONS = /^(?!x-.*)/;

module.exports = init;

function init(runner) {
  return new Middleware(runner);
}

function Middleware(runner) {
  this.runner = runner;
  this.config = runner.config.swaggerNode;

  this.metadata = function metadata() {

    return this.runner.swaggerTools.swaggerMetadata();
  };

  this.security = function security() {

    debug('swagger security config: %j', this.config.securityHandlers);
    return this.runner.swaggerTools.swaggerSecurity(this.config.securityHandlers);
  };

  this.validator = function validator() {

    var validatorConfig = {
      validateResponse: !!this.config.validateResponse
    };
    debug('validator config: %j', validatorConfig);
    return this.runner.swaggerTools.swaggerValidator(validatorConfig);
  };

  this.router = function router() {

    var controllers = this.config.mockMode ? this.config.mockControllersDirs : this.config.controllersDirs;

    var routerConfig = {
      useStubs: !!this.config.mockMode,
      controllers: controllers
    };
    debug('swaggerTools router config: %j', routerConfig);
    return this.runner.swaggerTools.swaggerRouter(routerConfig);
  };

  this.jsonErrorHandler = function() {

    debug('jsonErrorHandler configured');
    return function jsonErrorHandler(err, req, res, next) {
      debug('jsonErrorHandler: %s', err.message);
      if (err && typeof err === 'object') {
        Object.defineProperty(err, 'message', { enumerable: true }); // include message property in response
        try {
          return res.end(JSON.stringify(err));
        } catch (err2) {
          console.error(err2)
        }
      }
      next(err);
    };
  };

  this.expressCompatibilityMW = function expressCompatibilityMW() {
    debug('express compatibility middleware configured');
    return expressCompatibility;
  };

  this.swaggerDoc = function swaggerDoc() {

    var rawDocPath = this.config.docEndpoints.raw;

    debug('swagger raw doc path: %s', rawDocPath);

    var filter = DROP_SWAGGER_EXTENSIONS;
    if (this.config.docEndpoints.filter) {
      filter = new RegExp(this.config.docEndpoints.filter);
    }
    debug('swagger doc filter: %s', filter);
    var filteredSwagger = filterKeysRecursive(this.runner.swagger, filter);

    // should this just be based on accept type?
    var yaml = YAML.stringify(filteredSwagger, 99, 2);
    var json = JSON.stringify(filteredSwagger, null, 2);

    return function swaggerDoc(req, res, next) {
      if (rawDocPath) {
        try {
          if (!req.path) { req.path = Url.parse(req.url).path }
          if (rawDocPath.indexOf(req.path) != -1) {
            if (req.headers['accept'].indexOf('yaml') != -1) {
              res.setHeader('Content-Type', 'application/yaml');
              return res.end(yaml);
            } else {
              res.setHeader('Content-Type', 'application/json');
              return res.end(json);
            }
          }
        } catch (err) {
          return next(err);
        }
      }
      next();
    };

  };

  function filterKeysRecursive(object, regex) {
    if (_.isPlainObject(object)) {
      var result = {};
      _.each(object, function(value, key) {
        if (regex.test(key)) {
          result[key] = filterKeysRecursive(value, regex);
        }
      });
      return result;
    }
    return object;
  }

  // enables request.swaggerNode.config
  this.helpers = function helpers() {

    debug('swaggerNode.config will be attached to requests');
    var swaggerNode = {
      config: this.runner.config
    };
    return function(req, res, next) {
      req.swaggerNode = swaggerNode;
      next();
    };
  };

  // config options: https://www.npmjs.com/package/cors
  this.cors = function cors() {

    debug('cors config: %j', this.config.corsOptions);
    return CORS(this.config.corsOptions);
  };

  this.register = function register(app) {
    this.stack().forEach(function(mw) { app.use(mw); });
  };

  this.stack = function stack(includeErrorHandler) {

    includeErrorHandler = !!includeErrorHandler;

    var middlewares = [
      this.metadata(),
      this.security(),
      this.validator(),
      this.helpers(),
      this.expressCompatibilityMW(),
      this.router()
    ];

    if (this.config.corsOptions) {
      middlewares.unshift(this.cors());
    }

    if (includeErrorHandler && this.config.mapErrorsToJson) {
      middlewares.unshift(this.jsonErrorHandler());
    }

    if (this.config.docEndpoints && this.config.docEndpoints.raw) {
      middlewares.push(this.swaggerDoc());
    }

    return middlewares;
  };
}

function expressCompatibility(req, res, next) {

  if (!req.query || !req.path) {
    var url = Url.parse(req.url, !req.query);
    req.path = url.path;
    req.query = url.query;
  }

  res.json = function(obj) {
    res.statusCode = 200;
    res.setHeader('content-type', 'application/json');
    res.end(JSON.stringify(obj));
  };

  if (!req.get) req.get = function(name) {
    return this.headers[name];
  };

  if (!res.set) { res.set = res.setHeader; }
  if (!res.get) { res.get = res.getHeader; }
  if (!res.status) {
    res.status = function(status) {
      res.statusCode = status;
    };
  }

  next();
}
<|endoftext|>


<commit_before>'use strict';

const dir = require('node-dir');
const yaml = require('js-yaml');
const slugify = require('lodash/kebabCase');
const path = require('path');
const utils = require('../utils');

/**
 * Gets all Content Types
 *
 * @param {object} configuration - configuration object
 *
 * @returns {Promise} - An {Array} containing {Object} for each content type, including `name` {string}, `id` name {string}, and full config {object} loaded from YAML config file
 */
const load = (configuration) => {
  const config = configuration || {};
  const types = [];
  const ids = [];
  let contentPath = path.join(process.cwd(), 'content-types');

  return new Promise((resolve, reject) => {
    if (typeof config !== 'object') {
      reject(new Error('Configuration parameter must be an object'));
    }
    if (config.hasOwnProperty('content')) {
      if (config.content.hasOwnProperty('directory')) {
        contentPath = config.content.directory;
      }
    }

    dir.readFiles(contentPath, {
      match: /.yml$/,
      exclude: /^\./,
    }, (err, content, next) => {
      if (err) {
        reject(err);
      }

      const type = yaml.safeLoad(content);

      if (!type.hasOwnProperty('name')) {
        reject(new Error('Content types require a name'));
      }

      if (!type.hasOwnProperty('id')) {
        reject(new Error('Content types require an id'));
      }

      if (type.id !== slugify(type.id)) {
        reject(new Error(`${type.id} needs to be written in kebab case (e.g. ${slugify(type.id)}`));
      }

      if (ids.indexOf(type.id) === -1) {
        ids.push(type.id);
        types.push(type);
      }
      else {
        reject(new Error(`Content type ${type.id} is duplicated!`));
      }

      if (!type.hasOwnProperty('identifier')) {
        reject(new Error(`Identifier missing in content type '${type.name}'.`));
      }

      if (type.hasOwnProperty('identifier')) {
        if (typeof type.identifier !== 'string') {
          reject(new Error(`Identifier in content type '${type.name}' must be a string`));
        }
      }

      // find attribute which is the identifier
      const attr = type.attributes.find(a => {
        return a.id === type.identifier;
      });

      if (attr === undefined) {
        reject(new Error(`Identifier '${type.identifier}' is not an attribute in content type '${type.name}'.`));
      }

      // check attribute only has one input
      if (attr.inputs && Array.isArray(Object.keys(attr.inputs)) && Object.keys(attr.inputs).length > 1) {
        reject(new Error(`Attribute '${type.identifier}' in content type '${type.name}' cannot be the identifier. Only attributes with one input can be the identifier.`));
      }

      next();
    }, (err) => {
      if (err) {
        reject(err);
      }
      resolve(types);
    });
  });
};

/**
 * Names of Content Types
 *
 * @param {array} types - Array of content types
 *
 * @returns {array} of Content Type names
 */
const names = types => {
  return types.map(type => {
    return type.name;
  });
};

/**
 * Attributes of a Content Type
 *
 * @param {string} id  id of a content type
 * @param {array} types  array of content type objects
 *
 * @returns {array} array of content type's attribute objects
 */
const attributes = (id, types) => {
  const filtered = utils.singleItem('id', id, types);

  return filtered ? filtered[0].attributes : false;
};

module.exports = load;
module.exports.names = names;
module.exports.attributes = attributes;
<commit_msg>:art: consistent parameter name<commit_after>'use strict';

const dir = require('node-dir');
const yaml = require('js-yaml');
const slugify = require('lodash/kebabCase');
const path = require('path');
const utils = require('../utils');

/**
 * Gets all Content Types
 *
 * @param {object} config - configuration object
 *
 * @returns {Promise} - An {Array} containing {Object} for each content type, including `name` {string}, `id` name {string}, and full config {object} loaded from YAML config file
 */
const load = (config) => {
  const configuration = config || {};
  const types = [];
  const ids = [];
  let contentPath = path.join(process.cwd(), 'content-types');

  return new Promise((resolve, reject) => {
    if (typeof configuration !== 'object') {
      reject(new Error('Configuration parameter must be an object'));
    }

    if (configuration.hasOwnProperty('content')) {
      if (configuration.content.hasOwnProperty('directory')) {
        contentPath = configuration.content.directory;
      }
    }

    dir.readFiles(contentPath, {
      match: /.yml$/,
      exclude: /^\./,
    }, (err, content, next) => {
      if (err) {
        reject(err);
      }

      const type = yaml.safeLoad(content);

      if (!type.hasOwnProperty('name')) {
        reject(new Error('Content types require a name'));
      }

      if (!type.hasOwnProperty('id')) {
        reject(new Error('Content types require an id'));
      }

      if (type.id !== slugify(type.id)) {
        reject(new Error(`${type.id} needs to be written in kebab case (e.g. ${slugify(type.id)}`));
      }

      if (ids.indexOf(type.id) === -1) {
        ids.push(type.id);
        types.push(type);
      }
      else {
        reject(new Error(`Content type ${type.id} is duplicated!`));
      }

      if (!type.hasOwnProperty('identifier')) {
        reject(new Error(`Identifier missing in content type '${type.name}'.`));
      }

      if (type.hasOwnProperty('identifier')) {
        if (typeof type.identifier !== 'string') {
          reject(new Error(`Identifier in content type '${type.name}' must be a string`));
        }
      }

      // find attribute which is the identifier
      const attr = type.attributes.find(a => {
        return a.id === type.identifier;
      });

      if (attr === undefined) {
        reject(new Error(`Identifier '${type.identifier}' is not an attribute in content type '${type.name}'.`));
      }

      // check attribute only has one input
      if (attr.inputs && Array.isArray(Object.keys(attr.inputs)) && Object.keys(attr.inputs).length > 1) {
        reject(new Error(`Attribute '${type.identifier}' in content type '${type.name}' cannot be the identifier. Only attributes with one input can be the identifier.`));
      }

      next();
    }, (err) => {
      if (err) {
        reject(err);
      }
      resolve(types);
    });
  });
};

/**
 * Names of Content Types
 *
 * @param {array} types - Array of content types
 *
 * @returns {array} of Content Type names
 */
const names = types => {
  return types.map(type => {
    return type.name;
  });
};

/**
 * Attributes of a Content Type
 *
 * @param {string} id  id of a content type
 * @param {array} types  array of content type objects
 *
 * @returns {array} array of content type's attribute objects
 */
const attributes = (id, types) => {
  const filtered = utils.singleItem('id', id, types);

  return filtered ? filtered[0].attributes : false;
};

module.exports = load;
module.exports.names = names;
module.exports.attributes = attributes;
<|endoftext|>


<commit_before>'use strict';
const STATUS_CODES = require('./status-codes');

const Handlers = {
  allowed(methods) {
    return (req, res) => {
      res.set('Allow', methods);
      res.status(STATUS_CODES.METHOD_NOT_ALLOWED);
      res.end();
    };
  }
};

const Err = (err, req, res, next) => { // eslint-disable-line no-unused-vars
  const error = {
    error: {
      name: err.name,
      message: err.message
    }
  };
  const statusCode = err.statusCode || STATUS_CODES.BAD_REQUEST;

  // Include response headers if it's a HTTP error
  if (err.response) {
    error.error.headers = err.response.headers;
  }

  res.status(statusCode);
  res.json(error);
};

module.exports = {
  Handlers,
  Err
};
<commit_msg>Added method to verify headers as middleware and if they're missing, provide feedback.<commit_after>'use strict';
const STATUS_CODES = require('./status-codes');

const Handlers = {
  allowed(methods) {
    return (req, res) => {
      res.set('Allow', methods);
      res.status(STATUS_CODES.METHOD_NOT_ALLOWED);
      res.end();
    };
  }
};

const Err = (err, req, res, next) => { // eslint-disable-line no-unused-vars
  const error = {
    error: {
      name: err.name,
      message: err.message
    }
  };
  const statusCode = err.statusCode || STATUS_CODES.BAD_REQUEST;

  // Include response headers if it's a HTTP error
  if (err.response) {
    error.error.headers = err.response.headers;
  }

  res.status(statusCode);
  res.json(error);
};

const VerifyHeader = (header, value) => ((req, res, next) => {
  const h = req.headers[header];

  if (h && h.toLowerCase() === value) {
    return next();
  }

  // Throw exception here to unearth issues
  const err = new TypeError(`${header} must be \`${value}\`.`);

  err.statusCode = STATUS_CODES.BAD_REQUEST;
  err.response = {};
  err.response.headers = {
    Accept: 'application/json'
  };
  throw err;
});

module.exports = {
  Handlers,
  Err,
  VerifyHeader
};
<|endoftext|>