<commit_before>import React, {Component, PropTypes} from 'react'
import EventListener from 'react-event-listener'
import {colors} from '../../variables'

const defaultContainerStyle = {
  position: 'relative',
  display: 'inline-block'
}

const defaultChildrenStyle = {
  position: 'absolute',
  background: colors.white,
  border: '1px solid',
  borderColor: colors.pacificBlue,
  boxShadow: '0px 2px 7px rgba(0,0,0,.25)',
  minWidth: '100%',
  zIndex: 1000
}
const centerChildren = {left: '50%', transform: 'translate(-50%, 0)'}
const leftChildren = {left: 0}
const rightChildren = {right: 0}

const defaultTriggerStyle = {
  width: '100%'
}

const defaultBorderArrow = {
  bottom: '-6px',
  left: '50%',
  border: 'solid transparent',
  height: '0px',
  width: '0px',
  position: 'absolute',
  pointerEvents: 'none',
  borderBottomColor: colors.pacificBlue,
  borderWidth: '6px',
  marginLeft: '-6px',
  zIndex: 1001
}

const defaultBackgroundArrow = {
  bottom: '-6px',
  left: '50%',
  border: 'solid transparent',
  height: '0px',
  width: '0px',
  position: 'absolute',
  pointerEvents: 'none',
  borderBottomColor: colors.white,
  borderWidth: '5px',
  marginLeft: '-5px',
  zIndex: 1002
}

class Dropdown extends Component {
  state = {
    mouseIsOver: false,
    onMouseLeave: () => this.setState({mouseIsOver: false}),
    onMouseEnter: () => this.setState({mouseIsOver: true}),
    isOpen: false,
    openDropdown: (e) => {
      e.stopPropagation() // <-- stop the event from propagating to the window
      this.setState({isOpen: true})
    },
    closeDropdown: () => this.setState({isOpen: false})
  }
  render () {
    const {children, triggerNode, disabled, left, right, center, arrow, borderColor, backgroundColor} = this.props
    const {onMouseLeave, onMouseEnter, mouseIsOver, openDropdown, isOpen} = this.state

    const computedTriggerStyle = Object.assign({},
      defaultTriggerStyle,
      {cursor: disabled ? 'not-allowed' : 'pointer'}
    )

    const computedChildrenStyle = Object.assign({},
      defaultChildrenStyle,
      borderColor ? {borderColor} : {},
      backgroundColor ? {backgroundColor} : {},
      arrow ? {marginTop: '5px'} : {},
      left ? leftChildren : {},
      right ? rightChildren : {},
      center ? centerChildren : {}
    )

    const computedBackgroundArrow = Object.assign({},
      defaultBackgroundArrow,
      backgroundColor ? {borderBottomColor: backgroundColor} : {},
    )

    const computedBorderArrow = Object.assign({},
      defaultBorderArrow,
      borderColor ? {borderBottomColor: borderColor} : {},
    )

    const ArrowThing = (

    )

    const toggleFunc = isOpen ? () => false : openDropdown
    const computedToggleFunc = disabled ? () => false : toggleFunc
    return (

        {isOpen
          ? <EventListener target={window} onClick={this._onWindowClick} />
          : null
        }

          {triggerNode}

        {arrow && isOpen ? ArrowThing : null}
        {isOpen
          ?
{children}

          : null
        }

    )
  }
  _onWindowClick = () => {
    const {closeOnClick} = this.props
    const {mouseIsOver, closeDropdown} = this.state
    if (!mouseIsOver || closeOnClick) { closeDropdown() }
  }
}

Dropdown.propTypes = {
  arrow: PropTypes.bool,
  backgroundColor: PropTypes.string,
  borderColor: PropTypes.string,
  center: PropTypes.bool,
  closeOnClick: PropTypes.bool,
  children: PropTypes.node.isRequired,
  disabled: PropTypes.bool,
  left: PropTypes.bool,
  right: PropTypes.bool,
  triggerNode: PropTypes.node.isRequired
}

export default Dropdown
<commit_msg>Fix event propagation<commit_after>import React, {Component, PropTypes} from 'react'
import EventListener from 'react-event-listener'
import {colors} from '../../variables'

const defaultContainerStyle = {
  position: 'relative',
  display: 'inline-block'
}

const defaultChildrenStyle = {
  position: 'absolute',
  background: colors.white,
  border: '1px solid',
  borderColor: colors.pacificBlue,
  boxShadow: '0px 2px 7px rgba(0,0,0,.25)',
  minWidth: '100%',
  zIndex: 1000
}
const centerChildren = {left: '50%', transform: 'translate(-50%, 0)'}
const leftChildren = {left: 0}
const rightChildren = {right: 0}

const defaultTriggerStyle = {
  width: '100%'
}

const defaultBorderArrow = {
  bottom: '-6px',
  left: '50%',
  border: 'solid transparent',
  height: '0px',
  width: '0px',
  position: 'absolute',
  pointerEvents: 'none',
  borderBottomColor: colors.pacificBlue,
  borderWidth: '6px',
  marginLeft: '-6px',
  zIndex: 1001
}

const defaultBackgroundArrow = {
  bottom: '-6px',
  left: '50%',
  border: 'solid transparent',
  height: '0px',
  width: '0px',
  position: 'absolute',
  pointerEvents: 'none',
  borderBottomColor: colors.white,
  borderWidth: '5px',
  marginLeft: '-5px',
  zIndex: 1002
}

class Dropdown extends Component {
  state = {
    mouseIsOver: false,
    onMouseLeave: () => this.setState({mouseIsOver: false}),
    onMouseEnter: () => this.setState({mouseIsOver: true}),
    isOpen: false,
    stillPropagating: false,
    openDropdown: (e) => this.setState({isOpen: true, stillPropagating: true}),
    closeDropdown: () => this.setState({isOpen: false})
  }
  render () {
    const {children, triggerNode, disabled, left, right, center, arrow, borderColor, backgroundColor} = this.props
    const {onMouseLeave, onMouseEnter, mouseIsOver, openDropdown, isOpen} = this.state

    const computedTriggerStyle = Object.assign({},
      defaultTriggerStyle,
      {cursor: disabled ? 'not-allowed' : 'pointer'}
    )

    const computedChildrenStyle = Object.assign({},
      defaultChildrenStyle,
      borderColor ? {borderColor} : {},
      backgroundColor ? {backgroundColor} : {},
      arrow ? {marginTop: '5px'} : {},
      left ? leftChildren : {},
      right ? rightChildren : {},
      center ? centerChildren : {}
    )

    const computedBackgroundArrow = Object.assign({},
      defaultBackgroundArrow,
      backgroundColor ? {borderBottomColor: backgroundColor} : {},
    )

    const computedBorderArrow = Object.assign({},
      defaultBorderArrow,
      borderColor ? {borderBottomColor: borderColor} : {},
    )

    const ArrowThing = (

    )

    const toggleFunc = isOpen ? () => false : openDropdown
    const computedToggleFunc = disabled ? () => false : toggleFunc
    return (

        {isOpen
          ? <EventListener target={window} onClick={this._onWindowClick} />
          : null
        }

          {triggerNode}

        {arrow && isOpen ? ArrowThing : null}
        {isOpen
          ?
{children}

          : null
        }

    )
  }
  _onWindowClick = () => {
    const {closeOnClick} = this.props
    const {mouseIsOver, closeDropdown, isOpen, stillPropagating} = this.state
    if (closeOnClick && stillPropagating) {
      this.setState({stillPropagating: false}) // <-- bug fix for the synthetic event propagation... wow
    } else if (!mouseIsOver || closeOnClick) {
      closeDropdown()
    }
  }
}

Dropdown.propTypes = {
  arrow: PropTypes.bool,
  backgroundColor: PropTypes.string,
  borderColor: PropTypes.string,
  center: PropTypes.bool,
  closeOnClick: PropTypes.bool,
  children: PropTypes.node.isRequired,
  disabled: PropTypes.bool,
  left: PropTypes.bool,
  right: PropTypes.bool,
  triggerNode: PropTypes.node.isRequired
}

