<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
  View,
  ActivityIndicator,
  Dimensions,
  DeviceEventEmitter,
  Animated,
  Easing,
  StatusBar,
  Image,
  Text,
  Platform
} from 'react-native';
import CONSTANTS from 'config/constants';
import throttle from 'lodash/throttle';
import moment from 'moment';

import Theme from 'config/theme';
// import { daysToDate } from 'helpers/date';
import ActionBtn from 'components/common/action-button';
import AreaCarousel from 'containers/map/area-carousel/';
import Clusters from 'components/map/clusters/';
import tracker from 'helpers/googleAnalytics';
import I18n from 'locales';
import MapView from 'react-native-maps';
import styles from './styles';

import { SensorManager } from 'NativeModules'; // eslint-disable-line

const Timer = require('react-native-timer');
const geoViewport = require('@mapbox/geo-viewport');

const { RNLocation: Location } = require('NativeModules'); // eslint-disable-line

const { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 10;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

const markerImage = require('assets/marker.png');
const alertWhite = require('assets/alert-white.png');
const markerCompassRedImage = require('assets/compass_circle_red.png');
const compassImage = require('assets/compass_direction.png');
const backgroundImage = require('assets/map_bg_gradient.png');

function renderLoading() {
  return (
    <View style={[styles.container, styles.loader]}>
      <ActivityIndicator
        color={Theme.colors.color1}
        style={{ height: 80 }}
        size="large"
      />
    </View>
  );
}

class Map extends Component {
  static navigatorStyle = {
    navBarTextColor: Theme.colors.color5,
    navBarButtonColor: Theme.colors.color5,
    topBarElevationShadowEnabled: false,
    navBarBackgroundColor: Theme.background.main,
    navBarTransparent: true,
    navBarTranslucent: true
  };

  constructor(props) {
    super(props);
    const { center } = props;
    const initialCoords = center || { lat: CONSTANTS.maps.lat, lon: CONSTANTS.maps.lng };
    this.eventLocation = null;
    this.eventOrientation = null;
    this.hasSetCoordinates = false;
    // Google maps lon and lat are inverted
    this.state = {
      renderMap: false,
      lastPosition: null,
      hasCompass: false,
      compassFallback: null,
      heading: null,
      geoMarkerOpacity: new Animated.Value(0.3),
      region: {
        latitude: initialCoords.lon,
        longitude: initialCoords.lat,
        latitudeDelta: LATITUDE_DELTA,
        longitudeDelta: LONGITUDE_DELTA
      },
      urlTile: null,
      markers: []
    };
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  componentDidMount() {
    tracker.trackScreenView('Map');

    if (Platform.OS === 'ios') {
      Location.requestWhenInUseAuthorization();
      StatusBar.setBarStyle('light-content');
    }

    this.geoLocate();
    this.updateMarkers();
    Timer.setTimeout(this, 'renderMap', this.renderMap, 500);
  }

  componentWillUpdate(nextProps, nextState) {
    if (this.state.selectedAlertCoordinates !== nextState.selectedAlertCoordinates && this.state.lastPosition !== null) {
      this.setCompassLine();
    }
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.areaCoordinates !== prevProps.areaCoordinates) {
      this.updateSelectedArea();
    }
    if (this.state.selectedAlertCoordinates !== prevState.selectedAlertCoordinates) {
      this.setHeaderTitle();
    }
  }

  componentWillUnmount() {
    Location.stopUpdatingLocation();

    Timer.clearTimeout(this, 'renderMap');
    if (this.eventLocation) {
      this.eventLocation.remove();
    }

    if (this.eventOrientation) {
      this.eventOrientation.remove();
    }

    if (Platform.OS === 'ios') {
      StatusBar.setBarStyle('default');
      Location.stopUpdatingHeading();
    } else {
      SensorManager.stopOrientation();
    }
  }

  onNavigatorEvent(event) {
    const { actionsPending, syncModalOpen, syncSkip, setCanDisplayAlerts, canDisplayAlerts } = this.props;
    if (event.id === 'didAppear') {
      if (actionsPending > 0 && !syncModalOpen && !syncSkip && !this.syncModalOpen) {
        this.syncModalOpen = true;
        this.props.setSyncModal(true);
        this.props.navigator.showModal({
          screen: 'ForestWatcher.Sync',
          passProps: {
            goBackDisabled: true
          }
        });
      }
      if (!canDisplayAlerts) {
        setCanDisplayAlerts(true);
      }
    }
  }

  onLayout = () => {
    if (this.hasSetCoordinates === false && this.props.areaCoordinates) {
      const options = { edgePadding: { top: 250, right: 250, bottom: 250, left: 250 }, animated: false };
      this.map.fitToCoordinates(this.props.areaCoordinates, options);
      this.hasSetCoordinates = true;
    }
  }

  getMapZoom() {
    const position = this.state.region;

    const bounds = [
      position.longitude - (position.longitudeDelta / 2),
      position.latitude - (position.latitudeDelta / 2),
      position.longitude + (position.longitudeDelta / 2),
      position.latitude + (position.latitudeDelta / 2)
    ];

    return geoViewport.viewport(bounds, [width, height], 0, 21, 256).zoom || 0;
  }

  setCompassLine = () => {
    this.setState((prevState) => {
      const state = {};
      if (prevState.selectedAlertCoordinates !== null) {
        // extract not needed props
        // eslint-disable-next-line no-unused-vars
        const { accuracy, altitude, speed, course, ...rest } = this.state.lastPosition;
        state.compassFallback = [{ ...rest }, { ...this.state.selectedAlertCoordinates }];
      }
      if (prevState.compassFallback !== null && prevState.selectedAlertCoordinates === null) {
        state.compassFallback = null;
      }
      return state;
    });
  }

  setHeaderTitle = () => {
    const { selectedAlertCoordinates } = this.state;
    const headerText = selectedAlertCoordinates
      ? `${selectedAlertCoordinates.latitude.toFixed(4)}, ${selectedAlertCoordinates.longitude.toFixed(4)}`
      : I18n.t('dashboard.map');
    this.props.navigator.setTitle({
      title: headerText
    });
  }

  updateMarkers() {
    const markers = this.props.cluster && this.props.cluster.getClusters([
      this.state.region.longitude - (this.state.region.longitudeDelta / 2),
      this.state.region.latitude - (this.state.region.latitudeDelta / 2),
      this.state.region.longitude + (this.state.region.longitudeDelta / 2),
      this.state.region.latitude + (this.state.region.latitudeDelta / 2)
    ], this.getMapZoom());
    this.setState({
      markers: markers || []
    });
  }

  createReport = () => {
    const { selectedAlertCoordinates } = this.state;
    this.props.setCanDisplayAlerts(false);
    const { area } = this.props;
    let latLng = '0,0';
    if (selectedAlertCoordinates) {
      latLng = `${selectedAlertCoordinates.latitude},${selectedAlertCoordinates.longitude}`;
    }
    const screen = 'ForestWatcher.NewReport';
    const title = 'Report';
    const form = `${area.name.toUpperCase()}-${area.dataset.name}-REPORT--${moment().format('YYYY-MM-DDTHH:mm:ss')}`;
    this.props.createReport({
      area,
      name: form,
      userPosition: this.lastPosition || '0,0',
      clickedPosition: latLng
    });
    this.props.navigator.push({
      screen,
      title,
      passProps: {
        screen,
        title,
        form
      }
    });
  }

  animateGeo() {
    Animated.sequence([
      Animated.timing(this.state.geoMarkerOpacity, {
        toValue: 0.4,
        easing: Easing.in(Easing.quad),
        duration: 800
      }),
      Animated.timing(this.state.geoMarkerOpacity, {
        toValue: 0.15,
        easing: Easing.out(Easing.quad),
        duration: 1000
      })
    ]).start(event => {
      if (event.finished) {
        this.animateGeo();
      }
    });
  }

  geoLocate() {
    this.animateGeo();

    navigator.geolocation.getCurrentPosition(
      (location) => {
        this.setState({
          lastPosition: {
            latitude: location.coords.latitude,
            longitude: location.coords.longitude
          }
        });
      },
      (error) => console.info(error),
      { enableHighAccuracy: true, timeout: 30000, maximumAge: 0 }
    );

    Location.startUpdatingLocation();

    this.eventLocation = DeviceEventEmitter.addListener(
      'locationUpdated',
      throttle((location) => {
        const coords = Platform.OS === 'ios' ? location.coords : location;
        this.setState({
          lastPosition: coords
        });
      }, 300)
    );

    const updateHeading = heading => (prevState) => {
      const state = {
        heading: parseInt(heading, 10)
      };
      if (!prevState.hasCompass) state.hasCompass = true;
      return state;
    };

    if (Platform.OS === 'ios') {
      Location.startUpdatingHeading();
      this.eventOrientation = DeviceEventEmitter.addListener(
        'headingUpdated',
        (data) => {
          this.setState(updateHeading(data.heading));
        }
      );
    } else {
      SensorManager.startOrientation(300);
      this.eventOrientation = DeviceEventEmitter.addListener(
        'Orientation',
        throttle((data) => {
          this.setState(updateHeading(data.azimuth));
        }, 16)
      );
    }
  }

  updateRegion = (region) => {
    this.setState({ region }, () => {
      this.updateMarkers();
    });
  }

  zoomTo = (coordinates) => {
    const zoomCoordinates = {
      latitude: coordinates.latitude,
      longitude: coordinates.longitude,
      latitudeDelta: this.state.region.latitudeDelta / 2,
      longitudeDelta: this.state.region.longitudeDelta / 2
    };
    this.map.animateToRegion(zoomCoordinates);
  }

  selectAlert = (e) => {
    const { coordinate } = e.nativeEvent;
    if (coordinate) {
      this.setState((state) => ({
        selectedAlertCoordinates: state.selectedAlertCoordinates ? null : coordinate
      }));
    }
  }

  updateSelectedArea = () => {
    this.setState({
      selectedAlertCoordinates: null
    }, () => {
      this.updateMarkers();
      const options = { edgePadding: { top: 250, right: 250, bottom: 250, left: 250 }, animated: false };
      if (this.map) this.map.fitToCoordinates(this.props.areaCoordinates, options);
    });
  }

  renderMap = () => {
    if (!this.state.renderMap) {
      this.setState({
        renderMap: true
      });
    }
  }

  renderFooter() {
    const reportBtn = (
      <ActionBtn
        style={styles.footerButton}
        text={I18n.t('report.title')}
        onPress={this.createReport}
      />
    );
    return (
      <View style={styles.footer}>
        <Image
          style={styles.footerBg}
          source={backgroundImage}
        />
        {reportBtn}
      </View>
    );
  }

  renderFooterLoading() {
    return (!this.state.lastPosition &&
      <View pointerEvents="box-none" style={styles.footer}>
        <Image
          style={styles.footerBg}
          source={backgroundImage}
        />
        <View style={styles.signalNotice}>
          <View style={styles.geoLocationContainer}>
            <Image
              style={styles.marker}
              source={markerCompassRedImage}
            />
            <Animated.View
              style={[styles.geoLocation, { opacity: this.state.geoMarkerOpacity }]}
            />
          </View>
          <Text style={styles.signalNoticeText}>{I18n.t('alerts.satelliteSignal')}</Text>
        </View>
      </View>
    );
  }

  render() {
    const { hasCompass, lastPosition, compassFallback, selectedAlertCoordinates } = this.state;
    const { areaCoordinates, datasetSlug } = this.props;
    const showCompassFallback = !hasCompass && lastPosition && selectedAlertCoordinates && compassFallback;

    // Map elements
    const compassFallbackElement = showCompassFallback ? (<MapView.Polyline
      coordinates={this.state.compassFallback}
      strokeColor={Theme.colors.color5}
      strokeWidth={2}
    />) : null;
    const areaPolygonElement = areaCoordinates ? (
      <MapView.Polyline
        coordinates={areaCoordinates}
        strokeColor={Theme.colors.color1}
        strokeWidth={2}
      />
    ) : null;
    const userPositionElement = this.state.lastPosition ? (
      <MapView.Marker.Animated
        image={markerImage}
        coordinate={this.state.lastPosition}
        style={{ zIndex: 2 }}
        anchor={{ x: 0.5, y: 0.5 }}
        pointerEvents={'none'}
      />
    ) : null;
    const compassElement = this.state.lastPosition && this.state.heading ? (
      <MapView.Marker
        key={'compass'}
        coordinate={this.state.lastPosition}
        zIndex={1}
        anchor={{ x: 0.5, y: 0.5 }}
        pointerEvents={'none'}
      >
        <Animated.Image
          style={{
            width: 94,
            height: 94,
            transform: [
              { rotate: `${this.state.heading ? this.state.heading : '0'}deg` }
            ]
          }}
          source={compassImage}
        />
      </MapView.Marker>
    ) : null;
    const newSelectedAlert = selectedAlertCoordinates ? (
      <MapView.Marker
        key={'selectedAlert'}
        coordinate={selectedAlertCoordinates}
        image={alertWhite}
        anchor={{ x: 0.5, y: 0.5 }}
        zIndex={10}
      />
    ) : null;
    const clustersElement = datasetSlug ? (
      <Clusters
        markers={this.state.markers}
        selectAlert={this.selectAlert}
        zoomTo={this.zoomTo}
        datasetSlug={datasetSlug}
      />
    ) : null;
    return (
      this.state.renderMap
      ?
        <View style={styles.container}>
          <MapView
            ref={(ref) => { this.map = ref; }}
            style={styles.map}
            provider={MapView.PROVIDER_GOOGLE}
            mapType="hybrid"
            rotateEnabled={false}
            initialRegion={this.state.region}
            onRegionChangeComplete={this.updateRegion}
            onLayout={this.onLayout}
            moveOnMarkerPress={false}
            onPress={this.selectAlert}
          >
            {clustersElement}
            {compassFallbackElement}
            {areaPolygonElement}
            {userPositionElement}
            {compassElement}
            {newSelectedAlert}
          </MapView>
          <AreaCarousel
            navigator={this.props.navigator}
            alertSelected={selectedAlertCoordinates}
            lastPosition={this.state.lastPosition}
          />
          {selectedAlertCoordinates
            ? this.renderFooter()
            : this.renderFooterLoading()
          }
        </View>
      : renderLoading()
    );
  }
}

Map.propTypes = {
  navigator: PropTypes.object.isRequired,
  createReport: PropTypes.func.isRequired,
  cluster: PropTypes.object,
  center: PropTypes.shape({
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired
  }),
  datasetSlug: PropTypes.string,
  areaCoordinates: PropTypes.array,
  actionsPending: PropTypes.number.isRequired,
  syncModalOpen: PropTypes.bool.isRequired,
  syncSkip: PropTypes.bool.isRequired,
  setSyncModal: PropTypes.func.isRequired,
  setCanDisplayAlerts: PropTypes.func.isRequired,
  canDisplayAlerts: PropTypes.bool.isRequired,
  area: PropTypes.object.isRequired
};

export default Map;
<commit_msg>fix: adds fallback for if location data comes differently<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
  View,
  ActivityIndicator,
  Dimensions,
  DeviceEventEmitter,
  Animated,
  Easing,
  StatusBar,
  Image,
  Text,
  Platform
} from 'react-native';
import CONSTANTS from 'config/constants';
import throttle from 'lodash/throttle';
import moment from 'moment';

