<commit_before>
import moment from "moment/src/moment"

/**
 * Private field and method symbols.
 *
 * @type {Object<string, symbol>}
 */
const PRIVATE = Object.freeze({
  // fields
  apiClient: Symbol("apiClient"),

  // methods
  listAll: Symbol("listAll")
})

/**
 * High-level YouTube REST API client.
 */
export default class YouTubeApiClient {
  /**
   * Initializes the YouTube API client.
   *
   * @param {ApiClient} apiClient The Google REST API client to use for
   *        accessing the YouTube-related APIs.
   */
  constructor(apiClient) {
    /**
     * The Google REST API client to use for accessing the YouTube-related
     * APIs.
     *
     * @type {ApiClient}
     */
    this[PRIVATE.apiClient] = apiClient

    Object.freeze(this)
  }

  /**
   * Retrieves the information about the specified YouTube account or the
   * account of the currently authorized user.
   *
   * @param {?string} accountId The ID of the YouTube account, or {@code null}
   *        if the information should be fetched for the account of the
   *        currently authorized user.
   * @return {Promise<?{id: string, title: string, playlistIds: {watchHistory: ?string, watchLater: ?string}}>}
   *         A promise that will resolve into information about the YouTube
   *         account.
   */
  getAccountInfo(accountId = null) {
    let parameters = {
      part: "snippet,contentDetails",
      fields: "items(id,snippet/title," +
          "contentDetails/relatedPlaylists(watchHistory,watchLater))"
    }
    if (accountId) {
      parameters.id = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.apiClient].list(
      "channels",
      parameters,
      !accountId
    ).then((response) => {
      if (!response.items.length) {
        return null
      }

      let account = response.items[0]
      let result = {
        id: account.id,
        title: account.snippet.title,
        playlistIds: {
          watchHistory: null,
          watchLater: null
        }
      }
      if (account.contentDetails && account.contentDetails.relatedPlaylists) {
        let playlists = account.contentDetails.relatedPlaylists
        result.playlistIds.watchHistory = playlists.watchHistory || null
        result.playlistIds.watchLater = playlists.watchLater || null
      }

      return result
    })
  }

