<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import {blueA400, grey600, grey700, blueA100} from 'material-ui/styles/colors'
import { connect } from 'react-redux'
import sizeMe from 'react-sizeme'

import {Card, CardHeader} from 'material-ui/Card'
import ConfigurationPane from './configuration-pane/ConfigurationPane'
import PermissionsPane from './configuration-pane/PermissionsPane'
import PodTemplatePane from './configuration-pane/PodTemplatePane'
import EventViewer from './EventViewer'

import IconConfiguration from 'material-ui/svg-icons/action/list'
import IconPermissions from 'material-ui/svg-icons/communication/vpn-key'
import IconPodTemplate from 'material-ui/svg-icons/action/flip-to-back'
import IconLogs from 'material-ui/svg-icons/action/receipt'
import IconTerminal from 'material-ui/svg-icons/hardware/computer'
import IconEvents from 'material-ui/svg-icons/action/event'
import FilterChip from './FilterChip'

import { withRouter } from 'react-router-dom'

import {Tabs, Tab} from 'material-ui/Tabs'

import LoadingSpinner from './LoadingSpinner'
import KubeKinds from '../kube-kinds'
import KindAbbreviation from './KindAbbreviation'

import { resourceStatus as resourceStatusIcons } from './icons'
import ConfirmationDialog from './ConfirmationDialog'
import ScaleDialog from './ScaleDialog'
import ResourceInfoActionsMenu from './ResourceInfoActionsMenu'
import { sameResource } from '../utils/resource-utils'

import './ResourceInfoPage.css'

import Loadable from 'react-loadable'
import LoadingComponentStub from '../components/LoadingComponentStub'

const AsyncTerminalViewer = Loadable({
  loader: () => import('./TerminalViewer'),
  loading: LoadingComponentStub
})

const AsyncLogViewer = Loadable({
  loader: () => import('./LogViewer'),
  loading: LoadingComponentStub
})

const mapStateToProps = function(store) {
  return {
    pods: store.workloads.pods,
    accessEvaluator: store.session.accessEvaluator,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
  }
}