export default Dropdown
<|endoftext|>


<commit_before>import app from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_FIREBASE_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
}

class Firebase {
  constructor() {
    // Initialize the app
    app.initializeApp(config)
    // Get email auth provider
    this.emailAuthProvider = app.auth.EmailAuthProvider
    // Get FieldValue
    this.FieldValue = app.firestore.FieldValue
    // Initialize auth
    this.auth = app.auth()
    // Initialize firestore
    this.store = app.firestore()
    // Initialize social login providers
    this.googleProvider = new app.auth.GoogleAuthProvider()
    this.facebookProvider = new app.auth.FacebookAuthProvider()
  }

  // *** Auth API ***
  doCreateUserWithEmailAndPassword = (email, password) =>
    this.auth.createUserWithEmailAndPassword(email, password)

  doSignInWithEmailAndPassword = (email, password) =>
    this.auth.signInWithEmailAndPassword(email, password)

  doSignInWithGoogle = () => this.auth.signInWithPopup(this.googleProvider)

  doSignInWithFacebook = () => this.auth.signInWithPopup(this.facebookProvider)

  doSignOut = () => this.auth.signOut()

  doPasswordReset = email => this.auth.sendPasswordResetEmail(email)

  doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)

  doSendEmailVerification = () =>
    this.auth.currentUser.sendEmailVerification({
      url: process.env.REACT_APP_CONFIRMATION_EMAIL_REDIRECT,
    })

  // *** Auth State API ***
  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {
      if (authUser) {
        this.user(authUser.uid)
          .get()
          .then(snapshot => {
            // Get user document from snapshot data
            const user = snapshot.data()
            // Default empty roles
            if (!user.roles) {
              user.roles = {}
            }
            // Merge auth and user document
            authUser = {
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerified,
              providerData: authUser.providerData,
              ...user,
            }
            next(authUser)
          })
      } else {
        fallback()
      }
    })

  // *** User API ***
  users = () => this.store.collection('users')

  user = id => this.users().doc(id)

  userFriends = id => this.user(id).collection('friends')

  findUserForEmail = email => this.users().where('email', '==', email)

  findUserFriendForEmail = (email, id) =>
    this.userFriends(id).where('email', '==', email)

  userGames = id => this.user(id).collection('games')

  // *** Qwest API ***
  qwests = () => this.store.collection('qwests')

  qwest = id => this.qwests().doc(id)

  gameQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', false)

  gameCompletedQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', true)

  userQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', false)

  userCompletedQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', true)

  createQwest = qwest => {
    this.qwests().add(qwest)
  }

  updateQwest = (qwest, updatedQwest) => {
    this.qwest(qwest.id).update(updatedQwest)
  }

  completeQwest = id => {
    // Complete qwest
    this.qwest(id).update({
      isCompleted: true,
    })
  }

  resetQwest = id => {
    // Reset qwest
    this.qwest(id).update({
      isCompleted: false,
    })
  }

  deleteQwest = id => {
    // Delete qwest
    this.qwest(id).delete()
  }

  // *** Game API ***
  games = () => this.store.collection('games')

  game = id => this.games().doc(id)

  gamePlayers = id => this.game(id).collection('players')

  gameSubCollections = id =>
    this.store.collectionGroup('games').where('gameId', '==', id)

  createGame = async (game, authUser) => {
    // Create the new game
    const newGame = await this.games().add(game)
    // Create players collection for game
    newGame
      .collection('players')
      .doc(authUser.uid)
      .set({
        userId: authUser.uid,
        username: authUser.username,
        email: authUser.email,
      })
    // Add game to user's games collection
    this.userGames(authUser.uid)
      .doc(newGame.id)
      .set({
        gameId: newGame.id,
        name: game.name,
      })
  }

  updateGame = (game, updatedGame) => {
    // Update the game
    this.game(game.id).update(updatedGame)
    // Update game sub-collections
    this.gameSubCollections(game.id)
      .get()
      .then(snapshot => {
        snapshot.docs.forEach(doc => {
          doc.ref.update(updatedGame)
        })
      })
  }

  deleteGame = async id => {
    // Get game's players collections
    const players = await this.gamePlayers(id).get()
    // Iterate through each player
    players.docs.forEach(player => {
      // Remove game from player's games collection
      this.userGames(player.id)
        .doc(id)
        .delete()
    })
    // Get game's qwests collections
    const qwests = await this.gameQwests(id).get()
    // Iterate through each qwest
    qwests.docs.forEach(qwest => {
      // Delete qwest
      this.deleteQwest(qwest.id)
    })
    // Delete game
    this.game(id).delete()
  }

  // *** Invite API ***
  invites = () => this.store.collection('invites')

  invite = id => this.invites().doc(id)

  findSentInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('requesterId', '==', authUserId)
      .where('gameId', '==', null)

  findReceivedInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requesterId', '==', userId)
      .where('requestedId', '==', authUserId)
      .where('gameId', '==', null)

  findSentGameInvitesForUser = (userId, gameId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('gameId', '==', gameId)

  sentUserInvites = id =>
    this.invites()
      .where('requesterId', '==', id)
      .where('gameId', '==', null)

  sentGameInvites = id => this.invites().where('gameId', '==', id)

  receivedUserInvites = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '==', null)

  gameInvitesForUser = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '>=', '')

  createInvite = invite => {
    this.invites().add(invite)
  }

  acceptFriendInvite = invite => {
    // Update friends document of requested user with requester user data
    this.userFriends(invite.data().requestedId)
      .doc(invite.data().requesterId)
      .set({
        userId: invite.data().requesterId,
        username: invite.data().requesterUsername,
        email: invite.data().requesterEmail,
      })
    // Update friends document of requester user with requested user data
    this.userFriends(invite.data().requesterId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
  }

  acceptGameInvite = invite => {
    // Update games document's players collection with requested user data
    this.gamePlayers(invite.data().gameId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
    // Update user document's games collection with game data
    this.userGames(invite.data().requestedId)
      .doc(invite.data().gameId)
      .set({
        gameId: invite.data().gameId,
        name: invite.data().gameName,
      })
  }

  // *** Post API ***
  qwestPosts = id => this.qwest(id).collection('posts')

  gamePosts = id => this.game(id).collection('posts')

  createQwestPost = (id, post) => this.qwestPosts(id).add(post)

  createGamePost = (id, post) => this.gamePosts(id).add(post)

  mostRecentQwestPosts = id => this.qwestPosts(id).orderBy('createdAt', 'desc')

  mostRecentGamePosts = id => this.gamePosts(id).orderBy('createdAt', 'desc')

  // *** Task API ***
  tasks = id => this.store.collection('tasks')

  qwestTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', false)

  qwestCompletedTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', true)

  qwestTask = (qwestId, taskId) => this.qwestTasks(qwestId).doc(taskId)

  createQwestTask = (id, task) => this.qwestTasks(id).add(task)

  updateQwestTask = (qwestId, taskId, updatedTask) => {
    this.qwestTask(qwestId, taskId).update(updatedTask)
  }

  completeQwestTask = (qwestId, taskId) => {
    // Complete task
    this.qwestTask(qwestId, taskId).update({
      isCompleted: true,
    })
  }

  resetQwestTask = (qwestId, taskId) => {
    // Reset task
    this.qwestTask(qwestId, taskId).update({
      isCompleted: false,
    })
  }

  deleteQwestTask = (qwestId, taskId) => {
    // Delete task
    this.qwestTask(qwestId, taskId).delete()
  }
}

export default Firebase
<commit_msg>Updated firebase Task APIs<commit_after>import app from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_FIREBASE_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
}