  /**
   * Fetches the list of channels the current user is subscribed to. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality at the given
   * URL. The keys, sorted from the lowest quality, to the highest, with their
   * dimensions (at the moment of writing this) attached, are as follows:
   *
   * - {@code default} - 88 × 88 pixels
   * - {@code high} - 240 × 240 pixels
   *
   * @param {?string=} accountId Youtube account ID, must be the current user's
   *        account ID, or {@code null}
   * @return {Promise<{id: string, title: string, videoCount: number, thumbnails: Object<string, string>}[]>}
   *         A promise that will resolve into a list of objects, where each
   *         object represents a single YouTube channel the current user or the
   *         specified user is subscribed to.
   */
  getSubscribedChannels(accountId = null) {
    let parameters = {
      part: "snippet,contentDetails",
      maxResults: 50,
      fields: "pageInfo,nextPageToken," +
          "items(snippet(title,resourceId,thumbnails)," +
          "contentDetails/totalItemCount)"
    }
    if (accountId) {
      parameters.channelId = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.listAll](
      "subscriptions",
      parameters,
      () => true,
      true
    ).then((items) => {
      return items.map((item) => {
        return {
          id: item.snippet.resourceId.channelId,
          title: item.snippet.title,
          videoCount: item.contentDetails.totalItemCount,
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * Fetches the uploaded videos playlist ID of the specified YouTube channel.
   *
   * @param {string} channelId Channel ID.
   * @return {Promise<?string>} A promise that will resolve to the playlist ID
   *         of the uploads playlist for the specified channel ID. The ID will
   *         be {@code null} if the channel is not found.
   */
  getUploadsPlaylistId(channelId) {
    return this[PRIVATE.apiClient].list("channels", {
      part: "contentDetails",
      id: channelId,
      fields: "items/contentDetails/relatedPlaylists/uploads"
    }).then((response) => {
      if (response.items.length) {
        return null
      }

      let channelInfo = response.items[0]
      return channelInfo.contentDetails.relatedPlaylists.uploads
    })
  }

  /**
   * Retrieves the uploads playlist IDs for the specified YouTube channels.
   *
   * @param {string} channelIds Channel IDs.
   * @return {Promise<{id: string, uploadsPlaylistId: string}[]>} A promise
   *         that will resolve into an array of objects matching the channel
   *         IDs to uploads playlist IDs. The array will not contain elements
   *         for channels that were not found.
   */
  getUploadsPlaylistIds(channelIds) {
    return this[PRIVATE.listAll]("channels", {
      part: "contentDetails",
      id: channelIds.join(","),
      fields: "items(id,contentDetails/relatedPlaylists/uploads)"
    }).then((channels) => {
      return channels.map((channel) => {
        return {
          id: channel.id,
          uploadsPlaylistId: channel.contentDetails.relatedPlaylists.uploads
        }
      })
    })
  }

  /**
   * Fetches information about the specified playlist. The method requires the
   * current user to be authorized and have valid (unexpired) OAuth2 token if
   * the playlist is the user's watch history or the watch later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality, and, sorted
   * from the lowest quality to the highest, are as follows:
   *
   * - {@code default}
   * - {@code medium}
   * - {@code high}
   * - {@code standard}
   * - {@code maxres}
   *
   * Note that the thumbnails may change with every video added to the
   * playlist.
   *
   * @param {string} playlistId Playlist ID.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<?{id: string, title: string, description: string, videoCount: number, thumbnails: Object<string, {url: string, width: number, height: number}>}>}
   *         A promise that will resolve to information about the specified
   *         playlist. The playlist info will be {@code null} if the playlist
   *         is not found.
   */
  getPlaylistInfo(playlistId, authorized = false) {
    return this[PRIVATE.apiClient].list("playlists", {
      part: "snippet,contentDetails",
      id: playlistId,
      fields: "items(id,snippet(title,description,thumbnails),contentDetails)"
    }, authorized).then((response) => {
      if (!response.items.length) {
        return null
      }

      let playlist = response.items[0]
      return {
        id: playlist.id,
        title: playlist.snippet.title,
        description: playlist.snippet.description,
        videoCount: playlist.contentDetails.itemCount,
        thumbnails: playlist.snippet.thumbnails
      }
    })
  }

  /**
   * Retrieves the video counts for the specified playlists. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * @param {string[]} playlistIds Playlist IDs.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<{id: string, videoCount: number}[]>} A promise that will
   *         resolve to objects representing the meta information about the
   *         specified playlists. The array will not contain items for
   *         playlists that were not found.
   */
  getPlaylistVideoCounts(playlistIds, authorized = false) {
    return this[PRIVATE.listAll]("playlists", {
      part: "contentDetails",
      id: playlistIds.join(","),
      fields: "items(id,contentDetails)"
    }, () => true, authorized).then((playlists) => {
      return playlists.map((playlist) => {
        return {
          id: playlist.id,
          videoCount: playlist.contentDetails.itemCount
        }
      })
    })
  }

  /**
   * Fetches the list of videos present in the specified playlist. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality, and, sorted
   * from the lowest quality to the highest, are as follows:
   *
   * - {@code default}
   * - {@code medium}
   * - {@code high}
   * - {@code standard}
   * - {@code maxres}
   *
   * @param {string} playlistId Playlist ID.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<{id: number, title: string, description: string, publishedAt: Date, thumbnails: Object<string, {url: string, width: number, height: number}>}[]>}
   *         A promise that will resolve to array of objects, each representing
   *         a single video.
   */
  getPlaylistVideos(playlistId, continuationPredicate = () => true,
      authorized = false) {
    return this[PRIVATE.listAll]("playlistItems", {
      part: "snippet,contentDetails",
      maxResults: 50,
      playlistId,
      fields: "pageInfo,nextPageToken,items/snippet(publishedAt,title," +
          "description,thumbnails,resourceId/videoId)"
    }, continuationPredicate, authorized).then((items) => {
      return items.map((item) => {
        return {
          id: item.resourceId.videoId,
          title: item.snippet.title,
          description: item.snippet.description,
          publishedAt: new Date(item.snippet.publishedAt),
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * Fetches the duration and view count for the specified video.
   *
   * @param {string} videoId Video ID.
   * @return {Promise<?{id: number, duration: number, viewCount: number}>} A
   *         promise that will resolve to the video metadata. The result will
   *         be {@code null} if the video is not found.
   */
  getVideoMetaData(videoId) {
    return this[PRIVATE.apiClient].list("videos", {
      part: "contentDetails,statistics",
      id: videoId,
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let video = response.items[0]
      return {
        id: video.id,
        duration: moment.duration(video.contentDetails.duration).asSeconds(),
        viewCount: video.statistics.viewCount
      }
    })
  }

  /**
   * Fetches the durations and view counts for the specified videos.
   *
   * @param {string[]} videoIds Video IDs.
   * @return {Promise<{id: number, duration: number, viewCount: number}[]>} A
   *         promise that will resolve to the video metadata. The resulting
   *         array will not contain metadata of videos that were not found.
   */
  getVideosMetaData(videoIds) {
    return this[PRIVATE.listAll]("videos", {
      part: "contentDetails,statistics",
      id: videoIds.join(","),
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      return response.items.map((video) => {
        return {
          id: video.id,
          duration: moment.duration(video.contentDetails.duration).asSeconds(),
          viewCount: video.statistics.viewCount
        }
      })
    })
  }

  /**
   * Adds the specified video the the specified playlist. This method requires
   * the current user to be authorized and have valid (unexpired) OAuth2 token.
   *
   * @param {string} playlistId ID of the playlist to which the video should be
   *        added.
   * @param {string} videoId ID of the video to add to the playlist.
   * @return {Promise<undefined>} A promise that resolves when the item has
   *         been added to the playlist.
   */
  addPlaylistItem(playlistId, videoId) {
    return this[PRIVATE.apiClient].insert("playlistItems?part=snippet", {
      snippet: {
        playlistId: playlistId,
        resourceId: {
          kind: "youtube#video",
          videoId: videoId
        }
      }
    }, true).then(() => undefined)
  }

  /**
   * Fetches all pages of items from the REST API that are matching the
   * specified parameters.
   *
   * @param {string} path The path within the REST API denoting the resource or
   *        entity to query.
   * @param {Object<string, ?(boolean|number|string)>} parameters Parameters
   *        restricting the result to return.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        sent as authorized by user (requires OAuth2 token) or not. Some
   *        REST resources will require authorization.
   * @return {Promise<Object[]>} A promise that will resolve to the items
   *         fetched from the REST API.
   */
  [PRIVATE.listAll](path, parameters, continuationPredicate = () => true,
      authorized = false) {
    let items = []

    return fetchNextPage()

    function fetchNextPage(pageToken) {
      let fetchParams = Object.assign({}, parameters)
      if (pageToken) {
        fetchParams.pageToken = pageToken
      }

      return this[PRIVATE.apiClient].list(
        path,
        fetchParams,
        authorized
      ).then((response) => {
        if (response.items) {
          items = items.concat(response.items)
        }

        if (response.nextPageToken && continuationPredicate(response.items)) {
          return fetchNextPage(response.nextPageToken)
        } else {
          return items
        }
      })
    }
  }
}
<commit_msg>added channel IDs to videos listed from a playlist<commit_after>
import moment from "moment/src/moment"

/**
 * Private field and method symbols.
 *
 * @type {Object<string, symbol>}
 */
const PRIVATE = Object.freeze({
  // fields
  apiClient: Symbol("apiClient"),

  // methods
  listAll: Symbol("listAll")
})

/**
 * High-level YouTube REST API client.
 */
export default class YouTubeApiClient {
  /**
   * Initializes the YouTube API client.
   *
   * @param {ApiClient} apiClient The Google REST API client to use for
   *        accessing the YouTube-related APIs.
   */
  constructor(apiClient) {
    /**
     * The Google REST API client to use for accessing the YouTube-related
     * APIs.
     *
     * @type {ApiClient}
     */
    this[PRIVATE.apiClient] = apiClient

    Object.freeze(this)
  }

  /**
   * Retrieves the information about the specified YouTube account or the
   * account of the currently authorized user.
   *
   * @param {?string} accountId The ID of the YouTube account, or {@code null}
   *        if the information should be fetched for the account of the
   *        currently authorized user.
   * @return {Promise<?{id: string, title: string, playlistIds: {watchHistory: ?string, watchLater: ?string}}>}
   *         A promise that will resolve into information about the YouTube
   *         account.
   */
  getAccountInfo(accountId = null) {
    let parameters = {
      part: "snippet,contentDetails",
      fields: "items(id,snippet/title," +
          "contentDetails/relatedPlaylists(watchHistory,watchLater))"
    }
    if (accountId) {
      parameters.id = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.apiClient].list(
      "channels",
      parameters,
      !accountId
    ).then((response) => {
      if (!response.items.length) {
        return null
      }

      let account = response.items[0]
      let result = {
        id: account.id,
        title: account.snippet.title,
        playlistIds: {
          watchHistory: null,
          watchLater: null
        }
      }
      if (account.contentDetails && account.contentDetails.relatedPlaylists) {
        let playlists = account.contentDetails.relatedPlaylists
        result.playlistIds.watchHistory = playlists.watchHistory || null
        result.playlistIds.watchLater = playlists.watchLater || null
      }

      return result
    })
  }

  /**
   * Fetches the list of channels the current user is subscribed to. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality at the given
   * URL. The keys, sorted from the lowest quality, to the highest, with their
   * dimensions (at the moment of writing this) attached, are as follows:
   *
   * - {@code default} - 88 × 88 pixels
   * - {@code high} - 240 × 240 pixels
   *
   * @param {?string=} accountId Youtube account ID, must be the current user's
   *        account ID, or {@code null}
   * @return {Promise<{id: string, title: string, videoCount: number, thumbnails: Object<string, string>}[]>}
   *         A promise that will resolve into a list of objects, where each
   *         object represents a single YouTube channel the current user or the
   *         specified user is subscribed to.
   */
  getSubscribedChannels(accountId = null) {
    let parameters = {
      part: "snippet,contentDetails",
      maxResults: 50,
      fields: "pageInfo,nextPageToken," +
          "items(snippet(title,resourceId,thumbnails)," +
          "contentDetails/totalItemCount)"
    }
    if (accountId) {
      parameters.channelId = accountId
    } else {
      parameters.mine = true
    }

    return this[PRIVATE.listAll](
      "subscriptions",
      parameters,
      () => true,
      true
    ).then((items) => {
      return items.map((item) => {
        return {
          id: item.snippet.resourceId.channelId,
          title: item.snippet.title,
          videoCount: item.contentDetails.totalItemCount,
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * Fetches the uploaded videos playlist ID of the specified YouTube channel.
   *
   * @param {string} channelId Channel ID.
   * @return {Promise<?string>} A promise that will resolve to the playlist ID
   *         of the uploads playlist for the specified channel ID. The ID will
   *         be {@code null} if the channel is not found.
   */
  getUploadsPlaylistId(channelId) {
    return this[PRIVATE.apiClient].list("channels", {
      part: "contentDetails",
      id: channelId,
      fields: "items/contentDetails/relatedPlaylists/uploads"
    }).then((response) => {
      if (response.items.length) {
        return null
      }

      let channelInfo = response.items[0]
      return channelInfo.contentDetails.relatedPlaylists.uploads
    })
  }

  /**
   * Retrieves the uploads playlist IDs for the specified YouTube channels.
   *
   * @param {string} channelIds Channel IDs.
   * @return {Promise<{id: string, uploadsPlaylistId: string}[]>} A promise
   *         that will resolve into an array of objects matching the channel
   *         IDs to uploads playlist IDs. The array will not contain elements
   *         for channels that were not found.
   */
  getUploadsPlaylistIds(channelIds) {
    return this[PRIVATE.listAll]("channels", {
      part: "contentDetails",
      id: channelIds.join(","),
      fields: "items(id,contentDetails/relatedPlaylists/uploads)"
    }).then((channels) => {
      return channels.map((channel) => {
        return {
          id: channel.id,
          uploadsPlaylistId: channel.contentDetails.relatedPlaylists.uploads
        }
      })
    })
  }

  /**
   * Fetches information about the specified playlist. The method requires the
   * current user to be authorized and have valid (unexpired) OAuth2 token if
   * the playlist is the user's watch history or the watch later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality, and, sorted
   * from the lowest quality to the highest, are as follows:
   *
   * - {@code default}
   * - {@code medium}
   * - {@code high}
   * - {@code standard}
   * - {@code maxres}
   *
   * Note that the thumbnails may change with every video added to the
   * playlist.
   *
   * @param {string} playlistId Playlist ID.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<?{id: string, title: string, description: string, videoCount: number, thumbnails: Object<string, {url: string, width: number, height: number}>}>}
   *         A promise that will resolve to information about the specified
   *         playlist. The playlist info will be {@code null} if the playlist
   *         is not found.
   */
  getPlaylistInfo(playlistId, authorized = false) {
    return this[PRIVATE.apiClient].list("playlists", {
      part: "snippet,contentDetails",
      id: playlistId,
      fields: "items(id,snippet(title,description,thumbnails),contentDetails)"
    }, authorized).then((response) => {
      if (!response.items.length) {
        return null
      }

      let playlist = response.items[0]
      return {
        id: playlist.id,
        title: playlist.snippet.title,
        description: playlist.snippet.description,
        videoCount: playlist.contentDetails.itemCount,
        thumbnails: playlist.snippet.thumbnails
      }
    })
  }

  /**
   * Retrieves the video counts for the specified playlists. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * @param {string[]} playlistIds Playlist IDs.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<{id: string, videoCount: number}[]>} A promise that will
   *         resolve to objects representing the meta information about the
   *         specified playlists. The array will not contain items for
   *         playlists that were not found.
   */
  getPlaylistVideoCounts(playlistIds, authorized = false) {
    return this[PRIVATE.listAll]("playlists", {
      part: "contentDetails",
      id: playlistIds.join(","),
      fields: "items(id,contentDetails)"
    }, () => true, authorized).then((playlists) => {
      return playlists.map((playlist) => {
        return {
          id: playlist.id,
          videoCount: playlist.contentDetails.itemCount
        }
      })
    })
  }

  /**
   * Fetches the list of videos present in the specified playlist. The method
   * requires the current user to be authorized and have valid (unexpired)
   * OAuth2 token if the playlist is the user's watch history or the watch
   * later playlist.
   *
   * The thumbnails object keys describe the thumbnail quality, and, sorted
   * from the lowest quality to the highest, are as follows:
   *
   * - {@code default}
   * - {@code medium}
   * - {@code high}
   * - {@code standard}
   * - {@code maxres}
   *
   * @param {string} playlistId Playlist ID.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        authorized by the user. This is required for the user's watch
   *        history and watch later playlist.
   * @return {Promise<{id: number, title: string, description: string, publishedAt: Date, channelId: string, thumbnails: Object<string, {url: string, width: number, height: number}>}[]>}
   *         A promise that will resolve to array of objects, each representing
   *         a single video.
   */
  getPlaylistVideos(playlistId, continuationPredicate = () => true,
      authorized = false) {
    return this[PRIVATE.listAll]("playlistItems", {
      part: "snippet,contentDetails",
      maxResults: 50,
      playlistId,
      fields: "pageInfo,nextPageToken,items/snippet(publishedAt,title," +
          "description,channelId,thumbnails,resourceId/videoId)"
    }, continuationPredicate, authorized).then((items) => {
      return items.map((item) => {
        return {
          id: item.resourceId.videoId,
          title: item.snippet.title,
          description: item.snippet.description,
          publishedAt: new Date(item.snippet.publishedAt),
          channelId: item.snippet.channelId,
          thumbnails: item.snippet.thumbnails
        }
      })
    })
  }

  /**
   * Fetches the duration and view count for the specified video.
   *
   * @param {string} videoId Video ID.
   * @return {Promise<?{id: number, duration: number, viewCount: number}>} A
   *         promise that will resolve to the video metadata. The result will
   *         be {@code null} if the video is not found.
   */
  getVideoMetaData(videoId) {
    return this[PRIVATE.apiClient].list("videos", {
      part: "contentDetails,statistics",
      id: videoId,
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      if (!response.items.length) {
        return null
      }

      let video = response.items[0]
      return {
        id: video.id,
        duration: moment.duration(video.contentDetails.duration).asSeconds(),
        viewCount: video.statistics.viewCount
      }
    })
  }

  /**
   * Fetches the durations and view counts for the specified videos.
   *
   * @param {string[]} videoIds Video IDs.
   * @return {Promise<{id: number, duration: number, viewCount: number}[]>} A
   *         promise that will resolve to the video metadata. The resulting
   *         array will not contain metadata of videos that were not found.
   */
  getVideosMetaData(videoIds) {
    return this[PRIVATE.listAll]("videos", {
      part: "contentDetails,statistics",
      id: videoIds.join(","),
      fields: "items(id,contentDetails/duration,statistics/viewCount)"
    }).then((response) => {
      return response.items.map((video) => {
        return {
          id: video.id,
          duration: moment.duration(video.contentDetails.duration).asSeconds(),
          viewCount: video.statistics.viewCount
        }
      })
    })
  }

  /**
   * Adds the specified video the the specified playlist. This method requires
   * the current user to be authorized and have valid (unexpired) OAuth2 token.
   *
   * @param {string} playlistId ID of the playlist to which the video should be
   *        added.
   * @param {string} videoId ID of the video to add to the playlist.
   * @return {Promise<undefined>} A promise that resolves when the item has
   *         been added to the playlist.
   */
  addPlaylistItem(playlistId, videoId) {
    return this[PRIVATE.apiClient].insert("playlistItems?part=snippet", {
      snippet: {
        playlistId: playlistId,
        resourceId: {
          kind: "youtube#video",
          videoId: videoId
        }
      }
    }, true).then(() => undefined)
  }

  /**
   * Fetches all pages of items from the REST API that are matching the
   * specified parameters.
   *
   * @param {string} path The path within the REST API denoting the resource or
   *        entity to query.
   * @param {Object<string, ?(boolean|number|string)>} parameters Parameters
   *        restricting the result to return.
   * @param {function(Object[]): boolean} continuationPredicate A callback
   *        executed after each call to the REST API. The callback will receive
   *        the items fetched in the last REST API call and should return
   *        either {@code true} if the method should fetch the next page, or
   *        {@code false} if no additional pages need to be fetched.
   * @param {boolean=} authorized Flag signalling whether the request should be
   *        sent as authorized by user (requires OAuth2 token) or not. Some
   *        REST resources will require authorization.
   * @return {Promise<Object[]>} A promise that will resolve to the items
   *         fetched from the REST API.
   */
  [PRIVATE.listAll](path, parameters, continuationPredicate = () => true,
      authorized = false) {
    let items = []

    return fetchNextPage()

    function fetchNextPage(pageToken) {
      let fetchParams = Object.assign({}, parameters)
      if (pageToken) {
        fetchParams.pageToken = pageToken
      }

      return this[PRIVATE.apiClient].list(
        path,
        fetchParams,
        authorized
      ).then((response) => {
        if (response.items) {
          items = items.concat(response.items)
        }

        if (response.nextPageToken && continuationPredicate(response.items)) {
          return fetchNextPage(response.nextPageToken)
        } else {
          return items
        }
      })
    }
  }
}
<|endoftext|>


<commit_before>
import DBFactory from "../node_modules/indexed-db.es6/es2015/DBFactory"
import AbstractEntity from "../es2015/AbstractEntity"
import EntityManager from "../es2015/EntityManager"
import Transaction from "../es2015/Transaction"
import {promiseIt, delay} from "./testUtils"

fdescribe("Entity manager", () => {

  const DB_NAME = "testingDB"
  const OBJECT_STORE_NAME = "foo"

  let database
  let databasePromise
  let entityManager

  class Entity extends AbstractEntity {
    static get objectStore() {
      return OBJECT_STORE_NAME
    }
  }

  beforeEach((done) => {
    let rawRecords = [
      {
        bar: "baz"
      },
      {
        created: new Date()
      }
    ]

    databasePromise = DBFactory.open(DB_NAME, {
      version: 1,
      objectStores: [{
        name: OBJECT_STORE_NAME,
        keyPath: "id",
        autoIncrement: true
      }]
    }).then((db) => {
      database = db
      let transaction = database.startTransaction(OBJECT_STORE_NAME)

      for (let record of rawRecords) {
        let objectStore = transaction.getObjectStore(OBJECT_STORE_NAME)
        objectStore.add(record)
      }

      return transaction.completionPromise
    }).then(() => {
      entityManager = new EntityManager(databasePromise, {
        idleTransactions: {
          ttl: 10000,
          warningDelay: 2000,
          observer: (transaction, isAborted) => {
            if (isAborted) {
              console.error("A transaction has been aborted due to being " +
                  "idle for too long")
            } else {
              console.warn("A transaction is idle for too long")
            }
          }
        }
      }, new Map([[OBJECT_STORE_NAME, "id"]]))

      done()
      return database
    }).catch((error) => {
      fail(error)
      done()
    })
  })

  afterEach((done) => {
    database.close().then(() => {
      return DBFactory.deleteDatabase(DB_NAME)
    }).then(() => {
      done()
    }).catch((error) => {
      fail(error)
      done()
    })
  })

  promiseIt("should be able to fetch single entities", () => {
    return entityManager.find(Entity, 1).then((entity) => {
      expect(entity instanceof Entity).toBeTruthy()
      expect(Object.assign({}, entity)).toEqual({ id: 1, bar: "baz" })
    })
  })

  promiseIt("should resolve to null if fetching non-existing entities", () => {
    return entityManager.find(Entity, 10).then((entity) => {
      expect(entity).toBeNull()

      return entityManager.find(Entity, new Date(1))
    }).then((entity) => {
      expect(entity).toBeNull()
    })
  })

  promiseIt("should start a read-write transaction", () => {
    let transaction = entityManager.startTransaction()

    return transaction.persist(new Entity({
      foo: "bar"
    })).then(() => {
      return transaction.commit()
    })
  })

  promiseIt("must throw an error when starting multiple transactions", () => {
    let transaction = entityManager.startTransaction()
    expect(() => {
      entityManager.startTransaction()
    }).toThrow()

    return delay(100).then(() => {
      expect(() => {
        entityManager.startTransaction()
      }).toThrow()

      let commitPromise = transaction.commit()
      expect(() => {
        entityManager.startTransaction()
      }).toThrow()
      return commitPromise
    }).then(() => {
      return entityManager.startTransaction().commit()
    })
  })

  promiseIt("should start a long-running transaction", () => {
    let transaction = entityManager.startTransaction()
    return delay(100).then(() => {
      return transaction.persist(new Entity({}))
    }).then((entity) => {
      return delay(100).then(() => entity)
    }).then((entity) => {
      return transaction.commit().then(() => entity.id)
    }).then((id) => {
      expect(id).toBe(3)
    })
  })

  promiseIt("should run a transaction", () => {
    return entityManager.runTransaction((transaction) => {
      expect(transaction instanceof Transaction).toBeTruthy()

      return transaction.persist(new Entity({}))
    }).then((entity) => {
      expect(entity.id).toBe(3)
    })
  })

  promiseIt("should abort the ran transaction if an error is thrown", () => {
    return entityManager.runTransaction((transaction) => {
      transaction.persist(new Entity({}))
      throw new Error("testing, testing")
    }).then(() => {
      throw new Error("The transaction should have been aborted")
    }).catch((error) => {
      expect(error.message).toBe("testing, testing")
    }).then(() => {
      return entityManager.runTransaction(() => {
        return delay(100).then(() => Promise.reject(new Error("test #2")))
      })
    }).then(() => {
      throw new Error("The transaction should have been aborted")
    }).catch((error) => {
      expect(error.message).toBe("test #2")
    }).then(() => {
      return entityManager.runTransaction((transaction) => {
        return transaction.persist(new Entity({
          invalid: document.createElement("div")
        }))
      })
    }).then(() => {
      let error = new Error("The transaction should have been aborted")
      error.failed = true
      throw error
    }).catch((error) => {
      expect(error.failed).toBeFalsy()
    })
  })

  promiseIt("should not contain entities retrieved outside a transaction",
      () => {
    return entityManager.find(Entity, 1).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
      expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeFalsy()
    })
  })

  promiseIt("should contain entities retrieved within a transaction", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity) => {
        expect(entityManager.contains(entity)).toBeTruthy()
        expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeTruthy()
        return entity
      })
    }).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
      expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeFalsy()
    })
  })

  promiseIt("should not contain entities after transaction completes", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1)
    }).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
    })
  })

  promiseIt("should resolve find() to distinct instances outside " +
      "transaction", () => {
    return entityManager.find(Entity, 1).then((entity) => {
      return entityManager.find(Entity, 1).then(entity2 => [entity, entity2])
    }).then(([entity1, entity2]) => {
      expect(entity1).not.toBe(entity2)
      expect(entity1.id).toBe(1)
      expect(entity2.id).toBe(1)
    })
  })

  promiseIt("should resolve find() to the contained entity", () => {
    return entityManager.runTransaction(() => {
      return Promise.all([
        entityManager.find(Entity, 1),
        entityManager.find(Entity, 1)
      ])
    }).then(([entity1, entity2]) => {
      expect(entity1).toBe(entity2)
      expect(entity1.id).toBe(1)

      return entityManager.find(Entity, 1).then(entity => [entity, entity1])
    }).then(([entity1, entity2]) => {
      // persistence context is cleared after transaction
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("should perform read queries", () => {
    return entityManager.query(Entity).then((entities) => {
      expect(entities instanceof Array).toBeTruthy()
      expect(entities.length).toBe(2)
      expect(entities.every(entity => entity instanceof Entity)).toBeTruthy()
      expect(entities.some((entity) => {
        return entityManager.contains(entity)
      })).toBeFalsy()
      expect(entities.some((entity) => {
        return entityManager.containsByPrimaryKey(Entity, entity.id)
      })).toBeFalsy()

      return entityManager.query(Entity, { id: 2 })
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(2)

      return entityManager.query(Entity, null, ["!id"])
    }).then((entities) => {
      expect(entities[0].id).toBe(2)
      expect(entities[1].id).toBe(1)

      return entityManager.query(Entity, null, null, 1)
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(2)

      return entityManager.query(Entity, null, null, 0, 1)
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(1)
    })
  })

  promiseIt("should use the same entity store for find() and query()", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity) => {
        return entityManager.query(Entity, 1).then(([entity2]) => {
          return [entity, entity2]
        })
      }).then(([entity1, entity2]) => {
        expect(entity1).toBe(entity2)

        return entityManager.query(Entity, 2)
      }).then(([entity]) => {
        return entityManager.find(Entity, 2).then(entity2 => [entity, entity2])
      }).then(([entity1, entity2]) => {
        expect(entity1).toBe(entity2)
      })
    })
  })

  promiseIt("should persist new entities", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({})).then((entity) => {
        expect(entityManager.contains(entity)).toBeTruthy()
        let {id} = entity
        expect(entityManager.containsByPrimaryKey(Entity, id)).toBeTruthy()
        return entity
      })
    }).then((entity) => {
      return entityManager.find(Entity, 3).then(entity2 => [entity, entity2])
    }).then(([entity1, entity2]) => {
      expect(entity1).toEqual(entity2)
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("should persist an entity outside a transaction", () => {
    return entityManager.persist(new Entity()).then((entity) => {
      return entityManager.find(Entity, entity.id).then((entity2) => {
        return [entity, entity2]
      })
    }).then(([entity1, entity2]) => {
      expect(entity1).toEqual(entity2)
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("must not persist the same entity multiple times", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({ id: 6 })).then(() => {
        return entityManager.persist(new Entity({ id: 6 }))
      })
    }).then(() => {
      throw new Error("fail this test")
    }).catch((error) => {
      expect(error.message).not.toBe("fail this test")
    })
  })

  promiseIt("should use shared context for find() and persist()", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({})).then((entity) => {
        return entityManager.find(Entity, entity.id).then((entity2) => {
          return [entity, entity2]
        })
      })
    }).then(([entity1, entity2]) => {
      expect(entity1).toBe(entity2)
    })
  })

})
<commit_msg>tests for removing entities and update queries<commit_after>
import DBFactory from "../node_modules/indexed-db.es6/es2015/DBFactory"
import AbstractEntity from "../es2015/AbstractEntity"
import EntityManager from "../es2015/EntityManager"
import Transaction from "../es2015/Transaction"
import {promiseIt, delay} from "./testUtils"