const styles = {
  tabs: {
    backgroundColor: grey600,
  },
  tabsInkBar: {
    backgroundColor: blueA400,
    height: 3,
    marginTop: -4,
    borderTop: `1px ${blueA100} solid`,
  },
  card: {
    margin: 5,
    border: '1px solid rgba(33,33,33,0.8)',
  },
  cardHeader: {
    background: 'rgb(66,66,66)',
    minHeight: '125px',
  },
  cardHeaderTitle: {
    fontWeight: 600,
    fontSize: '18px',
  },
  cardHeaderTitleStyle: {
    fontSize: '24px',
    fontWeight: 600,
    paddingLeft: 10,
    color: 'rgba(240,240,240,1)',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    maxWidth: 'calc(100vw - 320px)',
    verticalAlign: 'middle',
    whiteSpace: 'nowrap',
  }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps) (
sizeMe({ monitorHeight: true, monitorWidth: true }) (
class ResourceInfoPage extends React.Component {

  static propTypes = {
    editResource: PropTypes.func,
    removeResource: PropTypes.func,
    scaleResource: PropTypes.func,
  }

  static defaultProps = {
    editResource: function(){},
    removeResource: function(){},
    scaleResource: function(){},
  }

  constructor(props) {
    super(props);
    this.state = {
      actionsOpen: false,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      editing: false,
    }

    if (props.resource) {
      this.kubeKind = KubeKinds[props.resourceGroup][props.resource.kind]
      let that = this
      this.props.accessEvaluator.getObjectAccess(props.resource, props.resourceGroup).then((access) => {
        that.setState({
          resourceAccess: access,
        })
      })
    }
    for (let fn of ['handleScale','handleSuspend','handleDelete']) {
      this[fn] = this[fn].bind(this)
    }
  }

  handleActionsTouchTap = (event) => {
    // This prevents ghost click.
    event.preventDefault();
    this.setState({
      actionsOpen: true,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      actionsAnchor: event.currentTarget,
    })
  }

  handleActionsRequestClose = () => {
    this.setState({
      actionsOpen: false,
    })
  }

  handleFilterLabelTouchTap = (filters) => {
    this.props.viewFilters(filters)
  }

  handleKindTouchTap = (kind, namespace) => {
    this.props.viewKind(kind, namespace)
  }

  handleRequestCloseDelete = () => {
    this.setState({
      deleteOpen: false,
    })
  }

  handleDelete = () => {
    this.setState({
      deleteOpen: true,
      actionsOpen: false,
    })
  }

  handleConfirmDelete = () => {
    this.setState({
      deleteOpen: false,
    })
    this.props.removeResource(this.props.resource)
  }

  handleScale = () => {
    this.setState({
      scaleOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseScale = () => {
    this.setState({
      scaleOpen: false,
    })
  }

  handleConfirmScale = (replicas) => {
    this.setState({
      scaleOpen: false,
    })
    if (replicas !== undefined) {
      this.props.scaleResource(this.props.resource, replicas)
    }
  }

  handleSuspend = () => {
    this.setState({
      suspendOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
  }

  handleConfirmSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
    this.props.scaleResource(this.props.resource, 0)
  }

  componentWillReceiveProps = (nextProps, nextState) => {
    if (!sameResource(nextProps.resource, this.props.resource)) {
      this.setState({resourceAccess: null})
      this.kubeKind = KubeKinds[nextProps.resourceGroup][nextProps.resource.kind]
      let that = this
      this.props.accessEvaluator.getObjectAccess(nextProps.resource, nextProps.resourceGroup).then((access) => {
        that.setState({
          resourceAccess: access,
        })
      })
    }
  }

  componentDidUpdate = () => {
    this.kubeKind = !!this.props.resource && KubeKinds[this.props.resourceGroup][this.props.resource.kind]
  }

  render() {

    let { resourceGroup, resource, logs } = this.props
    let { resourceAccess } = this.state

    let tabs = []
    let targetTab = this.props.activeTab
    if (!!resource) {

      tabs.push({
        name: 'config',
        component: ConfigurationPane,
        icon: <IconConfiguration/>,
        props: {resource: resource, resourceGroup: resourceGroup},
      })

      if (resource.kind === 'ServiceAccount') {
        tabs.push({
          name: 'permissions',
          component: PermissionsPane,
          icon: <IconPermissions/>,
          props: {serviceAccount: resource, resources: this.props.resources},
        })
      }

      if (resource.spec && resource.spec.template) {
        tabs.push({
          name: 'pod template',
          component: PodTemplatePane,
          icon: <IconPodTemplate/>,
          props: {resource: resource},
        })
      }
    }

    // TODO: this may also require a separate permissions check
    tabs.push({
      name: 'events',
      component: EventViewer,
      icon: <IconEvents/>,
    })

    if (resourceAccess && resourceAccess.logs) {
      tabs.push({
        name: 'logs',
        component: AsyncLogViewer,
        icon: <IconLogs/>,
        props: {logs: logs},
      })
    }

    if (resourceAccess && resourceAccess.exec) {
      tabs.push({
        name: 'terminal',
        component: AsyncTerminalViewer,
        icon: <IconTerminal/>,
        props: {logs: logs},
      })
    }

    let activeTab = tabs[0].name
    if (targetTab !== 'edit') {
      if (tabs.find((t) => t.name === targetTab)) {
        activeTab = targetTab
      }

      if (resourceAccess && activeTab !== targetTab) {
        return null
      }
    }

    return (

        <LoadingSpinner hidden={!this.props.resource}/>

        <Card className="resource-info" style={styles.card} >
          <CardHeader
            title={resource.metadata.name}
            titleStyle={styles.cardHeaderTitleStyle}
            subtitle={

                {resource.metadata.labels && Object.entries(resource.metadata.labels).map(([key, value]) =>
                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`, `${key}:${value}`])}
                    key={key}
                    prefix={key}
                    suffix={value} />)
                }

                  {'namespace' in resource.metadata &&
                    <FilterChip
                      onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`])}
                      prefixStyle={{color: grey700}}
                      prefix={'namespace'} suffix={resource.metadata.namespace} />
                  }
                  <FilterChip
                    onTouchTap={this.handleKindTouchTap.bind(this,resource.kind, resource.metadata.namespace)}
                    prefixStyle={{color: grey700}}
                    prefix={'kind'} suffix={resource.kind} />

            }
            avatar={<KindAbbreviation text={this.kubeKind.abbrev} color={this.kubeKind.color}/>}
            style={styles.cardHeader}
          >

          <ResourceInfoActionsMenu
            access={this.state.resourceAccess}
            handlers={{
              scale: this.handleScale,
              edit: () => {
                this.props.selectView('edit')
                this.setState({actionsOpen: false})
              },
              suspend: this.handleSuspend,
              delete: this.handleDelete,
            }}
            />

{resourceStatusIcons[resource.statusSummary]}

          </CardHeader>

          <Tabs
            style={{background: 'white'}}
            tabItemContainerStyle={styles.tabs}
            contentContainerStyle={{overflow: 'hidden'}}
            inkBarStyle={styles.tabsInkBar}
            className={`resource-tabs ${activeTab.replace(" ","-")}`}
            value={activeTab}
            ref={ (ref) => {
                if (!!ref) {
                  // get absolute bottom of the tab button bar so our tab contents can fill available space
                  let tabButtons = document.getElementsByClassName('resource-tabs')[0].children[0]
                  this.contentTop = tabButtons.getBoundingClientRect().bottom
                }
              }
            }
            >
            {tabs.map(tab => {
                let Component = tab.component
                return <Tab key={tab.name} icon={tab.icon} label={tab.name} value={tab.name} onActive={this.props.selectView.bind(this, tab.name)}>
                  <Component {...tab.props} contentTop={this.contentTop}/>
                </Tab>
              }
            )}
          </Tabs>
        </Card>

        <ConfirmationDialog
          open={this.state.deleteOpen}
          title={'Delete Resource(s):'}
          message={`Are you sure you want to delete the following resource?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseDelete}
          onConfirm={this.handleConfirmDelete}
          />

        <ConfirmationDialog
          open={this.state.suspendOpen}
          title={'Suspend Resource(s):'}
          message={`Are you sure you want to suspend the following resource (by scaling to 0 replicas)?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseSuspend}
          onConfirm={this.handleConfirmSuspend}
          />

        <ScaleDialog
          open={this.state.scaleOpen}
          resource={this.props.resource}
          onRequestClose={this.handleRequestCloseScale}
          onConfirm={this.handleConfirmScale}
          />

    )
  }
})))
<commit_msg>fix lineheight for resource info title<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import {blueA400, grey600, grey700, blueA100} from 'material-ui/styles/colors'
import { connect } from 'react-redux'
import sizeMe from 'react-sizeme'

import {Card, CardHeader} from 'material-ui/Card'
import ConfigurationPane from './configuration-pane/ConfigurationPane'
import PermissionsPane from './configuration-pane/PermissionsPane'
import PodTemplatePane from './configuration-pane/PodTemplatePane'
import EventViewer from './EventViewer'

import IconConfiguration from 'material-ui/svg-icons/action/list'
import IconPermissions from 'material-ui/svg-icons/communication/vpn-key'
import IconPodTemplate from 'material-ui/svg-icons/action/flip-to-back'
import IconLogs from 'material-ui/svg-icons/action/receipt'
import IconTerminal from 'material-ui/svg-icons/hardware/computer'
import IconEvents from 'material-ui/svg-icons/action/event'
import FilterChip from './FilterChip'

import { withRouter } from 'react-router-dom'

import {Tabs, Tab} from 'material-ui/Tabs'

import LoadingSpinner from './LoadingSpinner'
import KubeKinds from '../kube-kinds'
import KindAbbreviation from './KindAbbreviation'

import { resourceStatus as resourceStatusIcons } from './icons'
import ConfirmationDialog from './ConfirmationDialog'
import ScaleDialog from './ScaleDialog'
import ResourceInfoActionsMenu from './ResourceInfoActionsMenu'
import { sameResource } from '../utils/resource-utils'

import './ResourceInfoPage.css'

import Loadable from 'react-loadable'
import LoadingComponentStub from '../components/LoadingComponentStub'

const AsyncTerminalViewer = Loadable({
  loader: () => import('./TerminalViewer'),
  loading: LoadingComponentStub
})

const AsyncLogViewer = Loadable({
  loader: () => import('./LogViewer'),
  loading: LoadingComponentStub
})

const mapStateToProps = function(store) {
  return {
    pods: store.workloads.pods,
    accessEvaluator: store.session.accessEvaluator,
  }
}

const mapDispatchToProps = function(dispatch, ownProps) {
  return {
  }
}

const styles = {
  tabs: {
    backgroundColor: grey600,
  },
  tabsInkBar: {
    backgroundColor: blueA400,
    height: 3,
    marginTop: -4,
    borderTop: `1px ${blueA100} solid`,
  },
  card: {
    margin: 5,
    border: '1px solid rgba(33,33,33,0.8)',
  },
  cardHeader: {
    background: 'rgb(66,66,66)',
    minHeight: '125px',
  },
  cardHeaderTitle: {
    fontWeight: 600,
    fontSize: '18px',
  },
  cardHeaderTitleStyle: {
    fontSize: '24px',
    lineHeight: '30px',
    fontWeight: 600,
    paddingLeft: 10,
    color: 'rgba(240,240,240,1)',
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    maxWidth: 'calc(100vw - 320px)',
    verticalAlign: 'middle',
    whiteSpace: 'nowrap',
  },
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps) (
sizeMe({ monitorHeight: true, monitorWidth: true }) (
class ResourceInfoPage extends React.Component {

  static propTypes = {
    editResource: PropTypes.func,
    removeResource: PropTypes.func,
    scaleResource: PropTypes.func,
  }

  static defaultProps = {
    editResource: function(){},
    removeResource: function(){},
    scaleResource: function(){},
  }

  constructor(props) {
    super(props);
    this.state = {
      actionsOpen: false,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      editing: false,
    }

    if (props.resource) {
      this.kubeKind = KubeKinds[props.resourceGroup][props.resource.kind]
      let that = this
      this.props.accessEvaluator.getObjectAccess(props.resource, props.resourceGroup).then((access) => {
        that.setState({
          resourceAccess: access,
        })
      })
    }
    for (let fn of ['handleScale','handleSuspend','handleDelete']) {
      this[fn] = this[fn].bind(this)
    }
  }

  handleActionsTouchTap = (event) => {
    // This prevents ghost click.
    event.preventDefault();
    this.setState({
      actionsOpen: true,
      deleteOpen: false,
      scaleOpen: false,
      suspendOpen: false,
      actionsAnchor: event.currentTarget,
    })
  }

  handleActionsRequestClose = () => {
    this.setState({
      actionsOpen: false,
    })
  }

  handleFilterLabelTouchTap = (filters) => {
    this.props.viewFilters(filters)
  }

  handleKindTouchTap = (kind, namespace) => {
    this.props.viewKind(kind, namespace)
  }

  handleRequestCloseDelete = () => {
    this.setState({
      deleteOpen: false,
    })
  }

  handleDelete = () => {
    this.setState({
      deleteOpen: true,
      actionsOpen: false,
    })
  }

  handleConfirmDelete = () => {
    this.setState({
      deleteOpen: false,
    })
    this.props.removeResource(this.props.resource)
  }

  handleScale = () => {
    this.setState({
      scaleOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseScale = () => {
    this.setState({
      scaleOpen: false,
    })
  }

  handleConfirmScale = (replicas) => {
    this.setState({
      scaleOpen: false,
    })
    if (replicas !== undefined) {
      this.props.scaleResource(this.props.resource, replicas)
    }
  }

  handleSuspend = () => {
    this.setState({
      suspendOpen: true,
      actionsOpen: false,
    })
  }

  handleRequestCloseSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
  }

  handleConfirmSuspend = () => {
    this.setState({
      suspendOpen: false,
    })
    this.props.scaleResource(this.props.resource, 0)
  }

  componentWillReceiveProps = (nextProps, nextState) => {
    if (!sameResource(nextProps.resource, this.props.resource)) {
      this.setState({resourceAccess: null})
      this.kubeKind = KubeKinds[nextProps.resourceGroup][nextProps.resource.kind]
      let that = this
      this.props.accessEvaluator.getObjectAccess(nextProps.resource, nextProps.resourceGroup).then((access) => {
        that.setState({
          resourceAccess: access,
        })
      })
    }
  }

  componentDidUpdate = () => {
    this.kubeKind = !!this.props.resource && KubeKinds[this.props.resourceGroup][this.props.resource.kind]
  }

  render() {

    let { resourceGroup, resource, logs } = this.props
    let { resourceAccess } = this.state

    let tabs = []
    let targetTab = this.props.activeTab
    if (!!resource) {

      tabs.push({
        name: 'config',
        component: ConfigurationPane,
        icon: <IconConfiguration/>,
        props: {resource: resource, resourceGroup: resourceGroup},
      })

      if (resource.kind === 'ServiceAccount') {
        tabs.push({
          name: 'permissions',
          component: PermissionsPane,
          icon: <IconPermissions/>,
          props: {serviceAccount: resource, resources: this.props.resources},
        })
      }

      if (resource.spec && resource.spec.template) {
        tabs.push({
          name: 'pod template',
          component: PodTemplatePane,
          icon: <IconPodTemplate/>,
          props: {resource: resource},
        })
      }
    }

    // TODO: this may also require a separate permissions check
    tabs.push({
      name: 'events',
      component: EventViewer,
      icon: <IconEvents/>,
    })

    if (resourceAccess && resourceAccess.logs) {
      tabs.push({
        name: 'logs',
        component: AsyncLogViewer,
        icon: <IconLogs/>,
        props: {logs: logs},
      })
    }

    if (resourceAccess && resourceAccess.exec) {
      tabs.push({
        name: 'terminal',
        component: AsyncTerminalViewer,
        icon: <IconTerminal/>,
        props: {logs: logs},
      })
    }

    let activeTab = tabs[0].name
    if (targetTab !== 'edit') {
      if (tabs.find((t) => t.name === targetTab)) {
        activeTab = targetTab
      }

      if (resourceAccess && activeTab !== targetTab) {
        return null
      }
    }

    return (

        <LoadingSpinner hidden={!this.props.resource}/>

        <Card className="resource-info" style={styles.card} >
          <CardHeader
            title={resource.metadata.name}
            titleStyle={styles.cardHeaderTitleStyle}
            subtitle={

                {resource.metadata.labels && Object.entries(resource.metadata.labels).map(([key, value]) =>
                  <FilterChip
                    onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`, `${key}:${value}`])}
                    key={key}
                    prefix={key}
                    suffix={value} />)
                }

                  {'namespace' in resource.metadata &&
                    <FilterChip
                      onTouchTap={this.handleFilterLabelTouchTap.bind(this,[`namespace:${resource.metadata.namespace}`])}
                      prefixStyle={{color: grey700}}
                      prefix={'namespace'} suffix={resource.metadata.namespace} />
                  }
                  <FilterChip
                    onTouchTap={this.handleKindTouchTap.bind(this,resource.kind, resource.metadata.namespace)}
                    prefixStyle={{color: grey700}}
                    prefix={'kind'} suffix={resource.kind} />

            }
            avatar={<KindAbbreviation text={this.kubeKind.abbrev} color={this.kubeKind.color}/>}
            style={styles.cardHeader}
          >

          <ResourceInfoActionsMenu
            access={this.state.resourceAccess}
            handlers={{
              scale: this.handleScale,
              edit: () => {
                this.props.selectView('edit')
                this.setState({actionsOpen: false})
              },
              suspend: this.handleSuspend,
              delete: this.handleDelete,
            }}
            />

{resourceStatusIcons[resource.statusSummary]}

          </CardHeader>

          <Tabs
            style={{background: 'white'}}
            tabItemContainerStyle={styles.tabs}
            contentContainerStyle={{overflow: 'hidden'}}
            inkBarStyle={styles.tabsInkBar}
            className={`resource-tabs ${activeTab.replace(" ","-")}`}
            value={activeTab}
            ref={ (ref) => {
                if (!!ref) {
                  // get absolute bottom of the tab button bar so our tab contents can fill available space
                  let tabButtons = document.getElementsByClassName('resource-tabs')[0].children[0]
                  this.contentTop = tabButtons.getBoundingClientRect().bottom
                }
              }
            }
            >
            {tabs.map(tab => {
                let Component = tab.component
                return <Tab key={tab.name} icon={tab.icon} label={tab.name} value={tab.name} onActive={this.props.selectView.bind(this, tab.name)}>
                  <Component {...tab.props} contentTop={this.contentTop}/>
                </Tab>
              }
            )}
          </Tabs>
        </Card>

        <ConfirmationDialog
          open={this.state.deleteOpen}
          title={'Delete Resource(s):'}
          message={`Are you sure you want to delete the following resource?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseDelete}
          onConfirm={this.handleConfirmDelete}
          />

        <ConfirmationDialog
          open={this.state.suspendOpen}
          title={'Suspend Resource(s):'}
          message={`Are you sure you want to suspend the following resource (by scaling to 0 replicas)?`}
          resources={[this.props.resource]}
          onRequestClose={this.handleRequestCloseSuspend}
          onConfirm={this.handleConfirmSuspend}
          />

        <ScaleDialog
          open={this.state.scaleOpen}
          resource={this.props.resource}
          onRequestClose={this.handleRequestCloseScale}
          onConfirm={this.handleConfirmScale}
          />

    )
  }
})))
<|endoftext|>


<commit_before>'use strict';

const $ = require('jquery'),
      moment = require('moment'),
      tmplRow = require('../views/client/bookingCalendar-tmplRow.mustache'),
      tmplDate = require('../views/client/bookingCalendar-tmplDate.mustache'),
      tmplDay = require('../views/client/slotpicker-tmplDay.mustache');

moment.locale('en-au');

const defaults =  {
  days: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday'],
  months: ['January','February','March','April','May','June','July','August','September','October','November','December'],
  navPointer: 0
};

class SlotPicker {
  constructor(bookingData) {
    this.settings = defaults;
    this.settings.today = moment.utc();
    this.settings.bookingData = bookingData;
    this.settings.bookableDates = Object.keys(bookingData);
    this.settings.navMonths = this.setupNavMonths();
    this.updateNav();
    this.renderCalendar();
    this.bindEvents();
  }

  setupNavMonths() {
    let bookableMonths = [];
    let prevMonth = '', curDate;
    for (let date in this.settings.bookingData) {
      curDate = moment.utc(date);
      if(curDate.month() !== prevMonth) {
        bookableMonths.push(curDate);
        prevMonth = curDate.month();
      }
    }
    return bookableMonths;
  }

  updateNav() {
    if (this.settings.navPointer > 0) {
      $('.BookingCalendar-nav--prev').addClass('is-active');
    } else {
      $('.BookingCalendar-nav--prev').removeClass('is-active');
    }
    if (this.settings.navPointer + 1 < this.settings.navMonths.length) {
      $('.BookingCalendar-nav--next').addClass('is-active');
    } else {
      $('.BookingCalendar-nav--next').removeClass('is-active');
    }
  }

  renderCalendar() {
    for(let monthDate of this.settings.navMonths) {
      this.renderMonth(monthDate);
    }
    this.displayMonth(this.settings.navMonths[0].month());
  }

  renderMonth(monthDate) {
    let from = this.firstDayOfMonth(monthDate),
        to = this.lastDayOfMonth(monthDate);
    $('.BookingCalendar-datesBody').append(this.buildDates(tmplRow, tmplDate, from, to));
  }

  renderMonthLabel(month) {
    $('.BookingCalendar-currentMonth').text(this.settings.months[month]);
  }

  displayMonth(month) {
    $('.BookingCalendar-datesBody tr').hide();
    $('tr.CalRow.month-' + month).show();
    this.renderMonthLabel(month);
  }

  bindEvents() {
    var self = this;
    $('button.BookingCalendar-nav--next').click(() => {
      self.nudgeNav(1);
    });

    $('button.BookingCalendar-nav--prev').click(() => {
      self.nudgeNav(-1);
    });

    $('.BookingCalendar-dateLink').click(function() {
      self.highlightDate($( this ));
      let week = $(this).closest('tr').attr('data-week');
      let date = $(this).attr('data-date');
      document.querySelector(`tr.CalRow-slots[data-week='${week}']`).style.display='table-row';
      self.availableTimesForDate(date, () => {
        document.querySelector(`tr.CalRow-slots[data-week='${week}'] ul`).innerHTML = self.settings.bookingData[date].times;
      });
    });
  }

  highlightDate(day) {
    $('.BookingCalendar-date--bookable.is-active').removeClass('is-active');
    day.closest('td').addClass('is-active');
  }

  nudgeNav(i) {
    this.settings.navPointer = this.settings.navPointer + i;
    this.displayMonth(this.settings.navMonths[this.settings.navPointer].month());
    this.updateNav();
  }

  buildDates(templateRow, templateDate, from, to) {
    let out = '', row = '', curIso,
        displayAvailable='', dayStr = '',
        todayIso = this.formatIso(this.settings.today),
        count = 1,
        curDate = this.firstDayOfWeek(from),
        end = this.lastDayOfWeek(to);

    while (curDate <= end) {
      dayStr = this.settings.days[curDate.day()];
      curIso = this.formatIso(curDate);

      let className = this.dateBookable(this.formatIso(curDate), this.settings.bookableDates) ? 'BookingCalendar-date--bookable' : 'BookingCalendar-date--unavailable';
      if (curDate < from || curDate > to) {
        className += ' BookingCalendar-date--hide';
      }

      let bookable = this.settings.bookingData[this.formatIso(curDate)];
      if(bookable) {
        displayAvailable = `${bookable.available_slots_count} available`;
      } else {
        displayAvailable='';
      }

      row+= templateDate.render({
        date: curIso,
        day: curDate.date(),
        available: displayAvailable,
        today: curIso === todayIso,
        klass: className
      });

      if (count === 7) {
        out+= templateRow.render({
          week: curDate.week(),
          month: from.month(),
          cells: row
        });
        row = '';
        count = 0;
      }

      curDate.date(curDate.date() + 1);
      count++;
    }

    return out;
  }

  firstDayOfWeek(date) {
    return moment.utc(date).startOf('week');
  }

  firstDayOfMonth(date) {
    return moment.utc(date).startOf('month');
  }

  lastDayOfWeek(date) {
    return moment.utc(date).endOf('week');
  }

  lastDayOfMonth(date) {
    return moment.utc(date).endOf('month');
  }

  // HELPERS
  availableTimesForDate(date, callback) {
    if (!this.settings.bookingData[date].times) {
      let request = new XMLHttpRequest();
      request.open('GET', `get_available_times?date=${date}`);
      request.onload = () => {
        if (request.status === 200) {
          let availableTimes = JSON.parse(request.responseText).times;
          this.settings.bookingData[date].times = availableTimes;
          callback();
        } else {
          // FIXME(Emily)
        }
      };
      request.onerror = () => {
        // FIXME(Emily)
      };
      request.send();
    }
  }

  formatIso(date) {
    return date.format("YYYY-MM-DD");
  }

  dateBookable(date, dates) {
    return dates.indexOf(date) >= 0;
  }
};

module.exports = (bookingData) => {
  return new SlotPicker(bookingData);
};
<commit_msg>Use promise to fetch availableTimeswq<commit_after>'use strict';

const $ = require('jquery'),
      moment = require('moment'),
      tmplRow = require('../views/client/bookingCalendar-tmplRow.mustache'),
      tmplDate = require('../views/client/bookingCalendar-tmplDate.mustache'),
      tmplDay = require('../views/client/slotpicker-tmplDay.mustache');

moment.locale('en-au');

const defaults =  {
  days: ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday'],
  months: ['January','February','March','April','May','June','July','August','September','October','November','December'],
  navPointer: 0
};

class SlotPicker {
  constructor(bookingData) {
    this.settings = defaults;
    this.settings.today = moment.utc();
    for(let date of bookingData) {
        date.availableTimes: new Promise( function (resolve, reject) {
          let request = new XMLHttpRequest();
          request.open('GET', `get_available_times?date=${date}`);
          request.onload = () => {
            if (request.status === 200) {
              let availableTimes = JSON.parse(request.responseText).times;
              resolve(availableTimes);
            } else {
              reject(request.statusText);
            }
          };
          request.onerror = () => {
            reject("There was a problem sending the request.");
          };
          request.send();
        });
    }
    this.settings.bookingData = bookingData;
    this.settings.bookableDates = Object.keys(bookingData);
    this.settings.navMonths = this.setupNavMonths();
    this.updateNav();
    this.renderCalendar();
    this.bindEvents();
  }

  setupNavMonths() {
    let bookableMonths = [];
    let prevMonth = '', curDate;
    for (let date in this.settings.bookingData) {
      curDate = moment.utc(date);
      if(curDate.month() !== prevMonth) {
        bookableMonths.push(curDate);
        prevMonth = curDate.month();
      }
    }
    return bookableMonths;
  }

  updateNav() {
    if (this.settings.navPointer > 0) {
      $('.BookingCalendar-nav--prev').addClass('is-active');
    } else {
      $('.BookingCalendar-nav--prev').removeClass('is-active');
    }
    if (this.settings.navPointer + 1 < this.settings.navMonths.length) {
      $('.BookingCalendar-nav--next').addClass('is-active');
    } else {
      $('.BookingCalendar-nav--next').removeClass('is-active');
    }
  }

  renderCalendar() {
    for(let monthDate of this.settings.navMonths) {
      this.renderMonth(monthDate);
    }
    this.displayMonth(this.settings.navMonths[0].month());
  }

  renderMonth(monthDate) {
    let from = this.firstDayOfMonth(monthDate),
        to = this.lastDayOfMonth(monthDate);
    $('.BookingCalendar-datesBody').append(this.buildDates(tmplRow, tmplDate, from, to));
  }

  renderMonthLabel(month) {
    $('.BookingCalendar-currentMonth').text(this.settings.months[month]);
  }

  displayMonth(month) {
    $('.BookingCalendar-datesBody tr').hide();
    $('tr.CalRow.month-' + month).show();
    this.renderMonthLabel(month);
  }

  bindEvents() {
    var self = this;
    $('button.BookingCalendar-nav--next').click(() => {
      self.nudgeNav(1);
    });

    $('button.BookingCalendar-nav--prev').click(() => {
      self.nudgeNav(-1);
    });

    $('.BookingCalendar-dateLink').click(function() {
      self.highlightDate($( this ));
      let week = $(this).closest('tr').attr('data-week');
      let date = $(this).attr('data-date');
      document.querySelector(`tr.CalRow-slots[data-week='${week}']`).style.display='table-row';
      self.settings.bookingData[date].availableTimes.then((availableTimes) => {
        document.querySelector(`tr.CalRow-slots[data-week='${week}'] ul`).innerHTML =
        availableTimes;
      }, (err) => {
        // FIXME(Emily)
      });
    });
  }

  highlightDate(day) {
    $('.BookingCalendar-date--bookable.is-active').removeClass('is-active');
    day.closest('td').addClass('is-active');
  }

  nudgeNav(i) {
    this.settings.navPointer = this.settings.navPointer + i;
    this.displayMonth(this.settings.navMonths[this.settings.navPointer].month());
    this.updateNav();
  }

  buildDates(templateRow, templateDate, from, to) {
    let out = '', row = '', curIso,
        displayAvailable='', dayStr = '',
        todayIso = this.formatIso(this.settings.today),
        count = 1,
        curDate = this.firstDayOfWeek(from),
        end = this.lastDayOfWeek(to);

    while (curDate <= end) {
      dayStr = this.settings.days[curDate.day()];
      curIso = this.formatIso(curDate);

      let className = this.dateBookable(this.formatIso(curDate), this.settings.bookableDates) ? 'BookingCalendar-date--bookable' : 'BookingCalendar-date--unavailable';
      if (curDate < from || curDate > to) {
        className += ' BookingCalendar-date--hide';
      }

      let bookable = this.settings.bookingData[this.formatIso(curDate)];
      if(bookable) {
        displayAvailable = `${bookable.available_slots_count} available`;
      } else {
        displayAvailable='';
      }

      row+= templateDate.render({
        date: curIso,
        day: curDate.date(),
        available: displayAvailable,
        today: curIso === todayIso,
        klass: className
      });

      if (count === 7) {
        out+= templateRow.render({
          week: curDate.week(),
          month: from.month(),
          cells: row
        });
        row = '';
        count = 0;
      }

      curDate.date(curDate.date() + 1);
      count++;
    }

    return out;
  }

  firstDayOfWeek(date) {
    return moment.utc(date).startOf('week');
  }

  firstDayOfMonth(date) {
    return moment.utc(date).startOf('month');
  }

  lastDayOfWeek(date) {
    return moment.utc(date).endOf('week');
  }

  lastDayOfMonth(date) {
    return moment.utc(date).endOf('month');
  }

  // HELPERS

  formatIso(date) {
    return date.format("YYYY-MM-DD");
  }

  dateBookable(date, dates) {
    return dates.indexOf(date) >= 0;
  }
};

module.exports = (bookingData) => {
  return new SlotPicker(bookingData);
};
<|endoftext|>


<commit_before>/* Examples
 * */

ExampleEquations[0][0] = "";
ExampleEquations[0][1] = "";

ExampleEquations[1][0] = "";
ExampleEquations[1][1] = "";

ExampleEquations[2][0] = "";
ExampleEquations[2][1] = "";

ExampleEquations[3][0] = "";
ExampleEquations[3][1] = "";

ExampleEquations[4][0] = "";
ExampleEquations[4][1] = "";

ExampleEquations[5][0] = "";
ExampleEquations[5][1] = "";

ExampleEquations[6][0] = "";
ExampleEquations[6][1] = "";

ExampleEquations[7][0] = "";
ExampleEquations[7][1] = "";

ExampleEquations[8][0] = "";
ExampleEquations[8][1] = "";

ExampleEquations[9][0] = "";
ExampleEquations[9][1] = "";

ExampleEquations[10][0] = "";
ExampleEquations[10][1] = "";

ExampleEquations[11][0] = "";
ExampleEquations[11][1] = "";

ExampleEquations[12][0] = "";
ExampleEquations[12][1] = "";

ExampleEquations[13][0] = "";
ExampleEquations[13][1] = "";

ExampleEquations[14][0] = "";
ExampleEquations[14][1] = "";

ExampleEquations[15][0] = "";
ExampleEquations[15][1] = "";

ExampleEquations[16][0] = "";
ExampleEquations[16][1] = "";

ExampleEquations[17][0] = "";
ExampleEquations[17][1] = "";

ExampleEquations[18][0] = "";
ExampleEquations[18][1] = "";

ExampleEquations[19][0] = "";
ExampleEquations[19][1] = "";

ExampleEquations[20][0] = "";
ExampleEquations[20][1] = "";

ExampleEquations[21][0] = "";
ExampleEquations[21][1] = "";

ExampleEquations[22][0] = "";
ExampleEquations[22][1] = "";

ExampleEquations[23][0] = "";
ExampleEquations[23][1] = "";

ExampleEquations[24][0] = "";
ExampleEquations[24][1] = "";

ExampleEquations[25][0] = "";
ExampleEquations[25][1] = "";

ExampleEquations[26][0] = "";
ExampleEquations[26][1] = "";

ExampleEquations[27][0] = "";
ExampleEquations[27][1] = "";

ExampleEquations[28][0] = "";
ExampleEquations[28][1] = "";

ExampleEquations[29][0] = "";
ExampleEquations[29][1] = "";

ExampleEquations[30][0] = "";
ExampleEquations[30][1] = "";

ExampleEquations[31][0] = "";
ExampleEquations[31][1] = "";

ExampleEquations[32][0] = "";
ExampleEquations[32][1] = "";

ExampleEquations[33][0] = "";
ExampleEquations[33][1] = "";

ExampleEquations[34][0] = "";
ExampleEquations[34][1] = "";

ExampleEquations[35][0] = "";
ExampleEquations[35][1] = "";

ExampleEquations[36][0] = "";
ExampleEquations[36][1] = "";

ExampleEquations[37][0] = "";
ExampleEquations[37][1] = "";

ExampleEquations[38][0] = "";
ExampleEquations[38][1] = "";

ExampleEquations[39][0] = "";
ExampleEquations[39][1] = "";

ExampleEquations[40][0] = "";
ExampleEquations[40][1] = "";

ExampleEquations[41][0] = "";
ExampleEquations[41][1] = "";

ExampleEquations[42][0] = "";
ExampleEquations[42][1] = "";

ExampleEquations[43][0] = "";
ExampleEquations[43][1] = "";

ExampleEquations[44][0] = "";
ExampleEquations[44][1] = "";

ExampleEquations[45][0] = "";
ExampleEquations[45][1] = "";

ExampleEquations[46][0] = "";
ExampleEquations[46][1] = "";

ExampleEquations[47][0] = "";
ExampleEquations[47][1] = "";

ExampleEquations[48][0] = "";
ExampleEquations[48][1] = "";

ExampleEquations[49][0] = "";
ExampleEquations[49][1] = "";

<commit_msg>Updating example format<commit_after>/* Examples
 * */

Examples.push({
    name: "",
    fxn : ""
});

ExampleEquations[1][0] = "";
ExampleEquations[1][1] = "";

ExampleEquations[2][0] = "";
ExampleEquations[2][1] = "";

ExampleEquations[3][0] = "";
ExampleEquations[3][1] = "";

ExampleEquations[4][0] = "";
ExampleEquations[4][1] = "";

ExampleEquations[5][0] = "";
ExampleEquations[5][1] = "";

ExampleEquations[6][0] = "";
ExampleEquations[6][1] = "";

ExampleEquations[7][0] = "";
ExampleEquations[7][1] = "";

ExampleEquations[8][0] = "";
ExampleEquations[8][1] = "";

ExampleEquations[9][0] = "";
ExampleEquations[9][1] = "";

ExampleEquations[10][0] = "";
ExampleEquations[10][1] = "";

ExampleEquations[11][0] = "";
ExampleEquations[11][1] = "";

ExampleEquations[12][0] = "";
ExampleEquations[12][1] = "";

ExampleEquations[13][0] = "";
ExampleEquations[13][1] = "";

ExampleEquations[14][0] = "";
ExampleEquations[14][1] = "";

ExampleEquations[15][0] = "";
ExampleEquations[15][1] = "";

ExampleEquations[16][0] = "";
ExampleEquations[16][1] = "";

ExampleEquations[17][0] = "";
ExampleEquations[17][1] = "";

ExampleEquations[18][0] = "";
ExampleEquations[18][1] = "";

ExampleEquations[19][0] = "";
ExampleEquations[19][1] = "";

ExampleEquations[20][0] = "";
ExampleEquations[20][1] = "";

ExampleEquations[21][0] = "";
ExampleEquations[21][1] = "";

ExampleEquations[22][0] = "";
ExampleEquations[22][1] = "";

ExampleEquations[23][0] = "";
ExampleEquations[23][1] = "";

ExampleEquations[24][0] = "";
ExampleEquations[24][1] = "";

ExampleEquations[25][0] = "";
ExampleEquations[25][1] = "";

ExampleEquations[26][0] = "";
ExampleEquations[26][1] = "";

ExampleEquations[27][0] = "";
ExampleEquations[27][1] = "";

ExampleEquations[28][0] = "";
ExampleEquations[28][1] = "";

ExampleEquations[29][0] = "";
ExampleEquations[29][1] = "";

ExampleEquations[30][0] = "";
ExampleEquations[30][1] = "";

ExampleEquations[31][0] = "";
ExampleEquations[31][1] = "";

ExampleEquations[32][0] = "";
ExampleEquations[32][1] = "";

ExampleEquations[33][0] = "";
ExampleEquations[33][1] = "";

ExampleEquations[34][0] = "";
ExampleEquations[34][1] = "";

ExampleEquations[35][0] = "";
ExampleEquations[35][1] = "";

ExampleEquations[36][0] = "";
ExampleEquations[36][1] = "";

ExampleEquations[37][0] = "";
ExampleEquations[37][1] = "";

ExampleEquations[38][0] = "";
ExampleEquations[38][1] = "";

ExampleEquations[39][0] = "";
ExampleEquations[39][1] = "";

ExampleEquations[40][0] = "";
ExampleEquations[40][1] = "";

ExampleEquations[41][0] = "";
ExampleEquations[41][1] = "";

ExampleEquations[42][0] = "";
ExampleEquations[42][1] = "";

ExampleEquations[43][0] = "";
ExampleEquations[43][1] = "";

ExampleEquations[44][0] = "";
ExampleEquations[44][1] = "";

ExampleEquations[45][0] = "";
ExampleEquations[45][1] = "";

ExampleEquations[46][0] = "";
ExampleEquations[46][1] = "";

ExampleEquations[47][0] = "";
ExampleEquations[47][1] = "";

ExampleEquations[48][0] = "";
ExampleEquations[48][1] = "";

ExampleEquations[49][0] = "";
ExampleEquations[49][1] = "";

<|endoftext|>


<commit_before>document.addEventListener('DOMContentLoaded', function(event) {

    var farmers = [],
        markers = [],
        countries = [],
        farmerLegend = {},
        beatMe = [],
        rotation;

    var currentInfo = document.getElementsByTagName('ul')[0],
        body = document.getElementsByTagName('body')[0];

    var earth = new WE.map('earth_div', {
        sky: true,
        atmosphere: true
    });

    // This line adds support for MapQuest Aerial tiles
    WE.tileLayer('https://otile{s}-s.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg', {
        attribution: 'Tiles Courtesy of MapQuest.'
    }).addTo(earth);

    // Start a rotation animation
    rotation = setInterval(function() {
        var c = earth.getPosition();
        if (c[0] && c[1]) {
            earth.setCenter([c[0], c[1] + 0.1]);
        }
    }, 30);

    // Opening and closing the side menu
    document.getElementById('open-side-menu').addEventListener('click', function() {
        body.classList.toggle('open');
    }, false);

    // Maps all farmers to a random color and generates
    // an object of countries. Also keeps track of
    // heartbeat count.
    var generateLegend = function(farmers) {
        countries = [];
        beatMe = [];

        for (var j = 0; j < farmers.length; j++) {
            if (!(farmers[j].id in farmerLegend)) {
                farmerLegend[farmers[j].id] = {
                    color: randomColor(farmers[j].online),
                    beats: farmers[j].heartbeats
                };
            }

            // Add farmer id to beatMe list if heartbeats have incremented.
            if (farmers[j].heartbeats > farmerLegend[farmers[j].id].beats) {
                beatMe.push(farmers[j].id);
                farmerLegend[farmers[j].id].beats = farmers[j].heartbeats;
            }

            // Update country list.
            var country = farmers[j].location.country;
            if (countries.indexOf(country) === -1) {
                countries.push(country);
            }
        }
    }

    // This function sends an ajax get request to fetch
    // a list of farmers and coordinates, then renders
    // the globe.
    var getData = function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);
                farmers = data.farmers.filter(function(farmer) {
                    return Math.round(farmer.uptime) > 0;
                });
                generateLegend(farmers);
                render();
            }

        }
        xhr.open('GET', 'http://verify.driveshare.org/api/downstream/status/list/by/d/uptime');
        xhr.send();
    }

    // Return window width.
    function getWidth() {
        if (self.innerHeight) {
            return self.innerWidth;
        }

        if (document.documentElement && document.documentElement.clientHeight) {
            return document.documentElement.clientWidth;
        }

        if (document.body) {
            return document.body.clientWidth;
        }
    }

    // Fetch data for the globe once initially,
    // and then every 5 seconds.
    getData();
    setInterval(getData, 5000);

    // This function returns a random color. If node is offline, it will return
    // a random color with 50% opacity.
    var randomColor = function(isOnline) {
        return 'rgba(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ', ' + (isOnline ? 1 : 0.25) + ')';
    }

    // Returns white or black class depending on which
    // color contrasts more with submitted color.
    var invertColor = function(color) {
        // If color is hex-formatted, convert to RGB
        if (color.indexOf('#') !== -1) {
            var rgbArray = [hexToR(color), hexToG(color), hexToB(color)];
        } else {
            var rgbArray = color.replace('rgb(', '').replace(')', '').split(',');
        }

        // Calculate luminance.
        var L = (299 * rgbArray[0] + 587 * rgbArray[1] + 114 * rgbArray[2]) / 1000;

        // Return black or white, depending on luminance.
        return L > 130 ? 'highlightBlack' : 'highlightWhite';
    }

    // This function handles everything related to updating the HTML
    // with the returned data from the AJAX call:
    var render = function() {
        var entry;
        if (!farmers.length) {

            // If there isn't anybody online
            // Show an appropriate message and empty the summary and the list section
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'block';
            document.getElementsByClassName('.summary')[0].innerHTML = '';
        } else {
            // If there are people online:
            // On every call clear the side-menu list and globe markers and fill them up with new info.
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'none';

            var entriesContainer = document.createDocumentFragment();

            for (var i = 0; i < farmers.length; i++) {
                // Populate the side-menu with the farmers, ranked by uptime.
                var newEntry = document.createElement('li');
                newEntry.setAttribute('id', 'farmer' + (i + 1));
                newEntry.className = 'entry ' + (!farmers[i].online ? 'offline' : '');

                var newColorSpan = document.createElement('span');
                newColorSpan.className = 'color-effect';
                newColorSpan.style.backgroundColor = farmerLegend[farmers[i].id].color;
                newEntry.appendChild(newColorSpan);

                var newDiv = document.createElement('div');
                newDiv.className = 'farmerId';
                newDiv.innerHTML = farmers[i].address;
                newEntry.appendChild(newDiv);

                var newCountSpan = document.createElement('span');
                newCountSpan.className = 'count';
                newCountSpan.innerHTML = Math.round(farmers[i].uptime) + '%';
                newEntry.appendChild(newCountSpan)

                entriesContainer.appendChild(newEntry);
            }

            currentInfo.appendChild(entriesContainer);

            // Summary information
            document.getElementsByClassName('summary')[0].innerHTML = farmers.length + ' farmers / ' + countries.length + ' countries';
        }

        // Adding the markers to the globe.
        // First remove all existing markers from the globe and clear the array they are stored in.
        markers.forEach(function(b) {
            earth.removeMarker(b);
        });
        markers = [];

        // For each ip location create a new marker
        farmers.forEach(function(a) {
            var country = a.location.country;
            var city = a.location.city;
            var state = a.location.state;
            var numContracts = a.contracts;
            var heartbeats = a.heartbeats;
            var testFileSize = a.size;
            var firstBits = a.address.slice(0, 4);

            markers.push(WE.marker([a.location.lat, a.location.lon]).addTo(earth).bindPopup('Farmer ' + firstBits + '… from ' + (city ? city : country) + '' + (state ? ', ' + state + '' : '') + ' has ' + numContracts + ' contract(s) and passed ' + heartbeats + ' heartbeats with ' + testFileSize + '-byte test files.', {
                maxWidth: 150,
                maxHeight: 100,
                closeButton: true
            }));

            // Color the markers.
            var currentMarker = markers[markers.length - 1].element.firstChild;
            currentMarker.style.background = farmerLegend[a.id].color;
            if (beatMe.indexOf(a.id) > -1) {
                currentMarker.classList.add('beat');
            } else if (!a.online) {
                currentMarker.classList.add('offline');
            }
        });

        // Rotate the globe to the desired farmer from the list
        var entries = document.getElementsByClassName('entry');

        for (var i = 0; i < entries.length; i++) {
            entries[i].addEventListener('click', function() {
                if (getWidth() < 800) {
                    body.classList.remove('open');
                }
                clearInterval(rotation);
                var index = this.getAttribute('id').split('farmer')[1] - 1;
                earth.panTo([farmers[index].location.lat, farmers[index].location.lon]);
            });

            entries[i].addEventListener('mouseover', function() {
                this.classList.add(invertColor(this.getElementsByClassName('color-effect')[0].style.backgroundColor));
            });

            entries[i].addEventListener('mouseout', function() {
                this.classList.remove('highlightWhite');
                this.classList.remove('highlightBlack');
            });
        }
    }

    var earthDiv = document.getElementById('earth_div');
    ['click', 'touchstart', 'touchend'].forEach(function(event) {
        earthDiv.addEventListener(event, function() {
            clearInterval(rotation);
            if (getWidth() < 800) {
                body.classList.remove('open');
            }
        });
    });

    // Open the side menu on start up if the app is running on a big screen.
    if (getWidth() > 800) {
        body.classList.add('open');
    } else {
        earth.setZoom(1.8);
    }

});
<commit_msg>Try single server.<commit_after>document.addEventListener('DOMContentLoaded', function(event) {

    var farmers = [],
        markers = [],
        countries = [],
        farmerLegend = {},
        beatMe = [],
        rotation;

    var currentInfo = document.getElementsByTagName('ul')[0],
        body = document.getElementsByTagName('body')[0];

    var earth = new WE.map('earth_div', {
        sky: true,
        atmosphere: true
    });

    // This line adds support for MapQuest Aerial tiles
    WE.tileLayer('https://otile2-s.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg', {
        attribution: 'Tiles Courtesy of MapQuest.'
    }).addTo(earth);

    // Start a rotation animation
    rotation = setInterval(function() {
        var c = earth.getPosition();
        if (c[0] && c[1]) {
            earth.setCenter([c[0], c[1] + 0.1]);
        }
    }, 30);

    // Opening and closing the side menu
    document.getElementById('open-side-menu').addEventListener('click', function() {
        body.classList.toggle('open');
    }, false);

    // Maps all farmers to a random color and generates
    // an object of countries. Also keeps track of
    // heartbeat count.
    var generateLegend = function(farmers) {
        countries = [];
        beatMe = [];

        for (var j = 0; j < farmers.length; j++) {
            if (!(farmers[j].id in farmerLegend)) {
                farmerLegend[farmers[j].id] = {
                    color: randomColor(farmers[j].online),
                    beats: farmers[j].heartbeats
                };
            }

            // Add farmer id to beatMe list if heartbeats have incremented.
            if (farmers[j].heartbeats > farmerLegend[farmers[j].id].beats) {
                beatMe.push(farmers[j].id);
                farmerLegend[farmers[j].id].beats = farmers[j].heartbeats;
            }

            // Update country list.
            var country = farmers[j].location.country;
            if (countries.indexOf(country) === -1) {
                countries.push(country);
            }
        }
    }

    // This function sends an ajax get request to fetch
    // a list of farmers and coordinates, then renders
    // the globe.
    var getData = function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);
                farmers = data.farmers.filter(function(farmer) {
                    return Math.round(farmer.uptime) > 0;
                });
                generateLegend(farmers);
                render();
            }

        }
        xhr.open('GET', 'http://verify.driveshare.org/api/downstream/status/list/by/d/uptime');
        xhr.send();
    }

    // Return window width.
    function getWidth() {
        if (self.innerHeight) {
            return self.innerWidth;
        }

        if (document.documentElement && document.documentElement.clientHeight) {
            return document.documentElement.clientWidth;
        }

        if (document.body) {
            return document.body.clientWidth;
        }
    }

    // Fetch data for the globe once initially,
    // and then every 5 seconds.
    getData();
    setInterval(getData, 5000);

    // This function returns a random color. If node is offline, it will return
    // a random color with 50% opacity.
    var randomColor = function(isOnline) {
        return 'rgba(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ', ' + (isOnline ? 1 : 0.25) + ')';
    }

    // Returns white or black class depending on which
    // color contrasts more with submitted color.
    var invertColor = function(color) {
        // If color is hex-formatted, convert to RGB
        if (color.indexOf('#') !== -1) {
            var rgbArray = [hexToR(color), hexToG(color), hexToB(color)];
        } else {
            var rgbArray = color.replace('rgb(', '').replace(')', '').split(',');
        }

        // Calculate luminance.
        var L = (299 * rgbArray[0] + 587 * rgbArray[1] + 114 * rgbArray[2]) / 1000;

        // Return black or white, depending on luminance.
        return L > 130 ? 'highlightBlack' : 'highlightWhite';
    }

    // This function handles everything related to updating the HTML
    // with the returned data from the AJAX call:
    var render = function() {
        var entry;
        if (!farmers.length) {

            // If there isn't anybody online
            // Show an appropriate message and empty the summary and the list section
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'block';
            document.getElementsByClassName('.summary')[0].innerHTML = '';
        } else {
            // If there are people online:
            // On every call clear the side-menu list and globe markers and fill them up with new info.
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'none';

            var entriesContainer = document.createDocumentFragment();

            for (var i = 0; i < farmers.length; i++) {
                // Populate the side-menu with the farmers, ranked by uptime.
                var newEntry = document.createElement('li');
                newEntry.setAttribute('id', 'farmer' + (i + 1));
                newEntry.className = 'entry ' + (!farmers[i].online ? 'offline' : '');

                var newColorSpan = document.createElement('span');
                newColorSpan.className = 'color-effect';
                newColorSpan.style.backgroundColor = farmerLegend[farmers[i].id].color;
                newEntry.appendChild(newColorSpan);

                var newDiv = document.createElement('div');
                newDiv.className = 'farmerId';
                newDiv.innerHTML = farmers[i].address;
                newEntry.appendChild(newDiv);

                var newCountSpan = document.createElement('span');
                newCountSpan.className = 'count';
                newCountSpan.innerHTML = Math.round(farmers[i].uptime) + '%';
                newEntry.appendChild(newCountSpan)

                entriesContainer.appendChild(newEntry);
            }

            currentInfo.appendChild(entriesContainer);

            // Summary information
            document.getElementsByClassName('summary')[0].innerHTML = farmers.length + ' farmers / ' + countries.length + ' countries';
        }

        // Adding the markers to the globe.
        // First remove all existing markers from the globe and clear the array they are stored in.
        markers.forEach(function(b) {
            earth.removeMarker(b);
        });
        markers = [];

        // For each ip location create a new marker
        farmers.forEach(function(a) {
            var country = a.location.country;
            var city = a.location.city;
            var state = a.location.state;
            var numContracts = a.contracts;
            var heartbeats = a.heartbeats;
            var testFileSize = a.size;
            var firstBits = a.address.slice(0, 4);

            markers.push(WE.marker([a.location.lat, a.location.lon]).addTo(earth).bindPopup('Farmer ' + firstBits + '… from ' + (city ? city : country) + '' + (state ? ', ' + state + '' : '') + ' has ' + numContracts + ' contract(s) and passed ' + heartbeats + ' heartbeats with ' + testFileSize + '-byte test files.', {
                maxWidth: 150,
                maxHeight: 100,
                closeButton: true
            }));

            // Color the markers.
            var currentMarker = markers[markers.length - 1].element.firstChild;
            currentMarker.style.background = farmerLegend[a.id].color;
            if (beatMe.indexOf(a.id) > -1) {
                currentMarker.classList.add('beat');
            } else if (!a.online) {
                currentMarker.classList.add('offline');
            }
        });

        // Rotate the globe to the desired farmer from the list
        var entries = document.getElementsByClassName('entry');

        for (var i = 0; i < entries.length; i++) {
            entries[i].addEventListener('click', function() {
                if (getWidth() < 800) {
                    body.classList.remove('open');
                }
                clearInterval(rotation);
                var index = this.getAttribute('id').split('farmer')[1] - 1;
                earth.panTo([farmers[index].location.lat, farmers[index].location.lon]);
            });

            entries[i].addEventListener('mouseover', function() {
                this.classList.add(invertColor(this.getElementsByClassName('color-effect')[0].style.backgroundColor));
            });

            entries[i].addEventListener('mouseout', function() {
                this.classList.remove('highlightWhite');
                this.classList.remove('highlightBlack');
            });
        }
    }

    var earthDiv = document.getElementById('earth_div');
    ['click', 'touchstart', 'touchend'].forEach(function(event) {
        earthDiv.addEventListener(event, function() {
            clearInterval(rotation);
            if (getWidth() < 800) {
                body.classList.remove('open');
            }
        });
    });

    // Open the side menu on start up if the app is running on a big screen.
    if (getWidth() > 800) {
        body.classList.add('open');
    } else {
        earth.setZoom(1.8);
    }

});
<|endoftext|>