import Theme from 'config/theme';
// import { daysToDate } from 'helpers/date';
import ActionBtn from 'components/common/action-button';
import AreaCarousel from 'containers/map/area-carousel/';
import Clusters from 'components/map/clusters/';
import tracker from 'helpers/googleAnalytics';
import I18n from 'locales';
import MapView from 'react-native-maps';
import styles from './styles';

import { SensorManager } from 'NativeModules'; // eslint-disable-line

const Timer = require('react-native-timer');
const geoViewport = require('@mapbox/geo-viewport');

const { RNLocation: Location } = require('NativeModules'); // eslint-disable-line

const { width, height } = Dimensions.get('window');

const ASPECT_RATIO = width / height;
const LATITUDE_DELTA = 10;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

const markerImage = require('assets/marker.png');
const alertWhite = require('assets/alert-white.png');
const markerCompassRedImage = require('assets/compass_circle_red.png');
const compassImage = require('assets/compass_direction.png');
const backgroundImage = require('assets/map_bg_gradient.png');

function renderLoading() {
  return (
    <View style={[styles.container, styles.loader]}>
      <ActivityIndicator
        color={Theme.colors.color1}
        style={{ height: 80 }}
        size="large"
      />
    </View>
  );
}

class Map extends Component {
  static navigatorStyle = {
    navBarTextColor: Theme.colors.color5,
    navBarButtonColor: Theme.colors.color5,
    topBarElevationShadowEnabled: false,
    navBarBackgroundColor: Theme.background.main,
    navBarTransparent: true,
    navBarTranslucent: true
  };

  constructor(props) {
    super(props);
    const { center } = props;
    const initialCoords = center || { lat: CONSTANTS.maps.lat, lon: CONSTANTS.maps.lng };
    this.eventLocation = null;
    this.eventOrientation = null;
    this.hasSetCoordinates = false;
    // Google maps lon and lat are inverted
    this.state = {
      renderMap: false,
      lastPosition: null,
      hasCompass: false,
      compassFallback: null,
      heading: null,
      geoMarkerOpacity: new Animated.Value(0.3),
      region: {
        latitude: initialCoords.lon,
        longitude: initialCoords.lat,
        latitudeDelta: LATITUDE_DELTA,
        longitudeDelta: LONGITUDE_DELTA
      },
      urlTile: null,
      markers: []
    };
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  componentDidMount() {
    tracker.trackScreenView('Map');

    if (Platform.OS === 'ios') {
      Location.requestWhenInUseAuthorization();
      StatusBar.setBarStyle('light-content');
    }

    this.geoLocate();
    this.updateMarkers();
    Timer.setTimeout(this, 'renderMap', this.renderMap, 500);
  }

  componentWillUpdate(nextProps, nextState) {
    if (this.state.selectedAlertCoordinates !== nextState.selectedAlertCoordinates && this.state.lastPosition !== null) {
      this.setCompassLine();
    }
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.areaCoordinates !== prevProps.areaCoordinates) {
      this.updateSelectedArea();
    }
    if (this.state.selectedAlertCoordinates !== prevState.selectedAlertCoordinates) {
      this.setHeaderTitle();
    }
  }

  componentWillUnmount() {
    Location.stopUpdatingLocation();

    Timer.clearTimeout(this, 'renderMap');
    if (this.eventLocation) {
      this.eventLocation.remove();
    }

    if (this.eventOrientation) {
      this.eventOrientation.remove();
    }

    if (Platform.OS === 'ios') {
      StatusBar.setBarStyle('default');
      Location.stopUpdatingHeading();
    } else {
      SensorManager.stopOrientation();
    }
  }

  onNavigatorEvent(event) {
    const { actionsPending, syncModalOpen, syncSkip, setCanDisplayAlerts, canDisplayAlerts } = this.props;
    if (event.id === 'didAppear') {
      if (actionsPending > 0 && !syncModalOpen && !syncSkip && !this.syncModalOpen) {
        this.syncModalOpen = true;
        this.props.setSyncModal(true);
        this.props.navigator.showModal({
          screen: 'ForestWatcher.Sync',
          passProps: {
            goBackDisabled: true
          }
        });
      }
      if (!canDisplayAlerts) {
        setCanDisplayAlerts(true);
      }
    }
  }

  onLayout = () => {
    if (this.hasSetCoordinates === false && this.props.areaCoordinates) {
      const options = { edgePadding: { top: 250, right: 250, bottom: 250, left: 250 }, animated: false };
      this.map.fitToCoordinates(this.props.areaCoordinates, options);
      this.hasSetCoordinates = true;
    }
  }

  getMapZoom() {
    const position = this.state.region;

    const bounds = [
      position.longitude - (position.longitudeDelta / 2),
      position.latitude - (position.latitudeDelta / 2),
      position.longitude + (position.longitudeDelta / 2),
      position.latitude + (position.latitudeDelta / 2)
    ];

    return geoViewport.viewport(bounds, [width, height], 0, 21, 256).zoom || 0;
  }

  setCompassLine = () => {
    this.setState((prevState) => {
      const state = {};
      if (prevState.selectedAlertCoordinates !== null) {
        // extract not needed props
        // eslint-disable-next-line no-unused-vars
        const { accuracy, altitude, speed, course, ...rest } = this.state.lastPosition;
        state.compassFallback = [{ ...rest }, { ...this.state.selectedAlertCoordinates }];
      }
      if (prevState.compassFallback !== null && prevState.selectedAlertCoordinates === null) {
        state.compassFallback = null;
      }
      return state;
    });
  }

  setHeaderTitle = () => {
    const { selectedAlertCoordinates } = this.state;
    const headerText = selectedAlertCoordinates
      ? `${selectedAlertCoordinates.latitude.toFixed(4)}, ${selectedAlertCoordinates.longitude.toFixed(4)}`
      : I18n.t('dashboard.map');
    this.props.navigator.setTitle({
      title: headerText
    });
  }

  updateMarkers() {
    const markers = this.props.cluster && this.props.cluster.getClusters([
      this.state.region.longitude - (this.state.region.longitudeDelta / 2),
      this.state.region.latitude - (this.state.region.latitudeDelta / 2),
      this.state.region.longitude + (this.state.region.longitudeDelta / 2),
      this.state.region.latitude + (this.state.region.latitudeDelta / 2)
    ], this.getMapZoom());
    this.setState({
      markers: markers || []
    });
  }

  createReport = () => {
    const { selectedAlertCoordinates } = this.state;
    this.props.setCanDisplayAlerts(false);
    const { area } = this.props;
    let latLng = '0,0';
    if (selectedAlertCoordinates) {
      latLng = `${selectedAlertCoordinates.latitude},${selectedAlertCoordinates.longitude}`;
    }
    const screen = 'ForestWatcher.NewReport';
    const title = 'Report';
    const form = `${area.name.toUpperCase()}-${area.dataset.name}-REPORT--${moment().format('YYYY-MM-DDTHH:mm:ss')}`;
    this.props.createReport({
      area,
      name: form,
      userPosition: this.lastPosition || '0,0',
      clickedPosition: latLng
    });
    this.props.navigator.push({
      screen,
      title,
      passProps: {
        screen,
        title,
        form
      }
    });
  }

