<commit_before>import React from 'react'
import classes from './Schedule.scss'
import eventInfoClasses from 'components/EventInfo/EventInfo.scss'
import ScheduleElement from 'components/ScheduleElement'
import { Col, Row } from 'react-bootstrap'

let satStartTimes = ['10:00 am -', '10:00 am -', '11:45 am -', '1:30 pm -', '1:30 pm -', '1:30 pm -', '1:30 pm -', '2:00 pm -', '2:15 pm -', '2:15 pm -', '2:30 pm -', '2:30 pm -', '3:30 pm -', '3:30 pm -', '3:30 pm -', '3:30 pm -', '4:00 pm -', '4:15 pm -', '5:00 pm -', '5:00 pm -', '5:00 pm -', '5:00 pm -', '6:15 pm -', '7:45 pm -', '7:45 pm -', '8:30 pm -', '9:00 pm -', '10:00 pm -']
let satEndTimes = ['11:45 am ', '11:45 am ', '1:15 pm ', '2:30 pm ', '2:00 pm ', '2:00 pm ', '2:00 pm ', '3:00 pm ', '5:00 pm ', '3:00 pm ', '3:30 pm ', '3:15 pm ', '5:00 pm ', '4:00 pm ', '4:00 pm ', '4:00 pm ', '5:00 pm ', '5:00 pm ', '5:45 pm', '6:30 pm ', '6:00 pm ', '6:00 pm ', '7:45 pm ', '8:30 pm ', '8:30 pm ', '9:30 pm ', '12:00 am ', '11:00 pm ']
let satTitles = ['Check-In', 'Lunch', 'Opening Ceremony', 'Inequality Track Talk', 'Education Track Talk', 'Energy/Environment Track Talk', 'Health/Wellness Track Talk', 'Team Formation/Ideation', 'Hedgehogs!', 'Duke eNable Workshop', 'Smartcar Tech Talk', 'Coinbase Tech Talk', 'Introduction to Connected Hardware (Duke Students only)', 'Appian React Workshop', 'Market Failures, Incentives, and Cryptoassets', 'Github Tech Talk #1 — Intro to Electron', 'Office Hours', 'ConsenSys Tech Talk - How to use Uport', 'Info Session with ConsenSys', 'Scavenger Hunt', 'Soft Skills That Matter Talk', 'Github Tech Talk #2 — Intro to Git', 'Dinner', 'Zoho Tech Talk', 'Microsoft Tech Talk', 'Female-Identifying/Non-binary Hackers Mixer (3D printing session followed by networking + desserts)', 'Frisbee', 'MLH Stack Cup']
let satSpeakers = [null, null, null, null, null, null, null, null, null, null, null, null, null, 'Michael Deng', 'Dhruv Luthra', 'Shelley Vohr', 'Elissa Shevinsky', null, null, null, 'Salman Azhar — Duke Fuqua School of Business', 'Shelley Vohr', null, null, null, null, null, null]
let satLocations = ['Schiciano Atrium', 'CIEMAS', 'Page Auditorium', 'Hudson 208', 'Schiciano A', 'Hudson 207', 'Schiciano B', 'Same Room as Track Talk', 'Quad', 'Hudson 222', 'Hudson 115A', 'Hudson 232', 'Innovation Colab - TEC Classroom', 'Hudson 201', 'Hudson 222', 'Hudson 212', 'Hudson 115A', 'Hudson 222', 'Hudson 222', 'Meet in Schiciano Atrium', 'Hudson 216', 'Hudson 212', 'Schiciano Atrium', 'Hudson 216', 'Hudson 232', 'Innovation Colab - TEC Classroom', 'Equad', 'Schiciano']
let satLinks = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
let satLinkTitles = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]

let sunStartTimes = ['9:00 am -', '10:30 am -', '12:00 pm', '12:00 pm -', '1:30 pm', '1:45 pm -', '3:00 pm -', '4:30 pm -']
let sunEndTimes = ['10:30 am ', '12:00 pm ', null, '1:30 pm ', null, '2:45 pm ', '4:00 pm ', '5:30 pm ']
let sunTitles = ['Breakfast', 'Massage Therapists', 'Submissions Due to Devpost', 'Lunch', 'Hacking Ends', 'Expo 1', 'Expo 2', 'Closing Ceremony']
let sunSpeakers = [null, null, null, null, null, null, null, null]
let sunLocations = ['Schiciano Atrium', 'CIEMAS', null, 'Schiciano Atrium', null, 'CIEMAS', 'CIEMAS', 'Page Auditorium']

class Schedule extends React.Component {

  render () {
    return (

            SCHEDULE

          <Row>
            <Col md={6}>

                  SAT 10/28

                  {satStartTimes.map((startTime, index) => {
                    return <ScheduleElement startTime={startTime} key={index}
                      endTime={satEndTimes[index]}
                      title={satTitles[index]}
                      location={satLocations[index]}
                      speaker={satSpeakers[index]}
                      link={satLinks[index]}
                      linkTitle={satLinkTitles[index]} />
                  }
                  )}

            </Col>
            <Col md={6}>

                  SUN 10/29

                  {sunStartTimes.map((startTime, index) => {
                    return <ScheduleElement startTime={startTime} key={index}
                      endTime={sunEndTimes[index]}
                      title={sunTitles[index]}
                      location={sunLocations[index]}
                      speaker={sunSpeakers[index]} />
                  }
                  )}

            </Col>
          </Row>

    )
  }
}

export default Schedule
<commit_msg>updated room<commit_after>import React from 'react'
import classes from './Schedule.scss'
import eventInfoClasses from 'components/EventInfo/EventInfo.scss'
import ScheduleElement from 'components/ScheduleElement'
import { Col, Row } from 'react-bootstrap'

let satStartTimes = ['10:00 am -', '10:00 am -', '11:45 am -', '1:30 pm -', '1:30 pm -', '1:30 pm -', '1:30 pm -', '2:00 pm -', '2:15 pm -', '2:15 pm -', '2:30 pm -', '2:30 pm -', '3:30 pm -', '3:30 pm -', '3:30 pm -', '3:30 pm -', '4:00 pm -', '4:15 pm -', '5:00 pm -', '5:00 pm -', '5:00 pm -', '5:00 pm -', '6:15 pm -', '7:45 pm -', '7:45 pm -', '8:30 pm -', '9:00 pm -', '10:00 pm -']
let satEndTimes = ['11:45 am ', '11:45 am ', '1:15 pm ', '2:30 pm ', '2:00 pm ', '2:00 pm ', '2:00 pm ', '3:00 pm ', '5:00 pm ', '3:00 pm ', '3:30 pm ', '3:15 pm ', '5:00 pm ', '4:00 pm ', '4:00 pm ', '4:00 pm ', '5:00 pm ', '5:00 pm ', '5:45 pm', '6:30 pm ', '6:00 pm ', '6:00 pm ', '7:45 pm ', '8:30 pm ', '8:30 pm ', '9:30 pm ', '12:00 am ', '11:00 pm ']
let satTitles = ['Check-In', 'Lunch', 'Opening Ceremony', 'Inequality Track Talk', 'Education Track Talk', 'Energy/Environment Track Talk', 'Health/Wellness Track Talk', 'Team Formation/Ideation', 'Hedgehogs!', 'Duke eNable Workshop', 'Smartcar Tech Talk', 'Coinbase Tech Talk', 'Introduction to Connected Hardware (Duke Students only)', 'Appian React Workshop', 'Market Failures, Incentives, and Cryptoassets', 'Github Tech Talk #1 — Intro to Electron', 'Office Hours', 'ConsenSys Tech Talk - How to use Uport', 'Info Session with ConsenSys', 'Scavenger Hunt', 'Soft Skills That Matter Talk', 'Github Tech Talk #2 — Intro to Git', 'Dinner', 'Zoho Tech Talk', 'Microsoft Tech Talk', 'Female-Identifying/Non-binary Hackers Mixer (3D printing session followed by networking + desserts)', 'Frisbee', 'MLH Stack Cup']
let satSpeakers = [null, null, null, null, null, null, null, null, null, null, null, null, null, 'Michael Deng', 'Dhruv Luthra', 'Shelley Vohr', 'Elissa Shevinsky', null, null, null, 'Salman Azhar — Duke Fuqua School of Business', 'Shelley Vohr', null, null, null, null, null, null]
let satLocations = ['Schiciano Atrium', 'CIEMAS', 'Page Auditorium', 'Hudson 208', 'Schiciano A', 'Hudson 207', 'Schiciano B', 'Same Room as Track Talk', 'Quad', 'Hudson 222', 'Hudson 115A', 'Hudson 232', 'Innovation Colab - TEC Classroom', 'Hudson 201', 'Hudson 216', 'Hudson 212', 'Hudson 115A', 'Hudson 222', 'Hudson 222', 'Meet in Schiciano Atrium', 'Hudson 216', 'Hudson 212', 'Schiciano Atrium', 'Hudson 216', 'Hudson 232', 'Innovation Colab - TEC Classroom', 'Equad', 'Schiciano']
let satLinks = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
let satLinkTitles = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]

let sunStartTimes = ['9:00 am -', '10:30 am -', '12:00 pm', '12:00 pm -', '1:30 pm', '1:45 pm -', '3:00 pm -', '4:30 pm -']
let sunEndTimes = ['10:30 am ', '12:00 pm ', null, '1:30 pm ', null, '2:45 pm ', '4:00 pm ', '5:30 pm ']
let sunTitles = ['Breakfast', 'Massage Therapists', 'Submissions Due to Devpost', 'Lunch', 'Hacking Ends', 'Expo 1', 'Expo 2', 'Closing Ceremony']
let sunSpeakers = [null, null, null, null, null, null, null, null]
let sunLocations = ['Schiciano Atrium', 'CIEMAS', null, 'Schiciano Atrium', null, 'CIEMAS', 'CIEMAS', 'Page Auditorium']

class Schedule extends React.Component {

  render () {
    return (

            SCHEDULE

          <Row>
            <Col md={6}>

                  SAT 10/28

                  {satStartTimes.map((startTime, index) => {
                    return <ScheduleElement startTime={startTime} key={index}
                      endTime={satEndTimes[index]}
                      title={satTitles[index]}
                      location={satLocations[index]}
                      speaker={satSpeakers[index]}
                      link={satLinks[index]}
                      linkTitle={satLinkTitles[index]} />
                  }
                  )}

            </Col>
            <Col md={6}>

                  SUN 10/29

                  {sunStartTimes.map((startTime, index) => {
                    return <ScheduleElement startTime={startTime} key={index}
                      endTime={sunEndTimes[index]}
                      title={sunTitles[index]}
                      location={sunLocations[index]}
                      speaker={sunSpeakers[index]} />
                  }
                  )}

            </Col>
          </Row>

    )
  }
}

export default Schedule
<|endoftext|>


<commit_before>import React from 'react';
import Websocket from 'react-websocket';
import { Tab, Tabs } from 'react-bootstrap';
import './SoundTabs.css';
import SoundSubList from './SoundSubList.js';