<commit_before>//nativescript-effects

var viewModule = require("ui/core/view");
var typesModule = require("utils/types");
var definition = require("./tns-effects");

var Effects = {
    defaultDuration : 400,
    defaultSlideDistance: -100,
    defaultFloatDirection: 'up',
    presetDirections: {
        'up': {x: 0, y: 100},
        'down': {x: 0, y: -100},
        'left': {x: 100, y: 0},
        'right': {x: -100, y: 0}
    },
    presetDurations: {
        'fast' : 200,
        'slow' : 600
    },
    getMsValue: function(duration) {
        if (typesModule.isString(duration)) {
            if (typesModule.isDefined(Effects.presetDurations[duration])) {
                return Effects.presetDurations[duration];
            }
        }
        else if (typesModule.isNumber(duration)) {
            return duration;
        }
        else {
            return this.defaultDuration;
        }
    }
};
exports.ViewEffects = Effects;

viewModule.View.prototype.fadeIn = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: 1,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeOut = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
	var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: 0,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeTo = function(duration, opacity) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (opacity === void 0) { opacity = 1; }
    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: opacity,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeToggle = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (this.opacity > 0) {
        this.fadeOut(duration);
    }
    else {
        this.fadeIn(duration);
    }
};

//.floatIn( [duration ]  )
//.floatIn( options )
viewModule.View.prototype.floatIn = function(duration, direction) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (direction === void 0) { direction = Effects.defaultFloatDirection; }
    var self = this;
    var msDuration = Effects.getMsValue(duration);
    var dir = Effects.presetDirections[direction];
    if (!dir) {
        dir = Effects.presetDirections['up'];
    }

    var promiseSetup = self.animate({
        translate: { x: dir.x, y: dir.y },
        opacity: 0,
        duration: 1
    });

    return promiseSetup.then(function() {
        return self.animate({
            translate: { x: 0, y: 0 },
            opacity: 1,
            duration: msDuration
        });
    });
};

