<commit_before>var engine = require('player-engine')()
var coversActions = require('./covers.js')
var shuffle = require('shuffle-array')
var musicSimilarity = require('music-similarity')

var actions = {

  // Synchronize the player engine with the loaded state
  PLAYER_SYNCHRONIZE: () => {
    return (dispatch, getState) => {
      // Bind the event listeners to the actions
      engine.on('timeupdate', duration => dispatch(actions.PLAYER_CURRENT_DURATION(duration)))
      engine.on('ended', () => dispatch(actions.PLAYBACK_NEXT()))

      // Get the current state (just after loading)
      const state = getState()

      // Set the old volume and reset playing status
      actions.PLAYER_SET_VOLUME(state.player.volume)
      actions.PLAYER_SET_PLAYING(false, true)(dispatch, getState)

      // Get the filename of the last played song
      const song = getSong(state.player.songId, state)
      const filename = song ? song.filename : false
      if (!filename) {
        // Old file is not here anymore :(
        dispatch(actions.PLAYER_CURRENT_DURATION(0))
      } else {
        // Old file is still here, let's load it! :)
        engine.load(filename)
        engine.seek(state.player.currentDuration)
        coversActions.GET_COVER(song.album, song.artist, song.coverId)(dispatch, getState)
      }
    }
  },

  // Set the player engine to play a song
  PLAYER_SET_SONG: (id, ignoreHistory = false) => {
    return (dispatch, getState) => {
      var state = getState()

      // Don't write multiple times in the history if we are playing the same song
      if (state.player.songId === id) {
        ignoreHistory = true
      }

      // Get the song and the filename
      const song = getSong(id, state)
      const filename = song ? song.filename : false

      // Load and play the file in the engine
      engine.load(filename)
      actions.PLAYER_SET_PLAYING(true)(dispatch, getState)

      // Try and load an cover art
      coversActions.GET_COVER(song.album, song.artist, song.coverId)(dispatch, getState)

      // Update the view
      dispatch({
        type: 'PLAYER_SET_SONG',
        id,
        ignoreHistory
      })
    }
  },

  // Set the playing status to true or false
  PLAYER_SET_PLAYING: (playing, initialization) => {
    return (dispatch, getState) => {
      if (!initialization) {
        actions.FIX_TRACKS_FOR_ANDRIOD()(dispatch, getState)
      }

      (playing) ? engine.play() : engine.pause()
      dispatch({type: 'PLAYER_SET_PLAYING', playing})
    }
  },

  // Map through all songs that don't have a duration, play them with 0 volume
  // and remove them again after we grabbed the duration. This works around
  // andriod restrictions (play() is only ok if the user presses it) by hooking
  // into the user touching the screen for playback
  // Reference: https://github.com/peermusic/app/issues/6
  FIX_TRACKS_FOR_ANDRIOD: () => {
    return (dispatch, getState) => {
      const state = getState()
      const brokenSongs = state.songs.filter(s => s.duration === 0)
      if (brokenSongs.length === 0) {
        return
      }

      brokenSongs.map(song => {
        (song => {
          let triggered = false
          let audio = document.createElement('audio')
          audio.src = song.filename
          audio.volume = 0
          audio.play()
          audio.addEventListener('timeupdate', () => {
            if (triggered) { return }
            triggered = true
            audio.pause()
            dispatch({type: 'SET_SONG_DURATION', id: song.id, duration: audio.duration})
          })
        })(song)
      })
    }
  },

  // Seek to a position in the player
  PLAYER_SEEK: (duration) => {
    engine.seek(duration)
    return {
      type: 'PLAYER_CURRENT_DURATION',
      duration
    }
  },

  // Set the volume ofo the player
  PLAYER_SET_VOLUME: (volume) => {
    engine.volume(volume)
    return {
      type: 'PLAYER_SET_VOLUME',
      volume
    }
  },

  // Update the current duration of the player
  PLAYER_CURRENT_DURATION: (currentDuration) => {
    return {
      type: 'PLAYER_CURRENT_DURATION',
      currentDuration
    }
  },

  // Play a song and queue the next songs to be played
  PLAYBACK_SONG: (songs, index, play = true, replace = true) => {
    return (dispatch, getState) => {
      const state = getState()
      var playingSong

      // Only put songs into the playback queue that we
      // hold locally, and fix up the index afterwards
      const song = songs[index]
      songs = songs.filter(x => typeof x !== 'object' || x.local)
      index = songs.indexOf(song) !== -1 ? songs.indexOf(song) : 0
      songs = songs.map(x => x.id || x)

      // We tried to play a view where we don't have local songs
      if (songs.length === 0) {
        return
      }

      if (play) {
        // Take the first song and play it
        playingSong = songs[index]
        dispatch(actions.PLAYER_SET_SONG(playingSong))
        if (songs.length > 1) {
          dispatch({type: 'SAVE_POSSIBLE_SONG_QUEUE', songs})
        }
        // Remove the played songs
        songs = [...songs.slice(0, index), ...songs.slice(index + 1)]
      }

      if (state.player.randomPlayback) {
        // Random playback, shuffle all possible songs
        shuffle(songs)
      } else {
        // Normal playback, take the songs *after* the clicked song
        songs = [...songs.slice(index)]
      }

      // Radio playback, grab similar songs to this and
      // completely ignore queueing songs since the radio will do that
      if (state.player.radioPlayback && play) {
        actions.GET_RADIO_SONGS(getSong(playingSong, state))(dispatch, getState)
        return
      }

      // Queue the rest, but *not* for single songs
      if (songs.length > 0 && replace) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs
        })
      }

      if (songs.length > 0 && !replace) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE_PUSH',
          songs
        })
      }
    }
  },

  // Set the previous song for the player out of the history
  PLAYBACK_BACK: () => {
    return (dispatch, getState) => {
      const history = getState().player.history
      var id = history.songs[history.currentIndex - 1]
      dispatch(actions.PLAYER_SET_SONG(id, true))
      dispatch({
        type: 'HISTORY_BACK'
      })
    }
  },

  // Queue a song for playback
  PLAYBACK_USER_QUEUE: (id) => {
    return {
      type: 'PLAYBACK_USER_QUEUE_PUSH',
      id
    }
  },

  // Set the next song for the player
  PLAYBACK_NEXT: () => {
    return (dispatch, getState) => {
      const state = getState()

      // If we have a queue entry, it *always* takes priority
      const userQueue = state.player.userQueue
      if (userQueue.length > 0) {
        dispatch(actions.PLAYER_SET_SONG(userQueue[0]))
        dispatch({
          type: 'PLAYBACK_USER_QUEUE_POP'
        })
        return
      }

      // We have a history entry set, let's use that one.
      const history = state.player.history
      if (history.songs.length - 1 > history.currentIndex) {
        const id = history.songs[history.currentIndex + 1]
        dispatch(actions.PLAYER_SET_SONG(id, true))
        dispatch({
          type: 'HISTORY_NEXT'
        })
        return
      }

      const automaticQueue = state.player.automaticQueue

      // If we are on radio playback, make sure we have songs in the automatic queue
      if (state.player.radioPlayback && automaticQueue.length <= 15) {
        actions.GET_RADIO_SONGS(getSong(state.player.songId, state), true)(dispatch, getState)
      }

      // If we are on infinite playback, make sure that we have songs added to the automatic queue
      if (!state.player.radioPlayback && state.player.repeatPlayback && automaticQueue.length <= 15) {
        const index = !state.player.randomPlayback ? 0 : Math.floor(Math.random() * state.player.possibleQueue.length)
        actions.PLAYBACK_SONG(state.player.possibleQueue, index, false, false)(dispatch, getState)
      }

      // Take the automatically queued songs
      // (e.g. songs of a album or the radio songs)
      if (automaticQueue.length > 0) {
        dispatch(actions.PLAYER_SET_SONG(automaticQueue[0]))
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE_POP'
        })
        return
      }

      // Nothing set, let's pause on the last track
      console.log('Queue empty')
      actions.PLAYER_SET_PLAYING(false)(dispatch, getState)
      dispatch(actions.PLAYER_SEEK(0))
    }
  },

  // Toggle random playback
  TOGGLE_RANDOM_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      if (!state.player.randomPlayback) {
        // We were *not* on shuffle before, shuffle all songs in the queue
        dispatch({type: 'SHUFFLE_AUTOMATIC_QUEUE'})
      } else {
        // Unshuffle the songs by taking the index in the original queue and then adding
        // back the songs *after* that index
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: [...state.player.possibleQueue.slice(index + 1)]
        })
      }

      dispatch({type: 'TOGGLE_RANDOM_PLAYBACK'})
    }
  },

  // Toggle repeat playback
  TOGGLE_REPEAT_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      // Unqueue all songs that were on repeat
      if (state.player.repeatPlayback) {
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        const songs = [...state.player.possibleQueue.slice(index + 1)]
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: state.player.randomPlayback ? shuffle(songs, {copy: true}) : songs
        })
      }

      dispatch({type: 'TOGGLE_REPEAT_PLAYBACK'})
    }
  },

  // Toggle radio playback
  TOGGLE_RADIO_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      // Toggle the view state
      dispatch({type: 'TOGGLE_RADIO_PLAYBACK'})

      // Undo the radio playback and just take the songs we were going to play anyway
      if (state.player.radioPlayback) {
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        const songs = [...state.player.possibleQueue.slice(index + 1)]
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: state.player.randomPlayback ? shuffle(songs, {copy: true}) : songs
        })
        return
      }

      // Grab radio stuff for the current song
      const currentSong = getSong(state.player.songId, state)
      if (!currentSong) {
        return
      }

      actions.GET_RADIO_SONGS(currentSong)(dispatch, getState)
    }
  },

  // Get radio songs
  GET_RADIO_SONGS: (song, push = false) => {
    return (dispatch, getState) => {
      const state = getState()
      const metadata = {
        title: song.title,
        album: song.album,
        artist: song.artist,
        genre: song.genre
      }

      if (!push) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: []
        })
      }

      require('./sync.js').REQUEST_SIMILAR(song)

      // Get a similar track by metadata
      musicSimilarity(state.scrapingServers, metadata, function (list) {
        actions.SET_RADIO_SONGS(list, metadata, push)(dispatch, getState)
      })
    }
  },

  SET_RADIO_SONGS: (list, song, push = false) => {
    return (dispatch, getState) => {
      const state = getState()

      // Only get the songs that are currently available in the local library
      list = state.songs.filter(song => {
        return list.filter(s => s.artist === song.artist &&
            s.album === song.album &&
            s.title === song.title
          ).length > 0
      })

      // Remove songs that the user listened to recently
      list = filterSimilarSongs(list, state)

      // If we got no songs, fill it with songs out of the library
      // first by matching album, then by matching artist and then randomly
      if (list.length === 0) {
        list = getSimilarLibrarySongs(song, state)
      }

      // Set the playback queue with the radio songs
      if (!push) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: list.map(s => s.id)
        })
        return
      }

      dispatch({
        type: 'PLAYBACK_AUTOMATIC_QUEUE_PUSH',
        songs: list.map(s => s.id)
      })
    }
  }

}

// Get the song off the state based on a song id
function getSong (songId, state) {
  return state.songs.filter(x => x.id === songId)[0]
}

// Remove all songs that are in the history or queue
function filterSimilarSongs (results, state) {
  // Remove the current track
  results = results.filter(r => state.player.songId !== r.id)

  // Remove tracks that we just listened to
  results = results.filter(r => state.player.history.songs.slice(0, 10).indexOf(r.id) === -1)

  // Remove tracks already in the queue
  results = results.filter(r => state.player.automaticQueue.indexOf(r.id) === -1)

  return results
}

