<commit_before>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import UserData from '../components/UserData'
import styled from '@emotion/styled'
import {isEmpty} from 'lodash'

import graphql from '../graphql/client'
import {
  APP_USER_CONVERSATIONS,
  APP_USER_VISITS,
} from '../graphql/queries'

import {
  START_CONVERSATION,
  APP_USER_UPDATE_STATE,
  APP_USER_UPDATE
} from '../graphql/mutations'

import Grid from '@material-ui/core/Grid'
import Typography from '@material-ui/core/Typography'
import Button from '@material-ui/core/Button'
import Avatar from '@material-ui/core/Avatar'

import {
  getAppUser
} from '../actions/app_user'

import ContentHeader from '../components/ContentHeader'
import Content from '../components/Content'
import Mapa from '../components/map'
import UserListItem from '../components/conversation/UserListItem'
import sanitizeHtml from 'sanitize-html';
import DataTable from '../components/table'

import DialogEditor from '../components/conversation/DialogEditor'
import UserActionsMenu from '../components/userActionsMenu'
import TextField from '@material-ui/core/TextField'
import IconButton from '@material-ui/core/IconButton'
import EditIcon from '@material-ui/icons/EditOutlined'

const AppUserHeaderOverlay = styled.div`
  position: absolute;
  z-index: 99;
  color: #fff;
  width: 100%;
  height: 185px;
  background: #01011078;
  //background: linear-gradient(to bottom,rgba(250,250,250,0) 40%,#f6f6f6 100%);
`

const AppUserHeaderInfo = styled.div`
  position: absolute;
  z-index: 99;
  color: #fff;
  width: 100%;
  height: 185px;
  display: flex;
  align-items: self-start;
  justify-content: space-between;

  .name-description {
    display: flex;
    flex-direction: column;
    margin-left: 10px;
    color: white;
    input{
      color: white;
    }
  }

  .controls {
    display:flex;
    margin-top: 10px;
    margin-right: 10px;
  }

  .user-info {
    display: flex;
    align-items: center;
    margin-left: 20px;
    margin-top: 20px;
  }
`

class ProfilePage extends Component {

  state = {
    collection: [],
    meta: {},
    startConversationModal: false,
    editName: false
  }

  componentDidMount(){
    this.getUser( this.fetchUserConversations )
  }

  getUser = (cb)=>{
    this.props.dispatch(
      getAppUser(
        parseInt(this.props.match.params.id)
      , cb && cb() )
    )
  }

  fetchUserConversations = ()=>{
    graphql(APP_USER_CONVERSATIONS, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      page: 1,
      per: 20
    }, {
      success: (data)=>{
        const {collection} = data.app.appUser.conversations
        this.setState({collection: collection })
      },
      error: ()=>{

      }
    })
  }

  openStartConversationModal = ()=>{
    this.setState({startConversationModal: true})
  }

  handleDialogClose = ()=>{
    this.setState({startConversationModal: false})
  }

  handleSubmit = ({html, serialized, text})=> {

    graphql(START_CONVERSATION, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      message: {html, serialized, text}
    }, {
      success: (data)=>{
        const {conversation} = data.startConversation
        const url = `/apps/${this.props.app.key}/conversations/${conversation.key}`
        this.props.history.push(url)
      },
      errors: (error)=>{
        debugger
      }
    })
  }

  updateState = (option)=>{

    graphql(APP_USER_UPDATE_STATE, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      state: option.id
    }, {
      success: (data)=>{

        this.props.dispatch(
          getAppUser(
            parseInt(this.props.app_user.id)
          )
        )

        //data.appUserUpdateData.appUser
      },
      error: (error)=>{
        debugger
      }
    })

  }

  handleEnter = (e)=>{
    if(e.key === "Enter"){
      graphql(APP_USER_UPDATE, {
        appKey: this.props.app.key,
        id: this.props.app_user.id,
        options: {
          name: e.target.value,
        }
      }, {
        success: (data)=>{
          this.setState({editName: null})
          this.getUser()
        },
        error: ()=>{

        }
      })
    }
  }

  toggleNameEdit = ()=>{
    this.setState({editName: !this.state.editName})
  }

  render() {
    return (

        <ContentHeader title={"contact profile"}/>

        <Mapa
          interactive={true}
          data={[this.props.app_user]}
          forceZoom={10}
          wrapperStyle={{
            position: 'relative',
            width: '100%',
            height: '184px',
            marginTop: '0px',
          }}>

          <AppUserHeaderOverlay/>

          <AppUserHeaderInfo>

              <Avatar style={{width: '120px', height: '120px'}}
                src={this.props.app_user.avatarUrl + "&s=120px"}
              />

                <Typography variant={"h5"}>

                  {
                    this.props.app_user && this.state.editName ?
                    <TextField
                      //className={classes.input}
                      onKeyUp={this.handleEnter}
                      defaultValue={this.props.app_user.name}
                      placeholder="enter user's name"
                      inputProps={{ 'aria-label': 'enter agent\'s name' }}
                    /> : this.props.app_user.name
                  }

                  <IconButton onClick={this.toggleNameEdit} color={"inherit"}>
                    <EditIcon/>
                  </IconButton>
                </Typography>

                <Typography variant={"h6"}>
                  {this.props.app_user.email}
                </Typography>

                <Typography variant={"subtitle1"}>
                  {this.props.app_user.city}
                  {" "}
                  {this.props.app_user.country}
                </Typography>

                <Typography variant={"caption"}>
                  {this.props.app_user.state}
                </Typography>

              <Button
                variant="contained"
                color="secondary"
                onClick={this.openStartConversationModal}>
                start conversation
              </Button>

              <UserActionsMenu
                selected={this.props.app_user.state}
                handleClick={(item)=>{  this.updateState(item) }}
              />

          </AppUserHeaderInfo>

        </Mapa>

        <Content>

          <Grid container spacing={2}>

            <Grid item xs={12} sm={8}>
              <Typography variant="h5" gutterBottom>
                conversations
              </Typography>
              {
                this.state.collection.filter((o)=> o.lastMessage ).map((o)=>{
                  const user = o.mainParticipant

                  return <div
                            key={`user-list-${o.key}`}
                            onClick={(e)=> this.props.history.push(`/apps/${this.props.app.key}/conversations/${o.key}`) }>

                            <UserListItem
                              value={null}
                              mainUser={user}
                              object={o.key}
                              messageUser={o.lastMessage.appUser}
                              showUserDrawer={this.showUserDrawer}
                              messageObject={o.lastMessage}
                              conversation={o}
                              //createdAt={o.lastMessage.message.created_at}
                              message={sanitizeHtml(o.lastMessage.message.htmlContent).substring(0, 250)}
                            />

                })
              }
            </Grid>

            <Grid item xs={12} sm={4}>
              {
                !isEmpty(this.props.app_user) ?
                <UserData
                  width={"100%"}
                  hideConactInformation={true}
                  appUser={this.props.app_user}
                  app={this.props.app}
                /> : null
              }
            </Grid>

          </Grid>

          <Grid item xs={12} sm={12}>
            {
              this.props.app_user.id ?
              <AppUserVisits {...this.props}/> : null
            }
          </Grid>

        </Content>

        {
          this.state.startConversationModal ?

            <DialogEditor
              handleSubmit={this.handleSubmit}
              close={this.handleDialogClose}
              open={this.state.startConversationModal}
            /> : null
        }

    );
  }
}

class AppUserVisits extends React.Component {

  state = {
    collection: [],
    meta: {},
    loading: false
  }
  componentDidMount(){
    this.fetchvisits()
  }

  fetchvisits = (page=null)=>{
    this.setState({loading: true}, ()=>{
      graphql(APP_USER_VISITS, {
        appKey: this.props.app.key,
        id: this.props.app_user.id,
        page: page || this.state.meta.next_page || 1,
        per: 20
      }, {
        success: (data)=>{
          this.setState({
            collection: data.app.appUser.visits.collection,
            meta: data.app.appUser.visits.meta,
            loading: false
          })
        },
        error: ()=>{
          this.setState({
            loading: true
          })
        }
      })
    })
  }

  render(){
    return

      <DataTable
        title={"visits"}
        data={this.state.collection}
        columns={[
          {field: "url", title: "url"},
          {field: "title", title: "title"},
          {field: "browserName", title: "browser name"},
          {field: "browserVersion", title: "browser version"},
          {field: "os", title: "os"},
          {field: "osVersion", title: "os version"}
        ]}
        meta={this.state.meta}
        search={(page)=>this.fetchvisits(page)}
      />

  }
}

function mapStateToProps(state) {
  const { app_user, app } = state
  return {
    app_user,
    app
  }
}

//export default ShowAppContainer

export default withRouter(connect(mapStateToProps)(ProfilePage))
<commit_msg>Update ProfileView.js<commit_after>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import UserData from '../components/UserData'
import styled from '@emotion/styled'
import {isEmpty} from 'lodash'

import graphql from '../graphql/client'
import {
  APP_USER_CONVERSATIONS,
  APP_USER_VISITS,
} from '../graphql/queries'

import {
  START_CONVERSATION,
  APP_USER_UPDATE_STATE,
  APP_USER_UPDATE
} from '../graphql/mutations'

import Grid from '@material-ui/core/Grid'
import Typography from '@material-ui/core/Typography'
import Button from '@material-ui/core/Button'
import Avatar from '@material-ui/core/Avatar'

import {
  getAppUser
} from '../actions/app_user'

import ContentHeader from '../components/ContentHeader'
import Content from '../components/Content'
import Mapa from '../components/map'
import UserListItem from '../components/conversation/UserListItem'
import sanitizeHtml from 'sanitize-html';
import DataTable from '../components/table'

import DialogEditor from '../components/conversation/DialogEditor'
import UserActionsMenu from '../components/userActionsMenu'
import TextField from '@material-ui/core/TextField'
import IconButton from '@material-ui/core/IconButton'
import EditIcon from '@material-ui/icons/EditOutlined'

const AppUserHeaderOverlay = styled.div`
  position: absolute;
  z-index: 99;
  color: #fff;
  width: 100%;
  height: 185px;
  background: #01011078;
  //background: linear-gradient(to bottom,rgba(250,250,250,0) 40%,#f6f6f6 100%);
`

