<commit_before>const async = require('async')
const fs = require('fs')
const later = require('later')
const path = require('path')

const getOrderedSchedules = (_G, schedules) => {
  return Object.keys(schedules)
    .sort((a, b) => {
      let sched_a = later.parse.cron(schedules[a].crontab)
      let sched_b = later.parse.cron(schedules[b].crontab)
      schedules[a].prev = new Date(later.schedule(sched_a).prev().getTime())
      schedules[b].prev = new Date(later.schedule(sched_b).prev().getTime())
      return (
        schedules[b].prev < schedules[a].prev
        || (
          schedules[b].prev === schedules[a].prev
          && schedules[b].ordinal < schedules[a].ordinal
        )
      )
    })
    .map((a) => {
      // _G.playbackLog.log(schedules[a])
      return schedules[a]
    })
}

module.exports.render = (_G, configuration, mainCallback) => {
  document.body.style.cursor = 'none'
  if (_G.DEV_MODE) {
    document.body.style.cursor = 'crosshair'
  }
  while (document.getElementById('player').hasChildNodes()) {
    document.getElementById('player').removeChild(document.getElementById('player').lastChild)
  }
  let playerRootNode = document.createElement('div')
  playerRootNode.id = configuration.screenEid
  playerRootNode.className = 'screen'
  document.getElementById('player').appendChild(playerRootNode)

  playerRootNode.stopPlayback = function () {
    playerRootNode.playbackStatus = 'stopped'
    _G.playbackLog.log('Stop  all')
    Array.from(this.childNodes).forEach((a) => { a.stopPlayback() })
  }

  let configurationNode = document.createElement('pre')
  configurationNode.className = 'configuration'
  configurationNode.innerHTML = JSON.stringify(configuration, null, 4)
  document.getElementById('player').appendChild(configurationNode)

  // Schedules->layouts
  async.forEachOf(configuration.schedules, (schedule, ix, callback) => {
    schedule.layoutNode = document.createElement('div')
    let layoutNode = schedule.layoutNode
    layoutNode.id = schedule.eid + '.' + schedule.layoutEid
    layoutNode.className = 'layout'
    playerRootNode.appendChild(layoutNode)

    layoutNode.playbackStatus = 'stopped'
    layoutNode.swConfiguration = configuration
    layoutNode.swSchedule = schedule
    layoutNode.timers = []
    let later_sched = later.parse.cron(schedule.crontab)
    _G.playbackLog.log(layoutNode.swSchedule.name + ' initialized.', layoutNode.id)

    layoutNode.stopPlayback = function () { // this === layoutNode
      let self = this
      if (self.playbackStatus === 'stopped') {
        _G.playbackLog.log('Already stopped ' + self.swSchedule.name + ' schedule', self.id)
        return
      }

      _G.playbackLog.log('stopPlayback ' + self.swSchedule.name + ' schedule')
      self.timers.forEach((timer) => {
        clearTimeout(timer)
      })
      self.playbackStatus = 'stopped'
      _G.playbackLog.log('Stop layout ' + self.swSchedule.name + ' playlists.')
      Array.from(self.childNodes).forEach((a) => { a.stopPlayback() })
    }

    layoutNode.startPlayback = function () { // this === layoutNode
      let self = this
      //
      // if (self.playbackStatus === 'stopped') {
      //   _G.playbackLog.log('Already stopped ' + self.swSchedule.name + ' schedule')
      //   return
      // }
      _G.playbackLog.log('start ' + self.swSchedule.name, self.id)
      if (self.swSchedule.cleanup) {
        _G.playbackLog.log(self.swSchedule.name + ' requesting cleanup', self.id)
        playerRootNode.stopPlayback()
      } else if (self.playbackStatus === 'started') {
        self.stopPlayback()
      }

      let ms_from_latest_playback = new Date() - new Date(later.schedule(later_sched).prev())
      let ms_until_next_playback = new Date(later.schedule(later_sched).next()) - new Date()

      // dont restart layout if less than a second left to play
      if (ms_until_next_playback < 1000) {
        _G.playbackLog.log('Fix restart playback of schedule ' + self.swSchedule.name + ' in ' + ms_until_next_playback/1e3 + 's.', self.id)
        ms_until_next_playback = new Date(later.schedule(later_sched).next(2)[1]) - new Date()
      }

      // Schedule next occurrance from crontab
      _G.playbackLog.log('Schedule playback of schedule ' + self.swSchedule.name + ' in ' + ms_until_next_playback/1e3 + 's.', self.id)
      setTimeout(() => {
        self.startPlayback()
      }, ms_until_next_playback)

      // Stop if duration already exceeded by now
      if (schedule.duration && schedule.duration * 1e3 < ms_from_latest_playback) {
        _G.playbackLog.log('STOP    ' + self.swSchedule.name
          + '; duration ' + self.swSchedule.duration
          + '; sec_from_latest_playback ' + ms_from_latest_playback/1e3
          , self.id
        )
        self.stopPlayback()
        // Schedule might have duration less than interval between playback
      } else {
        if (schedule.duration && schedule.duration * 1e3 < (ms_from_latest_playback + ms_until_next_playback)) {
          let ms_left = schedule.duration * 1e3 - ms_from_latest_playback
          ms_left = (ms_left < 10 ? 10 : ms_left)
          setTimeout(function () {
            _G.playbackLog.log('STOP    ' + schedule.name + ' from timeout.', self.id)
            self.stopPlayback()
          }, ms_left)
        }
        self.playbackStatus = 'started'
        _G.playbackLog.log(self.swSchedule.name + ' layout status = "started".', self.id)

        // Start layout playlists (delayed a bit to avoid simultaneous pause/play)
        self.timers.push(setTimeout(function () {
          Array.from(self.childNodes).forEach((a) => {
            _G.playbackLog.log('START   ' + schedule.name + ' from timeout timer.', self.id)
            a.startPlayback()
          })
        }, 10))
      }
    }

    // Playlists
    async.forEachOf(schedule.layoutPlaylists, (playlist, layoutPlaylistEid, callback) => {
      playlist.playlistNode = document.createElement('div')
      let playlistNode = playlist.playlistNode
      // playlistNode.swPlaylist = playlist
      layoutNode.appendChild(playlistNode)
      playlistNode.id = playlistNode.parentNode.id + '.' + playlist.playlistEid
      playlistNode.className = 'playlist'
      if (playlist.inPixels) {
        playlistNode.style.top = (playlist.top / schedule.height * 100) + '%'
        playlistNode.style.left = (playlist.left / schedule.width * 100) + '%'
        playlistNode.style.width = (playlist.width / schedule.width * 100) + '%'
        playlistNode.style.height = (playlist.height / schedule.height * 100) + '%'
      }
      else {
        playlistNode.style.top = playlist.top + '%'
        playlistNode.style.left = playlist.left + '%'
        playlistNode.style.width = playlist.width + '%'
        playlistNode.style.height = playlist.height + '%'
      }

      playlistNode.stopPlayback = function () {
        let self = this
        if (self.playbackStatus === 'stopped') {
          _G.playbackLog.log('Already stopped ' + self.name + ' layoutPlaylist', self.id)
          return
        }
        self.playbackStatus = 'stopped'
        _G.playbackLog.log('Stop  playlist ' + self.name, self.id)
        Array.from(this.childNodes).forEach((a) => { a.stopPlayback() })
      }

      playlistNode.startPlayback = function () { // this === playlistNode
        let self = this
        if (self.playbackStatus === 'started') {
          _G.playbackLog.log('Already started ' + self.name + ' layoutPlaylist', self.id)
          return
        }
        self.playbackStatus = 'started'
        _G.playbackLog.log('Start playlist ' + self.name, self.id)
        self.firstChild.startPlayback()
      }

      // Medias
      let firstMediaNode
      let lastMediaNode
      async.forEachOf(playlist.playlistMedias, (swMedia, playlistMediaEid, callback) => {
        swMedia.mediaNode = document.createElement('div')
        let mediaNode = swMedia.mediaNode
        mediaNode.playlistNode = playlistNode
        mediaNode.swMedia = swMedia

        if (firstMediaNode === undefined) { firstMediaNode = mediaNode }
        if (lastMediaNode !== undefined) {
          lastMediaNode.nextMediaNode = mediaNode
        }
        lastMediaNode = mediaNode

        playlistNode.appendChild(mediaNode)
        mediaNode.id = mediaNode.parentNode.id + '.' + swMedia.mediaEid
        mediaNode.style.visibility = 'hidden'
        mediaNode.className = 'media'

        mediaNode.stopPlayback = function () { // this === mediaNode
          let self = this
          if (self.playbackStatus === 'stopped') {
            _G.playbackLog.log('Already stopped ' + self.swMedia.name + ' playlistMedias', self.id)
            return
          }
          self.timers.forEach((timer) => {
            clearTimeout(timer)
          })
          self.playbackStatus = 'stopped'
          _G.playbackLog.log('Stop  media ' + ' ' + self.swMedia.name, self.id)
          self.style.visibility = 'hidden'
          this.firstChild.pause()
          this.firstChild.currentTime = 0
        }

        mediaNode.startPlayback = function () { // this === mediaNode
          let self = this
          if (self.playbackStatus === 'started') {
            _G.playbackLog.log('Already started ' + self.name + ' playlistMedias', self.id)
            return
          }
          if (self.playlistNode.playbackStatus !== 'started') {
            _G.playbackLog.log('Cant start ' + self.name + ' playlistMedias in stopped playlist', self.id)
            return
          }
          self.playbackStatus = 'started'
          _G.playbackLog.log('Start media ' + self.id + ' ' + self.swMedia.name, self.id)
          self.style.visibility = 'visible'
          this.firstChild.currentTime = 0
          try {
            this.firstChild.play()
          } catch (err) {
            _G.playbackLog.log('media.play() errored for ' + self.id + '.', self.id)
            _G.playbackLog.log(err, self.id)
          }
            // .catch( function(reason) {
            //   console.log(reason)
            //   _G.playbackLog.log('media.play() errored for ' + self.id + '.', reason)
            // })
          if (swMedia.duration) {
            mediaNode.timers.push(setTimeout(function () {
              _G.playbackLog.log('mediaNode.stopPlayback() from "media duration exceeded" event.')
              mediaNode.stopPlayback()
              if (mediaNode.nextMediaNode) {
                mediaNode.timers.push(setTimeout(function () {
                  mediaNode.nextMediaNode.startPlayback()
                }, swMedia.delay * 1e3))
              } else {
                _G.playbackLog.log('Playlist finished. No next media to load.')
              }
            }, swMedia.duration * 1e3))
          }
        }
        insertMedia(_G, mediaNode, swMedia, callback)
      }, function (err) {
        if (playlist.loop !== false) {
          _G.playbackLog.log('Set loop', playlistNode.id)
          lastMediaNode.nextMediaNode = firstMediaNode
        } else {
          _G.playbackLog.log('Do not loop', playlistNode.id)
        }
        if (err) { console.error(err.message) }
        callback()
      })
    }, function (err) {
      if (err) { console.error(err.message) }
      callback()
    })
  }, function (err) {
    if (err) { console.error(err.message) }
    mainCallback(null, _G.codes.DOM_RENDERED)
    _G.playbackLog.log(_G.codes.DOM_RENDERED)
    getOrderedSchedules(_G, configuration.schedules)
      .forEach((a) => {
        _G.playbackLog.log('Start playback of ' + a.name, a.eid)
        a.layoutNode.startPlayback()
      })
  })
}