export default class SoundTabs extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            AvailableSounds: {},
            OverheatedSounds: [],
            Categories: []
        };
    }

    handleData(data) {
        let newState = {
            AvailableSounds: {},
            OverheatedSounds: [],
            Categories: []
        };

        JSON.parse(data).Sounds.forEach(function(x) {
           if (x.Overheated) {
               newState.OverheatedSounds.push(x);
           } else {
               if (newState.AvailableSounds[x.Category] === undefined) {
                   newState.AvailableSounds[x.Category] = [];
                   newState.Categories.push(x.Category);
               }
               newState.AvailableSounds[x.Category].push(x);
           }
        });

        newState.OverheatedSounds.sort(function(a, b) {
           return a.Temperature - b.Temperature;
        });

        this.setState(newState);
    }

    render() {

        var tabs = [];

        this.state.Categories.forEach(item => {
            tabs.push(
                <Tab eventKey={item} title={"Available sounds (" + this.state.AvailableSounds[item].length + ")"}>
                    <SoundSubList mode="normal" sounds={this.state.AvailableSounds[item]} />
                </Tab>
            );
        });

        return (

                <Tabs defaultActiveKey="default" id="sound-list-tabs">{tabs}</Tabs>
                <Websocket url="ws://localhost:8080/api/websocket" onMessage={this.handleData.bind(this)}/>

        );
    }

}
<commit_msg>category display<commit_after>import React from 'react';
import Websocket from 'react-websocket';
import { Tab, Tabs } from 'react-bootstrap';
import './SoundTabs.css';
import SoundSubList from './SoundSubList.js';

export default class SoundTabs extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            AvailableSounds: {},
            OverheatedSounds: [],
            Categories: []
        };
    }

    handleData(data) {
        let newState = {
            AvailableSounds: {},
            OverheatedSounds: [],
            Categories: []
        };

        JSON.parse(data).Sounds.forEach(function(x) {
           if (x.Overheated) {
               newState.OverheatedSounds.push(x);
           } else {
               if (newState.AvailableSounds[x.Category] === undefined) {
                   newState.AvailableSounds[x.Category] = [];
                   newState.Categories.push(x.Category);
               }
               newState.AvailableSounds[x.Category].push(x);
           }
        });

        newState.OverheatedSounds.sort(function(a, b) {
           return a.Temperature - b.Temperature;
        });

        this.setState(newState);
    }

    render() {

        var tabs = [];

        this.state.Categories.forEach(item => {
            tabs.push(
                <Tab eventKey={item} title={item + " (" + this.state.AvailableSounds[item].length + ")"}>
                    <SoundSubList mode="normal" sounds={this.state.AvailableSounds[item]} />
                </Tab>
            );
        });

        tabs.push(
            <Tab eventKey="overheated" title={"Overheated (" + this.state.OverheatedSounds.length + ")"}>
                <SoundSubList mode="normal" sounds={this.state.OverheatedSounds} />
            </Tab>
        );

        return (

                <Tabs defaultActiveKey="default" id="sound-list-tabs">
                    {tabs}
                </Tabs>
                <Websocket url="ws://pi:8080/api/websocket" onMessage={this.handleData.bind(this)}/>

        );
    }

}
<|endoftext|>


<commit_before>// Adds the apos.http client, which has the same API
// as the server-side apos.http client, although it may
// not have exactly the same features available.
// This is a lean, IE11-friendly implementation.

(function() {

  var apos = window.apos;
  apos.http = {};

  // Send a POST request. Note that POST body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.post = function(url, options, callback) {
    return apos.http.remote('POST', url, options, callback);
  };

  // Send a GET request. Note that query string data may be in
  // `options.qs`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.get = function(url, options, callback) {
    return apos.http.remote('GET', url, options, callback);
  };

  // Send a PATCH request. Note that PATCH body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.patch = function(url, options, callback) {
    return apos.http.remote('PATCH', url, options, callback);
  };

  // Send a PUT request. Note that PUT body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.put = function(url, options, callback) {
    return apos.http.remote('PUT', url, options, callback);
  };

  // Send a DELETE request. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.delete = function(url, options, callback) {
    return apos.http.remote('DELETE', url, options, callback);
  };

  // Send an HTTP request with the given method to the given URL and return the response body.
  //
  // The callback is optional as long as Promise support is present in the browser, directly or as
  // a polyfill. If a callback is used it will receive `(err, result)` where `result` is the
  // return value described below.
  //
  // Accepts the following options:
  //
  // `qs` (pass object; builds a query string, does not support recursion)
  // `send`: by default, `options.body` is sent as JSON if it is an object and it is not a
  // `FormData` object. If `send` is set to `json`, it is always sent as JSON.
  // `body` (request body, not for GET; if an object or array, sent as JSON, otherwise sent as-is, unless
  // the `send` option is set)
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
  // parsed as JSON only if the content-type is application/json)
  // `headers` (an object containing header names and values)
  // `csrf` (unless explicitly set to `false`, send the X-XSRF-TOKEN header when talking to the same site)
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
  // to lowercase names. If there are duplicate headers after canonicalization only the
  // last value is returned.
  //
  // `If a header appears multiple times an array is returned for it)
  //
  // If the status code is >= 400 an error is thrown. The error object will be
  // similar to a `fullResponse` object, with a `status` property.
  //
  // If the URL is site-relative (starts with /) it will be requested from
  // the apostrophe site itself.

  // Just before the XMLHTTPRequest is sent this method emits an
  // `apos-before-post` event on `document.body` (where `post` changes
  // to match the method, in lower case). The event object
  // has `uri`, `data` and `request` properties. `request` is the
  // XMLHTTPRequest object. You can use this to set custom headers
  // on all lean requests, etc.

  apos.http.remote = function(method, url, options, callback) {
    var startTime = performance.now();

    if (!callback) {
      if (!window.Promise) {
        throw new Error('If you wish to receive a promise from apos.http methods in older browsers you must have a Promise polyfill. If you do not want to provide one, pass a callback instead.');
      }
      return new window.Promise(function(resolve, reject) {
        return apos.http.remote(method, url, options, function(err, result) {
          if (err) {
            return reject(err);
          }
          return resolve(result);
        });
      });
    }
    if (apos.prefix) {
      if (apos.utils.sameSite(url)) {
        url = apos.prefix + url;
      }
    }

    var body = document.querySelector('body');
    var xmlhttp = new XMLHttpRequest();
    var csrfToken = apos.csrfCookieName ? apos.utils.getCookie(apos.csrfCookieName) : 'csrf-fallback';
    var data = options.qs;
    var keys;
    var i;
    if ((data !== null) && ((typeof data) === 'object')) {
      keys = Object.keys(data);
      for (i = 0; (i < keys.length); i++) {
        var key = keys[i];
        if (i > 0) {
          url += '&';
        } else {
          url += '?';
        }
        url += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
      }
    }

    apos.utils.emit(document.body, 'apos-before-' + method.toLowerCase(), {
      uri: url,
      data: options.body,
      request: xmlhttp
    });

    xmlhttp.addEventListener('progress', function() {
      var endTime = performance.now();
      if (options.busy && endTime - startTime > 300) {
        apos.utils.addClass(body, 'busy');
      }
    });
    xmlhttp.addEventListener('load', function() {
      var responseHeader = this.getResponseHeader('Content-Type');
      if (!responseHeader) {
        // Can happen when the response body is null
        return callback(error(), null);
      }
      var data;
      if ((options.parse === 'json') || (responseHeader.match(/^application\/json/))) {
        try {
          data = JSON.parse(this.responseText);
        } catch (e) {
          return reply(e, this.responseText);
        }
      } else {
        data = this.responseText;
      }
      return reply(error(), data);
      function error() {
        // xmlhttprequest does not consider a 404, 500, etc. to be
        // an "error" in the sense that would trigger the error
        // event handler function (below), but we do.
        if (xmlhttp.status < 400) {
          return null;
        }
        return xmlhttp;
      }
      function reply(error, data) {
        if (error || options.fullResponse) {
          return callback(error, {
            body: data,
            status: xmlhttp.status,
            headers: getHeaders()
          });
        } else {
          return callback(null, data);
        }
      }
    });
    xmlhttp.addEventListener('abort', function(evt) {
      return callback(evt);
    });
    xmlhttp.addEventListener('error', function(evt) {
      return callback(evt);
    });
    xmlhttp.addEventListener('loadend', function (evt) {
      if (options.busy) {
        apos.utils.removeClass(body, 'busy');
      }
    });

    xmlhttp.open(method, url);

    var formData = window.FormData && (data instanceof window.FormData);
    var sendJson = (options.send === 'json') || (options.body && ((typeof options.body) === 'object') && !formData);
    if (sendJson) {
      data = JSON.stringify(options.body);
      xmlhttp.setRequestHeader('Content-Type', 'application/json');
    } else {
      data = options.body;
    }
    if (csrfToken && (options.csrf !== false)) {
      if (apos.utils.sameSite(url)) {
        xmlhttp.setRequestHeader('X-XSRF-TOKEN', csrfToken);
      }
    }
    if (options.headers) {
      keys = Object.keys(options.headers);
      for (i = 0; (i < keys.length); i++) {
        xmlhttp.setRequestHeader(keys[i], options.headers[keys[i]]);
      }
    }
    xmlhttp.send(data);

    function getHeaders() {
      var headers = xmlhttp.getAllResponseHeaders();
      if (!headers) {
        return {};
      }
      // Per MDN
      var arr = headers.trim().split(/[\r\n]+/);
      // Create a map of header names to values
      var headerMap = {};
      arr.forEach(function (line) {
        var parts = line.split(': ');
        var header = parts.shift();
        if (!header) {
          return;
        }
        var value = parts.shift();
        // Optional support for fetching arrays of headers with the same name
        // could be added at a later time if anyone really cares. Usually
        // just a source of bugs
        headerMap[header.toLowerCase()] = value;
      });
      return headerMap;
    }
  };

})();
<commit_msg>Busy indicator: remove 300ms threshold<commit_after>// Adds the apos.http client, which has the same API
// as the server-side apos.http client, although it may
// not have exactly the same features available.
// This is a lean, IE11-friendly implementation.