fdescribe("Entity manager", () => {

  const DB_NAME = "testingDB"
  const OBJECT_STORE_NAME = "foo"

  let database
  let databasePromise
  let entityManager

  class Entity extends AbstractEntity {
    static get objectStore() {
      return OBJECT_STORE_NAME
    }
  }

  beforeEach((done) => {
    let rawRecords = [
      {
        bar: "baz"
      },
      {
        created: new Date()
      }
    ]

    databasePromise = DBFactory.open(DB_NAME, {
      version: 1,
      objectStores: [{
        name: OBJECT_STORE_NAME,
        keyPath: "id",
        autoIncrement: true
      }]
    }).then((db) => {
      database = db
      let transaction = database.startTransaction(OBJECT_STORE_NAME)

      for (let record of rawRecords) {
        let objectStore = transaction.getObjectStore(OBJECT_STORE_NAME)
        objectStore.add(record)
      }

      return transaction.completionPromise
    }).then(() => {
      entityManager = new EntityManager(databasePromise, {
        idleTransactions: {
          ttl: 10000,
          warningDelay: 2000,
          observer: (transaction, isAborted) => {
            if (isAborted) {
              console.error("A transaction has been aborted due to being " +
                  "idle for too long")
            } else {
              console.warn("A transaction is idle for too long")
            }
          }
        }
      }, new Map([[OBJECT_STORE_NAME, "id"]]))

      done()
      return database
    }).catch((error) => {
      fail(error)
      done()
    })
  })

  afterEach((done) => {
    database.close().then(() => {
      return DBFactory.deleteDatabase(DB_NAME)
    }).then(() => {
      done()
    }).catch((error) => {
      fail(error)
      done()
    })
  })

  promiseIt("should be able to fetch single entities", () => {
    return entityManager.find(Entity, 1).then((entity) => {
      expect(entity instanceof Entity).toBeTruthy()
      expect(Object.assign({}, entity)).toEqual({ id: 1, bar: "baz" })
    })
  })

  promiseIt("should resolve to null if fetching non-existing entities", () => {
    return entityManager.find(Entity, 10).then((entity) => {
      expect(entity).toBeNull()

      return entityManager.find(Entity, new Date(1))
    }).then((entity) => {
      expect(entity).toBeNull()
    })
  })

  promiseIt("should start a read-write transaction", () => {
    let transaction = entityManager.startTransaction()

    return transaction.persist(new Entity({
      foo: "bar"
    })).then(() => {
      return transaction.commit()
    })
  })

  promiseIt("must throw an error when starting multiple transactions", () => {
    let transaction = entityManager.startTransaction()
    expect(() => {
      entityManager.startTransaction()
    }).toThrow()

    return delay(100).then(() => {
      expect(() => {
        entityManager.startTransaction()
      }).toThrow()

      let commitPromise = transaction.commit()
      expect(() => {
        entityManager.startTransaction()
      }).toThrow()
      return commitPromise
    }).then(() => {
      return entityManager.startTransaction().commit()
    })
  })

  promiseIt("should start a long-running transaction", () => {
    let transaction = entityManager.startTransaction()
    return delay(100).then(() => {
      return transaction.persist(new Entity({}))
    }).then((entity) => {
      return delay(100).then(() => entity)
    }).then((entity) => {
      return transaction.commit().then(() => entity.id)
    }).then((id) => {
      expect(id).toBe(3)
    })
  })

  promiseIt("should run a transaction", () => {
    return entityManager.runTransaction((transaction) => {
      expect(transaction instanceof Transaction).toBeTruthy()

      return transaction.persist(new Entity({}))
    }).then((entity) => {
      expect(entity.id).toBe(3)
    })
  })

  promiseIt("should abort the ran transaction if an error is thrown", () => {
    return entityManager.runTransaction((transaction) => {
      transaction.persist(new Entity({}))
      throw new Error("testing, testing")
    }).then(() => {
      throw new Error("The transaction should have been aborted")
    }).catch((error) => {
      expect(error.message).toBe("testing, testing")
    }).then(() => {
      return entityManager.runTransaction(() => {
        return delay(100).then(() => Promise.reject(new Error("test #2")))
      })
    }).then(() => {
      throw new Error("The transaction should have been aborted")
    }).catch((error) => {
      expect(error.message).toBe("test #2")
    }).then(() => {
      return entityManager.runTransaction((transaction) => {
        return transaction.persist(new Entity({
          invalid: document.createElement("div")
        }))
      })
    }).then(() => {
      let error = new Error("The transaction should have been aborted")
      error.failed = true
      throw error
    }).catch((error) => {
      expect(error.failed).toBeFalsy()
    })
  })

  promiseIt("should not contain entities retrieved outside a transaction",
      () => {
    return entityManager.find(Entity, 1).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
      expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeFalsy()
    })
  })

  promiseIt("should contain entities retrieved within a transaction", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity) => {
        expect(entityManager.contains(entity)).toBeTruthy()
        expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeTruthy()
        return entity
      })
    }).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
      expect(entityManager.containsByPrimaryKey(Entity, 1)).toBeFalsy()
    })
  })

  promiseIt("should not contain entities after transaction completes", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1)
    }).then((entity) => {
      expect(entityManager.contains(entity)).toBeFalsy()
    })
  })

  promiseIt("should resolve find() to distinct instances outside " +
      "transaction", () => {
    return entityManager.find(Entity, 1).then((entity) => {
      return entityManager.find(Entity, 1).then(entity2 => [entity, entity2])
    }).then(([entity1, entity2]) => {
      expect(entity1).not.toBe(entity2)
      expect(entity1.id).toBe(1)
      expect(entity2.id).toBe(1)
    })
  })

  promiseIt("should resolve find() to the contained entity", () => {
    return entityManager.runTransaction(() => {
      return Promise.all([
        entityManager.find(Entity, 1),
        entityManager.find(Entity, 1)
      ])
    }).then(([entity1, entity2]) => {
      expect(entity1).toBe(entity2)
      expect(entity1.id).toBe(1)

      return entityManager.find(Entity, 1).then(entity => [entity, entity1])
    }).then(([entity1, entity2]) => {
      // persistence context is cleared after transaction
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("should perform read queries", () => {
    return entityManager.query(Entity).then((entities) => {
      expect(entities instanceof Array).toBeTruthy()
      expect(entities.length).toBe(2)
      expect(entities.every(entity => entity instanceof Entity)).toBeTruthy()
      expect(entities.some((entity) => {
        return entityManager.contains(entity)
      })).toBeFalsy()
      expect(entities.some((entity) => {
        return entityManager.containsByPrimaryKey(Entity, entity.id)
      })).toBeFalsy()

      return entityManager.query(Entity, { id: 2 })
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(2)

      return entityManager.query(Entity, null, ["!id"])
    }).then((entities) => {
      expect(entities[0].id).toBe(2)
      expect(entities[1].id).toBe(1)

      return entityManager.query(Entity, null, null, 1)
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(2)

      return entityManager.query(Entity, null, null, 0, 1)
    }).then((entities) => {
      expect(entities.length).toBe(1)
      expect(entities[0].id).toBe(1)
    })
  })

  promiseIt("should use the same entity store for find() and query()", () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity) => {
        return entityManager.query(Entity, 1).then(([entity2]) => {
          return [entity, entity2]
        })
      }).then(([entity1, entity2]) => {
        expect(entity1).toBe(entity2)

        return entityManager.query(Entity, 2)
      }).then(([entity]) => {
        return entityManager.find(Entity, 2).then(entity2 => [entity, entity2])
      }).then(([entity1, entity2]) => {
        expect(entity1).toBe(entity2)
      })
    })
  })

  promiseIt("should persist new entities", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({})).then((entity) => {
        expect(entityManager.contains(entity)).toBeTruthy()
        let {id} = entity
        expect(entityManager.containsByPrimaryKey(Entity, id)).toBeTruthy()
        return entity
      })
    }).then((entity) => {
      return entityManager.find(Entity, 3).then(entity2 => [entity, entity2])
    }).then(([entity1, entity2]) => {
      expect(entity1).toEqual(entity2)
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("should persist an entity outside a transaction", () => {
    return entityManager.persist(new Entity()).then((entity) => {
      return entityManager.find(Entity, entity.id).then((entity2) => {
        return [entity, entity2]
      })
    }).then(([entity1, entity2]) => {
      expect(entity1).toEqual(entity2)
      expect(entity1).not.toBe(entity2)
    })
  })

  promiseIt("must not persist the same entity multiple times", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({ id: 6 })).then(() => {
        return entityManager.persist(new Entity({ id: 6 }))
      })
    }).then(() => {
      throw new Error("fail this test")
    }).catch((error) => {
      expect(error.message).not.toBe("fail this test")
    })
  })

  promiseIt("should use shared context for find() and persist()", () => {
    return entityManager.runTransaction(() => {
      return entityManager.persist(new Entity({})).then((entity) => {
        return entityManager.find(Entity, entity.id).then((entity2) => {
          return [entity, entity2]
        })
      })
    }).then(([entity1, entity2]) => {
      expect(entity1).toBe(entity2)
    })
  })

  promiseIt("should remove existing entities", () => {
    return entityManager.remove(Entity, 1).then(() => {
      return entityManager.find(Entity, 1)
    }).then((entity) => {
      expect(entity).toBeNull()
    })
  })

  promiseIt("should do nothing when removing non-existing entities", () => {
    return entityManager.remove(Entity, -1).then(() => {
      return entityManager.query(Entity)
    }).then((entities) => {
      expect(entities.length).toBe(2)
    })
  })

  promiseIt("should remove deleted entities from the persistence context",
      () => {
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity) => {
        expect(entityManager.contains(entity)).toBeTruthy()

        return entityManager.remove(Entity, 1).then(() => entity)
      }).then((entity) => {
        expect(entityManager.contains(entity)).toBeFalsy()
      })
    })
  })

  promiseIt("should perform update queries", () => {
    return entityManager.updateQuery(Entity, 2)((entity) => {
      expect(entity instanceof Entity).toBeTruthy()
      expect(entity.id).toBe(2)
      entity.marked1 = 1
    }).then((updatedCount) => {
      expect(updatedCount).toBe(1)

      return entityManager.query(Entity, { marked1: 1 })
    }).then((matchingEntities) => {
      expect(matchingEntities.length).toBe(1)

      return entityManager.updateQuery(
        Entity,
        null,
        "prev",
        0,
        1
      )((entity) => {
        entity.marked2 = "foo"
      })
    }).then((updatedEntitiesCount) => {
      expect(updatedEntitiesCount).toBe(1)

      return entityManager.query(Entity, { marked2: "foo" })
    }).then((entities) => {
      expect(entities.length).toBe(1)
    })
  })

  promiseIt("should use shared persistence context between find() and " +
      "updateQuery()", () => {
    let entity2
    return entityManager.runTransaction(() => {
      return entityManager.find(Entity, 1).then((entity1) => {
        return entityManager.updateQuery(Entity, 1)((entity) => {
          expect(entity).toBe(entity1)
        })
      }).then(() => {
        return entityManager.updateQuery(Entity, 2)((entity) => {
          entity2 = entity
        })
      }).then(() => {
        return entityManager.find(Entity, 2)
      }).then((entity) => {
        expect(entity).toBe(entity2)
      })
    })
  })

})
<|endoftext|>