  animateGeo() {
    Animated.sequence([
      Animated.timing(this.state.geoMarkerOpacity, {
        toValue: 0.4,
        easing: Easing.in(Easing.quad),
        duration: 800
      }),
      Animated.timing(this.state.geoMarkerOpacity, {
        toValue: 0.15,
        easing: Easing.out(Easing.quad),
        duration: 1000
      })
    ]).start(event => {
      if (event.finished) {
        this.animateGeo();
      }
    });
  }

  geoLocate() {
    this.animateGeo();

    navigator.geolocation.getCurrentPosition(
      (location) => {
        const coords = typeof location.coords !== 'undefined' ? location.coords : location;
        this.setState({
          lastPosition: {
            latitude: coords.latitude,
            longitude: coords.longitude
          }
        });
      },
      (error) => console.info(error),
      { enableHighAccuracy: true, timeout: 30000, maximumAge: 0 }
    );

    Location.startUpdatingLocation();

    this.eventLocation = DeviceEventEmitter.addListener(
      'locationUpdated',
      throttle((location) => {
        const coords = typeof location.coords !== 'undefined' ? location.coords : location;
        this.setState({
          lastPosition: {
            latitude: coords.latitude,
            longitude: coords.longitude
          }
        });
      }, 300)
    );

    const updateHeading = heading => (prevState) => {
      const state = {
        heading: parseInt(heading, 10)
      };
      if (!prevState.hasCompass) state.hasCompass = true;
      return state;
    };

    if (Platform.OS === 'ios') {
      Location.startUpdatingHeading();
      this.eventOrientation = DeviceEventEmitter.addListener(
        'headingUpdated',
        (data) => {
          this.setState(updateHeading(data.heading));
        }
      );
    } else {
      SensorManager.startOrientation(300);
      this.eventOrientation = DeviceEventEmitter.addListener(
        'Orientation',
        throttle((data) => {
          this.setState(updateHeading(data.azimuth));
        }, 16)
      );
    }
  }

  updateRegion = (region) => {
    this.setState({ region }, () => {
      this.updateMarkers();
    });
  }

  zoomTo = (coordinates) => {
    const zoomCoordinates = {
      latitude: coordinates.latitude,
      longitude: coordinates.longitude,
      latitudeDelta: this.state.region.latitudeDelta / 2,
      longitudeDelta: this.state.region.longitudeDelta / 2
    };
    this.map.animateToRegion(zoomCoordinates);
  }

  selectAlert = (e) => {
    const { coordinate } = e.nativeEvent;
    if (coordinate) {
      this.setState((state) => ({
        selectedAlertCoordinates: state.selectedAlertCoordinates ? null : coordinate
      }));
    }
  }

  updateSelectedArea = () => {
    this.setState({
      selectedAlertCoordinates: null
    }, () => {
      this.updateMarkers();
      const options = { edgePadding: { top: 250, right: 250, bottom: 250, left: 250 }, animated: false };
      if (this.map) this.map.fitToCoordinates(this.props.areaCoordinates, options);
    });
  }

  renderMap = () => {
    if (!this.state.renderMap) {
      this.setState({
        renderMap: true
      });
    }
  }

  renderFooter() {
    const reportBtn = (
      <ActionBtn
        style={styles.footerButton}
        text={I18n.t('report.title')}
        onPress={this.createReport}
      />
    );
    return (
      <View style={styles.footer}>
        <Image
          style={styles.footerBg}
          source={backgroundImage}
        />
        {reportBtn}
      </View>
    );
  }

  renderFooterLoading() {
    return (!this.state.lastPosition &&
      <View pointerEvents="box-none" style={styles.footer}>
        <Image
          style={styles.footerBg}
          source={backgroundImage}
        />
        <View style={styles.signalNotice}>
          <View style={styles.geoLocationContainer}>
            <Image
              style={styles.marker}
              source={markerCompassRedImage}
            />
            <Animated.View
              style={[styles.geoLocation, { opacity: this.state.geoMarkerOpacity }]}
            />
          </View>
          <Text style={styles.signalNoticeText}>{I18n.t('alerts.satelliteSignal')}</Text>
        </View>
      </View>
    );
  }

  render() {
    const { hasCompass, lastPosition, compassFallback, selectedAlertCoordinates } = this.state;
    const { areaCoordinates, datasetSlug } = this.props;
    const showCompassFallback = !hasCompass && lastPosition && selectedAlertCoordinates && compassFallback;

    // Map elements
    const compassFallbackElement = showCompassFallback ? (<MapView.Polyline
      coordinates={this.state.compassFallback}
      strokeColor={Theme.colors.color5}
      strokeWidth={2}
    />) : null;
    const areaPolygonElement = areaCoordinates ? (
      <MapView.Polyline
        coordinates={areaCoordinates}
        strokeColor={Theme.colors.color1}
        strokeWidth={2}
      />
    ) : null;
    const userPositionElement = this.state.lastPosition ? (
      <MapView.Marker.Animated
        image={markerImage}
        coordinate={this.state.lastPosition}
        style={{ zIndex: 2 }}
        anchor={{ x: 0.5, y: 0.5 }}
        pointerEvents={'none'}
      />
    ) : null;
    const compassElement = this.state.lastPosition && this.state.heading ? (
      <MapView.Marker
        key={'compass'}
        coordinate={this.state.lastPosition}
        zIndex={1}
        anchor={{ x: 0.5, y: 0.5 }}
        pointerEvents={'none'}
      >
        <Animated.Image
          style={{
            width: 94,
            height: 94,
            transform: [
              { rotate: `${this.state.heading ? this.state.heading : '0'}deg` }
            ]
          }}
          source={compassImage}
        />
      </MapView.Marker>
    ) : null;
    const newSelectedAlert = selectedAlertCoordinates ? (
      <MapView.Marker
        key={'selectedAlert'}
        coordinate={selectedAlertCoordinates}
        image={alertWhite}
        anchor={{ x: 0.5, y: 0.5 }}
        zIndex={10}
      />
    ) : null;
    const clustersElement = datasetSlug ? (
      <Clusters
        markers={this.state.markers}
        selectAlert={this.selectAlert}
        zoomTo={this.zoomTo}
        datasetSlug={datasetSlug}
      />
    ) : null;
    return (
      this.state.renderMap
      ?
        <View style={styles.container}>
          <MapView
            ref={(ref) => { this.map = ref; }}
            style={styles.map}
            provider={MapView.PROVIDER_GOOGLE}
            mapType="hybrid"
            rotateEnabled={false}
            initialRegion={this.state.region}
            onRegionChangeComplete={this.updateRegion}
            onLayout={this.onLayout}
            moveOnMarkerPress={false}
            onPress={this.selectAlert}
          >
            {clustersElement}
            {compassFallbackElement}
            {areaPolygonElement}
            {userPositionElement}
            {compassElement}
            {newSelectedAlert}
          </MapView>
          <AreaCarousel
            navigator={this.props.navigator}
            alertSelected={selectedAlertCoordinates}
            lastPosition={this.state.lastPosition}
          />
          {selectedAlertCoordinates
            ? this.renderFooter()
            : this.renderFooterLoading()
          }
        </View>
      : renderLoading()
    );
  }
}

Map.propTypes = {
  navigator: PropTypes.object.isRequired,
  createReport: PropTypes.func.isRequired,
  cluster: PropTypes.object,
  center: PropTypes.shape({
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired
  }),
  datasetSlug: PropTypes.string,
  areaCoordinates: PropTypes.array,
  actionsPending: PropTypes.number.isRequired,
  syncModalOpen: PropTypes.bool.isRequired,
  syncSkip: PropTypes.bool.isRequired,
  setSyncModal: PropTypes.func.isRequired,
  setCanDisplayAlerts: PropTypes.func.isRequired,
  canDisplayAlerts: PropTypes.bool.isRequired,
  area: PropTypes.object.isRequired
};

export default Map;
<|endoftext|>


<commit_before>/**
 *
 * App
 *
 * This component is the skeleton around the actual pages, and should only
 * contain code that should be seen on all pages. (e.g. navigation bar)
 */

import React from 'react';
import Helmet from 'react-helmet';
import styled from 'styled-components';

import HeaderBaseBoilerPlate from 'components/HeaderBaseBoilerPlate';
import FooterBaseBoilerPlate from 'components/FooterBaseBoilerPlate';
import withProgressBar from 'components/ProgressBar';

const AppWrapper = styled.div`
  max-width: calc(768px + 16px * 2);
  margin: 0 auto;
  display: flex;
  min-height: 100%;
  padding: 0 16px;
  flex-direction: column;
`;

export function App(props) {
  return (
    <AppWrapper>
      <Helmet
        titleTemplate="%s - React.js Boilerplate"
        defaultTitle="React.js Boilerplate"
        meta={[
          { name: 'description', content: 'A React.js Boilerplate application' },
        ]}
      />
      <HeaderBaseBoilerPlate />
      {React.Children.toArray(props.children)}
      <FooterBaseBoilerPlate />
    </AppWrapper>
  );
}

App.propTypes = {
  children: React.PropTypes.node,
};

export default withProgressBar(App);
<commit_msg>FIX : Header et Footer non déclaré<commit_after>/**
 *
 * App
 *
 * This component is the skeleton around the actual pages, and should only
 * contain code that should be seen on all pages. (e.g. navigation bar)
 */

import React from 'react';
import Helmet from 'react-helmet';
import styled from 'styled-components';

import Header from 'components/Header';
import Footer from 'components/Footer';
import FooterBaseBoilerPlate from 'components/FooterBaseBoilerPlate';
import withProgressBar from 'components/ProgressBar';

const AppWrapper = styled.div`
  max-width: calc(768px + 16px * 2);
  margin: 0 auto;
  display: flex;
  min-height: 100%;
  padding: 0 16px;
  flex-direction: column;
`;

export function App(props) {
  return (
    <AppWrapper>
      <Helmet
        titleTemplate="%s - React.js Boilerplate"
        defaultTitle="React.js Boilerplate"
        meta={[
          { name: 'description', content: 'A React.js Boilerplate application' },
        ]}
      />
      <Header />
      {React.Children.toArray(props.children)}
      <Footer />
    </AppWrapper>
  );
}

App.propTypes = {
  children: React.PropTypes.node,
};

export default withProgressBar(App);
<|endoftext|>