const AppUserHeaderInfo = styled.div`
  position: absolute;
  z-index: 99;
  color: #fff;
  width: 100%;
  height: 185px;
  display: flex;
  align-items: self-start;
  justify-content: space-between;

  .name-description {
    display: flex;
    flex-direction: column;
    margin-left: 10px;
    color: white;
    input{
      color: white;
    }
  }

  .controls {
    display:flex;
    margin-top: 10px;
    margin-right: 10px;
  }

  .user-info {
    display: flex;
    align-items: center;
    margin-left: 20px;
    margin-top: 20px;
  }
`

class ProfilePage extends Component {

  state = {
    collection: [],
    meta: {},
    startConversationModal: false,
    editName: false,
    editEmail: false
  }

  componentDidMount(){
    this.getUser( this.fetchUserConversations )
  }

  getUser = (cb)=>{
    this.props.dispatch(
      getAppUser(
        parseInt(this.props.match.params.id)
      , cb && cb() )
    )
  }

  fetchUserConversations = ()=>{
    graphql(APP_USER_CONVERSATIONS, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      page: 1,
      per: 20
    }, {
      success: (data)=>{
        const {collection} = data.app.appUser.conversations
        this.setState({collection: collection })
      },
      error: ()=>{

      }
    })
  }

  openStartConversationModal = ()=>{
    this.setState({startConversationModal: true})
  }

  handleDialogClose = ()=>{
    this.setState({startConversationModal: false})
  }

  handleSubmit = ({html, serialized, text})=> {

    graphql(START_CONVERSATION, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      message: {html, serialized, text}
    }, {
      success: (data)=>{
        const {conversation} = data.startConversation
        const url = `/apps/${this.props.app.key}/conversations/${conversation.key}`
        this.props.history.push(url)
      },
      errors: (error)=>{
        debugger
      }
    })
  }

  updateState = (option)=>{

    graphql(APP_USER_UPDATE_STATE, {
      appKey: this.props.app.key,
      id: this.props.app_user.id,
      state: option.id
    }, {
      success: (data)=>{

        this.props.dispatch(
          getAppUser(
            parseInt(this.props.app_user.id)
          )
        )

        //data.appUserUpdateData.appUser
      },
      error: (error)=>{
        debugger
      }
    })

  }

  handleEnter = (e, attrName)=>{
    let attribute = {}
    attribute[attrName] = e.target.value

    if(e.key === "Enter"){
      graphql(APP_USER_UPDATE, {
        appKey: this.props.app.key,
        id: this.props.app_user.id,
        options: attribute
      }, {
        success: (data)=>{
          this.setState({editName: null})
          this.setState({editEmail: null})
          this.getUser()
        },
        error: ()=>{

        }
      })
    }
  }

  toggleNameEdit = ()=>{
    this.setState({editName: !this.state.editName})
  }

  toggleEmailEdit = ()=>{
    this.setState({editEmail: !this.state.editEmail})
  }

  render() {
    return (

        <ContentHeader title={"contact profile"}/>

        <Mapa
          interactive={true}
          data={[this.props.app_user]}
          forceZoom={10}
          wrapperStyle={{
            position: 'relative',
            width: '100%',
            height: '184px',
            marginTop: '0px',
          }}>

          <AppUserHeaderOverlay/>

          <AppUserHeaderInfo>

              <Avatar style={{width: '120px', height: '120px'}}
                src={this.props.app_user.avatarUrl + "&s=120px"}
              />

                <Typography variant={"h5"}>

                  {
                    this.props.app_user && this.state.editName ?
                    <TextField
                      //className={classes.input}
                      onKeyUp={(e)=>this.handleEnter(e, "name")}
                      defaultValue={this.props.app_user.name}
                      placeholder="enter user's name"
                      inputProps={{ 'aria-label': 'enter user\'s name' }}
                    /> : this.props.app_user.name
                  }

                  <IconButton onClick={this.toggleNameEdit} color={"inherit"}>
                    <EditIcon/>
                  </IconButton>
                </Typography>

                <Typography variant={"h6"}>
                  {
                    this.props.app_user && this.state.editEmail ?
                    <TextField
                      //className={classes.input}
                      onKeyUp={(e)=>this.handleEnter(e, "email")}
                      defaultValue={this.props.app_user.name}
                      placeholder="enter user's email"
                      inputProps={{ 'aria-label': 'user agent\'s email' }}
                    /> : this.props.app_user.email
                  }

                  <IconButton onClick={this.toggleEmailEdit} color={"inherit"}>
                    <EditIcon/>
                  </IconButton>

                </Typography>

                <Typography variant={"subtitle1"}>
                  {this.props.app_user.city}
                  {" "}
                  {this.props.app_user.country}
                </Typography>

                <Typography variant={"caption"}>
                  {this.props.app_user.state}
                </Typography>

              <Button
                variant="contained"
                color="secondary"
                onClick={this.openStartConversationModal}>
                start conversation
              </Button>

              <UserActionsMenu
                selected={this.props.app_user.state}
                handleClick={(item)=>{  this.updateState(item) }}
              />

          </AppUserHeaderInfo>

        </Mapa>

        <Content>

          <Grid container spacing={2}>

            <Grid item xs={12} sm={8}>
              <Typography variant="h5" gutterBottom>
                conversations
              </Typography>
              {
                this.state.collection.filter((o)=> o.lastMessage ).map((o)=>{
                  const user = o.mainParticipant

                  return <div
                            key={`user-list-${o.key}`}
                            onClick={(e)=> this.props.history.push(`/apps/${this.props.app.key}/conversations/${o.key}`) }>

                            <UserListItem
                              value={null}
                              mainUser={user}
                              object={o.key}
                              messageUser={o.lastMessage.appUser}
                              showUserDrawer={this.showUserDrawer}
                              messageObject={o.lastMessage}
                              conversation={o}
                              //createdAt={o.lastMessage.message.created_at}
                              message={sanitizeHtml(o.lastMessage.message.htmlContent).substring(0, 250)}
                            />

                })
              }
            </Grid>

            <Grid item xs={12} sm={4}>
              {
                !isEmpty(this.props.app_user) ?
                <UserData
                  width={"100%"}
                  hideConactInformation={true}
                  appUser={this.props.app_user}
                  app={this.props.app}
                /> : null
              }
            </Grid>

          </Grid>

          <Grid item xs={12} sm={12}>
            {
              this.props.app_user.id ?
              <AppUserVisits {...this.props}/> : null
            }
          </Grid>

        </Content>

        {
          this.state.startConversationModal ?

            <DialogEditor
              handleSubmit={this.handleSubmit}
              close={this.handleDialogClose}
              open={this.state.startConversationModal}
            /> : null
        }

    );
  }
}

class AppUserVisits extends React.Component {

  state = {
    collection: [],
    meta: {},
    loading: false
  }
  componentDidMount(){
    this.fetchvisits()
  }

  fetchvisits = (page=null)=>{
    this.setState({loading: true}, ()=>{
      graphql(APP_USER_VISITS, {
        appKey: this.props.app.key,
        id: this.props.app_user.id,
        page: page || this.state.meta.next_page || 1,
        per: 20
      }, {
        success: (data)=>{
          this.setState({
            collection: data.app.appUser.visits.collection,
            meta: data.app.appUser.visits.meta,
            loading: false
          })
        },
        error: ()=>{
          this.setState({
            loading: true
          })
        }
      })
    })
  }

  render(){
    return

      <DataTable
        title={"visits"}
        data={this.state.collection}
        columns={[
          {field: "url", title: "url"},
          {field: "title", title: "title"},
          {field: "browserName", title: "browser name"},
          {field: "browserVersion", title: "browser version"},
          {field: "os", title: "os"},
          {field: "osVersion", title: "os version"}
        ]}
        meta={this.state.meta}
        search={(page)=>this.fetchvisits(page)}
      />

  }
}

function mapStateToProps(state) {
  const { app_user, app } = state
  return {
    app_user,
    app
  }
}

//export default ShowAppContainer

export default withRouter(connect(mapStateToProps)(ProfilePage))
<|endoftext|>


