<commit_before>/*!
 * VisualEditor UserInterface MWMapsDialog class.
 *
 * @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * Dialog for editing MW maps.
 *
 * @class
 * @extends ve.ui.MWExtensionDialog
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
ve.ui.MWMapsDialog = function VeUiMWMapsDialog() {
	// Parent constructor
	ve.ui.MWMapsDialog.super.apply( this, arguments );

	this.updateGeoJson = $.debounce( 300, $.proxy( this.updateGeoJson, this ) );
};

/* Inheritance */

OO.inheritClass( ve.ui.MWMapsDialog, ve.ui.MWExtensionDialog );

/* Static Properties */

ve.ui.MWMapsDialog.static.name = 'mwMaps';

ve.ui.MWMapsDialog.static.title = OO.ui.deferMsg( 'visualeditor-mwmapsdialog-title' );

ve.ui.MWMapsDialog.static.size = 'larger';

ve.ui.MWMapsDialog.static.allowedEmpty = true;

ve.ui.MWMapsDialog.static.modelClasses = [ ve.dm.MWMapsNode, ve.dm.MWInlineMapsNode ];

/* Methods */

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.initialize = function () {
	var panel;

	// Parent method
	ve.ui.MWMapsDialog.super.prototype.initialize.call( this );

	this.$mapContainer = $( '<div>' ).addClass( 've-ui-mwMapsDialog-mapWidget' );
	this.$map = $( '<div>' ).appendTo( this.$mapContainer );
	this.map = null;
	this.mapPromise = null;
	this.scalable = null;
	this.updatingGeoJson = false;

	this.dimensions = new ve.ui.DimensionsWidget();

	this.input = new ve.ui.MWAceEditorWidget( {
		multiline: true,
		autosize: true,
		maxRows: 10,
		classes: [ 've-ui-mwMapsDialog-geoJSONWidget' ]
	} )
		.setLanguage( 'json' )
		.toggleLineNumbers( false )
		.setRTL( false );

	this.resetMapButton = new OO.ui.ButtonWidget( {
		label: ve.msg( 'visualeditor-mwmapsdialog-reset-map' )
	} );

	panel = new OO.ui.PanelLayout( {
		padded: true,
		expanded: false
	} );

	this.dimensionsField = new OO.ui.FieldLayout( this.dimensions, {
		align: 'right',
		label: ve.msg( 'visualeditor-mwmapsdialog-size' )
	} );

	this.$resetMapButtonContainer = $( '<div>' ).addClass( 've-ui-mwMapsDialog-resetMapButton' );

	this.geoJsonField = new OO.ui.FieldLayout( this.input, {
		align: 'top',
		label: ve.msg( 'visualeditor-mwmapsdialog-geojson' )
	} );

	panel.$element.append(
		this.dimensionsField.$element,
		this.$mapContainer,
		this.$resetMapButtonContainer.append( this.resetMapButton.$element ),
		this.geoJsonField.$element
	);
	this.$body.append( panel.$element );
};

/**
 * Handle change events on the dimensions widget
 *
 * @param {string} newValue
 */
ve.ui.MWMapsDialog.prototype.onDimensionsChange = function () {
	var dimensions, center;

	if ( !this.map ) {
		return;
	}

	dimensions = this.dimensions.getDimensions();
	center = this.map && this.map.getCenter();

	// Set container width for centering
	this.$mapContainer.css( { width: dimensions.width } );
	this.$map.css( dimensions );
	this.updateSize();

	if ( center ) {
		this.map.setView( center );
	}
	this.map.invalidateSize();
	this.updateActions();
};

/**
 * Reset the map's position
 */
ve.ui.MWMapsDialog.prototype.resetMapPosition = function () {
	var position,
		dialog = this;

	if ( this.map ) {
		position = this.getInitialMapPosition();
		this.map.setView( [ position.latitude, position.longitude ], position.zoom );
		this.updateActions();
		this.resetMapButton.setDisabled( true );

		this.map.on( 'moveend', function () {
			dialog.updateActions();
			dialog.resetMapButton.setDisabled( false );
		} );
	}
};

/**
 * Update action states
 */
ve.ui.MWMapsDialog.prototype.updateActions = function () {
	var newMwData, modified,
		mwData = this.selectedNode && this.selectedNode.getAttribute( 'mw' );

	if ( mwData ) {
		newMwData = ve.copy( mwData );
		this.updateMwData( newMwData );
		modified = !ve.compare( mwData, newMwData );
	} else {
		modified = true;
	}
	this.actions.setAbilities( { done: modified } );
};

/**
 * @inheritdoc ve.ui.MWExtensionWindow
 */
