<commit_before>import React from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { prefetch, defer } from 'react-fetcher'
import { commentUrl, peopleUrl } from '../../routes'
import { FETCH_PERSON, fetchPerson, fetchThanks, navigate } from '../../actions'
import { saveCurrentCommunityId } from '../../actions/util'
import { capitalize, compact, get, some, includes } from 'lodash'
import { isNull, map, omitBy, sortBy } from 'lodash/fp'
import { VIEWED_PERSON, VIEWED_SELF, trackEvent } from '../../util/analytics'
import { findError } from '../../actions/util'
import PostList from '../../components/PostList'
import A from '../../components/A'
import { fetch, ConnectedPostList } from '../ConnectedPostList'
import { refetch } from '../../util/caching'
import AccessErrorMessage from '../../components/AccessErrorMessage'
import CoverImagePage from '../../components/CoverImagePage'
import Icon from '../../components/Icon'
import Comment from '../../components/Comment'
import { parse } from 'url'
import moment from 'moment'
import { getPost } from '../../models/post'
import { getCurrentCommunity } from '../../models/community'

const { func, object } = React.PropTypes

const defaultBanner = 'https://d3ngex8q79bk55.cloudfront.net/misc/default_user_banner.jpg'
const spacer =   •
const subject = 'person'

const getFetchOpts = query => {
  switch (query.show) {
    case 'request':
      return {tag: 'request', type: null}
    case 'offer':
      return {tag: 'offer', type: null}
    case 'event':
      return {tag: null, type: 'event'}
    default:
      return {tag: null, type: null}
  }
}

const initialFetch = (id, query) => {
  if (query.show === 'thank') {
    return fetchThanks(id)
  } else {
    return fetch(subject, id, getFetchOpts(query))
  }
}

const PersonProfile = compose(
  prefetch(({ store, dispatch, params: { id }, query }) =>
  Promise.all([
    dispatch(fetchPerson(id))
    .then(({ payload: { shared_communities }, error }) => {
      if (error || !shared_communities) return
      let { currentCommunityId, people: { current } } = store.getState()
      if (includes(shared_communities, currentCommunityId)) return
      return saveCurrentCommunityId(dispatch, shared_communities[0] || 'all', current.id)
    }),
    dispatch(initialFetch(id, query))
  ])),
  defer(({ store, params: { id } }) => {
    let state = store.getState()
    let person = state.people[id]
    if (!person) return
    let currentUser = state.people.current
    if (get(currentUser, 'id') === person.id) {
      return trackEvent(VIEWED_SELF)
    } else {
      return trackEvent(VIEWED_PERSON, {person})
    }
  }),
  connect((state, { params: { id } }) => {
    const person = state.people[id]
    return omitBy(isNull, {
      person,
      community: getCurrentCommunity(state),
      currentUser: state.people.current,
      error: findError(state.errors, FETCH_PERSON, 'people', id),
      recentRequest: getPost(get(person, 'recent_request_id'), state),
      recentOffer: getPost(get(person, 'recent_offer_id'), state)
    })
  })
)(props => {
  const { person, error } = props
  if (error) return <AccessErrorMessage error={error}/>
  if (!person || !person.grouped_post_count) return
Loading...

  const {
    params: { id }, location: { query }, recentRequest, recentOffer, community
  } = props
  const category = query.show
  const {
    banner_url, bio, tags, location, url, created_at,
    facebook_url, linkedin_url, twitter_name
  } = person

  const joinDate = moment(created_at).format('MMMM YYYY')
  const requestCount = person.grouped_post_count.request || 0
  const offerCount = person.grouped_post_count.offer || 0
  const TabLink = setupTabLink(props)
  const postsToHide = category ? [] : map('id', compact([recentRequest, recentOffer]))

  return <CoverImagePage id='person' image={banner_url || defaultBanner}>

      <div className='avatar'
        style={{backgroundImage: `url(${person.avatar_url})`}}/>

        {person.name}

          {facebook_url && <SocialMediaIcon type='facebook' value={facebook_url}/>}
          {twitter_name && <SocialMediaIcon type='twitter' value={twitter_name}/>}
          {linkedin_url && <SocialMediaIcon type='linkedin' value={linkedin_url}/>}

        {location && {location}{spacer}}
        {url &&
          {parse(url).hostname}
          {spacer}
        }
        Joined {joinDate}

{bio}

    {some(tags) &&

Skills

      {tags.map(tag => <A key={tag} to={peopleUrl(community) + `?search=%23${tag}`}>
        #{tag}
      )}

}

      <TabLink category='offer' count={offerCount}/>
      <TabLink category='request' count={requestCount}/>
      <TabLink category='thank' count={person.thank_count}/>
      <TabLink category='event' count={person.event_count}/>

    {!category && recentRequest &&

Recent request

      <PostList posts={[recentRequest]} hideMobileSearch/>

}
    {!category && recentOffer &&

Recent offer

      <PostList posts={[recentOffer]} hideMobileSearch/>

}
    <ListLabel category={category}/>
    {category === 'thank'
      ? <Thanks person={person}/>
      : <ConnectedPostList {...{subject, id, query: getFetchOpts(query)}}
          hide={postsToHide}
          hideMobileSearch/>}
  </CoverImagePage>
})

PersonProfile.propTypes = {
  params: object,
  person: object,
  children: object,
  currentUser: object,
  error: object,
  location: object,
  dispatch: func,
  recentRequest: object,
  recentOffer: object
}

export default PersonProfile

const setupTabLink = (props) => {
  const { location, dispatch } = props
  const { query } = location

  const TabLink = ({ category, count }) => {
    const isActive = category === query.show
    const toggle = () =>
      dispatch(refetch({show: isActive ? null : category}, location))

    return
      {count} {capitalize(category)}{Number(count) === 1 ? '' : 's'}

  }

  return TabLink
}

const ListLabel = ({ category }) => {
  let label
  switch (category) {
    case 'offer': label = 'Offers'; break
    case 'request': label = 'Requests'; break
    case 'thank': label = 'Thanks'; break
    case 'event': label = 'Events'; break
    default: label = 'Recent posts'
  }
  return
{label}

}

const SocialMediaIcon = ({ type, value }) => {
  let href
  switch (type) {
    case 'facebook':
    case 'linkedin':
      href = value
      break
    case 'twitter':
      href = `http://twitter.com/${value}`
  }

  return
    <Icon name={type}/>

}

const Thanks = connect((state, { person }) => ({
  thanks: sortBy(t => -t.created_at, state.thanks[person.id])
}))(({ thanks, person, dispatch }) => {
  const visit = comment => dispatch(navigate(commentUrl(comment)))
  return

    {thanks.map(thank =>

        <A to={`/u/${thank.thankedBy.id}`}>{thank.thankedBy.name}
         thanked {person.name.split(' ')[0]} for:

      <Comment comment={{...thank.comment, user: person}} truncate
        expand={() => visit(thank.comment)}/>

)}

})
<commit_msg>use NonLinkAvatar component on profiles<commit_after>import React from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { prefetch, defer } from 'react-fetcher'
import { commentUrl, peopleUrl } from '../../routes'
import { FETCH_PERSON, fetchPerson, fetchThanks, navigate } from '../../actions'
import { saveCurrentCommunityId } from '../../actions/util'
import { capitalize, compact, get, some, includes } from 'lodash'
import { isNull, map, omitBy, sortBy } from 'lodash/fp'
import { VIEWED_PERSON, VIEWED_SELF, trackEvent } from '../../util/analytics'
import { findError } from '../../actions/util'
import PostList from '../../components/PostList'
import A from '../../components/A'
import { fetch, ConnectedPostList } from '../ConnectedPostList'
import { refetch } from '../../util/caching'
import AccessErrorMessage from '../../components/AccessErrorMessage'
import CoverImagePage from '../../components/CoverImagePage'
import Icon from '../../components/Icon'
import Comment from '../../components/Comment'
import { parse } from 'url'
import { NonLinkAvatar } from '../../components/Avatar'
import moment from 'moment'
import { getPost } from '../../models/post'
import { getCurrentCommunity } from '../../models/community'

const { func, object } = React.PropTypes

const defaultBanner = 'https://d3ngex8q79bk55.cloudfront.net/misc/default_user_banner.jpg'
const spacer =   •
const subject = 'person'

const getFetchOpts = query => {
  switch (query.show) {
    case 'request':
      return {tag: 'request', type: null}
    case 'offer':
      return {tag: 'offer', type: null}
    case 'event':
      return {tag: null, type: 'event'}
    default:
      return {tag: null, type: null}
  }
}

const initialFetch = (id, query) => {
  if (query.show === 'thank') {
    return fetchThanks(id)
  } else {
    return fetch(subject, id, getFetchOpts(query))
  }
}

const PersonProfile = compose(
  prefetch(({ store, dispatch, params: { id }, query }) =>
  Promise.all([
    dispatch(fetchPerson(id))
    .then(({ payload: { shared_communities }, error }) => {
      if (error || !shared_communities) return
      let { currentCommunityId, people: { current } } = store.getState()
      if (includes(shared_communities, currentCommunityId)) return
      return saveCurrentCommunityId(dispatch, shared_communities[0] || 'all', current.id)
    }),
    dispatch(initialFetch(id, query))
  ])),
  defer(({ store, params: { id } }) => {
    let state = store.getState()
    let person = state.people[id]
    if (!person) return
    let currentUser = state.people.current
    if (get(currentUser, 'id') === person.id) {
      return trackEvent(VIEWED_SELF)
    } else {
      return trackEvent(VIEWED_PERSON, {person})
    }
  }),
  connect((state, { params: { id } }) => {
    const person = state.people[id]
    return omitBy(isNull, {
      person,
      community: getCurrentCommunity(state),
      currentUser: state.people.current,
      error: findError(state.errors, FETCH_PERSON, 'people', id),
      recentRequest: getPost(get(person, 'recent_request_id'), state),
      recentOffer: getPost(get(person, 'recent_offer_id'), state)
    })
  })
)(props => {
  const { person, error } = props
  if (error) return <AccessErrorMessage error={error}/>
  if (!person || !person.grouped_post_count) return
Loading...

  const {
    params: { id }, location: { query }, recentRequest, recentOffer, community
  } = props
  const category = query.show
  const {
    banner_url, bio, tags, location, url, created_at,
    facebook_url, linkedin_url, twitter_name
  } = person

  const joinDate = moment(created_at).format('MMMM YYYY')
  const requestCount = person.grouped_post_count.request || 0
  const offerCount = person.grouped_post_count.offer || 0
  const TabLink = setupTabLink(props)
  const postsToHide = category ? [] : map('id', compact([recentRequest, recentOffer]))

  return <CoverImagePage id='person' image={banner_url || defaultBanner}>

      <NonLinkAvatar person={person}/>

        {person.name}

          {facebook_url && <SocialMediaIcon type='facebook' value={facebook_url}/>}
          {twitter_name && <SocialMediaIcon type='twitter' value={twitter_name}/>}
          {linkedin_url && <SocialMediaIcon type='linkedin' value={linkedin_url}/>}

        {location && {location}{spacer}}
        {url &&
          {parse(url).hostname}
          {spacer}
        }
        Joined {joinDate}

{bio}

    {some(tags) &&

Skills

      {tags.map(tag => <A key={tag} to={peopleUrl(community) + `?search=%23${tag}`}>
        #{tag}
      )}

}

      <TabLink category='offer' count={offerCount}/>
      <TabLink category='request' count={requestCount}/>
      <TabLink category='thank' count={person.thank_count}/>
      <TabLink category='event' count={person.event_count}/>

    {!category && recentRequest &&

Recent request

      <PostList posts={[recentRequest]} hideMobileSearch/>

}
    {!category && recentOffer &&

Recent offer

      <PostList posts={[recentOffer]} hideMobileSearch/>

}
    <ListLabel category={category}/>
    {category === 'thank'
      ? <Thanks person={person}/>
      : <ConnectedPostList {...{subject, id, query: getFetchOpts(query)}}
          hide={postsToHide}
          hideMobileSearch/>}
  </CoverImagePage>
})

