<commit_before>import {UICorePlugin, Styler, Events, template, $} from 'clappr'
import {Promise} from 'es6-promise-polyfill'
import pluginHtml from './public/scrub-thumbnails.html'
import pluginStyle from './public/style.sass'

export default class ScrubThumbnailsPlugin extends UICorePlugin {
  get name() { return 'scrub-thumbnails' }

  get attributes() {
    return {
      'class': this.name
    }
  }
  get template() { return template(pluginHtml) }

  /*
   * Helper to build the "thumbs" property for a sprite sheet.
   *
   * spriteSheetUrl- The url to the sprite sheet image
   * numThumbs- The number of thumbnails on the sprite sheet
   * thumbWidth- The width of each thumbnail.
   * thumbHeight- The height of each thumbnail.
   * numColumns- The number of columns in the sprite sheet.
   * timeInterval- The interval (in seconds) between the thumbnails.
   * startTime- The time (in seconds) that the first thumbnail represents. (defaults to 0)
   */
  static buildSpriteConfig(spriteSheetUrl, numThumbs, thumbWidth, thumbHeight, numColumns, timeInterval, startTime) {
    startTime = startTime || 0
    var thumbs = []
    for(let i=0; i<numThumbs; i++) {
      thumbs.push({
        url: spriteSheetUrl,
        time: startTime+(i*timeInterval),
        w: thumbWidth,
        h: thumbHeight,
        x: (i%numColumns) * thumbWidth,
        y: Math.floor(i/numColumns) * thumbHeight
      })
    }
    return thumbs
  }

  // TODO check if seek enabled

  constructor(core) {
    super(core)
    this._thumbsLoaded = false
    this._show = false
    // proportion into seek bar that the user is hovered over 0-1
    this._hoverPosition = 0
    this._oldContainer = null
    // each element is {x, y, w, h, imageW, imageH, url, time, duration, src}
    // one entry for each thumbnail
    this._thumbs = []
    this._spotlightThumb = null
    // a promise that will be resolved when thumbs have loaded
    this._onThumbsLoaded =  new Promise((resolve) => {
      this._onThumbsLoadedResolve = resolve
    })
    this._buildThumbsFromOptions().then(() => {
      this._thumbsLoaded = true
      this._onThumbsLoadedResolve()
      this._init()
    }).catch((err) => {
      throw err
    })
  }

  bindEvents() {
    // Clappr 0.3 support
    if (Events.CORE_ACTIVE_CONTAINER_CHANGED) {
      this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.rebindEvents)
    }
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, this._onMouseMove)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR, this._onMouseLeave)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this._init)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this._onMediaControlContainerChanged)
  }

  rebindEvents() {
    this.stopListening()
    this.bindEvents()
  }

  _bindContainerEvents() {
    if (this._oldContainer) {
      this.stopListening(this._oldContainer, Events.CONTAINER_TIMEUPDATE, this._renderPlugin)
    }
    this._oldContainer = this.core.mediaControl.container
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_TIMEUPDATE, this._renderPlugin)
  }

  _onMediaControlContainerChanged() {
    this._bindContainerEvents()
  }

  // thumbSrc may be an array to add multiple
  addThumbnail(thumbSrc) {
    var thumbSrcs = thumbSrc.constructor === Array ? thumbSrc : [thumbSrc]
    return this._onThumbsLoaded.then(() => {
      var promises = thumbSrcs.map((a) => {
        return this._addThumbFromSrc(a).then((thumb) => {
            if (this._getOptions().backdropHeight) {
              // append thumb to backdrop
              var index = this._thumbs.indexOf(thumb)
              var $img = this._buildImg(thumb, this._getOptions().backdropHeight)
              // Add thumbnail reference
              this._$backdropCarouselImgs.splice(index, 0, $img)
              // Add thumbnail to DOM
              if (this._$backdropCarouselImgs.length === 1) {
                this._$carousel.append($img)
              }
              else if (index === 0) {
                this._$backdropCarouselImgs[1].before($img)
              }
              else {
                this._$backdropCarouselImgs[index-1].after($img)
              }
            }
        })
      })
      return Promise.all(promises).then(() => {
          if (promises.length > 0) {
            this._renderPlugin()
          }
      })
    })
  }

  // provide a reference to the thumb object you provided to remove it
  // thumbSrc may be an array to remove multiple
  removeThumbnail(thumbSrc) {
    var thumbSrcs = thumbSrc.constructor === Array ? thumbSrc : [thumbSrc]
    return this._onThumbsLoaded.then(() => {
      var foundAll = true
      var foundOne = false
      thumbSrcs.forEach((a) => {
        var found = this._thumbs.some((thumb, i) => {
          if (thumb.src === a) {
            this._thumbs.splice(i, 1)
            if (this._getOptions().backdropHeight) {
              // remove image from carousel
              this._$backdropCarouselImgs[i].remove()
              this._$backdropCarouselImgs.splice(i, 1)
            }
            return true
          }
          return false
        })
        if (!found) {
          foundAll = false
        }
        else {
          foundOne = true
        }
      })
      if (foundOne) {
        this._renderPlugin()
      }
      return Promise.resolve(foundAll)
    })
  }

  _init() {
    if (!this._thumbsLoaded) {
      // _init() will be called when the thumbs are loaded,
      // and whenever the media control rendered event is fired as just before this the dom elements get wiped in IE (https://github.com/tjenkinson/clappr-thumbnails-plugin/issues/5)
      return
    }
    // Init the backdropCarousel as array to keep reference of thumbnail images
    this._$backdropCarouselImgs = []
    // create/recreate the dom elements for the plugin
    this._createElements()
    this._loadBackdrop()
    this._renderPlugin()
  }

  _getOptions() {
    if (!("scrubThumbnails" in this.core.options)) {
      throw "'scrubThumbnails property missing from options object."
    }
    return this.core.options.scrubThumbnails
  }

  _appendElToMediaControl() {
    // insert after the background
    this.core.mediaControl.$el.find(".media-control-background").first().after(this.el)
  }

  _onMouseMove(e) {
    this._calculateHoverPosition(e)
    this._show = true
    this._renderPlugin()
  }

  _onMouseLeave() {
    this._show = false
    this._renderPlugin()
  }

  _calculateHoverPosition(e) {
    var offset = e.pageX - this.core.mediaControl.$seekBarContainer.offset().left
    // proportion into the seek bar that the mouse is hovered over 0-1
    this._hoverPosition = Math.min(1, Math.max(offset/this.core.mediaControl.$seekBarContainer.width(), 0))
  }

  _buildThumbsFromOptions() {
    var thumbs = this._getOptions().thumbs
    var promises = thumbs.map((thumb) => {
      return this._addThumbFromSrc(thumb)
    })
    return Promise.all(promises)
  }

  _addThumbFromSrc(thumbSrc) {
    return new Promise((resolve, reject) => {
      var img = new Image()
      img.onload = () => {
        resolve(img)
      }
      img.onerror = reject
      img.src = thumbSrc.url
    }).then((img) => {
      var startTime = thumbSrc.time
      // determine the thumb index
      var index = null
      this._thumbs.some((thumb, i) => {
        if (startTime < thumb.time) {
          index = i
          return true
        }
        return false
      })
      if (index === null) {
        index = this._thumbs.length
      }

      var next = index < this._thumbs.length ? this._thumbs[index] : null
      var prev = index > 0 ? this._thumbs[index-1] : null
      if (prev) {
        // update the duration of the previous thumbnail
        prev.duration = startTime - prev.time
      }
      // the duration this thumb lasts for
      // if it is the last thumb then duration will be null
      var duration = next ? next.time - thumbSrc.time : null
      var imageW = img.width
      var imageH = img.height
      var thumb = {
        imageW: imageW, // actual width of image
        imageH: imageH, // actual height of image
        x: thumbSrc.x || 0, // x coord in image of sprite
        y: thumbSrc.y || 0, // y coord in image of sprite
        w: thumbSrc.w || imageW, // width of sprite
        h: thumbSrc.h || imageH, // height of sprite
        url: thumbSrc.url,
        time: startTime, // time this thumb represents
        duration: duration, // how long (from time) this thumb represents
        src: thumbSrc
      }
      this._thumbs.splice(index, 0, thumb)
      return thumb
    })
  }

  // builds a dom element which represents the thumbnail
  // scaled to the provided height
  _buildImg(thumb, height) {
    var scaleFactor = height / thumb.h
    var $img = $("<img />").addClass("thumbnail-img").attr("src", thumb.url)

    // the container will contain the image positioned so that the correct sprite
    // is visible
    var $container = $("<div />").addClass("thumbnail-container")
    $container.css("width", thumb.w * scaleFactor)
    $container.css("height", height)
    $img.css({
      height: thumb.imageH * scaleFactor,
      left: -1 * thumb.x * scaleFactor,
      top: -1 * thumb.y * scaleFactor
    })
    $container.append($img)
    return $container
  }

  _loadBackdrop() {
    if (!this._getOptions().backdropHeight) {
      // disabled
      return
    }

    // append each of the thumbnails to the backdrop carousel
    let $carousel = this._$carousel
    for (let i=0; i<this._thumbs.length; i++) {
      let $img = this._buildImg(this._thumbs[i], this._getOptions().backdropHeight)
      // Keep reference to thumbnail
      this._$backdropCarouselImgs.push($img)
      // Add thumbnail to DOM
      $carousel.append($img)
    }
  }

  // calculate how far along the carousel should currently be slid
  // depending on where the user is hovering on the progress bar
  _updateCarousel() {
    if (!this._getOptions().backdropHeight) {
      // disabled
      return
    }

    var hoverPosition = this._hoverPosition
    var videoDuration = this.core.mediaControl.container.getDuration()
    var startTimeOffset = this.core.mediaControl.container.getStartTimeOffset()
    // the time into the video at the current hover position
    var hoverTime = startTimeOffset + (videoDuration * hoverPosition)
    var backdropWidth = this._$backdrop.width()
    var $carousel = this._$carousel
    var carouselWidth = $carousel.width()

    // slide the carousel so that the image on the carousel that is above where the person
    // is hovering maps to that position in time.
    // Thumbnails may not be distributed at even times along the video
    var thumbs = this._thumbs

    // assuming that each thumbnail has the same width
    var thumbWidth = carouselWidth/thumbs.length

    // determine which thumbnail applies to the current time
    var thumbIndex = this._getThumbIndexForTime(hoverTime)
    var thumb = thumbs[thumbIndex]
    var thumbDuration = thumb.duration
    if (thumbDuration === null) {
      // the last thumbnail duration will be null as it can't be determined
      // e.g the duration of the video may increase over time (live stream)
      // so calculate the duration now so this last thumbnail lasts till the end
      thumbDuration = Math.max(videoDuration + startTimeOffset - thumb.time, 0)
    }

    // determine how far accross that thumbnail we are
    var timeIntoThumb = hoverTime - thumb.time
    var positionInThumb = timeIntoThumb / thumbDuration
    var xCoordInThumb = thumbWidth * positionInThumb

    // now calculate the position along carousel that we want to be above the hover position
    var xCoordInCarousel = (thumbIndex * thumbWidth) + xCoordInThumb
    // and finally the position of the carousel when the hover position is taken in to consideration
    var carouselXCoord = xCoordInCarousel - (hoverPosition*backdropWidth)

    $carousel.css("left", -carouselXCoord)

    var maxOpacity = this._getOptions().backdropMaxOpacity || 0.6;
    var minOpacity = this._getOptions().backdropMinOpacity || 0.08;

    // now update the transparencies so that they fade in around the active one
    for(let i=0; i<thumbs.length; i++) {
      let thumbXCoord = thumbWidth * i
      let distance = thumbXCoord - xCoordInCarousel
      if (distance < 0) {
        // adjust so that distance is always a measure away from
        // each side of the active thumbnail
        // at every point on the active thumbnail the distance should
        // be 0
        distance = Math.min(0, distance+thumbWidth)
      }
      // fade over the width of 2 thumbnails
      let opacity = Math.max(maxOpacity - (Math.abs(distance)/(2*thumbWidth)), minOpacity)
      this._$backdropCarouselImgs[i].css("opacity", opacity)
    }
  }

  _updateSpotlightThumb() {
    if (!this._getOptions().spotlightHeight) {
      // disabled
      return
    }

    var hoverPosition = this._hoverPosition
    var videoDuration = this.core.mediaControl.container.getDuration()
    // the time into the video at the current hover position
    var startTimeOffset = this.core.mediaControl.container.getStartTimeOffset()
    var hoverTime = startTimeOffset + (videoDuration * hoverPosition)

    // determine which thumbnail applies to the current time
    var thumbIndex = this._getThumbIndexForTime(hoverTime)
    var thumb = this._thumbs[thumbIndex]

    if (this._spotlightThumb !== thumb) {
      // update thumbnail
      var $spotlight = this._$spotlight
      $spotlight.empty()
      $spotlight.append(this._buildImg(thumb, this._getOptions().spotlightHeight))
      this._spotlightThumb = thumb;
    }

    var elWidth = this.$el.width()
    var thumbWidth = $spotlight.width()

    var spotlightXPos = (elWidth * hoverPosition) - (thumbWidth / 2)

    // adjust so the entire thumbnail is always visible
    spotlightXPos = Math.max(Math.min(spotlightXPos, elWidth - thumbWidth), 0)

    $spotlight.css("left", spotlightXPos)
  }

  // returns the thumbnail which represents a time in the video
  // or null if there is no thumbnail that can represent the time
  _getThumbIndexForTime(time) {
    var thumbs = this._thumbs
    for(let i=thumbs.length-1; i>=0; i--) {
      let thumb = thumbs[i]
      if (thumb.time <= time) {
        return i
      }
    }
    // stretch the first thumbnail back to the start
    return 0
  }

  _renderPlugin() {
    if (!this._thumbsLoaded) {
      return
    }
    if (this._show && this._thumbs.length > 0) {
      this.$el.removeClass("hidden")
      this._updateCarousel()
      this._updateSpotlightThumb()
    }
    else {
      this.$el.addClass("hidden")
    }
  }

  _createElements() {
    this.$el.html(this.template({'backdropHeight':this._getOptions().backdropHeight, 'spotlightHeight':this._getOptions().spotlightHeight}))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    // cache dom references
    this._$spotlight = this.$el.find(".spotlight")
    this._$backdrop = this.$el.find(".backdrop")
    this._$carousel = this._$backdrop.find(".carousel")
    this.$el.addClass("hidden")
    this._appendElToMediaControl()
  }
}
<commit_msg>always get reference to spotlight<commit_after>import {UICorePlugin, Styler, Events, template, $} from 'clappr'
import {Promise} from 'es6-promise-polyfill'
import pluginHtml from './public/scrub-thumbnails.html'
import pluginStyle from './public/style.sass'