<commit_before>// If something's wrong with the options (generally, if they aren't set yet),
// clear all options and reset to defaults.
// TODO: This is duplicated in options.js. Combine these somehow.
// TODO: Updating isn't exactly graceful. Find a better way to do this.
if (!localStorage.clockMode) localStorage.clockMode = false;
if (!localStorage.teams) localStorage.teams = undefined;
if (!localStorage.name) localStorage.name = true;
if (!localStorage.location) localStorage.location = true;
if (!localStorage.vetting) localStorage.vetting = true;
if (!localStorage.optionsButton) localStorage.optionsButton = true;

// Alias major page elements so we don't have to keep getting them by ID
var el = {
	number: document.getElementById('number'),
	name: document.getElementById('name'),
	location: document.getElementById('location'),
	bg: document.getElementById('background'),
	options: document.getElementById('options')
};

function importData() {
	// If custom teams value is truthy, valid, and not an empty array,
	// replace the data.js teams array with the custom one from localStorage.
	if (localStorage.teams !== '[]' && localStorage.teams !== '' && localStorage.teams !== 'undefined') {
		// TEMPORARY: If the 'teams' data has brackets around it, remove the brackets.
		if (localStorage.teams[0] === '[' || localStorage.teams[localStorage.teams.length - 1] === ']') {
			localStorage.teams = localStorage.teams.substring(1, localStorage.teams.length - 1);
		}
		teams = JSON.parse('[' + localStorage.teams + ']');
	}
}