const insertMedia = (_G, mediaNode, swMedia, callback) => {
  // _G.playbackLog.log('Insert media ' + swMedia.mediaEid + '(' + mediaNode.id + ').', swMedia.type)
  mediaNode.timers = []
  let mediaDomElement
  if (swMedia.type === _G.codes.MEDIA_TYPE_VIDEO) {
    mediaDomElement = document.createElement('VIDEO')
    let mimetype = 'video/' + swMedia.fileName.split('.')[swMedia.fileName.split('.').length - 1]
    mediaDomElement.type = mimetype
    // _G.playbackLog.log(mimetype)
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaDomElement.overflow = 'hidden'
    mediaDomElement.autoplay = false
    mediaDomElement.controls = _G.DEV_MODE
    mediaDomElement.muted = swMedia.mute
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.video'
    mediaDomElement.addEventListener('durationchange', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' duration ' + mediaDomElement.duration + 'sec')
    })
    mediaDomElement.addEventListener('play', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' started')
    })
    mediaDomElement.addEventListener('ended', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' ended. Start delay ' + swMedia.delay * 1e3 + 'ms')
      // _G.playbackLog.log('mediaNode.stopPlayback() from "video ended" event.')
      mediaNode.stopPlayback()
      mediaNode.timers.push(setTimeout(function () {
        mediaNode.nextMediaNode.startPlayback()
      }, swMedia.delay * 1e3))
    })
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_AUDIO) {
    let mediaDomElement = document.createElement('AUDIO')
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.audio'
    mediaDomElement.addEventListener('ended', () => {
      _G.playbackLog.log('mediaNode.stopPlayback() from "audio ended" event.')
      mediaNode.stopPlayback()
      mediaNode.timers.push(setTimeout(function () {
        mediaNode.nextMediaNode.startPlayback()
      }, swMedia.delay * 1e3))
    })
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_IMAGE) {
    let mediaDomElement = document.createElement('IMG')
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.img'
    // Properties and methods not present natively
    // mediaDomElement.currentTime = 0
    mediaDomElement.play = () => {}
    mediaDomElement.pause = () => {}
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_URL) {
    let mediaDomElement = document.createElement('IFRAME')
    mediaDomElement.src = swMedia.url
    mediaDomElement.scrolling = 'yes'
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.url'
    // Properties and methods not present natively
    // mediaDomElement.currentTime = 0
    mediaDomElement.play = () => {}
    mediaDomElement.pause = () => {}
    return callback()
  }
}
<commit_msg>bind timeout context<commit_after>const async = require('async')
const fs = require('fs')
const later = require('later')
const path = require('path')

const getOrderedSchedules = (_G, schedules) => {
  return Object.keys(schedules)
    .sort((a, b) => {
      let sched_a = later.parse.cron(schedules[a].crontab)
      let sched_b = later.parse.cron(schedules[b].crontab)
      schedules[a].prev = new Date(later.schedule(sched_a).prev().getTime())
      schedules[b].prev = new Date(later.schedule(sched_b).prev().getTime())
      return (
        schedules[b].prev < schedules[a].prev
        || (
          schedules[b].prev === schedules[a].prev
          && schedules[b].ordinal < schedules[a].ordinal
        )
      )
    })
    .map((a) => {
      // _G.playbackLog.log(schedules[a])
      return schedules[a]
    })
}