PersonProfile.propTypes = {
  params: object,
  person: object,
  children: object,
  currentUser: object,
  error: object,
  location: object,
  dispatch: func,
  recentRequest: object,
  recentOffer: object
}

export default PersonProfile

const setupTabLink = (props) => {
  const { location, dispatch } = props
  const { query } = location

  const TabLink = ({ category, count }) => {
    const isActive = category === query.show
    const toggle = () =>
      dispatch(refetch({show: isActive ? null : category}, location))

    return
      {count} {capitalize(category)}{Number(count) === 1 ? '' : 's'}

  }

  return TabLink
}

const ListLabel = ({ category }) => {
  let label
  switch (category) {
    case 'offer': label = 'Offers'; break
    case 'request': label = 'Requests'; break
    case 'thank': label = 'Thanks'; break
    case 'event': label = 'Events'; break
    default: label = 'Recent posts'
  }
  return
{label}

}

const SocialMediaIcon = ({ type, value }) => {
  let href
  switch (type) {
    case 'facebook':
    case 'linkedin':
      href = value
      break
    case 'twitter':
      href = `http://twitter.com/${value}`
  }

  return
    <Icon name={type}/>

}

const Thanks = connect((state, { person }) => ({
  thanks: sortBy(t => -t.created_at, state.thanks[person.id])
}))(({ thanks, person, dispatch }) => {
  const visit = comment => dispatch(navigate(commentUrl(comment)))
  return

    {thanks.map(thank =>

        <A to={`/u/${thank.thankedBy.id}`}>{thank.thankedBy.name}
         thanked {person.name.split(' ')[0]} for:

      <Comment comment={{...thank.comment, user: person}} truncate
        expand={() => visit(thank.comment)}/>

)}

})
<|endoftext|>


<commit_before>import { push } from 'connected-react-router/esm/immutable'
import invariant from 'invariant'
import * as React from 'react'
import GoogleLogin from 'react-google-login'
import * as redux from 'react-redux'
import * as M from '@material-ui/core'

import * as Notifications from 'containers/Notifications'
import * as Config from 'utils/Config'
import * as NamedRoutes from 'utils/NamedRoutes'
import * as Sentry from 'utils/Sentry'
import defer from 'utils/defer'

import * as actions from './actions'
import * as errors from './errors'
import googleLogo from './google-logo.svg'

const MUTEX_POPUP = 'sso:google:popup'
const MUTEX_REQUEST = 'sso:google:request'