viewModule.View.prototype.floatOut = function(duration, direction) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (direction === void 0) { direction = Effects.defaultFloatDirection; }
    var msDuration = Effects.getMsValue(duration);
    var dir = Effects.presetDirections[direction];
    if (!dir) {
        dir = Effects.presetDirections['down'];
    }
    return this.animate({
        translate: { x: dir.x, y: dir.y },
        opacity: 0,
        duration: msDuration
    });
};

//.hide()
//.hide( [duration ] )
//.hide( options )
viewModule.View.prototype.hide = function(duration) {
    if (duration === void 0) { duration = 1; }
    this.fadeOut(duration);
};

viewModule.View.prototype.show = function(duration) {
    if (duration === void 0) { duration = 1; }
    this.fadeIn(duration);
};

viewModule.View.prototype.toggle = function(duration) {
    if (duration === void 0) { duration = 1; }
    this.fadeToggle(duration);
};

//.slideDown( [duration ]  )
//.slideDown( options )
viewModule.View.prototype.slideDown = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }
    var self = this;

    var msDuration = Effects.getMsValue(duration);

    this.translateY = distance;
    this.opacity = 0;

    var promiseSetup = self.animate({
        translate: { x: 0, y: distance },
        opacity: 0,
        duration: 1
    });

    return promiseSetup.then(function() {
        return self.animate({
            translate: { x: 0, y: 0 },
            opacity: 1,
            duration: msDuration
        });
    });
};