(function() {

  var apos = window.apos;
  apos.http = {};

  // Send a POST request. Note that POST body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.post = function(url, options, callback) {
    return apos.http.remote('POST', url, options, callback);
  };

  // Send a GET request. Note that query string data may be in
  // `options.qs`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.get = function(url, options, callback) {
    return apos.http.remote('GET', url, options, callback);
  };

  // Send a PATCH request. Note that PATCH body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.patch = function(url, options, callback) {
    return apos.http.remote('PATCH', url, options, callback);
  };

  // Send a PUT request. Note that PUT body data should be in
  // `options.body`. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.put = function(url, options, callback) {
    return apos.http.remote('PUT', url, options, callback);
  };

  // Send a DELETE request. See `apos.http.remote` for details.
  // You do NOT have to pass a callback unless you must support IE11
  // and do not want to include a promise polyfill in your build.
  apos.http.delete = function(url, options, callback) {
    return apos.http.remote('DELETE', url, options, callback);
  };

  // Send an HTTP request with the given method to the given URL and return the response body.
  //
  // The callback is optional as long as Promise support is present in the browser, directly or as
  // a polyfill. If a callback is used it will receive `(err, result)` where `result` is the
  // return value described below.
  //
  // Accepts the following options:
  //
  // `qs` (pass object; builds a query string, does not support recursion)
  // `send`: by default, `options.body` is sent as JSON if it is an object and it is not a
  // `FormData` object. If `send` is set to `json`, it is always sent as JSON.
  // `body` (request body, not for GET; if an object or array, sent as JSON, otherwise sent as-is, unless
  // the `send` option is set)
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
  // parsed as JSON only if the content-type is application/json)
  // `headers` (an object containing header names and values)
  // `csrf` (unless explicitly set to `false`, send the X-XSRF-TOKEN header when talking to the same site)
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
  // to lowercase names. If there are duplicate headers after canonicalization only the
  // last value is returned.
  //
  // `If a header appears multiple times an array is returned for it)
  //
  // If the status code is >= 400 an error is thrown. The error object will be
  // similar to a `fullResponse` object, with a `status` property.
  //
  // If the URL is site-relative (starts with /) it will be requested from
  // the apostrophe site itself.

  // Just before the XMLHTTPRequest is sent this method emits an
  // `apos-before-post` event on `document.body` (where `post` changes
  // to match the method, in lower case). The event object
  // has `uri`, `data` and `request` properties. `request` is the
  // XMLHTTPRequest object. You can use this to set custom headers
  // on all lean requests, etc.

  apos.http.remote = function(method, url, options, callback) {
    if (!callback) {
      if (!window.Promise) {
        throw new Error('If you wish to receive a promise from apos.http methods in older browsers you must have a Promise polyfill. If you do not want to provide one, pass a callback instead.');
      }
      return new window.Promise(function(resolve, reject) {
        return apos.http.remote(method, url, options, function(err, result) {
          if (err) {
            return reject(err);
          }
          return resolve(result);
        });
      });
    }
    if (apos.prefix) {
      if (apos.utils.sameSite(url)) {
        url = apos.prefix + url;
      }
    }

    var body = document.querySelector('body');
    var xmlhttp = new XMLHttpRequest();
    var csrfToken = apos.csrfCookieName ? apos.utils.getCookie(apos.csrfCookieName) : 'csrf-fallback';
    var data = options.qs;
    var keys;
    var i;
    if ((data !== null) && ((typeof data) === 'object')) {
      keys = Object.keys(data);
      for (i = 0; (i < keys.length); i++) {
        var key = keys[i];
        if (i > 0) {
          url += '&';
        } else {
          url += '?';
        }
        url += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
      }
    }

    xmlhttp.open(method, url);
    var formData = window.FormData && (data instanceof window.FormData);
    var sendJson = (options.send === 'json') || (options.body && ((typeof options.body) === 'object') && !formData);
    if (sendJson) {
      xmlhttp.setRequestHeader('Content-Type', 'application/json');
    }
    if (csrfToken && (options.csrf !== false)) {
      if (apos.utils.sameSite(url)) {
        xmlhttp.setRequestHeader('X-XSRF-TOKEN', csrfToken);
      }
    }
    if (options.headers) {
      keys = Object.keys(options.headers);
      for (i = 0; (i < keys.length); i++) {
        xmlhttp.setRequestHeader(keys[i], options.headers[keys[i]]);
      }
    }
    apos.utils.emit(document.body, 'apos-before-' + method.toLowerCase(), {
      uri: url,
      data: options.body,
      request: xmlhttp
    });
    if (sendJson) {
      data = JSON.stringify(options.body);
    } else {
      data = options.body;
    }
    xmlhttp.send(data);
    xmlhttp.addEventListener('load', function() {
      if (options.busy) {
        apos.utils.addClass(body, 'busy');
      }
      var responseHeader = this.getResponseHeader('Content-Type');
      if (!responseHeader) {
        // Can happen when the response body is null
        return callback(error(), null);
      }
      var data;
      if ((options.parse === 'json') || (responseHeader.match(/^application\/json/))) {
        try {
          data = JSON.parse(this.responseText);
        } catch (e) {
          return reply(e, this.responseText);
        }
      } else {
        data = this.responseText;
      }
      return reply(error(), data);
      function error() {
        // xmlhttprequest does not consider a 404, 500, etc. to be
        // an "error" in the sense that would trigger the error
        // event handler function (below), but we do.
        if (xmlhttp.status < 400) {
          return null;
        }
        return xmlhttp;
      }
      function reply(error, data) {
        if (error || options.fullResponse) {
          return callback(error, {
            body: data,
            status: xmlhttp.status,
            headers: getHeaders()
          });
        } else {
          return callback(null, data);
        }
      }
    });
    xmlhttp.addEventListener('abort', function(evt) {
      return callback(evt);
    });
    xmlhttp.addEventListener('error', function(evt) {
      return callback(evt);
    });
    xmlhttp.addEventListener('loadend', function () {
      if (options.busy) {
        apos.utils.removeClass(body, 'busy');
      }
    });

    function getHeaders() {
      var headers = xmlhttp.getAllResponseHeaders();
      if (!headers) {
        return {};
      }
      // Per MDN
      var arr = headers.trim().split(/[\r\n]+/);
      // Create a map of header names to values
      var headerMap = {};
      arr.forEach(function (line) {
        var parts = line.split(': ');
        var header = parts.shift();
        if (!header) {
          return;
        }
        var value = parts.shift();
        // Optional support for fetching arrays of headers with the same name
        // could be added at a later time if anyone really cares. Usually
        // just a source of bugs
        headerMap[header.toLowerCase()] = value;
      });
      return headerMap;
    }
  };

})();
<|endoftext|>


<commit_before>/*
 *
 *
 */

(function($) {
  'use strict';

  //
  // Class DEFINITION

  var Untouchable = function(element, options) {
    this.options = options;
    this.$element = $(element);

    this.$element.on('mousemove', this.away);
  };

  //
  // DEFAULT SETTING

  Untouchable.DEFAULTS = {
    distance: 100,
    duration: 400
  };

  Untouchable.prototype.getXvactor = function(elem, mouseX) {
    return mouseX - (elem.offset().left + (elem.width() / 2));
  };

  Untouchable.prototype.getYvactor = function(elem, mouseY) {
    return mouseY - (elem.offset().top + (elem.height() / 2));
  };

  Untouchable.prototype.getDistance = function(elem, mouseX, mouseY) {
    return Math.sqrt(Math.pow(this.getXvactor(elem, mouseX), 2) + Math.pow(this.getYvactor(elem, mouseY), 2));
  };

  Untouchable.prototype.away = function(e) {
    var mouseX = e.pageX;
    var mouseY = e.pageY;

    var distance = this.getDistance($(this), mouseX, mouseY);

    if (distance < this.options.distance) {

      // away
      this.$element.animate({
        top : '-=' + this.getXvactor(),
        left: '-=' + this.getYvactor()
      }, $this.options.duration, $this.options.easing, $this.options.onAway);
    }
  };

  //
  // PLUGIN DEFINITION

  var old = $.fn.untouchable;

  $.fn.untouchable = function(option) {
    return this.each(function() {
      var $this   = $(this);
      var options = $.extend({}, Untouchable.DEFAULTS, $this.data(), typeof option == 'object' && options);

      if (!data) $this.data('untouchable', (data = new Untouchable(this, options)));

    });
  };

  $.fn.untouchable.Constructor = Untouchable;

  //
  // NO CONFLICT
  $.fn.untouchable.noConflict = function () {
    $.fn.untouchable = old;
    return this;
  }

}(jQuery))<commit_msg>var bug fix<commit_after>/*
 *
 *
 */

(function($) {
  'use strict';

  //
  // Class DEFINITION

  var Untouchable = function(element, options) {
    this.options = options;
    this.$element = $(element);

    this.$element.on('mousemove', this.away);
  };

  //
  // DEFAULT SETTING

  Untouchable.DEFAULTS = {
    distance: 100,
    duration: 400
  };

  Untouchable.prototype.getXvactor = function(elem, mouseX) {
    return mouseX - (elem.offset().left + (elem.width() / 2));
  };

  Untouchable.prototype.getYvactor = function(elem, mouseY) {
    return mouseY - (elem.offset().top + (elem.height() / 2));
  };

  Untouchable.prototype.getDistance = function(elem, mouseX, mouseY) {
    return Math.sqrt(Math.pow(this.getXvactor(elem, mouseX), 2) + Math.pow(this.getYvactor(elem, mouseY), 2));
  };

  Untouchable.prototype.away = function(e) {
    var mouseX = e.pageX;
    var mouseY = e.pageY;

    var distance = this.getDistance($(this), mouseX, mouseY);

    if (distance < this.options.distance) {

      // away
      this.$element.animate({
        top : '-=' + this.getXvactor(),
        left: '-=' + this.getYvactor()
      }, $this.options.duration, $this.options.easing, $this.options.onAway);
    }
  };

  //
  // PLUGIN DEFINITION

  var old = $.fn.untouchable;

  $.fn.untouchable = function(option) {
    return this.each(function() {
      var $this   = $(this);
      var data    = $this.data('untouchable');
      var options = $.extend({}, Untouchable.DEFAULTS, $this.data(), typeof option == 'object' && options);

      if (!data) $this.data('untouchable', (data = new Untouchable(this, options)));

    });
  };

  $.fn.untouchable.Constructor = Untouchable;

  //
  // NO CONFLICT
  $.fn.untouchable.noConflict = function () {
    $.fn.untouchable = old;
    return this;
  };

}(jQuery))<|endoftext|>