<commit_before>Checklist.TaxonConcept = DS.Model.extend({
  id: DS.attr('number'),
  parent: DS.belongsTo('Checklist.TaxonConcept', { key: 'parent_id' }),
  countries: DS.hasMany('Checklist.Country', { key: 'countries_ids' }),
  timelines_for_taxon_concept: DS.belongsTo('Checklist.TimelinesForTaxonConcept', { key: 'id' }),
  current_additions: DS.hasMany('Checklist.ListingChange', { embedded : true }),
  rank_name: DS.attr('string'),
  current_listing: DS.attr('string'),
  kingdom_name: DS.attr('string'),
  phylum_name: DS.attr('string'),
  class_name: DS.attr('string'),
  order_name: DS.attr('string'),
  family_name: DS.attr('string'),
  genus_name: DS.attr('string'),
  full_name: DS.attr('string'),
  author_year: DS.attr('string'),
  cites_accepted: DS.attr('boolean'),
  recently_changed: DS.attr('boolean'),
  english: DS.attr('string', { key: 'english_names' }),
  spanish: DS.attr('string', { key: 'spanish_names' }),
  french: DS.attr('string', { key: 'french_names' }),
  synonyms: DS.attr('string', { key: 'synonyms' }),
  synonyms_with_authors: DS.attr('string', { key: 'synonyms_with_authors' }),
  itemType: DS.attr('string', { key: 'item_type' }),
  ancestorsPath: DS.attr('string', { key: 'ancestors_path' }),
  isPlant: function(){
    return this.get('kingdom_name') == 'Plantae';
  }.property(),
  higherTaxa: function(){
    return this.get('ancestorsPath').split(',');
  }.property(),
  _cites_populations: [],
  cites_populations: function(){
    return this.get('_cites_populations');
  }.property('_cites_populations.@each'),
  populationsDidChange: function(){
    Ember.run.once(this, 'createCitesPopulations');
  }.observes('countries.@each', 'current_additions.@each.countries.@each'),
  createCitesPopulations: function(){
    //this should run only once per taxon concept
    var populations = this.get('countries').map(function(cnt){
      return Checklist.CitesPopulation.create({
        name: cnt.get('name')
      });
    });
    var defaultAppendix = null;
    if (this.get('current_additions.length') > 0){
      this.get('current_additions').forEach(function(listing_change){
        if (listing_change.get('countries.length') > 0){
          //now we go over countries listed under this appendix
          //to mark them accordingly in the main distribution list
          listing_change.get('countries').forEach(function(country){
            var population = populations.findProperty('name', country.get('name'));
            if (population != null){
              population.species_listing_name = listing_change.get('species_listing_name');
            }
          });
        } else {
          defaultAppendix = listing_change.get('species_listing_name');
        }
      });
    } else {
      defaultAppendix = this.get('current_listing');
    }
    if (defaultAppendix !== null){
      //go over all the countries not yet marked with any appendix
      //and mark them with default appendix
      populations.filter(function(item){
        return item.species_listing_name === null;
      }).forEach(function(item){
        item.species_listing_name = defaultAppendix;
      })
    }
    this.set('_cites_populations', populations);
  },
  current_parties: DS.hasMany('Checklist.Country', { key: 'current_parties_ids' })
});
<commit_msg>fixes #37746913<commit_after>Checklist.TaxonConcept = DS.Model.extend({
  id: DS.attr('number'),
  parent: DS.belongsTo('Checklist.TaxonConcept', { key: 'parent_id' }),
  countries: DS.hasMany('Checklist.Country', { key: 'countries_ids' }),
  timelines_for_taxon_concept: DS.belongsTo('Checklist.TimelinesForTaxonConcept', { key: 'id' }),
  current_additions: DS.hasMany('Checklist.ListingChange', { embedded : true }),
  rank_name: DS.attr('string'),
  current_listing: DS.attr('string'),
  kingdom_name: DS.attr('string'),
  phylum_name: DS.attr('string'),
  class_name: DS.attr('string'),
  order_name: DS.attr('string'),
  family_name: DS.attr('string'),
  genus_name: DS.attr('string'),
  full_name: DS.attr('string'),
  author_year: DS.attr('string'),
  cites_accepted: DS.attr('boolean'),
  recently_changed: DS.attr('boolean'),
  english: DS.attr('string', { key: 'english_names' }),
  spanish: DS.attr('string', { key: 'spanish_names' }),
  french: DS.attr('string', { key: 'french_names' }),
  synonyms: DS.attr('string', { key: 'synonyms' }),
  synonyms_with_authors: DS.attr('string', { key: 'synonyms_with_authors' }),
  itemType: DS.attr('string', { key: 'item_type' }),
  ancestorsPath: DS.attr('string', { key: 'ancestors_path' }),
  isPlant: function(){
    return this.get('kingdom_name') == 'Plantae';
  }.property(),
  higherTaxa: function(){
    return this.get('ancestorsPath').split(',');
  }.property(),
  _cites_populations: [],
  cites_populations: function(){
    return this.get('_cites_populations');
  }.property('_cites_populations.@each'),
  populationsDidLoad: function(){
    Ember.run.once(this, 'createCitesPopulations');
  }.observes('current_additions.@each.countries.@each.didLoad'),
  createCitesPopulations: function(){
    //this should run only once per taxon concept
    var populations = this.get('countries').map(function(cnt){
      return Checklist.CitesPopulation.create({
        name: cnt.get('name')
      });
    });
    var defaultAppendix = null;
    if (this.get('current_additions.length') > 0){
      this.get('current_additions').forEach(function(listing_change){
        if (listing_change.get('countries.length') > 0){
          //now we go over countries listed under this appendix
          //to mark them accordingly in the main distribution list
          listing_change.get('countries').forEach(function(country){
            var population = populations.findProperty('name', country.get('name'));
            if (population != null){
              population.species_listing_name = listing_change.get('species_listing_name');
            }
          });
        } else {
          defaultAppendix = listing_change.get('species_listing_name');
        }
      });
    } else {
      defaultAppendix = this.get('current_listing');
    }
    if (defaultAppendix !== null){
      //go over all the countries not yet marked with any appendix
      //and mark them with default appendix
      populations.filter(function(item){
        return item.species_listing_name === null;
      }).forEach(function(item){
        item.species_listing_name = defaultAppendix;
      })
    }
    this.set('_cites_populations', populations);
  },
  current_parties: DS.hasMany('Checklist.Country', { key: 'current_parties_ids' })
});
<|endoftext|>


<commit_before>// views.js
//
// The design docs specific to Circle Blvd.
var designDocs = require("./design-docs.js");

var usersDesignDoc = {
	url: '_design/users',
	body:
	{
		version: "1.0.5",
		language: "javascript",
		views: {
			byEmail: {
				map: function(doc) {
					if (doc.type === "user" && doc.email) {
						emit(doc.email, doc);
					}
				}
			},
			byId: {
				map: function (doc) {
					if (doc.type === "user" && doc.id) {
						emit(doc.id, doc);
					}
				}
			},
			byName: {
				map: function (doc) {
					if (doc.type === "user") {
						if (doc.name) {
							emit(doc.name.toLowerCase(), doc);
						}
						else {
							emit("", doc);
						}
					}
				}
			},
			byGroup: {
				map: function (doc) {
					if (doc.type === "user") {
						for (var i in doc.memberships) {
							var membership = doc.memberships[i];
							emit([membership.group, doc.name], doc);
						}
					}

				}
			},
			byCircle: {
				map: function (doc) {
					if (doc.type === "user") {
						for (var i in doc.memberships) {
							var membership = doc.memberships[i];
							emit([membership.circle, doc.name], doc);
						}
					}
				},
				reduce: function (keys, values, rereduce) {
					if (rereduce) {
						return true;
					}
					return keys[0][0][1];
				}
			}
		}
	}
};
designDocs.add(usersDesignDoc);

var passwordsDesignDoc = {
	url: '_design/passwords',
	body:
	{
		version: "1.0.0",
		language: "javascript",
		views: {
			byId: {
				map: function (doc) {
					if (doc.type === "password" && doc.userId) {
						emit(doc.userId, doc);
					}
				}
			}
		}
	}
};
designDocs.add(passwordsDesignDoc);

var settingsDesignDoc = {
	url: '_design/settings',
	body:
	{
		version: "1.0.4",
		language: "javascript",
		views: {
			'authorized': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "public"
						 || doc.visibility === "private") {
							emit(doc.name, doc);
						}
						else if (doc.visibility === "secret") {
							var setting = {};
							for (var prop in doc) {
								setting[prop] = doc[prop];
							}
							// Do not expose the value of secret settings
							setting.value = undefined;
							emit(doc.name, setting);
						}
					}
				}
			},
			'public': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "public") {
							emit(doc.name, doc);
						}
					}
				}
			},
			'private': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "private") {
							emit(doc.name, doc);
						}
					}
				}
			},
			'all': {
				map: function (doc) {
					if (doc.type === "setting") {
						emit(doc.name, doc);
					}
				}
			}
		}
	}
};
designDocs.add(settingsDesignDoc);

var circlesDesignDoc = {
	url: '_design/circles',
	body:
	{
		version: "1.0.1",
		language: "javascript",
		views: {
			byName: {
				map: function(doc) {
					if (doc.type === "circle") {
						emit(doc.name, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "circle") {
						emit(doc._id, doc);
					}
				}
			},

			count: {
				map: function (doc) {
					if (doc.type === "circle") {
						emit(null, doc._id);
					}
				},
				reduce : "_count"
			}
		}
	}
};
designDocs.add(circlesDesignDoc);

var groupsDesignDoc = {
	url: '_design/groups',
	body:
	{
		version: "1.0.2",
		language: "javascript",
		views: {
			byCircleId: {
				map: function(doc) {
					if (doc.type === "group"
						&& (doc.circleId || doc.projectId)) {
						emit(doc.circleId || doc.projectId, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "group") {
						emit(doc._id, doc);
					}
				}
			}
		}
	}
};
designDocs.add(groupsDesignDoc);

var queueDesignDoc = {
	url: '_design/queue',
	body: {
		version: "1.0.0",
		language: "javascript",
		views: {
			byTimestamp: {
				map: function (doc) {
					if (doc.type === "story-queue") {
						emit(doc.timestamp, doc);
					}
				}
			}
		}
	}
};
designDocs.add(queueDesignDoc);

var storiesDesignDoc = {
	url: '_design/stories',
	body:
	{
		version: "1.1.2",
		language: "javascript",
		views: {
			byListId: {
				map: function(doc) {
					if (doc.type === "story" && doc.projectId) {
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(doc.listId || doc.projectId, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "story") {
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(doc._id, doc);
					}
				}
			},

			byIds: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId && doc._id) {
						// composite key using project and story id.
						var key = doc.projectId + "," + doc._id;
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(key, doc);
					}
				}
			},

			byNextId: {
				map: function (doc) {
					if (doc.type === "story") {
						emit(doc.nextId || ("last-" + (doc.listId || doc.projectId)), doc);
					}
				}
			},

			firstsByProjectId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						if (doc.isFirstStory) {
							emit(doc.listId || doc.projectId, doc);
						}
					}
				}
			},

			nextMeetingByProjectId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						if (doc.isNextMeeting) {
							emit(doc.listId || doc.projectId, doc);
						}
					}
				}
			},

			countByCircleId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						emit(doc.projectId);
					}
				},
				reduce : "_count"
			},

			countByListId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						emit(doc.listId || doc.projectId);
					}
				},
				reduce : "_count"
			}
		},

		validate_doc_update: function (newDoc, oldDoc, userCtx) {
			if (!newDoc || !oldDoc) {
				// do nothing.
				return;
			}

			// Someone is trying to start a transaction involving
			// a list of documents.
			if (newDoc.transaction && !oldDoc.transaction) {
				var transactionDocs = newDoc.transaction.docs;
				var isAllowed = false;
				var message = "Document is not part of the transaction.";

				for (var docIndex in transactionDocs) {
					if (transactionDocs[docIndex].id === newDoc._id) {
						if (transactionDocs[docIndex].rev === oldDoc._rev) {
							isAllowed = true;
							break;
						}
						else {
							message = "Document revision is not the latest.";
							throw({forbidden: message});
						}
					}
				}

				if (!isAllowed) {
					throw({forbidden: message});
				}
			}

			// Someone is in the middle of a transaction.
			//
			// We are not doing ACID transactions. In other words,
			// transactions are not isolated, and only one is allowed
			// on a document at a time.
			if (newDoc.transaction && oldDoc.transaction) {
				if (newDoc.transaction.id === oldDoc.transaction.id) {
					// We're fine.
				}
				else {
					throw({forbidden: "A different transaction is in progress. Wait a little."})
				}
			}

			// Someone is trying to finish a transaction.
			// Or someone is trying to perform an operation on an
			// older document while we're doing a transaction.
			if (oldDoc.transaction && !newDoc.transaction) {
				if (newDoc.lastTransactionId === oldDoc.transaction.id) {
					// We're fine.
				}
				else {
					throw({forbidden: "Another transaction is in progress. Wait a little."});
				}
			}
		}
	}
};
designDocs.add(storiesDesignDoc);