<commit_before>Ext.define('Flux.controller.Animation', {
    extend: 'Ext.app.Controller',

    refs: [{
        'ref': 'settingsMenu',
        'selector': '#settings-menu'
    }, {
        'ref': 'topToolbar',
        'selector': 'viewport toolbar'
    }],

    requires: [
        'Ext.data.ArrayStore',
        'Ext.form.field.ComboBox',
        'Ext.form.field.Number',
        'Ext.resizer.Splitter',
        'Ext.window.Window',
        'Flux.model.RasterGrid',
        'Flux.store.Metadata'
    ],

    init: function () {
        // Create a new state Provider if one doesn't already exist
        if (Ext.state.Manager.getProvider().path === undefined) {
            Ext.state.Manager.setProvider(Ext.create('Ext.state.CookieProvider'));
        }

        ////////////////////////////////////////////////////////////////////////
        // Event Listeners /////////////////////////////////////////////////////

        this.control({

            '#settings-menu combo': {
                select: this.onStepSizeChange
            },

            '#animate-btn': {
                toggle: this.toggleAnimation
            },

            '#animate-delay': {
                dragend: this.onDelayChange
            },

            '#forward-btn, #backward-btn': {
                click: this.onStepButton
            }

        });
    },

    /**
        Calculates either the step size (unit length of time in an interval
        between data frames) or the number of such steps (if step size provided)
        to take in an animation frame.
        @param  s0          {Number}    Initial number of seconds between each step/span
        @param  stepSize    {String}    The size of the step/span
     */
    calcStepOrSize: function (s0, stepSize) {
        var steps;

        if (stepSize) {
            switch (stepSize) {
                case 'months':
                steps = s0 / 2678400;
                break;

                case 'days':
                steps = s0 / 86400;
                break;

                default:
                steps = s0 / 3600; // 'hours'
            }

            return Math.floor(steps);
        }

        if (s0 / 86400 < 1) { // Less than 1 day (86400 seconds)?
            return 'hours';
        }

        if (s0 / 2678400 < 1) { // Less than 1 month?
            return 'days';
        }

        return 'months';
    },

    /**
        Creates or clears the timing function for an animation.
        @param  state   {Boolean}
        @param  delay   {Number}
     */
    animate: function (state, delay) {
        if (state) {
            this._animation = window.setInterval(Ext.bind(this.stepBy,
                this, [this._steps]), delay * 1000); // Delay in milliseconds
        } else {
            window.clearInterval(this._animation);
        }
    },

    /**
        To be executed when the dataset (metadata) changes, this function
        calculates the default step size (e.g. an hour) and the number of steps
        (e.g. 3 steps == 3 hours) between each animation frame. The animation
        controls in the top toolbar are thus enabled and initialized.
        @param  metadata    {Flux.model.Metadata}
     */
    enableAnimation: function (metadata) {
        var d, steps, stepSize;
        var cmp = this.getSettingsMenu().down('field[name=stepSize]');
        var offsets = metadata.getTimeOffsets();

        if (!metadata) {
            return;
        }

        this._metadata = metadata;

        // Enable all the toolbar buttons related to animation
        Ext.each(this.getTopToolbar().query('button[cls=anim-btn]'), function (btn) {
            btn.enable();
        });

        // Figure out the default size of step (e.g. an hour) and the number of
        //  steps to take in each frame
        if (Ext.isEmpty(offsets)) {
            stepSize = steps = 1;
            d = [[1, 'steps']];
            cmp.disable();

        } else {
            stepSize = this.calcStepOrSize(offsets[0]);
            steps = this.calcStepOrSize(offsets[0], stepSize);

            // We can't reliably animate a time series that changes its time
            //  interval, so disable the selection of a different time interval
            if (offsets.length > 1) {
                d = [[offsets[0], 'steps']];
                cmp.disable();

            } else {
                switch (stepSize) {
                    case 'days':
                    d = [
                        ['days', 'day(s)']
                    ];
                    break;

                    case 'months':
                    d = [
                        ['days', 'day(s)'],
                        ['months', 'month(s)']
                    ];
                    break;

                    default:
                    d = [
                        ['hours', 'hour(s)'],
                        ['days', 'day(s)']
                    ];
                }
            }
        }

        // Removes and inserts the Ext.form.field.Number instance that represents
        //  the number of steps to be taken in an animation frame
        this.updateStepSelector(steps, Ext.isEmpty(offsets));

        // Create and bind a new store to hold the appropriate step sizes
        cmp.bindStore(Ext.create('Ext.data.ArrayStore', {
            fields: ['stepSize', 'text'],
            data: d
        }));

        // Apply the default settings to the UI
        cmp.setValue(stepSize);

        this._steps = steps;
        this._stepSize = stepSize;
        this._delay = this.getTopToolbar().down('#animate-delay').getValue();
    },

    /**TODO fetchRaster()
        Causes the corresponding view to "step" forwards or backwards in time
        with the animation according to a specified number of steps.
        @param  steps   {Number}    Negative steps are steps taken backwards
     */
    stepBy: function (steps) {
        var query = Ext.ComponentQuery.query('d3geomap');

        Ext.each(query, Ext.Function.bind(function (view) {
          var params;
	  var ts = view.getMoment();

            if (Ext.isEmpty(ts)) {
                return;
            }
            var agg_toggle = Ext.ComponentQuery.query('field[name=showAggregation]')[0].getValue();
            if (agg_toggle) {
		args = this.getController('UserInteraction').getAggregationArgs();
		params = {
		    aggregate: args.aggregate,
		    start: ts.clone()
			  .add(steps, this._stepSize)
			  .toISOString(),
		    end: ts.clone()
			  .add(args.intervals,args.intervalGrouping)
			  .add(steps, this._stepSize)
			  .toISOString()
		};
	    } else {
		params = {
		    time: ts.clone()
			.add(steps, this._stepSize)
			.toISOString()
		};
	    }
            this.getController('UserInteraction').fetchRaster(view, params);
        }, this));
    },

    ////////////////////////////////////////////////////////////////////////////
    // Event Handlers //////////////////////////////////////////////////////////

    /**
        Handles a change in the animation delay from the Slider instance in the
        top Toolbar.
        @param  slider  {Ext.slide.Multi}
     */
    onDelayChange: function (slider) {
        this._delay = slider.getValue();
        this.getTopToolbar().down('#animate-btn').toggle(false);
    },

    /**
        Handles forwards/backwards iteration through a time series dataset;
        responds to "forward" or "backward" button being clicked.
        @param  btn {Ext.button.Button}
     */
    onStepButton: function (btn) {
        // Uncheck the "Show Aggregation" and "Show Difference" checkboxes
        //this.getController('UserInteraction').uncheckAggregates();

        switch (btn.getItemId()) {
            case 'backward-btn':
            this.stepBy(this._steps * -1);
            break;

            default:
            this.stepBy(this._steps);
        }
    },

    /**
        Handles a change in the step size as set by the user.
        @param  c       {Ext.form.field.ComboBox}
        @param  recs    {Array}
     */
    onStepSizeChange: function (c, recs) {
        var steps;

        this._stepSize = recs[0].get('stepSize');
        steps = this.calcStepOrSize(this._metadata.getTimeOffsets()[0],
            recs[0].get('stepSize'));

        this.updateStepSelector((steps === 0) ? 1 : steps);
    },

    /**
        Handles a change in the number of steps as set by the user.
        @param  c       {Ext.form.field.Number}
        @param  value   {Number}
     */
    onStepsChange: function (c, value) {
        this._steps = value;
    },

    /**
        Pause/Play the animation.
        @param  btn     {Ext.button.Button}
        @param  pressed {Boolean}
     */
    toggleAnimation: function (btn, pressed) {
        // Uncheck the "Show Aggregation" and "Show Difference" checkboxes
        //this.getController('UserInteraction').uncheckAggregates();
	this.getController('UserInteraction').toggleAggregateParams(pressed);

        if (pressed) {
            btn.setText('Pause');
            btn.setIconCls('icon-control-pause');
        } else {
            btn.setText('Animate');
            btn.setIconCls('icon-control-play');
        }

        this.animate(pressed, this._delay);
    },

    /**
        Removes and inserts the Ext.form.field.Number instance that represents
        the number of steps to be taken in an animation frame.
        @param  steps       {Number}
        @param  disabled    {Boolean}
     */
    updateStepSelector: function (steps, disabled) {
        var menu = this.getSettingsMenu();
        if (menu.query('field[name=steps]').length !== 0) {
            menu.remove('steps');
        }
        menu.insert(menu.items.length - 1, Ext.create('Ext.form.field.Number', {
            xtype: 'numberfield',
            disabled: disabled,
            itemId: 'steps',
            name: 'steps',
            value: steps,
            minValue: steps,
            step: steps,
            listeners: {
                change: Ext.bind(this.onStepsChange, this)
            }
        }));
    }

});