viewModule.View.prototype.slideUp = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }

    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        translate: { x: 0, y: distance },
        opacity: 0,
        duration: msDuration
    });
};

viewModule.View.prototype.slideToggle = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }

    if (this.opacity > 0) {
        this.slideUp(duration, distance);
    }
    else {
        this.slideDown(duration, distance);
    }
};

viewModule.View.prototype.spring = function(duration, animation) {
    if (duration === void 0) { duration = Effects.defaultDuration; }

    var msDuration = Effects.getMsValue(duration);
    if (!animation) {
        animation = {
                translate: {
                    x: 0,
                    y: -100
                },
                scale: {
                    x: 2,
                    y: 2
                },
                duration: msDuration,
                delay: 0,
                dampingRatio: 0.3,
                velocity: 2.0,
                options: null
            };
    }
    else {
        animation.duration = msDuration;
    }

    var fx = new definition.ViewEffects(this);
    return fx.nativeSpring(animation);
}<commit_msg>fixed bug - not returning promise in all cases<commit_after>//nativescript-effects

var viewModule = require("ui/core/view");
var typesModule = require("utils/types");
var definition = require("./tns-effects");

var Effects = {
    defaultDuration : 400,
    defaultSlideDistance: -100,
    defaultFloatDirection: 'up',
    presetDirections: {
        'up': {x: 0, y: 100},
        'down': {x: 0, y: -100},
        'left': {x: 100, y: 0},
        'right': {x: -100, y: 0}
    },
    presetDurations: {
        'fast' : 200,
        'slow' : 600
    },
    getMsValue: function(duration) {
        if (typesModule.isString(duration)) {
            if (typesModule.isDefined(Effects.presetDurations[duration])) {
                return Effects.presetDurations[duration];
            }
        }
        else if (typesModule.isNumber(duration)) {
            return duration;
        }
        else {
            return this.defaultDuration;
        }
    }
};
exports.ViewEffects = Effects;