class Firebase {
  constructor() {
    // Initialize the app
    app.initializeApp(config)
    // Get email auth provider
    this.emailAuthProvider = app.auth.EmailAuthProvider
    // Get FieldValue
    this.FieldValue = app.firestore.FieldValue
    // Initialize auth
    this.auth = app.auth()
    // Initialize firestore
    this.store = app.firestore()
    // Initialize social login providers
    this.googleProvider = new app.auth.GoogleAuthProvider()
    this.facebookProvider = new app.auth.FacebookAuthProvider()
  }

  // *** Auth API ***
  doCreateUserWithEmailAndPassword = (email, password) =>
    this.auth.createUserWithEmailAndPassword(email, password)

  doSignInWithEmailAndPassword = (email, password) =>
    this.auth.signInWithEmailAndPassword(email, password)

  doSignInWithGoogle = () => this.auth.signInWithPopup(this.googleProvider)

  doSignInWithFacebook = () => this.auth.signInWithPopup(this.facebookProvider)

  doSignOut = () => this.auth.signOut()

  doPasswordReset = email => this.auth.sendPasswordResetEmail(email)

  doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)

  doSendEmailVerification = () =>
    this.auth.currentUser.sendEmailVerification({
      url: process.env.REACT_APP_CONFIRMATION_EMAIL_REDIRECT,
    })

  // *** Auth State API ***
  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {
      if (authUser) {
        this.user(authUser.uid)
          .get()
          .then(snapshot => {
            // Get user document from snapshot data
            const user = snapshot.data()
            // Default empty roles
            if (!user.roles) {
              user.roles = {}
            }
            // Merge auth and user document
            authUser = {
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerified,
              providerData: authUser.providerData,
              ...user,
            }
            next(authUser)
          })
      } else {
        fallback()
      }
    })

  // *** User API ***
  users = () => this.store.collection('users')

  user = id => this.users().doc(id)

  userFriends = id => this.user(id).collection('friends')

  findUserForEmail = email => this.users().where('email', '==', email)

  findUserFriendForEmail = (email, id) =>
    this.userFriends(id).where('email', '==', email)

  userGames = id => this.user(id).collection('games')

  // *** Qwest API ***
  qwests = () => this.store.collection('qwests')

  qwest = id => this.qwests().doc(id)

  gameQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', false)

  gameCompletedQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', true)

  userQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', false)

  userCompletedQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', true)

  createQwest = qwest => {
    this.qwests().add(qwest)
  }

  updateQwest = (qwest, updatedQwest) => {
    this.qwest(qwest.id).update(updatedQwest)
  }

  completeQwest = id => {
    // Complete qwest
    this.qwest(id).update({
      isCompleted: true,
    })
  }

  resetQwest = id => {
    // Reset qwest
    this.qwest(id).update({
      isCompleted: false,
    })
  }

  deleteQwest = id => {
    // Delete qwest
    this.qwest(id).delete()
  }

  // *** Game API ***
  games = () => this.store.collection('games')

  game = id => this.games().doc(id)

  gamePlayers = id => this.game(id).collection('players')

  gameSubCollections = id =>
    this.store.collectionGroup('games').where('gameId', '==', id)

  createGame = async (game, authUser) => {
    // Create the new game
    const newGame = await this.games().add(game)
    // Create players collection for game
    newGame
      .collection('players')
      .doc(authUser.uid)
      .set({
        userId: authUser.uid,
        username: authUser.username,
        email: authUser.email,
      })
    // Add game to user's games collection
    this.userGames(authUser.uid)
      .doc(newGame.id)
      .set({
        gameId: newGame.id,
        name: game.name,
      })
  }

  updateGame = (game, updatedGame) => {
    // Update the game
    this.game(game.id).update(updatedGame)
    // Update game sub-collections
    this.gameSubCollections(game.id)
      .get()
      .then(snapshot => {
        snapshot.docs.forEach(doc => {
          doc.ref.update(updatedGame)
        })
      })
  }

  deleteGame = async id => {
    // Get game's players collections
    const players = await this.gamePlayers(id).get()
    // Iterate through each player
    players.docs.forEach(player => {
      // Remove game from player's games collection
      this.userGames(player.id)
        .doc(id)
        .delete()
    })
    // Get game's qwests collections
    const qwests = await this.gameQwests(id).get()
    // Iterate through each qwest
    qwests.docs.forEach(qwest => {
      // Delete qwest
      this.deleteQwest(qwest.id)
    })
    // Delete game
    this.game(id).delete()
  }

  // *** Invite API ***
  invites = () => this.store.collection('invites')

  invite = id => this.invites().doc(id)

  findSentInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('requesterId', '==', authUserId)
      .where('gameId', '==', null)

  findReceivedInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requesterId', '==', userId)
      .where('requestedId', '==', authUserId)
      .where('gameId', '==', null)

  findSentGameInvitesForUser = (userId, gameId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('gameId', '==', gameId)

  sentUserInvites = id =>
    this.invites()
      .where('requesterId', '==', id)
      .where('gameId', '==', null)

  sentGameInvites = id => this.invites().where('gameId', '==', id)

  receivedUserInvites = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '==', null)

  gameInvitesForUser = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '>=', '')

  createInvite = invite => {
    this.invites().add(invite)
  }

  acceptFriendInvite = invite => {
    // Update friends document of requested user with requester user data
    this.userFriends(invite.data().requestedId)
      .doc(invite.data().requesterId)
      .set({
        userId: invite.data().requesterId,
        username: invite.data().requesterUsername,
        email: invite.data().requesterEmail,
      })
    // Update friends document of requester user with requested user data
    this.userFriends(invite.data().requesterId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
  }

  acceptGameInvite = invite => {
    // Update games document's players collection with requested user data
    this.gamePlayers(invite.data().gameId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
    // Update user document's games collection with game data
    this.userGames(invite.data().requestedId)
      .doc(invite.data().gameId)
      .set({
        gameId: invite.data().gameId,
        name: invite.data().gameName,
      })
  }

  // *** Post API ***
  qwestPosts = id => this.qwest(id).collection('posts')

  gamePosts = id => this.game(id).collection('posts')

  createQwestPost = (id, post) => this.qwestPosts(id).add(post)

  createGamePost = (id, post) => this.gamePosts(id).add(post)

  mostRecentQwestPosts = id => this.qwestPosts(id).orderBy('createdAt', 'desc')

  mostRecentGamePosts = id => this.gamePosts(id).orderBy('createdAt', 'desc')

  // *** Task API ***
  tasks = () => this.store.collection('tasks')

  task = id => this.tasks().doc(id)

  qwestTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', false)

  qwestCompletedTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', true)

  createTask = task => this.tasks().add(task)

  updateTask = (task, updatedTask) => {
    this.task(task.id).update(updatedTask)
  }

  completeTask = id => {
    // Complete task
    this.task(id).update({
      isCompleted: true,
    })
  }

  resetTask = id => {
    // Reset task
    this.task(id).update({
      isCompleted: false,
    })
  }

  deleteTask = id => {
    // Delete task
    this.task(id).delete()
  }
}

export default Firebase
<|endoftext|>


<commit_before>openerp.base.data_export = function(openerp) {
openerp.base.DataExport = openerp.base.Dialog.extend({
    template: 'ExportTreeView',
    dialog_title: 'Export Data',
    init: function(parent, dataset) {
        this._super(parent);
        this.records = {};
        this.dataset = dataset;
        this.exports = new openerp.base.DataSetSearch(
            this, 'ir.exports', this.dataset.get_context());
    },
    start: function() {
        var self = this;
        this._super.apply(this, arguments);
        this.open({
            modal: true,
            width: '55%',
            height: 'auto',
            position: 'top',
            buttons : {
                "Close" : function() {
                    self.close();
                  },
                "Export To File" : function() {
                    self.on_click_export_data();
                  }
               },
            close: function(event, ui){ self.close();}
        });
        self.$element.removeClass('ui-dialog-content ui-widget-content');
        self.$element.find('#add_field').click(function() {
            if ($('#field-tree-structure tr.ui-selected')) {
                var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a');
                for (var i = 0; i < fld.length; i++) {
                    var id = $(fld[i]).attr('id').split('-')[1];
                    var string = $(fld[i]).attr('string');
                    self.add_field(id, string);
                }
                self.$element.find('#field-tree-structure tr').removeClass('ui-selected');
            }
        });
        self.$element.find('#remove_field').click(function() {
            self.$element.find('#fields_list option:selected').remove();
        });
        self.$element.find('#remove_all_field').click(function() {
            self.$element.find('#fields_list').empty();
        });
        this.$element.find('#export_new_list').click(this.on_show_save_list);

        var got_fields = new $.Deferred();
        this.$element.find('#import_compat').change(function() {
            self.$element.find('#fields_list').empty();
            self.$element.find('#field-tree-structure').remove();
            var import_comp = self.$element.find("#import_compat").val();
            self.rpc("/base/export/get_fields", {
                model: self.dataset.model,
                import_compat: Boolean(import_comp)
            }, function (records) {
                got_fields.resolve();
                self.on_show_data(records);
            });
        }).change();

        return $.when(
            got_fields,
            this.rpc('/base/export/formats', {}, this.do_setup_export_formats),
            this.show_exports_list());
    },
    do_setup_export_formats: function (formats) {
        var $fmts = this.$element.find('#export_format');
        _(formats).each(function (format) {
            $fmts.append(new Option(format[1], format[0]));
        });
    },
    show_exports_list: function() {
        var self = this;
        if (self.$element.find('#saved_export_list').is(':hidden')) {
            self.$element.find('#ExistsExportList').show();
            return;
        }
        return this.exports.read_slice(['name'], {
            domain: [['resource', '=', this.dataset.model]]
        }, function (export_list) {
            if (!export_list.length) {
                return;
            }
            self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
            self.$element.find('#saved_export_list').change(function() {
                self.$element.find('#fields_list option').remove();
                var export_id = self.$element.find('#saved_export_list option:selected').val();
                if (export_id) {
                    self.rpc('/base/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field);
                }
            });
            self.$element.find('#delete_export_list').click(function() {
                var select_exp = self.$element.find('#saved_export_list option:selected');
                if (select_exp.val()) {
                    self.exports.unlink([parseInt(select_exp.val(), 10)]);
                    select_exp.remove();
                    if (self.$element.find('#saved_export_list option').length <= 1) {
                        self.$element.find('#ExistsExportList').hide();
                    }
                }
            });
        });
    },
    do_load_export_field: function(field_list) {
        var export_node = this.$element.find("#fields_list");
        for (var key in field_list) {
            export_node.append(new Option(field_list[key], key));
        }
    },
    on_show_save_list: function() {
        var self = this;
        var current_node = self.$element.find("#savenewlist");
        if (!(current_node.find("label")).length) {
            current_node.append(QWeb.render('ExportNewList'));
            current_node.find("#add_export_list").click(function() {
                var value = current_node.find("#savelist_name").val();
                if (value) {
                    self.do_save_export_list(value);
                } else {
                    alert("Pleae Enter Save Field List Name");
                }
            });
        } else {
            if (current_node.is(':hidden')) {
                current_node.show();
                current_node.find("#savelist_name").val("");
            } else {
               current_node.hide();
            }
        }
    },
    do_save_export_list: function(value) {
        var self = this;
        var fields = self.get_fields();
        if (!fields.length) {
            return;
        }
        this.exports.create({
            name: value,
            resource: this.dataset.model,
            export_fields: _(fields).map(function (field) {
                return [0, 0, {name: field}];
            })
        }, function (export_list_id) {
            if (!export_list_id) {
                return;
            }
            if (self.$element.find("#saved_export_list").length > 0) {
                self.$element.find("#saved_export_list").append(
                        new Option(value, export_list_id));
            } else {
                self.show_exports_list();
            }
            if (self.$element.find("#saved_export_list").is(":hidden")) {
                self.show_exports_list();
            }
        });
        this.on_show_save_list();
        this.$element.find("#fields_list option").remove();
    },
    on_click: function(id, record) {
        var self = this;
        if (!record['children']) {
            return;
        }
        var model = record['params']['model'],
            prefix = record['params']['prefix'],
            name = record['params']['name'];

        if (!record.loaded) {
            var import_comp = self.$element.find("#import_compat").val();
            self.rpc("/base/export/get_fields", {
                model: model,
                prefix: prefix,
                parent_name: name,
                import_compat: Boolean(import_comp),
                parent_field_type : record['field_type']
            }, function(results) {
                record.loaded = true;
                self.on_show_data(results, record.id);
            });
        } else {
            self.showcontent(record.id);
        }
    },
    on_show_data: function(result, after) {
        var self = this;
        var imp_cmpt = Boolean(self.$element.find("#import_compat").val());

        if (after) {
            var current_tr = self.$element.find("tr[id='treerow-" + after + "']");
            current_tr.addClass('open');
            current_tr.find('img').attr('src','/base/static/src/img/collapse.gif');
            current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
        } else {
            self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
        }
        _.each(result, function(record) {
            self.records[record.id] = record.value;
            if (record.required) {
                var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
                required_fld.addClass("oe_export_requiredfield");
            }
            self.$element.find("img[id='parentimg-" + record.id +"']").click(function() {
                self.on_click(this.id, record);
            });

            self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
                if (e.shiftKey) {
                    var frst_click, scnd_click = '';
                    if (self.row_index == 0) {
                        self.row_index = this.rowIndex;
                        frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1];
                        $(frst_click).addClass("ui-selected");
                    } else {
                        if (this.rowIndex >=self.row_index) {
                            for (var i = (self.row_index-1); i < this.rowIndex; i++) {
                                scnd_click = self.$element.find("tr[id^='treerow-']")[i];
                                if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
                                    $(scnd_click).addClass("ui-selected");
                                }
                            }
                        } else {
                            for (var i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
                                scnd_click = self.$element.find("tr[id^='treerow-']")[i];
                                if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
                                    $(scnd_click).addClass("ui-selected");
                                }
                            }
                        }
                    }
                }
                self.row_index = this.rowIndex;

                self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function() {
                    self.row_index = 0;
                });
                var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
                if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
                    return false;
                }
                if (e.ctrlKey) {
                    if ($(this).hasClass('ui-selected')) {
                        $(this).removeClass('ui-selected').find('a').blur();
                    } else {
                        $(this).addClass('ui-selected').find('a').focus();
                    }
                } else if (!e.shiftKey) {
                    self.$element.find("tr.ui-selected")
                            .removeClass("ui-selected").find('a').blur();
                    $(this).addClass("ui-selected").find('a').focus();
                }
                return false;
            });

            self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
                var keyCode = e.keyCode || e.which;
                var arrow = {left: 37, up: 38, right: 39, down: 40 };
                switch (keyCode) {
                    case arrow.left:
                        if ($(this).hasClass('open')) {
                            self.on_click(this.id, record);
                        }
                        break;
                    case arrow.right:
                        if (!$(this).hasClass('open')) {
                            self.on_click(this.id, record);
                        }
                        break;
                    case arrow.up:
                        var elem = this;
                        $(elem).removeClass("ui-selected");
                        while (!$(elem).prev().is(":visible")) {
                            elem = $(elem).prev();
                        }
                        if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            $(elem).prev().addClass("ui-selected");
                        }
                        $(elem).prev().find('a').focus();
                        break;
                    case arrow.down:
                        var elem = this;
                        $(elem).removeClass("ui-selected");
                        while(!$(elem).next().is(":visible")) {
                            elem = $(elem).next();
                        }
                        if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            $(elem).next().addClass("ui-selected");
                        }
                        $(elem).next().find('a').focus();
                        break;
                }
            });
            self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
                var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
                if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
                   self.add_field(record.id, $(this).find("a").attr("string"));
                }
            });
        });
        self.$element.find('#fields_list').mouseover(function(event) {
            if (event.relatedTarget) {
                if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
                    var field_id = event.relatedTarget.attributes["id"]["value"];
                    if (field_id && field_id.split("-")[0] === 'export') {
                        if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
                        }
                    }
                }
            }
        });
    },
    showcontent: function(id) {
        // show & hide the contents
        var $this = this.$element.find("tr[id='treerow-" + id + "']");
        var is_open = $this.hasClass('open');
        $this.toggleClass('open');

        var first_child = $this.find('img');
        if (is_open) {
            first_child.attr('src', '/base/static/src/img/expand.gif');
        } else {
            first_child.attr('src', '/base/static/src/img/collapse.gif');
        }
        var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
        var child_len = (id.split("/")).length + 1;
        for (var i = 0; i < child_field.length; i++) {
            var $child = $(child_field[i]);
            if (is_open) {
                $child.hide();
            } else if (child_len == (child_field[i].id.split("/")).length) {
                if ($child.hasClass('open')) {
                    $child.removeClass('open');
                    $child.find('img').attr('src', '/base/static/src/img/expand.gif');
                }
                $child.show();
            }
        }
    },
    add_field: function(field_id, string) {
        var field_list = this.$element.find('#fields_list');
        if (this.$element.find("#fields_list option[value='" + field_id + "']")
                && !this.$element.find("#fields_list option[value='" + field_id + "']").length) {
            field_list.append(new Option(string, field_id));
        }
    },
    get_fields: function() {
        var export_field = [];
        this.$element.find("#fields_list option").each(function() {
            export_field.push($(this).val());
        });
        if (!export_field.length) {
            alert('Please select fields to save export list...');
        }
        return export_field;
    },
    on_click_export_data: function() {
        $.blockUI(this.$element);
        var exported_fields = [], self = this;
        this.$element.find("#fields_list option").each(function() {
            var fieldname = self.records[$(this).val()];
            exported_fields.push({name: fieldname, label: $(this).text()});
        });
        if (_.isEmpty(exported_fields)) {
            alert('Please select fields to export...');
            return;
        }

        var export_format = this.$element.find("#export_format").val();
        this.session.get_file({
            url: '/base/export/' + export_format,
            data: {data: JSON.stringify({
                model: this.dataset.model,
                fields: exported_fields,
                ids: this.dataset.ids,
                domain: this.dataset.domain,
                import_compat: Boolean(
                        this.$element.find("#import_compat").val())
            })},
            complete: $.unblockUI
        });
    },
    close: function() {
        $(this.$dialog).remove();
        this._super();
    }
});

};
<commit_msg>[IMP] always export the current record's external ID as first field<commit_after>openerp.base.data_export = function(openerp) {
openerp.base.DataExport = openerp.base.Dialog.extend({
    template: 'ExportTreeView',
    dialog_title: 'Export Data',
    init: function(parent, dataset) {
        this._super(parent);
        this.records = {};
        this.dataset = dataset;
        this.exports = new openerp.base.DataSetSearch(
            this, 'ir.exports', this.dataset.get_context());
    },
    start: function() {
        var self = this;
        this._super.apply(this, arguments);
        this.open({
            modal: true,
            width: '55%',
            height: 'auto',
            position: 'top',
            buttons : {
                "Close" : function() {
                    self.close();
                  },
                "Export To File" : function() {
                    self.on_click_export_data();
                  }
               },
            close: function(event, ui){ self.close();}
        });
        self.$element.removeClass('ui-dialog-content ui-widget-content');
        self.$element.find('#add_field').click(function() {
            if ($('#field-tree-structure tr.ui-selected')) {
                var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a');
                for (var i = 0; i < fld.length; i++) {
                    var id = $(fld[i]).attr('id').split('-')[1];
                    var string = $(fld[i]).attr('string');
                    self.add_field(id, string);
                }
                self.$element.find('#field-tree-structure tr').removeClass('ui-selected');
            }
        });
        self.$element.find('#remove_field').click(function() {
            self.$element.find('#fields_list option:selected').remove();
        });
        self.$element.find('#remove_all_field').click(function() {
            self.$element.find('#fields_list').empty();
        });
        this.$element.find('#export_new_list').click(this.on_show_save_list);

        var got_fields = new $.Deferred();
        this.$element.find('#import_compat').change(function() {
            self.$element.find('#fields_list').empty();
            self.$element.find('#field-tree-structure').remove();
            var import_comp = self.$element.find("#import_compat").val();
            self.rpc("/base/export/get_fields", {
                model: self.dataset.model,
                import_compat: Boolean(import_comp)
            }, function (records) {
                got_fields.resolve();
                self.on_show_data(records);
            });
        }).change();

        return $.when(
            got_fields,
            this.rpc('/base/export/formats', {}, this.do_setup_export_formats),
            this.show_exports_list());
    },
    do_setup_export_formats: function (formats) {
        var $fmts = this.$element.find('#export_format');
        _(formats).each(function (format) {
            $fmts.append(new Option(format[1], format[0]));
        });
    },
    show_exports_list: function() {
        var self = this;
        if (self.$element.find('#saved_export_list').is(':hidden')) {
            self.$element.find('#ExistsExportList').show();
            return;
        }
        return this.exports.read_slice(['name'], {
            domain: [['resource', '=', this.dataset.model]]
        }, function (export_list) {
            if (!export_list.length) {
                return;
            }
            self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
            self.$element.find('#saved_export_list').change(function() {
                self.$element.find('#fields_list option').remove();
                var export_id = self.$element.find('#saved_export_list option:selected').val();
                if (export_id) {
                    self.rpc('/base/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field);
                }
            });
            self.$element.find('#delete_export_list').click(function() {
                var select_exp = self.$element.find('#saved_export_list option:selected');
                if (select_exp.val()) {
                    self.exports.unlink([parseInt(select_exp.val(), 10)]);
                    select_exp.remove();
                    if (self.$element.find('#saved_export_list option').length <= 1) {
                        self.$element.find('#ExistsExportList').hide();
                    }
                }
            });
        });
    },
    do_load_export_field: function(field_list) {
        var export_node = this.$element.find("#fields_list");
        for (var key in field_list) {
            export_node.append(new Option(field_list[key], key));
        }
    },
    on_show_save_list: function() {
        var self = this;
        var current_node = self.$element.find("#savenewlist");
        if (!(current_node.find("label")).length) {
            current_node.append(QWeb.render('ExportNewList'));
            current_node.find("#add_export_list").click(function() {
                var value = current_node.find("#savelist_name").val();
                if (value) {
                    self.do_save_export_list(value);
                } else {
                    alert("Pleae Enter Save Field List Name");
                }
            });
        } else {
            if (current_node.is(':hidden')) {
                current_node.show();
                current_node.find("#savelist_name").val("");
            } else {
               current_node.hide();
            }
        }
    },
    do_save_export_list: function(value) {
        var self = this;
        var fields = self.get_fields();
        if (!fields.length) {
            return;
        }
        this.exports.create({
            name: value,
            resource: this.dataset.model,
            export_fields: _(fields).map(function (field) {
                return [0, 0, {name: field}];
            })
        }, function (export_list_id) {
            if (!export_list_id) {
                return;
            }
            if (self.$element.find("#saved_export_list").length > 0) {
                self.$element.find("#saved_export_list").append(
                        new Option(value, export_list_id));
            } else {
                self.show_exports_list();
            }
            if (self.$element.find("#saved_export_list").is(":hidden")) {
                self.show_exports_list();
            }
        });
        this.on_show_save_list();
        this.$element.find("#fields_list option").remove();
    },
    on_click: function(id, record) {
        var self = this;
        if (!record['children']) {
            return;
        }
        var model = record['params']['model'],
            prefix = record['params']['prefix'],
            name = record['params']['name'];

        if (!record.loaded) {
            var import_comp = self.$element.find("#import_compat").val();
            self.rpc("/base/export/get_fields", {
                model: model,
                prefix: prefix,
                parent_name: name,
                import_compat: Boolean(import_comp),
                parent_field_type : record['field_type']
            }, function(results) {
                record.loaded = true;
                self.on_show_data(results, record.id);
            });
        } else {
            self.showcontent(record.id);
        }
    },
    on_show_data: function(result, after) {
        var self = this;
        var imp_cmpt = Boolean(self.$element.find("#import_compat").val());

        if (after) {
            var current_tr = self.$element.find("tr[id='treerow-" + after + "']");
            current_tr.addClass('open');
            current_tr.find('img').attr('src','/base/static/src/img/collapse.gif');
            current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
        } else {
            self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
        }
        _.each(result, function(record) {
            self.records[record.id] = record.value;
            if (record.required) {
                var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
                required_fld.addClass("oe_export_requiredfield");
            }
            self.$element.find("img[id='parentimg-" + record.id +"']").click(function() {
                self.on_click(this.id, record);
            });

            self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) {
                if (e.shiftKey) {
                    var frst_click, scnd_click = '';
                    if (self.row_index == 0) {
                        self.row_index = this.rowIndex;
                        frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1];
                        $(frst_click).addClass("ui-selected");
                    } else {
                        if (this.rowIndex >=self.row_index) {
                            for (var i = (self.row_index-1); i < this.rowIndex; i++) {
                                scnd_click = self.$element.find("tr[id^='treerow-']")[i];
                                if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
                                    $(scnd_click).addClass("ui-selected");
                                }
                            }
                        } else {
                            for (var i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
                                scnd_click = self.$element.find("tr[id^='treerow-']")[i];
                                if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
                                    $(scnd_click).addClass("ui-selected");
                                }
                            }
                        }
                    }
                }
                self.row_index = this.rowIndex;

                self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function() {
                    self.row_index = 0;
                });
                var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
                if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
                    return false;
                }
                if (e.ctrlKey) {
                    if ($(this).hasClass('ui-selected')) {
                        $(this).removeClass('ui-selected').find('a').blur();
                    } else {
                        $(this).addClass('ui-selected').find('a').focus();
                    }
                } else if (!e.shiftKey) {
                    self.$element.find("tr.ui-selected")
                            .removeClass("ui-selected").find('a').blur();
                    $(this).addClass("ui-selected").find('a').focus();
                }
                return false;
            });

            self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
                var keyCode = e.keyCode || e.which;
                var arrow = {left: 37, up: 38, right: 39, down: 40 };
                switch (keyCode) {
                    case arrow.left:
                        if ($(this).hasClass('open')) {
                            self.on_click(this.id, record);
                        }
                        break;
                    case arrow.right:
                        if (!$(this).hasClass('open')) {
                            self.on_click(this.id, record);
                        }
                        break;
                    case arrow.up:
                        var elem = this;
                        $(elem).removeClass("ui-selected");
                        while (!$(elem).prev().is(":visible")) {
                            elem = $(elem).prev();
                        }
                        if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            $(elem).prev().addClass("ui-selected");
                        }
                        $(elem).prev().find('a').focus();
                        break;
                    case arrow.down:
                        var elem = this;
                        $(elem).removeClass("ui-selected");
                        while(!$(elem).next().is(":visible")) {
                            elem = $(elem).next();
                        }
                        if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            $(elem).next().addClass("ui-selected");
                        }
                        $(elem).next().find('a').focus();
                        break;
                }
            });
            self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
                var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
                if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
                   self.add_field(record.id, $(this).find("a").attr("string"));
                }
            });
        });
        self.$element.find('#fields_list').mouseover(function(event) {
            if (event.relatedTarget) {
                if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
                    var field_id = event.relatedTarget.attributes["id"]["value"];
                    if (field_id && field_id.split("-")[0] === 'export') {
                        if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
                            self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
                        }
                    }
                }
            }
        });
    },
    showcontent: function(id) {
        // show & hide the contents
        var $this = this.$element.find("tr[id='treerow-" + id + "']");
        var is_open = $this.hasClass('open');
        $this.toggleClass('open');

        var first_child = $this.find('img');
        if (is_open) {
            first_child.attr('src', '/base/static/src/img/expand.gif');
        } else {
            first_child.attr('src', '/base/static/src/img/collapse.gif');
        }
        var child_field = this.$element.find("tr[id^='treerow-" + id +"/']");
        var child_len = (id.split("/")).length + 1;
        for (var i = 0; i < child_field.length; i++) {
            var $child = $(child_field[i]);
            if (is_open) {
                $child.hide();
            } else if (child_len == (child_field[i].id.split("/")).length) {
                if ($child.hasClass('open')) {
                    $child.removeClass('open');
                    $child.find('img').attr('src', '/base/static/src/img/expand.gif');
                }
                $child.show();
            }
        }
    },
    add_field: function(field_id, string) {
        var field_list = this.$element.find('#fields_list');
        if (this.$element.find("#fields_list option[value='" + field_id + "']")
                && !this.$element.find("#fields_list option[value='" + field_id + "']").length) {
            field_list.append(new Option(string, field_id));
        }
    },
    get_fields: function() {
        var export_field = [];
        this.$element.find("#fields_list option").each(function() {
            export_field.push($(this).val());
        });
        if (!export_field.length) {
            alert('Please select fields to save export list...');
        }
        return export_field;
    },
    on_click_export_data: function() {
        $.blockUI(this.$element);
        var exported_fields = [], self = this;
        this.$element.find("#fields_list option").each(function() {
            var fieldname = self.records[$(this).val()];
            exported_fields.push({name: fieldname, label: $(this).text()});
        });
        if (_.isEmpty(exported_fields)) {
            alert('Please select fields to export...');
            return;
        }

        exported_fields.unshift({name: 'id', label: 'External ID'});
        var export_format = this.$element.find("#export_format").val();
        this.session.get_file({
            url: '/base/export/' + export_format,
            data: {data: JSON.stringify({
                model: this.dataset.model,
                fields: exported_fields,
                ids: this.dataset.ids,
                domain: this.dataset.domain,
                import_compat: Boolean(
                        this.$element.find("#import_compat").val())
            })},
            complete: $.unblockUI
        });
    },
    close: function() {
        $(this.$dialog).remove();
        this._super();
    }
});

};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ReactTags from 'react-tag-autocomplete';
import 'react-tag-autocomplete/example/styles.css';