function loadTeam() {
	// Initialize int-type team number variable
	var teamNum;
	// and string-type team number var (used more for clock mode)
	var teamNumStr;

	// If clock mode is turned on, get the team that corresponds to the current time.
	if (JSON.parse(localStorage.clockMode)) {
		// Get date, current hour, and current minute.
		var d = new Date();
		var hours = d.getHours();
		var minutes = d.getMinutes();
		// Construct team number
		teamNum = hours * 100 + minutes;
		if (minutes < 10) {
			minutes = '0' + minutes;
		}
		teamNumStr = hours + ':' + minutes;
	} else {
		// If clock mode is off, choose a random team.
		teamNum = teams[parseInt(Math.random() * teams.length)];
		teamNumStr = teamNum.toString();
	}

	var teamInfo = {
		num: teamNum,
		numStr: teamNumStr
	}

	return teamInfo;
}

function showOptions() {
	// If the option to show the options button is on, show it.
	// (If it's not, it will remain hidden as normal.)
	if (JSON.parse(localStorage.optionsButton)) el.options.style.display = 'block';
}

function getTBAData(teamInfo) {
	try {
		// Create request to get data from TBA
		var req = new XMLHttpRequest();
		// Get data for team
		req.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamInfo.num + '?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
		// Send empty data for conclusion
		req.send();
		// When the data is ready, figure out where an image is and get ready to set it as the background.
		req.onreadystatechange = function() {
			if (req.readyState == 4 && req.status == 200) {
				// Parse the newly-fetched team data into JSON to get it ready to be used
				team = JSON.parse(req.responseText);

				// Put team number on page if the team exists
				el.number.innerHTML = '' + teamInfo.numStr + '';

				// If name showing is enabled,
				if (JSON.parse(localStorage.name)) {
					// If team has a website that isn't "Coming Soon"
					if (team.website && team.website !== 'Coming Soon') {
						// Make the name a link to the website
						el.name.innerHTML = '' + team.nickname + '';
					} else {
						// Insert name without a link
						el.name.innerHTML = team.nickname;
					}
				} else {
					// Otherwise, delete the name element from the DOM.
					el.name.parentNode.removeChild(el.name);
				}

				// If location showing is on,
				if (JSON.parse(localStorage.location)) {
					// Then set the location onscreen.
					el.location.innerHTML = team.location;
				} else {
					// Otherwise, remove the location element from the DOM.
					el.location.parentNode.removeChild(el.location);
				}

				if (JSON.parse(localStorage.dynamicTitle)) {
					// Display the team name and number in titlebar.
					el.number.insertAdjacentHTML('beforeend', '<title>' + teamInfo.num + ' - ' + team.nickname + '</title>');
				}
			} else if (req.readyState == 4 && req.status != 200) {
				// If the team doesn't exist (for clock mode)
				el.number.innerHTML = teamInfo.numStr;
			}
		};
	} catch (e) {}
}

function renderImage(src) {
	console.log('Rendering', src);
	// Create image. This will be used to check if the image is smaller than the window.
	var img = new Image();
	// Give it the proper source.
	img.src = src;
	// When the source is done loading,
	img.onload = function() {
		// Check if the image is smaller than the window.
		if (img.naturalWidth >= window.innerWidth) {
			// If it is, blur the background image.
			el.bg.style['-webkit-filter'] = 'none';
		}
	};

	// Set the src of the real background image.
	el.bg.style.backgroundImage = 'url(' + src + ')';
}

function getImage() {
	try {
		// Make a new request to get list of team media from TBA
		var mediaReq = new XMLHttpRequest();

		// When the data is ready,
		mediaReq.onreadystatechange = function() {
			if (mediaReq.readyState == 4 && mediaReq.status == 200) {
				// Parse data for processing
				var media = [];
				try {
					media = JSON.parse(mediaReq.responseText);
				} catch (e) {}
				console.log(media.length);
				if (media.length > 0) {
					var images = [];
					// Go through every piece of media and add high quality images to an array
					for (i = 0; i < media.length; i++) {
						// Find media that's an image

						if ((media[i].type === 'imgur' || media[i].type === 'cdphotothread')) {
							// If vetting is on and the image is high quality add it to the array
							if (JSON.parse(localStorage.vetting)) {
								if (media[i].preferred) images.push(i);
							} else {
								images.push(i); // Otherwise add every image
							}
						}
					}
					// Grab a random image if vetting is enabled; otherwise grab the first image
					var target = JSON.parse(localStorage.vetting) ? images[Math.floor(Math.random()*images.length)] : 0;
					// Check where the media is sourced from. Use this to build a link to the image.
					if (target !== null) {
						switch (media[target].type) {
							case 'imgur':
								src = 'http://i.imgur.com/' + media[target].foreign_key + '.png';
								break;
							case 'cdphotothread':
								src = 'https://www.chiefdelphi.com/media/img/' + media[target].details.image_partial;
								break;
						}
					}
				}
				// Put the image into the background (see below).
				renderImage();
			} else if (mediaReq.readyState == 4 && mediaReq.status != 200) {
				// Media GET request failed - render a stock image
			}
		};
		// Actually open the request.
		mediaReq.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamNum + '/media?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
		mediaReq.send();
	} catch (e) {}
	// If there's an error, we'll just render the random image from earlier

	return src;
}

// Import data.js
importData();

// Get and load team number
// Converts to clock mode if appropriate
teamInfo = loadTeam();

// Show options icon if appropriate
showOptions();

// Get data from TBA
getTBAData(teamInfo);

// Initialize background image source as a randomly-chosen fallback image.
var src = '../res/bg/' + (Math.floor(Math.random() * 10) + 1) + '.jpg';

// Get an image source from TBA
src = getImage();

// Render image on html page
renderImage(src);

// TODO: Make this whole process more efficient.
<commit_msg>Fix teamNum reference<commit_after>// If something's wrong with the options (generally, if they aren't set yet),
// clear all options and reset to defaults.
// TODO: This is duplicated in options.js. Combine these somehow.
// TODO: Updating isn't exactly graceful. Find a better way to do this.
if (!localStorage.clockMode) localStorage.clockMode = false;
if (!localStorage.teams) localStorage.teams = undefined;
if (!localStorage.name) localStorage.name = true;
if (!localStorage.location) localStorage.location = true;
if (!localStorage.vetting) localStorage.vetting = true;
if (!localStorage.optionsButton) localStorage.optionsButton = true;

// Alias major page elements so we don't have to keep getting them by ID
var el = {
	number: document.getElementById('number'),
	name: document.getElementById('name'),
	location: document.getElementById('location'),
	bg: document.getElementById('background'),
	options: document.getElementById('options')
};

function importData() {
	// If custom teams value is truthy, valid, and not an empty array,
	// replace the data.js teams array with the custom one from localStorage.
	if (localStorage.teams !== '[]' && localStorage.teams !== '' && localStorage.teams !== 'undefined') {
		// TEMPORARY: If the 'teams' data has brackets around it, remove the brackets.
		if (localStorage.teams[0] === '[' || localStorage.teams[localStorage.teams.length - 1] === ']') {
			localStorage.teams = localStorage.teams.substring(1, localStorage.teams.length - 1);
		}
		teams = JSON.parse('[' + localStorage.teams + ']');
	}
}

function loadTeam() {
	// Initialize int-type team number variable
	var teamNum;
	// and string-type team number var (used more for clock mode)
	var teamNumStr;

	// If clock mode is turned on, get the team that corresponds to the current time.
	if (JSON.parse(localStorage.clockMode)) {
		// Get date, current hour, and current minute.
		var d = new Date();
		var hours = d.getHours();
		var minutes = d.getMinutes();
		// Construct team number
		teamNum = hours * 100 + minutes;
		if (minutes < 10) {
			minutes = '0' + minutes;
		}
		teamNumStr = hours + ':' + minutes;
	} else {
		// If clock mode is off, choose a random team.
		teamNum = teams[parseInt(Math.random() * teams.length)];
		teamNumStr = teamNum.toString();
	}

	var teamInfo = {
		num: teamNum,
		numStr: teamNumStr
	}

	return teamInfo;
}

function showOptions() {
	// If the option to show the options button is on, show it.
	// (If it's not, it will remain hidden as normal.)
	if (JSON.parse(localStorage.optionsButton)) el.options.style.display = 'block';
}