viewModule.View.prototype.fadeIn = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: 1,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeOut = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
	var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: 0,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeTo = function(duration, opacity) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (opacity === void 0) { opacity = 1; }
    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        opacity: opacity,
        duration: msDuration
    });
};

viewModule.View.prototype.fadeToggle = function(duration) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (this.opacity > 0) {
        return this.fadeOut(duration);
    }
    else {
        return this.fadeIn(duration);
    }
};

//.floatIn( [duration ]  )
//.floatIn( options )
viewModule.View.prototype.floatIn = function(duration, direction) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (direction === void 0) { direction = Effects.defaultFloatDirection; }
    var self = this;
    var msDuration = Effects.getMsValue(duration);
    var dir = Effects.presetDirections[direction];
    if (!dir) {
        dir = Effects.presetDirections['up'];
    }

    var promiseSetup = self.animate({
        translate: { x: dir.x, y: dir.y },
        opacity: 0,
        duration: 1
    });

    return promiseSetup.then(function() {
        return self.animate({
            translate: { x: 0, y: 0 },
            opacity: 1,
            duration: msDuration
        });
    });
};

viewModule.View.prototype.floatOut = function(duration, direction) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (direction === void 0) { direction = Effects.defaultFloatDirection; }
    var msDuration = Effects.getMsValue(duration);
    var dir = Effects.presetDirections[direction];
    if (!dir) {
        dir = Effects.presetDirections['down'];
    }
    return this.animate({
        translate: { x: dir.x, y: dir.y },
        opacity: 0,
        duration: msDuration
    });
};

//.hide()
//.hide( [duration ] )
//.hide( options )
viewModule.View.prototype.hide = function(duration) {
    if (duration === void 0) { duration = 1; }
    return this.fadeOut(duration);
};

viewModule.View.prototype.show = function(duration) {
    if (duration === void 0) { duration = 1; }
    return this.fadeIn(duration);
};

viewModule.View.prototype.toggle = function(duration) {
    if (duration === void 0) { duration = 1; }
    return this.fadeToggle(duration);
};

//.slideDown( [duration ]  )
//.slideDown( options )
viewModule.View.prototype.slideDown = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }
    var self = this;

    var msDuration = Effects.getMsValue(duration);

    this.translateY = distance;
    this.opacity = 0;

    var promiseSetup = self.animate({
        translate: { x: 0, y: distance },
        opacity: 0,
        duration: 1
    });

    return promiseSetup.then(function() {
        return self.animate({
            translate: { x: 0, y: 0 },
            opacity: 1,
            duration: msDuration
        });
    });
};

viewModule.View.prototype.slideUp = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }

    var msDuration = Effects.getMsValue(duration);
    return this.animate({
        translate: { x: 0, y: distance },
        opacity: 0,
        duration: msDuration
    });
};

viewModule.View.prototype.slideToggle = function(duration, distance) {
    if (duration === void 0) { duration = Effects.defaultDuration; }
    if (distance === void 0) { distance = Effects.defaultSlideDistance; }

    if (this.opacity > 0) {
        return this.slideUp(duration, distance);
    }
    else {
        return this.slideDown(duration, distance);
    }
};

viewModule.View.prototype.spring = function(duration, animation) {
    if (duration === void 0) { duration = Effects.defaultDuration; }

    var msDuration = Effects.getMsValue(duration);
    if (!animation) {
        animation = {
                translate: {
                    x: 0,
                    y: -100
                },
                scale: {
                    x: 2,
                    y: 2
                },
                duration: msDuration,
                delay: 0,
                dampingRatio: 0.3,
                velocity: 2.0,
                options: null
            };
    }
    else {
        animation.duration = msDuration;
    }

    var fx = new definition.ViewEffects(this);
    return fx.nativeSpring(animation);
}<|endoftext|>


<commit_before>function interview()
{
	alert("example");
}<commit_msg>Archivo Eliminado<commit_after><|endoftext|>


<commit_before>// orbital.js
// Orbital camera
//

var util = require("../util");

var ms = function() {
    return (new Date()).getTime();
};

var OrbitalCamera = function(elem, renderer, f, props) {
    this.elem = elem;
    this.f = f;

    var propOrDefault = function(prop, def) {
        if (!props || !props[prop])
            return def;

        return props[prop];
    };

    this.renderer = renderer;   // renderer to query points from
	this.maxDistance = propOrDefault("maxDistance", 400);
    this.distance = propOrDefault("distance", this.maxDistance);
    this.target = propOrDefault("target", [0, 0, 0]);    // the intial target
    this.elevation = propOrDefault("elevation", 40);        // the elevation from Y plane
    this.azimuth = propOrDefault("azimuth", 180);         // the angle of rotation around Y axis

    this.minDistance = 1.0;

    this._bindHandlers();

    this.animFrameId = null;
    this.nextUpdateOverrides = null;
    this.isWaitingUpdate = false;

    var o = this;

    setTimeout(function() {
        o._emitCameraProps();
    });
};

var torad = function(a) {
    return Math.PI * a / 180.0;
};

OrbitalCamera.prototype._att = function() {
	// tells us based on what our max and min distances are, what attentuation factor applies
	// based on this.distance;

	// normalize f to 0 -> 9 range, so that +1 gets it to 10 and we can do a log on it and get a nice 0 -> 1 range
	var f = (this.distance - this.minDistance) / (this.maxDistance - this.minDistance);
	return f;
};

OrbitalCamera.prototype._range = function() {
	// tells us based on what our max and min distances are, what attentuation factor applies
	// based on this.distance;

	return this.maxDistance - this.minDistance;
};

OrbitalCamera.prototype.serialize = function() {
    return {
        distance: this.distance,
	    maxDistance: this.maxDistance,
        target: this.target,
        elevation: this.elevation,
        azimuth: this.azimuth
    };
};