module.exports.render = (_G, configuration, mainCallback) => {
  document.body.style.cursor = 'none'
  if (_G.DEV_MODE) {
    document.body.style.cursor = 'crosshair'
  }
  while (document.getElementById('player').hasChildNodes()) {
    document.getElementById('player').removeChild(document.getElementById('player').lastChild)
  }
  let playerRootNode = document.createElement('div')
  playerRootNode.id = configuration.screenEid
  playerRootNode.className = 'screen'
  document.getElementById('player').appendChild(playerRootNode)

  playerRootNode.stopPlayback = function () {
    playerRootNode.playbackStatus = 'stopped'
    _G.playbackLog.log('Stop  all')
    Array.from(this.childNodes).forEach((a) => { a.stopPlayback() })
  }

  let configurationNode = document.createElement('pre')
  configurationNode.className = 'configuration'
  configurationNode.innerHTML = JSON.stringify(configuration, null, 4)
  document.getElementById('player').appendChild(configurationNode)

  // Schedules->layouts
  async.forEachOf(configuration.schedules, (schedule, ix, callback) => {
    schedule.layoutNode = document.createElement('div')
    let layoutNode = schedule.layoutNode
    layoutNode.id = schedule.eid + '.' + schedule.layoutEid
    layoutNode.className = 'layout'
    playerRootNode.appendChild(layoutNode)

    layoutNode.playbackStatus = 'stopped'
    layoutNode.swConfiguration = configuration
    layoutNode.swSchedule = schedule
    layoutNode.timers = []
    let later_sched = later.parse.cron(schedule.crontab)
    _G.playbackLog.log(layoutNode.swSchedule.name + ' initialized.', layoutNode.id)

    layoutNode.stopPlayback = function () { // this === layoutNode
      let self = this
      if (self.playbackStatus === 'stopped') {
        _G.playbackLog.log('Already stopped ' + self.swSchedule.name + ' schedule', self.id)
        return
      }

      _G.playbackLog.log('stopPlayback ' + self.swSchedule.name + ' schedule')
      self.timers.forEach((timer) => {
        clearTimeout(timer)
      })
      self.playbackStatus = 'stopped'
      _G.playbackLog.log('Stop layout ' + self.swSchedule.name + ' playlists.')
      Array.from(self.childNodes).forEach((a) => { a.stopPlayback() })
    }

    layoutNode.startPlayback = function () { // this === layoutNode
      let self = this
      //
      // if (self.playbackStatus === 'stopped') {
      //   _G.playbackLog.log('Already stopped ' + self.swSchedule.name + ' schedule')
      //   return
      // }
      _G.playbackLog.log('start ' + self.swSchedule.name, self.id)
      if (self.swSchedule.cleanup) {
        _G.playbackLog.log(self.swSchedule.name + ' requesting cleanup', self.id)
        playerRootNode.stopPlayback()
      } else if (self.playbackStatus === 'started') {
        self.stopPlayback()
      }

      let ms_from_latest_playback = new Date() - new Date(later.schedule(later_sched).prev())
      let ms_until_next_playback = new Date(later.schedule(later_sched).next()) - new Date()

      // dont restart layout if less than a second left to play
      if (ms_until_next_playback < 1000) {
        _G.playbackLog.log('Fix restart playback of schedule ' + self.swSchedule.name + ' in ' + ms_until_next_playback/1e3 + 's.', self.id)
        ms_until_next_playback = new Date(later.schedule(later_sched).next(2)[1]) - new Date()
      }

      // Schedule next occurrance from crontab
      _G.playbackLog.log('Schedule playback of schedule ' + self.swSchedule.name + ' in ' + ms_until_next_playback/1e3 + 's.', self.id)
      setTimeout(() => {
        self.startPlayback()
      }, ms_until_next_playback)

      // Stop if duration already exceeded by now
      if (schedule.duration && schedule.duration * 1e3 < ms_from_latest_playback) {
        _G.playbackLog.log('STOP    ' + self.swSchedule.name
          + '; duration ' + self.swSchedule.duration
          + '; sec_from_latest_playback ' + ms_from_latest_playback/1e3
          , self.id
        )
        self.stopPlayback()
        // Schedule might have duration less than interval between playback
      } else {
        if (schedule.duration && schedule.duration * 1e3 < (ms_from_latest_playback + ms_until_next_playback)) {
          let ms_left = schedule.duration * 1e3 - ms_from_latest_playback
          ms_left = (ms_left < 10 ? 10 : ms_left)
          setTimeout(function () {
            _G.playbackLog.log('STOP    ' + schedule.name + ' from timeout.', self.id)
            self.stopPlayback()
          }, ms_left)
        }
        self.playbackStatus = 'started'
        _G.playbackLog.log(self.swSchedule.name + ' layout status = "started".', self.id)

        // Start layout playlists (delayed a bit to avoid simultaneous pause/play)
        self.timers.push(setTimeout(function () {
          Array.from(self.childNodes).forEach((a) => {
            _G.playbackLog.log('START   ' + schedule.name + ' from timeout timer.', self.id)
            a.startPlayback()
          })
        }, 10))
      }
    }

    // Playlists
    async.forEachOf(schedule.layoutPlaylists, (playlist, layoutPlaylistEid, callback) => {
      playlist.playlistNode = document.createElement('div')
      let playlistNode = playlist.playlistNode
      // playlistNode.swPlaylist = playlist
      layoutNode.appendChild(playlistNode)
      playlistNode.id = playlistNode.parentNode.id + '.' + playlist.playlistEid
      playlistNode.className = 'playlist'
      if (playlist.inPixels) {
        playlistNode.style.top = (playlist.top / schedule.height * 100) + '%'
        playlistNode.style.left = (playlist.left / schedule.width * 100) + '%'
        playlistNode.style.width = (playlist.width / schedule.width * 100) + '%'
        playlistNode.style.height = (playlist.height / schedule.height * 100) + '%'
      }
      else {
        playlistNode.style.top = playlist.top + '%'
        playlistNode.style.left = playlist.left + '%'
        playlistNode.style.width = playlist.width + '%'
        playlistNode.style.height = playlist.height + '%'
      }

      playlistNode.stopPlayback = function () {
        let self = this
        if (self.playbackStatus === 'stopped') {
          _G.playbackLog.log('Already stopped ' + self.name + ' layoutPlaylist', self.id)
          return
        }
        self.playbackStatus = 'stopped'
        _G.playbackLog.log('Stop  playlist ' + self.name, self.id)
        Array.from(this.childNodes).forEach((a) => { a.stopPlayback() })
      }

      playlistNode.startPlayback = function () { // this === playlistNode
        let self = this
        if (self.playbackStatus === 'started') {
          _G.playbackLog.log('Already started ' + self.name + ' layoutPlaylist', self.id)
          return
        }
        self.playbackStatus = 'started'
        _G.playbackLog.log('Start playlist ' + self.name, self.id)
        self.firstChild.startPlayback()
      }

      // Medias
      let firstMediaNode
      let lastMediaNode
      async.forEachOf(playlist.playlistMedias, (swMedia, playlistMediaEid, callback) => {
        swMedia.mediaNode = document.createElement('div')
        let mediaNode = swMedia.mediaNode
        mediaNode.playlistNode = playlistNode
        mediaNode.swMedia = swMedia

        if (firstMediaNode === undefined) { firstMediaNode = mediaNode }
        if (lastMediaNode !== undefined) {
          lastMediaNode.nextMediaNode = mediaNode
        }
        lastMediaNode = mediaNode

        playlistNode.appendChild(mediaNode)
        mediaNode.id = mediaNode.parentNode.id + '.' + swMedia.mediaEid
        mediaNode.style.visibility = 'hidden'
        mediaNode.className = 'media'

        mediaNode.stopPlayback = function () { // this === mediaNode
          let self = this
          if (self.playbackStatus === 'stopped') {
            _G.playbackLog.log('Already stopped ' + self.swMedia.name + ' playlistMedias', self.id)
            return
          }
          self.timers.forEach((timer) => {
            clearTimeout(timer)
          })
          self.playbackStatus = 'stopped'
          _G.playbackLog.log('Stop  media ' + ' ' + self.swMedia.name, self.id)
          self.style.visibility = 'hidden'
          this.firstChild.pause()
          this.firstChild.currentTime = 0
        }

        mediaNode.startPlayback = function () { // this === mediaNode
          let self = this
          if (self.playbackStatus === 'started') {
            _G.playbackLog.log('Already started ' + self.name + ' playlistMedias', self.id)
            return
          }
          if (self.playlistNode.playbackStatus !== 'started') {
            _G.playbackLog.log('Cant start ' + self.name + ' playlistMedias in stopped playlist', self.id)
            return
          }
          self.playbackStatus = 'started'
          _G.playbackLog.log('Start media ' + self.id + ' ' + self.swMedia.name, self.id)
          self.style.visibility = 'visible'
          this.firstChild.currentTime = 0
          try {
            this.firstChild.play()
          } catch (err) {
            _G.playbackLog.log('media.play() errored for ' + self.id + '.', self.id)
            _G.playbackLog.log(err, self.id)
          }
            // .catch( function(reason) {
            //   console.log(reason)
            //   _G.playbackLog.log('media.play() errored for ' + self.id + '.', reason)
            // })
          if (self.swMedia.duration) {
            let delayedStopPlayback = function() { // don't forget to bind "this"
              _G.playbackLog.log('mediaNode.stopPlayback() from "media duration exceeded" event.', this.id)
              this.stopPlayback()
              if (this.nextMediaNode) {
                _G.playbackLog.log('Load next media.', this.id)
                this.timers.push(setTimeout(this.startPlayback.bind(this.nextMediaNode), this.swMedia.delay * 1e3))
              } else {
                _G.playbackLog.log('Playlist finished. No next media to load.', this.id)
              }
            }
            self.timers.push(setTimeout(delayedStopPlayback.bind(self), swMedia.duration * 1e3) )
          }
        }
        insertMedia(_G, mediaNode, swMedia, callback)
      }, function (err) {
        if (playlist.loop !== false) {
          _G.playbackLog.log('Set loop', playlistNode.id)
          lastMediaNode.nextMediaNode = firstMediaNode
        } else {
          _G.playbackLog.log('Do not loop', playlistNode.id)
        }
        if (err) { console.error(err.message) }
        callback()
      })
    }, function (err) {
      if (err) { console.error(err.message) }
      callback()
    })
  }, function (err) {
    if (err) { console.error(err.message) }
    mainCallback(null, _G.codes.DOM_RENDERED)
    _G.playbackLog.log(_G.codes.DOM_RENDERED)
    getOrderedSchedules(_G, configuration.schedules)
      .forEach((a) => {
        _G.playbackLog.log('Start playback of ' + a.name, a.eid)
        a.layoutNode.startPlayback()
      })
  })
}

const insertMedia = (_G, mediaNode, swMedia, callback) => {
  // _G.playbackLog.log('Insert media ' + swMedia.mediaEid + '(' + mediaNode.id + ').', swMedia.type)
  mediaNode.timers = []
  let mediaDomElement
  if (swMedia.type === _G.codes.MEDIA_TYPE_VIDEO) {
    mediaDomElement = document.createElement('VIDEO')
    let mimetype = 'video/' + swMedia.fileName.split('.')[swMedia.fileName.split('.').length - 1]
    mediaDomElement.type = mimetype
    // _G.playbackLog.log(mimetype)
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaDomElement.overflow = 'hidden'
    mediaDomElement.autoplay = false
    mediaDomElement.controls = _G.DEV_MODE
    mediaDomElement.muted = swMedia.mute
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.video'
    mediaDomElement.addEventListener('durationchange', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' duration ' + mediaDomElement.duration + 'sec')
    })
    mediaDomElement.addEventListener('play', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' started')
    })
    mediaDomElement.addEventListener('ended', () => {
      _G.playbackLog.log('Video media ' + mediaNode.id + ' ended. Start delay ' + swMedia.delay * 1e3 + 'ms')
      // _G.playbackLog.log('mediaNode.stopPlayback() from "video ended" event.')
      mediaNode.stopPlayback()
      mediaNode.timers.push(setTimeout(function () {
        mediaNode.nextMediaNode.startPlayback()
      }, swMedia.delay * 1e3))
    })
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_AUDIO) {
    let mediaDomElement = document.createElement('AUDIO')
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.audio'
    mediaDomElement.addEventListener('ended', () => {
      _G.playbackLog.log('mediaNode.stopPlayback() from "audio ended" event.')
      mediaNode.stopPlayback()
      mediaNode.timers.push(setTimeout(function () {
        mediaNode.nextMediaNode.startPlayback()
      }, swMedia.delay * 1e3))
    })
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_IMAGE) {
    let mediaDomElement = document.createElement('IMG')
    mediaDomElement.src = path.resolve(_G.MEDIA_DIR, swMedia.mediaEid.toString())
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.img'
    // Properties and methods not present natively
    // mediaDomElement.currentTime = 0
    mediaDomElement.play = () => {}
    mediaDomElement.pause = () => {}
    return callback()
  }
  else if (swMedia.type === _G.codes.MEDIA_TYPE_URL) {
    let mediaDomElement = document.createElement('IFRAME')
    mediaDomElement.src = swMedia.url
    mediaDomElement.scrolling = 'yes'
    mediaNode.appendChild(mediaDomElement)
    mediaDomElement.id = mediaNode.id + '.url'
    // Properties and methods not present natively
    // mediaDomElement.currentTime = 0
    mediaDomElement.play = () => {}
    mediaDomElement.pause = () => {}
    return callback()
  }
}
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { setupFactoryGuy, make } from 'ember-data-factory-guy';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | provider-show', function(hooks) {
  setupRenderingTest(hooks);
  setupFactoryGuy(hooks);

  test('it renders', async function(assert) {
    this.set('model', make('client'));

    // Template block usage:
    await render(hbs`
      {{#provider-show model=model link="clients.index"}}

      {{/provider-show}}
    `);

    assert.dom('*').hasText('Organization Information Member ID Organization Name Australian Data Archive System Email Service Contact Information Password has not been set Billing Information has not been provided');
  });
});
<commit_msg>support test<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { setupFactoryGuy, make } from 'ember-data-factory-guy';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | provider-show', function(hooks) {
  setupRenderingTest(hooks);
  setupFactoryGuy(hooks);

  test('it renders', async function(assert) {
    this.set('model', make('client'));

    // Template block usage:
    await render(hbs`
      {{#provider-show model=model link="clients.index"}}

      {{/provider-show}}
    `);

    assert.dom('*').hasText('Organization Information Member ID Member Type Organization Name Australian Data Archive System Email Service Contact Information Password has not been set Billing Information has not been provided');
  });
});
<|endoftext|>


<commit_before>const express = require('express')
const replica = require('./lib/replica.js')
const app = express()
const port = 8003
const io = require('socket.io-client');
const images = __dirname + '/images/';

let imgArray = new Array();

// @TODO make queues work
let requests_awaiting = {}
let requests_finished = []

const REPLICA_NO = 3
const MULTICAST_PORT = 20000;
const MULTICAST_ADDR = "230.255.255.255";;
var dgram = require('dgram');
const client = dgram.createSocket({ type: "udp4", reuseAddr: true });