import Button from 'react-bootstrap/lib/Button';
import Thumbnail from 'react-bootstrap/lib/Thumbnail';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import FormControl from 'react-bootstrap/lib/FormControl';
import InputGroup from 'react-bootstrap/lib/InputGroup';
import Alert from 'react-bootstrap/lib/Alert';

import { formatGpsCoord } from 'fm3/geoutils';
import * as FmPropTypes from 'fm3/propTypes';

export default class GalleryUploadItem extends React.Component {
  static propTypes = {
    id: PropTypes.number.isRequired,
    filename: PropTypes.string,
    dataURL: PropTypes.string,
    position: FmPropTypes.point,
    title: PropTypes.string,
    description: PropTypes.string,
    takenAt: PropTypes.instanceOf(Date),
    tags: PropTypes.arrayOf(PropTypes.string.isRequired),
    error: PropTypes.string,
    onRemove: PropTypes.func.isRequired,
    onPositionPick: PropTypes.func.isRequired,
    onTitleChange: PropTypes.func.isRequired,
    onDescriptionChange: PropTypes.func.isRequired,
    onTakenAtChange: PropTypes.func.isRequired,
    onTagsChange: PropTypes.func.isRequired,
    disabled: PropTypes.bool,
  }

  handleRemove = () => {
    this.props.onRemove(this.props.id);
  }