OrbitalCamera.prototype.applyState = function(state) {
	state = state || {};

	var o = this;
	var s = function(prop) {
		return state[prop] ? state[prop] : o[prop];
	};

	this.distance = s('distance');
	this.maxDistance = s('maxDistance');
	this.target = s('target');
	this.elevation = s('elevation');
	this.azimuth = s('azimuth');

	// update next time we get a chance
	setTimeout(function() {
		o._emitCameraProps();
	});
};

OrbitalCamera.prototype.stop = function() {
    this._unbindHandlers();
};

OrbitalCamera.prototype.setHint = function(range) {
    // reset the camera properties and distance based on the range we
    // got here
    //
    var dist = Math.sqrt((range[0] / 2) * (range[0] / 2) +
                         (range[1] / 2) * (range[1] / 2));

    this.distance = dist;
    this.target = [0, 0, 0];    // always right in the middle

    this.maxDistance = this.distance;

    // unwound stack call
    var o = this;
    setTimeout(function() {
        o._emitCameraProps();
    });
};

OrbitalCamera.prototype._cameraProps = (function() {
    var right = vec3.create();
    var qe = quat.create();
    var qa = quat.create();
    var qr = quat.create();

    var up = [0, 1, 0];
    var dir = [0, 0, 1];

    var loc = [0, 0, 0];
    var eye = [0, 0, 0];

    return function(overrides) {
        // emits the camera properties to event handler, with optional overrides
        // if available

        overrides = overrides || {};

        var elevation = overrides.elevation || this.elevation;
        var azimuth = overrides.azimuth || this.azimuth;
        var distance = overrides.distance || this.distance;

        var target = overrides.target || this.target;

        // we need two rotations to figure out where the eye is going to be,
        // we first rotate around the Y axis using the azimuth
        //
        vec3.cross(right, dir, up);

        // figure out the quat rotations for the
        quat.setAxisAngle(qe, right, torad(elevation));
        quat.setAxisAngle(qa, up, torad(azimuth));

        quat.multiply(qr, qa, qe);

        loc[2] = distance;
        vec3.transformQuat(eye, loc, qr);

        // now translate the eye by where the target is
        return [vec3.add(eye, target, eye), target];
    };
})();

OrbitalCamera.prototype._emitCameraProps = function(overrides) {
    this.nextUpdateOverrides = overrides;
    if (!this.isWaitingUpdate) {
        this.isWaitingUpdate = true;
        var o = this;
        requestAnimationFrame(function() {
            var p = o._cameraProps(o.nextUpdateOverrides);
            o.isWaitingUpdate = false;
            o.f(p[0], p[1]);
        });
    }
};

OrbitalCamera.prototype._transitionTargetTo = function(newTarget, delay) {
    // cancel any animations which may be going on right now
    if (this.animFrameId !== null) {
        cancelAnimationFrame(o.animFrameId);
        o.animFrameId = null;
    }

    var easef = function (t) { return t<0.5 ? 2*t*t : -1+(4-2*t)*t; };

    delay = delay || 500;

    var st = this.target;
    var et = newTarget;

    var stime = ms();

    var o = this;
    var af = function() {
        var ttime = ms();
        if (ttime >= stime + delay) {
            o.target = newTarget;
            o.animFrameId = null;

            // send properties of the newly set stuff
            o._emitCameraProps();
        } else {
            var ff = (ttime - stime) / delay;

            ff = easef(ff);

            var x = st[0] + (et[0] - st[0]) * ff;
            var y = st[1] + (et[1] - st[1]) * ff;
            var z = st[2] + (et[2] - st[2]) * ff;

            o._emitCameraProps({target: [x, y, z]});

            o.animFrameId = requestAnimationFrame(af);
        }
    };

    af();
};

var pointInSpace = function(o, evt) {
    var w = o.elem.offsetWidth,
        h = o.elem.offsetHeight;

	var x = evt.offsetX==undefined?evt.layerX:evt.offsetX;
	var y = evt.offsetY==undefined?evt.layerY:evt.offsetY;

    // pick a point in renderer
    //
    var p = o.renderer.pickPoint(x, y);

    console.log("Renderer returned:", p);

    return p;
};

OrbitalCamera.prototype._bindHandlers = function() {
    var o = this;

    // TODO: stop this stuff and do a eye position based on two
    // parameters a: angle along X axis and b: angle around Y axis
    // and compute the position of eye on the fly

    // double click handling
    //
    var ondblclick = function(evt) {
        o._transitionTargetTo(pointInSpace(o, evt));
    };

    var oncontextmenu = function(e) {
        e.preventDefault();
        return false;
    };

    // click and drag handler
    //
    var dragstate = {};
    var onmousedown = function(e) {
        e.preventDefault();

        var buttonId = e.which || e.button;

        dragstate.ispanning = (buttonId === 3);

        if (dragstate.ispanning) {
            dragstate.start_target = o.target;
            dragstate.start_pos = pointInSpace(o, e);

            // we need to figure the axis we'd be moving along
            var props = o._cameraProps();
            var eye = props[0];
            var target = props[1];

            var targetToEye = vec3.subtract([], eye, target);
            vec3.normalize(targetToEye, targetToEye); // normalized vector from target to eye

            var rightVec = vec3.cross([], [0, 1, 0], targetToEye); // vector to the right
            var dir = vec3.cross([], rightVec, [0, 1, 0]);         // vector towards the user projected

            dragstate.lr = rightVec;
            dragstate.fb = dir;

            dragstate.distance = o.distance;
        }
        else {
            dragstate.start_e = o.elevation;
            dragstate.start_a = o.azimuth;
        }

        dragstate.active = true;
        dragstate.x = e.clientX;
        dragstate.y = e.clientY;
        dragstate.wasMoved = false;

        // attach handlers to document so that we're notified when the mouse goes
        // out of the window
        var onmousemove = function(e) {
            e.preventDefault();

            if (!dragstate.active) return; // nothing to do here

            dragstate.wasMoved = true;
            var dx = e.clientX - dragstate.x;
            var dy = e.clientY - dragstate.y;

            if (dragstate.ispanning) {
                // when panning, change the actual 3D position of the target
                //
                var nf = Math.max(0.01, o._att() * o._range() * 0.001);

                // figure out the displacement based on the two vectors
                var p = vec3.add([],
                                 vec3.scale([], dragstate.lr, -dx * nf),
                                 vec3.scale([], dragstate.fb, -dy * nf));

                vec3.add(p, p, dragstate.start_target);

                //o._transitionTargetTo(p, 50);
                o._emitCameraProps({
                    target: p
                });

                dragstate.last_position = p;
            }
            else {
                var fac = 0.01;
                var ddx = (dx * fac) * (dx * fac) * (dx < 0 ? -1 : 1);
                var ddy = (dy * fac) * (dy * fac) * (dy < 0 ? -1 : 1);

                var this_e = dragstate.start_e + ddy;
                var this_a = dragstate.start_a - ddx;

                // limit the elevation
                this_e = Math.max(1, Math.min(80, this_e));

                o._emitCameraProps({
                    elevation: this_e,
                    azimuth: this_a
                });

                // store them for final application when the mouseclick is released
                dragstate.last_e = this_e;
                dragstate.last_a = this_a;
            }
        };

        var norm = function(a) {
            // normalize azimuth
            if (a > 180) a = a - 360;
            if (a < -180) a = a + 360;

            return a;
        };

        var onmouseup = function(e) {
            e.preventDefault();

            dragstate.active = false;
            if (dragstate.wasMoved) {
                if (dragstate.ispanning) {
                    o.target = dragstate.last_position;
                }
                else {
                    o.azimuth = norm(dragstate.last_a);
                    o.elevation = dragstate.last_e;
                }

                o._emitCameraProps();
            }

            document.removeEventListener("mousemove", onmousemove);
            document.removeEventListener("mouseup", onmouseup);
        };

        document.addEventListener("mousemove", onmousemove);
        document.addEventListener("mouseup", onmouseup);
    };

    var isWindows = function() {
        return navigator.platform.indexOf("Win") === 0;
    };

    var isChrome = function() {
        var isChromium = window.chrome,
            vendorName = window.navigator.vendor;
        return (isChromium !== null && isChromium !== undefined && vendorName === "Google Inc.");
    };

    var onmousewheel = function(e) {
        e.preventDefault();
        var m = e.wheelDelta || (-120 * e.detail);
	    var r = o._range() / 100;

        var amp = 1.0; // no amplification by default

        // not sure why but sensitivity on windows in chrome is pretty terrible
        if (isWindows() && isChrome()) {
            amp = 5.0;
        }

        var d = 0.01 * amp * m * r * (o.distance / o.maxDistance);
        o.distance -= d;

        o.distance = Math.min(Math.max(o.minDistance, o.distance), o.maxDistance);

        if (o.animFrameId === null)
            o._emitCameraProps();
    };

    this.elem.addEventListener("dblclick", ondblclick);
    this.elem.addEventListener("contextmenu", oncontextmenu);
    this.elem.addEventListener("mousedown", onmousedown);
    this.elem.addEventListener("mousewheel", onmousewheel);
    this.elem.addEventListener("DOMMouseScroll", onmousewheel);

    var o = this;
    this._unbind = function() {
        o.elem.removeEventListener("dblclick", ondblclick);
        o.elem.removeEventListener("contextmenu", oncontextmenu);
        o.elem.removeEventListener("mousedown", onmousedown);
        o.elem.removeEventListener("mousewheel", onmousewheel);
        o.elem.removeEventListener("DOMMouseScroll", onmousewheel);
    };
};

OrbitalCamera.prototype._unbindHandlers = function() {
    if (this._unbind) {
        this._unbind();
        this._unbind = null;
    }
};

module.exports = {
    Orbital: OrbitalCamera
};
<commit_msg>Updates to orbital camera<commit_after>// orbital.js
// Orbital camera
//

var util = require("../util");

var ms = function() {
    return (new Date()).getTime();
};

var OrbitalCamera = function(elem, renderer, f, props) {
    this.elem = elem;
    this.f = f;

    var propOrDefault = function(prop, def) {
        if (!props || !props[prop])
            return def;

        return props[prop];
    };

    this.renderer = renderer;   // renderer to query points from
	this.maxDistance = propOrDefault("maxDistance", 400);
    this.distance = propOrDefault("distance", this.maxDistance);
    this.target = propOrDefault("target", [0, 0, 0]);    // the intial target
    this.elevation = propOrDefault("elevation", 40);        // the elevation from Y plane
    this.azimuth = propOrDefault("azimuth", 0);         // the angle of rotation around Y axis

    this.minDistance = 1.0;

    this._bindHandlers();

    this.animFrameId = null;
	this.headingAnimFrameId = null;

    this.nextUpdateOverrides = null;
    this.isWaitingUpdate = false;

    var o = this;

    setTimeout(function() {
        o._emitCameraProps();
    });
};

var torad = function(a) {
    return Math.PI * a / 180.0;
};

OrbitalCamera.prototype._att = function() {
	// tells us based on what our max and min distances are, what attentuation factor applies
	// based on this.distance;

	// normalize f to 0 -> 9 range, so that +1 gets it to 10 and we can do a log on it and get a nice 0 -> 1 range
	var f = (this.distance - this.minDistance) / (this.maxDistance - this.minDistance);
	return f;
};