ve.ui.MWMapsDialog.prototype.updateMwData = function ( mwData ) {
	var center, latitude, longitude, zoom,
		dimensions = this.scalable.getBoundedDimensions(
			this.dimensions.getDimensions()
		);

	// Parent method
	ve.ui.MWMapsDialog.super.prototype.updateMwData.call( this, mwData );

	if ( this.map ) {
		center = this.map.getCenter();
		latitude = center.lat;
		longitude = center.lng;
		zoom = this.map.getZoom();
	} else {
		// Map not loaded in insert, can't insert
		return;
	}
	mwData.attrs.latitude = latitude.toString();
	mwData.attrs.longitude = longitude.toString();
	mwData.attrs.zoom = zoom.toString();
	mwData.attrs.width = dimensions.width.toString();
	mwData.attrs.height = dimensions.height.toString();
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getReadyProcess = function ( data ) {
	return ve.ui.MWMapsDialog.super.prototype.getReadyProcess.call( this, data )
		.next( function () {
			this.setupMap();
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getSetupProcess = function ( data ) {
	data = data || {};
	return ve.ui.MWMapsDialog.super.prototype.getSetupProcess.call( this, data )
		.next( function () {
			this.input.clearUndoStack();

			this.actions.setMode( this.selectedNode ? 'edit' : 'insert' );

			if ( this.selectedNode instanceof ve.dm.MWMapsNode ) {
				this.scalable = this.selectedNode.getScalable();
			} else {
				this.scalable = ve.dm.MWMapsNode.static.createScalable( { width: 400, height: 300 } );
			}

			// Events
			this.input.connect( this, {
				change: 'updateGeoJson',
				resize: 'updateSize'
			} );
			this.dimensions.connect( this, {
				widthChange: 'onDimensionsChange',
				heightChange: 'onDimensionsChange'
			} );
			this.resetMapButton.connect( this, { click: 'resetMapPosition' } );

			// TODO: Support block/inline conversion

			this.$resetMapButtonContainer.toggle( !!this.selectedNode );

			this.dimensions.setDimensions( this.scalable.getCurrentDimensions() );

			this.updateActions();
		}, this );
};

/**
 * Setup the map control
 */
ve.ui.MWMapsDialog.prototype.setupMap = function () {
	var dialog = this;

	if ( this.mapPromise ) {
		return;
	}

	this.mapPromise = mw.loader.using( 'ext.kartographer.editor' ).then( function () {
		var geoJsonLayer, drawControl,
			defaultShapeOptions = { shapeOptions: L.mapbox.simplestyle.style( {} ) },
			mapPosition = dialog.getInitialMapPosition();

		// TODO: Support 'style' editing
		dialog.map = mw.kartographer.createMap( dialog.$map[ 0 ], mapPosition );

		dialog.updateGeoJson();
		dialog.onDimensionsChange();
		dialog.resetMapPosition();

		geoJsonLayer = mw.kartographer.getKartographerLayer( dialog.map );
		drawControl = new L.Control.Draw( {
			edit: { featureGroup: geoJsonLayer },
			draw: {
				circle: false,
				// TODO: Determine metric preference from locale information
				polyline: defaultShapeOptions,
				polygon: defaultShapeOptions,
				rectangle: defaultShapeOptions,
				marker: { icon: L.mapbox.marker.icon( {} ) }
			}
		} ).addTo( dialog.map );

		function update() {
			// Prevent circular update of map
			dialog.updatingGeoJson = true;
			try {
				dialog.input.setValue( JSON.stringify( geoJsonLayer.toGeoJSON(), null, '  ' ) );
			} finally {
				dialog.updatingGeoJson = false;
			}
			dialog.updateActions();
		}

		function created( e ) {
			e.layer.addTo( geoJsonLayer );
			update();
		}

		dialog.map
			.on( 'draw:edited', update )
			.on( 'draw:deleted', update )
			.on( 'draw:created', created );
	} );
};

/**
 * Get the initial map position (coordinates and zoom level)
 *
 * @return {Object} Object containing latitude, longitude and zoom
 */
ve.ui.MWMapsDialog.prototype.getInitialMapPosition = function () {
	var latitude, longitude, zoom,
		mwData = this.selectedNode && this.selectedNode.getAttribute( 'mw' ),
		mwAttrs = mwData && mwData.attrs;

	if ( mwAttrs && mwAttrs.zoom ) {
		latitude = +mwAttrs.latitude;
		longitude = +mwAttrs.longitude;
		zoom = +mwAttrs.zoom;
	} else {
		latitude = 30;
		longitude = 0;
		zoom = 2;
	}
	return {
		latitude: latitude,
		longitude: longitude,
		zoom: zoom
	};
};

/**
 * Update the GeoJSON layer from the current input state
 */
ve.ui.MWMapsDialog.prototype.updateGeoJson = function () {
	var self = this;

	if ( !this.map || this.updatingGeoJson ) {
		return;
	}

	mw.kartographer.updateKartographerLayer( this.map, this.input.getValue() )
		.done( function () {
			self.input.setValidityFlag( true );
		} )
		.fail( function () {
			self.input.setValidityFlag( false );
		} )
		.always( function () {
			self.updateActions();
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getTeardownProcess = function ( data ) {
	return ve.ui.MWMapsDialog.super.prototype.getTeardownProcess.call( this, data )
		.first( function () {
			// Events
			this.input.disconnect( this );
			this.dimensions.disconnect( this );
			this.resetMapButton.disconnect( this );

			this.dimensions.clear();
			if ( this.map ) {
				this.map.remove();
				this.map = null;
				this.mapPromise = null;
			}
		}, this );
};

/* Registration */

ve.ui.windowFactory.register( ve.ui.MWMapsDialog );
<commit_msg>Update scalable object when setting dimensions<commit_after>/*!
 * VisualEditor UserInterface MWMapsDialog class.
 *
 * @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * Dialog for editing MW maps.
 *
 * @class
 * @extends ve.ui.MWExtensionDialog
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
ve.ui.MWMapsDialog = function VeUiMWMapsDialog() {
	// Parent constructor
	ve.ui.MWMapsDialog.super.apply( this, arguments );

	this.updateGeoJson = $.debounce( 300, $.proxy( this.updateGeoJson, this ) );
};

/* Inheritance */

OO.inheritClass( ve.ui.MWMapsDialog, ve.ui.MWExtensionDialog );

/* Static Properties */

ve.ui.MWMapsDialog.static.name = 'mwMaps';

ve.ui.MWMapsDialog.static.title = OO.ui.deferMsg( 'visualeditor-mwmapsdialog-title' );

ve.ui.MWMapsDialog.static.size = 'larger';

ve.ui.MWMapsDialog.static.allowedEmpty = true;

ve.ui.MWMapsDialog.static.modelClasses = [ ve.dm.MWMapsNode, ve.dm.MWInlineMapsNode ];

/* Methods */

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.initialize = function () {
	var panel;

	// Parent method
	ve.ui.MWMapsDialog.super.prototype.initialize.call( this );

	this.$mapContainer = $( '<div>' ).addClass( 've-ui-mwMapsDialog-mapWidget' );
	this.$map = $( '<div>' ).appendTo( this.$mapContainer );
	this.map = null;
	this.mapPromise = null;
	this.scalable = null;
	this.updatingGeoJson = false;

	this.dimensions = new ve.ui.DimensionsWidget();

	this.input = new ve.ui.MWAceEditorWidget( {
		multiline: true,
		autosize: true,
		maxRows: 10,
		classes: [ 've-ui-mwMapsDialog-geoJSONWidget' ]
	} )
		.setLanguage( 'json' )
		.toggleLineNumbers( false )
		.setRTL( false );

	this.resetMapButton = new OO.ui.ButtonWidget( {
		label: ve.msg( 'visualeditor-mwmapsdialog-reset-map' )
	} );

	panel = new OO.ui.PanelLayout( {
		padded: true,
		expanded: false
	} );

	this.dimensionsField = new OO.ui.FieldLayout( this.dimensions, {
		align: 'right',
		label: ve.msg( 'visualeditor-mwmapsdialog-size' )
	} );

	this.$resetMapButtonContainer = $( '<div>' ).addClass( 've-ui-mwMapsDialog-resetMapButton' );

	this.geoJsonField = new OO.ui.FieldLayout( this.input, {
		align: 'top',
		label: ve.msg( 'visualeditor-mwmapsdialog-geojson' )
	} );

	panel.$element.append(
		this.dimensionsField.$element,
		this.$mapContainer,
		this.$resetMapButtonContainer.append( this.resetMapButton.$element ),
		this.geoJsonField.$element
	);
	this.$body.append( panel.$element );
};

/**
 * Handle change events on the dimensions widget
 *
 * @param {string} newValue
 */
ve.ui.MWMapsDialog.prototype.onDimensionsChange = function () {
	var dimensions, center;

	if ( !this.map ) {
		return;
	}

	dimensions = this.scalable.getBoundedDimensions(
		this.dimensions.getDimensions()
	);
	center = this.map && this.map.getCenter();

	// Set container width for centering
	this.$mapContainer.css( { width: dimensions.width } );
	this.$map.css( dimensions );
	this.updateSize();

	if ( center ) {
		this.map.setView( center );
	}
	this.map.invalidateSize();
	this.updateActions();
};

/**
 * Reset the map's position
 */
ve.ui.MWMapsDialog.prototype.resetMapPosition = function () {
	var position,
		dialog = this;

	if ( this.map ) {
		position = this.getInitialMapPosition();
		this.map.setView( [ position.latitude, position.longitude ], position.zoom );
		this.updateActions();
		this.resetMapButton.setDisabled( true );

		this.map.on( 'moveend', function () {
			dialog.updateActions();
			dialog.resetMapButton.setDisabled( false );
		} );
	}
};

/**
 * Update action states
 */
ve.ui.MWMapsDialog.prototype.updateActions = function () {
	var newMwData, modified,
		mwData = this.selectedNode && this.selectedNode.getAttribute( 'mw' );

	if ( mwData ) {
		newMwData = ve.copy( mwData );
		this.updateMwData( newMwData );
		modified = !ve.compare( mwData, newMwData );
	} else {
		modified = true;
	}
	this.actions.setAbilities( { done: modified } );
};

/**
 * @inheritdoc ve.ui.MWExtensionWindow
 */
ve.ui.MWMapsDialog.prototype.insertOrUpdateNode = function () {
	// Parent method
	ve.ui.MWMapsDialog.super.prototype.insertOrUpdateNode.apply( this, arguments );

	// Update scalable
	this.scalable.setCurrentDimensions(
		this.scalable.getBoundedDimensions(
			this.dimensions.getDimensions()
		)
	);
};

/**
 * @inheritdoc ve.ui.MWExtensionWindow
 */
ve.ui.MWMapsDialog.prototype.updateMwData = function ( mwData ) {
	var center, latitude, longitude, zoom,
		dimensions = this.scalable.getBoundedDimensions(
			this.dimensions.getDimensions()
		);

	// Parent method
	ve.ui.MWMapsDialog.super.prototype.updateMwData.call( this, mwData );

	if ( this.map ) {
		center = this.map.getCenter();
		latitude = center.lat;
		longitude = center.lng;
		zoom = this.map.getZoom();
	} else {
		// Map not loaded in insert, can't insert
		return;
	}
	mwData.attrs.latitude = latitude.toString();
	mwData.attrs.longitude = longitude.toString();
	mwData.attrs.zoom = zoom.toString();
	mwData.attrs.width = dimensions.width.toString();
	mwData.attrs.height = dimensions.height.toString();
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getReadyProcess = function ( data ) {
	return ve.ui.MWMapsDialog.super.prototype.getReadyProcess.call( this, data )
		.next( function () {
			this.setupMap();
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getSetupProcess = function ( data ) {
	data = data || {};
	return ve.ui.MWMapsDialog.super.prototype.getSetupProcess.call( this, data )
		.next( function () {
			this.input.clearUndoStack();

			this.actions.setMode( this.selectedNode ? 'edit' : 'insert' );

			if ( this.selectedNode instanceof ve.dm.MWMapsNode ) {
				this.scalable = this.selectedNode.getScalable();
			} else {
				this.scalable = ve.dm.MWMapsNode.static.createScalable( { width: 400, height: 300 } );
			}

			// Events
			this.input.connect( this, {
				change: 'updateGeoJson',
				resize: 'updateSize'
			} );
			this.dimensions.connect( this, {
				widthChange: 'onDimensionsChange',
				heightChange: 'onDimensionsChange'
			} );
			this.resetMapButton.connect( this, { click: 'resetMapPosition' } );

			// TODO: Support block/inline conversion

			this.$resetMapButtonContainer.toggle( !!this.selectedNode );

			this.dimensions.setDimensions( this.scalable.getCurrentDimensions() );

			this.updateActions();
		}, this );
};

/**
 * Setup the map control
 */
ve.ui.MWMapsDialog.prototype.setupMap = function () {
	var dialog = this;

	if ( this.mapPromise ) {
		return;
	}

	this.mapPromise = mw.loader.using( 'ext.kartographer.editor' ).then( function () {
		var geoJsonLayer, drawControl,
			defaultShapeOptions = { shapeOptions: L.mapbox.simplestyle.style( {} ) },
			mapPosition = dialog.getInitialMapPosition();

		// TODO: Support 'style' editing
		dialog.map = mw.kartographer.createMap( dialog.$map[ 0 ], mapPosition );

		dialog.updateGeoJson();
		dialog.onDimensionsChange();
		dialog.resetMapPosition();

		geoJsonLayer = mw.kartographer.getKartographerLayer( dialog.map );
		drawControl = new L.Control.Draw( {
			edit: { featureGroup: geoJsonLayer },
			draw: {
				circle: false,
				// TODO: Determine metric preference from locale information
				polyline: defaultShapeOptions,
				polygon: defaultShapeOptions,
				rectangle: defaultShapeOptions,
				marker: { icon: L.mapbox.marker.icon( {} ) }
			}
		} ).addTo( dialog.map );

		function update() {
			// Prevent circular update of map
			dialog.updatingGeoJson = true;
			try {
				dialog.input.setValue( JSON.stringify( geoJsonLayer.toGeoJSON(), null, '  ' ) );
			} finally {
				dialog.updatingGeoJson = false;
			}
			dialog.updateActions();
		}

		function created( e ) {
			e.layer.addTo( geoJsonLayer );
			update();
		}

		dialog.map
			.on( 'draw:edited', update )
			.on( 'draw:deleted', update )
			.on( 'draw:created', created );
	} );
};

/**
 * Get the initial map position (coordinates and zoom level)
 *
 * @return {Object} Object containing latitude, longitude and zoom
 */
ve.ui.MWMapsDialog.prototype.getInitialMapPosition = function () {
	var latitude, longitude, zoom,
		mwData = this.selectedNode && this.selectedNode.getAttribute( 'mw' ),
		mwAttrs = mwData && mwData.attrs;

	if ( mwAttrs && mwAttrs.zoom ) {
		latitude = +mwAttrs.latitude;
		longitude = +mwAttrs.longitude;
		zoom = +mwAttrs.zoom;
	} else {
		latitude = 30;
		longitude = 0;
		zoom = 2;
	}
	return {
		latitude: latitude,
		longitude: longitude,
		zoom: zoom
	};
};

/**
 * Update the GeoJSON layer from the current input state
 */
ve.ui.MWMapsDialog.prototype.updateGeoJson = function () {
	var self = this;

	if ( !this.map || this.updatingGeoJson ) {
		return;
	}

	mw.kartographer.updateKartographerLayer( this.map, this.input.getValue() )
		.done( function () {
			self.input.setValidityFlag( true );
		} )
		.fail( function () {
			self.input.setValidityFlag( false );
		} )
		.always( function () {
			self.updateActions();
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWMapsDialog.prototype.getTeardownProcess = function ( data ) {
	return ve.ui.MWMapsDialog.super.prototype.getTeardownProcess.call( this, data )
		.first( function () {
			// Events
			this.input.disconnect( this );
			this.dimensions.disconnect( this );
			this.resetMapButton.disconnect( this );

			this.dimensions.clear();
			if ( this.map ) {
				this.map.remove();
				this.map = null;
				this.mapPromise = null;
			}
		}, this );
};

/* Registration */

ve.ui.windowFactory.register( ve.ui.MWMapsDialog );
<|endoftext|>


<commit_before>// @flow

import type {
  NavigationScreenProp,
  NavigationLeafRoute,
  NavigationScene,
  NavigationTransitionProps,
} from 'react-navigation';
import {
  type Media,
  mediaPropType,
  type Dimensions,
  dimensionsPropType,
} from 'lib/types/media-types';
import type { AppState } from '../redux-setup';

import * as React from 'react';
import {
  View,
  StyleSheet,
  TouchableWithoutFeedback,
  Animated,
} from 'react-native';
import PropTypes from 'prop-types';

import { connect } from 'lib/utils/redux-utils';

import {
  dimensionsSelector,
  contentVerticalOffset,
} from '../selectors/dimension-selectors';
import Multimedia from './multimedia.react';
import ConnectedStatusBar from '../connected-status-bar.react';

type LayoutCoordinates = $ReadOnly<{|
  x: number,
  y: number,
  width: number,
  height: number,
|}>;
type NavProp = NavigationScreenProp<{|
  ...NavigationLeafRoute,
  params: {|
    media: Media,
    initialCoordinates: LayoutCoordinates,
  |},
|}>;

type Props = {|
  navigation: NavProp,
  scene: NavigationScene,
  transitionProps: NavigationTransitionProps,
  // Redux state
  screenDimensions: Dimensions,
|};
class MultimediaModal extends React.PureComponent<Props> {

  static propTypes = {
    navigation: PropTypes.shape({
      state: PropTypes.shape({
        params: PropTypes.shape({
          media: mediaPropType.isRequired,
          initialCoordinates: PropTypes.shape({
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired,
            width: PropTypes.number.isRequired,
            height: PropTypes.number.isRequired,
          }).isRequired,
        }).isRequired,
      }).isRequired,
      goBack: PropTypes.func.isRequired,
    }).isRequired,
    transitionProps: PropTypes.object.isRequired,
    scene: PropTypes.object.isRequired,
    screenDimensions: dimensionsPropType.isRequired,
  };

  get screenDimensions(): Dimensions {
    const { screenDimensions } = this.props;
    if (contentVerticalOffset === 0) {
      return screenDimensions;
    }
    const { height, width } = screenDimensions;
    return { height: height - contentVerticalOffset, width };
  }

  get imageDimensions(): Dimensions {
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const { dimensions } = this.props.navigation.state.params.media;
    if (dimensions.height < screenHeight && dimensions.width < screenWidth) {
      return dimensions;
    }
    const heightRatio = screenHeight / dimensions.height;
    const widthRatio = screenWidth / dimensions.width;
    if (heightRatio < widthRatio) {
      return {
        height: screenHeight,
        width: dimensions.width * heightRatio,
      };
    } else {
      return {
        width: screenWidth,
        height: dimensions.height * widthRatio,
      };
    }
  }

  get imageContainerStyle() {
    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + contentVerticalOffset;
    const left = (screenWidth - width) / 2;

    const { position } = this.props.transitionProps;
    const { index } = this.props.scene;
    const { initialCoordinates } = this.props.navigation.state.params;

    const initialScaleX = initialCoordinates.width / width;
    const scaleX = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialScaleX, 1 ]: number[]),
    });
    const initialScaleY = initialCoordinates.height / height;
    const scaleY = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialScaleY, 1 ]: number[]),
    });

    const initialTranslateX =
      (initialCoordinates.x + initialCoordinates.width / 2)
      - (left + width / 2);
    const translateX = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialTranslateX, 0 ]: number[]),
    });
    const initialTranslateY =
      (initialCoordinates.y + initialCoordinates.height / 2)
      - (top + height / 2);
    const translateY = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialTranslateY, 0 ]: number[]),
    });

    return {
      position: 'absolute',
      height,
      width,
      top,
      left,
      transform: [
        { translateX },
        { translateY },
        { scaleX },
        { scaleY },
      ],
    };
  }

  get backdropStyle() {
    const { position } = this.props.transitionProps;
    const { index } = this.props.scene;
    return {
      opacity: position.interpolate({
        inputRange: [ index - 1, index ],
        outputRange: ([ 0, 1 ]: number[]),
      }),
    };
  }

  render() {
    const { media } = this.props.navigation.state.params;
    return (
      <View style={styles.container}>
        <ConnectedStatusBar barStyle="light-content" />
        <TouchableWithoutFeedback onPress={this.close}>
          <Animated.View style={[ styles.backdrop, this.backdropStyle ]} />
        </TouchableWithoutFeedback>
        <Animated.View style={this.imageContainerStyle}>
          <Multimedia media={media} style={styles.image} />
        </Animated.View>
      </View>
    );
  }

  close = () => {
    this.props.navigation.goBack();
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backdrop: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: "black",
  },
  image: {
    flex: 1,
  },
});