function getTBAData(teamInfo) {
	try {
		// Create request to get data from TBA
		var req = new XMLHttpRequest();
		// Get data for team
		req.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + teamInfo.num + '?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
		// Send empty data for conclusion
		req.send();
		// When the data is ready, figure out where an image is and get ready to set it as the background.
		req.onreadystatechange = function() {
			if (req.readyState == 4 && req.status == 200) {
				// Parse the newly-fetched team data into JSON to get it ready to be used
				team = JSON.parse(req.responseText);

				// Put team number on page if the team exists
				el.number.innerHTML = '' + teamInfo.numStr + '';

				// If name showing is enabled,
				if (JSON.parse(localStorage.name)) {
					// If team has a website that isn't "Coming Soon"
					if (team.website && team.website !== 'Coming Soon') {
						// Make the name a link to the website
						el.name.innerHTML = '' + team.nickname + '';
					} else {
						// Insert name without a link
						el.name.innerHTML = team.nickname;
					}
				} else {
					// Otherwise, delete the name element from the DOM.
					el.name.parentNode.removeChild(el.name);
				}

				// If location showing is on,
				if (JSON.parse(localStorage.location)) {
					// Then set the location onscreen.
					el.location.innerHTML = team.location;
				} else {
					// Otherwise, remove the location element from the DOM.
					el.location.parentNode.removeChild(el.location);
				}

				if (JSON.parse(localStorage.dynamicTitle)) {
					// Display the team name and number in titlebar.
					el.number.insertAdjacentHTML('beforeend', '<title>' + teamInfo.num + ' - ' + team.nickname + '</title>');
				}
			} else if (req.readyState == 4 && req.status != 200) {
				// If the team doesn't exist (for clock mode)
				el.number.innerHTML = teamInfo.numStr;
			}
		};
	} catch (e) {}
}

function renderImage(src) {
	console.log('Rendering', src);
	// Create image. This will be used to check if the image is smaller than the window.
	var img = new Image();
	// Give it the proper source.
	img.src = src;
	// When the source is done loading,
	img.onload = function() {
		// Check if the image is smaller than the window.
		if (img.naturalWidth >= window.innerWidth) {
			// If it is, blur the background image.
			el.bg.style['-webkit-filter'] = 'none';
		}
	};

	// Set the src of the real background image.
	el.bg.style.backgroundImage = 'url(' + src + ')';
}

function getImage(info) {
	try {
		// Make a new request to get list of team media from TBA
		var mediaReq = new XMLHttpRequest();

		// When the data is ready,
		mediaReq.onreadystatechange = function() {
			if (mediaReq.readyState == 4 && mediaReq.status == 200) {
				// Parse data for processing
				var media = [];
				try {
					media = JSON.parse(mediaReq.responseText);
				} catch (e) {}
				console.log(media.length);
				if (media.length > 0) {
					var images = [];
					// Go through every piece of media and add high quality images to an array
					for (i = 0; i < media.length; i++) {
						// Find media that's an image

						if ((media[i].type === 'imgur' || media[i].type === 'cdphotothread')) {
							// If vetting is on and the image is high quality add it to the array
							if (JSON.parse(localStorage.vetting)) {
								if (media[i].preferred) images.push(i);
							} else {
								images.push(i); // Otherwise add every image
							}
						}
					}
					// Grab a random image if vetting is enabled; otherwise grab the first image
					var target = JSON.parse(localStorage.vetting) ? images[Math.floor(Math.random()*images.length)] : 0;
					// Check where the media is sourced from. Use this to build a link to the image.
					if (target !== null) {
						switch (media[target].type) {
							case 'imgur':
								src = 'http://i.imgur.com/' + media[target].foreign_key + '.png';
								break;
							case 'cdphotothread':
								src = 'https://www.chiefdelphi.com/media/img/' + media[target].details.image_partial;
								break;
						}
					}
				}
				// Put the image into the background (see below).
				renderImage();
			} else if (mediaReq.readyState == 4 && mediaReq.status != 200) {
				// Media GET request failed - render a stock image
			}
		};
		// Actually open the request.
		mediaReq.open('GET', 'https://www.thebluealliance.com/api/v2/team/frc' + info.num + '/media?X-TBA-App-Id=erikboesen:frcnewtab:v1.0');
		mediaReq.send();
	} catch (e) {}
	// If there's an error, we'll just render the random image from earlier

	return src;
}

// Import data.js
importData();

// Get and load team number
// Converts to clock mode if appropriate
teamInfo = loadTeam();

// Show options icon if appropriate
showOptions();

// Get data from TBA
getTBAData(teamInfo);

// Initialize background image source as a randomly-chosen fallback image.
var src = '../res/bg/' + (Math.floor(Math.random() * 10) + 1) + '.jpg';

// Get an image source from TBA
src = getImage();

// Render image on html page
renderImage(src);

// TODO: Make this whole process more efficient.
<|endoftext|>


<commit_before><commit_msg>Delete empty file.<commit_after><|endoftext|>


<commit_before>'use strict';

var assert = require('assert');

var Analysis = require('../../lib/analysis');

var testConfig = require('../test-config');
var QueryRunner = require('../../lib/postgresql/query-runner');

describe('trade-area analysis', function() {

    var queryRunner;

    before(function() {
        queryRunner = new QueryRunner(testConfig.db);
    });

    var QUERY = 'select * from atm_machines limit 2';
    var KIND = 'car';
    var TIME = 600;
    var ISOLINES = 4;

    var sourceAtmMachines = {
        type: 'source',
        params: {
            query: QUERY
        }
    };

    var config = testConfig.create({
        batch: {
            inlineExecution: true
        }
    });

    function performAnalysis(definition, callback) {
        Analysis.create(config, definition, function (err, analysis) {
            if (err) {
                return callback(err);
            }

            queryRunner.run(analysis.getQuery(), function(err, result) {
                if (err) {
                    return callback(err);
                }

                assert.ok(Array.isArray(result.rows));
                var values = result.rows.map(function (value) {
                    return value;
                });

                callback(null, values);
            });
        });
    }

    describe('trade area analysis', function () {
        var tradeAreaDefinition = {
            type: 'trade-area',
            params: {
                source: sourceAtmMachines,
                kind: KIND,
                time: TIME,
                isolines: ISOLINES,
                dissolved: false
            }
        };

        it('should create an analysis', function (done) {
            performAnalysis(tradeAreaDefinition, function (err, values) {
                if(err) {
                    return done(err);
                }
                assert.ok(values);
                done();
            });
        });
    });

    describe('trade area analysis dissolved', function () {
        var tradeAreaDefinition = {
            type: 'trade-area',
            params: {
                source: sourceAtmMachines,
                kind: KIND,
                time: TIME,
                isolines: ISOLINES,
                dissolved: true
            }
        };

        it('should create an analysis with boudaries dissolved', function (done) {
            performAnalysis(tradeAreaDefinition, function (err, values) {
                if(err) {
                    return done(err);
                }
                assert.ok(values);
                done();
            });
        });
    });
});
<commit_msg>Used test-helper utility<commit_after>'use strict';

var assert = require('assert');
var testHelper = require('../helper');