  handlePositionPick = () => {
    this.props.onPositionPick(this.props.id);
  }

  handleTitleChange = (e) => {
    this.props.onTitleChange(this.props.id, e.target.value || null);
  }

  handleDescriptionChange = (e) => {
    this.props.onDescriptionChange(this.props.id, e.target.value || null);
  }

  handleTakenAtChange = (e) => {
    this.props.onTakenAtChange(this.props.id, e.target.value ? new Date(e.target.value) : null);
  }

  handleTagAdded = ({ name }) => {
    if (!this.props.tags.includes(name)) {
      this.props.onTagsChange(this.props.id, [...this.props.tags, name]);
    }
  }

  handleTagDeleted = (i) => {
    const newTags = [...this.props.tags];
    newTags.splice(i, 1);
    this.props.onTagsChange(this.props.id, newTags);
  }

  render() {
    const { id, filename, dataURL, position, title, description, disabled, takenAt, tags, error } = this.props;
    return (
      <Thumbnail key={id} src={dataURL || require('fm3/images/spinnerbar.gif')} alt={filename}>
        {error && <Alert bsStyle="danger">{error}</Alert>}
        <fieldset disabled={disabled}>
          <FormGroup>
            <FormControl placeholder="Názov" type="text" value={title} onChange={this.handleTitleChange} />
          </FormGroup>
          <FormGroup>
            <FormControl placeholder="Popis" componentClass="textarea" value={description} onChange={this.handleDescriptionChange} />
          </FormGroup>
          <FormGroup>
            <FormControl
              type="datetime-local"
              placeholder="Dátum a čas fotenia"
              value={takenAt ? `${zeropad(takenAt.getFullYear(), 4)}-${zeropad(takenAt.getMonth() + 1)}-${zeropad(takenAt.getDate())}T${zeropad(takenAt.getHours())}:${zeropad(takenAt.getMinutes())}:${zeropad(takenAt.getSeconds())}` : ''}
              onChange={this.handleTakenAtChange}
            />
          </FormGroup>
          <FormGroup>
            <InputGroup>
              <FormControl
                type="text"
                placeholder="Pozícia"
                value={position ? `${formatGpsCoord(position.lat, 'SN')}, ${formatGpsCoord(position.lon, 'WE')}` : ''}
                onClick={this.handlePositionPick}
                readOnly
              />
              <InputGroup.Button>
                <Button onClick={this.handlePositionPick}><FontAwesomeIcon icon="dot-circle-o" />Nastaviť pozíciu</Button>
              </InputGroup.Button>
            </InputGroup>
          </FormGroup>
          <FormGroup>
            <ReactTags
              tags={tags.map(tag => ({ id: tag, name: tag }))}
              handleAddition={this.handleTagAdded}
              handleDelete={this.handleTagDeleted}
              allowNew
            />
          </FormGroup>
          {' '}
          <Button onClick={this.handleRemove} bsStyle="danger"><FontAwesomeIcon icon="times" />Odstrániť</Button>
        </fieldset>
      </Thumbnail>
    );
  }
}