<commit_msg>Added a catch in case a user tries to animate using aggregation with not all aggregation fields filled out<commit_after>Ext.define('Flux.controller.Animation', {
    extend: 'Ext.app.Controller',

    refs: [{
        'ref': 'settingsMenu',
        'selector': '#settings-menu'
    }, {
        'ref': 'topToolbar',
        'selector': 'viewport toolbar'
    }],

    requires: [
        'Ext.data.ArrayStore',
        'Ext.form.field.ComboBox',
        'Ext.form.field.Number',
        'Ext.resizer.Splitter',
        'Ext.window.Window',
        'Flux.model.RasterGrid',
        'Flux.store.Metadata'
    ],

    init: function () {
        // Create a new state Provider if one doesn't already exist
        if (Ext.state.Manager.getProvider().path === undefined) {
            Ext.state.Manager.setProvider(Ext.create('Ext.state.CookieProvider'));
        }

        ////////////////////////////////////////////////////////////////////////
        // Event Listeners /////////////////////////////////////////////////////

        this.control({

            '#settings-menu combo': {
                select: this.onStepSizeChange
            },

            '#animate-btn': {
                toggle: this.toggleAnimation
            },

            '#animate-delay': {
                dragend: this.onDelayChange
            },

            '#forward-btn, #backward-btn': {
                click: this.onStepButton
            }

        });
    },

    /**
        Calculates either the step size (unit length of time in an interval
        between data frames) or the number of such steps (if step size provided)
        to take in an animation frame.
        @param  s0          {Number}    Initial number of seconds between each step/span
        @param  stepSize    {String}    The size of the step/span
     */
    calcStepOrSize: function (s0, stepSize) {
        var steps;

        if (stepSize) {
            switch (stepSize) {
                case 'months':
                steps = s0 / 2678400;
                break;

                case 'days':
                steps = s0 / 86400;
                break;

                default:
                steps = s0 / 3600; // 'hours'
            }

            return Math.floor(steps);
        }

        if (s0 / 86400 < 1) { // Less than 1 day (86400 seconds)?
            return 'hours';
        }

        if (s0 / 2678400 < 1) { // Less than 1 month?
            return 'days';
        }

        return 'months';
    },

    /**
        Creates or clears the timing function for an animation.
        @param  state   {Boolean}
        @param  delay   {Number}
     */
    animate: function (state, delay) {
        if (state) {
            this._animation = window.setInterval(Ext.bind(this.stepBy,
                this, [this._steps]), delay * 1000); // Delay in milliseconds
        } else {
            window.clearInterval(this._animation);
        }
    },

    /**
        To be executed when the dataset (metadata) changes, this function
        calculates the default step size (e.g. an hour) and the number of steps
        (e.g. 3 steps == 3 hours) between each animation frame. The animation
        controls in the top toolbar are thus enabled and initialized.
        @param  metadata    {Flux.model.Metadata}
     */
    enableAnimation: function (metadata) {
        var d, steps, stepSize;
        var cmp = this.getSettingsMenu().down('field[name=stepSize]');
        var offsets = metadata.getTimeOffsets();

        if (!metadata) {
            return;
        }

        this._metadata = metadata;

        // Enable all the toolbar buttons related to animation
        Ext.each(this.getTopToolbar().query('button[cls=anim-btn]'), function (btn) {
            btn.enable();
        });

        // Figure out the default size of step (e.g. an hour) and the number of
        //  steps to take in each frame
        if (Ext.isEmpty(offsets)) {
            stepSize = steps = 1;
            d = [[1, 'steps']];
            cmp.disable();

        } else {
            stepSize = this.calcStepOrSize(offsets[0]);
            steps = this.calcStepOrSize(offsets[0], stepSize);

            // We can't reliably animate a time series that changes its time
            //  interval, so disable the selection of a different time interval
            if (offsets.length > 1) {
                d = [[offsets[0], 'steps']];
                cmp.disable();

            } else {
                switch (stepSize) {
                    case 'days':
                    d = [
                        ['days', 'day(s)']
                    ];
                    break;

                    case 'months':
                    d = [
                        ['days', 'day(s)'],
                        ['months', 'month(s)']
                    ];
                    break;

                    default:
                    d = [
                        ['hours', 'hour(s)'],
                        ['days', 'day(s)']
                    ];
                }
            }
        }

        // Removes and inserts the Ext.form.field.Number instance that represents
        //  the number of steps to be taken in an animation frame
        this.updateStepSelector(steps, Ext.isEmpty(offsets));

        // Create and bind a new store to hold the appropriate step sizes
        cmp.bindStore(Ext.create('Ext.data.ArrayStore', {
            fields: ['stepSize', 'text'],
            data: d
        }));

        // Apply the default settings to the UI
        cmp.setValue(stepSize);

        this._steps = steps;
        this._stepSize = stepSize;
        this._delay = this.getTopToolbar().down('#animate-delay').getValue();
    },

    /**TODO fetchRaster()
        Causes the corresponding view to "step" forwards or backwards in time
        with the animation according to a specified number of steps.
        @param  steps   {Number}    Negative steps are steps taken backwards
     */
    stepBy: function (steps) {
        var query = Ext.ComponentQuery.query('d3geomap');

        Ext.each(query, Ext.Function.bind(function (view) {
          var params, args, vals;
	  var ts = view.getMoment();

            if (Ext.isEmpty(ts)) {
                return;
            }
            var agg_toggle = Ext.ComponentQuery.query('field[name=showAggregation]')[0].getValue();
            if (agg_toggle) {
		args = this.getController('UserInteraction').getAggregationArgs();
		vals = Ext.Object.getValues(args);
		if (Ext.Array.clean(vals).length !== vals.length) {
		    // Throw an alert and to animation if not all of the aggregation fields are filled out
		    Ext.Msg.alert('Request Error', 'All aggregation fields must be filled out before animating');
		    this.getTopToolbar().down('#animate-btn').toggle(false);
		    return;
		}
		params = {
		    aggregate: args.aggregate,
		    start: ts.clone()
			  .add(steps, this._stepSize)
			  .toISOString(),
		    end: ts.clone()
			  .add(args.intervals,args.intervalGrouping)
			  .add(steps, this._stepSize)
			  .toISOString()
		};
	    } else {
		params = {
		    time: ts.clone()
			.add(steps, this._stepSize)
			.toISOString()
		};
	    }
            this.getController('UserInteraction').fetchRaster(view, params);
        }, this));
    },

    ////////////////////////////////////////////////////////////////////////////
    // Event Handlers //////////////////////////////////////////////////////////

    /**
        Handles a change in the animation delay from the Slider instance in the
        top Toolbar.
        @param  slider  {Ext.slide.Multi}
     */
    onDelayChange: function (slider) {
        this._delay = slider.getValue();
        this.getTopToolbar().down('#animate-btn').toggle(false);
    },

    /**
        Handles forwards/backwards iteration through a time series dataset;
        responds to "forward" or "backward" button being clicked.
        @param  btn {Ext.button.Button}
     */
    onStepButton: function (btn) {
        // Uncheck the "Show Aggregation" and "Show Difference" checkboxes
        //this.getController('UserInteraction').uncheckAggregates();

        switch (btn.getItemId()) {
            case 'backward-btn':
            this.stepBy(this._steps * -1);
            break;

            default:
            this.stepBy(this._steps);
        }
    },

    /**
        Handles a change in the step size as set by the user.
        @param  c       {Ext.form.field.ComboBox}
        @param  recs    {Array}
     */
    onStepSizeChange: function (c, recs) {
        var steps;

        this._stepSize = recs[0].get('stepSize');
        steps = this.calcStepOrSize(this._metadata.getTimeOffsets()[0],
            recs[0].get('stepSize'));

        this.updateStepSelector((steps === 0) ? 1 : steps);
    },

    /**
        Handles a change in the number of steps as set by the user.
        @param  c       {Ext.form.field.Number}
        @param  value   {Number}
     */
    onStepsChange: function (c, value) {
        this._steps = value;
    },

    /**
        Pause/Play the animation.
        @param  btn     {Ext.button.Button}
        @param  pressed {Boolean}
     */
    toggleAnimation: function (btn, pressed) {
        // Uncheck the "Show Aggregation" and "Show Difference" checkboxes
        //this.getController('UserInteraction').uncheckAggregates();
	this.getController('UserInteraction').toggleAggregateParams(pressed);

        if (pressed) {
            btn.setText('Pause');
            btn.setIconCls('icon-control-pause');
        } else {
            btn.setText('Animate');
            btn.setIconCls('icon-control-play');
        }

        this.animate(pressed, this._delay);
    },

    /**
        Removes and inserts the Ext.form.field.Number instance that represents
        the number of steps to be taken in an animation frame.
        @param  steps       {Number}
        @param  disabled    {Boolean}
     */
    updateStepSelector: function (steps, disabled) {
        var menu = this.getSettingsMenu();
        if (menu.query('field[name=steps]').length !== 0) {
            menu.remove('steps');
        }
        menu.insert(menu.items.length - 1, Ext.create('Ext.form.field.Number', {
            xtype: 'numberfield',
            disabled: disabled,
            itemId: 'steps',
            name: 'steps',
            value: steps,
            minValue: steps,
            step: steps,
            listeners: {
                change: Ext.bind(this.onStepsChange, this)
            }
        }));
    }

});

<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var mongoose = require('mongoose')
  , Article = mongoose.model('Article')
  , utils = require('../../lib/utils')
  , extend = require('util')._extend

/**
 * Load
 */

exports.load = function(req, res, next, id){
  var User = mongoose.model('User')

  Article.load(id, function (err, article) {
    if (err) return next(err)
    if (!article) return next(new Error('not found'))
    req.article = article
    next()
  })
}

/**
 * List
 */

exports.index = function(req, res){
  var page = (req.param('page') > 0 ? req.param('page') : 1) - 1
  var perPage = 30
  var options = {
    perPage: perPage,
    page: page
  }

  Article.list(options, function(err, articles) {
    if (err) return res.render('500')
    Article.count().exec(function (err, count) {
      res.render('articles/index', {
        title: 'Articles',
        articles: articles,
        page: page + 1,
        pages: Math.ceil(count / perPage)
      })
    })
  })
}

/**
 * New article
 */

exports.new = function(req, res){
  res.render('articles/new', {
    title: 'New Article',
    article: new Article({})
  })
}

/**
 * Create an article
 */

exports.create = function (req, res) {
  var article = new Article(req.body)
  article.user = req.user

  article.uploadAndSave(req.files.image, function (err) {
    if (!err) {
      req.flash('success', 'Successfully created article!')
      return res.redirect('/articles/'+article._id)
    }

    res.render('articles/new', {
      title: 'New Article',
      article: article,
      errors: utils.errors(err.errors || err)
    })
  })
}

/**
 * Edit an article
 */

exports.edit = function (req, res) {
  res.render('articles/edit', {
    title: 'Edit ' + req.article.title,
    article: req.article
  })
}

/**
 * Update article
 */

exports.update = function(req, res){
  var article = req.article
  article = extend(article, req.body)

  article.uploadAndSave(req.files.image, function(err) {
    if (!err) {
      return res.redirect('/articles/' + article._id)
    }

    res.render('articles/edit', {
      title: 'Edit Article',
      article: article,
      errors: err.errors
    })
  })
}

/**
 * Show
 */

exports.show = function(req, res){
  res.render('articles/show', {
    title: req.article.title,
    article: req.article
  })
}

/**
 * Delete an article
 */

exports.destroy = function(req, res){
  var article = req.article
  article.remove(function(err){
    req.flash('info', 'Deleted successfully')
    res.redirect('/articles')
  })
}
<commit_msg>fixes #98<commit_after>/**
 * Module dependencies.
 */

var mongoose = require('mongoose')
  , Article = mongoose.model('Article')
  , utils = require('../../lib/utils')
  , extend = require('util')._extend

/**
 * Load
 */

exports.load = function(req, res, next, id){
  var User = mongoose.model('User')

  Article.load(id, function (err, article) {
    if (err) return next(err)
    if (!article) return next(new Error('not found'))
    req.article = article
    next()
  })
}

/**
 * List
 */

exports.index = function(req, res){
  var page = (req.param('page') > 0 ? req.param('page') : 1) - 1
  var perPage = 30
  var options = {
    perPage: perPage,
    page: page
  }

  Article.list(options, function(err, articles) {
    if (err) return res.render('500')
    Article.count().exec(function (err, count) {
      res.render('articles/index', {
        title: 'Articles',
        articles: articles,
        page: page + 1,
        pages: Math.ceil(count / perPage)
      })
    })
  })
}

/**
 * New article
 */

exports.new = function(req, res){
  res.render('articles/new', {
    title: 'New Article',
    article: new Article({})
  })
}

/**
 * Create an article
 */

exports.create = function (req, res) {
  var article = new Article(req.body)
  article.user = req.user

  article.uploadAndSave(req.files.image, function (err) {
    if (!err) {
      req.flash('success', 'Successfully created article!')
      return res.redirect('/articles/'+article._id)
    }

    res.render('articles/new', {
      title: 'New Article',
      article: article,
      errors: utils.errors(err.errors || err)
    })
  })
}

/**
 * Edit an article
 */

exports.edit = function (req, res) {
  res.render('articles/edit', {
    title: 'Edit ' + req.article.title,
    article: req.article
  })
}

/**
 * Update article
 */

exports.update = function(req, res){
  var article = req.article
  article = extend(article, req.body)

  article.uploadAndSave(req.files.image, function(err) {
    if (!err) {
      return res.redirect('/articles/' + article._id)
    }

    res.render('articles/edit', {
      title: 'Edit Article',
      article: article,
      errors: utils.errors(err.errors || err)
    })
  })
}

/**
 * Show
 */

exports.show = function(req, res){
  res.render('articles/show', {
    title: req.article.title,
    article: req.article
  })
}

/**
 * Delete an article
 */

exports.destroy = function(req, res){
  var article = req.article
  article.remove(function(err){
    req.flash('info', 'Deleted successfully')
    res.redirect('/articles')
  })
}
<|endoftext|>


<commit_before>RippleTransaction = require("../models/ripple_transaction")

module.exports = (function(){
  return {
    index: function(req, res){

    },

    gateway: function(req, res) {
      sequelize.query('select SUM("cashAmount") as amount, "currency" FROM bank_txs GROUP BY "currency" WHERE accountId = ?', req.params.accountId).complete(function(err,rows){
        if (err) { res.send({ success: false, error: err }) }
        res.send({ success: true, rippleTransactions: rows })
      })
    }

  }
})();
<commit_msg>[FEATURE] Add shell controller action for getting user balances<commit_after>RippleTransaction = require("../models/ripple_transaction")