// Get similar songs out of the library
// First by matching album, then by matching artist and then randomly
function getSimilarLibrarySongs (song, state) {
  var results = []

  // Match album
  results = results.concat(state.songs.filter(s => s.artist === song.artist && s.album === song.album))

  // Match artist
  results = results.concat(state.songs.filter(s => s.artist === song.artist))

  // Random
  for (var i = 0; i < 6; i++) {
    results.push(state.songs[Math.floor((Math.random() * state.songs.length))])
  }

  results = filterSimilarSongs(results, state)

  // Only return 3 tracks, so we try getting similar radio songs sooner
  return results.slice(0, 3)
}

module.exports = actions
<commit_msg>Remove old initial cover load<commit_after>var engine = require('player-engine')()
var coversActions = require('./covers.js')
var shuffle = require('shuffle-array')
var musicSimilarity = require('music-similarity')

var actions = {

  // Synchronize the player engine with the loaded state
  PLAYER_SYNCHRONIZE: () => {
    return (dispatch, getState) => {
      // Bind the event listeners to the actions
      engine.on('timeupdate', duration => dispatch(actions.PLAYER_CURRENT_DURATION(duration)))
      engine.on('ended', () => dispatch(actions.PLAYBACK_NEXT()))

      // Get the current state (just after loading)
      const state = getState()

      // Set the old volume and reset playing status
      actions.PLAYER_SET_VOLUME(state.player.volume)
      actions.PLAYER_SET_PLAYING(false, true)(dispatch, getState)

      // Get the filename of the last played song
      const song = getSong(state.player.songId, state)
      const filename = song ? song.filename : false
      if (!filename) {
        // Old file is not here anymore :(
        dispatch(actions.PLAYER_CURRENT_DURATION(0))
      } else {
        // Old file is still here, let's load it! :)
        engine.load(filename)
        engine.seek(state.player.currentDuration)
      }
    }
  },

  // Set the player engine to play a song
  PLAYER_SET_SONG: (id, ignoreHistory = false) => {
    return (dispatch, getState) => {
      var state = getState()

      // Don't write multiple times in the history if we are playing the same song
      if (state.player.songId === id) {
        ignoreHistory = true
      }

      // Get the song and the filename
      const song = getSong(id, state)
      const filename = song ? song.filename : false

      // Load and play the file in the engine
      engine.load(filename)
      actions.PLAYER_SET_PLAYING(true)(dispatch, getState)

      // Try and load an cover art
      coversActions.GET_COVER(song.album, song.artist, song.coverId)(dispatch, getState)

      // Update the view
      dispatch({
        type: 'PLAYER_SET_SONG',
        id,
        ignoreHistory
      })
    }
  },

  // Set the playing status to true or false
  PLAYER_SET_PLAYING: (playing, initialization) => {
    return (dispatch, getState) => {
      if (!initialization) {
        actions.FIX_TRACKS_FOR_ANDRIOD()(dispatch, getState)
      }

      (playing) ? engine.play() : engine.pause()
      dispatch({type: 'PLAYER_SET_PLAYING', playing})
    }
  },

  // Map through all songs that don't have a duration, play them with 0 volume
  // and remove them again after we grabbed the duration. This works around
  // andriod restrictions (play() is only ok if the user presses it) by hooking
  // into the user touching the screen for playback
  // Reference: https://github.com/peermusic/app/issues/6
  FIX_TRACKS_FOR_ANDRIOD: () => {
    return (dispatch, getState) => {
      const state = getState()
      const brokenSongs = state.songs.filter(s => s.duration === 0)
      if (brokenSongs.length === 0) {
        return
      }

      brokenSongs.map(song => {
        (song => {
          let triggered = false
          let audio = document.createElement('audio')
          audio.src = song.filename
          audio.volume = 0
          audio.play()
          audio.addEventListener('timeupdate', () => {
            if (triggered) { return }
            triggered = true
            audio.pause()
            dispatch({type: 'SET_SONG_DURATION', id: song.id, duration: audio.duration})
          })
        })(song)
      })
    }
  },

  // Seek to a position in the player
  PLAYER_SEEK: (duration) => {
    engine.seek(duration)
    return {
      type: 'PLAYER_CURRENT_DURATION',
      duration
    }
  },

  // Set the volume ofo the player
  PLAYER_SET_VOLUME: (volume) => {
    engine.volume(volume)
    return {
      type: 'PLAYER_SET_VOLUME',
      volume
    }
  },

  // Update the current duration of the player
  PLAYER_CURRENT_DURATION: (currentDuration) => {
    return {
      type: 'PLAYER_CURRENT_DURATION',
      currentDuration
    }
  },

  // Play a song and queue the next songs to be played
  PLAYBACK_SONG: (songs, index, play = true, replace = true) => {
    return (dispatch, getState) => {
      const state = getState()
      var playingSong

      // Only put songs into the playback queue that we
      // hold locally, and fix up the index afterwards
      const song = songs[index]
      songs = songs.filter(x => typeof x !== 'object' || x.local)
      index = songs.indexOf(song) !== -1 ? songs.indexOf(song) : 0
      songs = songs.map(x => x.id || x)

      // We tried to play a view where we don't have local songs
      if (songs.length === 0) {
        return
      }

      if (play) {
        // Take the first song and play it
        playingSong = songs[index]
        dispatch(actions.PLAYER_SET_SONG(playingSong))
        if (songs.length > 1) {
          dispatch({type: 'SAVE_POSSIBLE_SONG_QUEUE', songs})
        }
        // Remove the played songs
        songs = [...songs.slice(0, index), ...songs.slice(index + 1)]
      }

      if (state.player.randomPlayback) {
        // Random playback, shuffle all possible songs
        shuffle(songs)
      } else {
        // Normal playback, take the songs *after* the clicked song
        songs = [...songs.slice(index)]
      }

      // Radio playback, grab similar songs to this and
      // completely ignore queueing songs since the radio will do that
      if (state.player.radioPlayback && play) {
        actions.GET_RADIO_SONGS(getSong(playingSong, state))(dispatch, getState)
        return
      }

      // Queue the rest, but *not* for single songs
      if (songs.length > 0 && replace) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs
        })
      }

      if (songs.length > 0 && !replace) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE_PUSH',
          songs
        })
      }
    }
  },

  // Set the previous song for the player out of the history
  PLAYBACK_BACK: () => {
    return (dispatch, getState) => {
      const history = getState().player.history
      var id = history.songs[history.currentIndex - 1]
      dispatch(actions.PLAYER_SET_SONG(id, true))
      dispatch({
        type: 'HISTORY_BACK'
      })
    }
  },

  // Queue a song for playback
  PLAYBACK_USER_QUEUE: (id) => {
    return {
      type: 'PLAYBACK_USER_QUEUE_PUSH',
      id
    }
  },

  // Set the next song for the player
  PLAYBACK_NEXT: () => {
    return (dispatch, getState) => {
      const state = getState()

      // If we have a queue entry, it *always* takes priority
      const userQueue = state.player.userQueue
      if (userQueue.length > 0) {
        dispatch(actions.PLAYER_SET_SONG(userQueue[0]))
        dispatch({
          type: 'PLAYBACK_USER_QUEUE_POP'
        })
        return
      }

      // We have a history entry set, let's use that one.
      const history = state.player.history
      if (history.songs.length - 1 > history.currentIndex) {
        const id = history.songs[history.currentIndex + 1]
        dispatch(actions.PLAYER_SET_SONG(id, true))
        dispatch({
          type: 'HISTORY_NEXT'
        })
        return
      }

      const automaticQueue = state.player.automaticQueue

      // If we are on radio playback, make sure we have songs in the automatic queue
      if (state.player.radioPlayback && automaticQueue.length <= 15) {
        actions.GET_RADIO_SONGS(getSong(state.player.songId, state), true)(dispatch, getState)
      }

      // If we are on infinite playback, make sure that we have songs added to the automatic queue
      if (!state.player.radioPlayback && state.player.repeatPlayback && automaticQueue.length <= 15) {
        const index = !state.player.randomPlayback ? 0 : Math.floor(Math.random() * state.player.possibleQueue.length)
        actions.PLAYBACK_SONG(state.player.possibleQueue, index, false, false)(dispatch, getState)
      }

      // Take the automatically queued songs
      // (e.g. songs of a album or the radio songs)
      if (automaticQueue.length > 0) {
        dispatch(actions.PLAYER_SET_SONG(automaticQueue[0]))
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE_POP'
        })
        return
      }

      // Nothing set, let's pause on the last track
      console.log('Queue empty')
      actions.PLAYER_SET_PLAYING(false)(dispatch, getState)
      dispatch(actions.PLAYER_SEEK(0))
    }
  },

  // Toggle random playback
  TOGGLE_RANDOM_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      if (!state.player.randomPlayback) {
        // We were *not* on shuffle before, shuffle all songs in the queue
        dispatch({type: 'SHUFFLE_AUTOMATIC_QUEUE'})
      } else {
        // Unshuffle the songs by taking the index in the original queue and then adding
        // back the songs *after* that index
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: [...state.player.possibleQueue.slice(index + 1)]
        })
      }

      dispatch({type: 'TOGGLE_RANDOM_PLAYBACK'})
    }
  },

  // Toggle repeat playback
  TOGGLE_REPEAT_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      // Unqueue all songs that were on repeat
      if (state.player.repeatPlayback) {
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        const songs = [...state.player.possibleQueue.slice(index + 1)]
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: state.player.randomPlayback ? shuffle(songs, {copy: true}) : songs
        })
      }

      dispatch({type: 'TOGGLE_REPEAT_PLAYBACK'})
    }
  },

  // Toggle radio playback
  TOGGLE_RADIO_PLAYBACK: () => {
    return (dispatch, getState) => {
      const state = getState()

      // Toggle the view state
      dispatch({type: 'TOGGLE_RADIO_PLAYBACK'})

      // Undo the radio playback and just take the songs we were going to play anyway
      if (state.player.radioPlayback) {
        const index = state.player.possibleQueue.indexOf(state.player.songId)
        const songs = [...state.player.possibleQueue.slice(index + 1)]
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: state.player.randomPlayback ? shuffle(songs, {copy: true}) : songs
        })
        return
      }

      // Grab radio stuff for the current song
      const currentSong = getSong(state.player.songId, state)
      if (!currentSong) {
        return
      }

      actions.GET_RADIO_SONGS(currentSong)(dispatch, getState)
    }
  },

  // Get radio songs
  GET_RADIO_SONGS: (song, push = false) => {
    return (dispatch, getState) => {
      const state = getState()
      const metadata = {
        title: song.title,
        album: song.album,
        artist: song.artist,
        genre: song.genre
      }

      if (!push) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: []
        })
      }

      require('./sync.js').REQUEST_SIMILAR(song)

      // Get a similar track by metadata
      musicSimilarity(state.scrapingServers, metadata, function (list) {
        actions.SET_RADIO_SONGS(list, metadata, push)(dispatch, getState)
      })
    }
  },

  SET_RADIO_SONGS: (list, song, push = false) => {
    return (dispatch, getState) => {
      const state = getState()

      // Only get the songs that are currently available in the local library
      list = state.songs.filter(song => {
        return list.filter(s => s.artist === song.artist &&
            s.album === song.album &&
            s.title === song.title
          ).length > 0
      })

      // Remove songs that the user listened to recently
      list = filterSimilarSongs(list, state)

      // If we got no songs, fill it with songs out of the library
      // first by matching album, then by matching artist and then randomly
      if (list.length === 0) {
        list = getSimilarLibrarySongs(song, state)
      }

      // Set the playback queue with the radio songs
      if (!push) {
        dispatch({
          type: 'PLAYBACK_AUTOMATIC_QUEUE',
          songs: list.map(s => s.id)
        })
        return
      }

      dispatch({
        type: 'PLAYBACK_AUTOMATIC_QUEUE_PUSH',
        songs: list.map(s => s.id)
      })
    }
  }

}