OrbitalCamera.prototype._range = function() {
	// tells us based on what our max and min distances are, what attentuation factor applies
	// based on this.distance;

	return this.maxDistance - this.minDistance;
};

OrbitalCamera.prototype.serialize = function() {
    return {
        distance: this.distance,
	    maxDistance: this.maxDistance,
        target: this.target,
        elevation: this.elevation,
        azimuth: this.azimuth
    };
};

OrbitalCamera.prototype.applyState = function(state) {
	state = state || {};

	var o = this;
	var s = function(prop) {
		return state[prop] ? state[prop] : o[prop];
	};

	this.distance = s('distance');
	this.maxDistance = s('maxDistance');
	this.target = s('target');
	this.elevation = s('elevation');
	this.azimuth = s('azimuth');

	// update next time we get a chance
	setTimeout(function() {
		o._emitCameraProps();
	});
};

OrbitalCamera.prototype.stop = function() {
    this._unbindHandlers();
};

OrbitalCamera.prototype.setHint = function(range) {
    // reset the camera properties and distance based on the range we
    // got here
    //
    var dist = Math.sqrt((range[0] / 2) * (range[0] / 2) +
                         (range[1] / 2) * (range[1] / 2));

    this.distance = dist;
    this.target = [0, 0, 0];    // always right in the middle

    this.maxDistance = this.distance;

    // unwound stack call
    var o = this;
    setTimeout(function() {
        o._emitCameraProps();
    });
};

OrbitalCamera.prototype._cameraProps = (function() {
    var right = vec3.create();
    var qe = quat.create();
    var qa = quat.create();
    var qr = quat.create();

    var up = [0, 1, 0];
    var dir = [0, 0, 1];

    var loc = [0, 0, 0];
    var eye = [0, 0, 0];

    return function(overrides) {
        // emits the camera properties to event handler, with optional overrides
        // if available

        overrides = overrides || {};

        var elevation = overrides.elevation || this.elevation;
        var azimuth = overrides.azimuth || this.azimuth;
        var distance = overrides.distance || this.distance;

        var target = overrides.target || this.target;

        // we need two rotations to figure out where the eye is going to be,
        // we first rotate around the Y axis using the azimuth
        //
        vec3.cross(right, dir, up);

        // figure out the quat rotations for the
        quat.setAxisAngle(qe, right, torad(elevation));
        quat.setAxisAngle(qa, up, torad(azimuth + 180));

        quat.multiply(qr, qa, qe);

        loc[2] = distance;
        vec3.transformQuat(eye, loc, qr);

        // now translate the eye by where the target is
        return [vec3.add(eye, target, eye), target];
    };
})();

OrbitalCamera.prototype._emitCameraProps = function(overrides) {
	console.log("emitting!");

    this.nextUpdateOverrides = overrides;
    if (!this.isWaitingUpdate) {
        this.isWaitingUpdate = true;
        var o = this;
        requestAnimationFrame(function() {
            var p = o._cameraProps(o.nextUpdateOverrides);
            o.isWaitingUpdate = false;
            o.f(p[0], p[1]);
        });
    }
};

var easef = function (t) { return t<0.5 ? 2*t*t : -1+(4-2*t)*t; };

OrbitalCamera.prototype._transitionTargetTo = function(newTarget, delay) {
    // cancel any animations which may be going on right now
    if (this.animFrameId !== null) {
        cancelAnimationFrame(this.animFrameId);
        this.animFrameId = null;
    }

    delay = delay || 500;

    var st = this.target;
    var et = newTarget;

    var stime = ms();

    var o = this;
    var af = function() {
        var ttime = ms();
        if (ttime >= stime + delay) {
            o.target = newTarget;
            o.animFrameId = null;

            // send properties of the newly set stuff
            o._emitCameraProps();
        } else {
            var ff = (ttime - stime) / delay;

            ff = easef(ff);

            var x = st[0] + (et[0] - st[0]) * ff;
            var y = st[1] + (et[1] - st[1]) * ff;
            var z = st[2] + (et[2] - st[2]) * ff;

            o._emitCameraProps({target: [x, y, z]});

            o.animFrameId = requestAnimationFrame(af);
        }
    };

    af();
};

OrbitalCamera.prototype.setHeading = function(newHeading, delay) {
    if (this.headingAnimFrameId !== null) {
	    cancelAnimationFrame(this.headingAnimFrameId);
        this.headingAnimFrameId = null;
    }

    delay = delay || 500;

	var st = this.azimuth;
	var et = newHeading;

    var stime = ms();

    var o = this;

    var af = function() {
        var ttime = ms();
        if (ttime >= stime + delay) {
	        o.azimuth = newHeading;

	        while (o.azimuth > 360) o.azimuth -= 360;
	        while (o.azimuth < 0) o.azimuth += 360;

            o.headingAnimFrameId = null;

            // send properties of the newly set stuff
            o._emitCameraProps();
        } else {
            var ff = (ttime - stime) / delay;

            ff = easef(ff);

            var v = st + (et - st) * ff;

            o._emitCameraProps({azimuth: v});
            o.headingAnimFrameId = requestAnimationFrame(af);
        }
    };

    af();
};

var pointInSpace = function(o, evt) {
    var w = o.elem.offsetWidth,
        h = o.elem.offsetHeight;

	var x = evt.offsetX==undefined?evt.layerX:evt.offsetX;
	var y = evt.offsetY==undefined?evt.layerY:evt.offsetY;

    // pick a point in renderer
    //
    var p = o.renderer.pickPoint(x, y);

    console.log("Renderer returned:", p);

    return p;
};

OrbitalCamera.prototype._bindHandlers = function() {
    var o = this;

    // TODO: stop this stuff and do a eye position based on two
    // parameters a: angle along X axis and b: angle around Y axis
    // and compute the position of eye on the fly

    // double click handling
    //
    var ondblclick = function(evt) {
        o._transitionTargetTo(pointInSpace(o, evt));
    };

    var oncontextmenu = function(e) {
        e.preventDefault();
        return false;
    };

    // click and drag handler
    //
    var dragstate = {};
    var onmousedown = function(e) {
        e.preventDefault();

        var buttonId = e.which || e.button;

        dragstate.ispanning = (buttonId === 3);

        if (dragstate.ispanning) {
            dragstate.start_target = o.target;
            dragstate.start_pos = pointInSpace(o, e);

            // we need to figure the axis we'd be moving along
            var props = o._cameraProps();
            var eye = props[0];
            var target = props[1];

            var targetToEye = vec3.subtract([], eye, target);
            vec3.normalize(targetToEye, targetToEye); // normalized vector from target to eye

            var rightVec = vec3.cross([], [0, 1, 0], targetToEye); // vector to the right
            var dir = vec3.cross([], rightVec, [0, 1, 0]);         // vector towards the user projected

            dragstate.lr = rightVec;
            dragstate.fb = dir;

            dragstate.distance = o.distance;
        }
        else {
            dragstate.start_e = o.elevation;
            dragstate.start_a = o.azimuth;
        }

        dragstate.active = true;
        dragstate.x = e.clientX;
        dragstate.y = e.clientY;
        dragstate.wasMoved = false;

        // attach handlers to document so that we're notified when the mouse goes
        // out of the window
        var onmousemove = function(e) {
            e.preventDefault();

            if (!dragstate.active) return; // nothing to do here

            dragstate.wasMoved = true;
            var dx = e.clientX - dragstate.x;
            var dy = e.clientY - dragstate.y;

            if (dragstate.ispanning) {
                // when panning, change the actual 3D position of the target
                //
                var nf = Math.max(0.01, o._att() * o._range() * 0.001);

                // figure out the displacement based on the two vectors
                var p = vec3.add([],
                                 vec3.scale([], dragstate.lr, -dx * nf),
                                 vec3.scale([], dragstate.fb, -dy * nf));

                vec3.add(p, p, dragstate.start_target);

                //o._transitionTargetTo(p, 50);
                o._emitCameraProps({
                    target: p
                });

                dragstate.last_position = p;
            }
            else {
                var fac = 0.01;
                var ddx = (dx * fac) * (dx * fac) * (dx < 0 ? -1 : 1);
                var ddy = (dy * fac) * (dy * fac) * (dy < 0 ? -1 : 1);

                var this_e = dragstate.start_e + ddy;
                var this_a = dragstate.start_a - ddx;

                // limit the elevation
                this_e = Math.max(1, Math.min(80, this_e));

                o._emitCameraProps({
                    elevation: this_e,
                    azimuth: this_a
                });

                // store them for final application when the mouseclick is released
                dragstate.last_e = this_e;
                dragstate.last_a = this_a;
            }
        };

        var norm = function(a) {
            // normalize azimuth
            if (a > 180) a = a - 360;
            if (a < -180) a = a + 360;

            return a;
        };

        var onmouseup = function(e) {
            e.preventDefault();

            dragstate.active = false;
            if (dragstate.wasMoved) {
                if (dragstate.ispanning) {
                    o.target = dragstate.last_position;
                }
                else {
                    o.azimuth = norm(dragstate.last_a);
                    o.elevation = dragstate.last_e;
                }

                o._emitCameraProps();
            }

            document.removeEventListener("mousemove", onmousemove);
            document.removeEventListener("mouseup", onmouseup);
        };

        document.addEventListener("mousemove", onmousemove);
        document.addEventListener("mouseup", onmouseup);
    };

    var isWindows = function() {
        return navigator.platform.indexOf("Win") === 0;
    };

	var isLinux = function() {
        return navigator.platform.indexOf("Linux") === 0;
	};

    var isChrome = function() {
        var isChromium = window.chrome,
            vendorName = window.navigator.vendor;
        return (isChromium !== null && isChromium !== undefined && vendorName === "Google Inc.");
    };

    var onmousewheel = function(e) {
	    console.log("wheel!")
        e.preventDefault();
        var m = e.wheelDelta || (-120 * e.detail);
	    var r = o._range() / 100;

        var amp = 1.0; // no amplification by default

        // not sure why but sensitivity on windows in chrome is pretty terrible
	    if ((isLinux() || isWindows()) && isChrome()) {
            amp = 5.0;
        }

	    console.log(m, r, amp, o.distance, o.maxDistance);

        var d = 0.01 * amp * m * r * (o.distance / o.maxDistance);
        o.distance -= d;

        o.distance = Math.min(Math.max(o.minDistance, o.distance), o.maxDistance);

	    if (o.animFrameId === null && o.headingAnimFrameId === null)
            o._emitCameraProps();
    };

	var te = this.elem;

    te.addEventListener("dblclick", ondblclick);
    te.addEventListener("contextmenu", oncontextmenu);
    te.addEventListener("mousedown", onmousedown);
    te.addEventListener("mousewheel", onmousewheel);
    te.addEventListener("DOMMouseScroll", onmousewheel);

    var o = this;
    this._unbind = function() {
        te.removeEventListener("dblclick", ondblclick);
        te.removeEventListener("contextmenu", oncontextmenu);
        te.removeEventListener("mousedown", onmousedown);
        te.removeEventListener("mousewheel", onmousewheel);
        te.removeEventListener("DOMMouseScroll", onmousewheel);
    };
};

OrbitalCamera.prototype._unbindHandlers = function() {
    if (this._unbind) {
        this._unbind();
        this._unbind = null;
    }
};

module.exports = {
    Orbital: OrbitalCamera
};
<|endoftext|>