module.exports = (function(){
  function userIndex(req, res) {
    User.find(req.params.id).complete(function(err, user) {
      user.externalBalances(function(err, externalBalances) {
        user.rippleBalances(function(err, rippleBalances) {
          res.send({
            external_balances: (externalBalances || []),
            ripple_balances: (rippleBalances || [])
          });
        })
      });
    });
  }

  return {
    userIndex: userIndex,

    gateway: function(req, res) {
      sequelize.query('select SUM("cashAmount") as amount, "currency" FROM bank_txs GROUP BY "currency" WHERE accountId = ?', req.params.accountId).complete(function(err,rows){
        if (err) { res.send({ success: false, error: err }) }
        res.send({ success: true, rippleTransactions: rows })
      })
    }

  }
})();
<|endoftext|>


<commit_before>import _ from 'lodash/lodash';
import moment from 'moment';
import Ember from 'ember';
import ApplicationController from './application';
import buildElasticCall from '../utils/build-elastic-call';
import ENV from '../config/environment';
import { getUniqueList, getSplitParams, encodeParams } from '../utils/elastic-query';

const MAX_SOURCES = 500;
let filterQueryParams = ['tags', 'sources', 'publishers', 'funders', 'institutions', 'organizations', 'language', 'contributors', 'type'];