// Get the song off the state based on a song id
function getSong (songId, state) {
  return state.songs.filter(x => x.id === songId)[0]
}

// Remove all songs that are in the history or queue
function filterSimilarSongs (results, state) {
  // Remove the current track
  results = results.filter(r => state.player.songId !== r.id)

  // Remove tracks that we just listened to
  results = results.filter(r => state.player.history.songs.slice(0, 10).indexOf(r.id) === -1)

  // Remove tracks already in the queue
  results = results.filter(r => state.player.automaticQueue.indexOf(r.id) === -1)

  return results
}

// Get similar songs out of the library
// First by matching album, then by matching artist and then randomly
function getSimilarLibrarySongs (song, state) {
  var results = []

  // Match album
  results = results.concat(state.songs.filter(s => s.artist === song.artist && s.album === song.album))

  // Match artist
  results = results.concat(state.songs.filter(s => s.artist === song.artist))

  // Random
  for (var i = 0; i < 6; i++) {
    results.push(state.songs[Math.floor((Math.random() * state.songs.length))])
  }

  results = filterSimilarSongs(results, state)

  // Only return 3 tracks, so we try getting similar radio songs sooner
  return results.slice(0, 3)
}

module.exports = actions
<|endoftext|>


<commit_before>var Mapeditor = {
	config: {
		urls: {
			backend: 'ajax/',
			sprites: 'http://cdn.otworlds.com/sprites/%sprite%.gif'
		}
	},
	internals: {

		/**
		 * Queue a tile for batch download
		 */
		downloadTile: function(posx, posy, posz){
			if(posz === undefined) posz = Mapeditor.map.currentFloor;
			Mapeditor.internals.tileAreaQueue.push(posx+','+posy+','+posz);
			Mapeditor.internals.downloadTileArea();
		},
		tileAreaQueue: [],
		/**
		 * Helper to avoid flooding server with requests
		 */
		createDebouncer: function(func, wait, immediate) {
			var timeout;
			return function() {
				var context = this, args = arguments;
				clearTimeout(timeout);
				timeout = setTimeout(function() {
					timeout = null;
					if (!immediate) func.apply(context, args);
				}, wait);
				if (immediate && !timeout) func.apply(context, args);
			};
		},
		figureOutTile: function(e){
			var $source = jQuery(e.currentTarget);
			if($source.hasClass("item")){
				var $parent = $source.parent();
				var posx = $parent.attr("col");
				var posy = $parent.attr("row");
				if(e.offsetY <= 32){
					if(e.offsetX <= 32){
						return jQuery(".tile[col="+ (posx-1) +"][row="+ (posy-1) +"]");
					}else{
						return jQuery(".tile[col="+ posx +"][row="+ (posy-1) +"]");
					}
				}else{
					if(e.offsetX <= 32){
						return jQuery(".tile[col="+ (posx-1) +"][row="+ posy +"]");
					}else{
						return $parent;
					}
				}
			}else{
				return $source;
			}
		}
	},
	load: function(id){
		//Load the map and fire up the grid
		jQuery.ajax(Mapeditor.config.urls.backend, {
			dataType: "json",
			data: {
				'action' : 'init',
				'map' : id
			},
			success: function(data){

				Mapeditor.map.meta.id = data.id;
				Mapeditor.map.meta.name = data.name;
				Mapeditor.map.meta.description = data.description;
				Mapeditor.map.meta.width = data.width;
				Mapeditor.map.meta.height = data.height;

				document.title = Mapeditor.map.meta.name + ' - ' + document.title;

				Mapeditor.Materials.load('xml/materials.xml', function(){
					Mapeditor.internals.infinitedrag = jQuery.infinitedrag("#canvas", {cursor: false},
						{
							width: 32,
							height: 32,
							range_col: [0, Mapeditor.map.meta.width],
							range_row: [0, Mapeditor.map.meta.height],
							start_col: (Mapeditor.map.meta.width/2)-(Math.round((jQuery(window).width() / 32) / 2)),
							start_row: (Mapeditor.map.meta.height/2) - (Math.round((jQuery(window).height() / 32) / 2)),
							class_name: 'tile',
							oncreate: function($element, col, row) {
								var tileObject = Object.create(Mapeditor.Tile);
								tileObject.load(col, row, Mapeditor.map.currentFloor, $element);
							}
						}
					);
				});
			}
		});
	},
	map: {
		currentFloor: 7,
		meta: {
		},
		//Z axis, then X axis, then Y axis, just like OTBM by Remere's
		_7 : {
			_100 : {
				_100: {
					itemid: 4526,
					items: [
						{
							itemid: 1337
						}
					]
				}
			}
		},
		/**
		 * Save a tile to cache
		 */
		cacheTile: function(posx, posy, posz, tile){
			if(Mapeditor.map["_"+posz] === undefined) Mapeditor.map["_"+posz] = {};
			if(Mapeditor.map["_"+posz]["_"+posx] === undefined) Mapeditor.map["_"+posz]["_"+posx] = {};
			Mapeditor.map["_"+posz]["_"+posx]["_"+posy] = tile;
		},
		getTile: function(posx, posy, posz){
			//assume Tile is cached
			var fetchRemote = false;

			if(Mapeditor.map["_"+posz] === undefined){
				fetchRemote = true;
			}else if(Mapeditor.map["_"+posz]["_"+posx] === undefined){
				fetchRemote = true;
			}else{
				if(Mapeditor.map["_"+posz]["_"+posx]["_"+posy] === undefined) fetchRemote = true;
			}

			//now, do we need to ask the server or not?
			if(fetchRemote){
				Mapeditor.internals.downloadTile(posx, posy, posz);
			}else{
				//TODO: this should not be inside an 'else' statement, because downloadTileArea() should guarantee that the tile is set, unless something epic happens :(
				return Mapeditor.map["_"+posz]["_"+posx]["_"+posy];
			}
		}
	},
	isEditing: false,
	toggleEdit: function(){
		$canvas.toggleClass('editing');
		Mapeditor.isEditing = !Mapeditor.isEditing;
		Mapeditor.internals.infinitedrag.disabled(Mapeditor.isEditing);
		console.log('Toggling canvasmode. Now '+(Mapeditor.isEditing ? 'editing' : 'viewing'));
	}
};

/**
 * Ask the server for a tile area
 */
Mapeditor.internals.downloadTileArea = Mapeditor.internals.createDebouncer(
	function(){
		console.log('Requesting '+Mapeditor.internals.tileAreaQueue.length+' tiles'+(Mapeditor.internals.tileAreaQueue.length > 500 ? ' in chunks of 500' : '')+'.');

		//Split requests into chunks of 500 tiles at a time
		var limitedArray = [];
		while (Mapeditor.internals.tileAreaQueue.length) {
			limitedArray = Mapeditor.internals.tileAreaQueue.splice(0, 500);

			//TODO: need to implement some kind of caching of non-existing tiles before enabling this: the first request is 86~ KB HTTP-POST data
			jQuery.ajax(Mapeditor.config.urls.backend, {
				dataType: "json",
				type: "POST",
				data: {
					'action' : 'loadtiles',
					'map' : Mapeditor.map.meta.id,
					'tiles' : limitedArray
				},
				success: function(data){
					//add tiles to cache
					jQuery.each(data.tiles, function(posz, zValue){
						jQuery.each(zValue, function(posx, xValue){
							jQuery.each(xValue, function(posy, tileValue){
								Mapeditor.map.cacheTile(posx, posy, posz, tileValue);
								//TODO: need to refresh the tiles somehow
								var $tile = jQuery('.tile[col='+posx+'][row='+posy+']');
								Mapeditor.Tile.load(posx, posy, posz, $tile);
								//jQuery('.tile[col='+posx+'][row='+posy+']').getTile().setItemid(tileValue.itemid);
							});
						});
					});
				}
			});
			limitedArray = [];
		}

		//Clear the queue as soon as the requests has been sent
		Mapeditor.internals.tileAreaQueue = [];
	},
	500
);

jQuery.fn.getTile = function(){
	var $this = this;
	return $this.data("tile");
}

Mapeditor.Tile = function(){};
Mapeditor.Tile.addItem = function(item){
	item.$element.appendTo(this.$element);
}
Mapeditor.Tile.load = function(posx, posy, posz, $element){
	this.$element = $element;

	var tileData = Mapeditor.map.getTile(posx, posy, posz);
	if(tileData){
		this.refresh(tileData);
	}

	this.$element.data("tile", this);
}
Mapeditor.Tile.refresh = function(tileData){
	var _this = this;
	if(tileData === undefined) tileData = _this;

	if(tileData.itemid) this.setItemid(tileData.itemid);
	if(tileData.items){
		jQuery.each(tileData.items, function(itemIndex, itemValue){
			var $child = jQuery('<div />');
			$child.addClass('item');
			$child.css("background-image", "url("+ Mapeditor.config.urls.sprites.replace('%sprite%', itemValue.itemid) +")");
			$child.appendTo(_this.$element);
		});
	}
}
Mapeditor.Tile.setItemid = function(newItemid){
	this.itemid = newItemid;
	this.$element.css("background-image", "url("+ Mapeditor.config.urls.sprites.replace('%sprite%', newItemid) +")");
}