export default connect(
  (state: AppState) => ({
    screenDimensions: dimensionsSelector(state),
  }),
)(MultimediaModal);
<commit_msg>[native] Drop MultimediaModal's Multimedia's opacity in last 10% of animation<commit_after>// @flow

import type {
  NavigationScreenProp,
  NavigationLeafRoute,
  NavigationScene,
  NavigationTransitionProps,
} from 'react-navigation';
import {
  type Media,
  mediaPropType,
  type Dimensions,
  dimensionsPropType,
} from 'lib/types/media-types';
import type { AppState } from '../redux-setup';

import * as React from 'react';
import {
  View,
  StyleSheet,
  TouchableWithoutFeedback,
  Animated,
} from 'react-native';
import PropTypes from 'prop-types';

import { connect } from 'lib/utils/redux-utils';

import {
  dimensionsSelector,
  contentVerticalOffset,
} from '../selectors/dimension-selectors';
import Multimedia from './multimedia.react';
import ConnectedStatusBar from '../connected-status-bar.react';

type LayoutCoordinates = $ReadOnly<{|
  x: number,
  y: number,
  width: number,
  height: number,
|}>;
type NavProp = NavigationScreenProp<{|
  ...NavigationLeafRoute,
  params: {|
    media: Media,
    initialCoordinates: LayoutCoordinates,
  |},
|}>;

type Props = {|
  navigation: NavProp,
  scene: NavigationScene,
  transitionProps: NavigationTransitionProps,
  // Redux state
  screenDimensions: Dimensions,
|};
class MultimediaModal extends React.PureComponent<Props> {

  static propTypes = {
    navigation: PropTypes.shape({
      state: PropTypes.shape({
        params: PropTypes.shape({
          media: mediaPropType.isRequired,
          initialCoordinates: PropTypes.shape({
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired,
            width: PropTypes.number.isRequired,
            height: PropTypes.number.isRequired,
          }).isRequired,
        }).isRequired,
      }).isRequired,
      goBack: PropTypes.func.isRequired,
    }).isRequired,
    transitionProps: PropTypes.object.isRequired,
    scene: PropTypes.object.isRequired,
    screenDimensions: dimensionsPropType.isRequired,
  };

  get screenDimensions(): Dimensions {
    const { screenDimensions } = this.props;
    if (contentVerticalOffset === 0) {
      return screenDimensions;
    }
    const { height, width } = screenDimensions;
    return { height: height - contentVerticalOffset, width };
  }