describe('trade-area analysis', function() {
    var QUERY = 'select * from atm_machines limit 2';
    var KIND = 'car';
    var TIME = 600;
    var ISOLINES = 4;

    var sourceAtmMachines = {
        type: 'source',
        params: {
            query: QUERY
        }
    };

    describe('trade area analysis', function () {
        var tradeAreaDefinition = {
            type: 'trade-area',
            params: {
                source: sourceAtmMachines,
                kind: KIND,
                time: TIME,
                isolines: ISOLINES,
                dissolved: false
            }
        };

        it('should create an analysis', function (done) {
            testHelper.createAnalyses(tradeAreaDefinition, function(err, tradeArea) {
                assert.ifError(err);

                var rootNode = tradeArea.getRoot();

                testHelper.getRows(rootNode.getQuery(), function(err, rows) {
                    assert.ifError(err);
                    rows.forEach(function(row) {
                        assert.ok(typeof row.cartodb_id === 'number');
                        assert.ok(typeof row.the_geom === 'string');
                        assert.ok(typeof row.data_range === 'number');
                    });

                    return done();
                });
            });
        });
    });

    describe('trade area analysis dissolved', function () {
        var tradeAreaDefinition = {
            type: 'trade-area',
            params: {
                source: sourceAtmMachines,
                kind: KIND,
                time: TIME,
                isolines: ISOLINES,
                dissolved: true
            }
        };

        it('should create an analysis with boudaries dissolved', function (done) {
            testHelper.createAnalyses(tradeAreaDefinition, function(err, tradeArea) {
                assert.ifError(err);

                var rootNode = tradeArea.getRoot();

                testHelper.getRows(rootNode.getQuery(), function(err, rows) {
                    assert.ifError(err);
                    rows.forEach(function(row) {
                        // FIXME: should return cartodb_id
                        // assert.ok(typeof row.cartodb_id === 'number');
                        assert.ok(typeof row.the_geom === 'string');
                        assert.ok(typeof row.data_range === 'number');
                    });

                    return done();
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>/**
* Story controller
* Used to get process info about a story such as url, title, and type
*/
HN.Story = (function(){

	function HNStory(storyInfo){
		this.storyInfo = storyInfo;
	};

	HNStory.prototype.url = function() {
		//for ask or show HN stories
		if(this.isLocalHNUrl()){
			return HN.askUrl + this.storyInfo['id'];
		}
		return this.storyInfo['url'];
	};
	HNStory.prototype.isLocalHNUrl = function(){
		if(!this.storyInfo['url']){
			return true;
		}
		return false;
	};

	/**
	* gets the url root domain for display with story
	* http://www.nytimes.com/story becomes (nytimes.com)
	*/
	HNStory.prototype.urlRoot = function() {
		//for ask or show HN stories
		if(this.isLocalHNUrl()){
			return '';
		}
		//removes first part of url (http://www.)
		var base_url = this.url();
		base_url =  base_url.replace(/^http(s)?:\/\/(www.)?/, '');
		//removes subfolders from url (/index.html)
		base_url = base_url.replace(/\/.*$/, '');
		//removes get requests from url (?q=something)
		return '(' + base_url.replace(/\?.*$/,'') + ')';
	};
	HNStory.prototype.title = function(){
		return this.storyInfo['title'] || '';
	};
	HNStory.prototype.storyType = function(){
		if(this.type() !== 'story'){
			return false;
		}
		if(this.isLocalHNUrl()){
			if(this.title().substring(0,3).toLowerCase() === 'ask'){
				return 'ask';
			}
			return 'show';
		}
		return 'story';
	};
	HNStory.prototype.type = function(){
		return this.storyInfo['type'];
	};
	HNStory.prototype.numComments = function(){
		if(!this.storyInfo['descendants']){
			return 0;
		}
		return this.storyInfo['descendants'];
	};
	HNStory.prototype.getTopLevelCommentsIds = function(){
		if(!this.storyInfo['kids']){
			return [];
		}
		return this.storyInfo['kids'];
	};
	HNStory.prototype.commentsUrl = function(){
		// return 'https://news.ycombinator.com/item?id=' + this.storyInfo['id'];
		return HN.commentsUrl + this.storyInfo['id'];
	};
	return HNStory;
})();
<commit_msg>add getters for text and author on story prototype<commit_after>/**
* Story controller
* Used to get process info about a story such as url, title, and type
*/
HN.Story = (function(){

	function HNStory(storyInfo){
		this.storyInfo = storyInfo;
	};

	HNStory.prototype.url = function() {
		//for ask or show HN stories
		if(this.isLocalHNUrl()){
			return HN.askUrl + this.storyInfo['id'];
		}
		return this.storyInfo['url'];
	};
	HNStory.prototype.isLocalHNUrl = function(){
		if(!this.storyInfo['url']){
			return true;
		}
		return false;
	};

	/**
	* gets the url root domain for display with story
	* http://www.nytimes.com/story becomes (nytimes.com)
	*/
	HNStory.prototype.urlRoot = function() {
		//for ask or show HN stories
		if(this.isLocalHNUrl()){
			return '';
		}
		//removes first part of url (http://www.)
		var base_url = this.url();
		base_url =  base_url.replace(/^http(s)?:\/\/(www.)?/, '');
		//removes subfolders from url (/index.html)
		base_url = base_url.replace(/\/.*$/, '');
		//removes get requests from url (?q=something)
		return '(' + base_url.replace(/\?.*$/,'') + ')';
	};
	HNStory.prototype.title = function(){
		return this.storyInfo['title'] || '';
	};
	HNStory.prototype.text = function(){
		return this.storyInfo['text'] || '';
	};
	HNStory.prototype.author = function(){
		return this.storyInfo['by'] || '';
	};
	HNStory.prototype.storyType = function(){
		if(this.type() !== 'story'){
			return false;
		}
		if(this.isLocalHNUrl()){
			if(this.title().substring(0,3).toLowerCase() === 'ask'){
				return 'ask';
			}
			return 'show';
		}
		return 'story';
	};
	HNStory.prototype.type = function(){
		return this.storyInfo['type'];
	};
	HNStory.prototype.numComments = function(){
		if(!this.storyInfo['descendants']){
			return 0;
		}
		return this.storyInfo['descendants'];
	};
	HNStory.prototype.getTopLevelCommentsIds = function(){
		if(!this.storyInfo['kids']){
			return [];
		}
		return this.storyInfo['kids'];
	};
	HNStory.prototype.commentsUrl = function(){
		// return 'https://news.ycombinator.com/item?id=' + this.storyInfo['id'];
		return HN.commentsUrl + this.storyInfo['id'];
	};
	return HNStory;
})();
<|endoftext|>


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

'use strict';
var currentType;

// Constants for types of profiles
var PERSONALITY = 0,
    NEEDS = 1,
    VALUES = 2;

/**
 * Display a celebrity and its traits
 * @param  {Object} celebrity the object with the twitter user,
 * profile and distance
 */

function displayCelebrity(celebrity) {
  $('.cel_name').text(celebrity.user.name);
  $('.cel_username').text('@' + celebrity.user.username);
  $('.cel_username').attr('href', 'https://twitter.com/' + celebrity.user.username);
  $('.politicalParty').text(celebrity.user.politicalParty);
  $('.cel_distance').text(Math.round(celebrity.distance * 100) + '%');
  $('.cel_image').attr('src', celebrity.user.image.replace('_normal', '_400x400'));

  var party = celebrity.user.politicalParty;
  var colorText = $(".cel_name, .matchdiv a, .matchdiv p");
  var colorElem = $(".left-bar, .celebrity .third-border, .celebrity .avatar, .celebrityp");
    if (party === "Republican") {
      colorText.css({color: "#D40000"});
      colorElem.css({backgroundColor: "D40000", color: "D40000", borderColor: "D40000"});
    } else if (party === "Democratic") {
      colorText.css({color: "0098ff"});
      colorElem.css({backgroundColor: "0098ff", color: "0098ff", borderColor: "0098ff"});
    } else if (party === "Libertarian") {
      colorText.css({color: "FFCB00"});
      colorElem.css({backgroundColor: "FFCB00", color: "FFCB00", borderColor: "FFCB00"});
    } else if (party === "Green Party") {
      colorText.css({color: "038F19"});
      colorElem.css({backgroundColor: "038F19", color: "038F19", borderColor: "038F19"});
    } else if (party === "Independent") {
      colorText.css({color: "3D3D3D"});
      colorElem.css({backgroundColor: "3D3D3D", color: "3D3D3D", borderColor: "3D3D3D"});
    }
    // var repub = ['realdonaldtrump', 'mike_pence', 'SenJohnMcCain', 'Schwarzenegger', 'SarahPalinUSA', 'glennbeck', 'BobbyJindal', 'MicheleBachmann', 'MittRomney', 'RandPaul', 'SpeakerRyan', 'GovMikeHuckabee', 'JebBush', 'newtgingrich', 'GovernorPerry', 'SpeakerBoehner', 'KarlRove', 'JimDeMint', 'EricCantor', 'jasoninthehouse', 'DarrellIssa','ScottWalker', 'AllenWest', 'ChuckGrassley', 'THEHermanCain', 'RickSantorum', 'ChrisChristie', 'tedcruz', 'SenateMajLdr', 'CondoleezzaRice',];
    // var democ = ['BarackObama', 'HillaryClinton', 'timkaine', 'algore', 'JohnKerry', 'JoeBiden', 'SenSanders', 'CoryBooker', 'NancyPelosi', 'BernieSanders', 'SenatorReid', 'JerryBrownGov', 'GabbyGiffords', 'clairecmc', 'SenWarren', 'NYCMayor', 'alfranken', 'SenGillibrand', 'MartinOMalley', 'MichelleObama', 'madeleine',];
    // var liber = ['GovGaryJohnson', 'RepRonPaul'];
    // var green = ['RalphNader', 'DrJillStein'];
    // var indep = ['MikeBloomberg'];
  // Update traits
  var idType;
  if (currentType === PERSONALITY) idType = '#personality_trait_';
  else if (currentType === NEEDS) idType = '#needs_trait_';
  else if (currentType === VALUES) idType = '#values_trait_';
  celebrity.profile.forEach(function(trait, i) {
    $(idType + i).css('left', 'calc(' + (trait.value * 100) + '%)');
  });
}

$('.avatar-small').click(function(e) {
  var celebrity;
  var id = $(this).find('img').prop('id');
  if (id.match('^s_')) {
    if (currentType === PERSONALITY) celebrity = similar_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = similar_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = similar_values[id.slice(2)];
  }
  else {
    if (currentType === PERSONALITY) celebrity = different_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = different_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = different_values[id.slice(2)];
  }

  if (celebrity)
    displayCelebrity(celebrity);
  else
    console.log('celebrity not found!');

});

// CHANGE OVERLAY COLOR
  $('.overlay').on('load', function(celebrity) {
    var party = celebrity.user.politicalParty;
     if (party === 'Republican') {
        $(this).addClass('overlay-red');
      } else if (party === 'Democratic') {
        $(this).addClass('overlay-blue');
      } else if (party === 'Libertarian') {
        $(this).addClass('overlay-yellow');
      } else if (party === 'Green Party') {
        $(this).addClass('overlay-green');
      } else if (party === 'Independent') {
        $(this).addClass('overlay-grey');
      }
  });

/**
 * Makes updates for a profile type switch
 */
function switchTypeUpdates(type, newCeleb, showClass, hideClass1, hideClass2, showSwitch, hideSwitch1, hideSwitch2, typeDesc) {
  if (currentType === type)
    return;
  currentType = type;
  displayCelebrity(newCeleb);

  // Make DOM class updates
  $(showClass).removeClass('hide-traits');
  $(hideClass1).addClass('hide-traits');
  $(hideClass2).addClass('hide-traits');
  $(showSwitch).addClass('switch-button-active');
  $(showSwitch).removeClass('switch-button-disabled');
  $(hideSwitch1).addClass('switch-button-disabled');
  $(hideSwitch1).removeClass('switch-button-active');
  $(hideSwitch2).addClass('switch-button-disabled');
  $(hideSwitch2).removeClass('switch-button-active');
  $('.type-desc').text(', your ' + typeDesc + '...');
}

/**
 * On click handlers for changing comparison views
 * Get the celeb id and call displayCelebrity if it exists
 * @param  {Object} e event
 */
$(document).on('click', '.personality-switch', function() {
  switchTypeUpdates(PERSONALITY, similar_personalities[0], '.personality', '.needs', '.values', '.personality-switch', '.needs-switch', '.values-switch', 'personality is');
});
$(document).on('click', '.needs-switch', function() {
  switchTypeUpdates(NEEDS, similar_needs[0], '.needs', '.personality', '.values', '.needs-switch', '.personality-switch', '.values-switch', 'needs are');
});
$(document).on('click', '.values-switch', function() {
  switchTypeUpdates(VALUES, similar_values[0], '.values', '.needs', '.personality', '.values-switch', '.needs-switch', '.personality-switch', 'values are');
});

/**
 * On click handler for celebrity images.
 * Get the celebrity id and call displayCelebrity if it exists
 * @param  {Object} e event
 */
$('.avatar-small').click(function(e) {
  var celebrity;
  var id = $(this).find('img').prop('id');
  if (id.match('^s_')) {
    if (currentType === PERSONALITY) celebrity = similar_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = similar_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = similar_values[id.slice(2)];
  }
  else {
    if (currentType === PERSONALITY) celebrity = different_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = different_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = different_values[id.slice(2)];
  }

  if (celebrity)
    displayCelebrity(celebrity);
  else
    console.log('celebrity not found!');

});

/**
 * On click handler for unhiding .overlay-screen.how-it-worked popup overlay.
 */
$(document).on('click', '.how-it-works-link', function() {
  $('.overlay-screen.how-it-worked').fadeIn(200);
  $('#wrap').css({
    'overflow': 'hidden',
    'height': '100%'
  });
});

/**
 * On click handler for hiding .overlay-screen.how-it-worked popup overlay.
 */
$(document).on('click', '.button.back', function() {
  $('.overlay-screen.how-it-worked').fadeOut(200);
  $('#wrap').css({
    'overflow': 'none',
    'height': 'auto'
  });
});

/**
 * On hover handler for making user's points bigger and celeb's points smaller
 */
$(document).on('mouseenter', '.avatars-row > .me', function() {
  $('.mep').addClass('bigger');
  $('.celebrityp').addClass('smaller');
});
$(document).on('mouseleave', '.avatars-row > .me', function() {
  $('.mep').removeClass('bigger');
  $('.celebrityp').removeClass('smaller');
});

/**
 * On hover handler for making celeb's points bigger and user's points smaller
 */
$(document).on('mouseenter', '.avatars-row > .celebrity', function() {
  $('.mep').addClass('smaller');
  $('.celebrityp').addClass('bigger');
});
$(document).on('mouseleave', '.avatars-row > .celebrity', function() {
  $('.mep').removeClass('smaller');
  $('.celebrityp').removeClass('bigger');
});

/**
 * On load handler for loading images a little more gracefully
 */
$('.page-content img').on('load', function() {
  // $(this).addClass('revealed');
  $(this).addClass('revealed');
}).each(function() {
  if (this.complete) {
    $(this).load();
  }
});

$(document).ready(function() {
  currentType = PERSONALITY;
  displayCelebrity(similar_personalities[0]);
});
<commit_msg>trying function<commit_after>/* Copyright IBM Corp. 2015
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*global $:false */

'use strict';
var currentType;

// Constants for types of profiles
var PERSONALITY = 0,
    NEEDS = 1,
    VALUES = 2;

/**
 * Display a celebrity and its traits
 * @param  {Object} celebrity the object with the twitter user,
 * profile and distance
 */

function displayCelebrity(celebrity) {
  $('.cel_name').text(celebrity.user.name);
  $('.cel_username').text('@' + celebrity.user.username);
  $('.cel_username').attr('href', 'https://twitter.com/' + celebrity.user.username);
  $('.politicalParty').text(celebrity.user.politicalParty);
  $('.cel_distance').text(Math.round(celebrity.distance * 100) + '%');
  $('.cel_image').attr('src', celebrity.user.image.replace('_normal', '_400x400'));

  var party = celebrity.user.politicalParty;
  var colorText = $(".cel_name, .matchdiv a, .matchdiv p");
  var colorElem = $(".left-bar, .celebrity .third-border, .celebrity .avatar, .celebrityp");
    if (party === "Republican") {
      colorText.css({color: "#D40000"});
      colorElem.css({backgroundColor: "D40000", color: "D40000", borderColor: "D40000"});
    } else if (party === "Democratic") {
      colorText.css({color: "0098ff"});
      colorElem.css({backgroundColor: "0098ff", color: "0098ff", borderColor: "0098ff"});
    } else if (party === "Libertarian") {
      colorText.css({color: "FFCB00"});
      colorElem.css({backgroundColor: "FFCB00", color: "FFCB00", borderColor: "FFCB00"});
    } else if (party === "Green Party") {
      colorText.css({color: "038F19"});
      colorElem.css({backgroundColor: "038F19", color: "038F19", borderColor: "038F19"});
    } else if (party === "Independent") {
      colorText.css({color: "3D3D3D"});
      colorElem.css({backgroundColor: "3D3D3D", color: "3D3D3D", borderColor: "3D3D3D"});
    }
    // var repub = ['realdonaldtrump', 'mike_pence', 'SenJohnMcCain', 'Schwarzenegger', 'SarahPalinUSA', 'glennbeck', 'BobbyJindal', 'MicheleBachmann', 'MittRomney', 'RandPaul', 'SpeakerRyan', 'GovMikeHuckabee', 'JebBush', 'newtgingrich', 'GovernorPerry', 'SpeakerBoehner', 'KarlRove', 'JimDeMint', 'EricCantor', 'jasoninthehouse', 'DarrellIssa','ScottWalker', 'AllenWest', 'ChuckGrassley', 'THEHermanCain', 'RickSantorum', 'ChrisChristie', 'tedcruz', 'SenateMajLdr', 'CondoleezzaRice',];
    // var democ = ['BarackObama', 'HillaryClinton', 'timkaine', 'algore', 'JohnKerry', 'JoeBiden', 'SenSanders', 'CoryBooker', 'NancyPelosi', 'BernieSanders', 'SenatorReid', 'JerryBrownGov', 'GabbyGiffords', 'clairecmc', 'SenWarren', 'NYCMayor', 'alfranken', 'SenGillibrand', 'MartinOMalley', 'MichelleObama', 'madeleine',];
    // var liber = ['GovGaryJohnson', 'RepRonPaul'];
    // var green = ['RalphNader', 'DrJillStein'];
    // var indep = ['MikeBloomberg'];
  // Update traits
  var idType;
  if (currentType === PERSONALITY) idType = '#personality_trait_';
  else if (currentType === NEEDS) idType = '#needs_trait_';
  else if (currentType === VALUES) idType = '#values_trait_';
  celebrity.profile.forEach(function(trait, i) {
    $(idType + i).css('left', 'calc(' + (trait.value * 100) + '%)');
  });
}

$('.avatar-small').click(function(e) {
  var celebrity;
  var id = $(this).find('img').prop('id');
  if (id.match('^s_')) {
    if (currentType === PERSONALITY) celebrity = similar_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = similar_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = similar_values[id.slice(2)];
  }
  else {
    if (currentType === PERSONALITY) celebrity = different_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = different_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = different_values[id.slice(2)];
  }

  if (celebrity)
    displayCelebrity(celebrity);
  else
    console.log('celebrity not found!');

});

// CHANGE OVERLAY COLOR
  $('.overlay').on('load', function(celebrity) {
    var party = this.celebrity.user.politicalParty;
     if (party === 'Republican') {
        $(this).addClass('overlay-red');
      } else if (party === 'Democratic') {
        $(this).addClass('overlay-blue');
      } else if (party === 'Libertarian') {
        $(this).addClass('overlay-yellow');
      } else if (party === 'Green Party') {
        $(this).addClass('overlay-green');
      } else if (party === 'Independent') {
        $(this).addClass('overlay-grey');
      }
  });

/**
 * Makes updates for a profile type switch
 */
function switchTypeUpdates(type, newCeleb, showClass, hideClass1, hideClass2, showSwitch, hideSwitch1, hideSwitch2, typeDesc) {
  if (currentType === type)
    return;
  currentType = type;
  displayCelebrity(newCeleb);

  // Make DOM class updates
  $(showClass).removeClass('hide-traits');
  $(hideClass1).addClass('hide-traits');
  $(hideClass2).addClass('hide-traits');
  $(showSwitch).addClass('switch-button-active');
  $(showSwitch).removeClass('switch-button-disabled');
  $(hideSwitch1).addClass('switch-button-disabled');
  $(hideSwitch1).removeClass('switch-button-active');
  $(hideSwitch2).addClass('switch-button-disabled');
  $(hideSwitch2).removeClass('switch-button-active');
  $('.type-desc').text(', your ' + typeDesc + '...');
}

/**
 * On click handlers for changing comparison views
 * Get the celeb id and call displayCelebrity if it exists
 * @param  {Object} e event
 */
$(document).on('click', '.personality-switch', function() {
  switchTypeUpdates(PERSONALITY, similar_personalities[0], '.personality', '.needs', '.values', '.personality-switch', '.needs-switch', '.values-switch', 'personality is');
});
$(document).on('click', '.needs-switch', function() {
  switchTypeUpdates(NEEDS, similar_needs[0], '.needs', '.personality', '.values', '.needs-switch', '.personality-switch', '.values-switch', 'needs are');
});
$(document).on('click', '.values-switch', function() {
  switchTypeUpdates(VALUES, similar_values[0], '.values', '.needs', '.personality', '.values-switch', '.needs-switch', '.personality-switch', 'values are');
});

/**
 * On click handler for celebrity images.
 * Get the celebrity id and call displayCelebrity if it exists
 * @param  {Object} e event
 */
$('.avatar-small').click(function(e) {
  var celebrity;
  var id = $(this).find('img').prop('id');
  if (id.match('^s_')) {
    if (currentType === PERSONALITY) celebrity = similar_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = similar_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = similar_values[id.slice(2)];
  }
  else {
    if (currentType === PERSONALITY) celebrity = different_personalities[id.slice(2)];
    else if (currentType === NEEDS) celebrity = different_needs[id.slice(2)];
    else if (currentType === VALUES) celebrity = different_values[id.slice(2)];
  }

  if (celebrity)
    displayCelebrity(celebrity);
  else
    console.log('celebrity not found!');

});

/**
 * On click handler for unhiding .overlay-screen.how-it-worked popup overlay.
 */
$(document).on('click', '.how-it-works-link', function() {
  $('.overlay-screen.how-it-worked').fadeIn(200);
  $('#wrap').css({
    'overflow': 'hidden',
    'height': '100%'
  });
});

/**
 * On click handler for hiding .overlay-screen.how-it-worked popup overlay.
 */
$(document).on('click', '.button.back', function() {
  $('.overlay-screen.how-it-worked').fadeOut(200);
  $('#wrap').css({
    'overflow': 'none',
    'height': 'auto'
  });
});

/**
 * On hover handler for making user's points bigger and celeb's points smaller
 */
$(document).on('mouseenter', '.avatars-row > .me', function() {
  $('.mep').addClass('bigger');
  $('.celebrityp').addClass('smaller');
});
$(document).on('mouseleave', '.avatars-row > .me', function() {
  $('.mep').removeClass('bigger');
  $('.celebrityp').removeClass('smaller');
});

/**
 * On hover handler for making celeb's points bigger and user's points smaller
 */
$(document).on('mouseenter', '.avatars-row > .celebrity', function() {
  $('.mep').addClass('smaller');
  $('.celebrityp').addClass('bigger');
});
$(document).on('mouseleave', '.avatars-row > .celebrity', function() {
  $('.mep').removeClass('smaller');
  $('.celebrityp').removeClass('bigger');
});

/**
 * On load handler for loading images a little more gracefully
 */
$('.page-content img').on('load', function() {
  // $(this).addClass('revealed');
  $(this).addClass('revealed');
}).each(function() {
  if (this.complete) {
    $(this).load();
  }
});

$(document).ready(function() {
  currentType = PERSONALITY;
  displayCelebrity(similar_personalities[0]);
});
<|endoftext|>


<commit_before>(function() {
	"use strict";
	function initPage() {

		var fileTypes = [ "pdf", "txt", "jpg", "gif", "png", "tif", "tiff", "mp3", "mp4", "xls", "xlsx", "csv" ];
		var list = fileTypes.join(", ").toUpperCase();
		var filter = fileTypes.join("|");
		var regex = new RegExp("(\.|\/)(" + filter + ")$", "i");

		var acceptableFileTypeList = $(".acceptable-file-type-list");
		acceptableFileTypeList.text(list);

		var fileUploadButton = $('#fileupload');
		if (fileUploadButton.length > 0) { // If we are on a page with file upload.
			// Override curation_concern's file filter. We wait until we are sure that the curation_concern has initialized to make sure this gets called last.
			setTimeout(function() {
				fileUploadButton.fileupload(
					'option',
					'acceptFileTypes',
					regex
				);
			}, 1000);
		}
	}

	$(window).bind('page:change', function() {
		initPage();
	});
})();
<commit_msg>Don't allow Excel files to be uploaded.<commit_after>(function() {
	"use strict";
	function initPage() {

		var fileTypes = [ "pdf", "txt", "jpg", "gif", "png", "tif", "tiff", "mp3", "mp4", "csv" ];
		var list = fileTypes.join(", ").toUpperCase();
		var filter = fileTypes.join("|");
		var regex = new RegExp("(\.|\/)(" + filter + ")$", "i");

		var acceptableFileTypeList = $(".acceptable-file-type-list");
		acceptableFileTypeList.text(list);

		var fileUploadButton = $('#fileupload');
		if (fileUploadButton.length > 0) { // If we are on a page with file upload.
			// Override curation_concern's file filter. We wait until we are sure that the curation_concern has initialized to make sure this gets called last.
			setTimeout(function() {
				fileUploadButton.fileupload(
					'option',
					'acceptFileTypes',
					regex
				);
			}, 1000);
		}
	}

	$(window).bind('page:change', function() {
		initPage();
	});
})();
<|endoftext|>