ioClient = io.connect("http://localhost:3000")

client.on('listening', function () {
    var address = client.address();
    console.log('UDP Client Group Multicast on ' + address.address + ":" + address.port)
    client.setBroadcast(true)
    client.setMulticastTTL(128)
    client.addMembership(MULTICAST_ADDR)
    ioClient.emit('chat message', 'UDP Client Group Multicast on ' + address.address + ":" + address.port)

});

client.on('message', function (message, remote) {
    console.log('A: Epic Command Received. Preparing Relay.');
    console.log('B: From: ' + remote.address + ':' + remote.port +' - ' + message);

    var msg = message.toString().split(',')

    if (msg[1]) {
        if (msg[1].toLowerCase() === 'canny') {
            console.log('C: processing canny for sequence: ' + msg[0])
            let args = {type: 'color', seq_id: msg[0]}
            replica.getCanny(args, (function (error, response, body) {

                if(error) {
                    console.log("got error")
                    console.log(error);
                }
                else {
                    imgArray[msg[0]] = body
                    sendDeliveredMessage(msg[0])

                    // @TODO this will be sent by RM
                    console.log("sending image to FE using WS")
                    ioClient.emit('chat message', 'sending image')

                    ioClient.emit('show image', { image: true, buffer: body.toString('base64') });

                }
            }))
        }
        else if (msg[1].toLowerCase() === 'contour') {
            console.log('C: processing contour for sequence: ' + msg[0])
            let args = {type: 'color', seq_id: msg[0]}
            replica.getContour(args, (function (error, response, body) {

                if(error) {
                    console.log("got error")
                    console.log(error);
                }
                else {
                    imgArray[msg[0]] = body
                    sendDeliveredMessage(msg[0])

                    // @TODO this will be sent by RM

                    ioClient.emit('show image', { image: true, buffer: body.toString('base64') });
                }
            }))
        }
        else {
            console.log('C: Ignoring message: '+ message)
        }
    }
    else {
        console.log('C: Ignoring message: '+ message)
    }
});

client.bind(MULTICAST_PORT);

app.get('/', (req, res) => {
    res.send('Hello World: ' + seq_id)
})

app.get('/getJob/:id', function(req, res, next) {

    if (imgArray[req.params.id]) {
        var img = Buffer.from(imgArray[req.params.id], 'base64')
        res.contentType('image/jpeg')
        return res.send(img)
    }
    else {
        res.status(404).send("Sorry can't find that!")
    }
})

// var doFrames = setInterval(myTimer, 3000);

// function myTimer() {
//     seq_id++
//     console.log("sequence = " + seq_id)
//     sendAwaitMessage(seq_id)
//     let args = {type: 'color', seq_id: seq_id}
//     replica.getCanny(args, (function (error, response, body) {

//         if(error) {
//             console.log("got error")
//             console.log(error);
//         }
//         else {
//             imgArray[seq_id] = body
//             sendDeliveredMessage(seq_id)
//         }
//     }))
//     if (seq_id === 3) { // Stop at given Frame
//         console.log('stopping timer')
//         return clearInterval(doFrames);
//     }
// }

function sendDeliveredMessage(id) {
    const message = Buffer.from(id+`,delivered,`+REPLICA_NO);
    client.send(message, 0, message.length, MULTICAST_PORT, MULTICAST_ADDR, function() {
        console.info(`Sending message "${message}"`);
    });
}

app.listen(port, () => {
  console.log(`OpenISS OpenCV replica listening at http://localhost:${port}`)
})<commit_msg>Added queues to node replica<commit_after>const express = require('express')
const replica = require('./lib/replica.js')
const app = express()
const port = 8003
const io = require('socket.io-client');
const images = __dirname + '/images/';

let imgArray = new Array();

// @TODO make queues work
let requests_awaiting = {}
let requests_finished = []

let SEQ_NUM = 1;
const REPLICA_NO = 3
const MULTICAST_PORT = 20000;
const MULTICAST_ADDR = "230.255.255.255";;
var dgram = require('dgram');
const { Console } = require('console');
const client = dgram.createSocket({ type: "udp4", reuseAddr: true });

ioClient = io.connect("http://localhost:3000")

client.on('listening', function () {
    var address = client.address();
    console.log('UDP Client Group Multicast on ' + address.address + ":" + address.port)
    client.setBroadcast(true)
    client.setMulticastTTL(128)
    client.addMembership(MULTICAST_ADDR)
    ioClient.emit('chat message', 'UDP Client Group Multicast on ' + address.address + ":" + address.port)

});

client.on('message', function (message, remote) {
    console.log('A: Epic Command Received. Preparing Relay.');
    console.log('B: From: ' + remote.address + ':' + remote.port +' - ' + message);

    var msg = message.toString().split(',')

    if (msg[1]) {
        if (msg[1].toLowerCase() === 'canny' || msg[1].toLowerCase() === 'contour') {
            if(!(msg[0] in requests_awaiting) && !(msg[0] in requests_finished)){
                requests_awaiting[msg[0]] = msg[1];
                //processFrame(msg[0], msg[1].toLowerCase());
                checkRequestsAwaiting();
            }
        }
        else {
            console.log('C: Ignoring message: '+ message)
        }
    }
    else {
        console.log('C: Ignoring message: '+ message)
    }
});

client.bind(MULTICAST_PORT);

app.get('/', (req, res) => {
    res.send('Hello World: ' + seq_id)
})

app.get('/getJob/:id', function(req, res, next) {

    if (imgArray[req.params.id]) {
        var img = Buffer.from(imgArray[req.params.id], 'base64')
        res.contentType('image/jpeg')
        return res.send(img)
    }
    else {
        res.status(404).send("Sorry can't find that!")
    }
})

// var doFrames = setInterval(myTimer, 3000);

// function myTimer() {
//     seq_id++
//     console.log("sequence = " + seq_id)
//     sendAwaitMessage(seq_id)
//     let args = {type: 'color', seq_id: seq_id}
//     replica.getCanny(args, (function (error, response, body) {

//         if(error) {
//             console.log("got error")
//             console.log(error);
//         }
//         else {
//             imgArray[seq_id] = body
//             sendDeliveredMessage(seq_id)
//         }
//     }))
//     if (seq_id === 3) { // Stop at given Frame
//         console.log('stopping timer')
//         return clearInterval(doFrames);
//     }
// }

function sendDeliveredMessage(id) {
    const message = Buffer.from(id+`,delivered,`+REPLICA_NO);
    client.send(message, 0, message.length, MULTICAST_PORT, MULTICAST_ADDR, function() {
        console.info(`Sending message "${message}"`);
    });
}

function checkRequestsAwaiting(){
    while(SEQ_NUM in requests_awaiting){
        processFrame(SEQ_NUM, requests_awaiting[SEQ_NUM]);
        sendDeliveredMessage(SEQ_NUM);
        delete requests_awaiting[SEQ_NUM];
        requests_finished.push(SEQ_NUM);
        SEQ_NUM += 1;
    }
}

function processFrame(frame, method){
    if(method.toLowerCase() === 'canny'){
        doCanny(frame);
    }
    else{
        doContour(frame);
    }
}

function doCanny(frame){
    console.log('C: processing canny for sequence: ' + frame)
    let args = {type: 'color', seq_id: frame}
    replica.getCanny(args, (function (error, response, body) {

        if(error) {
            console.log("got error")
            console.log(error);
        }
        else {
            console.log("Canny succesfully completed");
            imgArray[frame] = body
            // sendDeliveredMessage(frame)

            // // @TODO this will be sent by RM
            // console.log("sending image to FE using WS")
            // ioClient.emit('chat message', 'sending image')

            // ioClient.emit('show image', { image: true, buffer: body.toString('base64') });

        }
    }))
}

function doContour(frame){
    console.log('C: processing contour for sequence: ' + frame)
    let args = {type: 'color', seq_id: frame}
    replica.getContour(args, (function (error, response, body) {

        if(error) {
            console.log("got error")
            console.log(error);
        }
        else {
            console.log("Contour succesfully completed");
            imgArray[frame] = body
            // sendDeliveredMessage(frame)

            // // @TODO this will be sent by RM

            // ioClient.emit('show image', { image: true, buffer: body.toString('base64') });
        }
    }))
}

app.listen(port, () => {
  console.log(`OpenISS OpenCV replica listening at http://localhost:${port}`)
})<|endoftext|>


<commit_before>import State from '../State';
import Storage from '../Storage';
import {qs, qsAll} from '../utils';
import {showLoader, hideLoader} from './loader';
import {cleanHostInput} from '../utils';

const addButton = qs('.add-button');
const saveButton = qs('.save-button');
const umMaps = qs('.url-maps-list');
const umItem = qs('.url-map-item');

class URLMaps {

  constructor(state) {
    this.state = state;
    State.addListener(this.update.bind(this));
    addButton.addEventListener('click', this.addItem.bind(this, ''));
    saveButton.addEventListener('click', this.saveUrlMaps.bind(this));
    this.render();
    this.itemsCount = 0;
  }

  update(newState) {
    this.state = newState;
    this.render();
  }

  render() {
    showLoader();
    if(!this.state.urlMaps || !this.state.selectedIdentity) {
      return false;
    }

    umMaps.innerHTML = '';
    this.itemsCount = 0;

    for (const key in this.state.urlMaps) {
      const urlMap = this.state.urlMaps[key];
      if (this.state.selectedIdentity.cookieStoreId === urlMap.cookieStoreId) {
        this.addItem(urlMap.host);
      }
    }

    hideLoader();
  }

  addItem(host) {
    const item = umItem.cloneNode(true);
    item.setAttribute('data-id', String(this.itemsCount));
    item.classList.remove('template');
    qs('.url-input', item).value = host;
    qs('.remove-button', item).addEventListener('click', this.removeUrlMap.bind(this, this.itemsCount, host));
    umMaps.appendChild(item);
    this.itemsCount++;
  }