function zeropad(v, n = 2) {
  const raw = `0000${v}`;
  return raw.substring(raw.length - n, raw.length);
}
<commit_msg>tags placeholder<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ReactTags from 'react-tag-autocomplete';
import 'react-tag-autocomplete/example/styles.css';

import Button from 'react-bootstrap/lib/Button';
import Thumbnail from 'react-bootstrap/lib/Thumbnail';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import FormControl from 'react-bootstrap/lib/FormControl';
import InputGroup from 'react-bootstrap/lib/InputGroup';
import Alert from 'react-bootstrap/lib/Alert';

import { formatGpsCoord } from 'fm3/geoutils';
import * as FmPropTypes from 'fm3/propTypes';

export default class GalleryUploadItem extends React.Component {
  static propTypes = {
    id: PropTypes.number.isRequired,
    filename: PropTypes.string,
    dataURL: PropTypes.string,
    position: FmPropTypes.point,
    title: PropTypes.string,
    description: PropTypes.string,
    takenAt: PropTypes.instanceOf(Date),
    tags: PropTypes.arrayOf(PropTypes.string.isRequired),
    error: PropTypes.string,
    onRemove: PropTypes.func.isRequired,
    onPositionPick: PropTypes.func.isRequired,
    onTitleChange: PropTypes.func.isRequired,
    onDescriptionChange: PropTypes.func.isRequired,
    onTakenAtChange: PropTypes.func.isRequired,
    onTagsChange: PropTypes.func.isRequired,
    disabled: PropTypes.bool,
  }