  get imageDimensions(): Dimensions {
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const { dimensions } = this.props.navigation.state.params.media;
    if (dimensions.height < screenHeight && dimensions.width < screenWidth) {
      return dimensions;
    }
    const heightRatio = screenHeight / dimensions.height;
    const widthRatio = screenWidth / dimensions.width;
    if (heightRatio < widthRatio) {
      return {
        height: screenHeight,
        width: dimensions.width * heightRatio,
      };
    } else {
      return {
        width: screenWidth,
        height: dimensions.height * widthRatio,
      };
    }
  }

  get imageContainerStyle() {
    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2 + contentVerticalOffset;
    const left = (screenWidth - width) / 2;

    const { position } = this.props.transitionProps;
    const { index } = this.props.scene;
    const opacity = position.interpolate({
      inputRange: [ index - 1, index - 0.9 ],
      outputRange: ([ 0, 1 ]: number[]),
    });

    const { initialCoordinates } = this.props.navigation.state.params;
    const initialScaleX = initialCoordinates.width / width;
    const scaleX = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialScaleX, 1 ]: number[]),
    });
    const initialScaleY = initialCoordinates.height / height;
    const scaleY = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialScaleY, 1 ]: number[]),
    });

    const initialTranslateX =
      (initialCoordinates.x + initialCoordinates.width / 2)
      - (left + width / 2);
    const translateX = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialTranslateX, 0 ]: number[]),
    });
    const initialTranslateY =
      (initialCoordinates.y + initialCoordinates.height / 2)
      - (top + height / 2);
    const translateY = position.interpolate({
      inputRange: [ index - 1, index ],
      outputRange: ([ initialTranslateY, 0 ]: number[]),
    });

    return {
      position: 'absolute',
      height,
      width,
      top,
      left,
      opacity,
      transform: [
        { translateX },
        { translateY },
        { scaleX },
        { scaleY },
      ],
    };
  }

  get backdropStyle() {
    const { position } = this.props.transitionProps;
    const { index } = this.props.scene;
    return {
      opacity: position.interpolate({
        inputRange: [ index - 1, index ],
        outputRange: ([ 0, 1 ]: number[]),
      }),
    };
  }

  render() {
    const { media } = this.props.navigation.state.params;
    return (
      <View style={styles.container}>
        <ConnectedStatusBar barStyle="light-content" />
        <TouchableWithoutFeedback onPress={this.close}>
          <Animated.View style={[ styles.backdrop, this.backdropStyle ]} />
        </TouchableWithoutFeedback>
        <Animated.View style={this.imageContainerStyle}>
          <Multimedia media={media} style={styles.image} />
        </Animated.View>
      </View>
    );
  }

  close = () => {
    this.props.navigation.goBack();
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backdrop: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: "black",
  },
  image: {
    flex: 1,
  },
});

export default connect(
  (state: AppState) => ({
    screenDimensions: dimensionsSelector(state),
  }),
)(MultimediaModal);
<|endoftext|>


<commit_before>//converting object data in http://mbraak.github.io/jqTree/ format
//----------------------------------------------------------------

//return formatted html string with result type name and color style
//server-eval smartpackage adds custom property:
//  ____TYPE____ for special objects (e.g. Error, Function, ...)
var _typeHtml = function(value) {
	var type = "unknown";
	var type_style = 'style="white-space: pre; color: ';
	if (value.____TYPE____) {
		type = value.____TYPE____;
		if (type === "[Circular]") {
			type += "[" + value.path + "]";
			type_style += 'red;"';
		} else if (type.indexOf("[Object]") >= 0) {
			type_style += 'green;"';
		} else if (type === "[Error]") {
			type += "[" + value.err + "]";
			type_style += 'red;"';
		} else if (type == "[Function]") {
			type_style += 'blue;"';
		} else {
			type_style += 'gray;"';
		}
	} else if (_.isArray(value)) {
		type = '[Array[' + value.length + ']]';
		type_style += 'orange;"';
	} else if (_.isObject(value)) {
		type = '[Object]';
		type_style += 'green;"';
	} else {
		type = 'null';
		type_style += 'red;"';
	}
	return '' + type + "";
};

//converts error objects in jqtree format
var _errorToTreeData = function(obj) {
	var tree_data = [];
	var belowEval = false;
	var stacktrace = _.map(obj.stack || [], function(value, key, list) {
		value = value.replace(/\s*at\s*/, '');
		var call = value.replace(/\s+/, "|").split("|");
		var location = (call.length === 2 ? call[1] : call[0]);
		var method = (call.length === 2 ? call[0] : 'anonymous');

		if (method === "eval") {
			belowEval = true;
			return undefined; //remove the eval call
		}

		var special = "";
		if (!belowEval) {
			//above the eval call itself is most likely the important stuff
			special = "important";
		} else if (location.indexOf("server-eval") >= 0 || method.indexOf("__serverEval") >= 0) {
			//internal server-eval overhead
			special = "internal";
		}

		var error_method = '' +
			method + '';
		var error_location = '' +
			location + '';
		return {
			'label': error_method + error_location
		};
	});
	stacktrace = _.compact(stacktrace);
	tree_data.push({
		'label': _typeHtml(obj),
		'children': stacktrace
	});
	return tree_data;
};

var _createSubtree = function(label, children) {
	return {
		'label': label,
		'children': children
	};
};

var formatByteSize = function(bytes) {
	bytes = parseInt(bytes, 10);
	var bytes_formatted;
	if (isNaN(bytes) || bytes === 0) {
		return '';
	} else if (bytes < 1024) {
		bytes_formatted = bytes + "byte";
	} else if (bytes < 1048576) {
		bytes_formatted = (bytes / 1024).toFixed(2) + "kb";
	} else {
		floatNum = bytes / 1048576;
		bytes_formatted = floatNum.toFixed(2) + "mb";
	}
	return '' + bytes_formatted + ' est.';
};

//converts all kind of result objects in jqtree format
//recursive function adding subtrees, subtree subtrees, ...
var objectToTreeData = function(obj_raw, top_level) {
	var obj = obj_raw._id && obj_raw.result ? obj_raw.result /* special object from server-eval */ : obj_raw;

	if (!_.isObject(obj)) return [];

	var tree_data = [];
	var isError = obj.____TYPE____ === "[Error]";
	var isCircular = obj.____TYPE____ === "[Circular]";

	if (isError) {
		if (obj.size_error) {
			obj.err = obj.err.replace(/IGNORE/, 'ignore');
		}
		return _errorToTreeData(obj);
	}

	for (var key in obj) {
		var value = obj[key];
		//dont show special result type and circular path property in tree
		if (key === "____TYPE____" || isCircular && key === "path") {
			continue;
		}

		var sub_tree;
		if (_.isObject(value)) {
			//sub_tree with children (objects)
			sub_tree = _createSubtree(key + ": " + _typeHtml(value), objectToTreeData(value, false));
		} else {
			//sub_tree without children (string, number, boolean)
			sub_tree = _createSubtree(key + ": " + wrapPrimitives(value));
		}

		//top level label is just the _typeHtml and properties are direct children
		if (top_level) {
			if (tree_data.length === 0) {
				tree_data.push(_createSubtree(_typeHtml(obj) + formatByteSize(obj_raw.size), []));
			}
			tree_data[0].children.push(sub_tree);
		} else {
			tree_data.push(sub_tree);
		}
	}

	if (top_level && tree_data.length === 0) {
		tree_data.push(_createSubtree(_typeHtml(obj)));
	}

	return tree_data;
};<commit_msg>show link to testrunner page<commit_after>//converting object data in http://mbraak.github.io/jqTree/ format
//----------------------------------------------------------------

//return formatted html string with result type name and color style
//server-eval smartpackage adds custom property:
//  ____TYPE____ for special objects (e.g. Error, Function, ...)
var _typeHtml = function(value) {
	var type = "unknown";
	var type_style = 'style="white-space: pre; color: ';
	if (value.____TYPE____) {
		type = value.____TYPE____;
		if (type === "[Circular]") {
			type += "[" + value.path + "]";
			type_style += 'red;"';
		} else if (type.indexOf("[Object]") >= 0) {
			type_style += 'green;"';
		} else if (type === "[Error]") {
			type += "[" + value.err + "]";
			type_style += 'red;"';
		} else if (type === "[Function]") {
			type_style += 'blue;"';
		} else if (type === '[Tinytest]') {
			type += '[open...]';
			type_style += 'brown;"';
		} else {
			type_style += 'gray;"';
		}
	} else if (_.isArray(value)) {
		type = '[Array[' + value.length + ']]';
		type_style += 'orange;"';
	} else if (_.isObject(value)) {
		type = '[Object]';
		type_style += 'green;"';
	} else {
		type = 'null';
		type_style += 'red;"';
	}
	return '' + type + "";
};

//converts error objects in jqtree format
var _errorToTreeData = function(obj) {
	var tree_data = [];
	var belowEval = false;
	var stacktrace = _.map(obj.stack || [], function(value, key, list) {
		value = value.replace(/\s*at\s*/, '');
		var call = value.replace(/\s+/, "|").split("|");
		var location = (call.length === 2 ? call[1] : call[0]);
		var method = (call.length === 2 ? call[0] : 'anonymous');

		if (method === "eval") {
			belowEval = true;
			return undefined; //remove the eval call
		}

		var special = "";
		if (!belowEval) {
			//above the eval call itself is most likely the important stuff
			special = "important";
		} else if (location.indexOf("server-eval") >= 0 || method.indexOf("__serverEval") >= 0) {
			//internal server-eval overhead
			special = "internal";
		}

		var error_method = '' +
			method + '';
		var error_location = '' +
			location + '';
		return {
			'label': error_method + error_location
		};
	});
	stacktrace = _.compact(stacktrace);
	tree_data.push({
		'label': _typeHtml(obj),
		'children': stacktrace
	});
	return tree_data;
};