Mapeditor.Item = function(){};
//TODO: add property handling for things such as ActionID and count<commit_msg>Store position on the tile object<commit_after>var Mapeditor = {
	config: {
		urls: {
			backend: 'ajax/',
			sprites: 'http://cdn.otworlds.com/sprites/%sprite%.gif'
		}
	},
	internals: {

		/**
		 * Queue a tile for batch download
		 */
		downloadTile: function(posx, posy, posz){
			if(posz === undefined) posz = Mapeditor.map.currentFloor;
			Mapeditor.internals.tileAreaQueue.push(posx+','+posy+','+posz);
			Mapeditor.internals.downloadTileArea();
		},
		tileAreaQueue: [],
		/**
		 * Helper to avoid flooding server with requests
		 */
		createDebouncer: function(func, wait, immediate) {
			var timeout;
			return function() {
				var context = this, args = arguments;
				clearTimeout(timeout);
				timeout = setTimeout(function() {
					timeout = null;
					if (!immediate) func.apply(context, args);
				}, wait);
				if (immediate && !timeout) func.apply(context, args);
			};
		},
		figureOutTile: function(e){
			var $source = jQuery(e.currentTarget);
			if($source.hasClass("item")){
				var $parent = $source.parent();
				var posx = $parent.attr("col");
				var posy = $parent.attr("row");
				if(e.offsetY <= 32){
					if(e.offsetX <= 32){
						return jQuery(".tile[col="+ (posx-1) +"][row="+ (posy-1) +"]");
					}else{
						return jQuery(".tile[col="+ posx +"][row="+ (posy-1) +"]");
					}
				}else{
					if(e.offsetX <= 32){
						return jQuery(".tile[col="+ (posx-1) +"][row="+ posy +"]");
					}else{
						return $parent;
					}
				}
			}else{
				return $source;
			}
		}
	},
	load: function(id){
		//Load the map and fire up the grid
		jQuery.ajax(Mapeditor.config.urls.backend, {
			dataType: "json",
			data: {
				'action' : 'init',
				'map' : id
			},
			success: function(data){

				Mapeditor.map.meta.id = data.id;
				Mapeditor.map.meta.name = data.name;
				Mapeditor.map.meta.description = data.description;
				Mapeditor.map.meta.width = data.width;
				Mapeditor.map.meta.height = data.height;

				document.title = Mapeditor.map.meta.name + ' - ' + document.title;

				Mapeditor.Materials.load('xml/materials.xml', function(){
					Mapeditor.internals.infinitedrag = jQuery.infinitedrag("#canvas", {cursor: false},
						{
							width: 32,
							height: 32,
							range_col: [0, Mapeditor.map.meta.width],
							range_row: [0, Mapeditor.map.meta.height],
							start_col: (Mapeditor.map.meta.width/2)-(Math.round((jQuery(window).width() / 32) / 2)),
							start_row: (Mapeditor.map.meta.height/2) - (Math.round((jQuery(window).height() / 32) / 2)),
							class_name: 'tile',
							oncreate: function($element, col, row) {
								var tileObject = Object.create(Mapeditor.Tile);
								tileObject.load(col, row, Mapeditor.map.currentFloor, $element);
							}
						}
					);
				});
			}
		});
	},
	map: {
		currentFloor: 7,
		meta: {
		},
		//Z axis, then X axis, then Y axis, just like OTBM by Remere's
		_7 : {
			_100 : {
				_100: {
					itemid: 4526,
					items: [
						{
							itemid: 1337
						}
					]
				}
			}
		},
		/**
		 * Save a tile to cache
		 */
		cacheTile: function(posx, posy, posz, tile){
			if(Mapeditor.map["_"+posz] === undefined) Mapeditor.map["_"+posz] = {};
			if(Mapeditor.map["_"+posz]["_"+posx] === undefined) Mapeditor.map["_"+posz]["_"+posx] = {};
			Mapeditor.map["_"+posz]["_"+posx]["_"+posy] = tile;
		},
		getTile: function(posx, posy, posz){
			//assume Tile is cached
			var fetchRemote = false;

			if(Mapeditor.map["_"+posz] === undefined){
				fetchRemote = true;
			}else if(Mapeditor.map["_"+posz]["_"+posx] === undefined){
				fetchRemote = true;
			}else{
				if(Mapeditor.map["_"+posz]["_"+posx]["_"+posy] === undefined) fetchRemote = true;
			}

			//now, do we need to ask the server or not?
			if(fetchRemote){
				Mapeditor.internals.downloadTile(posx, posy, posz);
			}else{
				//TODO: this should not be inside an 'else' statement, because downloadTileArea() should guarantee that the tile is set, unless something epic happens :(
				return Mapeditor.map["_"+posz]["_"+posx]["_"+posy];
			}
		}
	},
	isEditing: false,
	toggleEdit: function(){
		$canvas.toggleClass('editing');
		Mapeditor.isEditing = !Mapeditor.isEditing;
		Mapeditor.internals.infinitedrag.disabled(Mapeditor.isEditing);
		console.log('Toggling canvasmode. Now '+(Mapeditor.isEditing ? 'editing' : 'viewing'));
	}
};

/**
 * Ask the server for a tile area
 */
Mapeditor.internals.downloadTileArea = Mapeditor.internals.createDebouncer(
	function(){
		console.log('Requesting '+Mapeditor.internals.tileAreaQueue.length+' tiles'+(Mapeditor.internals.tileAreaQueue.length > 500 ? ' in chunks of 500' : '')+'.');

		//Split requests into chunks of 500 tiles at a time
		var limitedArray = [];
		while (Mapeditor.internals.tileAreaQueue.length) {
			limitedArray = Mapeditor.internals.tileAreaQueue.splice(0, 500);

			//TODO: need to implement some kind of caching of non-existing tiles before enabling this: the first request is 86~ KB HTTP-POST data
			jQuery.ajax(Mapeditor.config.urls.backend, {
				dataType: "json",
				type: "POST",
				data: {
					'action' : 'loadtiles',
					'map' : Mapeditor.map.meta.id,
					'tiles' : limitedArray
				},
				success: function(data){
					//add tiles to cache
					jQuery.each(data.tiles, function(posz, zValue){
						jQuery.each(zValue, function(posx, xValue){
							jQuery.each(xValue, function(posy, tileValue){
								Mapeditor.map.cacheTile(posx, posy, posz, tileValue);
								//TODO: need to refresh the tiles somehow
								var $tile = jQuery('.tile[col='+posx+'][row='+posy+']');
								Mapeditor.Tile.load(posx, posy, posz, $tile);
								//jQuery('.tile[col='+posx+'][row='+posy+']').getTile().setItemid(tileValue.itemid);
							});
						});
					});
				}
			});
			limitedArray = [];
		}

		//Clear the queue as soon as the requests has been sent
		Mapeditor.internals.tileAreaQueue = [];
	},
	500
);

jQuery.fn.getTile = function(){
	var $this = this;
	return $this.data("tile");
}

Mapeditor.Tile = function(){};
Mapeditor.Tile.addItem = function(item){
	item.$element.appendTo(this.$element);
}
Mapeditor.Tile.load = function(posx, posy, posz, $element){
	this.$element = $element;

	var tileData = Mapeditor.map.getTile(posx, posy, posz);
	if(tileData){
		this.refresh(tileData);
	}
	this.x = posx;
	this.y = posy;
	this.z = posz;

	this.$element.data("tile", this);
}
Mapeditor.Tile.refresh = function(tileData){
	var _this = this;
	if(tileData === undefined) tileData = _this;

	if(tileData.itemid) this.setItemid(tileData.itemid);
	if(tileData.items){
		jQuery.each(tileData.items, function(itemIndex, itemValue){
			var $child = jQuery('<div />');
			$child.addClass('item');
			$child.css("background-image", "url("+ Mapeditor.config.urls.sprites.replace('%sprite%', itemValue.itemid) +")");
			$child.appendTo(_this.$element);
		});
	}
}
Mapeditor.Tile.setItemid = function(newItemid){
	this.itemid = newItemid;
	this.$element.css("background-image", "url("+ Mapeditor.config.urls.sprites.replace('%sprite%', newItemid) +")");
}

Mapeditor.Item = function(){};
//TODO: add property handling for things such as ActionID and count<|endoftext|>


<commit_before>// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
//   wallet_clean :  True, iff the wallet has been updated.
//   wallet_dirty :  True, iff the wallet needs to be updated.
//   balance:        The current stamp balance.
//   balance_proposed
//

// var network = require("./network.js");

var EventEmitter       = require('events').EventEmitter;
var util               = require('util');
var extend             = require('extend');

var Amount             = require('./amount').Amount;
var UInt160            = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;

/**
 * @constructor Account
 * @param {Remote} remote
 * @param {String} account
 */

function Account(remote, account) {
  EventEmitter.call(this);

  var self = this;

  this._remote     = remote;
  this._account    = UInt160.from_json(account);
  this._account_id = this._account.to_json();
  this._subs       = 0;

  // Ledger entry object
  // Important: This must never be overwritten, only extend()-ed
  this._entry = { };

  function listenerAdded(type, listener) {
    if (~Account.subscribeEvents.indexOf(type)) {
      if (!self._subs && self._remote._connected) {
        self._remote.request_subscribe()
        .add_account(self._account_id)
        .broadcast();
      }
      self._subs += 1;
    }
  };

  this.on('newListener', listenerAdded);

  function listenerRemoved(type, listener) {
    if (~Account.subscribeEvents.indexOf(type)) {
      self._subs -= 1;
      if (!self._subs && self._remote._connected) {
        self._remote.request_unsubscribe()
        .add_account(self._account_id)
        .broadcast();
      }
    }
  };

  this.on('removeListener', listenerRemoved);

  function attachAccount(request) {
    if (self._account.is_valid() && self._subs) {
      request.add_account(self._account_id);
    }
  };

  this._remote.on('prepare_subscribe', attachAccount);

  function handleTransaction(transaction) {
    var changed = false;

    transaction.mmeta.each(function(an) {
      var isAccount = an.fields.Account === self._account_id;
      var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');

      if (isAccountRoot) {
        extend(self._entry, an.fieldsNew, an.fieldsFinal);
        changed = true;
      }
    });

    if (changed) {
      self.emit('entry', self._entry);
    }
  };

  this.on('transaction', handleTransaction);

  this._transactionManager = new TransactionManager(this);

  return this;
};

util.inherits(Account, EventEmitter);

/**
 * List of events that require a remote subscription to the account.
 */

Account.subscribeEvents = [ 'transaction', 'entry' ];

Account.prototype.to_json = function () {
  return this._account.to_json();
};

/**
 * Whether the AccountId is valid.
 *
 * Note: This does not tell you whether the account exists in the ledger.
 */

Account.prototype.is_valid = function () {
  return this._account.is_valid();
};

/**
 * Request account info
 *
 * @param {Function} callback
 */

Account.prototype.getInfo = function(callback) {
  return this._remote.request_account_info(this._account_id, callback);
};

/**
 * Retrieve the current AccountRoot entry.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "entry" event.
 *
 * @param {Function} callback
 */

Account.prototype.entry = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountInfo(err, info) {
    if (err) {
      callback(err);
    } else {
      extend(self._entry, info.account_data);
      self.emit('entry', self._entry);
      callback(null, info);
    }
  };

  this.getInfo(accountInfo);

  return this;
};

Account.prototype.getNextSequence = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountInfo(err, info) {
    if (err) {
      callback(err);
    } else {
      callback(null, info.account_data.Sequence);
    }
  };

  this.getInfo(accountInfo);

  return this;
};

/**
 * Retrieve this account's Ripple trust lines.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "lines" event. (Not yet implemented.)
 *
 * @param {function (err, lines)} callback Called with the result
 */

Account.prototype.lines = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountLines(err, res) {
    if (err) {
      callback(err);
    } else {
      self._lines = res.lines;
      self.emit('lines', self._lines);
      callback(null, res);
    }
  }

  this._remote.requestAccountLines(this._account_id, accountLines);

  return this;
};

/**
 * Notify object of a relevant transaction.
 *
 * This is only meant to be called by the Remote class. You should never have to
 * call this yourself.
 *
 * @param {Object} message
 */

Account.prototype.notify =
Account.prototype.notifyTx = function (transaction) {
  // Only trigger the event if the account object is actually
  // subscribed - this prevents some weird phantom events from
  // occurring.
  if (this._subs) {
    this.emit('transaction', transaction);
    var account = transaction.transaction.Account;
    if (!account) return;
    if (account === this._account_id) {
      this.emit('transaction-outbound', transaction);
    } else {
      this.emit('transaction-inbound', transaction);
    }
  }
};

/**
 * Submit a transaction to an account's
 * transaction manager
 *
 * @param {Transaction} transaction
 */

Account.prototype.submit = function(transaction) {
  this._transactionManager.submit(transaction);
};

exports.Account = Account;

// vim:sw=2:sts=2:ts=8:et
<commit_msg>Add Account.prototype.line. Retrieve this account's single trust line.<commit_after>// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
//   wallet_clean :  True, iff the wallet has been updated.
//   wallet_dirty :  True, iff the wallet needs to be updated.
//   balance:        The current stamp balance.
//   balance_proposed
//

// var network = require("./network.js");

var EventEmitter       = require('events').EventEmitter;
var util               = require('util');
var extend             = require('extend');