<commit_before>/*
    The Conflict of Interest (COI) module of Kuali Research
    Copyright © 2015 Kuali, Inc.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

import * as ConfigController from './controllers/ConfigController';
import * as DisclosureController from './controllers/DisclosureController';
import * as TravelLogController from './controllers/TravelLogController';
import * as ProjectController from './controllers/ProjectController';
import * as FileController from './controllers/FileController';
import * as PIController from './controllers/PIController';
import * as UserController from './controllers/UserController';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import authentication from './middleware/authentication';
import apiAuthentication from './middleware/apiAuthentication';
import renderView from './middleware/renderView';
import Log from './Log';
import methodChecker from './middleware/methodChecker';
import ErrorLogger from './middleware/ErrorLogger';
import {COIConstants} from '../COIConstants';
import adminRoleCheck from './middleware/adminRoleCheck';
import unauthorized from './middleware/unauthorized';

export function run() {
  let app = express();
  app.disable('x-powered-by');
  app.set('view engine', 'jade');
  app.set('views', './views');
  configureProxy(app);

  let config;
  try {
    let extensions = require('research-extensions');
    extensions.express(app);
    config = extensions.config;
  } catch (e) {
    Log.info('extensions not found');
  }

  conditionallyLogRequests(app);

  app.use('/coi', express.static('client'));
  app.use('/coi/build', (req, res) => { res.sendStatus(404); }); // Static files that weren't found

  app.use(methodChecker);
  app.use(cookieParser());

  app.use('/coi/auth', renderView('auth'));
  app.use('/api', apiAuthentication);
  app.use('/coi', authentication);
  app.use('/coi$', renderView('index'));
  app.use('/coi/$', renderView('index'));
  app.use('/coi/archiveview', renderView('index'));
  app.use('/coi/dashboard', renderView('index'));
  app.use('/coi/disclosure', renderView('index'));
  app.use('/coi/travelLog', renderView('index'));
  app.use('/coi/revise', renderView('index'));

  app.use('/coi/admin', adminRoleCheck, renderView('admin/admin'));
  app.use('/coi/config', adminRoleCheck, renderView('admin/config'));
  app.use('/coi', unauthorized);

  app.use(bodyParser.json());
  ConfigController.init(app);
  DisclosureController.init(app);
  TravelLogController.init(app);
  ProjectController.init(app);
  FileController.init(app);
  PIController.init(app);
  UserController.init(app);
  app.use(ErrorLogger);

  let portNumber = config ? config.port : process.env.COI_PORT || 8090;
  let server = app.listen(portNumber);

  console.log(`Listening on port ${portNumber} in ${app.get('env')} mode`); // eslint-disable-line no-console

  process.on('uncaughtException', function(err) {
    Log.error(`Uncaught exception: ${err}`);
    Log.error(err);
    Log.error('waiting for pending connections to clear');
    server.close(() => {
      Log.error('shutting down');
      process.exit(1); //eslint-disable-line no-process-exit
    });
  });
}

function conditionallyLogRequests(app) {
  if (process.env.LOG_LEVEL <= COIConstants.LOG_LEVEL.INFO) {
    app.use((req, res, next) => {
      let startTime = new Date();
      next();
      let elapsedTime = new Date().getTime() - startTime.getTime();
      Log.info(`${req.originalUrl} - ${elapsedTime}ms`);
    });
  }
}

function configureProxy(app) {
  const TRUST_PROXY = process.env.TRUST_PROXY;
  if (TRUST_PROXY) {
    Log.info(`Using TRUST_PROXY value of ${TRUST_PROXY}`);

    if (TRUST_PROXY.toLowerCase() === 'true') {
      app.set('trust proxy', true);
    }
    else if (TRUST_PROXY.toLowerCase() === 'false') {
      app.set('trust proxy', false);
    }
    else {
      app.set('trust proxy', TRUST_PROXY);
    }
  }
}<commit_msg>Fixing timing of requests<commit_after>/*
    The Conflict of Interest (COI) module of Kuali Research
    Copyright © 2015 Kuali, Inc.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

import * as ConfigController from './controllers/ConfigController';
import * as DisclosureController from './controllers/DisclosureController';
import * as TravelLogController from './controllers/TravelLogController';
import * as ProjectController from './controllers/ProjectController';
import * as FileController from './controllers/FileController';
import * as PIController from './controllers/PIController';
import * as UserController from './controllers/UserController';
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import authentication from './middleware/authentication';
import apiAuthentication from './middleware/apiAuthentication';
import renderView from './middleware/renderView';
import Log from './Log';
import methodChecker from './middleware/methodChecker';
import ErrorLogger from './middleware/ErrorLogger';
import {COIConstants} from '../COIConstants';
import adminRoleCheck from './middleware/adminRoleCheck';
import unauthorized from './middleware/unauthorized';

export function run() {
  let app = express();
  app.disable('x-powered-by');
  app.set('view engine', 'jade');
  app.set('views', './views');
  configureProxy(app);

  let config;
  try {
    let extensions = require('research-extensions');
    extensions.express(app);
    config = extensions.config;
  } catch (e) {
    Log.info('extensions not found');
  }

  conditionallyLogRequests(app);

  app.use('/coi', express.static('client'));
  app.use('/coi/build', (req, res) => { res.sendStatus(404); }); // Static files that weren't found

  app.use(methodChecker);
  app.use(cookieParser());

  app.use('/coi/auth', renderView('auth'));
  app.use('/api', apiAuthentication);
  app.use('/coi', authentication);
  app.use('/coi$', renderView('index'));
  app.use('/coi/$', renderView('index'));
  app.use('/coi/archiveview', renderView('index'));
  app.use('/coi/dashboard', renderView('index'));
  app.use('/coi/disclosure', renderView('index'));
  app.use('/coi/travelLog', renderView('index'));
  app.use('/coi/revise', renderView('index'));

  app.use('/coi/admin', adminRoleCheck, renderView('admin/admin'));
  app.use('/coi/config', adminRoleCheck, renderView('admin/config'));
  app.use('/coi', unauthorized);

  app.use(bodyParser.json());
  ConfigController.init(app);
  DisclosureController.init(app);
  TravelLogController.init(app);
  ProjectController.init(app);
  FileController.init(app);
  PIController.init(app);
  UserController.init(app);
  app.use(ErrorLogger);

  let portNumber = config ? config.port : process.env.COI_PORT || 8090;
  let server = app.listen(portNumber);

  console.log(`Listening on port ${portNumber} in ${app.get('env')} mode`); // eslint-disable-line no-console

  process.on('uncaughtException', function(err) {
    Log.error(`Uncaught exception: ${err}`);
    Log.error(err);
    Log.error('waiting for pending connections to clear');
    server.close(() => {
      Log.error('shutting down');
      process.exit(1); //eslint-disable-line no-process-exit
    });
  });
}

function conditionallyLogRequests(app) {
  if (process.env.LOG_LEVEL <= COIConstants.LOG_LEVEL.INFO) {
    app.use((req, res, next) => {
      let startTime = new Date().getTime();
      res.on('finish', function() {
        let elapsedTime = new Date().getTime() - startTime;
        Log.info(`${req.originalUrl} - ${elapsedTime}ms`);
      });
      next();
    });
  }
}

function configureProxy(app) {
  const TRUST_PROXY = process.env.TRUST_PROXY;
  if (TRUST_PROXY) {
    Log.info(`Using TRUST_PROXY value of ${TRUST_PROXY}`);

    if (TRUST_PROXY.toLowerCase() === 'true') {
      app.set('trust proxy', true);
    }
    else if (TRUST_PROXY.toLowerCase() === 'false') {
      app.set('trust proxy', false);
    }
    else {
      app.set('trust proxy', TRUST_PROXY);
    }
  }
}<|endoftext|>


<commit_before>/**
 * Mail
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 * @copyright Christoph Wurst 2015, 2016
 */

define(function(require) {
	'use strict';

	var $ = require('jquery');
	var _ = require('underscore');
	var Handlebars = require('handlebars');
	var Marionette = require('marionette');
	var Radio = require('radio');
	var MessageTemplate = require('text!templates/message-list-item.html');

	return Marionette.View.extend({
		template: Handlebars.compile(MessageTemplate),
		ui: {
			iconDelete: '.action.delete',
			star: '.star'
		},
		events: {
			'click .action.delete': 'deleteMessage',
			'click .mail-message-header': 'openMessage',
			'click .star': 'toggleMessageStar'
		},
		modelEvents: {
			change: 'render'
		},
		onRender: function() {
			// Get rid of that pesky wrapping-div.
			// Assumes 1 child element present in template.
			this.$el = this.$el.children();
			// Unwrap the element to prevent infinitely
			// nesting elements during re-render.
			this.$el.unwrap();
			this.setElement(this.$el);

			var displayName = this.model.get('from');
			// Don't show any placeholder if 'from' isn't set
			if (displayName) {
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).height('32px');
					$(a).imageplaceholder(displayName, displayName);
				});
			}

			$('.action.delete').tooltip({placement: 'left'});
		},
		toggleMessageStar: function(event) {
			event.stopPropagation();

			var starred = this.model.get('flags').get('flagged');

			// directly change star state in the interface for quick feedback
			if (starred) {
				this.getUI('star')
						.removeClass('icon-starred')
						.addClass('icon-star');
			} else {
				this.getUI('star')
						.removeClass('icon-star')
						.addClass('icon-starred');
			}

			// TODO: globals are bad :-/
			var account = require('state').currentAccount;
			var folder = require('state').currentFolder;

			Radio.message.trigger('flag', account, folder, this.model, 'flagged', !starred);
		},
		openMessage: function(event) {
			event.stopPropagation();
			$('#mail-message').removeClass('hidden-mobile');
			var account = require('state').currentAccount;
			var folder = require('state').currentFolder;
			Radio.message.trigger('load', account, folder, this.model, {
				force: true
			});
		},
		deleteMessage: function(event) {
			event.stopPropagation();
			var thisModel = this.model;
			this.getUI('iconDelete').removeClass('icon-delete').addClass('icon-loading-small');
			$('.tooltip').remove();

			thisModel.get('flags').set('unseen', false);
			var folder = require('state').currentFolder;
			var count = folder.get('total');
			folder.set('total', count - 1);

			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index - 1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index + 1);
			}
			if (require('state').currentMessage && require('state').currentMessage.get('id') === thisModel.id) {
				if (nextMessage) {
					var nextAccount = require('state').currentAccount;
					var nextFolder = require('state').currentFolder;
					Radio.message.trigger('load', nextAccount, nextFolder, nextMessage);
				}
			}

			this.$el.addClass('transparency').slideUp(function() {
				$('.tooltip').remove();
				thisModelCollection.remove(thisModel);

				// manually trigger mouseover event for current mouse position
				// in order to create a tooltip for the next message if needed
				if (event.clientX) {
					$(document.elementFromPoint(event.clientX, event.clientY)).trigger('mouseover');
				}
			});

			// really delete the message
			var account = require('state').currentAccount;
			var deleting = Radio.message.request('delete', account, folder, this.model);

			$.when(deleting).fail(function() {
				// TODO: move to controller
				Radio.ui.trigger('error:show', t('mail', 'Error while deleting message.'));

				// Restore counter
				count = folder.get('total');
				folder.set('total', count + 1);
			});
		}
	});
});
<commit_msg>Backbone models are not serialized automatically anymore<commit_after>/**
 * Mail
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 * @copyright Christoph Wurst 2015, 2016
 */