var _createSubtree = function(label, children) {
	return {
		'label': label,
		'children': children
	};
};

var formatByteSize = function(bytes) {
	bytes = parseInt(bytes, 10);
	var bytes_formatted;
	if (isNaN(bytes) || bytes === 0) {
		return '';
	} else if (bytes < 1024) {
		bytes_formatted = bytes + "byte";
	} else if (bytes < 1048576) {
		bytes_formatted = (bytes / 1024).toFixed(2) + "kb";
	} else {
		floatNum = bytes / 1048576;
		bytes_formatted = floatNum.toFixed(2) + "mb";
	}
	return '' + bytes_formatted + ' est.';
};

//converts all kind of result objects in jqtree format
//recursive function adding subtrees, subtree subtrees, ...
var objectToTreeData = function(obj_raw, top_level) {
	var obj = obj_raw._id && obj_raw.result ? obj_raw.result /* special object from server-eval */ : obj_raw;

	if (!_.isObject(obj)) return [];

	var tree_data = [];
	var isError = obj.____TYPE____ === "[Error]";
	var isCircular = obj.____TYPE____ === "[Circular]";

	if (isError) {
		if (obj.size_error) {
			obj.err = obj.err.replace(/IGNORE/, 'ignore');
		}
		return _errorToTreeData(obj);
	}

	for (var key in obj) {
		var value = obj[key];
		//dont show special result type and circular path property in tree
		if (key === "____TYPE____" || isCircular && key === "path") {
			continue;
		}

		var sub_tree;
		if (_.isObject(value)) {
			//sub_tree with children (objects)
			sub_tree = _createSubtree(key + ": " + _typeHtml(value), objectToTreeData(value, false));
		} else {
			//sub_tree without children (string, number, boolean)
			sub_tree = _createSubtree(key + ": " + wrapPrimitives(value));
		}

		//top level label is just the _typeHtml and properties are direct children
		if (top_level) {
			if (tree_data.length === 0) {
				tree_data.push(_createSubtree(_typeHtml(obj) + formatByteSize(obj_raw.size), []));
			}
			tree_data[0].children.push(sub_tree);
		} else {
			tree_data.push(sub_tree);
		}
	}

	if (top_level && tree_data.length === 0) {
		tree_data.push(_createSubtree(_typeHtml(obj)));
	}

	return tree_data;
};<|endoftext|>


<commit_before>import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.audioLoader = new AudioLoader();

    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
  }

  addListener(object) {
    object.native.add(this.audioListener);
  };

  playAudio(path) {
    const sound = this.positionalAudio;

    this.audioLoader.load(path, buffer => {
      sound.setBuffer(buffer);
      sound.setRefDistance(50);
      sound.play();
    });
  };

  }
}
<commit_msg>Fix positioning<commit_after>import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.audioLoader = new AudioLoader();

    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
  }

  addListener(object) {
    object.native.add(this.audioListener);
  };

  playAudio(path) {
    const sound = this.positionalAudio;

    this.audioLoader.load(path, buffer => {
      sound.setBuffer(buffer);
      sound.setRefDistance(50);
      sound.play();
    });
  };

  bridge = {
    mesh(mesh, self) {
      mesh.add(self.positionalAudio);
      return mesh;
    }
  }
}
<|endoftext|>


<commit_before>import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
    this.audioLoader = new AudioLoader();
  }

  integrate(self) {
    this.addAudioListener = function (cameraModule) {
      this.native.add(self.positionalAudio);
      cameraModule.camera.native.add(self.audioListener);
    };

    this.playAudio = function (path) {
      const sound = self.positionalAudio;

      self.audioLoader.load(path, buffer => {
        sound.setBuffer(buffer);
        sound.setRefDistance(50);
        sound.play();
      });
    };
  }
}
<commit_msg>Move methods to module scope<commit_after>import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.audioLoader = new AudioLoader();

    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
  }

  addListener(object) {
    object.native.add(this.audioListener);
  };

  playAudio(path) {
    const sound = this.positionalAudio;

    this.audioLoader.load(path, buffer => {
      sound.setBuffer(buffer);
      sound.setRefDistance(50);
      sound.play();
    });
  };

  }
}
<|endoftext|>


<commit_before>console.log( "- josiah_request_item.js START" );

var request_item_flow_manager = new function() {
  /*
   * Class flow description:
   *   - Determines if there is a permalink
   *   - If so,
   *     - Grabs the bib
   *     - Builds and displays 'Request Item' link
   *   - Other code (will be put in a separate class):
   *     - Determines if its a Request Item page triggered by this js
   *     - If so, takes user directly to the login part of the process
   */

  var local_easyscan_link_element = null;

  this.check_permalink = function( easyscan_link_element ) {
      /* Controller to add `request-item` link to each available annex item.
       * Checks for permalink.
       * Called by josiah_easyscan.esyscn_flow_manager.update_row()
       */
      console.log( "- in request_item_flow_manager.check_permalink()" );
      local_easyscan_link_element = easyscan_link_element;  // used if adding item-link
      console.log( "- in request_item_flow_manager.check_permalink(); local_easyscan_link_element.context.nodeName, " + local_easyscan_link_element.context.nodeName );
      var all_html = $("body").html().toString();  // jquery already loaded (whew)
      var index = all_html.indexOf( "PermaLink to this record" );
      if (index != -1) {
        console.log( "- permalink found" );
        grab_bib_from_permalink();
      } else {
        console.log( "- permalink not found" );
        check_additionalCopiesNav_div( local_easyscan_link_element );
      }
  }

  var check_additionalCopiesNav_div = function( local_easyscan_link_element ) {
    /* Checks for a div where the bib might be.
     * Called by check_permalink() when it can't find a permalink.
     */
    var all_html = $("body").html().toString();  // jquery already loaded (whew)
    var index = all_html.indexOf( "class=\"additionalCopiesNav\"" );
    if (index != -1) {
      console.log( "- additionalCopiesNav_div found" );
      grab_bib_from_additionalCopiesNav_div();
    } else {
      console.log( "- additionalCopiesNav_div not found" );
      check_table_div( local_easyscan_link_element );
    }
  }

  var check_table_div = function( local_easyscan_link_element ) {
    /* Checks for a results-page enclosing table div.
     * Called by check_additionalCopiesNav_div() when it can't find an additionalCopiesNav div.
     */
    console.log( "- in request_item_flow_manager.check_table_div(); local_easyscan_link_element.context.nodeName, " + local_easyscan_link_element.context.nodeName );
    var tbody = local_easyscan_link_element.context.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
    if ( tbody.nodeName == "TBODY" ) {
      console.log( "- in request_item_flow_manager.check_table_div(); tbody found" );
      grab_bib_from_results_page( tbody );
    } else {
      console.log( "- in request_item_flow_manager.check_table_div(); tbody not found" );
    }
  }

  var grab_bib_from_results_page = function( tbody ) {
    /* Parses bib from results page table div.
     * Called by check_table_div()
     */
    console.log( "foo" );
     var row = tbody.children[0];
     var b_el = row.querySelector( "input" );
     var bib = b_el.value;
     console.log( "- in request_item_flow_manager.grab_bib_from_results_page(); bib is, " + bib );
     build_link_html( bib );
  }

  var grab_bib_from_permalink = function() {
    /* Parses bib from permalink.
     * Called by check_permalink()
     */
    el = document.getElementById( "recordnum" );
    href_string = el.getAttribute( "href" );
    b_string = href_string.split( "=" )[1];
    bib = b_string.slice( 0,8 );
    console.log( "- in request_item_flow_manager.grab_bib(); bib is, " + bib );
    build_link_html( bib );
  }

  var grab_bib_from_additionalCopiesNav_div = function() {
    /* Parses bib from additionalCopiesNav div.
     * Called by check_additionalCopiesNav_div()
     */
    var els = document.querySelectorAll( ".additionalCopiesNav" );
    var el = els[0];
    var link = el.children[0];
    var link_text = link.href;
    var segment = link_text.split( "/" )[4];  // .b1234567
    var bib = segment.slice( 1, 9 );
    console.log( "- in request_item_flow_manager.grab_bib_from_additionalCopiesNav_div(); bib is, " + bib );
    build_link_html( bib );
  }

  var build_link_html = function( bib ) {
    /* Builds link html and appends it.
     * Called by grab_bib()
     */
    console.log( "- in request_item_flow_manager.build_link_html(); bib, " + bib );
    var initial_link = ' | Item';
    var item_link = initial_link.replace( /THE_BIB/g, bib );  // http://www.w3schools.com/jsref/jsref_replace.asp
    console.log( "- in request_item_flow_manager.build_link_html(); item_link, " + item_link );
    // $( local_last_cell ).next().after( link );
    $( local_easyscan_link_element ).after( item_link );
  }

  /* other code called by josiah_easyscan.esyscn_flow_manager
   * TODO: put in separate class.
   */

  var check_url = function() {
    /* Checks url to see if we're requesting an annex item.
     * If so, speeds user to necessary login section.
     * Called by esyscn_flow_manager.delete_header_cell()
     */
     if ( location.toString().search("goal=request_annex_item") != -1 ) {
      console.log( "- in request_item_flow_manager.check_url(); `goal=request_annex_item` found" );
      // show( "annex" );  // in-page js function
      // toggleLayer( "requestForm" );  // in-page js function
     }
     console.log( "- in request_item_flow_manager.check_url(); done" );
     return
  }

}  // end request_item_flow_manager()