  removeUrlMap(rowId, host) {
    umMaps.removeChild(qs(`[data-id='${String(rowId)}']`));
    Storage.remove(host);
  }

  saveUrlMaps() {
    showLoader();
    const items = qsAll('.url-map-item');
    const maps = {};

    for (const item of items) {
      const urlInput = qs('.url-input', item);
      const host = cleanHostInput(urlInput && urlInput.value);

      if (host) {
        const map_host = (host.slice(-1) == '*' || host.slice(-1) == '/') ? host : host + '/';
        // append a '/' to the end of the rule, this makes sure there is a path
        maps[host] = {
          host,
          containerName: this.state.selectedIdentity.name,
          cookieStoreId: this.state.selectedIdentity.cookieStoreId,
          enabled: true,
        };
      }
    }

    Promise.all([
      Storage.setAll(maps),
    ]).then(() => {
      hideLoader();
      saveButton.innerHTML = 'Saved!';
      setTimeout(() => saveButton.innerHTML = 'Save', 3000);
    });
  }

}

export default new URLMaps({
  urlMaps: State.get('urlMaps'),
  selectedIdentity: State.get('selectedIdentity'),
});
<commit_msg>Fixed maps with unspecified paths<commit_after>import State from '../State';
import Storage from '../Storage';
import {qs, qsAll} from '../utils';
import {showLoader, hideLoader} from './loader';
import {cleanHostInput} from '../utils';

const addButton = qs('.add-button');
const saveButton = qs('.save-button');
const umMaps = qs('.url-maps-list');
const umItem = qs('.url-map-item');

class URLMaps {

  constructor(state) {
    this.state = state;
    State.addListener(this.update.bind(this));
    addButton.addEventListener('click', this.addItem.bind(this, ''));
    saveButton.addEventListener('click', this.saveUrlMaps.bind(this));
    this.render();
    this.itemsCount = 0;
  }

  update(newState) {
    this.state = newState;
    this.render();
  }

  render() {
    showLoader();
    if(!this.state.urlMaps || !this.state.selectedIdentity) {
      return false;
    }

    umMaps.innerHTML = '';
    this.itemsCount = 0;

    for (const key in this.state.urlMaps) {
      const urlMap = this.state.urlMaps[key];
      if (this.state.selectedIdentity.cookieStoreId === urlMap.cookieStoreId) {
        this.addItem(urlMap.host);
      }
    }

    hideLoader();
  }

  addItem(host) {
    const item = umItem.cloneNode(true);
    item.setAttribute('data-id', String(this.itemsCount));
    item.classList.remove('template');
    qs('.url-input', item).value = host;
    qs('.remove-button', item).addEventListener('click', this.removeUrlMap.bind(this, this.itemsCount, host));
    umMaps.appendChild(item);
    this.itemsCount++;
  }

  removeUrlMap(rowId, host) {
    umMaps.removeChild(qs(`[data-id='${String(rowId)}']`));
    Storage.remove(host);
  }

  saveUrlMaps() {
    showLoader();
    const items = qsAll('.url-map-item');
    const maps = {};

    for (const item of items) {
      const urlInput = qs('.url-input', item);
      const host = cleanHostInput(urlInput && urlInput.value);

      if (host) {
        const map_host = (host.indexOf('/') == -1) ? host + '/' : host;
        // append a '/' to the end of the rule, this makes sure there is a path
        maps[map_host] = {
          host: map_host,
          containerName: this.state.selectedIdentity.name,
          cookieStoreId: this.state.selectedIdentity.cookieStoreId,
          enabled: true,
        };
      }
    }

    Promise.all([
      Storage.setAll(maps),
    ]).then(() => {
      hideLoader();
      saveButton.innerHTML = 'Saved!';
      setTimeout(() => saveButton.innerHTML = 'Save', 3000);
    });
  }

}

export default new URLMaps({
  urlMaps: State.get('urlMaps'),
  selectedIdentity: State.get('selectedIdentity'),
});
<|endoftext|>


<commit_before>/**
 * @module dataserv-client/dataserv
 */

'use strict';

var os = require('os');
var child_process = require('child_process');
var exec = child_process.execFile;
var spawn = child_process.spawn;
var ipc = require('electron-safe-ipc/guest');
var logger = require('./logger');

/**
 * DataServ Client Wrapper
 * @constructor
 */
function DataServWrapper() {
  this._children = {};
  this._current = {};
}

/**
 * Bootstraps a new dataserv-client process
 * #_bootstrap
 * @param {String} executable
 * @param {String} name
 * @param {Array} args
 */
DataServWrapper.prototype._bootstrap = function(executable, name, args) {
  this.terminate(executable);
  logger.append(executable + ' ' + args.join(' '));

  this._current[executable] = name;
  var proc = this._children[executable] = spawn(executable, args);

  proc.stdout.on('data', function(data) {
    logger.append(data.toString());
  });

  proc.stderr.on('data', function(data) {
    logger.append(data.toString());
    ipc.send('processTerminated');
  });

  ipc.send('processStarted');

  return proc;
};

/**
 * Starts DataServClient `farm`
 * #farm
 * @param {String} execname - dataservclient executable name
 * @param {String} datadir - path the storage directory
 * @param {String} size
 * @param {String} unit
 */
DataServWrapper.prototype.farm = function(execname, datadir, size, unit) {
  return this._bootstrap(execname, 'FARMING', [
    '--store_path=' + datadir,
    '--max_size=' + size + unit,
    'farm'
  ]);
};

/**
 * Starts DataServClient `build`
 * #build
 * @param {String} execname - dataservclient executable name
 * @param {String} datadir - path the storage directory
 * @param {String} size
 * @param {String} unit
 */
DataServWrapper.prototype.build = function(execname, datadir, size, unit) {
  return this._bootstrap(execname, 'BUILDING', [
    '--store_path=' + datadir,
    '--max_size=' + size + unit,
    'build'
  ]);
};

/**
 * Starts DataServClient `register`
 * #register
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.register = function(execname) {
  return this._bootstrap(execname, 'REGISTERING', ['register']);
};

/**
 * Starts DataServClient `poll`
 * #poll
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.poll = function(execname) {
  return this._bootstrap(execname, 'POLLING', ['poll']);
};

/**
 * Calls DataServClient config setting
 * #poll
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.setAddress = function(execname, address) {
  return exec(execname, ['config', '--set_payout_address=' + address]);
};

/**
 * Validates the dataservclient executable by asking for it's version
 * #validateClient
 * @param {String} execname - dataservclient executable name
 * @param {Function} callback
 */
DataServWrapper.prototype.validateClient = function(execname, callback) {
  exec(execname, ['version'], function(err, stdout, stderr) {
    if (err) {
      return callback(err);
    }

    var version = os.platform() !== 'darwin' ? stdout : stderr;

    if (!version) {
      return callback(new Error('Invalid dataserv-client'));
    }

    logger.append(version);
    callback(null);
  });
};

/**
 * Terminates the given DataServClient process
 * #terminate
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.terminate = function(execname) {
  var proc = this._children[execname];

  if (typeof proc !== 'undefined') {
    logger.append(proc.pid + ' terminated');
    proc.kill();

    this._children[execname] = null;
    this._current[execname] = null;

    ipc.send('processTerminated');
  }
};

module.exports = new DataServWrapper();
module.exports.DataServWrapper = DataServWrapper;
<commit_msg>fix jsdoc comment<commit_after>/**
 * @module dataserv-client/dataserv
 */

'use strict';

var os = require('os');
var child_process = require('child_process');
var exec = child_process.execFile;
var spawn = child_process.spawn;
var ipc = require('electron-safe-ipc/guest');
var logger = require('./logger');

/**
 * DataServ Client Wrapper
 * @constructor
 */
function DataServWrapper() {
  this._children = {};
  this._current = {};
}

/**
 * Bootstraps a new dataserv-client process
 * #_bootstrap
 * @param {String} executable
 * @param {String} name
 * @param {Array} args
 */
DataServWrapper.prototype._bootstrap = function(executable, name, args) {
  this.terminate(executable);
  logger.append(executable + ' ' + args.join(' '));

  this._current[executable] = name;
  var proc = this._children[executable] = spawn(executable, args);

  proc.stdout.on('data', function(data) {
    logger.append(data.toString());
  });

  proc.stderr.on('data', function(data) {
    logger.append(data.toString());
    ipc.send('processTerminated');
  });

  ipc.send('processStarted');

  return proc;
};

/**
 * Starts DataServClient `farm`
 * #farm
 * @param {String} execname - dataservclient executable name
 * @param {String} datadir - path the storage directory
 * @param {String} size
 * @param {String} unit
 */
DataServWrapper.prototype.farm = function(execname, datadir, size, unit) {
  return this._bootstrap(execname, 'FARMING', [
    '--store_path=' + datadir,
    '--max_size=' + size + unit,
    'farm'
  ]);
};

/**
 * Starts DataServClient `build`
 * #build
 * @param {String} execname - dataservclient executable name
 * @param {String} datadir - path the storage directory
 * @param {String} size
 * @param {String} unit
 */
DataServWrapper.prototype.build = function(execname, datadir, size, unit) {
  return this._bootstrap(execname, 'BUILDING', [
    '--store_path=' + datadir,
    '--max_size=' + size + unit,
    'build'
  ]);
};

/**
 * Starts DataServClient `register`
 * #register
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.register = function(execname) {
  return this._bootstrap(execname, 'REGISTERING', ['register']);
};

/**
 * Starts DataServClient `poll`
 * #poll
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.poll = function(execname) {
  return this._bootstrap(execname, 'POLLING', ['poll']);
};

/**
 * Calls DataServClient config setting
 * #setAddress
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.setAddress = function(execname, address) {
  return exec(execname, ['config', '--set_payout_address=' + address]);
};

/**
 * Validates the dataservclient executable by asking for it's version
 * #validateClient
 * @param {String} execname - dataservclient executable name
 * @param {Function} callback
 */