define(function(require) {
	'use strict';

	var $ = require('jquery');
	var _ = require('underscore');
	var Handlebars = require('handlebars');
	var Marionette = require('marionette');
	var Radio = require('radio');
	var MessageTemplate = require('text!templates/message-list-item.html');

	return Marionette.View.extend({
		template: Handlebars.compile(MessageTemplate),
		ui: {
			iconDelete: '.action.delete',
			star: '.star'
		},
		events: {
			'click .action.delete': 'deleteMessage',
			'click .mail-message-header': 'openMessage',
			'click .star': 'toggleMessageStar'
		},
		modelEvents: {
			change: 'render'
		},
		serializeModel: function() {
			return this.model.toJSON();
		},
		onRender: function() {
			// Get rid of that pesky wrapping-div.
			// Assumes 1 child element present in template.
			this.$el = this.$el.children();
			// Unwrap the element to prevent infinitely
			// nesting elements during re-render.
			this.$el.unwrap();
			this.setElement(this.$el);

			var displayName = this.model.get('from');
			// Don't show any placeholder if 'from' isn't set
			if (displayName) {
				_.each(this.$el.find('.avatar'), function(a) {
					$(a).height('32px');
					$(a).imageplaceholder(displayName, displayName);
				});
			}

			$('.action.delete').tooltip({placement: 'left'});
		},
		toggleMessageStar: function(event) {
			event.stopPropagation();

			var starred = this.model.get('flags').get('flagged');

			// directly change star state in the interface for quick feedback
			if (starred) {
				this.getUI('star')
						.removeClass('icon-starred')
						.addClass('icon-star');
			} else {
				this.getUI('star')
						.removeClass('icon-star')
						.addClass('icon-starred');
			}

			// TODO: globals are bad :-/
			var account = require('state').currentAccount;
			var folder = require('state').currentFolder;

			Radio.message.trigger('flag', account, folder, this.model, 'flagged', !starred);
		},
		openMessage: function(event) {
			event.stopPropagation();
			$('#mail-message').removeClass('hidden-mobile');
			var account = require('state').currentAccount;
			var folder = require('state').currentFolder;
			Radio.message.trigger('load', account, folder, this.model, {
				force: true
			});
		},
		deleteMessage: function(event) {
			event.stopPropagation();
			var thisModel = this.model;
			this.getUI('iconDelete').removeClass('icon-delete').addClass('icon-loading-small');
			$('.tooltip').remove();

			thisModel.get('flags').set('unseen', false);
			var folder = require('state').currentFolder;
			var count = folder.get('total');
			folder.set('total', count - 1);

			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index - 1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index + 1);
			}
			if (require('state').currentMessage && require('state').currentMessage.get('id') === thisModel.id) {
				if (nextMessage) {
					var nextAccount = require('state').currentAccount;
					var nextFolder = require('state').currentFolder;
					Radio.message.trigger('load', nextAccount, nextFolder, nextMessage);
				}
			}

			this.$el.addClass('transparency').slideUp(function() {
				$('.tooltip').remove();
				thisModelCollection.remove(thisModel);

				// manually trigger mouseover event for current mouse position
				// in order to create a tooltip for the next message if needed
				if (event.clientX) {
					$(document.elementFromPoint(event.clientX, event.clientY)).trigger('mouseover');
				}
			});

			// really delete the message
			var account = require('state').currentAccount;
			var deleting = Radio.message.request('delete', account, folder, this.model);

			$.when(deleting).fail(function() {
				// TODO: move to controller
				Radio.ui.trigger('error:show', t('mail', 'Error while deleting message.'));

				// Restore counter
				count = folder.get('total');
				folder.set('total', count + 1);
			});
		}
	});
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Switch from 'react-router-dom/Switch';
import Route from 'react-router-dom/Route';
import Link from 'react-router-dom/Link';
import { connectFor } from '@folio/stripes-connect';
import { modules } from 'stripes-config'; // eslint-disable-line
import { withRouter } from 'react-router';
import NavList from '@folio/stripes-components/lib/NavList';
import NavListSection from '@folio/stripes-components/lib/NavListSection';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';

import AddContext from '../../AddContext';
import { stripesShape } from '../../Stripes';

const settingsModules = [].concat(
  (modules.app || []).filter(m => m.hasSettings),
  (modules.settings || []),
);

const Settings = (props) => {
  const stripes = props.stripes;
  const navLinks = settingsModules.sort(
    (x, y) => x.displayName > y.displayName,
  ).filter(
    x => stripes.hasPerm(`settings.${x.module.replace(/^@folio\//, '')}.enabled`),
  ).map(m => (
    <Link
      key={m.route}
      to={`/settings${m.route}`}
    >
      {m.displayName}
    </Link>
  ));

  const routes = settingsModules.filter(
    x => stripes.hasPerm(`settings.${x.module.replace(/^@folio\//, '')}.enabled`),
  ).map((m) => {
    const connect = connectFor(m.module, stripes.epics, stripes.logger);
    const Current = connect(m.getModule());
    const moduleStripes = stripes.clone({ connect });

    return (<Route
      path={`/settings${m.route}`}
      key={m.route}
      render={props2 => (
        <AddContext context={{ stripes: moduleStripes }}>
          <Current {...props2} stripes={moduleStripes} showSettings />
        </AddContext>
      )}
    />);
  });

  return (
    <Paneset>
      <Pane defaultWidth="20%" paneTitle="Settings">
        <NavList>
          <NavListSection activeLink={props.location.pathname} label="App Settings">
            {navLinks}
          </NavListSection>
        </NavList>
      </Pane>
      <Switch>
        {routes}
        <Route component={() =>
Choose settings
} />
      </Switch>
    </Paneset>
  );
};

Settings.propTypes = {
  stripes: stripesShape.isRequired,
  location: PropTypes.shape({
    pathname: PropTypes.string,
  }).isRequired,
};

export default withRouter(Settings);
<commit_msg>Re-added 'About' to settings since it had disappeared during a merge<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Switch from 'react-router-dom/Switch';
import Route from 'react-router-dom/Route';
import Link from 'react-router-dom/Link';
import { connectFor } from '@folio/stripes-connect';
import { modules } from 'stripes-config'; // eslint-disable-line
import { withRouter } from 'react-router';
import NavList from '@folio/stripes-components/lib/NavList';
import NavListSection from '@folio/stripes-components/lib/NavListSection';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';
import { FormattedMessage } from 'react-intl';

import About from '../About';
import AddContext from '../../AddContext';
import { stripesShape } from '../../Stripes';

const settingsModules = [].concat(
  (modules.app || []).filter(m => m.hasSettings),
  (modules.settings || []),
);

const Settings = (props) => {
  const stripes = props.stripes;
  const navLinks = settingsModules.sort(
    (x, y) => x.displayName > y.displayName,
  ).filter(
    x => stripes.hasPerm(`settings.${x.module.replace(/^@folio\//, '')}.enabled`),
  ).map(m => (
    <Link
      key={m.route}
      to={`/settings${m.route}`}
    >
      {m.displayName}
    </Link>
  ));

  const routes = settingsModules.filter(
    x => stripes.hasPerm(`settings.${x.module.replace(/^@folio\//, '')}.enabled`),
  ).map((m) => {
    const connect = connectFor(m.module, stripes.epics, stripes.logger);
    const Current = connect(m.getModule());
    const moduleStripes = stripes.clone({ connect });

    return (<Route
      path={`/settings${m.route}`}
      key={m.route}
      render={props2 => (
        <AddContext context={{ stripes: moduleStripes }}>
          <Current {...props2} stripes={moduleStripes} showSettings />
        </AddContext>
      )}
    />);
  });

  return (
    <Paneset>
      <Pane defaultWidth="20%" paneTitle="Settings">
        <NavList>
          <NavListSection activeLink={props.location.pathname} label="App Settings">
            {navLinks}
          </NavListSection>
        </NavList>

        <NavListSection label="System Information" activeLink={props.location.pathname}>
          <Link to="/settings/about"><FormattedMessage id="stripes-core.front.about" /></Link>
        </NavListSection>
      </Pane>
      <Switch>
        {routes}
        <Route path="/settings/about" component={() => <About stripes={stripes} />} key="about" />
        <Route component={() =>
Choose settings
} />
      </Switch>
    </Paneset>
  );
};

Settings.propTypes = {
  stripes: stripesShape.isRequired,
  location: PropTypes.shape({
    pathname: PropTypes.string,
  }).isRequired,
};

export default withRouter(Settings);
<|endoftext|>


<commit_before>
function toggleClipper(){
	var cliptype = document.getElementById('clip_type').value;
	document.getElementById('county_clipper_field').style.display = 'none';
	document.getElementById('city_clipper_field').style.display = 'none';
	document.getElementById('extent_clipper_field').style.display = 'none';
	switch(cliptype) {
		case 'County':
			document.getElementById('county_clipper_field').style.display = 'block';
			break;
		case 'City':
			document.getElementById('city_clipper_field').style.display = 'block';
			break;
		case 'Extent':
			document.getElementById('extent_clipper_field').style.display = 'block';
			showExtentMapWindow();
			break;
		case 'State':
			var allowed_vectors = ['SHAPE','FILEGDB'];
			var allowed_projections = ['26915'];
			var vector_formats = document.getElementById('vector_format_type');
			var vector_options = vector_formats.getElementsByTagName("option");
			for(var v = 0; v < vector_options.length; v++){
				if(allowed_vectors.indexOf(vector_options[v].value) == -1){
					vector_options[v].disabled = true;
				}else{
					if(vector_options[v].value == allowed_vectors[0]){
						vector_options[v].selected = 'selected';
					}
				}
			}
			var projection_types = document.getElementById('projection');
			var projection_options = projection_types.getElementsByTagName("option");
			for(var v = 0; v < projection_options.length; v++){
				if(allowed_projections.indexOf(projection_options[v].value) == -1){
					projection_options[v].disabled = true;
				}else{
					projection_options[v].selected = 'selected';
				}
			}
			break;
	}
}

var mapWindow;
var map;
Ext.onReady(function(){
	Ext.define('Ext.ux.LeafletMapView',{
		extend: 'Ext.Component',
		alias: 'widget.leafletmapview',
		config: { map: null},
		afterRender: function(t,eOpts){
			this.callParent(arguments);
			var leafletRef = window.L;
			if(leafletRef == null){
				this.update('No leaflet library loaded');
			}else{
				map = L.map(this.getId());
				map.setView([34.749, -92.286], 8);
				this.setMap(map);
				L.esri.basemapLayer('Gray').addTo(map);
    			L.esri.basemapLayer('GrayLabels').addTo(map);
			}

		}
	});
});

function showExtentMapWindow(){
	mapWindow = Ext.create('Ext.window.Window',{
		layout: 'fit',
		title: 'Extent Map Window',
		id: 'MapWindow',
		modal: true,
		resizeable: false,
		width: '90%',
		height: '80%',
		closeAction: 'destroy',
		items:[
			{
				xtype: 'leafletmapview'
			}
		],
		bbar: [{
			xtype: 'button',
			text: 'Use Current Extent',
			handler: function(){
				var extent = map.getBounds().toBBoxString();
				document.getElementById('extent_clipper').value = extent;
				mapWindow.close();
			}
		}]
	});
	mapWindow.show();
	map.invalidateSize();
}

function addMainMap(){
	var map = L.map('map').setView([34.749, -92.286], 8);

    L.esri.basemapLayer('Gray').addTo(map);
    L.esri.basemapLayer('GrayLabels').addTo(map);
}