var listsDesignDoc = {
    url: '_design/lists',
    body:
    {
        version: "1.0.0",
        language: "javascript",
        views: {
            byCircleId: {
                map: function (doc) {
                    if (doc.type === "list") {
                        emit(doc.circleId, doc);
                    }
                }
            }
        }
    }
};
designDocs.add(listsDesignDoc);

var archivesDesignDoc = {
	url: '_design/archives',
	body:
	{
		version: "1.0.2",
		language: "javascript",
		views: {
			byCircleId: {
				map: function (doc) {
					if (doc.type === "archive" && doc.projectId) {
						emit([doc.projectId, doc.sortIndex], doc);
					}
				}
			},

			countByCircleId: {
				map: function (doc) {
					if (doc.type === "archive" && doc.projectId) {
						emit(doc.projectId);
					}
				},
				reduce : "_count"
			}
		}
	}
};
designDocs.add(archivesDesignDoc);

var adminDesignDoc = {
	url: '_design/admin',
	body:
	{
		version: "1.0.1",
		language: "javascript",
		views: {
			waitlist: {
				map: function (doc) {
					if (doc.type === "waitlist") {
						emit(doc.timestamp, doc);
					}
				}
			}
		}
	}
};
designDocs.add(adminDesignDoc);

var createDesignDocs = function (database, callback) {
	designDocs.saveToDatabase(database, callback);
};

exports.create = createDesignDocs;<commit_msg>Data: Add views for invites<commit_after>// views.js
//
// The design docs specific to Circle Blvd.
var designDocs = require("./design-docs.js");

var usersDesignDoc = {
	url: '_design/users',
	body:
	{
		version: "1.0.5",
		language: "javascript",
		views: {
			byEmail: {
				map: function(doc) {
					if (doc.type === "user" && doc.email) {
						emit(doc.email, doc);
					}
				}
			},
			byId: {
				map: function (doc) {
					if (doc.type === "user" && doc.id) {
						emit(doc.id, doc);
					}
				}
			},
			byName: {
				map: function (doc) {
					if (doc.type === "user") {
						if (doc.name) {
							emit(doc.name.toLowerCase(), doc);
						}
						else {
							emit("", doc);
						}
					}
				}
			},
			byGroup: {
				map: function (doc) {
					if (doc.type === "user") {
						for (var i in doc.memberships) {
							var membership = doc.memberships[i];
							emit([membership.group, doc.name], doc);
						}
					}

				}
			},
			byCircle: {
				map: function (doc) {
					if (doc.type === "user") {
						for (var i in doc.memberships) {
							var membership = doc.memberships[i];
							emit([membership.circle, doc.name], doc);
						}
					}
				},
				reduce: function (keys, values, rereduce) {
					if (rereduce) {
						return true;
					}
					return keys[0][0][1];
				}
			}
		}
	}
};
designDocs.add(usersDesignDoc);

var passwordsDesignDoc = {
	url: '_design/passwords',
	body:
	{
		version: "1.0.0",
		language: "javascript",
		views: {
			byId: {
				map: function (doc) {
					if (doc.type === "password" && doc.userId) {
						emit(doc.userId, doc);
					}
				}
			}
		}
	}
};
designDocs.add(passwordsDesignDoc);

var invitesDesignDoc = {
	url: '_design/invites',
	body:
	{
		version: "1.0.0",
		language: "javascript",
		views: {
			byCircleId: {
				map: function (doc) {
					if (doc.type === "invite") {
						emit(doc.circleId, doc);
					}
				}
			},
			byExpires: {
				map: function (doc) {
					if (doc.type === "invite") {
						emit(doc.expires, doc);
					}
				}
			}
		}
	}
};
designDocs.add(invitesDesignDoc);

var settingsDesignDoc = {
	url: '_design/settings',
	body:
	{
		version: "1.0.4",
		language: "javascript",
		views: {
			'authorized': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "public"
						 || doc.visibility === "private") {
							emit(doc.name, doc);
						}
						else if (doc.visibility === "secret") {
							var setting = {};
							for (var prop in doc) {
								setting[prop] = doc[prop];
							}
							// Do not expose the value of secret settings
							setting.value = undefined;
							emit(doc.name, setting);
						}
					}
				}
			},
			'public': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "public") {
							emit(doc.name, doc);
						}
					}
				}
			},
			'private': {
				map: function (doc) {
					if (doc.type === "setting") {
						if (doc.visibility === "private") {
							emit(doc.name, doc);
						}
					}
				}
			},
			'all': {
				map: function (doc) {
					if (doc.type === "setting") {
						emit(doc.name, doc);
					}
				}
			}
		}
	}
};
designDocs.add(settingsDesignDoc);

var circlesDesignDoc = {
	url: '_design/circles',
	body:
	{
		version: "1.0.1",
		language: "javascript",
		views: {
			byName: {
				map: function(doc) {
					if (doc.type === "circle") {
						emit(doc.name, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "circle") {
						emit(doc._id, doc);
					}
				}
			},

			count: {
				map: function (doc) {
					if (doc.type === "circle") {
						emit(null, doc._id);
					}
				},
				reduce : "_count"
			}
		}
	}
};
designDocs.add(circlesDesignDoc);

var groupsDesignDoc = {
	url: '_design/groups',
	body:
	{
		version: "1.0.2",
		language: "javascript",
		views: {
			byCircleId: {
				map: function(doc) {
					if (doc.type === "group"
						&& (doc.circleId || doc.projectId)) {
						emit(doc.circleId || doc.projectId, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "group") {
						emit(doc._id, doc);
					}
				}
			}
		}
	}
};
designDocs.add(groupsDesignDoc);

var queueDesignDoc = {
	url: '_design/queue',
	body: {
		version: "1.0.0",
		language: "javascript",
		views: {
			byTimestamp: {
				map: function (doc) {
					if (doc.type === "story-queue") {
						emit(doc.timestamp, doc);
					}
				}
			}
		}
	}
};
designDocs.add(queueDesignDoc);

var storiesDesignDoc = {
	url: '_design/stories',
	body:
	{
		version: "1.1.2",
		language: "javascript",
		views: {
			byListId: {
				map: function(doc) {
					if (doc.type === "story" && doc.projectId) {
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(doc.listId || doc.projectId, doc);
					}
				}
			},

			byId: {
				map: function (doc) {
					if (doc.type === "story") {
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(doc._id, doc);
					}
				}
			},

			byIds: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId && doc._id) {
						// composite key using project and story id.
						var key = doc.projectId + "," + doc._id;
						doc.nextId = doc.nextId || "last-" + (doc.listId || doc.projectId);
						emit(key, doc);
					}
				}
			},

			byNextId: {
				map: function (doc) {
					if (doc.type === "story") {
						emit(doc.nextId || ("last-" + (doc.listId || doc.projectId)), doc);
					}
				}
			},

			firstsByProjectId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						if (doc.isFirstStory) {
							emit(doc.listId || doc.projectId, doc);
						}
					}
				}
			},

			nextMeetingByProjectId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						if (doc.isNextMeeting) {
							emit(doc.listId || doc.projectId, doc);
						}
					}
				}
			},

			countByCircleId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						emit(doc.projectId);
					}
				},
				reduce : "_count"
			},

			countByListId: {
				map: function (doc) {
					if (doc.type === "story" && doc.projectId) {
						emit(doc.listId || doc.projectId);
					}
				},
				reduce : "_count"
			}
		},

		validate_doc_update: function (newDoc, oldDoc, userCtx) {
			if (!newDoc || !oldDoc) {
				// do nothing.
				return;
			}

			// Someone is trying to start a transaction involving
			// a list of documents.
			if (newDoc.transaction && !oldDoc.transaction) {
				var transactionDocs = newDoc.transaction.docs;
				var isAllowed = false;
				var message = "Document is not part of the transaction.";

				for (var docIndex in transactionDocs) {
					if (transactionDocs[docIndex].id === newDoc._id) {
						if (transactionDocs[docIndex].rev === oldDoc._rev) {
							isAllowed = true;
							break;
						}
						else {
							message = "Document revision is not the latest.";
							throw({forbidden: message});
						}
					}
				}

				if (!isAllowed) {
					throw({forbidden: message});
				}
			}

			// Someone is in the middle of a transaction.
			//
			// We are not doing ACID transactions. In other words,
			// transactions are not isolated, and only one is allowed
			// on a document at a time.
			if (newDoc.transaction && oldDoc.transaction) {
				if (newDoc.transaction.id === oldDoc.transaction.id) {
					// We're fine.
				}
				else {
					throw({forbidden: "A different transaction is in progress. Wait a little."})
				}
			}

			// Someone is trying to finish a transaction.
			// Or someone is trying to perform an operation on an
			// older document while we're doing a transaction.
			if (oldDoc.transaction && !newDoc.transaction) {
				if (newDoc.lastTransactionId === oldDoc.transaction.id) {
					// We're fine.
				}
				else {
					throw({forbidden: "Another transaction is in progress. Wait a little."});
				}
			}
		}
	}
};
designDocs.add(storiesDesignDoc);