var Amount             = require('./amount').Amount;
var UInt160            = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;

/**
 * @constructor Account
 * @param {Remote} remote
 * @param {String} account
 */

function Account(remote, account) {
  EventEmitter.call(this);

  var self = this;

  this._remote     = remote;
  this._account    = UInt160.from_json(account);
  this._account_id = this._account.to_json();
  this._subs       = 0;

  // Ledger entry object
  // Important: This must never be overwritten, only extend()-ed
  this._entry = { };

  function listenerAdded(type, listener) {
    if (~Account.subscribeEvents.indexOf(type)) {
      if (!self._subs && self._remote._connected) {
        self._remote.request_subscribe()
        .add_account(self._account_id)
        .broadcast();
      }
      self._subs += 1;
    }
  };

  this.on('newListener', listenerAdded);

  function listenerRemoved(type, listener) {
    if (~Account.subscribeEvents.indexOf(type)) {
      self._subs -= 1;
      if (!self._subs && self._remote._connected) {
        self._remote.request_unsubscribe()
        .add_account(self._account_id)
        .broadcast();
      }
    }
  };

  this.on('removeListener', listenerRemoved);

  function attachAccount(request) {
    if (self._account.is_valid() && self._subs) {
      request.add_account(self._account_id);
    }
  };

  this._remote.on('prepare_subscribe', attachAccount);

  function handleTransaction(transaction) {
    var changed = false;

    transaction.mmeta.each(function(an) {
      var isAccount = an.fields.Account === self._account_id;
      var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');

      if (isAccountRoot) {
        extend(self._entry, an.fieldsNew, an.fieldsFinal);
        changed = true;
      }
    });

    if (changed) {
      self.emit('entry', self._entry);
    }
  };

  this.on('transaction', handleTransaction);

  this._transactionManager = new TransactionManager(this);

  return this;
};

util.inherits(Account, EventEmitter);

/**
 * List of events that require a remote subscription to the account.
 */

Account.subscribeEvents = [ 'transaction', 'entry' ];

Account.prototype.to_json = function () {
  return this._account.to_json();
};

/**
 * Whether the AccountId is valid.
 *
 * Note: This does not tell you whether the account exists in the ledger.
 */

Account.prototype.is_valid = function () {
  return this._account.is_valid();
};

/**
 * Request account info
 *
 * @param {Function} callback
 */

Account.prototype.getInfo = function(callback) {
  return this._remote.request_account_info(this._account_id, callback);
};

/**
 * Retrieve the current AccountRoot entry.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "entry" event.
 *
 * @param {Function} callback
 */

Account.prototype.entry = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountInfo(err, info) {
    if (err) {
      callback(err);
    } else {
      extend(self._entry, info.account_data);
      self.emit('entry', self._entry);
      callback(null, info);
    }
  };

  this.getInfo(accountInfo);

  return this;
};

Account.prototype.getNextSequence = function(callback) {
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountInfo(err, info) {
    if (err) {
      callback(err);
    } else {
      callback(null, info.account_data.Sequence);
    }
  };

  this.getInfo(accountInfo);

  return this;
};

/**
 * Retrieve this account's Ripple trust lines.
 *
 * To keep up-to-date with changes to the AccountRoot entry, subscribe to the
 * "lines" event. (Not yet implemented.)
 *
 * @param {function (err, lines)} callback Called with the result
 */

Account.prototype.lines = function (callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  function accountLines(err, res) {
    if (err) {
      callback(err);
    } else {
      self._lines = res.lines;
      self.emit('lines', self._lines);
      callback(null, res);
    }
  }

  this._remote.requestAccountLines(this._account_id, accountLines);

  return this;
};

/**
 * Retrieve this account's single trust line.
 *
 * @param {string} currency Currency
 * @param {string} address Ripple address
 * @param {function (err, line)} callback Called with the result
 * @returns {Account}
 */

Account.prototype.line = function (currency,address,callback) {
  var self = this;
  var callback = typeof callback === 'function' ? callback : function(){};

  self.lines(function(err,data){
    data.lines.forEach(function(line){
      if (address === line.account && currency === line.currency) {
        callback(null,line);
      }
    });

    callback();
  });

  return this;
};

/**
 * Notify object of a relevant transaction.
 *
 * This is only meant to be called by the Remote class. You should never have to
 * call this yourself.
 *
 * @param {Object} message
 */

Account.prototype.notify =
Account.prototype.notifyTx = function (transaction) {
  // Only trigger the event if the account object is actually
  // subscribed - this prevents some weird phantom events from
  // occurring.
  if (this._subs) {
    this.emit('transaction', transaction);
    var account = transaction.transaction.Account;
    if (!account) return;
    if (account === this._account_id) {
      this.emit('transaction-outbound', transaction);
    } else {
      this.emit('transaction-inbound', transaction);
    }
  }
};

/**
 * Submit a transaction to an account's
 * transaction manager
 *
 * @param {Transaction} transaction
 */

Account.prototype.submit = function(transaction) {
  this._transactionManager.submit(transaction);
};

exports.Account = Account;

// vim:sw=2:sts=2:ts=8:et
<|endoftext|>


<commit_before>import V3Adapter from 'travis/adapters/v3';