DataServWrapper.prototype.validateClient = function(execname, callback) {
  exec(execname, ['version'], function(err, stdout, stderr) {
    if (err) {
      return callback(err);
    }

    var version = os.platform() !== 'darwin' ? stdout : stderr;

    if (!version) {
      return callback(new Error('Invalid dataserv-client'));
    }

    logger.append(version);
    callback(null);
  });
};

/**
 * Terminates the given DataServClient process
 * #terminate
 * @param {String} execname - dataservclient executable name
 */
DataServWrapper.prototype.terminate = function(execname) {
  var proc = this._children[execname];

  if (typeof proc !== 'undefined') {
    logger.append(proc.pid + ' terminated');
    proc.kill();

    this._children[execname] = null;
    this._current[execname] = null;

    ipc.send('processTerminated');
  }
};

module.exports = new DataServWrapper();
module.exports.DataServWrapper = DataServWrapper;
<|endoftext|>


<commit_before>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Creates "back" button, if applicable
//------------------------------------------------------------------------------
d3plus.ui.history = function(vars) {

  if (!vars.small && vars.history.states.length > 0) {

    var min_height = size + vars.style.labels.padding*4
    if (vars.margin.top < min_height) {
      vars.margin.top = min_height
    }

    var button = vars.parent.selectAll("div#d3plus_back_button")
      .data(["d3plus_back_button"])

    var size = vars.title.value
      ? vars.style.title["font-size"] : vars.style.title.sub["font-size"]

    var color = vars.title.sub.value
      ? vars.style.title.sub["font-color"] : vars.style.title["font-color"]

    var family = vars.title.sub.value
      ? vars.style.title.sub["font-family"] : vars.style.title["font-family"]

    var weight = vars.title.sub.value
      ? vars.style.title.sub["font-weight"] : vars.style.title["font-weight"]

    function style(elem) {

        elem
          .style("position","absolute")
          .style("left",vars.style.labels.padding*2+"px")
          .style("top",vars.margin.top/2-size/2+"px")
          .style("color", color)
          .style("font-family", family)
          .style("font-weight", weight)
          .style("font-size",size+"px")
          .style("z-index",2000)

    }

    var enter = button.enter().append("div")
      .attr("id","d3plus_back_button")
      .style("opacity",0)
      .call(style)
      .html(function(){

        if (d3plus.fonts.awesome) {
          var arrow = "&#xf104"
        }
        else {
          var arrow = "« "
        }

        return arrow+vars.format("Back")

      })

    button
      .on(d3plus.evt.over,function(){

        d3.select(this)
          .style("cursor","pointer")
          .transition().duration(vars.style.timing.mouseevents)
            .style("color",d3plus.color.lighter(color))

      })
      .on(d3plus.evt.out,function(){

        d3.select(this)
          .style("cursor","auto")
          .transition().duration(vars.style.timing.mouseevents)
            .style("color",color)

      })
      .on(d3plus.evt.click,function(){

        vars.back()

      })
      .transition().duration(vars.style.timing.transitions)
        .style("opacity",1)
        .call(style)

  }
  else {
    vars.parent.selectAll("div#d3plus_back_button")
      .transition().duration(vars.style.timing.transitions)
      .style("opacity",0)
      .remove()
  }

}
<commit_msg>fixed back button not getting enough vertical space<commit_after>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Creates "back" button, if applicable
//------------------------------------------------------------------------------
d3plus.ui.history = function(vars) {

  if (!vars.small && vars.history.states.length > 0) {

    var button = vars.parent.selectAll("div#d3plus_back_button")
      .data(["d3plus_back_button"])

    var size = vars.title.value
      ? vars.style.title["font-size"] : vars.style.title.sub["font-size"]

    var color = vars.title.sub.value
      ? vars.style.title.sub["font-color"] : vars.style.title["font-color"]

    var family = vars.title.sub.value
      ? vars.style.title.sub["font-family"] : vars.style.title["font-family"]

    var weight = vars.title.sub.value
      ? vars.style.title.sub["font-weight"] : vars.style.title["font-weight"]

    function style(elem) {

        elem
          .style("position","absolute")
          .style("left",vars.style.labels.padding*2+"px")
          .style("top",vars.margin.top/2-size/2+"px")
          .style("color", color)
          .style("font-family", family)
          .style("font-weight", weight)
          .style("font-size",size+"px")
          .style("z-index",2000)

    }

    var min_height = size + vars.style.labels.padding*2
    if (vars.margin.top < min_height) {
      vars.margin.top = min_height
    }

    var enter = button.enter().append("div")
      .attr("id","d3plus_back_button")
      .style("opacity",0)
      .call(style)
      .html(function(){

        if (d3plus.fonts.awesome) {
          var arrow = "&#xf104"
        }
        else {
          var arrow = "« "
        }

        return arrow+vars.format("Back")

      })

    button
      .on(d3plus.evt.over,function(){

        d3.select(this)
          .style("cursor","pointer")
          .transition().duration(vars.style.timing.mouseevents)
            .style("color",d3plus.color.lighter(color))

      })
      .on(d3plus.evt.out,function(){

        d3.select(this)
          .style("cursor","auto")
          .transition().duration(vars.style.timing.mouseevents)
            .style("color",color)

      })
      .on(d3plus.evt.click,function(){

        vars.back()

      })
      .transition().duration(vars.style.timing.transitions)
        .style("opacity",1)
        .call(style)

  }
  else {
    vars.parent.selectAll("div#d3plus_back_button")
      .transition().duration(vars.style.timing.transitions)
      .style("opacity",0)
      .remove()
  }

}
<|endoftext|>


<commit_before>import EmberObject from '@ember/object';
import module from '../helpers/module-for-db';
import { test } from '../helpers/qunit';
import { Models, Model, models, find } from 'documents';

const Ducks = Models.extend({

  source: find({
    query() {
    },
    matches() {
      return true;
    }
  }),

  model: {
    observe: [],
    create(doc) {
      return {
        type: 'duck',
        props: {
          doc
        }
      }
    }
  }

});

const Duck = Model.extend();

module('computed-models-embedded-source', {
  beforeEach() {
    this.register('model:ducks', Ducks);
    this.register('model:duck', Duck);
    this.create = () => {
      let Subject = EmberObject.extend({
        prop: models({
          create(owner) {
            let database = owner.get('database');
            return {
              type: 'ducks',
              props: { database }
            };
          }
        })
      });
      return Subject.create({ database: this.db }, this.ownerInjection);
    };
  }
});

test('create succeeds, source is observed', function(assert) {
  this.db.doc({ id: 'one' });
  let subject = this.create();
  let models = subject.get('prop');
  assert.ok(models);
  assert.equal(models.get('length'), 1);
  assert.deepEqual(models.mapBy('doc.id'), [ 'one' ]);
  this.db.doc({ id: 'two' });
  assert.deepEqual(models.mapBy('doc.id'), [ 'one', 'two' ]);
});

// test('source recreate', function(assert) {
//   this.db.doc({ id: 'main:one' });
//   let subject = this.create();
//   let models = subject.get('prop');
//   assert.ok(models);
//   assert.deepEqual(models.mapBy('doc.id'), [ 'main:one' ]);
//   let second = this.store.database('second');
//   second.doc({ id: 'second:one' });
//   models.set('database', second);
//   assert.deepEqual(models.mapBy('doc.id'), [ 'second:one' ]);
// });
<commit_msg>notes<commit_after>import EmberObject from '@ember/object';
import module from '../helpers/module-for-db';
import { test } from '../helpers/qunit';
import { Models, Model, models, find } from 'documents';

const Ducks = Models.extend({

  source: find({
    query() {
    },
    matches() {
      return true;
    }
  }),

  model: {
    observe: [],
    create(doc) {
      return {
        type: 'duck',
        props: {
          doc
        }
      }
    }
  }

});

const Duck = Model.extend();

module('computed-models-embedded-source', {
  beforeEach() {
    this.register('model:ducks', Ducks);
    this.register('model:duck', Duck);
    this.create = () => {
      let Subject = EmberObject.extend({
        prop: models({
          create(owner) {
            let database = owner.get('database');
            return {
              type: 'ducks',
              props: { database }
            };
          }
        })
      });
      return Subject.create({ database: this.db }, this.ownerInjection);
    };
  }
});

test('create succeeds, source is observed', function(assert) {
  this.db.doc({ id: 'one' });
  let subject = this.create();
  let models = subject.get('prop');
  assert.ok(models);
  assert.equal(models.get('length'), 1);
  assert.deepEqual(models.mapBy('doc.id'), [ 'one' ]);
  this.db.doc({ id: 'two' });
  assert.deepEqual(models.mapBy('doc.id'), [ 'one', 'two' ]);
});

// TODO: models are observing just an array, there is no intermediate `models.source` observer.
// test('source recreate', function(assert) {
//   this.db.doc({ id: 'main:one' });
//   let subject = this.create();
//   let models = subject.get('prop');
//   assert.ok(models);
//   assert.deepEqual(models.mapBy('doc.id'), [ 'main:one' ]);
//   let second = this.store.database('second');
//   second.doc({ id: 'second:one' });
//   models.set('database', second);
//   assert.deepEqual(models.mapBy('doc.id'), [ 'second:one' ]);
// });
<|endoftext|>


<commit_before><commit_msg>upload to s3 issue<commit_after><|endoftext|>


<commit_before><commit_msg>date range picker fixed bug<commit_after><|endoftext|>