var listsDesignDoc = {
    url: '_design/lists',
    body:
    {
        version: "1.0.0",
        language: "javascript",
        views: {
            byCircleId: {
                map: function (doc) {
                    if (doc.type === "list") {
                        emit(doc.circleId, doc);
                    }
                }
            }
        }
    }
};
designDocs.add(listsDesignDoc);

var archivesDesignDoc = {
	url: '_design/archives',
	body:
	{
		version: "1.0.2",
		language: "javascript",
		views: {
			byCircleId: {
				map: function (doc) {
					if (doc.type === "archive" && doc.projectId) {
						emit([doc.projectId, doc.sortIndex], doc);
					}
				}
			},

			countByCircleId: {
				map: function (doc) {
					if (doc.type === "archive" && doc.projectId) {
						emit(doc.projectId);
					}
				},
				reduce : "_count"
			}
		}
	}
};
designDocs.add(archivesDesignDoc);

var adminDesignDoc = {
	url: '_design/admin',
	body:
	{
		version: "1.0.1",
		language: "javascript",
		views: {
			waitlist: {
				map: function (doc) {
					if (doc.type === "waitlist") {
						emit(doc.timestamp, doc);
					}
				}
			}
		}
	}
};
designDocs.add(adminDesignDoc);

var createDesignDocs = function (database, callback) {
	designDocs.saveToDatabase(database, callback);
};

exports.create = createDesignDocs;<|endoftext|>


<commit_before>'use strict'
import angular from 'angular'
import uiRouter from 'angular-ui-router'
import uiSelect from 'angular-ui-select'

import clone from 'lodash.clonedeep'
import debounce from 'lodash.debounce'
import foreach from 'lodash.foreach'

import xoApi from 'xo-api'
import xoServices from 'xo-services'

import view from './view'

export default angular.module('dashboard.overview', [
  uiRouter,
  uiSelect,
  xoApi,
  xoServices
])
  .config(function ($stateProvider) {
    $stateProvider.state('dashboard.overview', {
      controller: 'Overview as ctrl',
      data: {
        requireAdmin: true
      },
      url: '/overview',
      template: view
    })
  })
  .controller('Overview', function ($scope, $window, xoApi, xo, $timeout, bytesToSizeFilter, modal) {
    $window.bytesToSize = bytesToSizeFilter //  FIXME dirty workaround to custom a Chart.js tooltip template
    angular.extend($scope, {
      pools: {
        nb: 0
      },
      hosts: {
        nb: 0
      },
      vms: {
        nb: 0,
        running: 0,
        halted: 0,
        action: 0
      },
      ram: [0, 0],
      cpu: [0, 0],
      srs: []
    })

    $scope.installAllPatches = function () {
      modal.confirm({
        title: 'Install all the missing patches',
        message: 'Are you sure you want to install all the missing patches? This could take a while...'
      }).then(() =>
        foreach($scope.pools.all, function (pool, pool_id) {
          let pool_hosts = $scope.hostsByPool[pool_id]
          foreach(pool_hosts, function (host, host_id) {
            console.log('Installing all missing patches on host ', host_id)
            xo.host.installAllPatches(host_id)
          })
        })
      )
    }

    $scope.installHostPatches = function (hostId) {
      modal.confirm({
        title: 'Update host (' + $scope.nbUpdates[hostId] + ' patch(es))',
        message: 'Are you sure you want to install all the missing patches on this host? This could take a while...'
      }).then(() => {
        console.log('Installing all missing patches on host ', hostId)
        xo.host.installAllPatches(hostId)
      })
    }

    function populateChartsData () {
      let pools,
        vmsByContainer,
        hostsByPool,
        nb_hosts,
        nb_pools,
        vms,
        srsByContainer,
        srs

      nb_pools = 0
      nb_hosts = 0
      vms = {
        nb: 0,
        states: [0, 0, 0, 0]
      }
      const runningStateToIndex = {
        Running: 0,
        Halted: 1,
        Suspended: 2,
        Action: 3
      }

      nb_pools = 0
      srs = []

      // update vdi, set them to the right host
      $scope.pools = pools = xoApi.getView('pools')

      srsByContainer = xoApi.getIndex('srsByContainer')
      vmsByContainer = xoApi.getIndex('vmsByContainer')
      $scope.hostsByPool = hostsByPool = xoApi.getIndex('hostsByPool')
      $scope.nbUpdates = {}
      foreach(pools.all, function (pool, pool_id) {
        let pool_hosts = hostsByPool[pool_id]
        foreach(pool_hosts, function (host, host_id) {
          xo.host.listMissingPatches(host_id)
            .then(result => {
              $scope.nbUpdates[host_id] = result.length
            }
          )
        })
      })

      foreach(pools.all, function (pool, pool_id) {
        nb_pools++
        let pool_srs = srsByContainer[pool_id]
        foreach(pool_srs, (one_srs) => {
          if (one_srs.SR_type !== 'iso' && one_srs.SR_type !== 'udev') {
            one_srs = clone(one_srs)
            one_srs.ratio = one_srs.size ? one_srs.physical_usage / one_srs.size : 0
            one_srs.pool_label = pool.name_label
            srs.push(one_srs)
          }
        })
        let VMs = vmsByContainer[pool_id]
        foreach(VMs, function (VM) {
        // non running VM
          vms.states[runningStateToIndex[VM['power_state']]]++
          vms.nb++
        })
        let hosts = hostsByPool[pool_id]
        foreach(hosts, function (host, host_id) {
          let hosts_srs = srsByContainer[host_id]
          foreach(hosts_srs, (one_srs) => {
            if (one_srs.SR_type !== 'iso' && one_srs.SR_type !== 'udev') {
              one_srs = clone(one_srs)
              one_srs.ratio = one_srs.size ? one_srs.physical_usage / one_srs.size : 0
              one_srs.host_label = host.name_label
              one_srs.pool_label = pool.name_label
              srs.push(one_srs)
            }
          })
          nb_hosts++
          let VMs = vmsByContainer[host_id]
          foreach(VMs, function (VM) {
            vms.states[runningStateToIndex[VM['power_state']]]++
            vms.nb++
          })
        })
      })

      $scope.hosts.nb = nb_hosts
      $scope.vms = vms
      $scope.pools.nb = nb_pools
      $scope.srs = srs
      $scope.ram = [xoApi.stats.$memory.usage, xoApi.stats.$memory.size - xoApi.stats.$memory.usage]
      $scope.cpu = [[xoApi.stats.$vCPUs], [xoApi.stats.$CPUs]]
    }

    const debouncedPopulate = debounce(populateChartsData, 300, {leading: true, trailing: true})

    debouncedPopulate()
    xoApi.onUpdate(function () {
      debouncedPopulate()
    })
  })

  .name