function addACFMap(){
	var map = L.map('map').setView([34.749, -92.286], 8);

    L.esri.basemapLayer('Gray').addTo(map);
    L.esri.basemapLayer('GrayLabels').addTo(map);

    var ACFStatusLayer = L.esri.featureLayer('http://services.arcgis.com/PwY9ZuZRDiI5nXUB/ArcGIS/rest/services/ACF_Status_20140827/FeatureServer/0', {
        simplifyFactor: 0.5,
        fillOpacity: 1,
        color: '#000000 ',
        style: function (feature) {
            if (feature.properties.ACFYear === 2004) {
                return { fillColor: '#990000', weight: 2 };
            } else if (feature.properties.ACFYear === 2005) {
                return { fillColor: '#A00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2006) {
                return { fillColor: '#B00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2007) {
                return { fillColor: '#C00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2008) {
                return { fillColor: '#D00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2009) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2010) {
                return { fillColor: 'white', weight: 2 };
            } else if (feature.properties.ACFYear === 2011) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2012) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2013) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2014) {
                return { fillColor: '#000066', weight: 2 };
            }
        }
    }).addTo(map);

    var oldStyle;
    var oldID;
    var popup;
    ACFStatusLayer.on('mouseover', function (e) {
        popup = L.popup({offset: L.point(0, -5), keepInView: true, closeButton: false });
        popup.setLatLng(e.latlng);
        popup.setContent(e.layer.feature.properties.COUNTY_NAM + "
" + e.layer.feature.properties.ACFYear);
        popup.openOn(map);
        oldID = e.layer.feature.id;
        oldStyle = { fillColor: e.layer.options.fillColor, weight: e.layer.options.weight };
        e.layer.bringToFront();
        ACFStatusLayer.setFeatureStyle(e.layer.feature.id, {
            fillColor: '#9D78D2',
            weight: 3,
            opacity: 1
        });
    });

    ACFStatusLayer.on('mouseout', function (e) {
        ACFStatusLayer.setFeatureStyle(oldID, oldStyle);
    });
    document.getElementById('searchform').style.visibility = "hidden";
    document.getElementById('searchform').style.height = 0;

    var overlays = {
    	"ACF Status": ACFStatusLayer
    };

    L.control.layers(overlays).addTo(map);
}

function addCAMPMap(){

}
<commit_msg>Changes on download page for Statewide<commit_after>
function toggleClipper(){
	var cliptype = document.getElementById('clip_type').value;
	document.getElementById('county_clipper_field').style.display = 'none';
	document.getElementById('city_clipper_field').style.display = 'none';
	document.getElementById('extent_clipper_field').style.display = 'none';
	var vector_formats = document.getElementById('vector_format_type');
			var vector_options = vector_formats.getElementsByTagName("option");
			for(var v = 0; v < vector_options.length; v++){
				vector_options[v].disabled = false;
			}
			var projection_types = document.getElementById('projection');
			var projection_options = projection_types.getElementsByTagName("option");
			for(var v = 0; v < projection_options.length; v++){
				projection_options[v].disabled = false;
			}
	switch(cliptype) {
		case 'County':
			document.getElementById('county_clipper_field').style.display = 'block';
			break;
		case 'City':
			document.getElementById('city_clipper_field').style.display = 'block';
			break;
		case 'Extent':
			document.getElementById('extent_clipper_field').style.display = 'block';
			showExtentMapWindow();
			break;
		case 'State':
			var allowed_vectors = ['SHAPE','FILEGDB'];
			var allowed_projections = ['26915'];
			var vector_formats = document.getElementById('vector_format_type');
			var vector_options = vector_formats.getElementsByTagName("option");
			for(var v = 0; v < vector_options.length; v++){
				if(allowed_vectors.indexOf(vector_options[v].value) == -1){
					vector_options[v].disabled = true;
				}else{
					if(vector_options[v].value == allowed_vectors[0]){
						vector_options[v].selected = 'selected';
					}
				}
			}
			var projection_types = document.getElementById('projection');
			var projection_options = projection_types.getElementsByTagName("option");
			for(var v = 0; v < projection_options.length; v++){
				if(allowed_projections.indexOf(projection_options[v].value) == -1){
					projection_options[v].disabled = true;
				}else{
					projection_options[v].selected = 'selected';
				}
			}
			break;
	}
}

var mapWindow;
var map;
Ext.onReady(function(){
	Ext.define('Ext.ux.LeafletMapView',{
		extend: 'Ext.Component',
		alias: 'widget.leafletmapview',
		config: { map: null},
		afterRender: function(t,eOpts){
			this.callParent(arguments);
			var leafletRef = window.L;
			if(leafletRef == null){
				this.update('No leaflet library loaded');
			}else{
				map = L.map(this.getId());
				map.setView([34.749, -92.286], 8);
				this.setMap(map);
				L.esri.basemapLayer('Gray').addTo(map);
    			L.esri.basemapLayer('GrayLabels').addTo(map);
			}

		}
	});
});

function showExtentMapWindow(){
	mapWindow = Ext.create('Ext.window.Window',{
		layout: 'fit',
		title: 'Extent Map Window',
		id: 'MapWindow',
		modal: true,
		resizeable: false,
		width: '90%',
		height: '80%',
		closeAction: 'destroy',
		items:[
			{
				xtype: 'leafletmapview'
			}
		],
		bbar: [{
			xtype: 'button',
			text: 'Use Current Extent',
			handler: function(){
				var extent = map.getBounds().toBBoxString();
				document.getElementById('extent_clipper').value = extent;
				mapWindow.close();
			}
		}]
	});
	mapWindow.show();
	map.invalidateSize();
}

function addMainMap(){
	var map = L.map('map').setView([34.749, -92.286], 8);

    L.esri.basemapLayer('Gray').addTo(map);
    L.esri.basemapLayer('GrayLabels').addTo(map);
}

function addACFMap(){
	var map = L.map('map').setView([34.749, -92.286], 8);

    L.esri.basemapLayer('Gray').addTo(map);
    L.esri.basemapLayer('GrayLabels').addTo(map);

    var ACFStatusLayer = L.esri.featureLayer('http://services.arcgis.com/PwY9ZuZRDiI5nXUB/ArcGIS/rest/services/ACF_Status_20140827/FeatureServer/0', {
        simplifyFactor: 0.5,
        fillOpacity: 1,
        color: '#000000 ',
        style: function (feature) {
            if (feature.properties.ACFYear === 2004) {
                return { fillColor: '#990000', weight: 2 };
            } else if (feature.properties.ACFYear === 2005) {
                return { fillColor: '#A00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2006) {
                return { fillColor: '#B00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2007) {
                return { fillColor: '#C00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2008) {
                return { fillColor: '#D00000', weight: 2 };
            } else if (feature.properties.ACFYear === 2009) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2010) {
                return { fillColor: 'white', weight: 2 };
            } else if (feature.properties.ACFYear === 2011) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2012) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2013) {
                return { fillColor: '#E80000', weight: 2 };
            } else if (feature.properties.ACFYear === 2014) {
                return { fillColor: '#000066', weight: 2 };
            }
        }
    }).addTo(map);

    var oldStyle;
    var oldID;
    var popup;
    ACFStatusLayer.on('mouseover', function (e) {
        popup = L.popup({offset: L.point(0, -5), keepInView: true, closeButton: false });
        popup.setLatLng(e.latlng);
        popup.setContent(e.layer.feature.properties.COUNTY_NAM + "
" + e.layer.feature.properties.ACFYear);
        popup.openOn(map);
        oldID = e.layer.feature.id;
        oldStyle = { fillColor: e.layer.options.fillColor, weight: e.layer.options.weight };
        e.layer.bringToFront();
        ACFStatusLayer.setFeatureStyle(e.layer.feature.id, {
            fillColor: '#9D78D2',
            weight: 3,
            opacity: 1
        });
    });

    ACFStatusLayer.on('mouseout', function (e) {
        ACFStatusLayer.setFeatureStyle(oldID, oldStyle);
    });
    document.getElementById('searchform').style.visibility = "hidden";
    document.getElementById('searchform').style.height = 0;

    var overlays = {
    	"ACF Status": ACFStatusLayer
    };

    L.control.layers(overlays).addTo(map);
}

function addCAMPMap(){

}
<|endoftext|>


<commit_before>var Shape = require('./Shape');
var ConvexPolyhedron = require('./ConvexPolyhedron');
var Vec3 = require('../math/Vec3');
var Utils = require('../utils/Utils');

module.exports = Heightfield;

/**
 * Heightfield shape class. Height data is given as an array. These data points are spread out evenly with a given distance.
 * @class Heightfield
 * @extends Shape
 * @constructor
 * @param {Array} data An array of Y values that will be used to construct the terrain.
 * @param {object} options
 * @param {Number} [options.minValue] Minimum value of the data points in the data array. Will be computed automatically if not given.
 * @param {Number} [options.maxValue] Maximum value.
 * @param {Number} [options.elementSize=0.1] World spacing between the data points in X direction.
 * @todo Should be possible to use along all axes, not just y
 *
 * @example
 *     // Generate some height data (y-values).
 *     var data = [];
 *     for(var i = 0; i < 1000; i++){
 *         var y = 0.5 * Math.cos(0.2 * i);
 *         data.push(y);
 *     }
 *
 *     // Create the heightfield shape
 *     var heightfieldShape = new Heightfield(data, {
 *         elementSize: 1 // Distance between the data points in X and Y directions
 *     });
 *     var heightfieldBody = new Body();
 *     heightfieldBody.addShape(heightfieldShape);
 *     world.addBody(heightfieldBody);
 */
function Heightfield(data, options){
    options = Utils.defaults(options, {
        maxValue : null,
        minValue : null,
        elementSize : 1
    });

    /**
     * An array of numbers, or height values, that are spread out along the x axis.
     * @property {array} data
     */
    this.data = data;

    /**
     * Max value of the data
     * @property {number} maxValue
     */
    this.maxValue = options.maxValue;

    /**
     * Max value of the data
     * @property {number} minValue
     */
    this.minValue = options.minValue;

    /**
     * The width of each element
     * @property {number} elementSize
     * @todo elementSizeX and Y
     */
    this.elementSize = options.elementSize;

    if(options.minValue === null){
        this.updateMinValue();
    }
    if(options.maxValue === null){
        this.updateMaxValue();
    }

    this.cacheEnabled = true;

    Shape.call(this);

    this.pillarConvex = new ConvexPolyhedron();
    this.pillarOffset = new Vec3();

    this.type = Shape.types.HEIGHTFIELD;
    this.updateBoundingSphereRadius();

    // "i_j_isUpper" => { convex: ..., offset: ... }
    // for example:
    // _cachedPillars["0_2_1"]
    this._cachedPillars = {};
}
Heightfield.prototype = new Shape();

/**
 * Call whenever you change the data array.
 * @method update
 */
Heightfield.prototype.update = function(){
    this._cachedPillars = {};
};

/**
 * @method updateMinValue
 * Update the .minValue property
 */
Heightfield.prototype.updateMinValue = function(){
    var data = this.data;
    var minValue = data[0][0];
    for(var i=0; i !== data.length; i++){
        for(var j=0; j !== data[i].length; j++){
            var v = data[i][j];
            if(v < minValue){
                minValue = v;
            }
        }
    }
    this.minValue = minValue;
};

/**
 * Update the .maxValue property
 * @method updateMaxValue
 */
Heightfield.prototype.updateMaxValue = function(){
    var data = this.data;
    var maxValue = data[0][0];
    for(var i=0; i !== data.length; i++){
        for(var j=0; j !== data[i].length; j++){
            var v = data[i][j];
            if(v > maxValue){
                maxValue = v;
            }
        }
    }
    this.maxValue = maxValue;
};

/**
 * Set the height value at an index. Don't forget to update maxValue and minValue after you're done.
 * @method setHeightValueAtIndex
 * @param {integer} xi
 * @param {integer} yi
 * @param {number} value
 */
Heightfield.prototype.setHeightValueAtIndex = function(xi, yi, value){
    var data = this.data;
    data[xi][yi] = value;

    // Invalidate cache
    this.clearCachedConvexTrianglePillar(xi, yi, false);
    if(xi > 0){
        this.clearCachedConvexTrianglePillar(xi - 1, yi, true);
        this.clearCachedConvexTrianglePillar(xi - 1, yi, false);
    }
    if(yi > 0){
        this.clearCachedConvexTrianglePillar(xi, yi - 1, true);
        this.clearCachedConvexTrianglePillar(xi, yi - 1, false);
    }
    if(yi > 0 && xi > 0){
        this.clearCachedConvexTrianglePillar(xi - 1, yi - 1, true);
    }
};

/**
 * Get max/min in a rectangle in the matrix data
 * @method getRectMinMax
 * @param  {integer} iMinX
 * @param  {integer} iMinY
 * @param  {integer} iMaxX
 * @param  {integer} iMaxY
 * @param  {array} [result] An array to store the results in.
 * @return {array} The result array, if it was passed in. Minimum will be at position 0 and max at 1.
 */
Heightfield.prototype.getRectMinMax = function (iMinX, iMinY, iMaxX, iMaxY, result) {
    result = result || [];

    // Get max and min of the data
    var data = this.data,
        max = this.minValue; // Set first value
    for(var i = iMinX; i < iMaxX; i++){
        for(var j = iMinY; j < iMaxY; j++){
            var height = data[i][j];
            if(height > max){
                max = height;
            }
        }
    }

    result[0] = this.minValue;
    result[1] = max;
};

/**
 * Get the index of a local position on the heightfield. The indexes indicate the rectangles, so if your terrain is made of N x N height data points, you will have rectangle indexes ranging from 0 to N-1.
 * @method getIndexOfPosition
 * @param  {number} x
 * @param  {number} y
 * @param  {array} result Two-element array
 * @param  {boolean} clamp If the position should be clamped to the heightfield edge.
 * @return {boolean}
 */
Heightfield.prototype.getIndexOfPosition = function (x, y, result, clamp) {

    // Get the index of the data points to test against
    var w = this.elementSize;
    var data = this.data;
    var xi = Math.floor(x / w);
    var yi = Math.floor(y / w);

    result[0] = xi;
    result[1] = yi;

    if(clamp){
        // Clamp index to edges
        if(xi < 0){ xi = 0; }
        if(yi < 0){ yi = 0; }
        if(xi >= data.length - 1){ xi = data.length - 1; }
        if(yi >= data[0].length - 1){ yi = data[0].length - 1; }
    }

    // Bail out if we are out of the terrain
    if(xi < 0 || yi < 0 || xi >= data.length-1 || yi >= data[0].length-1){
        return false;
    }

    return true;
};

Heightfield.prototype.getHeightAt = function(x, y, edgeClamp){
    var idx = [];
    this.getIndexOfPosition(x, y, idx, edgeClamp);

    // TODO: do it better
    var minmax = [];
    this.getRectMinMax(idx[0], idx[1] + 1, idx[0], idx[1] + 1, minmax);

    return (minmax[0] + minmax[1]) / 2; // average
};

Heightfield.prototype.getCacheConvexTrianglePillarKey = function(xi, yi, getUpperTriangle){
    return xi + '_' + yi + '_' + (getUpperTriangle ? 1 : 0);
};

Heightfield.prototype.getCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    return this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)];
};