export default class ScrubThumbnailsPlugin extends UICorePlugin {
  get name() { return 'scrub-thumbnails' }

  get attributes() {
    return {
      'class': this.name
    }
  }
  get template() { return template(pluginHtml) }

  /*
   * Helper to build the "thumbs" property for a sprite sheet.
   *
   * spriteSheetUrl- The url to the sprite sheet image
   * numThumbs- The number of thumbnails on the sprite sheet
   * thumbWidth- The width of each thumbnail.
   * thumbHeight- The height of each thumbnail.
   * numColumns- The number of columns in the sprite sheet.
   * timeInterval- The interval (in seconds) between the thumbnails.
   * startTime- The time (in seconds) that the first thumbnail represents. (defaults to 0)
   */
  static buildSpriteConfig(spriteSheetUrl, numThumbs, thumbWidth, thumbHeight, numColumns, timeInterval, startTime) {
    startTime = startTime || 0
    var thumbs = []
    for(let i=0; i<numThumbs; i++) {
      thumbs.push({
        url: spriteSheetUrl,
        time: startTime+(i*timeInterval),
        w: thumbWidth,
        h: thumbHeight,
        x: (i%numColumns) * thumbWidth,
        y: Math.floor(i/numColumns) * thumbHeight
      })
    }
    return thumbs
  }

  // TODO check if seek enabled

  constructor(core) {
    super(core)
    this._thumbsLoaded = false
    this._show = false
    // proportion into seek bar that the user is hovered over 0-1
    this._hoverPosition = 0
    this._oldContainer = null
    // each element is {x, y, w, h, imageW, imageH, url, time, duration, src}
    // one entry for each thumbnail
    this._thumbs = []
    this._spotlightThumb = null
    // a promise that will be resolved when thumbs have loaded
    this._onThumbsLoaded =  new Promise((resolve) => {
      this._onThumbsLoadedResolve = resolve
    })
    this._buildThumbsFromOptions().then(() => {
      this._thumbsLoaded = true
      this._onThumbsLoadedResolve()
      this._init()
    }).catch((err) => {
      throw err
    })
  }

  bindEvents() {
    // Clappr 0.3 support
    if (Events.CORE_ACTIVE_CONTAINER_CHANGED) {
      this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.rebindEvents)
    }
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, this._onMouseMove)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR, this._onMouseLeave)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this._init)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this._onMediaControlContainerChanged)
  }

  rebindEvents() {
    this.stopListening()
    this.bindEvents()
  }

  _bindContainerEvents() {
    if (this._oldContainer) {
      this.stopListening(this._oldContainer, Events.CONTAINER_TIMEUPDATE, this._renderPlugin)
    }
    this._oldContainer = this.core.mediaControl.container
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_TIMEUPDATE, this._renderPlugin)
  }

  _onMediaControlContainerChanged() {
    this._bindContainerEvents()
  }

  // thumbSrc may be an array to add multiple
  addThumbnail(thumbSrc) {
    var thumbSrcs = thumbSrc.constructor === Array ? thumbSrc : [thumbSrc]
    return this._onThumbsLoaded.then(() => {
      var promises = thumbSrcs.map((a) => {
        return this._addThumbFromSrc(a).then((thumb) => {
            if (this._getOptions().backdropHeight) {
              // append thumb to backdrop
              var index = this._thumbs.indexOf(thumb)
              var $img = this._buildImg(thumb, this._getOptions().backdropHeight)
              // Add thumbnail reference
              this._$backdropCarouselImgs.splice(index, 0, $img)
              // Add thumbnail to DOM
              if (this._$backdropCarouselImgs.length === 1) {
                this._$carousel.append($img)
              }
              else if (index === 0) {
                this._$backdropCarouselImgs[1].before($img)
              }
              else {
                this._$backdropCarouselImgs[index-1].after($img)
              }
            }
        })
      })
      return Promise.all(promises).then(() => {
          if (promises.length > 0) {
            this._renderPlugin()
          }
      })
    })
  }

  // provide a reference to the thumb object you provided to remove it
  // thumbSrc may be an array to remove multiple
  removeThumbnail(thumbSrc) {
    var thumbSrcs = thumbSrc.constructor === Array ? thumbSrc : [thumbSrc]
    return this._onThumbsLoaded.then(() => {
      var foundAll = true
      var foundOne = false
      thumbSrcs.forEach((a) => {
        var found = this._thumbs.some((thumb, i) => {
          if (thumb.src === a) {
            this._thumbs.splice(i, 1)
            if (this._getOptions().backdropHeight) {
              // remove image from carousel
              this._$backdropCarouselImgs[i].remove()
              this._$backdropCarouselImgs.splice(i, 1)
            }
            return true
          }
          return false
        })
        if (!found) {
          foundAll = false
        }
        else {
          foundOne = true
        }
      })
      if (foundOne) {
        this._renderPlugin()
      }
      return Promise.resolve(foundAll)
    })
  }

  _init() {
    if (!this._thumbsLoaded) {
      // _init() will be called when the thumbs are loaded,
      // and whenever the media control rendered event is fired as just before this the dom elements get wiped in IE (https://github.com/tjenkinson/clappr-thumbnails-plugin/issues/5)
      return
    }
    // Init the backdropCarousel as array to keep reference of thumbnail images
    this._$backdropCarouselImgs = []
    // create/recreate the dom elements for the plugin
    this._createElements()
    this._loadBackdrop()
    this._renderPlugin()
  }

  _getOptions() {
    if (!("scrubThumbnails" in this.core.options)) {
      throw "'scrubThumbnails property missing from options object."
    }
    return this.core.options.scrubThumbnails
  }

  _appendElToMediaControl() {
    // insert after the background
    this.core.mediaControl.$el.find(".media-control-background").first().after(this.el)
  }

  _onMouseMove(e) {
    this._calculateHoverPosition(e)
    this._show = true
    this._renderPlugin()
  }

  _onMouseLeave() {
    this._show = false
    this._renderPlugin()
  }

  _calculateHoverPosition(e) {
    var offset = e.pageX - this.core.mediaControl.$seekBarContainer.offset().left
    // proportion into the seek bar that the mouse is hovered over 0-1
    this._hoverPosition = Math.min(1, Math.max(offset/this.core.mediaControl.$seekBarContainer.width(), 0))
  }

  _buildThumbsFromOptions() {
    var thumbs = this._getOptions().thumbs
    var promises = thumbs.map((thumb) => {
      return this._addThumbFromSrc(thumb)
    })
    return Promise.all(promises)
  }

  _addThumbFromSrc(thumbSrc) {
    return new Promise((resolve, reject) => {
      var img = new Image()
      img.onload = () => {
        resolve(img)
      }
      img.onerror = reject
      img.src = thumbSrc.url
    }).then((img) => {
      var startTime = thumbSrc.time
      // determine the thumb index
      var index = null
      this._thumbs.some((thumb, i) => {
        if (startTime < thumb.time) {
          index = i
          return true
        }
        return false
      })
      if (index === null) {
        index = this._thumbs.length
      }

      var next = index < this._thumbs.length ? this._thumbs[index] : null
      var prev = index > 0 ? this._thumbs[index-1] : null
      if (prev) {
        // update the duration of the previous thumbnail
        prev.duration = startTime - prev.time
      }
      // the duration this thumb lasts for
      // if it is the last thumb then duration will be null
      var duration = next ? next.time - thumbSrc.time : null
      var imageW = img.width
      var imageH = img.height
      var thumb = {
        imageW: imageW, // actual width of image
        imageH: imageH, // actual height of image
        x: thumbSrc.x || 0, // x coord in image of sprite
        y: thumbSrc.y || 0, // y coord in image of sprite
        w: thumbSrc.w || imageW, // width of sprite
        h: thumbSrc.h || imageH, // height of sprite
        url: thumbSrc.url,
        time: startTime, // time this thumb represents
        duration: duration, // how long (from time) this thumb represents
        src: thumbSrc
      }
      this._thumbs.splice(index, 0, thumb)
      return thumb
    })
  }

  // builds a dom element which represents the thumbnail
  // scaled to the provided height
  _buildImg(thumb, height) {
    var scaleFactor = height / thumb.h
    var $img = $("<img />").addClass("thumbnail-img").attr("src", thumb.url)

    // the container will contain the image positioned so that the correct sprite
    // is visible
    var $container = $("<div />").addClass("thumbnail-container")
    $container.css("width", thumb.w * scaleFactor)
    $container.css("height", height)
    $img.css({
      height: thumb.imageH * scaleFactor,
      left: -1 * thumb.x * scaleFactor,
      top: -1 * thumb.y * scaleFactor
    })
    $container.append($img)
    return $container
  }

  _loadBackdrop() {
    if (!this._getOptions().backdropHeight) {
      // disabled
      return
    }

    // append each of the thumbnails to the backdrop carousel
    let $carousel = this._$carousel
    for (let i=0; i<this._thumbs.length; i++) {
      let $img = this._buildImg(this._thumbs[i], this._getOptions().backdropHeight)
      // Keep reference to thumbnail
      this._$backdropCarouselImgs.push($img)
      // Add thumbnail to DOM
      $carousel.append($img)
    }
  }

  // calculate how far along the carousel should currently be slid
  // depending on where the user is hovering on the progress bar
  _updateCarousel() {
    if (!this._getOptions().backdropHeight) {
      // disabled
      return
    }

    var hoverPosition = this._hoverPosition
    var videoDuration = this.core.mediaControl.container.getDuration()
    var startTimeOffset = this.core.mediaControl.container.getStartTimeOffset()
    // the time into the video at the current hover position
    var hoverTime = startTimeOffset + (videoDuration * hoverPosition)
    var backdropWidth = this._$backdrop.width()
    var $carousel = this._$carousel
    var carouselWidth = $carousel.width()

    // slide the carousel so that the image on the carousel that is above where the person
    // is hovering maps to that position in time.
    // Thumbnails may not be distributed at even times along the video
    var thumbs = this._thumbs

    // assuming that each thumbnail has the same width
    var thumbWidth = carouselWidth/thumbs.length

    // determine which thumbnail applies to the current time
    var thumbIndex = this._getThumbIndexForTime(hoverTime)
    var thumb = thumbs[thumbIndex]
    var thumbDuration = thumb.duration
    if (thumbDuration === null) {
      // the last thumbnail duration will be null as it can't be determined
      // e.g the duration of the video may increase over time (live stream)
      // so calculate the duration now so this last thumbnail lasts till the end
      thumbDuration = Math.max(videoDuration + startTimeOffset - thumb.time, 0)
    }

    // determine how far accross that thumbnail we are
    var timeIntoThumb = hoverTime - thumb.time
    var positionInThumb = timeIntoThumb / thumbDuration
    var xCoordInThumb = thumbWidth * positionInThumb

    // now calculate the position along carousel that we want to be above the hover position
    var xCoordInCarousel = (thumbIndex * thumbWidth) + xCoordInThumb
    // and finally the position of the carousel when the hover position is taken in to consideration
    var carouselXCoord = xCoordInCarousel - (hoverPosition*backdropWidth)

    $carousel.css("left", -carouselXCoord)

    var maxOpacity = this._getOptions().backdropMaxOpacity || 0.6;
    var minOpacity = this._getOptions().backdropMinOpacity || 0.08;

    // now update the transparencies so that they fade in around the active one
    for(let i=0; i<thumbs.length; i++) {
      let thumbXCoord = thumbWidth * i
      let distance = thumbXCoord - xCoordInCarousel
      if (distance < 0) {
        // adjust so that distance is always a measure away from
        // each side of the active thumbnail
        // at every point on the active thumbnail the distance should
        // be 0
        distance = Math.min(0, distance+thumbWidth)
      }
      // fade over the width of 2 thumbnails
      let opacity = Math.max(maxOpacity - (Math.abs(distance)/(2*thumbWidth)), minOpacity)
      this._$backdropCarouselImgs[i].css("opacity", opacity)
    }
  }

  _updateSpotlightThumb() {
    if (!this._getOptions().spotlightHeight) {
      // disabled
      return
    }

    var hoverPosition = this._hoverPosition
    var videoDuration = this.core.mediaControl.container.getDuration()
    // the time into the video at the current hover position
    var startTimeOffset = this.core.mediaControl.container.getStartTimeOffset()
    var hoverTime = startTimeOffset + (videoDuration * hoverPosition)

    // determine which thumbnail applies to the current time
    var thumbIndex = this._getThumbIndexForTime(hoverTime)
    var thumb = this._thumbs[thumbIndex]
    var $spotlight = this._$spotlight

    if (this._spotlightThumb !== thumb) {
      // update thumbnail
      $spotlight.empty()
      $spotlight.append(this._buildImg(thumb, this._getOptions().spotlightHeight))
      this._spotlightThumb = thumb;
    }

    var elWidth = this.$el.width()
    var thumbWidth = $spotlight.width()

    var spotlightXPos = (elWidth * hoverPosition) - (thumbWidth / 2)

    // adjust so the entire thumbnail is always visible
    spotlightXPos = Math.max(Math.min(spotlightXPos, elWidth - thumbWidth), 0)

    $spotlight.css("left", spotlightXPos)
  }

  // returns the thumbnail which represents a time in the video
  // or null if there is no thumbnail that can represent the time
  _getThumbIndexForTime(time) {
    var thumbs = this._thumbs
    for(let i=thumbs.length-1; i>=0; i--) {
      let thumb = thumbs[i]
      if (thumb.time <= time) {
        return i
      }
    }
    // stretch the first thumbnail back to the start
    return 0
  }

  _renderPlugin() {
    if (!this._thumbsLoaded) {
      return
    }
    if (this._show && this._thumbs.length > 0) {
      this.$el.removeClass("hidden")
      this._updateCarousel()
      this._updateSpotlightThumb()
    }
    else {
      this.$el.addClass("hidden")
    }
  }

  _createElements() {
    this.$el.html(this.template({'backdropHeight':this._getOptions().backdropHeight, 'spotlightHeight':this._getOptions().spotlightHeight}))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    // cache dom references
    this._$spotlight = this.$el.find(".spotlight")
    this._$backdrop = this.$el.find(".backdrop")
    this._$carousel = this._$backdrop.find(".carousel")
    this.$el.addClass("hidden")
    this._appendElToMediaControl()
  }
}
<|endoftext|>