  handleRemove = () => {
    this.props.onRemove(this.props.id);
  }

  handlePositionPick = () => {
    this.props.onPositionPick(this.props.id);
  }

  handleTitleChange = (e) => {
    this.props.onTitleChange(this.props.id, e.target.value || null);
  }

  handleDescriptionChange = (e) => {
    this.props.onDescriptionChange(this.props.id, e.target.value || null);
  }

  handleTakenAtChange = (e) => {
    this.props.onTakenAtChange(this.props.id, e.target.value ? new Date(e.target.value) : null);
  }

  handleTagAdded = ({ name }) => {
    if (!this.props.tags.includes(name)) {
      this.props.onTagsChange(this.props.id, [...this.props.tags, name]);
    }
  }

  handleTagDeleted = (i) => {
    const newTags = [...this.props.tags];
    newTags.splice(i, 1);
    this.props.onTagsChange(this.props.id, newTags);
  }

  render() {
    const { id, filename, dataURL, position, title, description, disabled, takenAt, tags, error } = this.props;
    return (
      <Thumbnail key={id} src={dataURL || require('fm3/images/spinnerbar.gif')} alt={filename}>
        {error && <Alert bsStyle="danger">{error}</Alert>}
        <fieldset disabled={disabled}>
          <FormGroup>
            <FormControl placeholder="Názov" type="text" value={title} onChange={this.handleTitleChange} />
          </FormGroup>
          <FormGroup>
            <FormControl placeholder="Popis" componentClass="textarea" value={description} onChange={this.handleDescriptionChange} />
          </FormGroup>
          <FormGroup>
            <FormControl
              type="datetime-local"
              placeholder="Dátum a čas fotenia"
              value={takenAt ? `${zeropad(takenAt.getFullYear(), 4)}-${zeropad(takenAt.getMonth() + 1)}-${zeropad(takenAt.getDate())}T${zeropad(takenAt.getHours())}:${zeropad(takenAt.getMinutes())}:${zeropad(takenAt.getSeconds())}` : ''}
              onChange={this.handleTakenAtChange}
            />
          </FormGroup>
          <FormGroup>
            <InputGroup>
              <FormControl
                type="text"
                placeholder="Pozícia"
                value={position ? `${formatGpsCoord(position.lat, 'SN')}, ${formatGpsCoord(position.lon, 'WE')}` : ''}
                onClick={this.handlePositionPick}
                readOnly
              />
              <InputGroup.Button>
                <Button onClick={this.handlePositionPick}><FontAwesomeIcon icon="dot-circle-o" />Nastaviť pozíciu</Button>
              </InputGroup.Button>
            </InputGroup>
          </FormGroup>
          <FormGroup>
            <ReactTags
              placeholder="Tagy"
              tags={tags.map(tag => ({ id: tag, name: tag }))}
              handleAddition={this.handleTagAdded}
              handleDelete={this.handleTagDeleted}
              allowNew
            />
          </FormGroup>
          {' '}
          <Button onClick={this.handleRemove} bsStyle="danger"><FontAwesomeIcon icon="times" />Odstrániť</Button>
        </fieldset>
      </Thumbnail>
    );
  }
}