/* document-ready only for testing */
// $(document).ready(
//   function() {
//     console.log( "- josiah_request_item.js says document loaded" );
//     request_item_flow_manager.check_permalink();
//   }
// );

console.log( "- josiah_request_item.js END" );
<commit_msg>adds comment.<commit_after>console.log( "- josiah_request_item.js START" );

var request_item_flow_manager = new function() {
  /*
   * Class flow description:
   *   - Determines if there is a permalink
   *   - If so,
   *     - Grabs the bib
   *     - Builds and displays 'Request Item' link
   *   - Other code (will be put in a separate class):
   *     - Determines if its a Request Item page triggered by this js
   *     - If so, takes user directly to the login part of the process
   */

  var local_easyscan_link_element = null;

  this.check_permalink = function( easyscan_link_element ) {
      /* Controller to add `request-item` link to each available annex item.
       * Checks for permalink.
       * Called by josiah_easyscan.esyscn_flow_manager.update_row()
       */
      console.log( "- in request_item_flow_manager.check_permalink()" );
      local_easyscan_link_element = easyscan_link_element;  // used if adding item-link -- TODO: this 'renaming' seems unnecessary, but I have a recollection that the original var name didn't work. Test and simplify if possible.
      console.log( "- in request_item_flow_manager.check_permalink(); local_easyscan_link_element.context.nodeName, " + local_easyscan_link_element.context.nodeName );
      var all_html = $("body").html().toString();  // jquery already loaded (whew)
      var index = all_html.indexOf( "PermaLink to this record" );
      if (index != -1) {
        console.log( "- permalink found" );
        grab_bib_from_permalink();
      } else {
        console.log( "- permalink not found" );
        check_additionalCopiesNav_div( local_easyscan_link_element );
      }
  }

  var check_additionalCopiesNav_div = function( local_easyscan_link_element ) {
    /* Checks for a div where the bib might be.
     * Called by check_permalink() when it can't find a permalink.
     */
    var all_html = $("body").html().toString();  // jquery already loaded (whew)
    var index = all_html.indexOf( "class=\"additionalCopiesNav\"" );
    if (index != -1) {
      console.log( "- additionalCopiesNav_div found" );
      grab_bib_from_additionalCopiesNav_div();
    } else {
      console.log( "- additionalCopiesNav_div not found" );
      check_table_div( local_easyscan_link_element );
    }
  }

  var check_table_div = function( local_easyscan_link_element ) {
    /* Checks for a results-page enclosing table div.
     * Called by check_additionalCopiesNav_div() when it can't find an additionalCopiesNav div.
     */
    console.log( "- in request_item_flow_manager.check_table_div(); local_easyscan_link_element.context.nodeName, " + local_easyscan_link_element.context.nodeName );
    var tbody = local_easyscan_link_element.context.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
    if ( tbody.nodeName == "TBODY" ) {
      console.log( "- in request_item_flow_manager.check_table_div(); tbody found" );
      grab_bib_from_results_page( tbody );
    } else {
      console.log( "- in request_item_flow_manager.check_table_div(); tbody not found" );
    }
  }

  var grab_bib_from_results_page = function( tbody ) {
    /* Parses bib from results page table div.
     * Called by check_table_div()
     */
    console.log( "foo" );
     var row = tbody.children[0];
     var b_el = row.querySelector( "input" );
     var bib = b_el.value;
     console.log( "- in request_item_flow_manager.grab_bib_from_results_page(); bib is, " + bib );
     build_link_html( bib );
  }

  var grab_bib_from_permalink = function() {
    /* Parses bib from permalink.
     * Called by check_permalink()
     */
    el = document.getElementById( "recordnum" );
    href_string = el.getAttribute( "href" );
    b_string = href_string.split( "=" )[1];
    bib = b_string.slice( 0,8 );
    console.log( "- in request_item_flow_manager.grab_bib(); bib is, " + bib );
    build_link_html( bib );
  }

  var grab_bib_from_additionalCopiesNav_div = function() {
    /* Parses bib from additionalCopiesNav div.
     * Called by check_additionalCopiesNav_div()
     */
    var els = document.querySelectorAll( ".additionalCopiesNav" );
    var el = els[0];
    var link = el.children[0];
    var link_text = link.href;
    var segment = link_text.split( "/" )[4];  // .b1234567
    var bib = segment.slice( 1, 9 );
    console.log( "- in request_item_flow_manager.grab_bib_from_additionalCopiesNav_div(); bib is, " + bib );
    build_link_html( bib );
  }

  var build_link_html = function( bib ) {
    /* Builds link html and appends it.
     * Called by grab_bib()
     */
    console.log( "- in request_item_flow_manager.build_link_html(); bib, " + bib );
    var initial_link = ' | Item';
    var item_link = initial_link.replace( /THE_BIB/g, bib );  // http://www.w3schools.com/jsref/jsref_replace.asp
    console.log( "- in request_item_flow_manager.build_link_html(); item_link, " + item_link );
    // $( local_last_cell ).next().after( link );
    $( local_easyscan_link_element ).after( item_link );
  }

  /* other code called by josiah_easyscan.esyscn_flow_manager
   * TODO: put in separate class.
   */

  var check_url = function() {
    /* Checks url to see if we're requesting an annex item.
     * If so, speeds user to necessary login section.
     * Called by esyscn_flow_manager.delete_header_cell()
     */
     if ( location.toString().search("goal=request_annex_item") != -1 ) {
      console.log( "- in request_item_flow_manager.check_url(); `goal=request_annex_item` found" );
      // show( "annex" );  // in-page js function
      // toggleLayer( "requestForm" );  // in-page js function
     }
     console.log( "- in request_item_flow_manager.check_url(); done" );
     return
  }

}  // end request_item_flow_manager()

/* document-ready only for testing */
// $(document).ready(
//   function() {
//     console.log( "- josiah_request_item.js says document loaded" );
//     request_item_flow_manager.check_permalink();
//   }
// );

console.log( "- josiah_request_item.js END" );
<|endoftext|>


<commit_before>/**
 * ConstantBehaviour constructor.
 * Applies a constant force to the acceleration vector of a particle.
 * @constructor
 * @param {String} namespace ConstantBehaviour namespace.
 */
(function(Behaviour) {

})(NDP.Behaviour.create('ConstantBehaviour'));
<commit_msg>:gb: Started implementation of ConstantBehaviour<commit_after>/**
 * ConstantBehaviour constructor.
 * Applies a constant force to the acceleration of a particle.
 * @constructor
 * @param {String} namespace ConstantBehaviour namespace.
 */
(function(Behaviour) {

  // this.__force = this.__vector.create();

  // Object.defineProperty(Behaviour.prototype, 'force', {
  //   set: function(value) {
  //     if (NDP.isArray(value)) {
  //       this.__force = value;
  //     }
  //   },
  //   get: function() {
  //     return this.__force;
  //   }
  // });

  Behaviour.prototype.setForce = function(force) {
    NDP.copyValuesToArray(force, this.__force);
  };

  Behaviour.prototype.apply = function(particle, delta, index) {
    this.__vector.add(particle.__acc, particle.__acc, this.__force);
  };

})(NDP.Behaviour.create('ConstantBehaviour'));
<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global require */

(function () {
  "use strict";
  var Foxx = require("org/arangodb/foxx"),
    _ = require("underscore"),
    Repository = {},
    State = {},
    FGRepository;

  FGRepository = Foxx.Repository.extend({
    updateByIdWithOperations: function (id, operations) {
      var model = this.byId(id);
      _.each(operations, function (operation) {
        operation.execute(model);
      });
      return this.replace(model);
    }
  });

  Repository.generate = function (options) {
    var appContext = options.applicationContext,
      controller = new Foxx.Controller(appContext),
      model = options.contains,
      name = options.name,
      repository = new FGRepository(appContext.collection(name), { model: model }),
      per_page = options.per_page || 10,
      BodyParam,
      ReplaceOperation,
      attributes = model.attributes;

    BodyParam = Foxx.Model.extend({}, { attributes: attributes });
    ReplaceOperation = Foxx.Model.extend({
      isValid: function () {
        return (this.get('op') === 'replace');
      },

      execute: function (model) {
        model.set(this.getAttributeName(), this.get('value'));
      },

      // Fake implementation
      getAttributeName: function () {
        return this.get('path').split('/').pop();
      }
    }, {
      op: { type: 'string', required: true },
      path: { type: 'string', required: true },
      value: { type: 'string', required: true }
    });

    controller.get('/', function (req, res) {
      var data = {},
        page = req.params('page') || 0,
        skip = page * per_page;

      data[name] = _.map(repository.all({skip: skip, limit: per_page}), function (datum) {
        return datum.forClient();
      });
      res.json(data);
    }).queryParam('page', {
      description: 'Page of the results',
      type: 'int'
    }).summary('Get all entries')
      .notes('Some fancy documentation');

    controller.get('/:id', function (req, res) {
      var id = req.params('id'),
        entry = repository.byId(id),
        data = {};

      data[name] = [entry.forClient()];

      res.json(data);
    }).pathParam('id', {
      description: 'ID of the document',
      type: 'string'
    }).summary('Get a specific entry')
      .notes('Some fancy documentation');

    // This works a little different from the standard:
    // It expects a root element, the standard does not
    controller.patch('/:id', function (req, res) {
      var operations = req.params('operations'),
        id = req.params('id'),
        data = {};

      if (_.all(operations, function (x) { return x.isValid(); })) {
        data[name] = repository.updateByIdWithOperations(id, operations).forClient();
        res.json(data);
      } else {
        res.json({ error: 'Only replace is supported right now' });
        res.status(405);
      }
    }).pathParam('id', {
      description: 'ID of the document',
      type: 'string'
    }).bodyParam('operations', 'The operations to be executed on the document', [ReplaceOperation])
      .summary('Update an entry')
      .notes('Some fancy documentation');

    controller.post('/', function (req, res) {
      var data = {};
      data[name] = _.map(req.params(name), function (model) {
        return repository.save(model).forClient();
      });
      res.status(201);
      res.json(data);
    }).bodyParam(name, 'TODO', [BodyParam])
      .summary('Post new entries')
      .notes('Some fancy documentation');
  };

  State.generate = function (options) {
    var attributes = options.attributes;
    return Foxx.Model.extend({
      forClient: function () {
        return _.extend({ id: this.get('_key') }, this.whitelistedAttributes);
      }
    }, { attributes: attributes });
  };

  exports.Repository = Repository;
  exports.State = State;
}());
<commit_msg>Delete implemented<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100 */
/*global require */