Heightfield.prototype.setCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle, convex, offset){
    this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)] = {
        convex: convex,
        offset: offset
    };
};

Heightfield.prototype.clearCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    delete this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)];
};

/**
 * Get a triangle in the terrain in the form of a triangular convex shape.
 * @method getConvexTrianglePillar
 * @param  {integer} i
 * @param  {integer} j
 * @param  {boolean} getUpperTriangle
 */
Heightfield.prototype.getConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    var result = this.pillarConvex;
    var offsetResult = this.pillarOffset;

    if(this.cacheEnabled){
        var data = this.getCachedConvexTrianglePillar(xi, yi, getUpperTriangle);
        if(data){
            this.pillarConvex = data.convex;
            this.pillarOffset = data.offset;
            return;
        }

        result = new ConvexPolyhedron();
        offsetResult = new Vec3();

        this.pillarConvex = result;
        this.pillarOffset = offsetResult;
    }

    var data = this.data;
    var elementSize = this.elementSize;
    var faces = result.faces;

    // Reuse verts if possible
    result.vertices.length = 6;
    for (var i = 0; i < 6; i++) {
        if(!result.vertices[i]){
            result.vertices[i] = new Vec3();
        }
    }

    // Reuse faces if possible
    faces.length = 5;
    for (var i = 0; i < 5; i++) {
        if(!faces[i]){
            faces[i] = [];
        }
    }

    var verts = result.vertices;

    var h = (Math.min(
        data[xi][yi],
        data[xi+1][yi],
        data[xi][yi+1],
        data[xi+1][yi+1]
    ) - this.minValue ) / 2 + this.minValue;

    if (!getUpperTriangle) {

        // Center of the triangle pillar - all polygons are given relative to this one
        offsetResult.set(
            (xi + 0.25) * elementSize, // sort of center of a triangle
            (yi + 0.25) * elementSize,
            h // vertical center
        );

        // Top triangle verts
        verts[0].set(
            -0.25 * elementSize,
            -0.25 * elementSize,
            data[xi][yi] - h
        );
        verts[1].set(
            0.75 * elementSize,
            -0.25 * elementSize,
            data[xi + 1][yi] - h
        );
        verts[2].set(
            -0.25 * elementSize,
            0.75 * elementSize,
            data[xi][yi + 1] - h
        );

        // bottom triangle verts
        verts[3].set(
            -0.25 * elementSize,
            -0.25 * elementSize,
            -h-1
        );
        verts[4].set(
            0.75 * elementSize,
            -0.25 * elementSize,
            -h-1
        );
        verts[5].set(
            -0.25 * elementSize,
            0.75  * elementSize,
            -h-1
        );

        // top triangle
        faces[0][0] = 0;
        faces[0][1] = 1;
        faces[0][2] = 2;

        // bottom triangle
        faces[1][0] = 5;
        faces[1][1] = 4;
        faces[1][2] = 3;

        // -x facing quad
        faces[2][0] = 0;
        faces[2][1] = 2;
        faces[2][2] = 5;
        faces[2][3] = 3;

        // -y facing quad
        faces[3][0] = 1;
        faces[3][1] = 0;
        faces[3][2] = 3;
        faces[3][3] = 4;

        // +xy facing quad
        faces[4][0] = 4;
        faces[4][1] = 5;
        faces[4][2] = 2;
        faces[4][3] = 1;

    } else {

        // Center of the triangle pillar - all polygons are given relative to this one
        offsetResult.set(
            (xi + 0.75) * elementSize, // sort of center of a triangle
            (yi + 0.75) * elementSize,
            h // vertical center
        );

        // Top triangle verts
        verts[0].set(
            0.25 * elementSize,
            0.25 * elementSize,
            data[xi + 1][yi + 1] - h
        );
        verts[1].set(
            -0.75 * elementSize,
            0.25 * elementSize,
            data[xi][yi + 1] - h
        );
        verts[2].set(
            0.25 * elementSize,
            -0.75 * elementSize,
            data[xi + 1][yi] - h
        );

        // bottom triangle verts
        verts[3].set(
            0.25 * elementSize,
            0.25 * elementSize,
            - h-1
        );
        verts[4].set(
            -0.75 * elementSize,
            0.25 * elementSize,
            - h-1
        );
        verts[5].set(
            0.25 * elementSize,
            -0.75 * elementSize,
            - h-1
        );

        // Top triangle
        faces[0][0] = 0;
        faces[0][1] = 1;
        faces[0][2] = 2;

        // bottom triangle
        faces[1][0] = 5;
        faces[1][1] = 4;
        faces[1][2] = 3;

        // +x facing quad
        faces[2][0] = 2;
        faces[2][1] = 5;
        faces[2][2] = 3;
        faces[2][3] = 0;

        // +y facing quad
        faces[3][0] = 3;
        faces[3][1] = 4;
        faces[3][2] = 1;
        faces[3][3] = 0;

        // -xy facing quad
        faces[4][0] = 1;
        faces[4][1] = 4;
        faces[4][2] = 5;
        faces[4][3] = 2;
    }

    result.computeNormals();
    result.computeEdges();
    result.updateBoundingSphereRadius();

    this.setCachedConvexTrianglePillar(xi, yi, getUpperTriangle, result, offsetResult);
};

Heightfield.prototype.calculateLocalInertia = function(mass, target){
    target = target || new Vec3();
    target.set(0, 0, 0);
    return target;
};

Heightfield.prototype.volume = function(){
    return Number.MAX_VALUE; // The terrain is infinite
};

Heightfield.prototype.calculateWorldAABB = function(pos, quat, min, max){
    // TODO: do it properly
    min.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
    max.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
};

Heightfield.prototype.updateBoundingSphereRadius = function(){
    // Use the bounding box of the min/max values
    var data = this.data,
        s = this.elementSize;
    this.boundingSphereRadius = new Vec3(data.length * s, data[0].length * s, Math.max(Math.abs(this.maxValue), Math.abs(this.minValue))).norm();
};<commit_msg>fix Heightfield.getRectMinMax()<commit_after>var Shape = require('./Shape');
var ConvexPolyhedron = require('./ConvexPolyhedron');
var Vec3 = require('../math/Vec3');
var Utils = require('../utils/Utils');

module.exports = Heightfield;

/**
 * Heightfield shape class. Height data is given as an array. These data points are spread out evenly with a given distance.
 * @class Heightfield
 * @extends Shape
 * @constructor
 * @param {Array} data An array of Y values that will be used to construct the terrain.
 * @param {object} options
 * @param {Number} [options.minValue] Minimum value of the data points in the data array. Will be computed automatically if not given.
 * @param {Number} [options.maxValue] Maximum value.
 * @param {Number} [options.elementSize=0.1] World spacing between the data points in X direction.
 * @todo Should be possible to use along all axes, not just y
 *
 * @example
 *     // Generate some height data (y-values).
 *     var data = [];
 *     for(var i = 0; i < 1000; i++){
 *         var y = 0.5 * Math.cos(0.2 * i);
 *         data.push(y);
 *     }
 *
 *     // Create the heightfield shape
 *     var heightfieldShape = new Heightfield(data, {
 *         elementSize: 1 // Distance between the data points in X and Y directions
 *     });
 *     var heightfieldBody = new Body();
 *     heightfieldBody.addShape(heightfieldShape);
 *     world.addBody(heightfieldBody);
 */
function Heightfield(data, options){
    options = Utils.defaults(options, {
        maxValue : null,
        minValue : null,
        elementSize : 1
    });

    /**
     * An array of numbers, or height values, that are spread out along the x axis.
     * @property {array} data
     */
    this.data = data;

    /**
     * Max value of the data
     * @property {number} maxValue
     */
    this.maxValue = options.maxValue;

    /**
     * Max value of the data
     * @property {number} minValue
     */
    this.minValue = options.minValue;

    /**
     * The width of each element
     * @property {number} elementSize
     * @todo elementSizeX and Y
     */
    this.elementSize = options.elementSize;

    if(options.minValue === null){
        this.updateMinValue();
    }
    if(options.maxValue === null){
        this.updateMaxValue();
    }

    this.cacheEnabled = true;

    Shape.call(this);

    this.pillarConvex = new ConvexPolyhedron();
    this.pillarOffset = new Vec3();

    this.type = Shape.types.HEIGHTFIELD;
    this.updateBoundingSphereRadius();

    // "i_j_isUpper" => { convex: ..., offset: ... }
    // for example:
    // _cachedPillars["0_2_1"]
    this._cachedPillars = {};
}
Heightfield.prototype = new Shape();