export default function SSOGoogle({ mutex, next, ...props }) {
  const cfg = Config.useConfig()
  invariant(!!cfg.googleClientId, 'Auth.SSO.Google: config missing "googleClientId"')

  const sentry = Sentry.use()
  const dispatch = redux.useDispatch()
  const { push: notify } = Notifications.use()
  const { urls } = NamedRoutes.use()
  const { claim, release } = mutex

  const handleClick = (onClick) => (...args) => {
    if (mutex.current) return
    claim(MUTEX_POPUP)
    onClick(...args)
  }

  const handleSuccess = React.useCallback(
    async (user) => {
      const provider = 'google'
      const { id_token: token } = user.getAuthResponse()
      const result = defer()
      claim(MUTEX_REQUEST)
      try {
        dispatch(actions.signIn({ provider, token }, result.resolver))
        await result.promise
      } catch (e) {
        if (e instanceof errors.SSOUserNotFound) {
          if (cfg.ssoAuth === true) {
            dispatch(push(urls.ssoSignUp({ provider, token, next })))
            // dont release mutex on redirect
            return
          }
          notify(
            'No Quilt user linked to this Google account. Notify your Quilt administrator.',
          )
        } else {
          notify('Unable to sign in with Google. Try again later or contact support.')
          sentry('captureException', e)
        }
        release(MUTEX_REQUEST)
      }
    },
    [dispatch, claim, release, sentry, notify, cfg.ssoAuth, next, urls],
  )

  const handleFailure = React.useCallback(
    ({ error: code, details }) => {
      if (code !== 'popup_closed_by_user') {
        notify(`Unable to sign in with Google. ${e.details}`)
        const e = new errors.SSOError({ provider: 'google', code, details })
        sentry('captureException', e)
      }
      release(MUTEX_POPUP)
    },
    [release, sentry, notify],
  )

  return (
    <GoogleLogin
      clientId={cfg.googleClientId}
      onSuccess={handleSuccess}
      onFailure={handleFailure}
      cookiePolicy="single_host_origin"
      disabled={!!mutex.current}
      render={({ onClick, disabled }) => (
        <M.Button
          variant="outlined"
          onClick={handleClick(onClick)}
          disabled={disabled}
          {...props}
        >
          {mutex.current === MUTEX_REQUEST ? (
            <M.CircularProgress size={18} />
          ) : (
            <M.Box component="img" src={googleLogo} alt="" />
          )}
          <M.Box mr={1} />
          Sign in with Google
        </M.Button>
      )}
    />
  )
}
<commit_msg>Fix error in SSOGoogle error handler (#2309)<commit_after>import { push } from 'connected-react-router/esm/immutable'
import invariant from 'invariant'
import * as React from 'react'
import GoogleLogin from 'react-google-login'
import * as redux from 'react-redux'
import * as M from '@material-ui/core'

import * as Notifications from 'containers/Notifications'
import * as Config from 'utils/Config'
import * as NamedRoutes from 'utils/NamedRoutes'
import * as Sentry from 'utils/Sentry'
import defer from 'utils/defer'

import * as actions from './actions'
import * as errors from './errors'
import googleLogo from './google-logo.svg'

const MUTEX_POPUP = 'sso:google:popup'
const MUTEX_REQUEST = 'sso:google:request'

export default function SSOGoogle({ mutex, next, ...props }) {
  const cfg = Config.useConfig()
  invariant(!!cfg.googleClientId, 'Auth.SSO.Google: config missing "googleClientId"')

  const sentry = Sentry.use()
  const dispatch = redux.useDispatch()
  const { push: notify } = Notifications.use()
  const { urls } = NamedRoutes.use()
  const { claim, release } = mutex

  const handleClick = (onClick) => (...args) => {
    if (mutex.current) return
    claim(MUTEX_POPUP)
    onClick(...args)
  }

  const handleSuccess = React.useCallback(
    async (user) => {
      const provider = 'google'
      const { id_token: token } = user.getAuthResponse()
      const result = defer()
      claim(MUTEX_REQUEST)
      try {
        dispatch(actions.signIn({ provider, token }, result.resolver))
        await result.promise
      } catch (e) {
        if (e instanceof errors.SSOUserNotFound) {
          if (cfg.ssoAuth === true) {
            dispatch(push(urls.ssoSignUp({ provider, token, next })))
            // dont release mutex on redirect
            return
          }
          notify(
            'No Quilt user linked to this Google account. Notify your Quilt administrator.',
          )
        } else {
          notify('Unable to sign in with Google. Try again later or contact support.')
          sentry('captureException', e)
        }
        release(MUTEX_REQUEST)
      }
    },
    [dispatch, claim, release, sentry, notify, cfg.ssoAuth, next, urls],
  )

  const handleFailure = React.useCallback(
    ({ error: code, details }) => {
      if (code !== 'popup_closed_by_user') {
        notify(`Unable to sign in with Google. ${details}`)
        const e = new errors.SSOError({ provider: 'google', code, details })
        sentry('captureException', e)
      }
      release(MUTEX_POPUP)
    },
    [release, sentry, notify],
  )

  return (
    <GoogleLogin
      clientId={cfg.googleClientId}
      onSuccess={handleSuccess}
      onFailure={handleFailure}
      cookiePolicy="single_host_origin"
      disabled={!!mutex.current}
      render={({ onClick, disabled }) => (
        <M.Button
          variant="outlined"
          onClick={handleClick(onClick)}
          disabled={disabled}
          {...props}
        >
          {mutex.current === MUTEX_REQUEST ? (
            <M.CircularProgress size={18} />
          ) : (
            <M.Box component="img" src={googleLogo} alt="" />
          )}
          <M.Box mr={1} />
          Sign in with Google
        </M.Button>
      )}
    />
  )
}
<|endoftext|>


<commit_before>/**
 * Commands Controller.
 *
 * Manages HTTP requests to /commands.
 *
 * Grammar that the parser understands:
 *  Turn the <tag> light <on|off>
 *  Turn <tag> <on|off>
 *  Shut <tag> <on|off>
 *  Shut the <tag> light <on|off>
 *  When was <tag> last <boolean>
 *  Is <tag> <boolean>
 *  Is <tag> not <boolean>
 *
 * <tag> must match the .name property of one of the /things objects
 *  in order for the command to be executed.
 *
 * Sample curl command to test from the command line:
 * curl -H 'Authorization:Bearer '<JSONWebToken>'
 *        -H "Content-Type: application/json"
 *        -H "Accept: application/json"
 *        -d '{"text":"turn the kitchen light on"}'
 *        https://localhost:4443/commands
 *        -k
 *
 * HTTP Response Codes        Status message
 *              201           Command Created
 *              400           Text element not defined
 *              404           Thing Not Found
 *              406           Unable to understand command
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const PromiseRouter = require('express-promise-router');
const AddonManager = require('../addon-manager');
const CommandUtils = require('../command-utils');
const IntentParser = require('../models/intentparser');
const Things = require('../models/things');

const CommandsController = PromiseRouter();

function internalError(response) {
  response.status(400).json({
    message: 'Sorry, something went wrong.',
  });
}

function thingNotFound(response, name) {
  response.status(400).json({
    message: `Sorry, I couldn't find ${name}.`,
  });
}

function invalidForDevice(response) {
  response.status(400).json({
    message: 'Sorry, I\'m afraid I can\'t do that.',
  });
}

function propertyNotFound(response, thing, property) {
  response.status(400).json({
    message: `Sorry, that thing has no property named ${property}.`,
  });
}

function invalidCommand(response) {
  response.status(400).json({
    message: 'Sorry, I didn\'t understand that.',
  });
}

function failedToSet(response) {
  response.status(400).json({
    message: 'Sorry, that didn\'t work.',
  });
}

/**
 * Parses the intent for a text sentence and sends to the intent parser to
 * determine intent. Then executes the intent as an action on the thing API.
 */
CommandsController.post('/', async (request, response) => {
  if (!request.body || !request.body.hasOwnProperty('text')) {
    response.status(400).json({
      message: 'Text element not defined',
    });
    return;
  }

  let titles = await Things.getThingTitles();
  titles = titles.concat(Object.keys(CommandUtils.plurals));
  titles = titles.map((t) => t.toLowerCase());

  try {
    await IntentParser.train(titles);
  } catch (e) {
    console.log('Error training:', e);
    internalError(response);
    return;
  }

  let payload;
  try {
    payload = await IntentParser.query(request.body.text);
  } catch (e) {
    console.log('Error parsing intent:', e);
    invalidCommand(response);
    return;
  }

  const title = payload.thing;
  const thing = await Things.getThingByTitle(title);

  if (!thing) {
    thingNotFound(response, name);
    return;
  }

  const properties = {
    on: CommandUtils.findProperty(thing, 'OnOffProperty', 'on'),
    color: CommandUtils.findProperty(thing, 'ColorProperty', 'color'),
    colorTemperature: CommandUtils.findProperty(thing,
                                                'ColorTemperatureProperty',
                                                'colorTemperature'),
    level: CommandUtils.findProperty(thing, 'LevelProperty', 'level'),
    brightness: CommandUtils.findProperty(thing,
                                          'BrightnessProperty',
                                          'level'),
  };

  for (const name in CommandUtils.booleans) {
    const bool = CommandUtils.booleans[name];
    if (!bool.hasOwnProperty('value') || !bool['@type']) {
      continue;
    }
    if (!properties[name]) {
      properties[name] = CommandUtils.findProperty(thing, bool['@type'], name);
    }
  }

  if (IntentParser.keywordsSet.includes(payload.keyword)) {
    await handleSet(payload, thing, properties, response);
  } else if (IntentParser.keywordsGet.includes(payload.keyword)) {
    await handleGet(payload, thing, properties, response);
  } else {
    invalidCommand(response);
  }
});

async function handleSet(payload, thing, properties, response) {
  let propertyName, value;

  if (['on', 'off'].includes(payload.value)) {
    if (!properties.on) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.on;
    value = payload.value === 'on';
  } else if (['warmer', 'cooler'].includes(payload.value)) {
    if (!properties.colorTemperature) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.colorTemperature;

    let current;
    try {
      current = await AddonManager.getProperty(thing.id, propertyName);
    } catch (e) {
      failedToSet(response);
      return;
    }

    value = payload.value === 'warmer' ? current - 100 : current + 100;
  } else if (['dim', 'brighten'].includes(payload.keyword) ||
             CommandUtils.percentages.hasOwnProperty(payload.value)) {
    if (!properties.brightness) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.brightness;

    const percent =
      payload.value ? CommandUtils.percentages[payload.value] : 10;

    if (payload.keyword === 'set') {
      value = percent;
    } else if (payload.keyword === 'dim' || payload.keyword === 'brighten') {
      let current;
      try {
        current = await AddonManager.getProperty(thing.id, propertyName);
      } catch (e) {
        failedToSet(response);
        return;
      }

      if (payload.keyword === 'dim') {
        value = current - percent;
      } else {
        value = current + percent;
      }
    }
  } else if (CommandUtils.colors.hasOwnProperty(payload.value)) {
    if (!properties.color) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.color;
    value = CommandUtils.colors[payload.value];
  } else {
    invalidCommand(response);
    return;
  }

  try {
    await AddonManager.setProperty(thing.id, propertyName, value);
  } catch (e) {
    failedToSet(response);
    return;
  }

  // Returning 201 to signify that the command was mapped to an
  // intent and matched a 'thing' in our list. Return a response to
  // caller with this status before the command finishes execution
  // as the execution can take some time (e.g. blinds)
  response.status(201).json({
    payload: payload,
  });
}

async function handleGet(payload, thing, properties, response) {
  if (CommandUtils.booleans[payload.value]) {
    const bool = CommandUtils.booleans[payload.value];

    let prop = properties[payload.value];
    let negated = false;
    if (!prop && !bool.value) {
      prop = properties[bool.negation];
      negated = true;
    }

    if (!prop) {
      propertyNotFound(response, payload.thing, payload.value);
      return;
    }

    let value = await AddonManager.getProperty(thing.id, prop);
    if (negated) {
      value = !value;
    }

    const valueStr = value ? payload.value : bool.negation;
    const verb = bool.verb || 'is';
    const answer = value ? 'Yes' : 'No';
    response.status(201).json({
      message: `${answer}, the ${payload.thing} ${verb} ${valueStr}.`,
      payload: payload,
    });
    return;
  }
  response.status(400).json({
    message: `I don't feel like dealing with thing ${payload.thing} property ${payload.value} today`,
  });
}

module.exports = CommandsController;
<commit_msg>Be helpful for non-boolean properties<commit_after>/**
 * Commands Controller.
 *
 * Manages HTTP requests to /commands.
 *
 * Grammar that the parser understands:
 *  Turn the <tag> light <on|off>
 *  Turn <tag> <on|off>
 *  Shut <tag> <on|off>
 *  Shut the <tag> light <on|off>
 *  When was <tag> last <boolean>
 *  Is <tag> <boolean>
 *  Is <tag> not <boolean>
 *
 * <tag> must match the .name property of one of the /things objects
 *  in order for the command to be executed.
 *
 * Sample curl command to test from the command line:
 * curl -H 'Authorization:Bearer '<JSONWebToken>'
 *        -H "Content-Type: application/json"
 *        -H "Accept: application/json"
 *        -d '{"text":"turn the kitchen light on"}'
 *        https://localhost:4443/commands
 *        -k
 *
 * HTTP Response Codes        Status message
 *              201           Command Created
 *              400           Text element not defined
 *              404           Thing Not Found
 *              406           Unable to understand command
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const PromiseRouter = require('express-promise-router');
const AddonManager = require('../addon-manager');
const CommandUtils = require('../command-utils');
const IntentParser = require('../models/intentparser');
const Things = require('../models/things');

const CommandsController = PromiseRouter();

function internalError(response) {
  response.status(400).json({
    message: 'Sorry, something went wrong.',
  });
}

function thingNotFound(response, name) {
  response.status(400).json({
    message: `Sorry, I couldn't find ${name}.`,
  });
}

function invalidForDevice(response) {
  response.status(400).json({
    message: 'Sorry, I\'m afraid I can\'t do that.',
  });
}

function propertyNotFound(response, thing, property) {
  response.status(400).json({
    message: `Sorry, that thing has no property named ${property}.`,
  });
}

function invalidCommand(response) {
  response.status(400).json({
    message: 'Sorry, I didn\'t understand that.',
  });
}

function failedToSet(response) {
  response.status(400).json({
    message: 'Sorry, that didn\'t work.',
  });
}

/**
 * Parses the intent for a text sentence and sends to the intent parser to
 * determine intent. Then executes the intent as an action on the thing API.
 */
CommandsController.post('/', async (request, response) => {
  if (!request.body || !request.body.hasOwnProperty('text')) {
    response.status(400).json({
      message: 'Text element not defined',
    });
    return;
  }

  let titles = await Things.getThingTitles();
  titles = titles.concat(Object.keys(CommandUtils.plurals));
  titles = titles.map((t) => t.toLowerCase());

  try {
    await IntentParser.train(titles);
  } catch (e) {
    console.log('Error training:', e);
    internalError(response);
    return;
  }

  let payload;
  try {
    payload = await IntentParser.query(request.body.text);
  } catch (e) {
    console.log('Error parsing intent:', e);
    invalidCommand(response);
    return;
  }

  const title = payload.thing;
  const thing = await Things.getThingByTitle(title);

  if (!thing) {
    thingNotFound(response, name);
    return;
  }

  const properties = {
    on: CommandUtils.findProperty(thing, 'OnOffProperty', 'on'),
    color: CommandUtils.findProperty(thing, 'ColorProperty', 'color'),
    colorTemperature: CommandUtils.findProperty(thing,
                                                'ColorTemperatureProperty',
                                                'colorTemperature'),
    level: CommandUtils.findProperty(thing, 'LevelProperty', 'level'),
    brightness: CommandUtils.findProperty(thing,
                                          'BrightnessProperty',
                                          'level'),
  };

  for (const name in CommandUtils.booleans) {
    const bool = CommandUtils.booleans[name];
    if (!bool.hasOwnProperty('value') || !bool['@type']) {
      continue;
    }
    if (!properties[name]) {
      properties[name] = CommandUtils.findProperty(thing, bool['@type'], name);
    }
  }

  if (IntentParser.keywordsSet.includes(payload.keyword)) {
    await handleSet(payload, thing, properties, response);
  } else if (IntentParser.keywordsGet.includes(payload.keyword)) {
    await handleGet(payload, thing, properties, response);
  } else {
    invalidCommand(response);
  }
});

async function handleSet(payload, thing, properties, response) {
  let propertyName, value;

  if (['on', 'off'].includes(payload.value)) {
    if (!properties.on) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.on;
    value = payload.value === 'on';
  } else if (['warmer', 'cooler'].includes(payload.value)) {
    if (!properties.colorTemperature) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.colorTemperature;

    let current;
    try {
      current = await AddonManager.getProperty(thing.id, propertyName);
    } catch (e) {
      failedToSet(response);
      return;
    }

    value = payload.value === 'warmer' ? current - 100 : current + 100;
  } else if (['dim', 'brighten'].includes(payload.keyword) ||
             CommandUtils.percentages.hasOwnProperty(payload.value)) {
    if (!properties.brightness) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.brightness;

    const percent =
      payload.value ? CommandUtils.percentages[payload.value] : 10;

    if (payload.keyword === 'set') {
      value = percent;
    } else if (payload.keyword === 'dim' || payload.keyword === 'brighten') {
      let current;
      try {
        current = await AddonManager.getProperty(thing.id, propertyName);
      } catch (e) {
        failedToSet(response);
        return;
      }

      if (payload.keyword === 'dim') {
        value = current - percent;
      } else {
        value = current + percent;
      }
    }
  } else if (CommandUtils.colors.hasOwnProperty(payload.value)) {
    if (!properties.color) {
      invalidForDevice(response);
      return;
    }

    propertyName = properties.color;
    value = CommandUtils.colors[payload.value];
  } else {
    invalidCommand(response);
    return;
  }

  try {
    await AddonManager.setProperty(thing.id, propertyName, value);
  } catch (e) {
    failedToSet(response);
    return;
  }

  // Returning 201 to signify that the command was mapped to an
  // intent and matched a 'thing' in our list. Return a response to
  // caller with this status before the command finishes execution
  // as the execution can take some time (e.g. blinds)
  response.status(201).json({
    payload: payload,
  });
}

async function handleGet(payload, thing, properties, response) {
  if (CommandUtils.booleans[payload.value]) {
    const bool = CommandUtils.booleans[payload.value];

    let prop = properties[payload.value];
    let negated = false;
    if (!prop && !bool.value) {
      prop = properties[bool.negation];
      negated = true;
    }

    if (!prop) {
      propertyNotFound(response, payload.thing, payload.value);
      return;
    }

    let value = await AddonManager.getProperty(thing.id, prop);
    if (negated) {
      value = !value;
    }

    const valueStr = value ? payload.value : bool.negation;
    const verb = bool.verb || 'is';
    const answer = value ? 'Yes' : 'No';
    response.status(201).json({
      message: `${answer}, the ${payload.thing} ${verb} ${valueStr}.`,
      payload: payload,
    });
    return;
  }
  response.status(400).json({
    message: `Sorry, that thing's property ${payload.value} is currently unsupported`,
  });
}

module.exports = CommandsController;
<|endoftext|>


<commit_before>/* @flow */
import React, { PureComponent } from 'react';
import { View, StyleSheet } from 'react-native';

import type { Actions } from '../types';
import { Label, LoadingIndicator, ZulipButton } from '../common';
import ConversationList from './ConversationList';

const componentStyles = StyleSheet.create({
  container: {
    flex: 1,
  },
  button: {
    margin: 8,
  },
  emptySlate: {
    flex: 1,
    textAlign: 'center',
    fontSize: 20,
  },
});

type Props = {
  actions: Actions,
  conversations: Object[],
  isLoading: boolean,
  presences: Object,
  usersByEmail: Object,
};

export default class ConversationsCard extends PureComponent<Props> {
  props: Props;

  static contextTypes = {
    styles: () => null,
  };

  handleSearchPeople = () => {
    const { actions } = this.props;
    actions.navigateToUsersScreen();
  };

  render() {
    const { styles } = this.context;
    const { actions, conversations, isLoading, presences, usersByEmail } = this.props;

    if (isLoading) {
      return <LoadingIndicator size={40} />;
    }

    return (
      <View style={[componentStyles.container, styles.background]}>
        <ZulipButton
          secondary
          style={componentStyles.button}
          text="Search people"
          onPress={this.handleSearchPeople}
        />
        {conversations.length === 0 ? (
          <Label style={componentStyles.emptySlate} text="No recent conversations" />
        ) : (
          <ConversationList
            actions={actions}
            conversations={conversations}
            presences={presences}
            usersByEmail={usersByEmail}
          />
        )}
      </View>
    );
  }
}
<commit_msg>ui: Add "create group" button<commit_after>/* @flow */
import React, { PureComponent } from 'react';
import { View, StyleSheet } from 'react-native';

import type { Actions } from '../types';
import { Label, LoadingIndicator, ZulipButton } from '../common';
import { IconPeople, IconSearch } from '../common/Icons';
import ConversationList from './ConversationList';

const componentStyles = StyleSheet.create({
  container: {
    flex: 1,
  },
  button: {
    margin: 8,
    flex: 1,
  },
  emptySlate: {
    flex: 1,
    textAlign: 'center',
    fontSize: 20,
  },
  row: {
    flexDirection: 'row',
  },
});

type Props = {
  actions: Actions,
  conversations: Object[],
  isLoading: boolean,
  presences: Object,
  usersByEmail: Object,
};

export default class ConversationsCard extends PureComponent<Props> {
  props: Props;

  static contextTypes = {
    styles: () => null,
  };

  render() {
    const { styles } = this.context;
    const { actions, conversations, isLoading, presences, usersByEmail } = this.props;

    if (isLoading) {
      return <LoadingIndicator size={40} />;
    }

    return (
      <View style={[componentStyles.container, styles.background]}>
        <View style={componentStyles.row}>
          <ZulipButton
            secondary
            Icon={IconPeople}
            style={componentStyles.button}
            text="Create group"
            onPress={actions.navigateToCreateGroup}
          />
          <ZulipButton
            secondary
            Icon={IconSearch}
            style={componentStyles.button}
            text="Search"
            onPress={actions.navigateToUsersScreen}
          />
        </View>
        {conversations.length === 0 ? (
          <Label style={componentStyles.emptySlate} text="No recent conversations" />
        ) : (
          <ConversationList
            actions={actions}
            conversations={conversations}
            presences={presences}
            usersByEmail={usersByEmail}
          />
        )}
      </View>
    );
  }
}
<|endoftext|>


<commit_before>// controller for start & end inputs
app.controller('inputsController', ['$scope', '$http', '$state', '$sce', 'RestaurantAndRoute', 'Auth', function($scope, $http, $state, $sce, RestaurantAndRoute, Auth) {

  if (!Auth.check()) {
    // if a user is not logged in, redirect to login page
    console.log('You are not logged in!');
    $state.go('login');

  } else {

    $scope.start; // start location input
    $scope.end; // end location input

    $scope.directions = ''; // directions from start to end

    $scope.logout = () => {
      Auth.delete();
      $state.go('login');
    }

    // POST users' start and end locations to server
    $scope.submit = function(form) {

      // to refresh states from main.map, need to redirect to main first
      $state.go('main');

      if (form.$valid) {
        RestaurantAndRoute.fetchRestaurants($scope.start, $scope.end).then(restaurants => {

          // update list of restaurants in the factory
          console.log('restaurants: ', restaurants);

          // switch states to show restaurants and map
          $state.go('main.map');

        }).catch(err => {
          console.log('Error submitting: ', err);
        })

        var directionsService = new google.maps.DirectionsService;
        var directionsDisplay = new google.maps.DirectionsRenderer;
        var map;
        var coord = new google.maps.LatLng(37.8, -122.4);

        function initMap(coord) { // creates a map
          map = new google.maps.Map(document.getElementById('map'), {
            center: coord,
            zoom: 14
          })
          // Associate the route with our current map
          directionsDisplay.setMap(map);
          //clear existing markers
          RestaurantAndRoute.removeMarkers();
          //add restaurant markers
          RestaurantAndRoute.addMarkers(map);

        }
        initMap(coord);

        // Immediately invoke the method with the passed in data.
        RestaurantAndRoute.calculateAndDisplayRoute(directionsService, directionsDisplay, $scope.start, $scope.end);

        // // POST start & end locations to server
        // // server *should* return directions and list of restaurants
        // $http({
        //   method: 'POST',
        //   url: '/maps/submit',
        //   headers: {
        //     'Content-Type': 'application/json'
        //   },
        //   data: {
        //     start: $scope.start,
        //     end: $scope.end
        //   }
        // }).then((response) => {

        //   var results = response.data.route;
        //   var route = response.data.route[0];
        //   var list = response.data.restaurants;
        //   console.log('Returned route: ', route);
        //   console.log('Returned restaurant: ', list);

        //   // reset the directions upon new search
        //   $scope.directions = '';

        //   // write out the steps from start to end
        //   results[0].legs[0].steps.forEach((step => {
        //     $scope.directions += step['html_instructions'] + '
';
        //   }));
        // }).catch((err) => {
        //   console.log('Error submitting: ', err);
        // })
      }
    };
  }

}]);
<commit_msg>Fix marker logic<commit_after>// controller for start & end inputs
app.controller('inputsController', ['$scope', '$http', '$state', '$sce', 'RestaurantAndRoute', 'Auth', function($scope, $http, $state, $sce, RestaurantAndRoute, Auth) {

  if (!Auth.check()) {
    // if a user is not logged in, redirect to login page
    console.log('You are not logged in!');
    $state.go('login');

  } else {

    $scope.start; // start location input
    $scope.end; // end location input

    $scope.directions = ''; // directions from start to end

    $scope.logout = () => {
      Auth.delete();
      $state.go('login');
    }

    // POST users' start and end locations to server
    $scope.submit = function(form) {

      // to refresh states from main.map, need to redirect to main first
      $state.go('main');

      if (form.$valid) {
        RestaurantAndRoute.fetchRestaurants($scope.start, $scope.end).then(restaurants => {

          // // update list of restaurants in the factory
          console.log('restaurants: ', restaurants);

          var directionsService = new google.maps.DirectionsService;
          var directionsDisplay = new google.maps.DirectionsRenderer;
          var map;
          var coord = new google.maps.LatLng(37.8, -122.4);

          function initMap(coord) { // creates a map
            map = new google.maps.Map(document.getElementById('map'), {
              center: coord,
              zoom: 14
            })
            // Associate the route with our current map
            directionsDisplay.setMap(map);
            //clear existing markers
            RestaurantAndRoute.removeMarkers();
            //add restaurant markers
            RestaurantAndRoute.addMarkers(map);
          }
          initMap(coord);
          RestaurantAndRoute.calculateAndDisplayRoute(directionsService, directionsDisplay, $scope.start, $scope.end);
        }).catch(err => {
          console.log('Error submitting: ', err);
        })

        // // POST start & end locations to server
        // // server *should* return directions and list of restaurants
        // $http({
        //   method: 'POST',
        //   url: '/maps/submit',
        //   headers: {
        //     'Content-Type': 'application/json'
        //   },
        //   data: {
        //     start: $scope.start,
        //     end: $scope.end
        //   }
        // }).then((response) => {

        //   var results = response.data.route;
        //   var route = response.data.route[0];
        //   var list = response.data.restaurants;
        //   console.log('Returned route: ', route);
        //   console.log('Returned restaurant: ', list);

        //   // reset the directions upon new search
        //   $scope.directions = '';

        //   // write out the steps from start to end
        //   results[0].legs[0].steps.forEach((step => {
        //     $scope.directions += step['html_instructions'] + '
';
        //   }));
        // }).catch((err) => {
        //   console.log('Error submitting: ', err);
        // })
      }
    };
  }

}]);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Question1',
  properties: [
    {
      name: 'question',
      label: 'Who was the best Star Wars robot?',
      view: {
        class: 'foam.u2.view.ChoiceView',
        class: 'foam.u2.view.RadioView',
        placeholder: 'Please select',
        choices: [
          'C3-PO',
          'R2-D2',
          'BB-8',
          'K-2SO'
        ]
      }
    },
    {
      name: 'answer',
      hidden: true,
      value: 'R2-D2'
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Question2',
  properties: [
    {
      name: 'question',
      label: 'Which of the following do robots make',
      view: {
        class: 'foam.u2.view.ChoiceView',
//        class: 'foam.u2.view.RadioView',
        placeholder: 'Please select',
        choices: [
          'Cars',
          'Washing Machines',
          'Computers',
          'Robots',
          'All of the Above'

        ]
      }
    },
    {
      name: 'answer',
      hidden: true,
      value: 'All of the Above'
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Scale',
  extends: 'foam.graphics.Circle',
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Snake',
  extends: 'foam.graphics.CView',

  requires: [
    'com.foamdev.demos.snake.Scale',
    'com.foamdev.demos.snake.Laser'
  ],

  imports: [
    'game',
    'timer',
    'R'
  ],

  properties: [
//    { name: 'scales', factory: function() { return []; } },
    [ 'sx', 240 ],
    [ 'sy', 240 ],
    [ 'vx', 1/5 ],
    [ 'vy', 0 ],
    [ 'length', 5 ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.timer.i$.sub(this.tick);
    },
    function up()    { this.vy = -1/5; this.vx =  0; },
    function down()  { this.vy =  1/5; this.vx =  0; },
    function left()  { this.vy =  0; this.vx = -1/5; },
    function right() { this.vy =  0; this.vx =  1/5; },
    function fire () {
      this.Laser.create({x: this.sx, y: this.sy, vx: this.vx, vy: this.vy});
    },
    function intersects(o) {
      if ( ! this.children.length ) return false;
      return this.children[this.children.length-1].intersects(o);
    }
  ],

  listeners: [
    {
      name: 'tick',
      isMerged: 150,
      code: function() {
        this.sx += this.vx * 2 * this.R;
        this.sy += this.vy * 2 * this.R;
        if ( this.children.length )
          this.children[this.children.length-1].color = 'green';
        this.add(this.Scale.create({x: this.sx, y: this.sy, radius: this.R, color: 'red'}));
        if ( this.children.length > this.length )
          this.children.shift();
      }
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Food',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.animation.Animation' ],

  imports: [ 'game' ],

  properties: [
    [ 'color', 'darkblue' ],
    [ 'radius', 10 ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.Animation.create({
        duration: 5000,
        f: ()=> this.radius = 0,
        onEnd: () => this.game.removeChild(this),
        objs: [this]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Mushroom',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.graphics.Box', 'foam.animation.Animation' ],

  imports: [ 'game' ],

  properties: [
    [ 'radius', 20 ],
    [ 'color',  'red' ],
    [ 'start',  Math.PI ],
    [ 'end',    0 ],
    'stem',
    [ 'exploded', false ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.add(this.stem = this.Box.create({
        x: -7.5,
        y: -0.5,
        width: 15,
        height: 20,
        color: 'gray'
      }));
    },

    function explode() {
      if ( this.exploded ) return;
      this.exploded = true;

      this.stem.color = 'red';

      this.Animation.create({
        duration: 600,
        f: ()=> {
          this.scaleX   = this.scaleY = 10;
          this.alpha    = 0;
          this.rotation = Math.PI * 6;
        },
        onEnd: () => this.game.removeChild(this),
        slots: [ this.scaleX$, this.scaleY$, this.alpha$, this.rotation$ ],
        objs: [this, this.stem]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Laser',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.animation.Animation' ],
  imports: [ 'game'],

  properties: [
    [ 'color', 'yellow' ],
    [ 'radius', 12 ],
    'vx',
    'vy'
  ],

  methods: [
    function init() {
      this.SUPER();

      this.game.addChild(this);
      this.Animation.create({
        duration: 5000,
        f: () => {
          this.x += 2000 * this.vx;
          this.y += 2000 * this.vy;
        },
        onEnd: () => this.game.removeChild(this),
        objs: [ this ]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Game',
  extends: 'foam.u2.Element',

  requires: [
    'com.foamdev.demos.snake.Food',
    'com.foamdev.demos.snake.Laser',
    'com.foamdev.demos.snake.Mushroom',
    'com.foamdev.demos.snake.Question1',
    'com.foamdev.demos.snake.Question2',
    'com.foamdev.demos.snake.Snake',
    'com.google.foam.demos.robot.Robot',
    'foam.graphics.Box as Rectangle',
    'foam.graphics.CView',
    'foam.graphics.Label',
    'foam.input.Gamepad',
    'foam.movement.Movement',
    'foam.physics.Collider',
    'foam.util.Timer'
  ],

  exports: [
    'R',
    'timer',
    'as game',
    'movement'
  ],

  constants: { R: 20 },

  properties: [
    {
      name: 'gamepad',
      factory: function() { return this.Gamepad.create(); }
    },
    {
      name: 'movement',
      factory: function() { return this.Movement.create(); }
    },
    {
      name: 'timer',
      factory: function() { return this.Timer.create(); }
    },
    [ 'width', 1600 ],
    [ 'height', 800 ],
    {
      name: 'snake',
      factory: function() { return this.Snake.create(); }
    },
    {
      name: 'table',
      factory: function() {
        return this.Rectangle.create({
          color: 'lightblue',
          width: 800, //window.innerWidth,
          height: 500, //window.innerHeight
        });
      }
    },
    {
      name: 'robot',
      factory: function() {
        return this.Robot.create({x:200, y:200});
      }
    },
    {
      name: 'collider',
      factory: function() { return this.Collider.create(); }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

//      this.addChild(this.collider);

      this.timer.i$.sub(this.tick);
      this.timer.start();

      this.gamepad.pressed.sub('button4', () => this.fire());
      this.gamepad.pressed.sub('button5', () => this.fire());

//      this.gamepad.pressed.sub('button1', () => robot.x+=10);
      this.gamepad.pressed.sub(function() { console.log('pressed', arguments); });

      this.addChild(this.robot);
      this.addChild(this.snake);

      // Setup Physics
      this.collider.collide = function(o1, o2) {
        if ( this.Laser.isInstance(o2) || this.Snake.isInstance(o2) ) {
          var tmp = o1;
          o1 = o2;
          o2 = tmp;
        }
        if ( this.Laser.isInstance(o1) ) {
          if ( this.Mushroom.isInstance(o2) ) {
            o2.explode();
            this.removeChild(o1);
          } else if ( this.Food.isInstance(o2) ) {
          }
        }
        if ( this.Snake.isInstance(o1) && this.Mushroom.isInstance(o2) ) {
          if ( o2.scaleX == 1 )
            this.gameOver();
          else
            this.removeChild(o2);
        }
        if ( this.Snake.isInstance(o1) && this.Food.isInstance(o2) ) {
          this.removeChild(o2);
          this.snake.length++;
        }
//        console.log('BANG', o1, o2);
      }.bind(this);
      this.collider.start();
    },

    function initE() {
      this.SUPER();
      this.focus();
      this.style({display:'flex'}).add(this.table).add(this.Question2.create());
    },

    function gameOver() {
      this.timer.stop();
//      this.collider.stop(); // TODO: add stop() method to collider
      this.table.color = 'orange';
      this.addChild(this.Label.create({
        text: 'Game Over',
        align: 'center',
        color: 'white',
        scaleX: 10,
        scaleY: 10,
        x: this.table.width/2,
        y: 100
      }));
    },

    function addChild(c) {
      this.table.add(c);
      if ( c.intersects ) this.collider.add(c);
    },

    function removeChild(c) {
      this.table.remove(c);
      if ( c.intersects ) this.collider.remove(c);
    },

    function addFood() {
      var R = this.R;
      var f = this.Food.create({
        x: Math.round(1+Math.random()*(this.table.width -4*R)/R)*2*R,
        y: Math.round(1+Math.random()*(this.table.height-4*R)/R)*2*R,
      });
      this.addChild(f);
    },

    function addMushroom() {
      var R = this.R;
      var m = this.Mushroom.create({
        x: Math.round(1+Math.random()*(this.table.width -4*R)/R)*2*R,
        y: Math.round(1+Math.random()*(this.table.height-4*R)/R)*2*R,
        scaleX: 1,
        scaleY: 1});

        /*
        TODO
      this.movement.animate(7000, function() {
        m.scaleX = m.scaleY = 1;
      })();
      */

      this.addChild(m);
    }
  ],

  listeners: [
    {
      name: 'tick',
      code: function(_, __, ___, t) {
        if ( this.gamepad.button0 ) this.robot.y -= 3;
        if ( this.gamepad.button1 ) this.robot.x += 3;
        if ( this.gamepad.button2 ) this.robot.y += 3;
        if ( this.gamepad.button3 ) this.robot.x -= 3;

        if ( t.get() % 10 == 0 ) this.addFood();
        if ( Math.random() < 0.02 ) this.addMushroom();
      }
    }
  ],

  actions: [
    {
      name: 'up',
      keyboardShortcuts: [ 38 /* up arrow */, 'w' ],
      code: function() { this.snake.up(); }
    },
    {
      name: 'down',
      keyboardShortcuts: [ 40 /* down arrow */, 's' ],
      code: function() { this.snake.down(); }
    },
    {
      name: 'left',
      keyboardShortcuts: [ 37 /* left arrow */, 'a' ],
      code: function() { this.snake.left(); }
    },
    {
      name: 'right',
      keyboardShortcuts: [ 39 /* right arrow */, 'd' ],
      code: function() { this.snake.right(); }
    },
    {
      name: 'fire',
      keyboardShortcuts: [ ' ', 'x' ],
      code: function() { this.snake.fire(); }
    }
  ]
});
<commit_msg>Mushrooms grow in Snake game.<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Question1',
  properties: [
    {
      name: 'question',
      label: 'Who was the best Star Wars robot?',
      view: {
        class: 'foam.u2.view.ChoiceView',
        class: 'foam.u2.view.RadioView',
        placeholder: 'Please select',
        choices: [
          'C3-PO',
          'R2-D2',
          'BB-8',
          'K-2SO'
        ]
      }
    },
    {
      name: 'answer',
      hidden: true,
      value: 'R2-D2'
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Question2',
  properties: [
    {
      name: 'question',
      label: 'Which of the following do robots make',
      view: {
        class: 'foam.u2.view.ChoiceView',
//        class: 'foam.u2.view.RadioView',
        placeholder: 'Please select',
        choices: [
          'Cars',
          'Washing Machines',
          'Computers',
          'Robots',
          'All of the Above'

        ]
      }
    },
    {
      name: 'answer',
      hidden: true,
      value: 'All of the Above'
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Scale',
  extends: 'foam.graphics.Circle',
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Snake',
  extends: 'foam.graphics.CView',

  requires: [
    'com.foamdev.demos.snake.Scale',
    'com.foamdev.demos.snake.Laser'
  ],

  imports: [
    'game',
    'timer',
    'R'
  ],

  properties: [
//    { name: 'scales', factory: function() { return []; } },
    [ 'sx', 240 ],
    [ 'sy', 240 ],
    [ 'vx', 1/5 ],
    [ 'vy', 0 ],
    [ 'length', 5 ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.timer.i$.sub(this.tick);
    },
    function up()    { this.vy = -1/5; this.vx =  0; },
    function down()  { this.vy =  1/5; this.vx =  0; },
    function left()  { this.vy =  0; this.vx = -1/5; },
    function right() { this.vy =  0; this.vx =  1/5; },
    function fire () {
      this.Laser.create({x: this.sx, y: this.sy, vx: this.vx, vy: this.vy});
    },
    function intersects(o) {
      if ( ! this.children.length ) return false;
      return this.children[this.children.length-1].intersects(o);
    }
  ],

  listeners: [
    {
      name: 'tick',
      isMerged: 150,
      code: function() {
        this.sx += this.vx * 2 * this.R;
        this.sy += this.vy * 2 * this.R;
        if ( this.children.length )
          this.children[this.children.length-1].color = 'green';
        this.add(this.Scale.create({x: this.sx, y: this.sy, radius: this.R, color: 'red'}));
        if ( this.children.length > this.length )
          this.children.shift();
      }
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Food',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.animation.Animation' ],

  imports: [ 'game' ],

  properties: [
    [ 'color', 'darkblue' ],
    [ 'radius', 10 ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.Animation.create({
        duration: 5000,
        f: ()=> this.radius = 0,
        onEnd: () => this.game.removeChild(this),
        objs: [this]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Mushroom',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.graphics.Box', 'foam.animation.Animation' ],

  imports: [ 'game' ],

  properties: [
    [ 'radius', 20 ],
    [ 'color',  'gray' ],
    [ 'start',  Math.PI ],
    [ 'end',    0 ],
    'stem',
    [ 'exploded', false ]
  ],

  methods: [
    function init() {
      this.SUPER();

      this.add(this.stem = this.Box.create({
        x: -7.5,
        y: -0.5,
        width: 15,
        height: 20,
        color: 'gray'
      }));
    },

    function explode() {
      if ( this.exploded ) return;
      this.exploded = true;

      this.stem.color = 'red';

      this.Animation.create({
        duration: 600,
        f: ()=> {
          this.scaleX   = this.scaleY = 10;
          this.alpha    = 0;
          this.rotation = Math.PI * 6;
        },
        onEnd: () => this.game.removeChild(this),
        slots: [ this.scaleX$, this.scaleY$, this.alpha$, this.rotation$ ],
        objs: [this, this.stem]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Laser',
  extends: 'foam.graphics.Circle',

  requires: [ 'foam.animation.Animation' ],
  imports: [ 'game'],

  properties: [
    [ 'color', 'yellow' ],
    [ 'radius', 12 ],
    'vx',
    'vy'
  ],

  methods: [
    function init() {
      this.SUPER();

      this.game.addChild(this);
      this.Animation.create({
        duration: 5000,
        f: () => {
          this.x += 2000 * this.vx;
          this.y += 2000 * this.vy;
        },
        onEnd: () => this.game.removeChild(this),
        objs: [ this ]
      }).start();
    }
  ]
});

foam.CLASS({
  package: 'com.foamdev.demos.snake',
  name: 'Game',
  extends: 'foam.u2.Element',

  requires: [
    'com.foamdev.demos.snake.Food',
    'com.foamdev.demos.snake.Laser',
    'com.foamdev.demos.snake.Mushroom',
    'com.foamdev.demos.snake.Question1',
    'com.foamdev.demos.snake.Question2',
    'com.foamdev.demos.snake.Snake',
    'com.google.foam.demos.robot.Robot',
    'foam.animation.Animation',
    'foam.graphics.Box as Rectangle',
    'foam.graphics.CView',
    'foam.graphics.Label',
    'foam.input.Gamepad',
    'foam.physics.Collider',
    'foam.util.Timer'
  ],

  exports: [
    'R',
    'timer',
    'as game'
  ],

  constants: { R: 20 },

  properties: [
    [ 'width', 1600 ],
    [ 'height', 800 ],
    {
      name: 'gamepad',
      factory: function() { return this.Gamepad.create(); }
    },
    {
      name: 'timer',
      factory: function() { return this.Timer.create(); }
    },
    {
      name: 'snake',
      factory: function() { return this.Snake.create(); }
    },
    {
      name: 'table',
      factory: function() {
        return this.Rectangle.create({
          color: 'lightblue',
          width: 800, // window.innerWidth,
          height: 500 // window.innerHeight
        });
      }
    },
    {
      name: 'robot',
      factory: function() {
        return this.Robot.create({x:200, y:200});
      }
    },
    {
      name: 'collider',
      factory: function() { return this.Collider.create(); }
    }
  ],

  methods: [
    function init() {
      this.SUPER();

//      this.addChild(this.collider);

      this.timer.i$.sub(this.tick);
      this.timer.start();

      this.gamepad.pressed.sub('button4', () => this.fire());
      this.gamepad.pressed.sub('button5', () => this.fire());

//      this.gamepad.pressed.sub('button1', () => robot.x+=10);
      this.gamepad.pressed.sub(function() { console.log('pressed', arguments); });

      this.addChild(this.robot);
      this.addChild(this.snake);

      // Setup Physics
      this.collider.collide = function(o1, o2) {
        if ( this.Laser.isInstance(o2) || this.Snake.isInstance(o2) ) {
          var tmp = o1;
          o1 = o2;
          o2 = tmp;
        }
        if ( this.Laser.isInstance(o1) ) {
          if ( this.Mushroom.isInstance(o2) ) {
            o2.explode();
            this.removeChild(o1);
          } else if ( this.Food.isInstance(o2) ) {
          }
        }
        if ( this.Snake.isInstance(o1) && this.Mushroom.isInstance(o2) ) {
          if ( o2.scaleX == 1 )
            this.gameOver();
          else
            this.removeChild(o2);
        }
        if ( this.Snake.isInstance(o1) && this.Food.isInstance(o2) ) {
          this.removeChild(o2);
          this.snake.length++;
        }
//        console.log('BANG', o1, o2);
      }.bind(this);
      this.collider.start();
    },

    function initE() {
      this.SUPER();
      this.focus();
      this.style({display:'flex'}).add(this.table).add(this.Question2.create());
    },

    function gameOver() {
      this.timer.stop();
//      this.collider.stop(); // TODO: add stop() method to collider
      this.table.color = 'orange';
      this.addChild(this.Label.create({
        text: 'Game Over',
        align: 'center',
        color: 'white',
        scaleX: 10,
        scaleY: 10,
        x: this.table.width/2,
        y: 100
      }));
    },

    function addChild(c) {
      this.table.add(c);
      if ( c.intersects ) this.collider.add(c);
    },

    function removeChild(c) {
      this.table.remove(c);
      if ( c.intersects ) this.collider.remove(c);
    },

    function addFood() {
      var R = this.R;
      var f = this.Food.create({
        x: Math.round(1+Math.random()*(this.table.width -4*R)/R)*2*R,
        y: Math.round(1+Math.random()*(this.table.height-4*R)/R)*2*R,
      });
      this.addChild(f);
    },

    function addMushroom() {
      var R = this.R;
      var m = this.Mushroom.create({
        x: Math.round(1+Math.random()*(this.table.width -4*R)/R)*2*R,
        y: Math.round(1+Math.random()*(this.table.height-4*R)/R)*2*R,
        scaleX: 0.1,
        scaleY: 0.1});

      this.Animation.create({
        duration: 5000,
        f: ()=> { m.scaleX = 1; m.scaleY = 1; },
        onEnd: () => m.color = 'red',
        objs: [m]
      }).start();

      this.addChild(m);
    }
  ],

  listeners: [
    {
      name: 'tick',
      code: function(_, __, ___, t) {
        if ( this.gamepad.button0 ) this.robot.y -= 3;
        if ( this.gamepad.button1 ) this.robot.x += 3;
        if ( this.gamepad.button2 ) this.robot.y += 3;
        if ( this.gamepad.button3 ) this.robot.x -= 3;

        if ( t.get() % 10 == 0 ) this.addFood();
        if ( Math.random() < 0.02 ) this.addMushroom();
      }
    }
  ],

  actions: [
    {
      name: 'up',
      keyboardShortcuts: [ 38 /* up arrow */, 'w' ],
      code: function() { this.snake.up(); }
    },
    {
      name: 'down',
      keyboardShortcuts: [ 40 /* down arrow */, 's' ],
      code: function() { this.snake.down(); }
    },
    {
      name: 'left',
      keyboardShortcuts: [ 37 /* left arrow */, 'a' ],
      code: function() { this.snake.left(); }
    },
    {
      name: 'right',
      keyboardShortcuts: [ 39 /* right arrow */, 'd' ],
      code: function() { this.snake.right(); }
    },
    {
      name: 'fire',
      keyboardShortcuts: [ ' ', 'x' ],
      code: function() { this.snake.fire(); }
    }
  ]
});
<|endoftext|>


<commit_before>'use strict';

angular.module('algorea')
   .service('itemService', ['$rootScope', '$timeout', function($rootScope, $timeout) {
    /*
     * Simple service providing items.
     */
      ModelsManager.init(models);
      SyncQueue.init(ModelsManager);
      SyncQueue.requests = {algorea: {type: 'getAllLevels'}, loginData: {}};
      var callbacks = {};
      var userCallback = null;
      var syncDone = 0;
      var firstSyncDone = 0;
      var lastSyncLogin = null;
      var newLogin = null;
      var intervalIsSet = false;
      var firstSyncFailed = false; // case of first sync without session, before login
      function setSyncInterval() {
         if (!intervalIsSet) {
            intervalIsSet = true;
            setInterval(SyncQueue.planToSend, 15000);
         }
      }
      SyncQueue.planToSend();
      setSyncInterval();
      function syncStartListener(data) {
         if (!lastSyncLogin && data && data.changes && data.changes.loginData && data.changes.loginData.sLogin) {
            // case of the first sync, before any login is done, this relies on the local session
            lastSyncLogin = data.changes.loginData.sLogin;
            newLogin = lastSyncLogin;
            console.error('newLogin: '+newLogin);
            SyncQueue.requests.loginData = data.changes.loginData;
         } else {
            if (!lastSyncLogin) {
               firstSyncFailed = true;
               SyncQueue.sentVersion = 0;
               SyncQueue.resetSync = true;
            }
         }
      }
//      $rootScope.$on('$stateChangeSuccess', function() {
//         var oldIds = idsToSync;
//         var newIds =
//      });
      function syncEndListener () {
         if (firstSyncFailed) { return; }
         if (lastSyncLogin == newLogin || !lastSyncLogin) {
            lastSyncLogin = newLogin;
            if (!firstSyncDone) {
               // we don't want just new descendants, but all descendants:
               SyncQueue.sentVersion = 0;
               SyncQueue.resetSync = true;
               // refreshing ids to sync (containing version number)
               SyncQueue.requests = {algorea: {type: 'getItemsFromAncestors', ancestors: getIdsToSync(true)}};
               SyncQueue.planToSend(0);
               firstSyncDone = 1;
               if (userCallback) {
                  var user = getUser();
                  userCallback(user);
               }
            } else if (!syncDone) {
               // calling callbacks after first full sync
               SyncQueue.requests = {algorea: {type: 'getItemsFromAncestors', ancestors: getIdsToSync(false)}};
               angular.forEach(callbacks, function(callbackIDlist, model) {
                  angular.forEach(callbackIDlist, function(callbacklist, ID) {
                     var record = (model == 'general') ? null : ModelsManager.curData[model][ID];
                     for (var i=0; i< callbacklist.length; i++) {
                        callbacklist[i](record);
                     }
                  });
               });
               syncDone = 1;
            }
            $rootScope.$apply();
            $timeout(function() {$timeout($rootScope.refreshSizes);}, 300); // see layout.js. 300 is a more or less random value...
         } else {
            lastSyncLogin = newLogin;
            ModelsManager.init(models);
            SyncQueue.init(ModelsManager);
            SyncQueue.requests = {algorea: {type: 'getAllLevels'}};
            SyncQueue.sentVersion = 0;
            SyncQueue.resetSync = true;
            syncDone = 0;
            firstSyncDone = 0;
            lastSyncLogin = newLogin;
            $rootScope.$broadcast('syncResetted');
            SyncQueue.planToSend();
         }
      }
      $rootScope.$on('login.logout', function() {
         delete SyncQueue.requests.loginData;
      });
      function syncWithNewLogin(login, loginData) {
         SyncQueue.requests.loginData = loginData;
         newLogin = login;
         firstSyncFailed = false;
         if (newLogin !== lastSyncLogin) {
            SyncQueue.planToSend();
         }
         // TODO: build SyncQueue.cancelCurrentSync() with a StartSyncListener
      }
      function getUserID() {

         return SyncQueue.requests.loginData.ID;
      }
      function getLoginData() {
         return SyncQueue.requests.loginData;
      }
      function getUser() {
         var res = false;
         console.error(SyncQueue.requests);
         var userID = SyncQueue.requests.loginData.ID;
         angular.forEach(ModelsManager.curData.users, function(user, ID) {
            if (ID == userID) {
               res = user;
            }
         });
         return res;
      }
      var idsToSync = {};
      function getIdsToSync(reset) {
//         var pathItems = $stateParams.path ? $stateParams.path.split('/') : ['6'];
//         if (/*pathItems[0] == '6'*/ false) {
//            var rootItem = ModelsManager.getRecords('items')[6];
//            pathItems = [];
//            if ( rootItem && rootItem.children) {
//               angular.forEach(rootItem.children, function(child) {
//                  if (child.child.iLevel != 0 && child.child.iLevel != 127 && pathItems.indexOf(parseInt(child.idItemChild)) == -1) {
//                     pathItems.push(parseInt(child.idItemChild));
//                  }
//               });
//            }
//         } else {
//            pathItems = [pathItems[0]];
//         }
         console.error(config);
         var pathItems = [config.domains.current.ProgressRootItemId, config.domains.current.DiscoverRootItemId, config.domains.current.ContestRootItemId];
         console.error(pathItems);
         angular.forEach(pathItems, function(itemID) {
            idsToSync[itemID] = {'itemID': itemID, 'minVersion': (itemID in idsToSync) ? SyncQueue.serverVersion : 0};
         });
         if (reset) {
            idsToSync.resetMinVersion = true;
         }
         return idsToSync;
      }
      SyncQueue.addSyncEndListeners("ItemsService", syncEndListener);
      SyncQueue.addSyncStartListeners("ItemsService", syncStartListener);
      return {
         getItem: function(ID) {
            return ModelsManager.getRecord('items', ID);
         },
         getAsyncUser: function(callback) {
            if (syncDone) {
               callback(getUser());
            }
            userCallback = callback;
         },
         saveUser: function() {
            var user_id = getUserID();
            ModelsManager.updated('users', user_id, false);
         },
         syncWithNewLogin: syncWithNewLogin,
         getAsyncRecord: function (model, ID, callback) {
            if (syncDone) {
               callback(ModelsManager.getRecord(model, ID));
            } else {
               if (! callbacks[model]) {
                  callbacks[model] = {};
               }
               if (callbacks[model][ID]) {
                  callbacks[model][ID].push(callback);
               } else {
                 callbacks[model][ID] = [callback];
               }
            }
         },
         onNewLoad: function (callback) {
            if (syncDone) {
               callback();
            } else {
               if (! callbacks.general) {
                  callbacks.general = {0: []};
               }
               callbacks.general[0].push(callback);
            }
         },
         saveRecord: function(model, ID) {
            ModelsManager.updated(model, ID, false);
         },
         // XXX: change this for language filtering
         getStrings: function(item) {
            return item.strings[0];
         },
         getUserItem: function(item) {
            var result_user_item = null;
            angular.forEach(item.user_item, function(user_item) {
               result_user_item = user_item;
               return;
            });
            return result_user_item;
         },
         getCurrentAnswer: function(item) {
            var result_user_answer = null;
            angular.forEach(item.user_answers, function(user_answer) {
               if (!result_user_answer || result_user_answer.sSubmissionDate < user_answer.sSubmissionDate) {
                  result_user_answer = user_answer;
               }
            });
            return result_user_answer;
         },
         getBrothersFromParent: function(parentID) {
            return this.getChildren(this.getItem(parentID));
         },
         getChildren: function(item) {
            var children = [];
            if (!item || !item.children) {
               return children;
            }
//            console.error('getting children of '+item.ID);
            // a few convoluted checks for duplicated child items and child order
            var childrenz = [];
            var seenIDs = [];
            angular.forEach(item.children, function(child) {
//               if (item.sType == 'Root') {
//                  if (child.child.iLevel != 0 && child.child.iLevel != 127 && !(child.idItemChild in seenIDs)) {
//                     childrenz.push(child);
//                     seenIDs.push(child.idItemChild);
//                  }
//               } else
               if (!(child.idItemChild in seenIDs) ){
                  var lang = child.child.sSupportedLangProg;
                  if (typeof lang !== 'undefined' && (!lang || lang == '*' || lang.indexOf('Python') != -1)) {
                     childrenz.push(child);
                  }
                  seenIDs.push(child.idItemChild);
               }
            });
            childrenz = childrenz.sort(function(a,b) {
               return a.iChildOrder - b.iChildOrder;
            });
            angular.forEach(childrenz, function (child) {
               children.push(child.child);
            });
//            console.error(children);
            return children;
         },
         getItemIdByTextId: function(sTextId) {
            return ModelsManager.indexes.sTextId[sTextId];
         },
         isSonOf: function(sonItemId, parentItemId) {
            var parentItem = ModelsManager.getRecord('items', parentItemId);
            if (!parentItem) { return false; }
            var result = false;
            angular.forEach(parentItem.children, function(child) {
               if (child.child.ID == sonItemId) {
                  result = true;
                  return;
               }
            });
            return result;
         },
         onSeen: function(item) {
            var user_item = this.getUserItem(item);
            if (user_item) {
               user_item.sLastActivityDate = new Date();
               ModelsManager.updated('users_items', user_item.ID, false, true);
               $rootScope.$broadcast('algorea.itemTriggered', item.ID);
            }
         },
         getItemTypeStr: function(item) {
            if (!item || item.sType == 'Root') return '';
            var type = item.sType;
            if (type == 'GenericChapter' || type == 'StaticChapter'  || type == 'ContestChapter'  || type == 'LimitedTimeChapter') {
               type = 'Chapter';
            }
            var typeStr;
            if (type == 'Level') {
               typeStr = 'Niveau' + (item.iLevel ? ' '+item.iLevel : '');
            } else if(type == 'Chapter') {
               typeStr = 'Chapitre';
            } else if (type == 'Category') {
               typeStr = 'Catégorie';
            } else if (type == 'Section') {
               typeStr = 'Section';
            } else if (type == 'Task') {
               typeStr = 'Exercice';
            } else if (type == 'Course') {
               typeStr = 'Cours';
            }
            return typeStr;
         }
      };
   }]);
<commit_msg>fix userInfos<commit_after>'use strict';

angular.module('algorea')
   .service('itemService', ['$rootScope', '$timeout', function($rootScope, $timeout) {
    /*
     * Simple service providing items.
     */
      ModelsManager.init(models);
      SyncQueue.init(ModelsManager);
      SyncQueue.requests = {algorea: {type: 'getAllLevels'}, loginData: {}};
      var callbacks = {};
      var userCallback = null;
      var syncDone = 0;
      var firstSyncDone = 0;
      var lastSyncLogin = null;
      var newLogin = null;
      var intervalIsSet = false;
      var firstSyncFailed = false; // case of first sync without session, before login
      function setSyncInterval() {
         if (!intervalIsSet) {
            intervalIsSet = true;
            setInterval(SyncQueue.planToSend, 15000);
         }
      }
      SyncQueue.planToSend();
      setSyncInterval();
      function syncStartListener(data) {
         if (!lastSyncLogin && data && data.changes && data.changes.loginData && data.changes.loginData.sLogin) {
            // case of the first sync, before any login is done, this relies on the local session
            lastSyncLogin = data.changes.loginData.sLogin;
            newLogin = lastSyncLogin;
            SyncQueue.requests.loginData = data.changes.loginData;
         } else {
            if (!lastSyncLogin) {
               firstSyncFailed = true;
               SyncQueue.sentVersion = 0;
               SyncQueue.resetSync = true;
            }
         }
      }
//      $rootScope.$on('$stateChangeSuccess', function() {
//         var oldIds = idsToSync;
//         var newIds =
//      });
      function syncEndListener () {
         if (firstSyncFailed) { return; }
         if (lastSyncLogin == newLogin || !lastSyncLogin) {
            lastSyncLogin = newLogin;
            if (!firstSyncDone) {
               // we don't want just new descendants, but all descendants:
               SyncQueue.sentVersion = 0;
               SyncQueue.resetSync = true;
               // refreshing ids to sync (containing version number)
               SyncQueue.requests.algorea = {type: 'getItemsFromAncestors', ancestors: getIdsToSync(true)};
               SyncQueue.planToSend(0);
               firstSyncDone = 1;
               if (userCallback) {
                  var user = getUser();
                  userCallback(user);
               }
            } else if (!syncDone) {
               // calling callbacks after first full sync
               SyncQueue.requests.algorea = {type: 'getItemsFromAncestors', ancestors: getIdsToSync(false)};
               angular.forEach(callbacks, function(callbackIDlist, model) {
                  angular.forEach(callbackIDlist, function(callbacklist, ID) {
                     var record = (model == 'general') ? null : ModelsManager.curData[model][ID];
                     for (var i=0; i< callbacklist.length; i++) {
                        callbacklist[i](record);
                     }
                  });
               });
               syncDone = 1;
            }
            $rootScope.$apply();
            $timeout(function() {$timeout($rootScope.refreshSizes);}, 300); // see layout.js. 300 is a more or less random value...
         } else {
            lastSyncLogin = newLogin;
            ModelsManager.init(models);
            SyncQueue.init(ModelsManager);
            SyncQueue.requests.algorea = {type: 'getAllLevels'};
            SyncQueue.sentVersion = 0;
            SyncQueue.resetSync = true;
            syncDone = 0;
            firstSyncDone = 0;
            lastSyncLogin = newLogin;
            $rootScope.$broadcast('syncResetted');
            SyncQueue.planToSend();
         }
      }
      $rootScope.$on('login.logout', function() {
         delete SyncQueue.requests.loginData;
      });
      function syncWithNewLogin(login, loginData) {
         SyncQueue.requests.loginData = loginData;
         newLogin = login;
         firstSyncFailed = false;
         if (newLogin !== lastSyncLogin) {
            SyncQueue.planToSend();
         }
         // TODO: build SyncQueue.cancelCurrentSync() with a StartSyncListener
      }
      function getUserID() {

         return SyncQueue.requests.loginData.ID;
      }
      function getLoginData() {
         return SyncQueue.requests.loginData;
      }
      function getUser() {
         var res = false;
         console.error(SyncQueue.requests);
         var userID = SyncQueue.requests.loginData.ID;
         angular.forEach(ModelsManager.curData.users, function(user, ID) {
            if (ID == userID) {
               res = user;
            }
         });
         return res;
      }
      var idsToSync = {};
      function getIdsToSync(reset) {
//         var pathItems = $stateParams.path ? $stateParams.path.split('/') : ['6'];
//         if (/*pathItems[0] == '6'*/ false) {
//            var rootItem = ModelsManager.getRecords('items')[6];
//            pathItems = [];
//            if ( rootItem && rootItem.children) {
//               angular.forEach(rootItem.children, function(child) {
//                  if (child.child.iLevel != 0 && child.child.iLevel != 127 && pathItems.indexOf(parseInt(child.idItemChild)) == -1) {
//                     pathItems.push(parseInt(child.idItemChild));
//                  }
//               });
//            }
//         } else {
//            pathItems = [pathItems[0]];
//         }
         console.error(config);
         var pathItems = [config.domains.current.ProgressRootItemId, config.domains.current.DiscoverRootItemId, config.domains.current.ContestRootItemId];
         console.error(pathItems);
         angular.forEach(pathItems, function(itemID) {
            idsToSync[itemID] = {'itemID': itemID, 'minVersion': (itemID in idsToSync) ? SyncQueue.serverVersion : 0};
         });
         if (reset) {
            idsToSync.resetMinVersion = true;
         }
         return idsToSync;
      }
      SyncQueue.addSyncEndListeners("ItemsService", syncEndListener);
      SyncQueue.addSyncStartListeners("ItemsService", syncStartListener);
      return {
         getItem: function(ID) {
            return ModelsManager.getRecord('items', ID);
         },
         getAsyncUser: function(callback) {
            if (syncDone) {
               callback(getUser());
            }
            userCallback = callback;
         },
         saveUser: function() {
            var user_id = getUserID();
            ModelsManager.updated('users', user_id, false);
         },
         syncWithNewLogin: syncWithNewLogin,
         getAsyncRecord: function (model, ID, callback) {
            if (syncDone) {
               callback(ModelsManager.getRecord(model, ID));
            } else {
               if (! callbacks[model]) {
                  callbacks[model] = {};
               }
               if (callbacks[model][ID]) {
                  callbacks[model][ID].push(callback);
               } else {
                 callbacks[model][ID] = [callback];
               }
            }
         },
         onNewLoad: function (callback) {
            if (syncDone) {
               callback();
            } else {
               if (! callbacks.general) {
                  callbacks.general = {0: []};
               }
               callbacks.general[0].push(callback);
            }
         },
         saveRecord: function(model, ID) {
            ModelsManager.updated(model, ID, false);
         },
         // XXX: change this for language filtering
         getStrings: function(item) {
            return item.strings[0];
         },
         getUserItem: function(item) {
            var result_user_item = null;
            angular.forEach(item.user_item, function(user_item) {
               result_user_item = user_item;
               return;
            });
            return result_user_item;
         },
         getCurrentAnswer: function(item) {
            var result_user_answer = null;
            angular.forEach(item.user_answers, function(user_answer) {
               if (!result_user_answer || result_user_answer.sSubmissionDate < user_answer.sSubmissionDate) {
                  result_user_answer = user_answer;
               }
            });
            return result_user_answer;
         },
         getBrothersFromParent: function(parentID) {
            return this.getChildren(this.getItem(parentID));
         },
         getChildren: function(item) {
            var children = [];
            if (!item || !item.children) {
               return children;
            }
//            console.error('getting children of '+item.ID);
            // a few convoluted checks for duplicated child items and child order
            var childrenz = [];
            var seenIDs = [];
            angular.forEach(item.children, function(child) {
//               if (item.sType == 'Root') {
//                  if (child.child.iLevel != 0 && child.child.iLevel != 127 && !(child.idItemChild in seenIDs)) {
//                     childrenz.push(child);
//                     seenIDs.push(child.idItemChild);
//                  }
//               } else
               if (!(child.idItemChild in seenIDs) ){
                  var lang = child.child.sSupportedLangProg;
                  if (typeof lang !== 'undefined' && (!lang || lang == '*' || lang.indexOf('Python') != -1)) {
                     childrenz.push(child);
                  }
                  seenIDs.push(child.idItemChild);
               }
            });
            childrenz = childrenz.sort(function(a,b) {
               return a.iChildOrder - b.iChildOrder;
            });
            angular.forEach(childrenz, function (child) {
               children.push(child.child);
            });
//            console.error(children);
            return children;
         },
         getItemIdByTextId: function(sTextId) {
            return ModelsManager.indexes.sTextId[sTextId];
         },
         isSonOf: function(sonItemId, parentItemId) {
            var parentItem = ModelsManager.getRecord('items', parentItemId);
            if (!parentItem) { return false; }
            var result = false;
            angular.forEach(parentItem.children, function(child) {
               if (child.child.ID == sonItemId) {
                  result = true;
                  return;
               }
            });
            return result;
         },
         onSeen: function(item) {
            var user_item = this.getUserItem(item);
            if (user_item) {
               user_item.sLastActivityDate = new Date();
               ModelsManager.updated('users_items', user_item.ID, false, true);
               $rootScope.$broadcast('algorea.itemTriggered', item.ID);
            }
         },
         getItemTypeStr: function(item) {
            if (!item || item.sType == 'Root') return '';
            var type = item.sType;
            if (type == 'GenericChapter' || type == 'StaticChapter'  || type == 'ContestChapter'  || type == 'LimitedTimeChapter') {
               type = 'Chapter';
            }
            var typeStr;
            if (type == 'Level') {
               typeStr = 'Niveau' + (item.iLevel ? ' '+item.iLevel : '');
            } else if(type == 'Chapter') {
               typeStr = 'Chapitre';
            } else if (type == 'Category') {
               typeStr = 'Catégorie';
            } else if (type == 'Section') {
               typeStr = 'Section';
            } else if (type == 'Task') {
               typeStr = 'Exercice';
            } else if (type == 'Course') {
               typeStr = 'Cours';
            }
            return typeStr;
         }
      };
   }]);
<|endoftext|>


<commit_before>var config = module.exports;

config['NavyJS Tests'] = {
rootPath: '../',
  environment: 'browser', // or "node"
  sources: [
    'build/navy.js',
  ],
  tests: [
    'test/test_env.js',
    'test/lib/class_test.js'
  ]
};
<commit_msg>けつかんま修正<commit_after>var config = module.exports;

config['NavyJS Tests'] = {
rootPath: '../',
  environment: 'browser', // or "node"
  sources: [
    'build/navy.js'
  ],
  tests: [
    'test/test_env.js',
    'test/lib/class_test.js'
  ]
};
<|endoftext|>


<commit_before>import { h, Component } from 'preact/preact';
import SVGIcon 			from 'com/svg-icon/icon';

export default class SidebarUpcoming extends Component {
	constructor() {
	}

	render( props, state ) {
		return (

<SVGIcon baseline>calendar-wide</SVGIcon> Coming Up

Nov. 28th - Theme Slaughter

Dec. 9th - Ludum Dare 37 <SVGIcon baseline small>trophy</SVGIcon>

 { console.log('full-schedlue'); window.location.hash = "#cal"; }}>Full Schedule

		);
	}
}

//
Dec. 30th - Results

<commit_msg>Changed Slaughter date (but I may start it early anyway)<commit_after>import { h, Component } from 'preact/preact';
import SVGIcon 			from 'com/svg-icon/icon';

export default class SidebarUpcoming extends Component {
	constructor() {
	}

	render( props, state ) {
		return (

<SVGIcon baseline>calendar-wide</SVGIcon> Coming Up

Nov. 29th - Theme Slaughter

Dec. 9th - Ludum Dare 37 <SVGIcon baseline small>trophy</SVGIcon>

 { console.log('full-schedlue'); window.location.hash = "#cal"; }}>Full Schedule

		);
	}
}

//
Dec. 30th - Results

<|endoftext|>