<commit_before>Ext.define('Slate.ui.app.Container', {
    extend: 'Ext.Container',
    xtype: 'slate-appcontainer',
    requires: [
        'Slate.ui.app.Header',
        'Slate.ui.Placeholder'
    ],

    config: {

        /**
         * @cfg {boolean}
         * Whether app should be full-width or padded
         */
        fullWidth: false,

        /**
         * @cfg {Slate.ui.app.Header|Object|string|boolean}
         * Instance or configuration for header component.
         *
         * Setting boolean values change visibility.
         */
        header: null,

        /**
         * @cfg {Slate.ui.Placeholder|Object|string|boolean}
         * Instance or configuration for placeholder component.
         *
         * Setting boolean values change visibility.
         */
        placeholder: null,
    },

    // component properties
    componentCls: 'slate-appcontainer',

    childEls: [
        'bodyWrap'
    ],

    renderTpl: [
        '{% Ext.DomHelper.generateMarkup(values.$comp.getHeader().getRenderTree(), out); %}',
        '
',
        '    {% this.renderContainer(out, values); %}',
        '
',
    ],

    // config handlers
    updateFullWidth: function(fullWidth) {
        this.toggleCls('slate-appcontainer-fullwidth', fullWidth);
        this.toggleCls('slate-appcontainer-padded', !fullWidth);
    },

    applyHeader: function(header, oldHeader) {
        if (typeof header === 'boolean') {
            header = {
                hidden: !header
            };
        } else if (typeof header === 'string') {
            header = {
                title: header
            };
        }

        return Ext.factory(header, 'Slate.ui.app.Header', oldHeader);
    },

    updateHeader: function(header, oldHeader) {
        var me = this;

        if (oldHeader) {
            delete oldHeader.ownerCt;
        }

        if (header) {
            header.ownerCt = me;

            if (me.rendered) {
                header.render(me.el, me.bodyWrap);
                header.updateLayout({ isRoot: true });
            }
        }
    },

    applyPlaceholder: function(placeholder, oldPlaceholder) {
        var type = typeof placeholder;

        if (type == 'boolean') {
            placeholder = {
                hidden: !placeholder
            };
        } else if (type == 'string') {
            placeholder = {
                html: placeholder
            };
        }

        return Ext.factory(placeholder, 'Slate.ui.Placeholder', oldPlaceholder);
    },

    updatePlaceholder: function(placeholder, oldPlaceholder) {
        var me = this,
            items = me.items;

        if (items && items.isMixedCollection) {
            if (oldPlaceholder) {
                me.remove(oldPlaceholder);
            }

            if (placeholder) {
                me.insert(0, placeholder);
            }
        }
    },

    // container lifecycle
    initItems: function() {
        var me = this,
            placeholder = me.getPlaceholder();

        me.callParent(arguments);

        if (placeholder) {
            me.insert(0, placeholder);
        }
    },

    getRefItems: function(deep) {
        var header = this.getHeader(),
            items = this.callParent(arguments);

        if (header) {
            if (deep) {
                items.unshift(...header.getRefItems(deep));
            }
            items.unshift(header);
        }

        return items;
    },

    afterComponentLayout: function() {
        this.callParent(arguments);
        this.getHeader().updateLayout();
    },

    privates: {
        finishRender: function () {
            this.callParent(arguments);
            this.getHeader().finishRender();
        }
    },
});<commit_msg>Remove indentation space within tpls<commit_after>Ext.define('Slate.ui.app.Container', {
    extend: 'Ext.Container',
    xtype: 'slate-appcontainer',
    requires: [
        'Slate.ui.app.Header',
        'Slate.ui.Placeholder'
    ],

    config: {

        /**
         * @cfg {boolean}
         * Whether app should be full-width or padded
         */
        fullWidth: false,

        /**
         * @cfg {Slate.ui.app.Header|Object|string|boolean}
         * Instance or configuration for header component.
         *
         * Setting boolean values change visibility.
         */
        header: null,

        /**
         * @cfg {Slate.ui.Placeholder|Object|string|boolean}
         * Instance or configuration for placeholder component.
         *
         * Setting boolean values change visibility.
         */
        placeholder: null,
    },

    // component properties
    componentCls: 'slate-appcontainer',

    childEls: [
        'bodyWrap'
    ],

    renderTpl: [
        '{% Ext.DomHelper.generateMarkup(values.$comp.getHeader().getRenderTree(), out); %}',
        '
',
            '{% this.renderContainer(out, values); %}',
        '
',
    ],

    // config handlers
    updateFullWidth: function(fullWidth) {
        this.toggleCls('slate-appcontainer-fullwidth', fullWidth);
        this.toggleCls('slate-appcontainer-padded', !fullWidth);
    },

    applyHeader: function(header, oldHeader) {
        if (typeof header === 'boolean') {
            header = {
                hidden: !header
            };
        } else if (typeof header === 'string') {
            header = {
                title: header
            };
        }

        return Ext.factory(header, 'Slate.ui.app.Header', oldHeader);
    },

    updateHeader: function(header, oldHeader) {
        var me = this;

        if (oldHeader) {
            delete oldHeader.ownerCt;
        }

        if (header) {
            header.ownerCt = me;

            if (me.rendered) {
                header.render(me.el, me.bodyWrap);
                header.updateLayout({ isRoot: true });
            }
        }
    },

    applyPlaceholder: function(placeholder, oldPlaceholder) {
        var type = typeof placeholder;

        if (type == 'boolean') {
            placeholder = {
                hidden: !placeholder
            };
        } else if (type == 'string') {
            placeholder = {
                html: placeholder
            };
        }

        return Ext.factory(placeholder, 'Slate.ui.Placeholder', oldPlaceholder);
    },

    updatePlaceholder: function(placeholder, oldPlaceholder) {
        var me = this,
            items = me.items;

        if (items && items.isMixedCollection) {
            if (oldPlaceholder) {
                me.remove(oldPlaceholder);
            }

            if (placeholder) {
                me.insert(0, placeholder);
            }
        }
    },

    // container lifecycle
    initItems: function() {
        var me = this,
            placeholder = me.getPlaceholder();

        me.callParent(arguments);

        if (placeholder) {
            me.insert(0, placeholder);
        }
    },

    getRefItems: function(deep) {
        var header = this.getHeader(),
            items = this.callParent(arguments);

        if (header) {
            if (deep) {
                items.unshift(...header.getRefItems(deep));
            }
            items.unshift(header);
        }

        return items;
    },

    afterComponentLayout: function() {
        this.callParent(arguments);
        this.getHeader().updateLayout();
    },

    privates: {
        finishRender: function () {
            this.callParent(arguments);
            this.getHeader().finishRender();
        }
    },
});<|endoftext|>


<commit_before>/**
 * Created by Abdellatif on 1/9/2017.
 */
var React = require('react');
var ReactDOM = require('react-dom');
var firebase = require('firebase');
import Paper from "material-ui/Paper";
import RaisedButton from "material-ui/RaisedButton";
import FlatButton from "material-ui/FlatButton";
import Dialog from "material-ui/Dialog";

const bgColor = {
    backgroundImage: 'url("img/circles-25.jpg")',
};
const buttonStyle = {
    padding: '0px',
};
const paperStyle = {
    backgroundColor: 'transparent'
};

export default class Resources extends React.Component {

    componentWillMount(){
        const prayingSpaceRef = firebase.database().ref('Praying Space');
        prayingSpaceRef.on('value', snap => {
            this.setState({
                prayingSpace: snap.val()
            });
        })
        const halaqaRef = firebase.database().ref('Halaqa');
        halaqaRef.on('value', snap => {
            this.setState({
                halaqa: snap.val()
            });
        })
    }

    state = {
        open: false,
        prayingSpace: 'fetching',
        halaqa: 'fetching'
    };

    handleOpen = () => {
        this.setState({open: true});
    };

    handleClose = () => {
        this.setState({open: false});
    };

    render() {

        const actions = [
            <FlatButton
                label="Close"
                primary={true}
                onTouchTap={this.handleClose}
            />
        ];

        return (

                <Paper className="section no-padding" rounded={false} id="resources" style={paperStyle} zDepth={1}>

Praying Space

The on-campus praying space is located in the Mosaic
                                Cross Cultural Center. Located on the east side of the Student Union. The space is also equipped with a
                                wudoo’ (ablution) area.

                                The praying space is open on:

M-Th: 9 am to 8 pm

 Fr: 9 am - 5 pm

                            <FlatButton label="Location by video" onTouchTap={this.handleOpen}/>
                            <Dialog
                                title="Prayer Space Location"
                                actions={actions}
                                modal={false}
                                open={this.state.open}
                                onRequestClose={this.handleClose}
                            >

                            </Dialog>

                </Paper>
                <Paper className="section no-padding" rounded={false} style={paperStyle} zDepth={5}>

Quran Halaqa

                                We have a weekly Qur'an halaqa (session) in which we review or memorize a part of Qur'an.

                                location: TBD for Spring

                                Time: TBD for Spring

                            <FlatButton label="Sign up"/>

                </Paper>

New Students Handbook

Find off-campus Mosques, Roommates, Halal Restaurants and our unofficial hangout spot!

                    <RaisedButton secondary={true} target="_blank" href="handbook.pdf" label="Download PDF" style={buttonStyle}/>

        );
    }
}
<commit_msg>link the video to the praying space<commit_after>/**
 * Created by Abdellatif on 1/9/2017.
 */
var React = require('react');
var ReactDOM = require('react-dom');
var firebase = require('firebase');
import Paper from "material-ui/Paper";
import RaisedButton from "material-ui/RaisedButton";
import FlatButton from "material-ui/FlatButton";
import Dialog from "material-ui/Dialog";

const bgColor = {
    backgroundImage: 'url("img/circles-25.jpg")',
};
const buttonStyle = {
    padding: '0px',
};
const paperStyle = {
    backgroundColor: 'transparent'
};

export default class Resources extends React.Component {

    componentWillMount(){
        const prayingSpaceRef = firebase.database().ref('Praying Space');
        prayingSpaceRef.on('value', snap => {
            this.setState({
                prayingSpace: snap.val()
            });
        })
        const halaqaRef = firebase.database().ref('Halaqa');
        halaqaRef.on('value', snap => {
            this.setState({
                halaqa: snap.val()
            });
        })
    }

    state = {
        open: false,
        prayingSpace: 'fetching',
        halaqa: 'fetching'
    };

    handleOpen = () => {
        this.setState({open: true});
    };

    handleClose = () => {
        this.setState({open: false});
    };