/**
 * Call whenever you change the data array.
 * @method update
 */
Heightfield.prototype.update = function(){
    this._cachedPillars = {};
};

/**
 * @method updateMinValue
 * Update the .minValue property
 */
Heightfield.prototype.updateMinValue = function(){
    var data = this.data;
    var minValue = data[0][0];
    for(var i=0; i !== data.length; i++){
        for(var j=0; j !== data[i].length; j++){
            var v = data[i][j];
            if(v < minValue){
                minValue = v;
            }
        }
    }
    this.minValue = minValue;
};

/**
 * Update the .maxValue property
 * @method updateMaxValue
 */
Heightfield.prototype.updateMaxValue = function(){
    var data = this.data;
    var maxValue = data[0][0];
    for(var i=0; i !== data.length; i++){
        for(var j=0; j !== data[i].length; j++){
            var v = data[i][j];
            if(v > maxValue){
                maxValue = v;
            }
        }
    }
    this.maxValue = maxValue;
};

/**
 * Set the height value at an index. Don't forget to update maxValue and minValue after you're done.
 * @method setHeightValueAtIndex
 * @param {integer} xi
 * @param {integer} yi
 * @param {number} value
 */
Heightfield.prototype.setHeightValueAtIndex = function(xi, yi, value){
    var data = this.data;
    data[xi][yi] = value;

    // Invalidate cache
    this.clearCachedConvexTrianglePillar(xi, yi, false);
    if(xi > 0){
        this.clearCachedConvexTrianglePillar(xi - 1, yi, true);
        this.clearCachedConvexTrianglePillar(xi - 1, yi, false);
    }
    if(yi > 0){
        this.clearCachedConvexTrianglePillar(xi, yi - 1, true);
        this.clearCachedConvexTrianglePillar(xi, yi - 1, false);
    }
    if(yi > 0 && xi > 0){
        this.clearCachedConvexTrianglePillar(xi - 1, yi - 1, true);
    }
};

/**
 * Get max/min in a rectangle in the matrix data
 * @method getRectMinMax
 * @param  {integer} iMinX
 * @param  {integer} iMinY
 * @param  {integer} iMaxX
 * @param  {integer} iMaxY
 * @param  {array} [result] An array to store the results in.
 * @return {array} The result array, if it was passed in. Minimum will be at position 0 and max at 1.
 */
Heightfield.prototype.getRectMinMax = function (iMinX, iMinY, iMaxX, iMaxY, result) {
    result = result || [];

    // Get max and min of the data
    var data = this.data,
        max = this.minValue; // Set first value
    for(var i = iMinX; i <= iMaxX; i++){
        for(var j = iMinY; j <= iMaxY; j++){
            var height = data[i][j];
            if(height > max){
                max = height;
            }
        }
    }

    result[0] = this.minValue;
    result[1] = max;
};

/**
 * Get the index of a local position on the heightfield. The indexes indicate the rectangles, so if your terrain is made of N x N height data points, you will have rectangle indexes ranging from 0 to N-1.
 * @method getIndexOfPosition
 * @param  {number} x
 * @param  {number} y
 * @param  {array} result Two-element array
 * @param  {boolean} clamp If the position should be clamped to the heightfield edge.
 * @return {boolean}
 */
Heightfield.prototype.getIndexOfPosition = function (x, y, result, clamp) {

    // Get the index of the data points to test against
    var w = this.elementSize;
    var data = this.data;
    var xi = Math.floor(x / w);
    var yi = Math.floor(y / w);

    result[0] = xi;
    result[1] = yi;

    if(clamp){
        // Clamp index to edges
        if(xi < 0){ xi = 0; }
        if(yi < 0){ yi = 0; }
        if(xi >= data.length - 1){ xi = data.length - 1; }
        if(yi >= data[0].length - 1){ yi = data[0].length - 1; }
    }

    // Bail out if we are out of the terrain
    if(xi < 0 || yi < 0 || xi >= data.length-1 || yi >= data[0].length-1){
        return false;
    }

    return true;
};

Heightfield.prototype.getHeightAt = function(x, y, edgeClamp){
    var idx = [];
    this.getIndexOfPosition(x, y, idx, edgeClamp);

    // TODO: do it better
    var minmax = [];
    this.getRectMinMax(idx[0], idx[1] + 1, idx[0], idx[1] + 1, minmax);

    return (minmax[0] + minmax[1]) / 2; // average
};

Heightfield.prototype.getCacheConvexTrianglePillarKey = function(xi, yi, getUpperTriangle){
    return xi + '_' + yi + '_' + (getUpperTriangle ? 1 : 0);
};

Heightfield.prototype.getCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    return this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)];
};

Heightfield.prototype.setCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle, convex, offset){
    this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)] = {
        convex: convex,
        offset: offset
    };
};

Heightfield.prototype.clearCachedConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    delete this._cachedPillars[this.getCacheConvexTrianglePillarKey(xi, yi, getUpperTriangle)];
};

/**
 * Get a triangle in the terrain in the form of a triangular convex shape.
 * @method getConvexTrianglePillar
 * @param  {integer} i
 * @param  {integer} j
 * @param  {boolean} getUpperTriangle
 */
Heightfield.prototype.getConvexTrianglePillar = function(xi, yi, getUpperTriangle){
    var result = this.pillarConvex;
    var offsetResult = this.pillarOffset;

    if(this.cacheEnabled){
        var data = this.getCachedConvexTrianglePillar(xi, yi, getUpperTriangle);
        if(data){
            this.pillarConvex = data.convex;
            this.pillarOffset = data.offset;
            return;
        }

        result = new ConvexPolyhedron();
        offsetResult = new Vec3();

        this.pillarConvex = result;
        this.pillarOffset = offsetResult;
    }

    var data = this.data;
    var elementSize = this.elementSize;
    var faces = result.faces;

    // Reuse verts if possible
    result.vertices.length = 6;
    for (var i = 0; i < 6; i++) {
        if(!result.vertices[i]){
            result.vertices[i] = new Vec3();
        }
    }

    // Reuse faces if possible
    faces.length = 5;
    for (var i = 0; i < 5; i++) {
        if(!faces[i]){
            faces[i] = [];
        }
    }

    var verts = result.vertices;

    var h = (Math.min(
        data[xi][yi],
        data[xi+1][yi],
        data[xi][yi+1],
        data[xi+1][yi+1]
    ) - this.minValue ) / 2 + this.minValue;

    if (!getUpperTriangle) {

        // Center of the triangle pillar - all polygons are given relative to this one
        offsetResult.set(
            (xi + 0.25) * elementSize, // sort of center of a triangle
            (yi + 0.25) * elementSize,
            h // vertical center
        );

        // Top triangle verts
        verts[0].set(
            -0.25 * elementSize,
            -0.25 * elementSize,
            data[xi][yi] - h
        );
        verts[1].set(
            0.75 * elementSize,
            -0.25 * elementSize,
            data[xi + 1][yi] - h
        );
        verts[2].set(
            -0.25 * elementSize,
            0.75 * elementSize,
            data[xi][yi + 1] - h
        );

        // bottom triangle verts
        verts[3].set(
            -0.25 * elementSize,
            -0.25 * elementSize,
            -h-1
        );
        verts[4].set(
            0.75 * elementSize,
            -0.25 * elementSize,
            -h-1
        );
        verts[5].set(
            -0.25 * elementSize,
            0.75  * elementSize,
            -h-1
        );

        // top triangle
        faces[0][0] = 0;
        faces[0][1] = 1;
        faces[0][2] = 2;

        // bottom triangle
        faces[1][0] = 5;
        faces[1][1] = 4;
        faces[1][2] = 3;

        // -x facing quad
        faces[2][0] = 0;
        faces[2][1] = 2;
        faces[2][2] = 5;
        faces[2][3] = 3;

        // -y facing quad
        faces[3][0] = 1;
        faces[3][1] = 0;
        faces[3][2] = 3;
        faces[3][3] = 4;

        // +xy facing quad
        faces[4][0] = 4;
        faces[4][1] = 5;
        faces[4][2] = 2;
        faces[4][3] = 1;

    } else {

        // Center of the triangle pillar - all polygons are given relative to this one
        offsetResult.set(
            (xi + 0.75) * elementSize, // sort of center of a triangle
            (yi + 0.75) * elementSize,
            h // vertical center
        );

        // Top triangle verts
        verts[0].set(
            0.25 * elementSize,
            0.25 * elementSize,
            data[xi + 1][yi + 1] - h
        );
        verts[1].set(
            -0.75 * elementSize,
            0.25 * elementSize,
            data[xi][yi + 1] - h
        );
        verts[2].set(
            0.25 * elementSize,
            -0.75 * elementSize,
            data[xi + 1][yi] - h
        );

        // bottom triangle verts
        verts[3].set(
            0.25 * elementSize,
            0.25 * elementSize,
            - h-1
        );
        verts[4].set(
            -0.75 * elementSize,
            0.25 * elementSize,
            - h-1
        );
        verts[5].set(
            0.25 * elementSize,
            -0.75 * elementSize,
            - h-1
        );

        // Top triangle
        faces[0][0] = 0;
        faces[0][1] = 1;
        faces[0][2] = 2;

        // bottom triangle
        faces[1][0] = 5;
        faces[1][1] = 4;
        faces[1][2] = 3;

        // +x facing quad
        faces[2][0] = 2;
        faces[2][1] = 5;
        faces[2][2] = 3;
        faces[2][3] = 0;

        // +y facing quad
        faces[3][0] = 3;
        faces[3][1] = 4;
        faces[3][2] = 1;
        faces[3][3] = 0;

        // -xy facing quad
        faces[4][0] = 1;
        faces[4][1] = 4;
        faces[4][2] = 5;
        faces[4][3] = 2;
    }

    result.computeNormals();
    result.computeEdges();
    result.updateBoundingSphereRadius();

    this.setCachedConvexTrianglePillar(xi, yi, getUpperTriangle, result, offsetResult);
};

Heightfield.prototype.calculateLocalInertia = function(mass, target){
    target = target || new Vec3();
    target.set(0, 0, 0);
    return target;
};

Heightfield.prototype.volume = function(){
    return Number.MAX_VALUE; // The terrain is infinite
};

Heightfield.prototype.calculateWorldAABB = function(pos, quat, min, max){
    // TODO: do it properly
    min.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
    max.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
};

Heightfield.prototype.updateBoundingSphereRadius = function(){
    // Use the bounding box of the min/max values
    var data = this.data,
        s = this.elementSize;
    this.boundingSphereRadius = new Vec3(data.length * s, data[0].length * s, Math.max(Math.abs(this.maxValue), Math.abs(this.minValue))).norm();
};
<|endoftext|>