<commit_before>(function (root, name, definer){

	var obj = definer()

	if (typeof define === 'function'){ // AMD
		define(obj)

	} else if (typeof module !== 'undefined' && module.exports) { // Node.js
		module.exports = obj

	} else { // Browser
		if (root[name]) throw new Error(name + ' is already globally defined')
		root[name] = obj
	}

})(this, 'acto', function () {

// --------------------

var slice = Array.prototype.slice

// -------------------- CREATE

// _ -> Signal A
function create () {
	return {
		listeners: 	[],
		active: 	true,
		value:      null
	}
}

// (A -> _) -> Signal A
function fromCallback (f) {
	var s = create()
	f(function (v) {
		send(s, v)
		stop(s)
	})
	return s
}

// Promise -> Signal A
function fromPromise (promise) {
	var s = create()
	promise
		.then(function (v) {
			send(s, v)
			stop(s)
		})
		.catch(function (error) {
			send(s, error instanceof Error ? error : new Error(error))
		})
	return s
}

// DomNode -> String -> Signal DomEvent
function fromDomEvent (node, eventName) {
	var s = create()
	s.dom = {
		node: node,
		eventName: eventName,
		listener: function (evt) { send(s, evt) }
	}
	if (node.addEventListener) {
		node.addEventListener(eventName, s.dom.listener, false)
	} else {
		node.attachEvent(eventName, s.dom.listener)
	}
	return s
}

// Int -> Signal Int
function fromInterval (interval) {
	var count = 0
	var s = create()
	s.interval = setInterval(function () {
		send(s, count)
		count++
	}, interval)
	return s
}

// _ -> Signal Number
function fromAnimationFrames () {
    const s = create()
    function step (time) {
        send(s, time)
        window.requestAnimationFrame(step)
    }
    window.requestAnimationFrame(step)
    return s
}

// -------------------- INTERACT

// Signal A -> (A -> _) -> Signal A
function listen (s, f) {
	if (s.active) s.listeners.push(f)
	return s
}

// Signal A -> (A -> _) -> Signal A
function unlisten (s, f) {
	s.listeners = s.listeners.filter(function (listener) { return listener !== f })
	return s
}

// Signal A -> A -> Signal A
function send (s, v) {
	if (s.active) {
		s.value = v
		s.listeners.forEach(function (f) { f(v) })
	}
	return s
}

// Signal A -> Signal A
function stop (s) {

	s.listeners = []
	s.active = false

	if (s.interval) {
		clearInterval(s.interval)
		delete s.interval
	}

	if (s.dom) {
		if (s.dom.node.removeEventListener) {
			s.dom.node.removeEventListener(s.dom.eventName, s.dom.listener, false)
		} else {
			s.dom.node.detachEvent(s.dom.eventName, s.dom.listener)
		}
	}

	return s
}

// -------------------- TRANSFORM

// (... _ -> B) -> ... Signal _ -> Signal B
function map () {
	var args = [].slice.call(arguments)
	var f    = args[0]
	var ss   = args.slice(1)
	var s2   = create()
	ss.forEach(function (s3) {
		listen(s3, function () {
			send(s2, f.apply(null, ss.map(function (s) { return s.value })))
		})
	})
	return s2
}

// (A -> Bool) -> Signal A -> Signal A
function filter (f, s) {
	var s2 = create()
	listen(s, function (v) {
		if (f(v)) send(s2, v)
	})
	return s2
}

// Signal A -> Signal A
function dropRepeats (s) {
	var s2 = create()
	if (s.value) send(s2, s.value)
	listen(s, function (v) {
		if (v !== s2.value) send(s2, v)
	})
	return s2
}

// (A -> B -> B) -> B -> Signal A -> Signal B
function fold (f, seed, s) {
	var s2 = create()
	listen(s, function (v) {
		send(s2, seed = f(v, seed))
	})
	return s2
}

// [Signal _] -> Signal _
function merge () {
	var ss = [].slice.call(arguments)
	var s2 = create()
	ss.forEach(function (s) {
		listen(s, function (v) {
			send(s2, v)
		})
	})
	return s2
}

// Signal A -> Signal B -> Signal A
function sampleOn (s, s2) {
	var s3 = create()
	if (s.value) send(s3, s.value)
	listen(s2, function () {
		send(s3, s.value)
	})
	return s3
}

// Int -> Signal A -> Signal [A]
function slidingWindow (length, s) {
	var s2 = create()
	var frame = []
	listen(s, function (v) {
		if (frame.length > length - 1) frame.shift()
		frame.push(v)
		send(s2, frame.slice())
	})
	return s2
}

// (A -> Signal B) -> Signal A -> Signal B
function flatMap (lift, s) {
	var s2 = create()
	listen(s, function (v1) {
		listen(lift(v1), function (v2) {
			send(s2, v2)
		})
	})
	return s2
}

// (A -> Signal B) -> Signal A -> Signal B
function flatMapLatest (lift, s) {
	var s2 = create()
	var s3
	listen(s, function (v1) {
		if (s3) stop(s3)
		s3 = lift(v1)
		listen(s3, function (v2) {
			send(s2, v2)
		})
	})
	return s2
}

// Signal A -> Int -> Signal A
function debounce (s, quiet) {
	return flatMapLatest(function (v) {
		return fromCallback(function (cback) {
			setTimeout(function () {
				cback(v)
			}, quiet)
		})
	}, s)
}

// API --------------------

return {
	create: create,
	fromPromise: fromPromise,
	fromCallback: fromCallback,
	fromAnimationFrames: fromAnimationFrames,
	fromDomEvent: fromDomEvent,
	fromInterval: fromInterval,
	listen: listen,
	unlisten: unlisten,
	send: send,
	stop: stop,
	map: map,
	filter: filter,
	fold: fold,
	merge: merge,
	dropRepeats: dropRepeats,
	sampleOn: sampleOn,
	flatMap: flatMap,
	flatMapLatest: flatMapLatest,
	debounce: debounce,
	slidingWindow: slidingWindow
}

})
<commit_msg>formatting, fromInteral gets 0 as initial value<commit_after>(function (root, name, definer){

    var obj = definer()

    if (typeof define === 'function'){ // AMD
        define(obj)

    } else if (typeof module !== 'undefined' && module.exports) { // Node.js
        module.exports = obj

    } else { // Browser
        if (root[name]) throw new Error(name + ' is already globally defined')
        root[name] = obj
    }

})(this, 'acto', function () {

// --------------------

var slice = Array.prototype.slice

// -------------------- CREATE

// _ -> Signal A
function create (initialValue) {
    return {
        listeners:  [],
        active:     true,
        value:      initialValue || null
    }
}

// (A -> _) -> Signal A
function fromCallback (f) {
    var s = create()
    f(function (v) {
        send(s, v)
        stop(s)
    })
    return s
}

// Promise -> Signal A
function fromPromise (promise) {
    var s = create()
    promise
        .then(function (v) {
            send(s, v)
            stop(s)
        })
        .catch(function (error) {
            send(s, error instanceof Error ? error : new Error(error))
        })
    return s
}

// DomNode -> String -> Signal DomEvent
function fromDomEvent (node, eventName) {
    var s = create()
    s.dom = {
        node: node,
        eventName: eventName,
        listener: function (evt) { send(s, evt) }
    }
    if (node.addEventListener) {
        node.addEventListener(eventName, s.dom.listener, false)
    } else {
        node.attachEvent(eventName, s.dom.listener)
    }
    return s
}

// Int -> Signal Int
function fromInterval (interval) {
    var count = 0
    var s = create(count)
    s.interval = setInterval(function () {
        count++
        send(s, count)
    }, interval)
    return s
}

// _ -> Signal Number
function fromAnimationFrames () {
    const s = create()
    function step (time) {
        send(s, time)
        window.requestAnimationFrame(step)
    }
    window.requestAnimationFrame(step)
    return s
}

// -------------------- INTERACT

// Signal A -> (A -> _) -> Signal A
function listen (s, f) {
    if (s.active) s.listeners.push(f)
    return s
}

// Signal A -> (A -> _) -> Signal A
function unlisten (s, f) {
    s.listeners = s.listeners.filter(function (listener) { return listener !== f })
    return s
}

// Signal A -> A -> Signal A
function send (s, v) {
    if (s.active) {
        s.value = v
        s.listeners.forEach(function (f) { f(v) })
    }
    return s
}

// Signal A -> Signal A
function stop (s) {

    s.listeners = []
    s.active = false

    if (s.interval) {
        clearInterval(s.interval)
        delete s.interval
    }

    if (s.dom) {
        if (s.dom.node.removeEventListener) {
            s.dom.node.removeEventListener(s.dom.eventName, s.dom.listener, false)
        } else {
            s.dom.node.detachEvent(s.dom.eventName, s.dom.listener)
        }
    }

    return s
}

// -------------------- TRANSFORM

// (... _ -> B) -> ... Signal _ -> Signal B
function map () {
    var args = [].slice.call(arguments)
    var f    = args[0]
    var ss   = args.slice(1)
    var s2   = create()
    ss.forEach(function (s3) {
        listen(s3, function () {
            send(s2, f.apply(null, ss.map(function (s) { return s.value })))
        })
    })
    return s2
}

// (A -> Bool) -> Signal A -> Signal A
function filter (f, s) {
    var s2 = create()
    listen(s, function (v) {
        if (f(v)) send(s2, v)
    })
    return s2
}

// Signal A -> Signal A
function dropRepeats (s) {
    var s2 = create()
    if (s.value) send(s2, s.value)
    listen(s, function (v) {
        if (v !== s2.value) send(s2, v)
    })
    return s2
}

// (A -> B -> B) -> B -> Signal A -> Signal B
function fold (f, seed, s) {
    var s2 = create(seed)
    listen(s, function (v) {
        send(s2, seed = f(v, seed))
    })
    return s2
}

// [Signal _] -> Signal _
function merge () {
    var ss = [].slice.call(arguments)
    var s2 = create()
    ss.forEach(function (s) {
        listen(s, function (v) {
            send(s2, v)
        })
    })
    return s2
}

// Signal A -> Signal B -> Signal A
function sampleOn (s, s2) {
    var s3 = create()
    if (s.value) send(s3, s.value)
    listen(s2, function () {
        send(s3, s.value)
    })
    return s3
}

// Int -> Signal A -> Signal [A]
function slidingWindow (length, s) {
    var s2 = create()
    var frame = []
    listen(s, function (v) {
        if (frame.length > length - 1) frame.shift()
        frame.push(v)
        send(s2, frame.slice())
    })
    return s2
}

// (A -> Signal B) -> Signal A -> Signal B
function flatMap (lift, s) {
    var s2 = create()
    listen(s, function (v1) {
        listen(lift(v1), function (v2) {
            send(s2, v2)
        })
    })
    return s2
}

// (A -> Signal B) -> Signal A -> Signal B
function flatMapLatest (lift, s) {
    var s2 = create()
    var s3
    listen(s, function (v1) {
        if (s3) stop(s3)
        s3 = lift(v1)
        listen(s3, function (v2) {
            send(s2, v2)
        })
    })
    return s2
}

// Signal A -> Int -> Signal A
function debounce (s, quiet) {
    return flatMapLatest(function (v) {
        return fromCallback(function (cback) {
            setTimeout(function () {
                cback(v)
            }, quiet)
        })
    }, s)
}

// API --------------------

return {
    create: create,
    fromPromise: fromPromise,
    fromCallback: fromCallback,
    fromAnimationFrames: fromAnimationFrames,
    fromDomEvent: fromDomEvent,
    fromInterval: fromInterval,
    listen: listen,
    unlisten: unlisten,
    send: send,
    stop: stop,
    map: map,
    filter: filter,
    fold: fold,
    merge: merge,
    dropRepeats: dropRepeats,
    sampleOn: sampleOn,
    flatMap: flatMap,
    flatMapLatest: flatMapLatest,
    debounce: debounce,
    slidingWindow: slidingWindow
}

})
<|endoftext|>