(function () {
  "use strict";
  var Foxx = require("org/arangodb/foxx"),
    _ = require("underscore"),
    Repository = {},
    State = {},
    FGRepository,
    ArangoError = require('internal').ArangoError;

  FGRepository = Foxx.Repository.extend({
    updateByIdWithOperations: function (id, operations) {
      var model = this.byId(id);
      _.each(operations, function (operation) {
        operation.execute(model);
      });
      return this.replace(model);
    }
  });

  Repository.generate = function (options) {
    var appContext = options.applicationContext,
      controller = new Foxx.Controller(appContext),
      model = options.contains,
      name = options.name,
      repository = new FGRepository(appContext.collection(name), { model: model }),
      per_page = options.per_page || 10,
      BodyParam,
      ReplaceOperation,
      attributes = model.attributes;

    BodyParam = Foxx.Model.extend({}, { attributes: attributes });
    ReplaceOperation = Foxx.Model.extend({
      isValid: function () {
        return (this.get('op') === 'replace');
      },

      execute: function (model) {
        model.set(this.getAttributeName(), this.get('value'));
      },

      // Fake implementation
      getAttributeName: function () {
        return this.get('path').split('/').pop();
      }
    }, {
      op: { type: 'string', required: true },
      path: { type: 'string', required: true },
      value: { type: 'string', required: true }
    });

    controller.get('/', function (req, res) {
      var data = {},
        page = req.params('page') || 0,
        skip = page * per_page;

      data[name] = _.map(repository.all({skip: skip, limit: per_page}), function (datum) {
        return datum.forClient();
      });
      res.json(data);
    }).queryParam('page', {
      description: 'Page of the results',
      type: 'int'
    }).summary('Get all entries')
      .notes('Some fancy documentation');

    controller.get('/:id', function (req, res) {
      var id = req.params('id'),
        entry = repository.byId(id),
        data = {};

      data[name] = [entry.forClient()];

      res.json(data);
    }).pathParam('id', {
      description: 'ID of the document',
      type: 'string'
    }).summary('Get a specific entry')
      .notes('Some fancy documentation');

    // This works a little different from the standard:
    // It expects a root element, the standard does not
    controller.patch('/:id', function (req, res) {
      var operations = req.params('operations'),
        id = req.params('id'),
        data = {};

      if (_.all(operations, function (x) { return x.isValid(); })) {
        data[name] = repository.updateByIdWithOperations(id, operations).forClient();
        res.json(data);
      } else {
        res.json({ error: 'Only replace is supported right now' });
        res.status(405);
      }
    }).pathParam('id', {
      description: 'ID of the document',
      type: 'string'
    }).bodyParam('operations', 'The operations to be executed on the document', [ReplaceOperation])
      .summary('Update an entry')
      .notes('Some fancy documentation');

    controller.del('/:id', function (req, res) {
      var id = req.params('id');
      repository.removeById(id);
      res.status(204);
    }).pathParam('id', {
      description: 'ID of the document',
      type: 'string'
    }).errorResponse(ArangoError, 404, 'An entry with this ID could not be found');

    controller.post('/', function (req, res) {
      var data = {};
      data[name] = _.map(req.params(name), function (model) {
        return repository.save(model).forClient();
      });
      res.status(201);
      res.json(data);
    }).bodyParam(name, 'TODO', [BodyParam])
      .summary('Post new entries')
      .notes('Some fancy documentation');
  };

  State.generate = function (options) {
    var attributes = options.attributes;
    return Foxx.Model.extend({
      forClient: function () {
        return _.extend({ id: this.get('_key') }, this.whitelistedAttributes);
      }
    }, { attributes: attributes });
  };

  exports.Repository = Repository;
  exports.State = State;
}());
<|endoftext|>


<commit_before>var inflect = require('i')();
var should = require('should');
var _ = require('lodash');
var request = require('supertest');
var Promise = require('bluebird');

var seeder = require('./seeder.js');

describe("filters", function () {

    var config, ids;
    before(function () {
        config = this.config;
        this.timeout(config.esIndexWaitTime + 1000);
        return seeder(this.harvesterApp).dropCollectionsAndSeed(false, 'people', 'pets').then(function (result) {
            ids = result;
            return new Promise(function (resolve) {
                var payload = {};

                payload.people = [
                    {
                        links: {
                            pets: [ids.pets[0]]
                        }
                    }
                ];

                request(config.baseUrl).put('/people/' + ids.people[0]).send(payload).expect('Content-Type', /json/).expect(200).end(function (error,
                                                                                response) {
                    should.not.exist(error);
                    var body = JSON.parse(response.text);
                    (body.people[0].links.pets).should.containEql(ids.pets[0]);
                    resolve();
                });
            }).then(function () {
                    return Promise.delay(config.esIndexWaitTime);
                });
        });
    });

    it("should allow top-level resource filtering for search route", function (done) {
        request(config.baseUrl).get('/people/search?name=Dilbert').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow top-level resource filtering based on a numeric value", function (done) {
        request(config.baseUrl).get('/people/search?appearances=1934').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });
    it("should allow combining top-level resource filtering for search route based on string & numeric values", function (done) {
        request(config.baseUrl).get('/people/search?name=Dilbert&appearances=3457').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow resource sub-document filtering", function (done) {
        //add mapping & do nesting to enable this.
        request(config.baseUrl).get("/people/search?links.pets.name=Dogbert").end(function (err, response) {
            should.not.exist(err);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow resource sub-document filtering combined with subdocument range queries", function (done) {
        //add mapping & do nesting to enable this.
        request(config.baseUrl).get("/people/search?links.pets.name=Dogbert&links.pets.appearances=lt=1935").end(function (err, response) {
            should.not.exist(err);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it('should support lt query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=lt=1935').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Wally');
            done();
        });
    });
    it('should support le query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=le=1934').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Wally');
            done();
        });
    });
    it('should support gt query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=gt=1935').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });
    it('should support ge query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=ge=3457').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });
    it('should support multiple range queries on the same property', function (done) {
        request(config.baseUrl).get('/people/search?appearances=ge=3457&appearances=lt=3500').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multiple range queries on the same nested property', function (done) {
        request(config.baseUrl).get('/people/search?links.pets.appearances=lt=1904&links.pets.appearances=ge=1903').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multiple range queries on the same nested property', function (done) {
        request(config.baseUrl).get('/people/search?links.pets.appearances=lt=1903&links.pets.appearances=ge=1903').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(0);
            done();
        });
    });

    it('should support wildcard queries', function (done) {
        request(config.baseUrl).get('/people/search?name=D*').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multi-value queries', function (done) {
        //NOTE: these ids will fail a basic match query lookup, while most other id values will not;
        //they are specially selected and should be retained in any version of this test.
        request(config.baseUrl).get('/people/search?id=b76826d0-0ab6-11e5-a3f4-470467a3b6a8,b767ffc1-0ab6-11e5-a3f4-470467a3b6a8').expect(200).end(function (err,
                                                                                res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(2);
            done();
        });
    });

    it('should support multi-value queries on numeric fields', function (done) {
        request(config.baseUrl).get('/people/search?appearances=1934,3457').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(2);
            done();
        });
    });
});
<commit_msg>reset ES index between tests<commit_after>var inflect = require('i')();
var should = require('should');
var _ = require('lodash');
var request = require('supertest');
var Promise = require('bluebird');

var seeder = require('./seeder.js');