export default ApplicationController.extend({

    metrics: Ember.inject.service(),
    category: 'discover',

    queryParams:  Ember.computed(function() {
        let allParams = ['q', 'start', 'end', 'sort', 'page'];
        allParams.push(...filterQueryParams);
        return allParams;
    }),

    page: 1,
    size: 10,
    q: '',
    tags: '',
    sources: '',
    publishers: '',
    funders: '',
    institutions: '',
    organizations: '',
    language: '',
    contributors: '',
    start: '',
    end: '',
    type: '',
    sort: '',

    noResultsMessage: Ember.computed('numberOfResults', function() {
        return this.get('numberOfResults') > 0 ? '' : 'No results. Try removing some filters.';
    }),

    collapsedFilters: true,
    collapsedQueryBody: true,

    results: Ember.ArrayProxy.create({ content: [] }),
    loading: true,
    eventsLastUpdated: Date().toString(),
    numberOfResults: 0,
    took: 0,
    numberOfSources: 0,
    types: {},

    totalPages: Ember.computed('numberOfResults', 'size', function() {
        return Math.ceil(this.get('numberOfResults') / this.get('size'));
    }),

    clampedPages: Ember.computed('totalPages', 'size', function() {
        let maxPages = Math.ceil(10000 / this.get('size'));
        let totalPages = this.get('totalPages');
        return totalPages < maxPages ? totalPages : maxPages;
    }),

    hiddenPages: Ember.computed('clampedPages', 'totalPages', function() {
        const total = this.get('totalPages');
        const max = this.get('clampedPages');
        if (total !== max) {
            return total - max;
        }
        return null;
    }),

    processedTypes: Ember.computed('types', function() {
        return this.transformTypes(this.get('types'));
    }),

    sortOptions: [{
        display: 'Relevance',
        sortBy: ''
    }, {
        display: 'Date Updated (Desc)',
        sortBy: '-date_updated'
    }, {
        display: 'Date Updated (Asc)',
        sortBy: 'date_updated'
    }, {
        display: 'Ingest Date (Asc)',
        sortBy: 'date_created'
    }, {
        display: 'Ingest Date (Desc)',
        sortBy: '-date_created'
    }],

    init() {
        //TODO Sort initial results on date_modified
        this._super(...arguments);
        this.set('firstLoad', true);
        this.set('facetFilters', Ember.Object.create());
        this.getTypes();
        this.set('debouncedLoadPage', _.debounce(this.loadPage.bind(this), 500));
        this.getCounts();
    },

    getCounts() {
        let queryBody = JSON.stringify({
            size: 0,
            aggregations: {
                sources: {
                    cardinality: {
                        field: 'sources',
                        precision_threshold: MAX_SOURCES
                    }
                }
            }
        });
        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then((json) => {
            this.setProperties({
                numberOfEvents: json.hits.total,
                numberOfSources: json.aggregations.sources.value
            });
        });
    },

    transformTypes(obj) {
        if (typeof (obj) !== 'object') {
            return obj;
        }

        for (let key in obj) {
            let lowKey = key.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
            obj[lowKey] = this.transformTypes(obj[key]);
            if (key !== lowKey) {
                delete obj[key];
            }
        }
        return obj;
    },

    getTypes() {
        return Ember.$.ajax({
            url: ENV.apiUrl + '/schema/creativework/hierarchy/',
            crossDomain: true,
            type: 'GET',
            contentType: 'application/vnd.api+json',
        }).then((json) => {
            if (json.data) {
                this.set('types', json.data);
            }
        });
    },

    searchUrl: Ember.computed(function() {
        return buildElasticCall();
    }),

    getQueryBody() {
        let facetFilters = this.get('facetFilters');
        let filters = [];
        for (let k of Object.keys(facetFilters)) {
            let filter = facetFilters[k];
            if (filter) {
                if (Ember.$.isArray(filter)) {
                    filters = filters.concat(filter);
                } else {
                    filters.push(filter);
                }
            }
        }

        let query = {
            query_string: {
                query: this.get('q') || '*'
            }
        };
        if (filters.length) {
            query = {
                bool: {
                    must: query,
                    filter: filters
                }
            };
        }

        let page = this.get('page');
        let queryBody = {
            query,
            from: (page - 1) * this.get('size')
        };
        if (this.get('sort')) {
            let sortBy = {};
            sortBy[this.get('sort').replace(/^-/, '')] = this.get('sort')[0] === '-' ? 'desc' : 'asc';
            queryBody.sort = sortBy;
        }
        if (page === 1 || this.get('firstLoad')) {
            queryBody.aggregations = this.get('elasticAggregations');
        }

        this.set('displayQueryBody', { query });
        return this.set('queryBody', queryBody);
    },

    elasticAggregations: Ember.computed(function() {
        return {
            sources: {
                terms: {
                    field: 'sources',
                    size: MAX_SOURCES
                }
            }
        };
    }),

    loadPage() {
        let queryBody = JSON.stringify(this.getQueryBody());
        this.set('loading', true);
        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then((json) => {
            let results = json.hits.hits.map(hit => Object.assign(
                {},
                hit._source,
                ['contributors', 'publishers'].reduce((acc, key) => Object.assign(
                    acc,
                    { [key]: hit._source.lists[key] }
                ), { typeSlug: hit._source.type.classify().toLowerCase() })
            ));

            if (json.aggregations) {
                this.set('aggregations', json.aggregations);
            }
            this.setProperties({
                numberOfResults: json.hits.total,
                took: moment.duration(json.took).asSeconds(),
                loading: false,
                firstLoad: false,
                results: results,
                queryError: false
            });
            if (this.get('totalPages') && this.get('totalPages') < this.get('page')) {
                this.search();
            }
        }, (errorResponse) => {
            this.setProperties({
                loading: false,
                firstLoad: false,
                numberOfResults: 0,
                results: []
            });
            if (errorResponse.status === 400) {
                this.set('queryError', true);
            } else {
                this.send('elasticDown');
            }
        });
    },

    search() {
        if (!this.get('firstLoad')) {
            this.set('page', 1);
        }
        this.set('loading', true);
        this.get('results').clear();
        this.get('debouncedLoadPage')();
    },

    facets: Ember.computed('processedTypes', function() {
        return [
            { key: 'sources', title: 'Source', component: 'search-facet-source' },
            { key: 'date', title: 'Date', component: 'search-facet-daterange' },
            { key: 'type', title: 'Type', component: 'search-facet-worktype', data: this.get('processedTypes') },
            { key: 'tags', title: 'Tag', component: 'search-facet-typeahead' },
            { key: 'publishers', title: 'Publisher', component: 'search-facet-typeahead', base: 'agents', type: 'publisher' },
            { key: 'funders', title: 'Funder', component: 'search-facet-typeahead', base: 'agents', type: 'funder' },
            { key: 'language', title: 'Language', component: 'search-facet-language' },
            { key: 'contributors', title: 'People', component: 'search-facet-typeahead', base: 'agents', type: 'person' },
        ];
    }),

    facetStatesArray: [],

    facetStates: Ember.computed(...filterQueryParams, 'end', 'start', function() {
        let facetStates = {};
        for (let param of filterQueryParams) {
            facetStates[param] = getSplitParams(this.get(param));
        }
        facetStates.date = { start: this.get('start'), end: this.get('end') };

        Ember.run.once(this, function() {
            let facets = this.get('facetStates');
            let facetArray = [];
            for (let key of Object.keys(facets)) {
                facetArray.push({ key: key, value: facets[key] });
            }
            this.set('facetStatesArray', facetArray);
        });
        return facetStates;
    }),

    atomFeedUrl: Ember.computed('queryBody', function() {
        let query = this.get('queryBody.query');
        let encodedQuery = encodeURIComponent(JSON.stringify(query));
        return `${ENV.apiUrl}/atom/?elasticQuery=${encodedQuery}`;
    }),

    scrollToResults() {
        Ember.$('html, body').scrollTop(Ember.$('.results-top').position().top);
    },

    actions: {

        addFilter(type, filterValue) {
            const category = this.get('category');
            const action = 'add-filter';
            const label = filterValue;

            this.get('metrics').trackEvent({ category, action, label });

            let currentValue = getSplitParams(this.get(type)) || [];
            let newValue = getUniqueList([filterValue].concat(currentValue));
            this.set(type, encodeParams(newValue));
        },

        removeFilter(type, filterValue) {
            const category = this.get('category');
            const action = 'remove-filter';
            const label = filterValue;

            this.get('metrics').trackEvent({ category, action, label });

            let currentValue = getSplitParams(this.get(type)) || [];
            let index = currentValue.indexOf(filterValue);
            if (index > -1) {
                currentValue.splice(index, 1);
            }
            currentValue = currentValue.length ? encodeParams(currentValue) : '';
            this.set(type, currentValue);
            this.get('facetFilters');
        },

        toggleCollapsedQueryBody() {
            this.toggleProperty('collapsedQueryBody');
        },

        toggleCollapsedFilters() {
            this.toggleProperty('collapsedFilters');
        },

        typing(val, event) {
            // Ignore all keycodes that do not result in the value changing
            // 8 == Backspace, 32 == Space
            if (event.keyCode < 49 && !(event.keyCode === 8 || event.keyCode === 32)) {
                return;
            }
            this.search();
        },

        search() {
            const category = this.get('category');
            const action = 'search';
            const label = this.get('q');

            this.get('metrics').trackEvent({ category, action, label });

            this.search();
        },

        updateParams(key, value) {
            if (key === 'date') {
                this.set('start', value.start);
                this.set('end', value.end);
            } else {
                value = value ? encodeParams(value) : '';
                this.set(key, value);
            }
        },

        filtersChanged() {
            this.search();
        },

        loadPageNoScroll(newPage) {
            this.send('loadPage', newPage, false);
        },

        loadPage(newPage, scroll = true) {
            if (newPage === this.get('page') || newPage < 1 || newPage > this.get('totalPages')) {
                return;
            }

            const category = this.get('category');
            const action = 'load-result-page';
            const label = newPage;

            this.get('metrics').trackEvent({ category, action, label });

            this.set('page', newPage);
            if (scroll) {
                this.scrollToResults();
            }
            this.loadPage();
        },

        selectSortOption(option) {
            this.set('sort', option);
            this.search();
        },

        clearFilters() {
            const category = this.get('category');
            const action = 'clear-filters';
            const label = 'clear';

            this.get('metrics').trackEvent({ category, action, label });

            this.set('facetFilters', Ember.Object.create());
            for (var param in filterQueryParams) {
                let key = filterQueryParams[param];
                if (filterQueryParams.indexOf(key) > -1) {
                    this.set(key, '');
                }
            }
            this.set('start', '');
            this.set('end', '');
            this.set('sort', '');
            this.search();
        }
    }
});
<commit_msg>[SHARE-596][Feature] Add note about limiting pages (#172)<commit_after>import _ from 'lodash/lodash';
import moment from 'moment';
import Ember from 'ember';
import ApplicationController from './application';
import buildElasticCall from '../utils/build-elastic-call';
import ENV from '../config/environment';
import { getUniqueList, getSplitParams, encodeParams } from '../utils/elastic-query';

const MAX_SOURCES = 500;
let filterQueryParams = ['tags', 'sources', 'publishers', 'funders', 'institutions', 'organizations', 'language', 'contributors', 'type'];

export default ApplicationController.extend({

    metrics: Ember.inject.service(),
    category: 'discover',

    queryParams:  Ember.computed(function() {
        let allParams = ['q', 'start', 'end', 'sort', 'page'];
        allParams.push(...filterQueryParams);
        return allParams;
    }),

    page: 1,
    size: 10,
    q: '',
    tags: '',
    sources: '',
    publishers: '',
    funders: '',
    institutions: '',
    organizations: '',
    language: '',
    contributors: '',
    start: '',
    end: '',
    type: '',
    sort: '',

    noResultsMessage: Ember.computed('numberOfResults', function() {
        return this.get('numberOfResults') > 0 ? '' : 'No results. Try removing some filters.';
    }),

    collapsedFilters: true,
    collapsedQueryBody: true,

    results: Ember.ArrayProxy.create({ content: [] }),
    loading: true,
    eventsLastUpdated: Date().toString(),
    numberOfResults: 0,
    took: 0,
    numberOfSources: 0,
    types: {},

    totalPages: Ember.computed('numberOfResults', 'size', function() {
        return Math.ceil(this.get('numberOfResults') / this.get('size'));
    }),

    clampedPages: Ember.computed('totalPages', 'size', function() {
        // requesting over 10000 will error due to elastic limitations
        // https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
        let maxPages = Math.ceil(10000 / this.get('size'));
        let totalPages = this.get('totalPages');
        return totalPages < maxPages ? totalPages : maxPages;
    }),

    hiddenPages: Ember.computed('clampedPages', 'totalPages', function() {
        const total = this.get('totalPages');
        const max = this.get('clampedPages');
        if (total !== max) {
            return total - max;
        }
        return null;
    }),

    processedTypes: Ember.computed('types', function() {
        return this.transformTypes(this.get('types'));
    }),

    sortOptions: [{
        display: 'Relevance',
        sortBy: ''
    }, {
        display: 'Date Updated (Desc)',
        sortBy: '-date_updated'
    }, {
        display: 'Date Updated (Asc)',
        sortBy: 'date_updated'
    }, {
        display: 'Ingest Date (Asc)',
        sortBy: 'date_created'
    }, {
        display: 'Ingest Date (Desc)',
        sortBy: '-date_created'
    }],

    init() {
        //TODO Sort initial results on date_modified
        this._super(...arguments);
        this.set('firstLoad', true);
        this.set('facetFilters', Ember.Object.create());
        this.getTypes();
        this.set('debouncedLoadPage', _.debounce(this.loadPage.bind(this), 500));
        this.getCounts();
    },

    getCounts() {
        let queryBody = JSON.stringify({
            size: 0,
            aggregations: {
                sources: {
                    cardinality: {
                        field: 'sources',
                        precision_threshold: MAX_SOURCES
                    }
                }
            }
        });
        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then((json) => {
            this.setProperties({
                numberOfEvents: json.hits.total,
                numberOfSources: json.aggregations.sources.value
            });
        });
    },

    transformTypes(obj) {
        if (typeof (obj) !== 'object') {
            return obj;
        }

        for (let key in obj) {
            let lowKey = key.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
            obj[lowKey] = this.transformTypes(obj[key]);
            if (key !== lowKey) {
                delete obj[key];
            }
        }
        return obj;
    },

    getTypes() {
        return Ember.$.ajax({
            url: ENV.apiUrl + '/schema/creativework/hierarchy/',
            crossDomain: true,
            type: 'GET',
            contentType: 'application/vnd.api+json',
        }).then((json) => {
            if (json.data) {
                this.set('types', json.data);
            }
        });
    },

    searchUrl: Ember.computed(function() {
        return buildElasticCall();
    }),

    getQueryBody() {
        let facetFilters = this.get('facetFilters');
        let filters = [];
        for (let k of Object.keys(facetFilters)) {
            let filter = facetFilters[k];
            if (filter) {
                if (Ember.$.isArray(filter)) {
                    filters = filters.concat(filter);
                } else {
                    filters.push(filter);
                }
            }
        }

        let query = {
            query_string: {
                query: this.get('q') || '*'
            }
        };
        if (filters.length) {
            query = {
                bool: {
                    must: query,
                    filter: filters
                }
            };
        }

        let page = this.get('page');
        let queryBody = {
            query,
            from: (page - 1) * this.get('size')
        };
        if (this.get('sort')) {
            let sortBy = {};
            sortBy[this.get('sort').replace(/^-/, '')] = this.get('sort')[0] === '-' ? 'desc' : 'asc';
            queryBody.sort = sortBy;
        }
        if (page === 1 || this.get('firstLoad')) {
            queryBody.aggregations = this.get('elasticAggregations');
        }

        this.set('displayQueryBody', { query });
        return this.set('queryBody', queryBody);
    },

    elasticAggregations: Ember.computed(function() {
        return {
            sources: {
                terms: {
                    field: 'sources',
                    size: MAX_SOURCES
                }
            }
        };
    }),

    loadPage() {
        let queryBody = JSON.stringify(this.getQueryBody());
        this.set('loading', true);
        return Ember.$.ajax({
            url: this.get('searchUrl'),
            crossDomain: true,
            type: 'POST',
            contentType: 'application/json',
            data: queryBody
        }).then((json) => {
            let results = json.hits.hits.map(hit => Object.assign(
                {},
                hit._source,
                ['contributors', 'publishers'].reduce((acc, key) => Object.assign(
                    acc,
                    { [key]: hit._source.lists[key] }
                ), { typeSlug: hit._source.type.classify().toLowerCase() })
            ));

            if (json.aggregations) {
                this.set('aggregations', json.aggregations);
            }
            this.setProperties({
                numberOfResults: json.hits.total,
                took: moment.duration(json.took).asSeconds(),
                loading: false,
                firstLoad: false,
                results: results,
                queryError: false
            });
            if (this.get('totalPages') && this.get('totalPages') < this.get('page')) {
                this.search();
            }
        }, (errorResponse) => {
            this.setProperties({
                loading: false,
                firstLoad: false,
                numberOfResults: 0,
                results: []
            });
            if (errorResponse.status === 400) {
                this.set('queryError', true);
            } else {
                this.send('elasticDown');
            }
        });
    },

    search() {
        if (!this.get('firstLoad')) {
            this.set('page', 1);
        }
        this.set('loading', true);
        this.get('results').clear();
        this.get('debouncedLoadPage')();
    },

    facets: Ember.computed('processedTypes', function() {
        return [
            { key: 'sources', title: 'Source', component: 'search-facet-source' },
            { key: 'date', title: 'Date', component: 'search-facet-daterange' },
            { key: 'type', title: 'Type', component: 'search-facet-worktype', data: this.get('processedTypes') },
            { key: 'tags', title: 'Tag', component: 'search-facet-typeahead' },
            { key: 'publishers', title: 'Publisher', component: 'search-facet-typeahead', base: 'agents', type: 'publisher' },
            { key: 'funders', title: 'Funder', component: 'search-facet-typeahead', base: 'agents', type: 'funder' },
            { key: 'language', title: 'Language', component: 'search-facet-language' },
            { key: 'contributors', title: 'People', component: 'search-facet-typeahead', base: 'agents', type: 'person' },
        ];
    }),

    facetStatesArray: [],

    facetStates: Ember.computed(...filterQueryParams, 'end', 'start', function() {
        let facetStates = {};
        for (let param of filterQueryParams) {
            facetStates[param] = getSplitParams(this.get(param));
        }
        facetStates.date = { start: this.get('start'), end: this.get('end') };

        Ember.run.once(this, function() {
            let facets = this.get('facetStates');
            let facetArray = [];
            for (let key of Object.keys(facets)) {
                facetArray.push({ key: key, value: facets[key] });
            }
            this.set('facetStatesArray', facetArray);
        });
        return facetStates;
    }),

    atomFeedUrl: Ember.computed('queryBody', function() {
        let query = this.get('queryBody.query');
        let encodedQuery = encodeURIComponent(JSON.stringify(query));
        return `${ENV.apiUrl}/atom/?elasticQuery=${encodedQuery}`;
    }),

    scrollToResults() {
        Ember.$('html, body').scrollTop(Ember.$('.results-top').position().top);
    },

    actions: {

        addFilter(type, filterValue) {
            const category = this.get('category');
            const action = 'add-filter';
            const label = filterValue;

            this.get('metrics').trackEvent({ category, action, label });

            let currentValue = getSplitParams(this.get(type)) || [];
            let newValue = getUniqueList([filterValue].concat(currentValue));
            this.set(type, encodeParams(newValue));
        },

        removeFilter(type, filterValue) {
            const category = this.get('category');
            const action = 'remove-filter';
            const label = filterValue;

            this.get('metrics').trackEvent({ category, action, label });

            let currentValue = getSplitParams(this.get(type)) || [];
            let index = currentValue.indexOf(filterValue);
            if (index > -1) {
                currentValue.splice(index, 1);
            }
            currentValue = currentValue.length ? encodeParams(currentValue) : '';
            this.set(type, currentValue);
            this.get('facetFilters');
        },

        toggleCollapsedQueryBody() {
            this.toggleProperty('collapsedQueryBody');
        },

        toggleCollapsedFilters() {
            this.toggleProperty('collapsedFilters');
        },

        typing(val, event) {
            // Ignore all keycodes that do not result in the value changing
            // 8 == Backspace, 32 == Space
            if (event.keyCode < 49 && !(event.keyCode === 8 || event.keyCode === 32)) {
                return;
            }
            this.search();
        },

        search() {
            const category = this.get('category');
            const action = 'search';
            const label = this.get('q');

            this.get('metrics').trackEvent({ category, action, label });

            this.search();
        },

        updateParams(key, value) {
            if (key === 'date') {
                this.set('start', value.start);
                this.set('end', value.end);
            } else {
                value = value ? encodeParams(value) : '';
                this.set(key, value);
            }
        },

        filtersChanged() {
            this.search();
        },

        loadPageNoScroll(newPage) {
            this.send('loadPage', newPage, false);
        },

        loadPage(newPage, scroll = true) {
            if (newPage === this.get('page') || newPage < 1 || newPage > this.get('totalPages')) {
                return;
            }

            const category = this.get('category');
            const action = 'load-result-page';
            const label = newPage;

            this.get('metrics').trackEvent({ category, action, label });

            this.set('page', newPage);
            if (scroll) {
                this.scrollToResults();
            }
            this.loadPage();
        },

        selectSortOption(option) {
            this.set('sort', option);
            this.search();
        },

        clearFilters() {
            const category = this.get('category');
            const action = 'clear-filters';
            const label = 'clear';

            this.get('metrics').trackEvent({ category, action, label });

            this.set('facetFilters', Ember.Object.create());
            for (var param in filterQueryParams) {
                let key = filterQueryParams[param];
                if (filterQueryParams.indexOf(key) > -1) {
                    this.set(key, '');
                }
            }
            this.set('start', '');
            this.set('end', '');
            this.set('sort', '');
            this.search();
        }
    }
});
<|endoftext|>


<commit_before>import * as React from "react";
import {getFields} from "./utils/getFields.js";

/**
 * Component used to render Build Information.
 *
 * @param {*} props input properties containing build information
 */
export const BuildSnapshot = (props) => {
  const headerFields = ["description", "commitHash", "repository", "status"];
  const trayFields = ["builders", "timestamp"];

  const headerData = getFields(props.buildData, headerFields, "");
  const trayData = getFields(props.buildData, trayFields, "");

  return (

    Snapshot

  );
}
<commit_msg>Implement state variable to indicate if element 'open'<commit_after>import * as React from "react";
import {getFields} from "./utils/getFields.js";

/**
 * Component used to render Build Information.
 *
 * @param {*} props input properties containing build information
 */
export const BuildSnapshot = (props) => {
  // State variable indicating whether the BuildSnapshot is open
  // isOpen === true indicates the tray should be visible
  // and the indicator arrow on the Header should be facing downwards.
  const [isOpen, setIsOpen] = React.useState(false);

  const headerFields = ["description", "commitHash", "repository", "status"];
  const trayFields = ["builders", "timestamp"];

  const headerData = getFields(props.buildData, headerFields, "");
  const trayData = getFields(props.buildData, trayFields, "");

  return (

    Snapshot

  );
}
<|endoftext|>


<commit_before>var teamsCtrl = controllers.controller("TeamsCtrl", function($scope, Restangular){
    Restangular.all("players").getList().then(function(players){
        $scope.players = players;
        Restangular.all("games").getList().then(function(games){
            $scope.game = _.findWhere(games,{finished: false});
            for(var i=0;i<$scope.players.length;i++){
                var player = $scope.players[i];
                player.teamRef = (_.contains($scope.game.teamA.teammateRefs, player._id)) ?
                                 "A" : (_.contains($scope.game.teamB.teammateRefs, player._id)) ?
                                 "B" : "N";
            }
        }, function errorCallback() {
            alert("Oops unable to get info from server. Please refresh. :(");
        });
    }, function errorCallback() {
        alert("Oops unable to get info from server. Please refresh. :(");
    });

    $('.tooltiped').tooltip({container: 'body'});

    $scope.join = function(player){
        $('#updateFade').modal('toggle');
        var team = {};
        var keyTeam = "";
        if(player.teamRef==="N"){
            team = ($scope.game.teamA.teammateRefs.length <= $scope.game.teamB.teammateRefs.length) ? $scope.game.teamA : $scope.game.teamB ;
            keyTeam = ($scope.game.teamA.teammateRefs.length <= $scope.game.teamB.teammateRefs.length) ? "A" : "B" ;
        } else {
            team = (player.teamRef==="A") ? $scope.game.teamB : $scope.game.teamA ;
            keyTeam = (player.teamRef==="A") ? "B" : "A" ;
        }
        $scope.game.customPUT({}, "join", {keyTeam: keyTeam, keyPlayer: player._id}).then(function(){
            team.teammateRefs.push(player._id);
            player.teamRef = keyTeam;
        }, function errorCallback() {
            alert("Oooops unable to update server. Please refresh. :(");
        });
        $('#updateFade').modal('toggle');
    }

    $scope.leave = function(player){
        $('#updateFade').modal('toggle');
        $scope.game.customPUT({}, "leave", {keyPlayer: player._id}).then(function(){
            var i = $scope.game.teamA.teammateRefs.indexOf(player._id);
            if($scope.game.teamA.teammateRefs.indexOf(player._id) != -1) {
                $scope.game.teamA.teammateRefs.splice(i, 1);
            }
            i = $scope.game.teamB.teammateRefs.indexOf(player._id);
            if ($scope.game.teamB.teammateRefs.indexOf(player._id) != -1) {
                $scope.game.teamB.teammateRefs.splice(i, 1);
            }
            player.teamRef = "N";
        }, function errorCallback() {
            alert("Oooops unable to update server. Please refresh. :(");
         });
         $('#updateFade').modal('toggle');
    }

    $scope.createPlayer = function(){
        var newPlayer = {firstName: $scope.newFirstName, lastName: $scope.newLastName, goals: 0, wins: 0, losses: 0, subscription: false, certificate: false};
        $scope.players.post(newPlayer).then(function(playerPostResponse) {
            $scope.newFirstName="";
            $scope.newLastName="";
            playerPostResponse.teamRef = "N";
            $scope.players.push(playerPostResponse);
        }, function errorCallback(){
            $scope.newFirstName="";
            $scope.newLastName="";
            alert("Oooops unable to create from server. Please refresh. :(");
        });
        $('#addPlayerModal').modal('toggle');
    }

    $scope.destroyPlayer = function(player){
        player.remove().then(function() {
            $scope.players = _.without($scope.players, player);
        }, function errorCallback(){
            alert("Oooops unable to delete from server. Please refresh. :(");
        });
    }

    $scope.editPlayer = function(player){
        $scope.editedPlayer = Restangular.copy(player);
        $('#editPlayerModal').modal('toggle');
    }

    $scope.updatePlayer = function(){
        $scope.editedPlayer.put().then(function() {
            $scope.players[_.indexOf($scope.players, _.findWhere($scope.players, {_id: $scope.editedPlayer._id}))] = $scope.editedPlayer;
            $scope.editedPlayer = {};
        }, function errorCallback(){
            $scope.editedPlayer = {};
            alert("Oooops unable to update from server. Please refresh. :(");
        });
        $('#editPlayerModal').modal('toggle');
    }
});<commit_msg>toggles team panels as we move players from one to another<commit_after>var teamsCtrl = controllers.controller("TeamsCtrl", function($scope, Restangular){
    Restangular.all("players").getList().then(function(players){
        $scope.players = players;
        Restangular.all("games").getList().then(function(games){
            $scope.game = _.findWhere(games,{finished: false});
            for(var i=0;i<$scope.players.length;i++){
                var player = $scope.players[i];
                player.teamRef = (_.contains($scope.game.teamA.teammateRefs, player._id)) ?
                                 "A" : (_.contains($scope.game.teamB.teammateRefs, player._id)) ?
                                 "B" : "N";
            }
        }, function errorCallback() {
            alert("Oops unable to get info from server. Please refresh. :(");
        });
    }, function errorCallback() {
        alert("Oops unable to get info from server. Please refresh. :(");
    });

    //init tooltips
    $('.tooltiped').tooltip({container: 'body'});

    //init collapsible elements
    $('#collapseOne').collapse({
      toggle: false,
      parent: "#accordion"
    })
    $('#collapseTwo').collapse({
      toggle: false,
      parent: "#accordion"
    })
    $('#collapseThree').collapse({
      toggle: false,
      parent: "#accordion"
    })

    $scope.join = function(player){
        $('#updateFade').modal('toggle');
        var team = {};
        var keyTeam = "";
        if(player.teamRef==="N"){
            team = ($scope.game.teamA.teammateRefs.length <= $scope.game.teamB.teammateRefs.length) ? $scope.game.teamA : $scope.game.teamB ;
            keyTeam = ($scope.game.teamA.teammateRefs.length <= $scope.game.teamB.teammateRefs.length) ? "A" : "B" ;
        } else {
            team = (player.teamRef==="A") ? $scope.game.teamB : $scope.game.teamA ;
            keyTeam = (player.teamRef==="A") ? "B" : "A" ;
        }
        $scope.game.customPUT({}, "join", {keyTeam: keyTeam, keyPlayer: player._id}).then(function(){
            team.teammateRefs.push(player._id);
            player.teamRef = keyTeam;
            if(player.teamRef == "A"){
                $('#collapseOne').collapse('toggle');
            } else {
                $('#collapseTwo').collapse('toggle');
            }
        }, function errorCallback() {
            alert("Oooops unable to update server. Please refresh. :(");
        });
        $('#updateFade').modal('toggle');
    }

    $scope.leave = function(player){
        $('#updateFade').modal('toggle');
        $scope.game.customPUT({}, "leave", {keyPlayer: player._id}).then(function(){
            var i = $scope.game.teamA.teammateRefs.indexOf(player._id);
            if($scope.game.teamA.teammateRefs.indexOf(player._id) != -1) {
                $scope.game.teamA.teammateRefs.splice(i, 1);
            }
            i = $scope.game.teamB.teammateRefs.indexOf(player._id);
            if ($scope.game.teamB.teammateRefs.indexOf(player._id) != -1) {
                $scope.game.teamB.teammateRefs.splice(i, 1);
            }
            player.teamRef = "N";
            $('#collapseThree').collapse('toggle')
        }, function errorCallback() {
            alert("Oooops unable to update server. Please refresh. :(");
         });
         $('#updateFade').modal('toggle');
    }

    $scope.createPlayer = function(){
        var newPlayer = {firstName: $scope.newFirstName, lastName: $scope.newLastName, goals: 0, wins: 0, losses: 0, subscription: false, certificate: false};
        $scope.players.post(newPlayer).then(function(playerPostResponse) {
            $scope.newFirstName="";
            $scope.newLastName="";
            playerPostResponse.teamRef = "N";
            $scope.players.push(playerPostResponse);
        }, function errorCallback(){
            $scope.newFirstName="";
            $scope.newLastName="";
            alert("Oooops unable to create from server. Please refresh. :(");
        });
        $('#addPlayerModal').modal('toggle');
    }

    $scope.destroyPlayer = function(player){
        player.remove().then(function() {
            $scope.players = _.without($scope.players, player);
        }, function errorCallback(){
            alert("Oooops unable to delete from server. Please refresh. :(");
        });
    }

    $scope.editPlayer = function(player){
        $scope.editedPlayer = Restangular.copy(player);
        $('#editPlayerModal').modal('toggle');
    }

    $scope.updatePlayer = function(){
        $scope.editedPlayer.put().then(function() {
            $scope.players[_.indexOf($scope.players, _.findWhere($scope.players, {_id: $scope.editedPlayer._id}))] = $scope.editedPlayer;
            $scope.editedPlayer = {};
        }, function errorCallback(){
            $scope.editedPlayer = {};
            alert("Oooops unable to update from server. Please refresh. :(");
        });
        $('#editPlayerModal').modal('toggle');
    }
});<|endoftext|>