<commit_before>(function(window) {
  'use strict';

  function toTime(sec) {
    sec = parseInt(sec, 10);

    var hours = Math.floor(sec / 3600);
    var minutes = Math.floor((sec - (hours * 3600)) / 60);
    var seconds = sec - (hours * 3600) - (minutes * 60);

    if (hours < 10) { hours   = '0' + hours; }
    if (minutes < 10) { minutes = '0' + minutes; }
    if (seconds < 10) { seconds = '0' + seconds; }

    return hours + ':' + minutes;
  }

  function readBattery(battery) {
    console.log(battery);
    console.log(toTime(battery.chargingTime));
    console.log(toTime(battery.dischargingTime));

    var percentage = parseFloat((battery.level * 100).toFixed(2)) + '%';
    document.styleSheets[0].insertRule('.battery:before{width:' + percentage + '}', 0);
  }

  if ('battery' in navigator) {
    readBattery(navigator.battery);
  } else {
    navigator.getBattery().then(readBattery);
  }

}(this));<commit_msg>Add battery API detection. Lint code.<commit_after>(function() {
  'use strict';

  function toTime(sec) {
    sec = parseInt(sec, 10);

    var hours = Math.floor(sec / 3600),
        minutes = Math.floor((sec - (hours * 3600)) / 60),
        seconds = sec - (hours * 3600) - (minutes * 60);

    if (hours < 10) { hours   = '0' + hours; }
    if (minutes < 10) { minutes = '0' + minutes; }
    if (seconds < 10) { seconds = '0' + seconds; }

    return hours + ':' + minutes;
  }

  function readBattery(battery) {
    console.log(battery);
    console.log(toTime(battery.chargingTime));
    console.log(toTime(battery.dischargingTime));

    var percentage = parseFloat((battery.level * 100).toFixed(2)) + '%';
    document.styleSheets[0].insertRule('.battery:before{width:' + percentage + '}', 0);
  }

  if (navigator.battery) {
    readBattery(navigator.battery);

  } else if (navigator.getBattery) {
    navigator.getBattery().then(readBattery);

  } else {
    console.log('Your browser don\'t support Battery API.');
  }

}());<|endoftext|>


<commit_before>import RemoteBrowserTarget from './RemoteBrowserTarget';
import uploadReport from './uploadReport';

export {
  RemoteBrowserTarget,
  uploadReport,
};
<commit_msg>Add fetchReport to exported functions<commit_after>import RemoteBrowserTarget from './RemoteBrowserTarget';
import fetchReport from './fetchReport';
import uploadReport from './uploadReport';

export {
  RemoteBrowserTarget,
  uploadReport,
  fetchReport,
};
<|endoftext|>


<commit_before>import axios from 'axios'
import { camel, deserialise, error, kebab, query, serialise, snake } from './util'

/**
 * @name Kitsu
 * @param {Object} options Options
 * @param {string} options.baseURL Set the API endpoint (default `https://kitsu.io/api/edge`)
 * @param {Object} options.headers Additional headers to send with requests
 * @param {boolean} options.camelCaseTypes If true, the `type` value will be camelCased, e.g `library-entries` and `library_entries` become `libraryEntries`  (default `true`)
 * @param {string} options.resourceCase `kebab`, `snake` or `none`. If `kebab`, `/libraryEntries` will become `/library-entries`. If `snake`, `/libraryEntries` will become `/library_entries`, If `none`, `/libraryEntries` will be unchanged (default `kebab`)
 * @param {boolean} options.pluralize If `true`, `/user` will become `/users` in the URL request and `type` will be pluralized in post, patch and delete requests - `user` -> `users` (default `true`)
 * @param {number} options.timeout Set the request timeout in milliseconds (default `30000`)
 *
 * @example
 * // If using Kitsu.io's API
 * const api = new Kitsu()
 *
 * @example
 * // If using another API server
 * const api = new Kitsu({
 *   baseURL: 'https://api.example.org/2'
 * })
 *
 * @example
 * // Set a `user-agent` and an `authorization` token
 * const api = new Kitsu({
 *   headers: {
 *     'User-Agent': 'MyApp/1.0.0 (github.com/username/repo)',
 *     Authorization: 'Bearer 1234567890'
 *   }
 * })
 */
export default class Kitsu {
  constructor (options = {}) {
    if (options.camelCaseTypes === false) this.camel = s => s
    else this.camel = camel

    if (options.resourceCase === 'none') this.resCase = s => s
    else if (options.resourceCase === 'snake') this.resCase = snake
    else this.resCase = kebab

    if (options.pluralize === false) this.plural = s => s
    else this.plural = require('pluralize')

    /**
     * Get the current headers or add additional headers
     *
     * @memberof Kitsu
     * @returns {Object} All the current headers
     *
     * @example
     * // Receive all the headers
     * api.headers
     *
     * @example
     * // Receive a specific header
     * api.headers['User-Agent']
     *
     * @example
     * // Add or update a header
     * api.headers['Authorization'] = 'Bearer 1234567890'
     */
    this.headers = Object.assign({}, options.headers, { 'Accept': 'application/vnd.api+json', 'Content-Type': 'application/vnd.api+json' })

    this.axios = axios.create({
      baseURL: options.baseURL || 'https://kitsu.io/api/edge',
      timeout: options.timeout || 30000
    })
  }

  /**
   * Check if the client is authenticated (oAuth2/Authorization header)
   *
   * @memberof Kitsu
   * @returns {boolean}
   *
   * @example
   * if (api.isAuth) console.log('Authenticated')
   * else console.log('Not authenticated')
   */
  get isAuth () {
    return Boolean(this.headers.Authorization)
  }