    render() {

        const actions = [
            <FlatButton
                label="Close"
                primary={true}
                onTouchTap={this.handleClose}
            />
        ];

        return (

                <Paper className="section no-padding" rounded={false} id="resources" style={paperStyle} zDepth={1}>

Praying Space

The on-campus praying space is located in the Mosaic
                                Cross Cultural Center. Located on the east side of the Student Union. The space is also equipped with a
                                wudoo’ (ablution) area.

                                The praying space is open on:

M-Th: 9 am to 8 pm

 Fr: 9 am - 5 pm

                            <FlatButton label="Location by video" onTouchTap={this.handleOpen}/>
                            <Dialog
                                title="Prayer Space Location"
                                actions={actions}
                                modal={false}
                                open={this.state.open}
                                onRequestClose={this.handleClose}
                            >

                            </Dialog>

                </Paper>
                <Paper className="section no-padding" rounded={false} style={paperStyle} zDepth={5}>

Quran Halaqa

                                We have a weekly Qur'an halaqa (session) in which we review or memorize a part of Qur'an.

                                location: TBD for Spring

                                Time: TBD for Spring

                            <FlatButton label="Sign up"/>

                </Paper>

New Students Handbook

Find off-campus Mosques, Roommates, Halal Restaurants and our unofficial hangout spot!

                    <RaisedButton secondary={true} target="_blank" href="handbook.pdf" label="Download PDF" style={buttonStyle}/>

        );
    }
}
<|endoftext|>


<commit_before>(function(window) {
	var Sparky = window.Sparky;

	function wrap(i, min, max) {
		return i < min ? max - (min - i) :
			i >= max ? min + (i - max) :
			i ;
	}

	function Throttle(fn, time) {
		var flag = false;
		var context, args;

		function update() {
			fn.apply(context, args);
			flag = false;
		}

		return function() {
			context = this;
			args = arguments;

			if (flag) { return; }
			flag = true;

			setTimeout(update, time);
		};
	}

	var request = Throttle(function request(url, tip, node) {
		jQuery
		.ajax(url)
		.then(function(data) {
			if (!data || data.length === 0) { return; }
			tip.scope(Collection(data));

			var elem = tip.tojQuery();

			window.requestAnimationFrame(function() {
				elem.trigger({ type: 'activate', relatedTarget: node });

				var inputs = elem.find('input');

				var i = 0;

				inputs.eq(i).addClass('focus');

				node.addEventListener('keydown', function(e) {
					if (e.keyCode === 38) {
						e.preventDefault();
						console.log('UP');
						inputs.eq(i).removeClass('focus');
						i = wrap(i - 1, 0, inputs.length);
						inputs.eq(i).addClass('focus');
					}
					else if (e.keyCode === 40) {
						e.preventDefault();
						console.log('DOWN');
						inputs.eq(i).removeClass('focus');
						i = wrap(i + 1, 0, inputs.length);
						inputs.eq(i).addClass('focus');
					}
					else if (e.keyCode === 13) {
						e.preventDefault();
						console.log('RETURN');
						inputs.eq(i)[0].checked = true;
						Sparky.dom.trigger(inputs.eq(i)[0], 'valuechange');
					}
				});
			});
		});
	}, 320);

	Sparky.fn.suggest = function(node) {
		if (Sparky.dom.tag(node) !== 'input') {
			console.warn('Sparky: data-fn="suggest" can only be applied to an <input>.');
			return;
		}

		var id    = node.getAttribute('data-suggest-template');
		var url   = node.getAttribute('data-suggest-url');
		var value = node.getAttribute('data-suggest-value');
		var fn    = node.getAttribute('data-suggest-fn');
		var minlength = parseInt(node.getAttribute('data-suggest-minlength') || 3, 10);

		fn = this.data[fn];

		if (!id || !url) {
			console.warn('Sparky: data-fn="suggest" requires attributes data-suggest-template and data-suggest-url.', node);
			return;
		}

		var tip = Sparky(id);

		var scope;

		function listen(tip) {
			function update(data) {
				jQuery(tip).trigger('deactivate');

				if (value) {
					node.value = Sparky.render(value, data);
					Sparky.dom.trigger(node, 'valuechange');
				}

				if (fn) {
					fn(node, scope, data);
				}

				window.requestAnimationFrame(function() {
					// Refocus the original input
					node.focus();
				});
			}

			function change(e) {
				if (!e.target.checked) { return; }
				update(Sparky.getScope(e.target));
			}

			tip.addEventListener('change', change);
			tip.addEventListener('valuechange', change);
		}

		Sparky.dom.append(document.body, tip);

		listen(tip.filter(Sparky.dom.isElementNode)[0]);

		node.addEventListener('input', function(e) {
			var text = e.target.value;

			if (text.length < minlength) {
				jQuery(tip).trigger('deactivate');
				return;
			}

			request(url + text, tip, node);
		});

		this.on('scope', function(sparky, newscope) {
			scope = newscope;
		});

		node.addEventListener('blur', function(e) {
			jQuery(tip).trigger('deactivate');
		});
	};
})(this);
<commit_msg>Suggest<commit_after>(function(window) {
	var Sparky = window.Sparky;

	function wrap(i, min, max) {
		return i < min ? max - (min - i) :
			i >= max ? min + (i - max) :
			i ;
	}

	function Throttle(fn, time) {
		var flag = false;
		var context, args;

		function update() {
			fn.apply(context, args);
			flag = false;
		}

		return function() {
			context = this;
			args = arguments;

			if (flag) { return; }
			flag = true;

			setTimeout(update, time);
		};
	}

	function activate(data, tip, node) {
		if (!data || data.length === 0) { return; }

		tip.scope(data);

		var elem = tip.tojQuery();

		window.requestAnimationFrame(function() {
			elem.trigger({ type: 'activate', relatedTarget: node });

			var buttons = elem.find('button');
			var i = 0;

			buttons.eq(i).addClass('focus');

			node.addEventListener('keydown', function(e) {
				if (e.keyCode === 38) {
					e.preventDefault();
					console.log('UP');
					buttons.eq(i).removeClass('focus');
					i = wrap(i - 1, 0, buttons.length);
					buttons.eq(i).addClass('focus');
				}
				else if (e.keyCode === 40) {
					e.preventDefault();
					console.log('DOWN');
					buttons.eq(i).removeClass('focus');
					i = wrap(i + 1, 0, buttons.length);
					buttons.eq(i).addClass('focus');
				}
				else if (e.keyCode === 13) {
					e.preventDefault();
					console.log('RETURN');
					Sparky.dom.trigger(buttons.eq(i)[0], 'click');
				}
			});
		});
	}

	var request = Throttle(function request(url, tip, node) {
		jQuery
		.ajax(url)
		.then(function(data) {
			if (!data || data.length === 0) { return; }
			tip.scope(Collection(data));

			var elem = tip.tojQuery();

			window.requestAnimationFrame(function() {
				elem.trigger({ type: 'activate', relatedTarget: node });

				var inputs = elem.find('input');

				var i = 0;

				inputs.eq(i).addClass('focus');

				node.addEventListener('keydown', function(e) {
					if (e.keyCode === 38) {
						e.preventDefault();
						console.log('UP');
						inputs.eq(i).removeClass('focus');
						i = wrap(i - 1, 0, inputs.length);
						inputs.eq(i).addClass('focus');
					}
					else if (e.keyCode === 40) {
						e.preventDefault();
						console.log('DOWN');
						inputs.eq(i).removeClass('focus');
						i = wrap(i + 1, 0, inputs.length);
						inputs.eq(i).addClass('focus');
					}
					else if (e.keyCode === 13) {
						e.preventDefault();
						console.log('RETURN');
						inputs.eq(i)[0].checked = true;
						Sparky.dom.trigger(inputs.eq(i)[0], 'valuechange');
					}
				});
			});
		});
	}, 320);

	Sparky.fn.suggest = function(node) {
		if (Sparky.dom.tag(node) !== 'input') {
			console.warn('Sparky: data-fn="suggest" can only be applied to an <input>.');
			return;
		}

		var url   = node.getAttribute('data-suggest-data');
		var id    = node.getAttribute('data-suggest-template');
		var value = node.getAttribute('data-suggest-value');
		var fn    = node.getAttribute('data-suggest-fn');
		var minlength = parseInt(node.getAttribute('data-suggest-minlength') || 1, 10);

		if (!id || !url) {
			console.warn('Sparky: data-fn="suggest" requires attributes data-suggest-template and data-suggest-data.', node);
			return;
		}

		// Look for a data-suggest attribute having a tag in it
		Sparky.rsimpletags.lastIndex = 0;
		var tag = Sparky.rsimpletags.exec(url);
		var prop;

		if (tag) {
			if (url.length !== tag[0].length) {
				console.warn('Sparky: There is not just a simple tag in this attribute.');
				return;
			}

			prop = tag[1];
		}

		if (fn) { fn = this.data[fn]; }

		var tip = Sparky(id);
		Sparky.dom.append(document.body, tip);

		var scope;
		this.on('scope', function(sparky, newscope) {
			scope = newscope;
		});

		function listen(tip) {
			function update(data) {
				jQuery(tip).trigger('deactivate');

				if (value) {
					node.value = Sparky.render(value, data);
					Sparky.dom.trigger(node, 'change');
				}

				if (fn) {
					fn(node, scope, data);
				}

				window.requestAnimationFrame(function() {
					// Refocus the original input
					node.focus();
				});
			}

			function click(e) {
				update(Sparky.getScope(e.target));
			}

			function change(e) {
				if (e.target.checked) { return; }
				update(Sparky.getScope(e.target));
			}

			tip.addEventListener('click', click);
			tip.addEventListener('change', change);
			tip.addEventListener('valuechange', change);
		}

		listen(tip.filter(Sparky.dom.isElementNode)[0]);

		node.addEventListener('input', function(e) {
			var text = e.target.value;

			if (text.length < minlength) {
				jQuery(tip).trigger('deactivate');
				return;
			}

			if (tag) {
				activate(scope[prop], tip, node);
			}
			else {
				request(url + text, tip, node);
			}
		});

		node.addEventListener('blur', function(e) {
			jQuery(tip).trigger('deactivate');
		});
	};
})(this);
<|endoftext|>