function zeropad(v, n = 2) {
  const raw = `0000${v}`;
  return raw.substring(raw.length - n, raw.length);
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import s from './HomePage.scss';
import withStyles from '../../decorators/withStyles';
import Config from '../config.json';

const title = 'Connect with Fitbit';

@withStyles(s)
class HomePage extends Component {

  static contextTypes = {
    onSetTitle: PropTypes.func.isRequired,
  };

  componentWillMount() {
    this.context.onSetTitle(title);
  }

  render() {
    var authUrl = 'https://www.fitbit.com/oauth2/authorize' +
                  '?client_id=' + Config.fitbit.clientId;
    return (

{title}

              Sign in to Fitbit

    );
  }

}

export default HomePage;
<commit_msg>Flesh out Fitbit auth URL with state, other params<commit_after>import React, { Component, PropTypes } from 'react';
import s from './HomePage.scss';
import withStyles from '../../decorators/withStyles';
import Config from '../config.json';
import LocalStorage from '../LocalStorage';

const title = 'Connect with Fitbit';

@withStyles(s)
class HomePage extends Component {

  static contextTypes = {
    onSetTitle: PropTypes.func.isRequired,
  };

  componentWillMount() {
    this.context.onSetTitle(title);
  }

  render() {
    var scopes = 'activity profile';
    var state = new Date().getTime() / 1000;
    LocalStorage.set('state', state);
    var authUrl = 'https://www.fitbit.com/oauth2/authorize' +
                  '?client_id=' + Config.fitbit.clientId +
                  '&response_type=token' +
                  '&scope=' + encodeURIComponent(scopes) +
                  '&redirect_uri=' +
                  encodeURIComponent(Config.fitbit.redirectUri) +
                  '&state=' + encodeURIComponent(state);
    return (

{title}

              Sign in to Fitbit

    );
  }

}

export default HomePage;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone';
import { imagePreview } from '../lib/utils';
import { upload } from '../lib/api';
import withIntl from '../lib/withIntl';
import { defineMessages } from 'react-intl';

class InputTypeDropzone extends React.Component {
  static propTypes = {
    defaultValue: PropTypes.string,
    className: PropTypes.string,
    onChange: PropTypes.func.isRequired,
    options: PropTypes.object,
  };

  constructor(props) {
    super(props);
    this.renderContainer = this.renderContainer.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      loading: false,
      value: props.defaultValue,
      url: props.defaultValue,
    }; // value can be base64 encoded after upload, url is always an url
    this.messages = defineMessages({
      placeholder: {
        id: 'uploadImage.placeholder',
        defaultMessage: 'Drop an image or click to upload',
      },
      isDragActive: {
        id: 'uploadImage.isDragActive',
        defaultMessage: "Drop it like it's hot 🔥",
      },
      isDragReject: {
        id: 'uploadImage.isDragReject',
        defaultMessage: '🚫 This file type is not accepted',
      },
      error: { id: 'uploadImage.error', defaultMessage: 'Error: {error}' },
    });
  }

  handleChange(files) {
    if (!files) {
      this.setState({ value: null, url: null, loading: false });
      return this.props.onChange(null);
    }

    this.setState({ loading: true });
    // for e2e testing purposes
    if (window.location.hostname === 'localhost') {
      const fileUrl = 'https://d.pr/free/i/OlQVIb+';
      return setTimeout(() => {
        this.setState({ value: fileUrl, url: fileUrl, loading: false });
        return this.props.onChange(fileUrl);
      }, 2500);
    }

    const file = files[0];
    upload(file)
      .then(fileUrl => {
        this.setState({ value: fileUrl, url: fileUrl, loading: false });
        return this.props.onChange(fileUrl);
      })
      .catch(err => {
        console.error('>>> error uploading image', file, err);
        this.setState({
          error: 'error uploading image, please try again',
          loading: false,
        });
      });
  }

  renderContainer({ isDragActive, isDragReject }) {
    const { intl } = this.props;

    let messageId = 'placeholder';
    if (isDragActive) {
      messageId = 'isDragActive';
    }
    if (isDragReject) {
      messageId = 'isDragReject';
    }
    if (this.state.error) {
      messageId = 'error';
    }

    return (

        {messageId && (
          <div className={`message ${messageId}`}>
            {intl.formatMessage(this.messages[messageId], {
              error: this.state.error,
            })}

        )}
        {this.state.loading && (

        )}

    );
  }

  render() {
    const options = this.props.options || {};
    options.accept = options.accept || 'image/png, image/jpeg';

    return (
      <div className={`InputTypeDropzone ${this.props.className}`}>

        <Dropzone
          multiple={false}
          onDrop={this.handleChange}
          className={`${this.props.name}-dropzone dropzone ${!this.state
            .value && 'empty'}`}
          style={{}}
          {...options}
        >
          {this.renderContainer}
        </Dropzone>
        {this.state.value && (
           this.handleChange(null)}>
            ❌ remove image

        )}

    );
  }
}

export default withIntl(InputTypeDropzone);
<commit_msg>Show error message when file size exceed limit<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone';
import { get } from 'lodash';
import { imagePreview } from '../lib/utils';
import { upload } from '../lib/api';
import withIntl from '../lib/withIntl';
import { defineMessages } from 'react-intl';

class InputTypeDropzone extends React.Component {
  static propTypes = {
    defaultValue: PropTypes.string,
    className: PropTypes.string,
    onChange: PropTypes.func.isRequired,
    options: PropTypes.object,
  };

  constructor(props) {
    super(props);
    this.renderContainer = this.renderContainer.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      loading: false,
      value: props.defaultValue,
      url: props.defaultValue,
    }; // value can be base64 encoded after upload, url is always an url
    this.messages = defineMessages({
      placeholder: {
        id: 'uploadImage.placeholder',
        defaultMessage: 'Drop an image or click to upload',
      },
      isDragActive: {
        id: 'uploadImage.isDragActive',
        defaultMessage: "Drop it like it's hot 🔥",
      },
      isDragReject: {
        id: 'uploadImage.isDragReject',
        defaultMessage: '🚫 This file type is not accepted',
      },
      error: { id: 'uploadImage.error', defaultMessage: 'Error: {error}' },
    });
  }

  handleChange(files) {
    if (!files) {
      this.setState({ value: null, url: null, loading: false });
      return this.props.onChange(null);
    }

    this.setState({ loading: true });
    // for e2e testing purposes
    if (window.location.hostname === 'localhost') {
      const fileUrl = 'https://d.pr/free/i/OlQVIb+';
      return setTimeout(() => {
        this.setState({ value: fileUrl, url: fileUrl, loading: false });
        return this.props.onChange(fileUrl);
      }, 2500);
    }

    const file = files[0];
    upload(file)
      .then(fileUrl => {
        this.setState({
          value: fileUrl,
          url: fileUrl,
          loading: false,
          error: null,
        });
        return this.props.onChange(fileUrl);
      })
      .catch(err => {
        console.error('>>> error uploading image', file, err);
        const message = get(err, ['json', 'error', 'fields', 'file']);
        this.setState({
          error: message || 'error uploading image, please try again',
          loading: false,
        });
      });
  }

  renderContainer({ isDragActive, isDragReject }) {
    const { intl } = this.props;

    let messageId = 'placeholder';
    if (isDragActive) {
      messageId = 'isDragActive';
    }
    if (isDragReject) {
      messageId = 'isDragReject';
    }
    if (this.state.error) {
      messageId = 'error';
    }

    return (

        {messageId && (
          <div className={`message ${messageId}`}>
            {intl.formatMessage(this.messages[messageId], {
              error: this.state.error,
            })}

        )}
        {this.state.loading && (

        )}

    );
  }

  render() {
    const options = this.props.options || {};
    options.accept = options.accept || 'image/png, image/jpeg';

    return (
      <div className={`InputTypeDropzone ${this.props.className}`}>

        <Dropzone
          multiple={false}
          onDrop={this.handleChange}
          className={`${this.props.name}-dropzone dropzone ${!this.state
            .value && 'empty'}`}
          style={{}}
          {...options}
        >
          {this.renderContainer}
        </Dropzone>
        {this.state.value && (
           this.handleChange(null)}>
            ❌ remove image

        )}

    );
  }
}

export default withIntl(InputTypeDropzone);
<|endoftext|>