  /**
   * Fetch resources
   * Aliases: `fetch`
   *
   * @memberof Kitsu
   * @param {string} model Model to fetch data from
   * @param {Object} params JSON-API request queries
   * @param {Object} params.page [JSON:API Pagination](http://jsonapi.org/format/#fetching-pagination)
   * @param {number} params.page.limit Number of resources to return in request (Max `20` for Kitsu.io except on `libraryEntries` which has a max of `500`)
   * @param {number} params.page.offset Number of resources to offset the dataset by
   * @param {Object} params.fields Return a sparse fieldset with only the included attributes/relationships - [JSON:API Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets)
   * @param {Object} params.filter Filter dataset by attribute values - [JSON:API Filtering](http://jsonapi.org/format/#fetching-filtering)
   * @param {string} params.sort Sort dataset by one or more comma separated attributes (prepend `-` for descending order) - [JSON:API Sorting](http://jsonapi.org/format/#fetching-sorting)
   * @param {string} params.include Include relationship data - [JSON:API Includes](http://jsonapi.org/format/#fetching-includes)
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   *
   * @example
   * // Get a specific user's name & birthday
   * api.get('users', {
   *   fields: {
   *     users: 'name,birthday'
   *   },
   *   filter: {
   *     name: 'wopian'
   *   }
   * })
   *
   * @example
   * // Get a collection of anime resources and their categories
   * api.get('anime', {
   *   include: 'categories'
   * })
   *
   * @example
   * // Get a single resource and its relationships by ID (method one)
   * api.get('anime', {
   *   include: 'categories',
   *   filter: { id: '2' }
   * })
   *
   * @example
   * // Get a single resource and its relationships by ID (method two)
   * api.get('anime/2', {
   *   include: 'categories'
   * })
   *
   * @example
   * // Get a resource's relationship data only
   * api.get('anime/2/categories')
   *
   * @example
   * // Handling errors (async/await)
   * // http://jsonapi.org/format/#error-objects
   * try {
   *   const { data } = api.get('anime')
   * } catch (err) {
   *   if (err.errors) err.errors.forEach(error => {
   *       console.log(error) // Prints JSON:API error object
   *   })
   * }
   *
   * @example
   * // Handling errors (Promise)
   * // http://jsonapi.org/format/#error-objects
   * api.get('anime')
   *   .then(res => res.data)
   *   .catch(err => {
   *     if (err.errors) err.errors.forEach(error => {
   *       console.log(error) // Prints JSON:API error object
   *     })
   *   })
   */
  async get (model, params = {}, headers = {}) {
    try {
      let [ res, id, relationship ] = model.split('/')

      let url = this.plural(this.resCase(res))
      if (id) url += `/${id}`
      if (relationship) url += `/${this.resCase(relationship)}`

      /* istanbul ignore next */
      const { data } = await this.axios.get(url, {
        params,
        paramsSerializer: p => query(p),
        headers: Object.assign(this.headers, headers)
      })

      return deserialise(data)
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Update a resource
   * Aliases: `update`
   *
   * @memberof Kitsu
   * @param {string} model Model to update data in
   * @param {Object} body Data to send in the request
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   *
   * @example
   * // Update a user's post (Note: For Kitsu.io, posts cannot be edited 30 minutes after creation)
   * api.update('posts', {
   *   id: '12345678',
   *   content: 'Goodbye World'
   * })
   */
  async patch (model, body, headers = {}) {
    try {
      const serialData = await serialise.apply(this, [ model, body, 'PATCH' ])
      const url = this.plural(this.resCase(model)) + '/' + body.id
      const { data } = await this.axios.patch(
        url,
        serialData,
        { headers: Object.assign(this.headers, headers) }
      )

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Create a new resource
   * Aliases: `create`
   *
   * @memberof Kitsu
   * @param {string} model Model to create a resource under
   * @param {Object} body Data to send in the request
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   *
   * @example
   * // Post to a user's own profile
   * api.create('posts', {
   *   content: 'Hello World',
   *   targetUser: {
   *     id: '42603',
   *     type: 'users'
   *   },
   *   user: {
   *     id: '42603',
   *     type: 'users'
   *   }
   * })
   */
  async post (model, body, headers = {}) {
    try {
      const url = this.plural(this.resCase(model))
      const { data } = await this.axios.post(
        url,
        await serialise.apply(this, [ model, body ]),
        { headers: Object.assign(this.headers, headers) }
      )

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Remove a resource
   *
   * @memberof Kitsu
   * @param {string} model Model to remove data from
   * @param {string|number} id Resource ID to remove
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   *
   * @example
   * // Delete a user's post
   * api.remove('posts', 123)
   */
  async remove (model, id, headers = {}) {
    try {
      const url = this.plural(this.resCase(model)) + '/' + id
      const { data } = await this.axios.delete(url, {
        data: await serialise.apply(this, [ model, { id }, 'DELETE' ]),
        headers: Object.assign(this.headers, headers)
      })

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Get the authenticated user's data
   * Note: Requires the JSON:API server to support `filter[self]=true`
   *
   * @memberof Kitsu
   * @param {Object} params JSON-API request queries
   * @param {Object} params.fields Return a sparse fieldset with only the included attributes/relationships - [JSON:API Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets)
   * @param {string} params.include Include relationship data - [JSON:API Includes](http://jsonapi.org/format/#fetching-includes)
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   *
   * @example
   * // Receive all attributes
   * api.self()
   *
   * @example
   * // Receive a sparse fieldset
   * api.self({
   *   fields: 'name,birthday'
   * })
   */
  async self (params = {}, headers = {}) {
    try {
      const res = await this.get('users', Object.assign({ filter: { self: true } }, params), headers)
      return res.data[0]
    } catch (E) {
      throw error(E)
    }
  }

  fetch = this.get
  update = this.patch
  create = this.post
}
<commit_msg>docs(isAuth): add deprecation notice<commit_after>import axios from 'axios'
import { camel, deserialise, error, kebab, query, serialise, snake } from './util'

/**
 * Creates a new `kitsu` instance
 *
 * @param {Object} options Options
 * @param {string} options.baseURL Set the API endpoint (default `https://kitsu.io/api/edge`)
 * @param {Object} options.headers Additional headers to send with requests
 * @param {boolean} options.camelCaseTypes If true, the `type` value will be camelCased, e.g `library-entries` and `library_entries` become `libraryEntries`  (default `true`)
 * @param {string} options.resourceCase `kebab`, `snake` or `none`. If `kebab`, `/libraryEntries` will become `/library-entries`. If `snake`, `/libraryEntries` will become `/library_entries`, If `none`, `/libraryEntries` will be unchanged (default `kebab`)
 * @param {boolean} options.pluralize If `true`, `/user` will become `/users` in the URL request and `type` will be pluralized in post, patch and delete requests - `user` -> `users` (default `true`)
 * @param {number} options.timeout Set the request timeout in milliseconds (default `30000`)
 * @example
Using with Kitsu.io's API

 * const api = new Kitsu()
 * @example
Using another API server

 * const api = new Kitsu({
 *   baseURL: 'https://api.example.org/2'
 * })
 * @example
Setting headers

 * const api = new Kitsu({
 *   headers: {
 *     'User-Agent': 'MyApp/1.0.0 (github.com/username/repo)',
 *     Authorization: 'Bearer 1234567890'
 *   }
 * })
 */
export default class Kitsu {
  constructor (options = {}) {
    if (options.camelCaseTypes === false) this.camel = s => s
    else this.camel = camel

    if (options.resourceCase === 'none') this.resCase = s => s
    else if (options.resourceCase === 'snake') this.resCase = snake
    else this.resCase = kebab

    /**
     * If pluralization is enabled (default, see Kitsu constructor docs) then pluralization rules can be added
     *
     * @memberof Kitsu
     * @external plural
     * @see {@link https://www.npmjs.com/package/pluralize} for documentation
     * @see Kitsu constructor options for disabling pluralization
     * @example
Adding an uncountable pluralization rule

     * api.plural.plural('paper') //=> 'papers'
     * api.plural.addUncountableRule('paper')
     * api.plural.plural('paper') //=> 'paper'
     *
     */
    if (options.pluralize === false) this.plural = s => s
    else this.plural = require('pluralize')

    /**
     * Get the current headers or add additional headers
     *
     * @memberof Kitsu
     * @returns {Object} All the current headers
     * @example
Get all headers

     * api.headers
     * @example
Get a single header's value

     * api.headers['User-Agent']
     * @example
Add or update a header's value

     * api.headers['Authorization'] = 'Bearer 1234567890'
     */
    this.headers = Object.assign({}, options.headers, { 'Accept': 'application/vnd.api+json', 'Content-Type': 'application/vnd.api+json' })

    this.axios = axios.create({
      baseURL: options.baseURL || 'https://kitsu.io/api/edge',
      timeout: options.timeout || 30000
    })
  }

  /**
   * Check if the client is authenticated (oAuth2/Authorization header)
   *
   * @memberof Kitsu
   * @returns {boolean}
   * @deprecated since 4.5.0 - will be removed in 5.0.0 as it doesn't guarantee client is genuinely authenticated with the API
   * @example
   * if (api.isAuth) console.log('Authenticated')
   * else console.log('Not authenticated')
   */
  get isAuth () {
    return Boolean(this.headers.Authorization)
  }

  /**
   * Fetch resources (alias `fetch`)
   *
   * @memberof Kitsu
   * @param {string} model Model to fetch data from
   * @param {Object} params JSON-API request queries
   * @param {Object} params.page [JSON:API Pagination](http://jsonapi.org/format/#fetching-pagination)
   * @param {number} params.page.limit Number of resources to return in request (Max `20` for Kitsu.io except on `libraryEntries` which has a max of `500`)
   * @param {number} params.page.offset Number of resources to offset the dataset by
   * @param {Object} params.fields Return a sparse fieldset with only the included attributes/relationships - [JSON:API Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets)
   * @param {Object} params.filter Filter dataset by attribute values - [JSON:API Filtering](http://jsonapi.org/format/#fetching-filtering)
   * @param {string} params.sort Sort dataset by one or more comma separated attributes (prepend `-` for descending order) - [JSON:API Sorting](http://jsonapi.org/format/#fetching-sorting)
   * @param {string} params.include Include relationship data - [JSON:API Includes](http://jsonapi.org/format/#fetching-includes)
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   * @example
Getting a resource with JSON:API parameters

   * api.get('users', {
   *   fields: {
   *     users: 'name,birthday'
   *   },
   *   filter: {
   *     name: 'wopian'
   *   }
   * })
   * @example
Getting a collection of resources with their relationships

   * api.get('anime', {
   *   include: 'categories'
   * })
   * @example
Getting a single resource by ID (method one)

   * api.get('anime/2', {
   *   include: 'categories'
   * })
   * @example
Getting a single resource by ID (method two)

   * api.get('anime', {
   *   include: 'categories',
   *   filter: { id: '2' }
   * })
   * @example
Getting a resource's relationship data only

   * api.get('anime/2/categories')
   * @example
Handling errors (async/await)

   * try {
   *   const { data } = await api.get('anime')
   * } catch (err) {
   *   // Array of JSON:API errors: http://jsonapi.org/format/#error-objects
   *   if (err.errors) err.errors.forEach(error => { ... })
   *   // Error type (Error, TypeError etc.)
   *   err.name
   *   // Error message
   *   err.message
   *   // Axios request parameters
   *   err.config
   *   // Axios response parameters
   *   err.response
   * }
   * @example
Handling errors (Promises)

   * api.get('anime')
   *   .then(({ data }) => { ... })
   *   .catch(err => {
   *     // Array of JSON:API errors: http://jsonapi.org/format/#error-objects
   *     if (err.errors) err.errors.forEach(error => { ... })
   *     // Error type (Error, TypeError etc.)
   *     err.name
   *     // Error message
   *     err.message
   *     // Axios request parameters
   *     err.config
   *     // Axios response parameters
   *     err.response
   *   })
   */
  async get (model, params = {}, headers = {}) {
    try {
      let [ res, id, relationship ] = model.split('/')

      let url = this.plural(this.resCase(res))
      if (id) url += `/${id}`
      if (relationship) url += `/${this.resCase(relationship)}`

      /* istanbul ignore next */
      const { data } = await this.axios.get(url, {
        params,
        paramsSerializer: p => query(p),
        headers: Object.assign(this.headers, headers)
      })

      return deserialise(data)
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Update a resource (alias `update`)
   *
   * @memberof Kitsu
   * @param {string} model Model to update data in
   * @param {Object} body Data to send in the request
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   * @example
Update a post

   * api.update('posts', {
   *   id: '12345678',
   *   content: 'Goodbye World'
   * })
   */
  async patch (model, body, headers = {}) {
    try {
      const serialData = await serialise.apply(this, [ model, body, 'PATCH' ])
      const url = this.plural(this.resCase(model)) + '/' + body.id
      const { data } = await this.axios.patch(
        url,
        serialData,
        { headers: Object.assign(this.headers, headers) }
      )

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Create a new resource (alias `create`)
   *
   * @memberof Kitsu
   * @param {string} model Model to create a resource under
   * @param {Object} body Data to send in the request
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   * @example
Create a post on a user's profile feed

   * api.create('posts', {
   *   content: 'Hello World',
   *   targetUser: {
   *     id: '42603',
   *     type: 'users'
   *   },
   *   user: {
   *     id: '42603',
   *     type: 'users'
   *   }
   * })
   */
  async post (model, body, headers = {}) {
    try {
      const url = this.plural(this.resCase(model))
      const { data } = await this.axios.post(
        url,
        await serialise.apply(this, [ model, body ]),
        { headers: Object.assign(this.headers, headers) }
      )

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Remove a resource
   *
   * @memberof Kitsu
   * @param {string} model Model to remove data from
   * @param {string|number} id Resource ID to remove
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   * @example
Remove a user's post

   * api.remove('posts', 123)
   */
  async remove (model, id, headers = {}) {
    try {
      const url = this.plural(this.resCase(model)) + '/' + id
      const { data } = await this.axios.delete(url, {
        data: await serialise.apply(this, [ model, { id }, 'DELETE' ]),
        headers: Object.assign(this.headers, headers)
      })

      return data
    } catch (E) {
      throw error(E)
    }
  }

  /**
   * Get the authenticated user's data
   *
   * **Note** Requires the JSON:API server to support `filter[self]=true`
   *
   * @memberof Kitsu
   * @param {Object} params JSON-API request queries
   * @param {Object} params.fields Return a sparse fieldset with only the included attributes/relationships - [JSON:API Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets)
   * @param {string} params.include Include relationship data - [JSON:API Includes](http://jsonapi.org/format/#fetching-includes)
   * @param {Object} headers Additional headers to send with request
   * @returns {Object} JSON-parsed response
   * @example
Get the authenticated user's resource

   * api.self()
   * @example
Using JSON:API parameters

   * api.self({
   *   fields: 'name,birthday'
   * })
   */
  async self (params = {}, headers = {}) {
    try {
      const res = await this.get('users', Object.assign({ filter: { self: true } }, params), headers)
      return res.data[0]
    } catch (E) {
      throw error(E)
    }
  }

  fetch = this.get
  update = this.patch
  create = this.post
}
<|endoftext|>


<commit_before>'use strict'

var APP_ID = 'amzn1.ask.skill.36b93194-b1d4-4479-91c0-37e909e2ecca';
var Alexa = require('./AlexaSkill');
var OUTPUT = "Welcome to symptom checker. Please list your symptoms.";

var ResponseService = function(){
	Alexa.call(this, APP_ID);
};

ResponseService.prototype = Object.create(Alexa.prototype);

var responseFunction = function(intent, session, response){
	response.tell(OUTPUT);
}

var diagnosisFunction = function(intent,session,response){
	response.tell('You will probably die');
}

ResponseService.prototype.eventHandlers.onLaunch = responseFunction;

ResponseService.prototype.intentHandlers = {

	'GetDiagnosisIntent' : diagnosisFunction;
};

exports.handler = function(event, context){
	var responseService = new ResponseService();
	responseService.execute(event,context);
}<commit_msg>testing<commit_after>'use strict'

var APP_ID = 'amzn1.ask.skill.7aae67c9-5555-4e27-954f-ba88b2abfef3';
var Alexa = require('./AlexaSkill');
var OUTPUT = "Welcome to symptom checker. Please list your symptoms.";

var ResponseService = function(){
	Alexa.call(this, APP_ID);
};

ResponseService.prototype = Object.create(Alexa.prototype);

var responseFunction = function(intent, session, response){
	response.tell(OUTPUT);
}

var diagnosisFunction = function(intent,session,response){
	response.tell('You will probably die');
}

ResponseService.prototype.eventHandlers.onLaunch = responseFunction;

ResponseService.prototype.intentHandlers = {

	'GetDiagnosisIntent' : diagnosisFunction;
};

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


<commit_before>/**
 * @author Alex Mourtziapis
 * @copyright 2015 Shunless Studio.
 */

//  ++++   Supported Game Types  ++++  ///////////////////////////////////////
//  1-$ Normal      #6 w/h beatlock
//  2-$ Endless     #7 w/h beatlock
//  3-$ SkipSmash   #8 w/h beatlock
//  4-$ PaintStack  #9 w/h beatlock
//////////////////////////////////////////////////////////////////////////////

// Active game type (string)
var GameType = 'Normal';

//beat refresh rate (number)
var beatRate = 1000;
//beat interval in %[0-Min, 100 Max] (string)
var beatInterval = 0;

var EnemyMoveTimeout = 0;

//editor/runtime window scale in pixels (number)
var Editor_Width = cellSize * 7;
var Editor_Height = cellSize * 7;
//the aspect ratio of the screen (decimal)
var aspect_ratio = Editor_Width / Editor_Height;
//world bounds (number)
var World_bounds_x = Editor_Width;
var World_bounds_y = Editor_Height;
//amount of cells in grid (number)
var cellsCntX = 7;
var cellsCntY = 7;
//cell scale in pixels (number)
var cellWidth;
var cellHeight;
//the color of the enemies (string)
var enemiesColor = '#b50000';
//indicates how many times game has been restared. (number)
var gameStateRestarts = 0;

//-----$*********$-----
//-----$ CLASSES $-----
//-----$*********$-----

var color;
var grid;
var actor;
var enemy;

/*~~~~~$*********$~~~~~*/
/*~~~~~$ CLASSES $~~~~~*/
/*~~~~~$*********$~~~~~*/

//indicates what s the next move gonna be. (sring)
var enemyMove;
var movesHaveBeenStored;
//indicates the time that the game actually started.(number)*ms
var LoadTime;
//indicates the elapsed time since level load.(number)*s
var timeSinceLevelLoad;
//$ phaser game instance (object)
var game;
//indicates if user lost in the previous session(boolean)
var justLost = true;
//indicates if the grid has just being expanded(boolean)
var justExpandedGrid = false;

var cursors;

//$ preload function $
function preload() {
  //Reset Arrays
  enemy = new Array();
  enemyMove = new Array();

  movesHaveBeenStored = false;
  cellWidth = (Editor_Width - (border * 2 * cellsCntX + border * 2)) / (cellsCntX);
  cellHeight = (Editor_Width - (border * 2 * cellsCntY + border * 2)) / (cellsCntY);
}
//$ create function $
function create() {
  //For *not* mobile devices
  if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    //  And some controls to play the game with keyboard
    cursors = game.input.keyboard.createCursorKeys();
    cursors.left.onDown.add(function() {
      actor.move('left');
    }, this);

    cursors.right.onDown.add(function() {
      actor.move('right');
    }, this);

    cursors.down.onDown.add(function() {
      actor.move('bottom');
    }, this);

    cursors.up.onDown.add(function() {
      actor.move('top');
    }, this);
  }
  // Set up handlers for mouse events
  game.input.onDown.add(mouseDragStart, this);
  game.input.onUp.add(mouseDragEnd, this);

  //add profiler
  //- https://github.com/englercj/phaser-debug -
  //game.add.plugin(Phaser.Plugin.Debug);

  color = new Color();

  //+++++++++++++++++++++++++++++
  //  Grid initialization
  //+++++++++++++++++++++++++++++

  grid = new Grid(cellsCntX, cellsCntY, cellWidth, cellHeight, '#333', '#ffffff', border * 2);
  grid.init();

  //+++++++++++++++++++++++++++++
  //  Actor initialization
  //+++++++++++++++++++++++++++++

  if (justLost === true)
    actor = new Actor('#006400');
  else {
    var _O_ = actor._c;
    actor = new Actor('#006400', grid.cell[_O_].HashId);
  }
  actor.init();

  //+++++++++++++++++++++++++++++
  //  Enemies initialization
  //+++++++++++++++++++++++++++++

  for (var i = 0; i < 3 + gameStateRestarts; i++) {
    var x = '';
    if (randomBoolean[0]() == true) {
      if (randomBoolean[0]() == true)
        x = '0-' + getRandomInt(0, cellsCntY);
      else
        x = getRandomInt(0, cellsCntX) + '-0';
    } else {
      if (randomBoolean[0]() == true)
        x = (cellsCntX - 1) + '-' + getRandomInt(0, cellsCntY);
      else
        x = getRandomInt(0, cellsCntX) + '-' + (cellsCntY - 1);
    }

    if (grid.cell[grid.getCell(x)].type === 'Normal')
      enemy.push(new Enemy(enemiesColor, x));
    else
      i--;
  }

  for (var x = 0; x < enemy.length; x++)
    enemy[x].init();

  EnemyMoveTimeout = game.time.time + beatRate;
  justLost = justExpandedGrid = false;
  LoadTime = game.time.now;
}

// GAME LOOP
function update() {
  if (game.time.time > EnemyMoveTimeout) {

    for (var i = 0; i < enemy.length; i++)
      enemy[i].move(enemyMove[i]);

    //this function triggers the sfx player
    //blips_sfx.play();

    movesHaveBeenStored = false;

    //refresh move timeout
    EnemyMoveTimeout = game.time.time + beatRate;

  } else if (movesHaveBeenStored === false) {

    //We reset the predicted moves array
    enemyMove = new Array();

    for (var x = 0; x < enemy.length; x++)
      enemyMove.push(enemy[x].Nextmove());

    movesHaveBeenStored = true;
  }

  if (GameType === 'Normal' || GameType === 'Endless') {
    //if user has killed all the enemies.
    if (enemy.length === 0)
      expand();
  } else if (GameType === 'SkipSmash') {
    if (enemy.length === 1)
      expand();
  } else if (GameType === 'PaintStack') {
    //@ToDo *Implement PaintStack game type
  }

  //calculate new inteval
  beatInterval = Math.round(((EnemyMoveTimeout - game.time.time) / beatRate) * 100);

  timeSinceLevelLoad = Math.round((game.time.now - LoadTime) / 1000);
}

//RENDER LOOP
function render() {
  //draws cells and grid $ 1st Draw Call $
  grid.render();
  //debug text draw calls
  game.debug.text('grid: ' + cellsCntX + '-' + cellsCntY, 3, 14, '#b1ff00');
  game.debug.text('enemies: ' + enemy.length, 3, 27, '#b1ff00');
  game.debug.text('beat rate: ' + beatRate + ' ms', 3, 40, '#b1ff00');
  game.debug.text('Interval: ' + beatInterval + ' %', 3, 53, beatInterval < 20 ? '#ff0000' : '#00ff27');
  game.debug.text('time: ' + timeSinceLevelLoad + ' s', 3, 66, '#b1ff00');
  if(GameType === 'PaintStack')
      game.debug.text('marked area: ' + Math.floor(actor.markedArea) + '%' , 3, 79, '#b1ff00');
}
//$ game over $
//every game type has the same game over :)
function gameOver() {
  justLost = true;

  //reset grid back to 7x7
  cellsCntY = cellsCntX = 7;
  gameStateRestarts = 0;
  game.state.start(game.state.current);

  LoadTime = game.time.now;
}
//$ expand " grid " $
function expand() {
  justExpandedGrid = true;

  //NORMAL AND SKIPSMASH GAME TYPES
  if (GameType === 'Normal' || GameType === 'SkipSmash') {
    justLost = true;
    gameStateRestarts++;

    //increment cells by 1 if enemies > ceil(cells/2)
    if ((3 + gameStateRestarts) > Math.ceil(cellsCntX / 2))
      cellsCntY = ++cellsCntX;

    game.state.start(game.state.current);
  }
  //ENDLESS GAME TYPE
  else if (GameType === 'Endless') {
    gameStateRestarts++;

    //increment cells by 1 if enemies > ceil(cells/2)
    if ((3 + gameStateRestarts) > Math.ceil(cellsCntX / 2))
      cellsCntY = ++cellsCntX;

    game.state.start(game.state.current);
  }
  //PAINTSTACK GAME TYPE
  else if (GameType === 'PaintStack') {
    //@ToDo
  }
}
<commit_msg>Implemented PaintStack game type gameover logic.<commit_after>/**
 * @author Alex Mourtziapis
 * @copyright 2015 Shunless Studio.
 */

//  ++++   Supported Game Types  ++++  ///////////////////////////////////////
//  1-$ Normal      #6 w/h beatlock
//  2-$ Endless     #7 w/h beatlock
//  3-$ SkipSmash   #8 w/h beatlock
//  4-$ PaintStack  #9 w/h beatlock
//////////////////////////////////////////////////////////////////////////////

// Active game type (string)
var GameType = 'Normal';

//beat refresh rate (number)
var beatRate = 1000;
//beat interval in %[0-Min, 100 Max] (string)
var beatInterval = 0;

var EnemyMoveTimeout = 0;

//editor/runtime window scale in pixels (number)
var Editor_Width = cellSize * 7;
var Editor_Height = cellSize * 7;
//the aspect ratio of the screen (decimal)
var aspect_ratio = Editor_Width / Editor_Height;
//world bounds (number)
var World_bounds_x = Editor_Width;
var World_bounds_y = Editor_Height;
//amount of cells in grid (number)
var cellsCntX = 7;
var cellsCntY = 7;
//cell scale in pixels (number)
var cellWidth;
var cellHeight;
//the color of the enemies (string)
var enemiesColor = '#b50000';
//indicates how many times game has been restared. (number)
var gameStateRestarts = 0;

//-----$*********$-----
//-----$ CLASSES $-----
//-----$*********$-----

var color;
var grid;
var actor;
var enemy;

/*~~~~~$*********$~~~~~*/
/*~~~~~$ CLASSES $~~~~~*/
/*~~~~~$*********$~~~~~*/

//indicates what s the next move gonna be. (sring)
var enemyMove;
var movesHaveBeenStored;
//indicates the time that the game actually started.(number)*ms
var LoadTime;
//indicates the elapsed time since level load.(number)*s
var timeSinceLevelLoad;
//$ phaser game instance (object)
var game;
//indicates if user lost in the previous session(boolean)
var justLost = true;
//indicates if the grid has just being expanded(boolean)
var justExpandedGrid = false;

var cursors;

//$ preload function $
function preload() {
  //Reset Arrays
  enemy = new Array();
  enemyMove = new Array();

  movesHaveBeenStored = false;
  cellWidth = (Editor_Width - (border * 2 * cellsCntX + border * 2)) / (cellsCntX);
  cellHeight = (Editor_Width - (border * 2 * cellsCntY + border * 2)) / (cellsCntY);
}
//$ create function $
function create() {
  //For *not* mobile devices
  if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    //  And some controls to play the game with keyboard
    cursors = game.input.keyboard.createCursorKeys();
    cursors.left.onDown.add(function() {
      actor.move('left');
    }, this);

    cursors.right.onDown.add(function() {
      actor.move('right');
    }, this);

    cursors.down.onDown.add(function() {
      actor.move('bottom');
    }, this);

    cursors.up.onDown.add(function() {
      actor.move('top');
    }, this);
  }
  // Set up handlers for mouse events
  game.input.onDown.add(mouseDragStart, this);
  game.input.onUp.add(mouseDragEnd, this);

  //add profiler
  //- https://github.com/englercj/phaser-debug -
  //game.add.plugin(Phaser.Plugin.Debug);

  color = new Color();

  //+++++++++++++++++++++++++++++
  //  Grid initialization
  //+++++++++++++++++++++++++++++

  grid = new Grid(cellsCntX, cellsCntY, cellWidth, cellHeight, '#333', '#ffffff', border * 2);
  grid.init();

  //+++++++++++++++++++++++++++++
  //  Actor initialization
  //+++++++++++++++++++++++++++++

  if (justLost === true)
    actor = new Actor('#006400');
  else {
    var _O_ = actor._c;
    actor = new Actor('#006400', grid.cell[_O_].HashId);
  }
  actor.init();

  //+++++++++++++++++++++++++++++
  //  Enemies initialization
  //+++++++++++++++++++++++++++++

  for (var i = 0; i < 3 + gameStateRestarts; i++) {
    var x = '';
    if (randomBoolean[0]() == true) {
      if (randomBoolean[0]() == true)
        x = '0-' + getRandomInt(0, cellsCntY);
      else
        x = getRandomInt(0, cellsCntX) + '-0';
    } else {
      if (randomBoolean[0]() == true)
        x = (cellsCntX - 1) + '-' + getRandomInt(0, cellsCntY);
      else
        x = getRandomInt(0, cellsCntX) + '-' + (cellsCntY - 1);
    }

    if (grid.cell[grid.getCell(x)].type === 'Normal')
      enemy.push(new Enemy(enemiesColor, x));
    else
      i--;
  }

  for (var x = 0; x < enemy.length; x++)
    enemy[x].init();

  EnemyMoveTimeout = game.time.time + beatRate;
  justLost = justExpandedGrid = false;
  LoadTime = game.time.now;
}

// GAME LOOP
function update() {
  if (game.time.time > EnemyMoveTimeout) {

    for (var i = 0; i < enemy.length; i++)
      enemy[i].move(enemyMove[i]);

    //this function triggers the sfx player
    //blips_sfx.play();

    movesHaveBeenStored = false;

    //refresh move timeout
    EnemyMoveTimeout = game.time.time + beatRate;

  } else if (movesHaveBeenStored === false) {

    //We reset the predicted moves array
    enemyMove = new Array();

    for (var x = 0; x < enemy.length; x++)
      enemyMove.push(enemy[x].Nextmove());

    movesHaveBeenStored = true;
  }

  if (GameType === 'Normal' || GameType === 'Endless') {
    //if user has killed all the enemies.
    if (enemy.length === 0)
      expand();
  } else if (GameType === 'SkipSmash') {
    if (enemy.length === 1)
      expand();
  } else if (GameType === 'PaintStack') {
    //expand grid when over 70% of it is marked
    if(Math.floor(actor.markedArea)>70)
      expand();
  }

  //calculate new inteval
  beatInterval = Math.round(((EnemyMoveTimeout - game.time.time) / beatRate) * 100);

  timeSinceLevelLoad = Math.round((game.time.now - LoadTime) / 1000);
}

//RENDER LOOP
function render() {
  //draws cells and grid $ 1st Draw Call $
  grid.render();
  //debug text draw calls
  game.debug.text('grid: ' + cellsCntX + '-' + cellsCntY, 3, 14, '#b1ff00');
  game.debug.text('enemies: ' + enemy.length, 3, 27, '#b1ff00');
  game.debug.text('beat rate: ' + beatRate + ' ms', 3, 40, '#b1ff00');
  game.debug.text('Interval: ' + beatInterval + ' %', 3, 53, beatInterval < 20 ? '#ff0000' : '#00ff27');
  game.debug.text('time: ' + timeSinceLevelLoad + ' s', 3, 66, '#b1ff00');
  if(GameType === 'PaintStack')
    game.debug.text('marked area: ' + Math.floor(actor.markedArea) + '%' , 3, 79, '#b1ff00');
}
//$ game over $
//every game type has the same game over :)
function gameOver() {
  justLost = true;

  //reset grid back to 7x7
  cellsCntY = cellsCntX = 7;
  gameStateRestarts = 0;
  game.state.start(game.state.current);

  LoadTime = game.time.now;
}
//$ expand " grid " $
function expand() {
  justExpandedGrid = true;

  //NORMAL AND SKIPSMASH GAME TYPES
  if (GameType === 'Normal' || GameType === 'SkipSmash') {
    justLost = true;
    gameStateRestarts++;

    //increment cells by 1 if enemies > ceil(cells/2)
    if ((3 + gameStateRestarts) > Math.ceil(cellsCntX / 2))
      cellsCntY = ++cellsCntX;

    game.state.start(game.state.current);
  }
  //ENDLESS GAME TYPE
  else if (GameType === 'Endless') {
    gameStateRestarts++;

    //increment cells by 1 if enemies > ceil(cells/2)
    if ((3 + gameStateRestarts) > Math.ceil(cellsCntX / 2))
      cellsCntY = ++cellsCntX;

    game.state.start(game.state.current);
  }
  //PAINTSTACK GAME TYPE
  else if (GameType === 'PaintStack') {
    justLost = true;
    gameStateRestarts++;

    //increment cells by 1 if enemies > ceil(cells/2)
    if ((3 + gameStateRestarts) > Math.ceil(cellsCntX / 2))
      cellsCntY = ++cellsCntX;

    game.state.start(game.state.current);
  }
}
<|endoftext|>


<commit_before>'use strict';

var PluginError = require('gulp-util').PluginError;
var assign      = require('lodash.assign');
var fm          = require('front-matter');
var isEqual     = require('lodash.isequal');
var path        = require('path');
var through     = require('through2');
var yaml        = require('js-yaml');

var compareOrder = require('./compare-order');
var parse        = require('./parse');

module.exports = function(options) {

  options = assign({
    defaultLocale:  null,
    locales:        null,
    site:           null,
    templateEngine: require('./engines/nunjucks')(),
    draft:          false,
    dataExtensions: ['yml', 'yaml', 'json'],
    inject:         null,
  }, options);

  // template data
  // =============

  var pages       = {};
  var collections = {};
  var references  = {};
  [''].concat(options.locales || []).forEach(function(locale) {
    pages[locale]       = [];
    collections[locale] = {};
    references[locale]  = {};
  });

  var site;
  if (
    options.locales &&
    options.locales.length &&
    isEqual(
      Object.keys(options.site || {})
        .filter(function(x) { return x }).sort(),
      options.locales.slice().sort()
    )
  ) {
    site = options.site;
  } else {
    site = (options.locales || [])
      .reduce(function(site, locale) {
        site[locale] = options.site;
        return site;
      }, {'': options.site});
  }

  // init file
  // =========

  var initFile = function(file) {
    var data = file.data = parse(file.relative, {locales: options.locales});

    // data.locale
    data.locale || (data.locale = options.defaultLocale);

    // data.document
    if (~(options.dataExtensions || []).indexOf(data.extname)) {
      data.document = 'data';
    } else if (fm.test(file.contents.toString())) {
      data.document = 'text';
    } else {
      data.document = false;
    }

    // data.filepaths
    data.filepaths = [];
    data.filepaths.push(data.locale === options.defaultLocale ? null : data.locale);
    data.filepaths.push.apply(data.filepaths, data.dirnames);
    if (data.document) {
      data.filepaths.push(data.index ? null : data.slug);
      data.filepaths.push('index.html');
    } else {
      data.filepaths.push(data.slug + '.' + data.extname);
    }
    data.filepaths = data.filepaths.filter(function(x) { return x });

    // data.filepath
    data.filepath = path.join.apply(path, data.filepaths);

    // file.path
    file.path = path.join(file.base, data.filepath);

    return file;
  };

  // check duplicates
  // ================

  var checkDuplicates = (function() {
    var paths = {};
    return function(file) {
      if (file.path in paths) {
        throw new PluginError('gulp-minisite', [
          'creating two files into the same path: ' + file.path,
          'file 1: ' + paths[file.path].relative,
          'file 2: ' + file.relative,
        ].join('\n'));
      }
      paths[file.path] = file;
    };
  })();

  // init doc
  // ========

  var initDoc = (function() {
    var resourceGroup = {};

    return function(file) {
      var data   = file.data;
      var locale = data.locale || '';

      // data.resourceId
      data.resourceId = data.dirnames.concat(data.index ? [] : [data.slug]).join('/');
      // references
      references[locale][data.resourceId] = data;
      // data.locales
      resourceGroup[data.resourceId] || (resourceGroup[data.resourceId] = {});
      resourceGroup[data.resourceId][locale] = data;
      data.locales = resourceGroup[data.resourceId];

      // data.collectionId
      data.collectionId = data.dirnames.join('/');
      // collections
      if (!data.index) {
        collections[locale][data.collectionId] || (collections[locale][data.collectionId] = []);
        collections[locale][data.collectionId].push(data);
        collections[locale][data.collectionId].sort(compareOrder);
      }
      // data.collection
      collections[locale][data.resourceId] || (collections[locale][data.resourceId] = []);
      data.collection = collections[locale][data.resourceId];

      // data.paths
      data.paths = data.filepaths.slice(0, -1);
      data.paths.unshift('/');

      // data.path
      data.path = path.join.apply(path, data.paths);

      // pages
      pages[locale].push(data);

      // data & body
      if (data.document === 'data') {
        data.data = yaml.safeLoad(file.contents.toString());
        (data.data === undefined) && (data.data = {});
        data.body = '';
      } else if (data.document === 'text') {
        var fmData = fm(file.contents.toString());
        data.data  = fmData.attributes;
        data.body  = fmData.body;
      }
      // shallow attribute access
      for (var key in data.data) {
        if (key in data) {
          continue;
        }
        data[key] = data.data[key];
      }
    };
  })();

  // ===
  // ===

  var files = [];

  // transform
  // =========

  var transform = function(file, _, done) {
    if (file.isNull()) {
      return done(null, file);
    }
    if (file.isStream()) {
      return done(new PluginError('gulp-minisite', 'Streaming not supported'));
    }

    files.push(file);
    return done();
  };

  // flush
  // =====

  var flush = function(done) {
    if (!files.length) {
      return done();
    }

    var storedFiles = [];
    var storedDocs  = [];
    var tmpFiles = files.slice();
    var tmpDocs;

    var injects = options.inject ? (Array.isArray(options.inject) ? options.inject : [options.inject]) : [];
    var injectedFiles;

    while (injects.length || tmpFiles.length) {
      tmpFiles = tmpFiles
        .map(initFile)
        .filter(function(v) { return !v.data.draft || options.draft });

      try {
        tmpFiles.forEach(checkDuplicates);
      } catch (e) {
        return done(e);
      }

      tmpDocs = tmpFiles.filter(function(v) { return v.data.document });
      tmpDocs.forEach(initDoc);

      storedFiles = storedFiles.concat(tmpFiles);
      tmpFiles    = [];
      storedDocs  = storedDocs.concat(tmpDocs);
      tmpDocs     = [];

      if (injects.length) {
        tmpFiles = (injects.shift())({
          site:        site,
          pages:       pages,
          collections: collections,
          references:  references,
        }, options);
      }
    }

    // document rendering
    // ------------------

    for (var file of storedDocs) {
      if (file.data.template) {
        var locale   = file.data.locale || '';
        var tmplName = file.data.template;
        var tmplData = {
          site:        site[locale],
          page:        file.data,
          pages:       pages[locale],
          collections: collections[locale],
          references:  references[locale],
          global:      {
            site:        site,
            pages:       pages,
            collections: collections,
            references:  references,
          },
        };
        try {
          file.contents = new Buffer(options.templateEngine(tmplName, tmplData), 'utf8');
        } catch (e) {
          return done(new PluginError('gulp-minisite', e.message));
        }
      } else {
        file.contents = new Buffer(file.data.body, 'utf8');
      }
    }

    // pipe
    // ----

    storedFiles.forEach(this.push, this);

    // done
    // ----

    return done();
  };

  // through
  // =======

  return through.obj(transform, flush);
};
<commit_msg>Add page.prev, page.next. Unsophisticated implementation yet #37<commit_after>'use strict';

var PluginError = require('gulp-util').PluginError;
var assign      = require('lodash.assign');
var fm          = require('front-matter');
var isEqual     = require('lodash.isequal');
var path        = require('path');
var through     = require('through2');
var yaml        = require('js-yaml');

var compareOrder = require('./compare-order');
var parse        = require('./parse');

module.exports = function(options) {

  options = assign({
    defaultLocale:  null,
    locales:        null,
    site:           null,
    templateEngine: require('./engines/nunjucks')(),
    draft:          false,
    dataExtensions: ['yml', 'yaml', 'json'],
    inject:         null,
  }, options);

  // template data
  // =============

  var pages       = {};
  var collections = {};
  var references  = {};
  [''].concat(options.locales || []).forEach(function(locale) {
    pages[locale]       = [];
    collections[locale] = {};
    references[locale]  = {};
  });

  var site;
  if (
    options.locales &&
    options.locales.length &&
    isEqual(
      Object.keys(options.site || {})
        .filter(function(x) { return x }).sort(),
      options.locales.slice().sort()
    )
  ) {
    site = options.site;
  } else {
    site = (options.locales || [])
      .reduce(function(site, locale) {
        site[locale] = options.site;
        return site;
      }, {'': options.site});
  }

  // init file
  // =========

  var initFile = function(file) {
    var data = file.data = parse(file.relative, {locales: options.locales});

    // data.locale
    data.locale || (data.locale = options.defaultLocale);

    // data.document
    if (~(options.dataExtensions || []).indexOf(data.extname)) {
      data.document = 'data';
    } else if (fm.test(file.contents.toString())) {
      data.document = 'text';
    } else {
      data.document = false;
    }

    // data.filepaths
    data.filepaths = [];
    data.filepaths.push(data.locale === options.defaultLocale ? null : data.locale);
    data.filepaths.push.apply(data.filepaths, data.dirnames);
    if (data.document) {
      data.filepaths.push(data.index ? null : data.slug);
      data.filepaths.push('index.html');
    } else {
      data.filepaths.push(data.slug + '.' + data.extname);
    }
    data.filepaths = data.filepaths.filter(function(x) { return x });

    // data.filepath
    data.filepath = path.join.apply(path, data.filepaths);

    // file.path
    file.path = path.join(file.base, data.filepath);

    return file;
  };

  // check duplicates
  // ================

  var checkDuplicates = (function() {
    var paths = {};
    return function(file) {
      if (file.path in paths) {
        throw new PluginError('gulp-minisite', [
          'creating two files into the same path: ' + file.path,
          'file 1: ' + paths[file.path].relative,
          'file 2: ' + file.relative,
        ].join('\n'));
      }
      paths[file.path] = file;
    };
  })();

  // init doc
  // ========

  var initDoc = (function() {
    var resourceGroup = {};

    return function(file) {
      var data   = file.data;
      var locale = data.locale || '';

      // data.resourceId
      data.resourceId = data.dirnames.concat(data.index ? [] : [data.slug]).join('/');
      // references
      references[locale][data.resourceId] = data;
      // data.locales
      resourceGroup[data.resourceId] || (resourceGroup[data.resourceId] = {});
      resourceGroup[data.resourceId][locale] = data;
      data.locales = resourceGroup[data.resourceId];

      // data.collectionId
      data.collectionId = data.dirnames.join('/');
      // collections
      if (!data.index) {
        collections[locale][data.collectionId] || (collections[locale][data.collectionId] = []);
        collections[locale][data.collectionId].push(data);
        collections[locale][data.collectionId].sort(compareOrder)
          .reduce(function(prev, curr) {
            curr.prev = prev;
            curr.next = null;
            if (!prev) return curr;
            return prev.next = curr;
          }, null);
      }
      // data.collection
      collections[locale][data.resourceId] || (collections[locale][data.resourceId] = []);
      data.collection = collections[locale][data.resourceId];

      // data.paths
      data.paths = data.filepaths.slice(0, -1);
      data.paths.unshift('/');

      // data.path
      data.path = path.join.apply(path, data.paths);

      // pages
      pages[locale].push(data);

      // data & body
      if (data.document === 'data') {
        data.data = yaml.safeLoad(file.contents.toString());
        (data.data === undefined) && (data.data = {});
        data.body = '';
      } else if (data.document === 'text') {
        var fmData = fm(file.contents.toString());
        data.data  = fmData.attributes;
        data.body  = fmData.body;
      }
      // shallow attribute access
      for (var key in data.data) {
        if (key in data) {
          continue;
        }
        data[key] = data.data[key];
      }
    };
  })();

  // ===
  // ===

  var files = [];

  // transform
  // =========

  var transform = function(file, _, done) {
    if (file.isNull()) {
      return done(null, file);
    }
    if (file.isStream()) {
      return done(new PluginError('gulp-minisite', 'Streaming not supported'));
    }

    files.push(file);
    return done();
  };

  // flush
  // =====

  var flush = function(done) {
    if (!files.length) {
      return done();
    }

    var storedFiles = [];
    var storedDocs  = [];
    var tmpFiles = files.slice();
    var tmpDocs;

    var injects = options.inject ? (Array.isArray(options.inject) ? options.inject : [options.inject]) : [];
    var injectedFiles;

    while (injects.length || tmpFiles.length) {
      tmpFiles = tmpFiles
        .map(initFile)
        .filter(function(v) { return !v.data.draft || options.draft });

      try {
        tmpFiles.forEach(checkDuplicates);
      } catch (e) {
        return done(e);
      }

      tmpDocs = tmpFiles.filter(function(v) { return v.data.document });
      tmpDocs.forEach(initDoc);

      storedFiles = storedFiles.concat(tmpFiles);
      tmpFiles    = [];
      storedDocs  = storedDocs.concat(tmpDocs);
      tmpDocs     = [];

      if (injects.length) {
        tmpFiles = (injects.shift())({
          site:        site,
          pages:       pages,
          collections: collections,
          references:  references,
        }, options);
      }
    }

    // document rendering
    // ------------------

    for (var file of storedDocs) {
      if (file.data.template) {
        var locale   = file.data.locale || '';
        var tmplName = file.data.template;
        var tmplData = {
          site:        site[locale],
          page:        file.data,
          pages:       pages[locale],
          collections: collections[locale],
          references:  references[locale],
          global:      {
            site:        site,
            pages:       pages,
            collections: collections,
            references:  references,
          },
        };
        try {
          file.contents = new Buffer(options.templateEngine(tmplName, tmplData), 'utf8');
        } catch (e) {
          return done(new PluginError('gulp-minisite', e.message));
        }
      } else {
        file.contents = new Buffer(file.data.body, 'utf8');
      }
    }

    // pipe
    // ----

    storedFiles.forEach(this.push, this);

    // done
    // ----

    return done();
  };

  // through
  // =======

  return through.obj(transform, flush);
};
<|endoftext|>


<commit_before>/**!
Copyright 2016 SEITENBAU GmbH

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

'use strict';

var mixNmock = require('./lib/mix-n-mock');
var automaticRestart = require('./lib/automatic-restart');

var params = process.argv.slice(2); // node scriptName [...]

// main
var restartIndex = params.indexOf('--restart');
if (restartIndex !== -1) {
    params.splice(restartIndex, 1);
    params.forEach(automaticRestart);
} else {
    if (params.length) {
        params.forEach(mixNmock);
    } else {
        mixNmock();
    }
}
<commit_msg>Allow `-r` short notation.<commit_after>/**!
Copyright 2016 SEITENBAU GmbH

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

'use strict';

var mixNmock = require('./lib/mix-n-mock');
var automaticRestart = require('./lib/automatic-restart');

var params = process.argv.slice(2); // node scriptName [...]

// process command line parameters
var allowedFlags = {
    '-r': '--restart'
};
var flags = params.reduceRight((result, param, i) => {
    if (param[0] === '-') {
        params.splice(i, 1);
        result[allowedFlags[param] || param] = true;
    }
    return result;
}, {});

// main
if (flags.hasOwnProperty('--restart')) {
    params.forEach(automaticRestart);
} else {
    if (params.length) {
        params.forEach(mixNmock);
    } else {
        mixNmock();
    }
}
<|endoftext|>