export default V3Adapter.extend({
  includes: 'build.commit,build.branch,build.request',

  pathPrefix(modelName, id, snapshot, type, query) {
    if (type === 'query' && query.repository_id) {
      return `repo/${query.repository_id}`;
    }
  },

  buildURL(modelName, id, snapshot, requestType, query) {
    if (requestType == 'queryRecord' && query.id) {
      let id = query.id;
      delete query.id;
      return this._super(modelName, id, snapshot, 'findRecord', query);
    } else {
      return this._super(...arguments);
    }
  }
});
<commit_msg>Add skip_count for builds (#1687)<commit_after>import V3Adapter from 'travis/adapters/v3';

export default V3Adapter.extend({
  includes: 'build.commit,build.branch,build.request',

  pathPrefix(modelName, id, snapshot, type, query) {
    if (type === 'query' && query.repository_id) {
      return `repo/${query.repository_id}`;
    }
  },

  buildURL(modelName, id, snapshot, requestType, query) {
    if (requestType == 'queryRecord' && query.id) {
      let id = query.id;
      delete query.id;
      return this._super(modelName, id, snapshot, 'findRecord', query);
    } else {
      // This tells the API to skip page count for pagination, speeding up queries.
      if (query) {
        query.skip_count = true;
      }
      return this._super(...arguments);
    }
  }
});
<|endoftext|>


<commit_before>var html = '

      Select batch for publishing<select></select>
<input id="td11_unpublish_all" type="button" value="Unpublish">    <input id="td11_publish_all" type="button" value="Publish All">    <input id="td11_clear_all" type="button" value="Clear All">

';
var container = document.createElement("div");
container.innerHTML = html;
console.log(document);

document.body.appendChild(container)<commit_msg>Updated UI<commit_after>var html = '

name	type	Date

Select batch for publishing<select></select>
<input id="td11_unpublish_all" type="button" value="Unpublish"><input id="td11_publish_all" type="button" value="Publish All"><input id="td11_clear_all" type="button" value="Clear All">

';
var container = document.createElement("div");
container.innerHTML = html;
document.body.appendChild(container)<|endoftext|>


<commit_before>import React, { Component, StyleSheet, View, Dimensions, AlertIOS, Text, Image  } from 'react-native';
import Button from 'react-native-button';
import MapView from 'react-native-maps';
import _ from 'underscore';
import baseImg from '../assets/redPin.png';
import targetImg from '../assets/blackPin.png';
import { PinCallout } from './PinCallout';
import PinEditButton from './PinEditButton';
import { myCurrLoc, currLoc } from '../lib/db/db';

export default class Map extends Component {
  constructor(props) {
    super(props);
    this.state = {
      position: null,
      selectedPin: undefined,
      dropPinLocation: undefined,
      loaded: false,
      friendLocs: {},
    };
  }
// Friend
  componentWillMount() {
    const { friends } = this.props;
    let self = this;
    let counter = 0;
     for(var friendId in friends) {
      self.setListener(friends[friendId]);
      counter++;
      if(counter === Object.keys(friends).length) {
        this.setState({loaded: true});
      }
    }
  }

  setListener(friend) {
    let self = this;
    currLoc.child(friend.id).on("value", function(snap) {
      console.log('jake is moving')
      self.state.friendLocs[friend.id] = snap.val();
    });
  }

  renderFriends() {
    // renders friends current locations
    const { friends } = this.props;
    let copy = this.state.friendLocs;
    return _.map(copy, (coords, id) => {
        return (
        <MapView.Marker
          coordinate={coords}
          key={id}
          image={{uri: friends[id].picture}}
          style={styles.icon}
        />

        )
      });
  }

  onRegionChange(region) {
    this.setState({ position: region });
  }

  setPinTitle(title) {
    const { getLocationToSave, recent } = this.props;
    getLocationToSave(this.state.dropPinLocation, recent, title);
    this.setState({dropPinLocation: undefined});
  }

  dropPin(coordinate) {
    this.setState({dropPinLocation: coordinate});
    AlertIOS.prompt(
        'Drop a Pin?',
        'Enter title:',
        [{
          text: 'Cancel',
          style: 'cancel'
        },
        {
          text: 'OK',
          onPress: this.setPinTitle.bind(this)
        }],
        'plain-text'
      );
  }

  renderMarkers() {
    const { pins, targetPin } = this.props;

    return _.map(pins, (pinObject, key) => {

      let image = baseImg;
      if ( key === targetPin.id ) {
        image = targetImg;
      }
      return (
        <MapView.Marker
          key={key}
          coordinate={{latitude: pinObject.latitude, longitude: pinObject.longitude}}
          onSelect={() => this.setState({ selectedPin: pinObject })}
          onDeselect={() => this.setState({ selectedPin: undefined })}
        >
          <Image source={image} />
          <MapView.Callout tooltip>
            <PinCallout>
              <Text style={{ color: 'black', alignSelf:'center', fontSize:16 }}>{pinObject.title}</Text>
            </PinCallout>
          </MapView.Callout>

        </MapView.Marker>
      );
    });
  }

  renderEditButton() {
    const { updatePins, updateRecent, deletePin, setTarget, targetPin } = this.props;
    return (
      <View style={styles.editButton}>
        <PinEditButton
          pin={this.state.selectedPin}
          updatePins={updatePins}
          updateRecent={updateRecent}
          deletePin={deletePin}
          setTarget={setTarget}
          targetPin={targetPin}
          hideButton={() => this.setState({selectedPin: undefined})}
        />
      </View>
    )
  }

  moveMapToUser(location) {
    const {currLoc} = this.props;
    let currRegion = {
      latitude: currLoc.latitude,
      longitude: currLoc.longitude,
      latitudeDelta: 0.005,
      longitudeDelta: 0.005
    };
    this.refs.map.animateToRegion(currRegion, 250)
  }

  render() {
    // TODO: Map is re-rendering continually. Fix bug
    const { pins, getLocationToSave, currLoc, recent, friends, fullLoc } = this.props;
    return (
      <View style={styles.container}>
        <MapView
          ref="map"
          showsUserLocation={true}
          //TODO: find a better way to show map initially, added below line so it would stop zooming in from world view
          initialRegion={{ longitudeDelta: 0.005, latitude: currLoc.latitude,longitude: currLoc.longitude, latitudeDelta: 0.005 }}
          region={this.state.position}
          onRegionChange={this.onRegionChange.bind(this)}
          style={styles.map}
          showsCompass={true}
          onLongPress={ (e) => {
              let coords = e.nativeEvent.coordinate
              this.dropPin(coords);
            }
          }
        >
        { Object.keys(pins).length !== 0 ? this.renderMarkers.call(this) : void 0 }

        { this.state.loaded === true ? this.renderFriends.call(this) : void 0 }

        </MapView>
        { this.state.selectedPin ? this.renderEditButton.call(this) : void 0 }
        <View style={styles.centerButton}>
          <Button
            style={[styles.bubble, styles.button]}
            onPress={this.moveMapToUser.bind(this, fullLoc)}>
            CENTER ON ME
          </Button>
        </View>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#F5FCFF',
    height: Dimensions.get('window').height,
  },

  map: {
    height: Dimensions.get('window').height/1.15,
    margin: 10,
    borderWidth: 1,
    borderColor: '#000000',
  },

  editButton: {
    position: 'absolute',
    backgroundColor: 'transparent',
    left: Dimensions.get('window').width/2 - 75,
    bottom: 90,
  },

  centerButton: {
    position: 'absolute',
    backgroundColor: 'transparent',
    left: Dimensions.get('window').width/2 - 100,
    bottom: 40,
    borderRadius: 10,
  },

  bubble: {
    backgroundColor: 'rgba(255,255,255,0.7)',
    paddingHorizontal: 15,
    paddingVertical: 10,
    borderRadius: 10,
  },

  button: {
    width: 200,
    alignItems: 'center',
  },

  icon: {
    borderRadius: 13,
    backgroundColor: 'transparent',
  },
});

<commit_msg>cleaned up code<commit_after>import React, { Component, StyleSheet, View, Dimensions, AlertIOS, Text, Image  } from 'react-native';
import Button from 'react-native-button';
import MapView from 'react-native-maps';
import _ from 'underscore';
import baseImg from '../assets/redPin.png';
import targetImg from '../assets/blackPin.png';
import { PinCallout } from './PinCallout';
import PinEditButton from './PinEditButton';
import { myCurrLoc, currLoc } from '../lib/db/db';

export default class Map extends Component {
  constructor(props) {
    super(props);
    this.state = {
      position: null,
      selectedPin: undefined,
      dropPinLocation: undefined,
      loaded: false,
      friendLocs: {},
    };
  }
// Friend
  componentWillMount() {
    const { friends } = this.props;
    let self = this;
    let counter = 0;
     for(var friendId in friends) {
      self.setListener(friends[friendId]);
      counter++;
      if(counter === Object.keys(friends).length) {
        this.setState({loaded: true});
      }
    }
  }

  setListener(friend) {
    let self = this;
    currLoc.child(friend.id).on("value", function(snap) {
      console.log('jake is moving')
      self.state.friendLocs[friend.id] = snap.val();
    });
  }

  renderFriends() {
    // renders friends current locations
    const { friends } = this.props;
    let copy = this.state.friendLocs;
    return _.map(copy, (coords, id) => {
        return (
        <MapView.Marker
          coordinate={coords}
          key={id}
          image={{uri: friends[id].picture}}
          style={styles.icon}
        />

        )
      });
  }

  onRegionChange(region) {
    this.setState({ position: region });
  }

  setPinTitle(title) {
    const { getLocationToSave, recent } = this.props;
    getLocationToSave(this.state.dropPinLocation, recent, title);
    this.setState({dropPinLocation: undefined});
  }

  dropPin(coordinate) {
    this.setState({dropPinLocation: coordinate});
    AlertIOS.prompt(
        'Drop a Pin?',
        'Enter title:',
        [{
          text: 'Cancel',
          style: 'cancel'
        },
        {
          text: 'OK',
          onPress: this.setPinTitle.bind(this)
        }],
        'plain-text'
      );
  }

  renderMarkers() {
    const { pins, targetPin } = this.props;

    return _.map(pins, (pinObject, key) => {

      let image = baseImg;
      if ( key === targetPin.id ) {
        image = targetImg;
      }
      return (
        <MapView.Marker
          key={key}
          coordinate={{latitude: pinObject.latitude, longitude: pinObject.longitude}}
          onSelect={() => this.setState({ selectedPin: pinObject })}
          onDeselect={() => this.setState({ selectedPin: undefined })}
        >
          <Image source={image} />
          <MapView.Callout tooltip>
            <PinCallout>
              <Text style={{ color: 'black', alignSelf:'center', fontSize:16 }}>{pinObject.title}</Text>
            </PinCallout>
          </MapView.Callout>

        </MapView.Marker>
      );
    });
  }

  renderEditButton() {
    const { updatePins, updateRecent, deletePin, setTarget, targetPin } = this.props;
    return (
      <View style={styles.editButton}>
        <PinEditButton
          pin={this.state.selectedPin}
          updatePins={updatePins}
          updateRecent={updateRecent}
          deletePin={deletePin}
          setTarget={setTarget}
          targetPin={targetPin}
          hideButton={() => this.setState({selectedPin: undefined})}
        />
      </View>
    )
  }

  moveMapToUser(location) {
    const {currLoc} = this.props;
    let currRegion = {
      latitude: currLoc.latitude,
      longitude: currLoc.longitude,
      latitudeDelta: 0.005,
      longitudeDelta: 0.005
    };
    this.refs.map.animateToRegion(currRegion, 250)
  }

  render() {
    // TODO: Map is re-rendering continually. Fix bug
    const { pins, getLocationToSave, currLoc, recent, friends } = this.props;
    return (
      <View style={styles.container}>
        <MapView
          ref="map"
          showsUserLocation={true}
          //TODO: find a better way to show map initially, added below line so it would stop zooming in from world view
          initialRegion={{ longitudeDelta: 0.005, latitude: currLoc.latitude,longitude: currLoc.longitude, latitudeDelta: 0.005 }}
          region={this.state.position}
          onRegionChange={this.onRegionChange.bind(this)}
          style={styles.map}
          showsCompass={true}
          onLongPress={ (e) => {
              let coords = e.nativeEvent.coordinate
              this.dropPin(coords);
            }
          }
        >
        { Object.keys(pins).length !== 0 ? this.renderMarkers.call(this) : void 0 }

        { this.state.loaded === true ? this.renderFriends.call(this) : void 0 }

        </MapView>
        { this.state.selectedPin ? this.renderEditButton.call(this) : void 0 }
        <View style={styles.centerButton}>
          <Button
            style={[styles.bubble, styles.button]}
            onPress={this.moveMapToUser.bind(this)}>
            CENTER ON ME
          </Button>
        </View>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#F5FCFF',
    height: Dimensions.get('window').height,
  },

  map: {
    height: Dimensions.get('window').height/1.15,
    margin: 10,
    borderWidth: 1,
    borderColor: '#000000',
  },

  editButton: {
    position: 'absolute',
    backgroundColor: 'transparent',
    left: Dimensions.get('window').width/2 - 75,
    bottom: 90,
  },

  centerButton: {
    position: 'absolute',
    backgroundColor: 'transparent',
    left: Dimensions.get('window').width/2 - 100,
    bottom: 40,
    borderRadius: 10,
  },

  bubble: {
    backgroundColor: 'rgba(255,255,255,0.7)',
    paddingHorizontal: 15,
    paddingVertical: 10,
    borderRadius: 10,
  },

  button: {
    width: 200,
    alignItems: 'center',
  },

  icon: {
    borderRadius: 13,
    backgroundColor: 'transparent',
  },
});

<|endoftext|>


<commit_before>/*eslint-env browser */
/*global require: false, module: false, document: false */
'use strict';

var _ = require('./underscore_ext');
var Rx = require('rx');
var React = require('react');
var Column = require('./Column');
var Legend = require('./Legend');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var {deepPureRenderMixin, rxEventsMixin} = require('./react-utils');
var widgets = require('columnWidgets');
var util = require('./util');
var CanvasDrawing = require('./CanvasDrawing');

var features = require('./models/mutationVector');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
	module.hot.accept('./models/mutationVector', () => {
		features = require('./models/mutationVector');
	});
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var radius = 4;

// Group by consecutive matches, perserving order.
function groupByConsec(sortedArray, prop, ctx) {
	var cb = _.iteratee(prop, ctx);
	var last = {}, current; // init 'last' with a sentinel, !== to everything
	return _.reduce(sortedArray, (acc, el) => {
		var key = cb(el);
		if (key !== last) {
			current = [];
			last = key;
			acc.push(current);
		}
		current.push(el);
		return acc;
	}, []);
}

function push(arr, v) {
	arr.push(v);
	return arr;
}

function drawBackground(vg, width, height, pixPerRow, hasValue) {
	var ctx = vg.context(),
		[stripes] = _.reduce(
			groupByConsec(hasValue, _.identity),
			([acc, sum], g) =>
				[g[0] ? acc : push(acc, [sum, g.length]), sum + g.length],
			[[], 0]);

	vg.smoothing(false);
	vg.box(0, 0, width, height, 'white'); // white background

	ctx.beginPath();                      // grey for missing data
	stripes.forEach(([offset, len]) =>
		ctx.rect(
			0,
			(offset * pixPerRow),
			width,
			pixPerRow * len
	));
	ctx.fillStyle = 'grey';
	ctx.fill();
}

function drawImpactPx(vg, width, pixPerRow, color, variants) {
	var ctx = vg.context(),
		varByImp = groupByConsec(variants, v => v.group);

	_.each(varByImp, vars => {
		ctx.beginPath(); // halos
		_.each(vars, v => {
			var padding = Math.max(0, radius - (v.xEnd - v.xStart + 1) / 2.0);
			ctx.moveTo(v.xStart - padding, v.y);
			ctx.lineTo(v.xEnd + padding, v.y);
		});
		ctx.lineWidth = pixPerRow;
		ctx.strokeStyle = color(vars[0].group);
		ctx.stroke();

		if (pixPerRow > 2){ // centers when there is enough vertical room for each sample
			ctx.beginPath();
			_.each(vars, v => {
				ctx.moveTo(v.xStart, v.y);
				ctx.lineTo(v.xEnd, v.y);
			});
			ctx.lineWidth = pixPerRow / 8;
			ctx.strokeStyle = 'black';
			ctx.stroke();
		}
	});
}

function draw(vg, props) {
	let {width, zoom: {count, height}, nodes} = props;
	if (!nodes) {
		vg.box(0, 0, width, height, "gray");
		return;
	}
	let {feature, samples, index: {bySample: samplesInDS}} = props,
		pixPerRow = height / count, // XXX also appears in mutationVector
		minppr = Math.max(pixPerRow, 2),
		hasValue = samples.map(s => samplesInDS[s]);

	drawBackground(vg, width, height, pixPerRow, hasValue);
	drawImpactPx(vg, width, minppr, features[feature].color, nodes);
}

function drawLegend(feature) {
	var {colors, labels, align} = features[feature].legend;
	return (
		<Legend
			colors={['rgb(255,255,255)', ...colors]}
			labels={['no mutation', ...labels]}
			align={align}
			ellipsis='' />
	);
}

function closestNode(nodes, pixPerRow, x, y) {
	var cutoffX = radius,
		cutoffY = pixPerRow / 2.0,
		min = Number.POSITIVE_INFINITY,
		distance;

	return _.reduce(nodes, function (closest, n) {
		if ((Math.abs(y - n.y) < cutoffY) && (x > n.xStart - cutoffX) && (x < n.xEnd + cutoffX)) {
			distance = Math.pow((y - n.y), 2) + Math.pow((x - (n.xStart + n.xEnd) / 2.0), 2);
			if (distance < min) {
				min = distance;
				return n;
			} else {
				return closest;
			}
		}
		else {
			return closest;
		}
	}, undefined);
}

function formatAf(af) {
	return (af === 'NA' || af === '' || af == null) ? null :
		Math.round(af * 100) + '%';
}

var fmtIf = (x, fmt) => x ? fmt(x) : '';
var gbURL = 'http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg19&position=';
var dropNulls = rows => rows.map(row => row.filter(col => col != null)) // drop empty cols
	.filter(row => row.length > 0); // drop empty rows

function sampleTooltip(data, gene) {
	var dnaVaf = data.dna_vaf == null ? null : ['labelValue',  'DNA variant allele freq', formatAf(data.dna_vaf)],
		rnaVaf = data.rna_vaf == null ? null : ['labelValue',  'RNA variant allele freq', formatAf(data.rna_vaf)],
		refAlt = data.reference && data.alt && ['value', `${data.reference} to ${data.alt}`],
		pos = data && `${data.chr}:${util.addCommas(data.start)}-${util.addCommas(data.end)}`,
		posURL = ['url', `hg19 ${pos}`, gbURL + encodeURIComponent(pos)],
		effect = ['value', fmtIf(data.effect, x => `${x}, `) +  gene + //eslint-disable-line comma-spacing
					fmtIf(data.amino_acid, x => ` (${x})`)];

	return {
		rows: dropNulls([
			[effect],
			[posURL, refAlt],
			[dnaVaf],
			[rnaVaf]
		]),
		sampleID: data.sample
	};
}

function tooltip(nodes, samples, {height, count, index}, gene,  ev) {
	var {x, y} = util.eventOffset(ev),
		pixPerRow = height / count, // XXX also appears in mutationVector
		minppr = Math.max(pixPerRow, 2), // XXX appears multiple places
		node = closestNode(nodes, minppr, x, y);

	return node ?
		sampleTooltip(node.data, gene) :
		{sampleID: samples[Math.floor((y * count / height) + index)]};
}

var MutationColumn = hotOrNot(React.createClass({
	mixins: [rxEventsMixin, deepPureRenderMixin],
	componentWillMount: function () {
		this.events('mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = this.ev.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.selectMany(() => {
				return this.ev.mousemove.takeUntil(this.ev.mouseout)
					.map(ev => ({data: this.tooltip(ev), open: true})) // look up current data
					.concat(Rx.Observable.return({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.dispose();
	},
	onMuPit: function () {
		// Construct the url, which will be opened in new window
		let rows = _.getIn(this.props, ['data', 'req', 'rows']),
			uriList = _.uniq(_.map(rows, n => `${n.chr}:${n.start.toString()}`)).join(','),
			url = `http://mupit.icm.jhu.edu/?gm=${uriList}`;

		window.open(url);
	},
	tooltip: function (ev) {
		var {column: {nodes, fields}, samples, zoom} = this.props;
		return tooltip(nodes, samples, zoom, fields[0], ev);
	},
	render: function () {
		var {column, samples, zoom, data, index, hasSurvival} = this.props,
			feature = _.getIn(column, ['sFeature']),
			disableMenu = data ? false : true,
			menuItemName = 'MuPIT View' + (disableMenu ? ' (loading..)' : '');

		// XXX Make plot a child instead of a prop? There's also legend.
		return (
			<Column
				callback={this.props.callback}
				id={this.props.id}
				hasSurvival={hasSurvival}
				download={() => console.log('fixme')} //eslint-disable-line no-undef
				column={column}
				zoom={zoom}
				menu={<MenuItem disabled={disableMenu} onSelect={this.onMuPit}>{menuItemName}</MenuItem>}
				data={data}
				plot={<CanvasDrawing
						ref='plot'
						draw={draw}
						wrapperProps={{
							className: 'Tooltip-target',
							onMouseMove: this.ev.mousemove,
							onMouseOut: this.ev.mouseout,
							onMouseOver: this.ev.mouseover,
							onClick: this.props.onClick
						}}
						feature={feature}
						nodes={column.nodes}
						width={column.width}
						data={data}
						index={index}
						samples={samples}
						xzoom={column.zoom}
						zoom={zoom}/>}
				legend={drawLegend(feature)}
			/>
		);
	}
}));

var getColumn = (props) => <MutationColumn {...props} />;

widgets.column.add('mutationVector', getColumn);
<commit_msg>Fix mutation gray/white background.<commit_after>/*eslint-env browser */
/*global require: false, module: false, document: false */
'use strict';

var _ = require('./underscore_ext');
var Rx = require('rx');
var React = require('react');
var Column = require('./Column');
var Legend = require('./Legend');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var {deepPureRenderMixin, rxEventsMixin} = require('./react-utils');
var widgets = require('columnWidgets');
var util = require('./util');
var CanvasDrawing = require('./CanvasDrawing');

var features = require('./models/mutationVector');

// Since we don't set module.exports, but instead register ourselves
// with columWidgets, react-hot-loader can't handle the updates automatically.
// Accept hot loading here.
if (module.hot) {
	module.hot.accept();
	module.hot.accept('./models/mutationVector', () => {
		features = require('./models/mutationVector');
	});
}

// Since there are multiple components in the file we have to use makeHot
// explicitly.
function hotOrNot(component) {
	return module.makeHot ? module.makeHot(component) : component;
}

var radius = 4;

// Group by consecutive matches, perserving order.
function groupByConsec(sortedArray, prop, ctx) {
	var cb = _.iteratee(prop, ctx);
	var last = {}, current; // init 'last' with a sentinel, !== to everything
	return _.reduce(sortedArray, (acc, el) => {
		var key = cb(el);
		if (key !== last) {
			current = [];
			last = key;
			acc.push(current);
		}
		current.push(el);
		return acc;
	}, []);
}

function push(arr, v) {
	arr.push(v);
	return arr;
}

function drawBackground(vg, width, height, pixPerRow, hasValue) {
	var ctx = vg.context(),
		[stripes] = _.reduce(
			groupByConsec(hasValue, _.identity),
			([acc, sum], g) =>
				[g[0] ? acc : push(acc, [sum, g.length]), sum + g.length],
			[[], 0]);

	vg.smoothing(false);
	vg.box(0, 0, width, height, 'white'); // white background

	ctx.beginPath();                      // grey for missing data
	stripes.forEach(([offset, len]) =>
		ctx.rect(
			0,
			(offset * pixPerRow),
			width,
			pixPerRow * len
	));
	ctx.fillStyle = 'grey';
	ctx.fill();
}

function drawImpactPx(vg, width, pixPerRow, color, variants) {
	var ctx = vg.context(),
		varByImp = groupByConsec(variants, v => v.group);

	_.each(varByImp, vars => {
		ctx.beginPath(); // halos
		_.each(vars, v => {
			var padding = Math.max(0, radius - (v.xEnd - v.xStart + 1) / 2.0);
			ctx.moveTo(v.xStart - padding, v.y);
			ctx.lineTo(v.xEnd + padding, v.y);
		});
		ctx.lineWidth = pixPerRow;
		ctx.strokeStyle = color(vars[0].group);
		ctx.stroke();

		if (pixPerRow > 2){ // centers when there is enough vertical room for each sample
			ctx.beginPath();
			_.each(vars, v => {
				ctx.moveTo(v.xStart, v.y);
				ctx.lineTo(v.xEnd, v.y);
			});
			ctx.lineWidth = pixPerRow / 8;
			ctx.strokeStyle = 'black';
			ctx.stroke();
		}
	});
}

function draw(vg, props) {
	let {width, zoom: {count, height, index}, nodes} = props;
	if (!nodes) {
		vg.box(0, 0, width, height, "gray");
		return;
	}
	let {feature, samples, index: {bySample: samplesInDS}} = props,
		pixPerRow = height / count, // XXX also appears in mutationVector
		minppr = Math.max(pixPerRow, 2),
		hasValue = samples.slice(index, index + count).map(s => samplesInDS[s]);

	drawBackground(vg, width, height, pixPerRow, hasValue);
	drawImpactPx(vg, width, minppr, features[feature].color, nodes);
}

function drawLegend(feature) {
	var {colors, labels, align} = features[feature].legend;
	return (
		<Legend
			colors={['rgb(255,255,255)', ...colors]}
			labels={['no mutation', ...labels]}
			align={align}
			ellipsis='' />
	);
}

function closestNode(nodes, pixPerRow, x, y) {
	var cutoffX = radius,
		cutoffY = pixPerRow / 2.0,
		min = Number.POSITIVE_INFINITY,
		distance;

	return _.reduce(nodes, function (closest, n) {
		if ((Math.abs(y - n.y) < cutoffY) && (x > n.xStart - cutoffX) && (x < n.xEnd + cutoffX)) {
			distance = Math.pow((y - n.y), 2) + Math.pow((x - (n.xStart + n.xEnd) / 2.0), 2);
			if (distance < min) {
				min = distance;
				return n;
			} else {
				return closest;
			}
		}
		else {
			return closest;
		}
	}, undefined);
}

function formatAf(af) {
	return (af === 'NA' || af === '' || af == null) ? null :
		Math.round(af * 100) + '%';
}

var fmtIf = (x, fmt) => x ? fmt(x) : '';
var gbURL = 'http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg19&position=';
var dropNulls = rows => rows.map(row => row.filter(col => col != null)) // drop empty cols
	.filter(row => row.length > 0); // drop empty rows

function sampleTooltip(data, gene) {
	var dnaVaf = data.dna_vaf == null ? null : ['labelValue',  'DNA variant allele freq', formatAf(data.dna_vaf)],
		rnaVaf = data.rna_vaf == null ? null : ['labelValue',  'RNA variant allele freq', formatAf(data.rna_vaf)],
		refAlt = data.reference && data.alt && ['value', `${data.reference} to ${data.alt}`],
		pos = data && `${data.chr}:${util.addCommas(data.start)}-${util.addCommas(data.end)}`,
		posURL = ['url', `hg19 ${pos}`, gbURL + encodeURIComponent(pos)],
		effect = ['value', fmtIf(data.effect, x => `${x}, `) +  gene + //eslint-disable-line comma-spacing
					fmtIf(data.amino_acid, x => ` (${x})`)];

	return {
		rows: dropNulls([
			[effect],
			[posURL, refAlt],
			[dnaVaf],
			[rnaVaf]
		]),
		sampleID: data.sample
	};
}

function tooltip(nodes, samples, {height, count, index}, gene,  ev) {
	var {x, y} = util.eventOffset(ev),
		pixPerRow = height / count, // XXX also appears in mutationVector
		minppr = Math.max(pixPerRow, 2), // XXX appears multiple places
		node = closestNode(nodes, minppr, x, y);

	return node ?
		sampleTooltip(node.data, gene) :
		{sampleID: samples[Math.floor((y * count / height) + index)]};
}

var MutationColumn = hotOrNot(React.createClass({
	mixins: [rxEventsMixin, deepPureRenderMixin],
	componentWillMount: function () {
		this.events('mouseout', 'mousemove', 'mouseover');

		// Compute tooltip events from mouse events.
		this.ttevents = this.ev.mouseover.filter(ev => util.hasClass(ev.currentTarget, 'Tooltip-target'))
			.selectMany(() => {
				return this.ev.mousemove.takeUntil(this.ev.mouseout)
					.map(ev => ({data: this.tooltip(ev), open: true})) // look up current data
					.concat(Rx.Observable.return({open: false}));
			}).subscribe(this.props.tooltip);
	},
	componentWillUnmount: function () {
		this.ttevents.dispose();
	},
	onMuPit: function () {
		// Construct the url, which will be opened in new window
		let rows = _.getIn(this.props, ['data', 'req', 'rows']),
			uriList = _.uniq(_.map(rows, n => `${n.chr}:${n.start.toString()}`)).join(','),
			url = `http://mupit.icm.jhu.edu/?gm=${uriList}`;

		window.open(url);
	},
	tooltip: function (ev) {
		var {column: {nodes, fields}, samples, zoom} = this.props;
		return tooltip(nodes, samples, zoom, fields[0], ev);
	},
	render: function () {
		var {column, samples, zoom, data, index, hasSurvival} = this.props,
			feature = _.getIn(column, ['sFeature']),
			disableMenu = data ? false : true,
			menuItemName = 'MuPIT View' + (disableMenu ? ' (loading..)' : '');

		// XXX Make plot a child instead of a prop? There's also legend.
		return (
			<Column
				callback={this.props.callback}
				id={this.props.id}
				hasSurvival={hasSurvival}
				download={() => console.log('fixme')} //eslint-disable-line no-undef
				column={column}
				zoom={zoom}
				menu={<MenuItem disabled={disableMenu} onSelect={this.onMuPit}>{menuItemName}</MenuItem>}
				data={data}
				plot={<CanvasDrawing
						ref='plot'
						draw={draw}
						wrapperProps={{
							className: 'Tooltip-target',
							onMouseMove: this.ev.mousemove,
							onMouseOut: this.ev.mouseout,
							onMouseOver: this.ev.mouseover,
							onClick: this.props.onClick
						}}
						feature={feature}
						nodes={column.nodes}
						width={column.width}
						data={data}
						index={index}
						samples={samples}
						xzoom={column.zoom}
						zoom={zoom}/>}
				legend={drawLegend(feature)}
			/>
		);
	}
}));

var getColumn = (props) => <MutationColumn {...props} />;

widgets.column.add('mutationVector', getColumn);
<|endoftext|>


<commit_before>$(document).ready(function(){

    var tabbedAutocomplete = $('#mainsearch').autocomplete({
		source: function( request, response ) {
			$.ajax({
				url: "/autocomplete/all/" + $('#mainsearch').val(),
          		dataType: "json",
          		type: 'get',
          		success: function( data ) {
					response( data );
				}
			});
		},
		sourceCategory: 'all',
		minLength: 3,
		focus: function( event, ui ) {
			if(typeof ui.item !== 'undefined') {
				ui.item.value = stripTags(ui.item.value);
			}
		},
		select: function( event, ui ) {
			//console.log(ui.item);
			if(ui.item) {
				document.location.href = ui.item.url;
				return false;
				$('#searchform').submit();
			}
		}
	}).data("ui-autocomplete");
     /* custom boostrap markup for items */
     tabbedAutocomplete._renderItem = function (ul, item) {

     	var widgetLink = '';
     	if(item.type == 'track') {
     		widgetLink = $('')
     			.attr('class', 'trigger-modal')
     			.attr('href', '/markup/widget-trackcontrol?item='+ item.itemid )
     			.html(' TODO: ')
     			.bind('click', function(e){
     				e.preventDefault();
     				// TODO: unbind click event of whole item
     				// TODO: is it possible to use event listener which already exists on all .trigger-modal elements?
     				//$(this).parent().parent().parent().parent().unbind('click');
			        $.ajax({
						url: $(this).attr('href')
					}).done(function(response){
						$('#global-modal .modal-content').html(response);
						$('#global-modal').modal('show');
					});
     			});
     	}

		var markup = $('
', {'class':'row'})
		.append(
			$('
', {'class':'col-md-2'}).append(
				$('', {'src':item.img, 'width': 50, 'height': 50})
			)
		)
		.append(
			$('
', {'class':'col-md-10'}).append(
				$('', {'href':item.url, html: item.label})
			)
			.append(
				$('
')
			)
			.append(
				$('', {'class': 'dark', text:item.typelabel })
			)
			.append(
				$(widgetLink)
			)
		 );
         return $("

")
             .data("item.autocomplete", item)
             .append(markup)
             .appendTo(ul);
     };
     // create a few filter links in autocomplete widget
    tabbedAutocomplete._renderMenu = function( ul, items, type ) {
		var that = this;
		var markup = $('
').attr('class', 'nav nav-pills ac-nav');
		var filterLinks = ["all", "artist", "album", "label"];
		var cat = this.options.sourceCategory;
		filterLinks.forEach(function(filter){
			$('<button>').attr('type', 'button')
			.attr('class', 'btn btn-primary' + ((cat === filter)?'active':''))
			.attr('data-filter', filter)
			.text(filter).bind('click', function(){
				changeAutocompleteUrl(filter);
			}).appendTo(markup);
		});
		$(markup).wrapAll($('
').attr('class', 'ui-state-disabled')).appendTo(ul);

		$.each( items, function( index, item ) {
			that._renderItemData( ul, item );
		});
	};

	// arrow left right for switching between tabs
	$('#mainsearch').keydown( function( event ) {

		// check if widget is visile
		var isOpen = $( this ).autocomplete( "widget" ).is( ":visible" );

		// TODO: limit functionality on focused item
		//focused = $('#mainsearch').data("ui-autocomplete").menu.element.find("li.ui-state-focus").length;

		if ( isOpen /*&& focused == 1*/ && event.keyCode == $.ui.keyCode.LEFT) {
			var prev = $('.ac-nav button.btn-primaryactive').prev();
			if(prev.length) {
				changeAutocompleteUrl(prev.attr('data-filter'));
				return false;
			}
		}

		if ( isOpen /*&& focused == 1*/ && event.keyCode == $.ui.keyCode.RIGHT) {
			var next = $('.ac-nav button.btn-primaryactive').next();
			if(next.length) {
				changeAutocompleteUrl(next.attr('data-filter'));
				return false;
			}
		}
	});
});

function changeAutocompleteUrl(type) {
	// set input value to initial searchterm
	$('#mainsearch').val($('#mainsearch').data("ui-autocomplete").term);

	// change ajax-url
	$('#mainsearch').autocomplete('option', 'source', function( request, response ) {
		$.ajax({
			url: "/autocomplete/"+ type+"/" + $('#mainsearch').val(),
      		dataType: "json",
      		type: 'get',
      		success: function( data ) {
				response( data );
			}
		});
	});

	// store active filter in variable
	$('#mainsearch').autocomplete('option', 'sourceCategory', type);

	// trigger refresh with new ajax-url
	$('#mainsearch').autocomplete().data("ui-autocomplete")._search();
}

function stripTags( str ) {
    str=str.toString();
    return str.replace(/<\/?[^>]+>/gi, '');
}

<commit_msg>fixed modal-trigger in autocomplete item<commit_after>$(document).ready(function(){

    var tabbedAutocomplete = $('#mainsearch').autocomplete({
		source: function( request, response ) {
			$.ajax({
				url: "/autocomplete/all/" + $('#mainsearch').val(),
          		dataType: "json",
          		type: 'get',
          		success: function( data ) {
					response( data );
				}
			});
		},
		sourceCategory: 'all',
		minLength: 3,
		focus: function( event, ui ) {
			if(typeof ui.item !== 'undefined') {
				ui.item.value = stripTags(ui.item.value);
			}
		},
		select: function( event, ui ) {
			// do not navigate away with visible modal
			if($("#global-modal").hasClass('in')) {
				return false;
			}
			//console.log(ui.item);
			if(ui.item) {
				document.location.href = ui.item.url;
				return false;
				$('#searchform').submit();
			}
		}
	}).data("ui-autocomplete");
     /* custom boostrap markup for items */
     tabbedAutocomplete._renderItem = function (ul, item) {

     	var widgetLink = '';
     	if(item.type == 'track') {
     		widgetLink = $('')
     			.attr('class', 'trigger-modal')
     			.attr('href', '/markup/widget-trackcontrol?item='+ item.itemid )
     			.html(' ')
     			.bind('click', function(e){
     				// TODO: find another way to disable autocomplete-select-event when modal-opm has been fired
     				$("#global-modal").addClass('in');
     				e.preventDefault();
     				// TODO: is it possible to use event listener which already exists on all .trigger-modal elements?
			        $.ajax({
						url: $(this).attr('href')
					}).done(function(response){
						$('#global-modal .modal-content').html(response);
						$('#global-modal').modal('show');
					});
     			});
     	}

		var markup = $('
', {'class':'row'})
		.append(
			$('
', {'class':'col-md-2'}).append(
				$('', {'src':item.img, 'width': 50, 'height': 50})
			)
		)
		.append(
			$('
', {'class':'col-md-10'}).append(
				$('', {'href':item.url, html: item.label})
			)
			.append(
				$('
')
			)
			.append(
				$('', {'class': 'dark', text:item.typelabel })
			)
			.append(
				$(widgetLink)
			)
		 );
         return $("

")
             .data("item.autocomplete", item)
             .append(markup)
             .appendTo(ul);
     };
     // create a few filter links in autocomplete widget
    tabbedAutocomplete._renderMenu = function( ul, items, type ) {
		var that = this;
		var markup = $('
').attr('class', 'nav nav-pills ac-nav');
		var filterLinks = ["all", "artist", "album", "label"];
		var cat = this.options.sourceCategory;
		filterLinks.forEach(function(filter){
			$('<button>').attr('type', 'button')
			.attr('class', 'btn btn-primary' + ((cat === filter)?'active':''))
			.attr('data-filter', filter)
			.text(filter).bind('click', function(){
				changeAutocompleteUrl(filter);
			}).appendTo(markup);
		});
		$(markup).wrapAll($('
').attr('class', 'ui-state-disabled')).appendTo(ul);

		$.each( items, function( index, item ) {
			that._renderItemData( ul, item );
		});
	};

	// arrow left right for switching between tabs
	$('#mainsearch').keydown( function( event ) {

		// check if widget is visile
		var isOpen = $( this ).autocomplete( "widget" ).is( ":visible" );

		// TODO: limit functionality on focused item
		//focused = $('#mainsearch').data("ui-autocomplete").menu.element.find("li.ui-state-focus").length;

		if ( isOpen /*&& focused == 1*/ && event.keyCode == $.ui.keyCode.LEFT) {
			var prev = $('.ac-nav button.btn-primaryactive').prev();
			if(prev.length) {
				changeAutocompleteUrl(prev.attr('data-filter'));
				return false;
			}
		}

		if ( isOpen /*&& focused == 1*/ && event.keyCode == $.ui.keyCode.RIGHT) {
			var next = $('.ac-nav button.btn-primaryactive').next();
			if(next.length) {
				changeAutocompleteUrl(next.attr('data-filter'));
				return false;
			}
		}
	});
});

function changeAutocompleteUrl(type) {
	// set input value to initial searchterm
	$('#mainsearch').val($('#mainsearch').data("ui-autocomplete").term);

	// change ajax-url
	$('#mainsearch').autocomplete('option', 'source', function( request, response ) {
		$.ajax({
			url: "/autocomplete/"+ type+"/" + $('#mainsearch').val(),
      		dataType: "json",
      		type: 'get',
      		success: function( data ) {
				response( data );
			}
		});
	});

	// store active filter in variable
	$('#mainsearch').autocomplete('option', 'sourceCategory', type);

	// trigger refresh with new ajax-url
	$('#mainsearch').autocomplete().data("ui-autocomplete")._search();
}

function stripTags( str ) {
    str=str.toString();
    return str.replace(/<\/?[^>]+>/gi, '');
}

<|endoftext|>