describe("filters", function () {

    var config, ids;
    beforeEach(function () {
        config = this.config;
        this.timeout(config.esIndexWaitTime + 1000);
        return seeder(this.harvesterApp).dropCollectionsAndSeed(false, 'people', 'pets').then(function (result) {
            ids = result;
            return new Promise(function (resolve) {
                var payload = {};

                payload.people = [
                    {
                        links: {
                            pets: [ids.pets[0]]
                        }
                    }
                ];

                request(config.baseUrl).put('/people/' + ids.people[0]).send(payload).expect('Content-Type', /json/).expect(200).end(function (error,
                                                                                response) {
                    should.not.exist(error);
                    var body = JSON.parse(response.text);
                    (body.people[0].links.pets).should.containEql(ids.pets[0]);
                    resolve();
                });
            }).then(function () {
                    return Promise.delay(config.esIndexWaitTime);
                });
        });
    });

    it("should allow top-level resource filtering for search route", function (done) {
        request(config.baseUrl).get('/people/search?name=Dilbert').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow top-level resource filtering based on a numeric value", function (done) {
        request(config.baseUrl).get('/people/search?appearances=1934').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });
    it("should allow combining top-level resource filtering for search route based on string & numeric values", function (done) {
        request(config.baseUrl).get('/people/search?name=Dilbert&appearances=3457').expect('Content-Type', /json/).expect(200).end(function (error, response) {
            should.not.exist(error);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow resource sub-document filtering", function (done) {
        //add mapping & do nesting to enable this.
        request(config.baseUrl).get("/people/search?links.pets.name=Dogbert").end(function (err, response) {
            should.not.exist(err);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it("should allow resource sub-document filtering combined with subdocument range queries", function (done) {
        //add mapping & do nesting to enable this.
        request(config.baseUrl).get("/people/search?links.pets.name=Dogbert&links.pets.appearances=lt=1935").end(function (err, response) {
            should.not.exist(err);
            var body = JSON.parse(response.text);
            body.people.length.should.equal(1);
            done();
        });
    });

    it('should support lt query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=lt=1935').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Wally');
            done();
        });
    });
    it('should support le query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=le=1934').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Wally');
            done();
        });
    });
    it('should support gt query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=gt=1935').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });
    it('should support ge query', function (done) {
        request(config.baseUrl).get('/people/search?appearances=ge=3457').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });
    it('should support multiple range queries on the same property', function (done) {
        request(config.baseUrl).get('/people/search?appearances=ge=3457&appearances=lt=3500').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multiple range queries on the same nested property', function (done) {
        request(config.baseUrl).get('/people/search?links.pets.appearances=lt=1904&links.pets.appearances=ge=1903').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multiple range queries on the same nested property', function (done) {
        request(config.baseUrl).get('/people/search?links.pets.appearances=lt=1903&links.pets.appearances=ge=1903').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(0);
            done();
        });
    });

    it('should support wildcard queries', function (done) {
        request(config.baseUrl).get('/people/search?name=D*').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(1);
            (body.people[0].name).should.equal('Dilbert');
            done();
        });
    });

    it('should support multi-value queries', function (done) {
        //NOTE: these ids will fail a basic match query lookup, while most other id values will not;
        //they are specially selected and should be retained in any version of this test.
        request(config.baseUrl)
            .get('/people/search?id=b76826d0-0ab6-11e5-a3f4-470467a3b6a8,b767ffc1-0ab6-11e5-a3f4-470467a3b6a8')
            .expect(200)
            .end(function (err,
                                                                                res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(2);
            done();
        });
    });

    it('should support multi-value queries on numeric fields', function (done) {
        request(config.baseUrl).get('/people/search?appearances=1934,3457').expect(200).end(function (err, res) {
            should.not.exist(err);
            var body = JSON.parse(res.text);
            (body.people.length).should.equal(2);
            done();
        });
    });
});
<|endoftext|>


<commit_before>!function(window){

    "use strict";

    window.Timestring = function(settings) {
        // default settings
        var defaults = {
            hoursPerDay: 24,
            daysPerWeek: 7,
            weeksPerMonth: 4,
            monthsPerYear: 12
        };

        // merge default settings with user settings
        var settings = settings || {};
        this.settings = {};
        for (var property in defaults) { this.settings[property] = defaults[property]; }
        for (var property in settings) { this.settings[property] = settings[property]; }

        // time units
        this.units = {
            s: ['s', 'sec', 'secs', 'second', 'seconds'],
            m: ['m', 'min', 'mins', 'minute', 'minutes'],
            h: ['h', 'hr', 'hrs', 'hour', 'hours'],
            d: ['d', 'day', 'days'],
            w: ['w', 'week', 'weeks'],
            mth: ['mth', 'mths','month', 'months'],
            y: ['y', 'yr', 'yrs', 'year', 'years']
        };

        // time unit seconds mappings
        this.unitValues = {
            s: 1,
            m: 60,
            h: 3600
        };

        // dynamic time unit seconds mappings
        // these are dynamic based on the settings
        this.unitValues.d = this.settings.hoursPerDay * this.unitValues.h;
        this.unitValues.w = this.settings.daysPerWeek * this.unitValues.d;
        this.unitValues.mth = this.settings.weeksPerMonth * this.unitValues.w;
        this.unitValues.y = this.settings.monthsPerYear * this.unitValues.w;
    };

    Timestring.prototype.parse = function(string, returnUnit) {
        // reference to this
        var self = this;

        // convert a value to a specific unit
        function convert(value, unit) {
            var baseValue = self.unitValues[unit];
            return value / baseValue;
        }

        // get a value in seconds based on a specific unit
        function getSeconds(value, unit) {
            var baseValue = self.unitValues[unit];
            return value * baseValue;
        }

        // seconds counter
        var totalSeconds = 0;

        // dynamically generate units regular expression
        var unitsRegExp = '';

        for(var unit in this.units) {
            unitsRegExp += ',' + this.units[unit].join();
        }

        unitsRegExp = '(' +
            unitsRegExp
                .replace(/,/g, '|')
                .replace('|', '')
            + ')';

        // split string into groups and get total seconds for each group
        var groups = string
                        .replace(/[^\w+-]+/g, '') // remove white space
                        .match(/[-+]?[0-9]*\.?[0-9]+\D/g);

        if (groups !== null) {
            for(var group in groups) {
                var g = groups[group],
                    value = g.match(/[-+]?[0-9]*\.?[0-9]+/g),
                    unit = g.match(new RegExp(unitsRegExp, 'g'));

                totalSeconds += getSeconds(value, unit);
            }
        }

        // return total, convert if needed
        return (returnUnit) ? convert(totalSeconds, returnUnit) : totalSeconds;
    }

    // add convenience method to string proto
    String.prototype.parseTime = function (unit, settings) {
        return (new Timestring(settings)).parse(this, unit);
    }

}(window);
<commit_msg>removed uneeded window useage<commit_after>!function(){

    "use strict";

    var Timestring = function(settings) {
        // default settings
        var defaults = {
            hoursPerDay: 24,
            daysPerWeek: 7,
            weeksPerMonth: 4,
            monthsPerYear: 12
        };

        // merge default settings with user settings
        var settings = settings || {};
        this.settings = {};
        for (var property in defaults) { this.settings[property] = defaults[property]; }
        for (var property in settings) { this.settings[property] = settings[property]; }

        // time units
        this.units = {
            s: ['s', 'sec', 'secs', 'second', 'seconds'],
            m: ['m', 'min', 'mins', 'minute', 'minutes'],
            h: ['h', 'hr', 'hrs', 'hour', 'hours'],
            d: ['d', 'day', 'days'],
            w: ['w', 'week', 'weeks'],
            mth: ['mth', 'mths','month', 'months'],
            y: ['y', 'yr', 'yrs', 'year', 'years']
        };

        // time unit seconds mappings
        this.unitValues = {
            s: 1,
            m: 60,
            h: 3600
        };

        // dynamic time unit seconds mappings
        // these are dynamic based on the settings
        this.unitValues.d = this.settings.hoursPerDay * this.unitValues.h;
        this.unitValues.w = this.settings.daysPerWeek * this.unitValues.d;
        this.unitValues.mth = this.settings.weeksPerMonth * this.unitValues.w;
        this.unitValues.y = this.settings.monthsPerYear * this.unitValues.w;
    };

    Timestring.prototype.parse = function(string, returnUnit) {
        // reference to this
        var self = this;

        // convert a value to a specific unit
        function convert(value, unit) {
            var baseValue = self.unitValues[unit];
            return value / baseValue;
        }

        // get a value in seconds based on a specific unit
        function getSeconds(value, unit) {
            var baseValue = self.unitValues[unit];
            return value * baseValue;
        }

        // seconds counter
        var totalSeconds = 0;

        // dynamically generate units regular expression
        var unitsRegExp = '';

        for(var unit in this.units) {
            unitsRegExp += ',' + this.units[unit].join();
        }

        unitsRegExp = '(' +
            unitsRegExp
                .replace(/,/g, '|')
                .replace('|', '')
            + ')';

        // split string into groups and get total seconds for each group
        var groups = string
                        .replace(/[^\w+-]+/g, '') // remove white space
                        .match(/[-+]?[0-9]*\.?[0-9]+\D/g);

        if (groups !== null) {
            for(var group in groups) {
                var g = groups[group],
                    value = g.match(/[-+]?[0-9]*\.?[0-9]+/g),
                    unit = g.match(new RegExp(unitsRegExp, 'g'));

                totalSeconds += getSeconds(value, unit);
            }
        }

        // return total, convert if needed
        return (returnUnit) ? convert(totalSeconds, returnUnit) : totalSeconds;
    }

    // add convenience method to string proto
    String.prototype.parseTime = function (unit, settings) {
        return (new Timestring(settings)).parse(this, unit);
    }

}();
<|endoftext|>