<commit_msg>Stop computing charts data when no longer on dashboard (fix #604).<commit_after>'use strict'
import angular from 'angular'
import uiRouter from 'angular-ui-router'
import uiSelect from 'angular-ui-select'

import clone from 'lodash.clonedeep'
import debounce from 'lodash.debounce'
import foreach from 'lodash.foreach'

import xoApi from 'xo-api'
import xoServices from 'xo-services'

import view from './view'

export default angular.module('dashboard.overview', [
  uiRouter,
  uiSelect,
  xoApi,
  xoServices
])
  .config(function ($stateProvider) {
    $stateProvider.state('dashboard.overview', {
      controller: 'Overview as ctrl',
      data: {
        requireAdmin: true
      },
      url: '/overview',
      template: view
    })
  })
  .controller('Overview', function ($scope, $window, xoApi, xo, $timeout, bytesToSizeFilter, modal) {
    $window.bytesToSize = bytesToSizeFilter //  FIXME dirty workaround to custom a Chart.js tooltip template
    angular.extend($scope, {
      pools: {
        nb: 0
      },
      hosts: {
        nb: 0
      },
      vms: {
        nb: 0,
        running: 0,
        halted: 0,
        action: 0
      },
      ram: [0, 0],
      cpu: [0, 0],
      srs: []
    })

    $scope.installAllPatches = function () {
      modal.confirm({
        title: 'Install all the missing patches',
        message: 'Are you sure you want to install all the missing patches? This could take a while...'
      }).then(() =>
        foreach($scope.pools.all, function (pool, pool_id) {
          let pool_hosts = $scope.hostsByPool[pool_id]
          foreach(pool_hosts, function (host, host_id) {
            console.log('Installing all missing patches on host ', host_id)
            xo.host.installAllPatches(host_id)
          })
        })
      )
    }

    $scope.installHostPatches = function (hostId) {
      modal.confirm({
        title: 'Update host (' + $scope.nbUpdates[hostId] + ' patch(es))',
        message: 'Are you sure you want to install all the missing patches on this host? This could take a while...'
      }).then(() => {
        console.log('Installing all missing patches on host ', hostId)
        xo.host.installAllPatches(hostId)
      })
    }

    function populateChartsData () {
      let pools,
        vmsByContainer,
        hostsByPool,
        nb_hosts,
        nb_pools,
        vms,
        srsByContainer,
        srs

      nb_pools = 0
      nb_hosts = 0
      vms = {
        nb: 0,
        states: [0, 0, 0, 0]
      }
      const runningStateToIndex = {
        Running: 0,
        Halted: 1,
        Suspended: 2,
        Action: 3
      }

      nb_pools = 0
      srs = []

      // update vdi, set them to the right host
      $scope.pools = pools = xoApi.getView('pools')

      srsByContainer = xoApi.getIndex('srsByContainer')
      vmsByContainer = xoApi.getIndex('vmsByContainer')
      $scope.hostsByPool = hostsByPool = xoApi.getIndex('hostsByPool')
      $scope.nbUpdates = {}
      foreach(pools.all, function (pool, pool_id) {
        let pool_hosts = hostsByPool[pool_id]
        foreach(pool_hosts, function (host, host_id) {
          xo.host.listMissingPatches(host_id)
            .then(result => {
              $scope.nbUpdates[host_id] = result.length
            }
          )
        })
      })

      foreach(pools.all, function (pool, pool_id) {
        nb_pools++
        let pool_srs = srsByContainer[pool_id]
        foreach(pool_srs, (one_srs) => {
          if (one_srs.SR_type !== 'iso' && one_srs.SR_type !== 'udev') {
            one_srs = clone(one_srs)
            one_srs.ratio = one_srs.size ? one_srs.physical_usage / one_srs.size : 0
            one_srs.pool_label = pool.name_label
            srs.push(one_srs)
          }
        })
        let VMs = vmsByContainer[pool_id]
        foreach(VMs, function (VM) {
        // non running VM
          vms.states[runningStateToIndex[VM['power_state']]]++
          vms.nb++
        })
        let hosts = hostsByPool[pool_id]
        foreach(hosts, function (host, host_id) {
          let hosts_srs = srsByContainer[host_id]
          foreach(hosts_srs, (one_srs) => {
            if (one_srs.SR_type !== 'iso' && one_srs.SR_type !== 'udev') {
              one_srs = clone(one_srs)
              one_srs.ratio = one_srs.size ? one_srs.physical_usage / one_srs.size : 0
              one_srs.host_label = host.name_label
              one_srs.pool_label = pool.name_label
              srs.push(one_srs)
            }
          })
          nb_hosts++
          let VMs = vmsByContainer[host_id]
          foreach(VMs, function (VM) {
            vms.states[runningStateToIndex[VM['power_state']]]++
            vms.nb++
          })
        })
      })

      $scope.hosts.nb = nb_hosts
      $scope.vms = vms
      $scope.pools.nb = nb_pools
      $scope.srs = srs
      $scope.ram = [xoApi.stats.$memory.usage, xoApi.stats.$memory.size - xoApi.stats.$memory.usage]
      $scope.cpu = [[xoApi.stats.$vCPUs], [xoApi.stats.$CPUs]]
    }

    const debouncedPopulate = debounce(populateChartsData, 300, {leading: true, trailing: true})

    debouncedPopulate()

    $scope.$on('$destroy', xoApi.onUpdate(debouncedPopulate))
  })

  .name
<|endoftext|>


<commit_before>var display = require('gamejs/display');
var gamejs = require('gamejs');

/**
 * @fileoverview Methods for polling mouse, keyboard and ntwork;
 *
 * Call get() in your main loop to get a list of events that happend since you last called.
 *
 *
 * A pattern for using this might look like so: your main game function (tick in this example)
 * is being called by [gamejs.time.fpsCallback()](../time/#fpsCallback) 25 times per second.
 * Inside tick we call [gamejs.event.get()](#get) for a list of events that happened since the last
 * tick and we loop over each event and act on the event properties.
 *
 *     var events = gamejs.event.get()
 *     events.forEach(function(event) {
 *        if (event.type === gamejs.event.MOUSE_UP) {
 *          gamejs.log(event.pos, event.button);
 *        } else if (event.type === gamejs.event.KEY_UP) {
 *          gamejs.log(event.key);
 *        }
 *     });
 *
 */
// key constants
exports.K_UP = 38;
exports.K_DOWN = 40;
exports.K_RIGHT = 39;
exports.K_LEFT = 37;

exports.K_SPACE = 32;
exports.K_TAB = 9;
exports.K_ENTER = 13;
exports.K_CTRL = 17;
exports.K_ALT = 18;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;
exports.K_a = 65;
exports.K_b = 66;
exports.K_c = 67;
exports.K_d = 68;
exports.K_e = 69;
exports.K_f = 70;
exports.K_g = 71;
exports.K_h = 72;
exports.K_i = 73;
exports.K_j = 74;
exports.K_k = 75;
exports.K_l = 76;
exports.K_m = 77;
exports.K_n = 78;
exports.K_o = 79;
exports.K_p = 80;
exports.K_q = 81;
exports.K_r = 82;
exports.K_s = 83;
exports.K_t = 84;
exports.K_u = 85;
exports.K_v = 86;
exports.K_w = 87;
exports.K_x = 88;
exports.K_y = 89;
exports.K_z = 90;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;

exports.K_KP1 = 97;
exports.K_KP2 = 98;
exports.K_KP3 = 99;
exports.K_KP4 = 100;
exports.K_KP5 = 101;
exports.K_KP6 = 102;
exports.K_KP7 = 103;
exports.K_KP8 = 104;
exports.K_KP9 = 105;

// event type constants
exports.QUIT = 0;
exports.KEY_DOWN = 1;
exports.KEY_UP = 2;
exports.MOUSE_MOTION = 3;
exports.MOUSE_UP = 4
exports.MOUSE_DOWN = 5;

var QUEUE = [];

/**
 * Get all events from the event queue
 * @returns {Array}
 */
exports.get = function() {
   return QUEUE.splice(0, QUEUE.length);
};

/**
 * Get the newest event of the event queue
 * @returns {gamejs.event.Event}
 */
exports.poll = function() {
   return QUEUE.pop();
};

/**
 * Post an event to the event queue.
 * @param {gamejs.event.Event} userEvent the event to post to the queue
 */
exports.post = function(userEvent) {
   QUEUE.push(userEvent);
   return;
};

/**
 * Holds all information about an event.
 * @class
 */

exports.Event = function() {
    /**
     * The type of the event. e.g., gamejs.event.QUIT, KEYDOWN, MOUSEUP.
     */
    this.type = null;
    /**
     * key the keyCode of the key. compare with gamejs.event.K_a, gamejs.event.K_b,...
     */
    this.key = null;
    /**
     * relative movement for a mousemove event
     */
    this.rel = null;
    /**
     * the number of the mousebutton pressed
     */
    this.button = null;
    /**
     * pos the position of the event for mouse events
     */
    this.pos = null;
};

/**
 * @ignore
 */
exports.init = function() {

   // anonymouse functions as event handlers = memory leak, see MDC:elementAddEventListener

   function onMouseDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.MOUSE_DOWN,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
      });
   };

   function onMouseUp (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type':gamejs.event.MOUSE_UP,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
      });
   };

   function onKeyDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.KEY_DOWN,
         'key': ev.keyCode || ev.which,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
      return;
   };

   function onKeyUp (ev) {
      QUEUE.push({
         'type': gamejs.event.KEY_UP,
         'key': ev.keyCode,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   };

   function onMouseMove (ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      var relativePos = [];
      if (lastPos.length) {
         relativePos = [
            lastPos[0] - currentPos[0],
            lastPos[1] - currentPos[1]
         ];
      }
      QUEUE.push({
         'type': gamejs.event.MOUSE_MOTION,
         'pos': currentPos,
         'rel': relativePos,
         'buttons': null, // FIXME, fixable?
         'timestamp': ev.timeStamp,
      });
      lastPos = currentPos;
      return;
   };

   function onBeforeUnload (ev) {
      QUEUE.push({
         'type': gamejs.event.QUIT,
      });
      return;
   }

   // IEFIX does not support addEventListener on document itself
   // hook onto document.body not canvas to avoid dependancy into gamejs.display
   document.body.addEventListener('mousedown', onMouseDown, false);

   document.body.addEventListener('mouseup', onMouseUp, false);

   document.body.addEventListener('keydown', onKeyDown, false);

   document.body.addEventListener('keyup', onKeyUp, false);

   var lastPos = [];
   document.body.addEventListener('mousemove', onMouseMove, false);

   document.body.addEventListener('beforeunload', onBeforeUnload, false);

};
<commit_msg>attach event handlers to document, ff & opera don't catch events in some cases<commit_after>var display = require('gamejs/display');
var gamejs = require('gamejs');

/**
 * @fileoverview Methods for polling mouse, keyboard and ntwork;
 *
 * Call get() in your main loop to get a list of events that happend since you last called.
 *
 *
 * A pattern for using this might look like so: your main game function (tick in this example)
 * is being called by [gamejs.time.fpsCallback()](../time/#fpsCallback) 25 times per second.
 * Inside tick we call [gamejs.event.get()](#get) for a list of events that happened since the last
 * tick and we loop over each event and act on the event properties.
 *
 *     var events = gamejs.event.get()
 *     events.forEach(function(event) {
 *        if (event.type === gamejs.event.MOUSE_UP) {
 *          gamejs.log(event.pos, event.button);
 *        } else if (event.type === gamejs.event.KEY_UP) {
 *          gamejs.log(event.key);
 *        }
 *     });
 *
 */
// key constants
exports.K_UP = 38;
exports.K_DOWN = 40;
exports.K_RIGHT = 39;
exports.K_LEFT = 37;

exports.K_SPACE = 32;
exports.K_TAB = 9;
exports.K_ENTER = 13;
exports.K_CTRL = 17;
exports.K_ALT = 18;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;
exports.K_a = 65;
exports.K_b = 66;
exports.K_c = 67;
exports.K_d = 68;
exports.K_e = 69;
exports.K_f = 70;
exports.K_g = 71;
exports.K_h = 72;
exports.K_i = 73;
exports.K_j = 74;
exports.K_k = 75;
exports.K_l = 76;
exports.K_m = 77;
exports.K_n = 78;
exports.K_o = 79;
exports.K_p = 80;
exports.K_q = 81;
exports.K_r = 82;
exports.K_s = 83;
exports.K_t = 84;
exports.K_u = 85;
exports.K_v = 86;
exports.K_w = 87;
exports.K_x = 88;
exports.K_y = 89;
exports.K_z = 90;

exports.K_0 = 48;
exports.K_1 = 49;
exports.K_2 = 50;
exports.K_3 = 51;
exports.K_4 = 52;
exports.K_5 = 53;
exports.K_6 = 54;
exports.K_7 = 55;
exports.K_8 = 56;
exports.K_9 = 57;

exports.K_KP1 = 97;
exports.K_KP2 = 98;
exports.K_KP3 = 99;
exports.K_KP4 = 100;
exports.K_KP5 = 101;
exports.K_KP6 = 102;
exports.K_KP7 = 103;
exports.K_KP8 = 104;
exports.K_KP9 = 105;

// event type constants
exports.QUIT = 0;
exports.KEY_DOWN = 1;
exports.KEY_UP = 2;
exports.MOUSE_MOTION = 3;
exports.MOUSE_UP = 4
exports.MOUSE_DOWN = 5;

var QUEUE = [];

/**
 * Get all events from the event queue
 * @returns {Array}
 */
exports.get = function() {
   return QUEUE.splice(0, QUEUE.length);
};

/**
 * Get the newest event of the event queue
 * @returns {gamejs.event.Event}
 */
exports.poll = function() {
   return QUEUE.pop();
};

/**
 * Post an event to the event queue.
 * @param {gamejs.event.Event} userEvent the event to post to the queue
 */
exports.post = function(userEvent) {
   QUEUE.push(userEvent);
   return;
};

/**
 * Holds all information about an event.
 * @class
 */

exports.Event = function() {
    /**
     * The type of the event. e.g., gamejs.event.QUIT, KEYDOWN, MOUSEUP.
     */
    this.type = null;
    /**
     * key the keyCode of the key. compare with gamejs.event.K_a, gamejs.event.K_b,...
     */
    this.key = null;
    /**
     * relative movement for a mousemove event
     */
    this.rel = null;
    /**
     * the number of the mousebutton pressed
     */
    this.button = null;
    /**
     * pos the position of the event for mouse events
     */
    this.pos = null;
};

/**
 * @ignore
 */
exports.init = function() {

   // anonymouse functions as event handlers = memory leak, see MDC:elementAddEventListener

   function onMouseDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.MOUSE_DOWN,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
      });
   };

   function onMouseUp (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type':gamejs.event.MOUSE_UP,
         'pos': [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]],
         'button': ev.button,
      });
   };

   function onKeyDown (ev) {
      var canvasOffset = display._getCanvasOffset();
      QUEUE.push({
         'type': gamejs.event.KEY_DOWN,
         'key': ev.keyCode || ev.which,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
      return;
   };

   function onKeyUp (ev) {
      QUEUE.push({
         'type': gamejs.event.KEY_UP,
         'key': ev.keyCode,
         'shiftKey': ev.shiftKey,
         'ctrlKey': ev.ctrlKey,
         'metaKey': ev.metaKey
      });
   };

   function onMouseMove (ev) {
      var canvasOffset = display._getCanvasOffset();
      var currentPos = [ev.clientX - canvasOffset[0], ev.clientY - canvasOffset[1]];
      var relativePos = [];
      if (lastPos.length) {
         relativePos = [
            lastPos[0] - currentPos[0],
            lastPos[1] - currentPos[1]
         ];
      }
      QUEUE.push({
         'type': gamejs.event.MOUSE_MOTION,
         'pos': currentPos,
         'rel': relativePos,
         'buttons': null, // FIXME, fixable?
         'timestamp': ev.timeStamp,
      });
      lastPos = currentPos;
      return;
   };

   function onBeforeUnload (ev) {
      QUEUE.push({
         'type': gamejs.event.QUIT,
      });
      return;
   }

   // IEFIX does not support addEventListener on document itself
   // MOZFIX but in moz & opera events don't reach body if mouse outside window or on menubar
   // hook onto document.body not canvas to avoid dependancy into gamejs.display
   document.addEventListener('mousedown', onMouseDown, false);

   document.addEventListener('mouseup', onMouseUp, false);

   document.addEventListener('keydown', onKeyDown, false);

   document.addEventListener('keyup', onKeyUp, false);

   var lastPos = [];
   document.addEventListener('mousemove', onMouseMove, false);

   document.addEventListener('beforeunload', onBeforeUnload, false);

};
<|endoftext|>


<commit_before>L.TileLayer.Canvas.InteractiveCountryVectorLayer = L.TileLayer.Canvas.extend({
	options: {
		aggregateProperty: null,

		protoUrl: 'http://osm.personalwerk.de/example-vectiles/proto/vectiles.proto',
		url: 'http://osm.personalwerk.de/example-vectiles/{z}/{x}/{y}.pbf.base64',

		fillStyle: 'orange',
		strokeStyle: 'black',

		activeFillStyle: 'red',
		activeStrokeStyle: 'black',

		paint: null,
	},

	initialize: function(options) {
		L.TileLayer.Canvas.prototype.initialize.call(this, L.extend(options, {
			async: true,
			noWrap: true
		}));

		this._vectileData = {};
		this._displayCanvas = {};
		this._hitmapImageData = {};

		this._currentHover =  null;
		this._map = null;

		this.Vectile = dcodeIO.ProtoBuf.loadProtoFile(this.options.protoUrl).build("de.pwrk.vectiles.Vectile");
	},

	_id: function(z, x, y) {
		if(typeof(y) != "undefined")
			return z+'/'+x+'/'+y;
		else
			return z+'/'+x.x+'/'+x.y;
	},

	onAdd: function(map) {
		L.TileLayer.Canvas.prototype.onAdd.call(this, map);

		var layer = this;
		layer._map = map;

		var isDragging = false;
		map.on('dragstart', function() {
			isDragging = true;
		}).on('dragend', function() {
			isDragging = false;
		})

		if(!layer.options.aggregateProperty)
			console.warn('without an aggregateProperty hover/click will be disabled');
		else map.on('mousemove', function(e) {
			if(isDragging) return;

			var zoom = layer._map.getZoom();

			if (zoom > layer.options.maxZoom ||
				zoom < layer.options.minZoom) { return; }

			// determine tile under mouse
			// check hitmap for poly unter curser
			// check if the hoverstate has changed
			// if yes
			//   find tiles in viewport
			//   iterate those tiles
			//     check if those tiles contain the old or the new hovered poly
			//     if yes
			//       redraw them

			// determine tile unter mouse && check hitmap for poly unter curser
			var
				hoverFeature = layer._findHoverFeature(e.layerPoint),
				oldHoverValue = layer._currentHover ? layer._currentHover.tags[layer.options.aggregateProperty] : null,
				newHoverValue = hoverFeature ? hoverFeature.tags[layer.options.aggregateProperty] : null;

			layer._currentHover = hoverFeature;

			// check if the hoverstate has changed
			if(newHoverValue != oldHoverValue)
			{
				layer.fire('activeChanged', {'new': hoverFeature, 'old': layer._currentHover});

				// iterate tiles
				var kArr, x, y, key;
				for (key in layer._tiles) {
					kArr = key.split(':');
					x = parseInt(kArr[0], 10);
					y = parseInt(kArr[1], 10);

					var id = layer._id(zoom, x, y),
						containedValues = layer._vectileData[id] ? layer._vectileData[id].containedAggerateValues : null;

					// check if those tiles contain the old or the new hovered poly
					if(
						containedValues &&
						(containedValues.indexOf(oldHoverValue) !== -1 || containedValues.indexOf(newHoverValue) !== -1)
					) {
						// if yes, redraw them
						layer._drawDispalyTile(L.point(x, y), zoom);
					}
				}

			}
		}).on('click', function(e) {
			var hoverFeature = layer._findHoverFeature(e.layerPoint);

			layer.fire('featureClick', {
				'feature': hoverFeature
			});
			map.fireEvent('mousemove', e);
		});
	},

	_findHoverFeature: function(layerPoint) {
		var
			layer = this,
			tilePoint = layerPoint
				.add(layer._map.getPixelOrigin())
				.divideBy(layer._getTileSize())
				.floor(),
			tilePixel = layerPoint
				.add(layer._map.getPixelOrigin())
				.subtract(
					tilePoint.multiplyBy(layer._getTileSize())
				),
			id = layer._id(layer._map._zoom, tilePoint),
			hitmapImageData = layer._hitmapImageData[id];

		if(!hitmapImageData)
			return null;

		var
			pxIdx = Math.floor(tilePixel.x) * 4 + Math.floor(tilePixel.y) * hitmapImageData.width * 4,
			polyIdx = layer._rgbintsToInt(hitmapImageData.data[pxIdx+0], hitmapImageData.data[pxIdx+1], hitmapImageData.data[pxIdx+2]),
			polyPlausibility = hitmapImageData.data[pxIdx+3];

		if(255 != polyPlausibility)
			return null;

		if(0 == polyIdx)
			return null;

		return this._vectileData[id].polys[polyIdx-1];
	},

	drawTile: function(canvas, tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			url = L.Util.template(layer.options.url, {z: zoom, x: tilePoint.x, y: tilePoint.y});

		$.get(url, function(buf) {
			var
				vectile = layer.Vectile.decode(buf),
				containedAggerates = {};

			$.each(vectile.polys, function(polyIdx, poly) {
				var tags = {};

				for (var i = 0; i < poly.tags.length; i++) {
					var
						k = poly.tags[i].k,
						v = poly.tags[i].v;

					tags[k] = v;

					if(layer.options.aggregateProperty == k)
						containedAggerates[v] = true;
				}
				poly.tags = tags;

			});
			vectile.containedAggerateValues = Object.keys(containedAggerates);

			layer._vectileData[id] = vectile;

			var hitmapCanvas = document.createElement('canvas');
			hitmapCanvas.width = canvas.width;
			hitmapCanvas.height = canvas.height;

			layer._drawHitmapTile(hitmapCanvas, tilePoint, zoom);
			layer._hitmapImageData[id] = hitmapCanvas.getContext('2d').getImageData(0, 0, hitmapCanvas.width, hitmapCanvas.height);

			layer._displayCanvas[id] = canvas;
			layer._drawDispalyTile(tilePoint, zoom);

			layer.tileDrawn(canvas);
		});
	},

	_drawHitmapTile: function(hitmapCanvas, tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			vectile = layer._vectileData[id],
			ctx = hitmapCanvas.getContext('2d');

		$.each(vectile.polys, function(polyIdx, poly) {
			ctx.beginPath();

			layer._drawPoly(ctx, poly);

			ctx.closePath();
			ctx.fillStyle = layer._intToRgb(polyIdx+1);
			ctx.fill();
		});
	},

	_drawDispalyTile: function(tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			vectile = layer._vectileData[id],
			ctx = layer._displayCanvas[id].getContext('2d');

		$.each(vectile.polys, function(polyIdx, poly) {
			var
				value = poly.tags[layer.options.aggregateProperty],
				active = (layer.options.aggregateProperty && layer._currentHover && layer._currentHover.tags[layer.options.aggregateProperty] == value);

			ctx.beginPath();

			layer._drawPoly(ctx, poly);

			ctx.closePath();
			layer._canvasPaintPath(ctx, active, poly);
		});
	},

	_canvasPaintPath: function(ctx, active, poly) {
		var layer = this;

		if(layer.options.paint)
		{
			layer.options.paint(ctx, active, poly, layer);
		}
		else
		{
			ctx.fillStyle = active ? layer.options.activeFillStyle : layer.options.fillStyle;
			ctx.strokeStyle = active ? layer.options.activeStrokeStyle : layer.options.strokeStyle;

			ctx.fill();
			ctx.stroke();
		}
	},

	_drawPoly: function(ctx, poly) {
		var deltax = null, deltay = null;
		$.each(poly.delta_x, function(coordIdx) {
			var
				dx = poly.delta_x[coordIdx],
				dy = poly.delta_y[coordIdx];

			if(deltax == null && deltay == null)
				ctx.moveTo(deltax = dx, 256 - (deltay = dy));
			else
				ctx.lineTo(deltax += dx, 256 - (deltay += dy));
		});
	},

	_lpad: function(str, length, padchar)
	{
		padchar = padchar || '0';

		while(str.length < length)
			str = padchar + str;

		return str;
	},

	_intToRgb: function(i)
	{
		return '#'+this._lpad((new Number(i)).toString(16), 6);
	},

	_rgbToInt: function(rgb)
	{
		return parseInt(rgb.slice(1), 16);
	},

	_rgbintsToInt: function(r, g, b)
	{
		return (r << 16) + (g << 8) + b;
	}
});

L.tileLayer.canvas.interactiveCountryVectorLayer = function(options) {
	return new L.TileLayer.Canvas.InteractiveCountryVectorLayer(options);
}
<commit_msg>added latlng to featureClick event<commit_after>L.TileLayer.Canvas.InteractiveCountryVectorLayer = L.TileLayer.Canvas.extend({
	options: {
		aggregateProperty: null,

		protoUrl: 'http://osm.personalwerk.de/example-vectiles/proto/vectiles.proto',
		url: 'http://osm.personalwerk.de/example-vectiles/{z}/{x}/{y}.pbf.base64',

		fillStyle: 'orange',
		strokeStyle: 'black',

		activeFillStyle: 'red',
		activeStrokeStyle: 'black',

		paint: null,
	},

	initialize: function(options) {
		L.TileLayer.Canvas.prototype.initialize.call(this, L.extend(options, {
			async: true,
			noWrap: true
		}));

		this._vectileData = {};
		this._displayCanvas = {};
		this._hitmapImageData = {};

		this._currentHover =  null;
		this._map = null;

		this.Vectile = dcodeIO.ProtoBuf.loadProtoFile(this.options.protoUrl).build("de.pwrk.vectiles.Vectile");
	},

	_id: function(z, x, y) {
		if(typeof(y) != "undefined")
			return z+'/'+x+'/'+y;
		else
			return z+'/'+x.x+'/'+x.y;
	},

	onAdd: function(map) {
		L.TileLayer.Canvas.prototype.onAdd.call(this, map);

		var layer = this;
		layer._map = map;

		var isDragging = false;
		map.on('dragstart', function() {
			isDragging = true;
		}).on('dragend', function() {
			isDragging = false;
		})

		if(!layer.options.aggregateProperty)
			console.warn('without an aggregateProperty hover/click will be disabled');
		else map.on('mousemove', function(e) {
			if(isDragging) return;

			var zoom = layer._map.getZoom();

			if (zoom > layer.options.maxZoom ||
				zoom < layer.options.minZoom) { return; }

			// determine tile under mouse
			// check hitmap for poly unter curser
			// check if the hoverstate has changed
			// if yes
			//   find tiles in viewport
			//   iterate those tiles
			//     check if those tiles contain the old or the new hovered poly
			//     if yes
			//       redraw them

			// determine tile unter mouse && check hitmap for poly unter curser
			var
				hoverFeature = layer._findHoverFeature(e.layerPoint),
				oldHoverValue = layer._currentHover ? layer._currentHover.tags[layer.options.aggregateProperty] : null,
				newHoverValue = hoverFeature ? hoverFeature.tags[layer.options.aggregateProperty] : null;

			layer._currentHover = hoverFeature;

			// check if the hoverstate has changed
			if(newHoverValue != oldHoverValue)
			{
				layer.fire('activeChanged', {'new': hoverFeature, 'old': layer._currentHover});

				// iterate tiles
				var kArr, x, y, key;
				for (key in layer._tiles) {
					kArr = key.split(':');
					x = parseInt(kArr[0], 10);
					y = parseInt(kArr[1], 10);

					var id = layer._id(zoom, x, y),
						containedValues = layer._vectileData[id] ? layer._vectileData[id].containedAggerateValues : null;

					// check if those tiles contain the old or the new hovered poly
					if(
						containedValues &&
						(containedValues.indexOf(oldHoverValue) !== -1 || containedValues.indexOf(newHoverValue) !== -1)
					) {
						// if yes, redraw them
						layer._drawDispalyTile(L.point(x, y), zoom);
					}
				}

			}
		}).on('click', function(e) {
			var hoverFeature = layer._findHoverFeature(e.layerPoint);

			layer.fire('featureClick', {
				'feature': hoverFeature,
				'latlng': e.latlng
			});
			map.fireEvent('mousemove', e);
		});
	},

	_findHoverFeature: function(layerPoint) {
		var
			layer = this,
			tilePoint = layerPoint
				.add(layer._map.getPixelOrigin())
				.divideBy(layer._getTileSize())
				.floor(),
			tilePixel = layerPoint
				.add(layer._map.getPixelOrigin())
				.subtract(
					tilePoint.multiplyBy(layer._getTileSize())
				),
			id = layer._id(layer._map._zoom, tilePoint),
			hitmapImageData = layer._hitmapImageData[id];

		if(!hitmapImageData)
			return null;

		var
			pxIdx = Math.floor(tilePixel.x) * 4 + Math.floor(tilePixel.y) * hitmapImageData.width * 4,
			polyIdx = layer._rgbintsToInt(hitmapImageData.data[pxIdx+0], hitmapImageData.data[pxIdx+1], hitmapImageData.data[pxIdx+2]),
			polyPlausibility = hitmapImageData.data[pxIdx+3];

		if(255 != polyPlausibility)
			return null;

		if(0 == polyIdx)
			return null;

		return this._vectileData[id].polys[polyIdx-1];
	},

	drawTile: function(canvas, tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			url = L.Util.template(layer.options.url, {z: zoom, x: tilePoint.x, y: tilePoint.y});

		$.get(url, function(buf) {
			var
				vectile = layer.Vectile.decode(buf),
				containedAggerates = {};

			$.each(vectile.polys, function(polyIdx, poly) {
				var tags = {};

				for (var i = 0; i < poly.tags.length; i++) {
					var
						k = poly.tags[i].k,
						v = poly.tags[i].v;

					tags[k] = v;

					if(layer.options.aggregateProperty == k)
						containedAggerates[v] = true;
				}
				poly.tags = tags;

			});
			vectile.containedAggerateValues = Object.keys(containedAggerates);

			layer._vectileData[id] = vectile;

			var hitmapCanvas = document.createElement('canvas');
			hitmapCanvas.width = canvas.width;
			hitmapCanvas.height = canvas.height;

			layer._drawHitmapTile(hitmapCanvas, tilePoint, zoom);
			layer._hitmapImageData[id] = hitmapCanvas.getContext('2d').getImageData(0, 0, hitmapCanvas.width, hitmapCanvas.height);

			layer._displayCanvas[id] = canvas;
			layer._drawDispalyTile(tilePoint, zoom);

			layer.tileDrawn(canvas);
		});
	},

	_drawHitmapTile: function(hitmapCanvas, tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			vectile = layer._vectileData[id],
			ctx = hitmapCanvas.getContext('2d');

		$.each(vectile.polys, function(polyIdx, poly) {
			ctx.beginPath();

			layer._drawPoly(ctx, poly);

			ctx.closePath();
			ctx.fillStyle = layer._intToRgb(polyIdx+1);
			ctx.fill();
		});
	},

	_drawDispalyTile: function(tilePoint, zoom) {
		var
			layer = this,
			id = layer._id(zoom, tilePoint),
			vectile = layer._vectileData[id],
			ctx = layer._displayCanvas[id].getContext('2d');

		$.each(vectile.polys, function(polyIdx, poly) {
			var
				value = poly.tags[layer.options.aggregateProperty],
				active = (layer.options.aggregateProperty && layer._currentHover && layer._currentHover.tags[layer.options.aggregateProperty] == value);

			ctx.beginPath();

			layer._drawPoly(ctx, poly);

			ctx.closePath();
			layer._canvasPaintPath(ctx, active, poly);
		});
	},

	_canvasPaintPath: function(ctx, active, poly) {
		var layer = this;

		if(layer.options.paint)
		{
			layer.options.paint(ctx, active, poly, layer);
		}
		else
		{
			ctx.fillStyle = active ? layer.options.activeFillStyle : layer.options.fillStyle;
			ctx.strokeStyle = active ? layer.options.activeStrokeStyle : layer.options.strokeStyle;

			ctx.fill();
			ctx.stroke();
		}
	},

	_drawPoly: function(ctx, poly) {
		var deltax = null, deltay = null;
		$.each(poly.delta_x, function(coordIdx) {
			var
				dx = poly.delta_x[coordIdx],
				dy = poly.delta_y[coordIdx];

			if(deltax == null && deltay == null)
				ctx.moveTo(deltax = dx, 256 - (deltay = dy));
			else
				ctx.lineTo(deltax += dx, 256 - (deltay += dy));
		});
	},

	_lpad: function(str, length, padchar)
	{
		padchar = padchar || '0';

		while(str.length < length)
			str = padchar + str;

		return str;
	},

	_intToRgb: function(i)
	{
		return '#'+this._lpad((new Number(i)).toString(16), 6);
	},

	_rgbToInt: function(rgb)
	{
		return parseInt(rgb.slice(1), 16);
	},

	_rgbintsToInt: function(r, g, b)
	{
		return (r << 16) + (g << 8) + b;
	}
});

L.tileLayer.canvas.interactiveCountryVectorLayer = function(options) {
	return new L.TileLayer.Canvas.InteractiveCountryVectorLayer(options);
}
<|endoftext|>