<commit_before>/**
 * Selectify - an unobtrusive select overlay that reproduces the
 * native <select> element experience Pretty Well(TM).
 *
 * by James Ducker <james.ducker+js@gmail.com>
 *
 * Works with jQuery >= 1.7
 *
 * "Add-and-forget" - manipulate your underlying <select> element
 * and changes are immediately propagated to Selectify's overlay.
 *
 * Project home: https://github.com/jamesinc/selectify
 * Issue tracker: https://github.com/jamesinc/selectify/issues
 */
( function ( $ ) {

	"use strict";

	// Got some default options up in here.
	var defaults = {
		duration: 50,
		autoWidth: true,
		btnText: "▼",
		classes: {
			container: "sl-container",
			placeholderContainer: "sl-placeholder-container",
			placeholder: "sl-placeholder",
			btn: "sl-button",
			options: "sl-options",
			selected: "sl-selected",
			open: "sl-open"
		}
	};

	/**
	 * Call against any select element or collection containing select elements
	 * Non-select elements will be ignored.
	 * @param  {Object} options Options to pass to the plugin.
	 *                          Valid options are:
	 *                          duration	(number, milliseconds) slide up/slide
	 *                                      down animation duration.
	 *                          classes.container
	 *                                      Class name of the container element
	 *                          classes.placeholder
	 *                                      Class name of the placeholder element
	 *                                      (holds the current value of the select)
	 *                          classes.options
	 *                                      Class name of the dropdown
	 *                                      container
 element
	 *                          classes.selected
	 *                                      Class name of the option that is currently
	 *                                      selected. Applied to  inside

	 *                                      inside options
.
	 *                          classes.open
	 *                                      Added to container element when the
	 *                                      options list is open.
	 *
	 * @return {jQuery}         The jQuery object.
	 */
	$.fn.selectify = function ( options ) {

		var settings,
			// Maintain a dictionary of values in the select
			// so they can be matched as the user types
			Dictionary = function ( select ) {

				var optionsHtml = "",
					searchString = "",
					searchTimer = null,
					dict = { },
					clearSearchString = function ( ) {

						searchString = "";

					},
					// JS doesn't notify us when 3rd-party code modifies the options
					// list, so we get the options HTML as a string and compare it
					// to the last one we stored. If they're different, we rebuild
					// the dictionary.
					rebuild = function ( ) {

						if ( select.html() !== optionsHtml ) {

							$.each( select.find( "option" ), function ( ) {

								var el = $( this );

								if ( el.text().length ) {

									dict[ el.text() ] = el.val();

								}

							});

							optionsHtml = select.html();

						}

					},
					// Restarts the countdown to clear the search string
					resetSearch = function ( ) {

						clearTimeout( searchTimer );

						searchTimer = setTimeout( clearSearchString, 1000 );

					};

				return {

					find: function ( text ) {

						var textLength,
							selectedOption = $( select.find("option").get(select.get(0).selectedIndex) ),
							result = false;

						rebuild();
						resetSearch();

						searchString += text;
						textLength = searchString.length;

						if ( !selectedOption.length || selectedOption.text().substr(0, textLength).toUpperCase() !== searchString ) {

							if ( textLength ) {

								$.each( dict, function ( key, value ) {

									if ( key.substr(0, textLength).toUpperCase() === searchString ) {

										result = value;
										return false;

									}

								});

							}

						}

						return result;

					},
					clear: function ( ) {

						clearTimeout( searchTimer );
						clearSearchString();

					}

				};

			};

		options = options || { };

		// Merge defaults and options to make settings
		settings = $.extend( true, { }, defaults, options );

		// Setup each <select> in the provided collection.
		this.each( function ( ) {

			var el = $( this ),
				dict = new Dictionary( el ),
				self = this,
				options,
				container, placeholderContainer,
				placeholder, btn, list, anchors;

			// If current element is not a <select> element, skip it.
			if ( !el.is("select") ) {

				return;

			}

			// Check to see if this element has already been selectified(TM)
			if ( el.data("sl") === true ) {

				// Remove old selectifications from the element
				el.next( "." + settings.classes.container ).remove();

			}

			// Get collection of <option> elements
			options = el.find( "option" );

			// Create container element for the new UI
			container = $( "
", {
				"class": settings.classes.container,
				css: {
					"position": "relative"
				}
			});

			placeholderContainer = $( "
", {
				"class": settings.classes.placeholderContainer,
				tabindex: 0
			});

			// Create placeholder element to display the select's
			// current value
			placeholder = $( "
", {
				"class": settings.classes.placeholder,
			});

			btn = $( "
", {
				"class": settings.classes.btn,
				text: settings.btnText,
				css: {
					position: "absolute",
					right: 0,
					top: 0,
					height: "100%"
				}
			});

			placeholderContainer.append( placeholder, btn );

			// Create the list element for options
			list = $( "
", {
				"class": settings.classes.options,
				css: {
					position: "absolute",
					left: 0
				}
			});

			// Go through each option and add it to the list
			options.each( function ( ) {

				var self = $( this ),
					a = $( "", {
						href: "#",
						text: self.text(),
						css: {
							"white-space": "nowrap"
						},
						click: function ( e ) {

							e.preventDefault();

							if ( el.val() !== self.val() ) {

								placeholder.text( self.text() );
								list.find( "a" ).removeClass( settings.classes.selected );
								a.addClass( settings.classes.selected );
								el
									.val( self.val() )
									.trigger( "change", [ { sl: true }] );

							}

							list.slideUp( settings.duration );
							container.removeClass( settings.classes.open );

						}

					}),
					li = $( "
" );

				li.append( a );
				list.append( li );

			});

			// Handle list open/close.
			placeholderContainer.on( "click", function ( e ) {

				e.preventDefault();

				list.slideToggle( settings.duration );
				container.toggleClass( settings.classes.open );

			});

			placeholderContainer.on( "keydown", function ( e ) {

				var key = e.which,
					keyChar = String.fromCharCode( key ),
					match,
					intercept = false,
					currentIndex = self.selectedIndex;

				// Down arrow, right arrow
				if ( key === 40 || key === 39 ) {

					intercept = true;

					self.selectedIndex = Math.min( options.length - 1, self.selectedIndex + 1 );

				}

				// Up arrow, left arrow
				if ( key === 38 || key === 37 ) {

					intercept = true;

					self.selectedIndex = Math.max( 0, self.selectedIndex - 1 );

				}

				// Stop processing if it's a command key (i.e. directional arrow)
				if ( intercept ) {

					e.preventDefault();

					if ( currentIndex !== self.selectedIndex ) {

						dict.clear();
						el.trigger( "change" );

					}

					return false;

				}

				// Enter key, space bar
				// Causes the options list to close (if it's open)
				if ( key === 13 || key === 32 ) {

					list.slideUp( settings.duration );

				}

				// Match other keystrokes - used for typing values into the select
				// and having it match them
				if ( /[a-zA-Z0-9-_ !@#$%^&*\(\)+\/\\?><;:'"|]/.test(keyChar) ) {

					match = dict.find( keyChar );

					if ( match && el.val() !== match ) {

						el
							.val( match )
							.trigger( "change" );

					}

				}

			});

			placeholderContainer.on( "selectstart", function ( e ) {

				return false;

			});

			// Close the list if the user clicks elsewhere on the page.
			$( document ).on( "mouseup", function ( e ) {

				if ( !container.has( e.target ).length ) {

					list.slideUp( settings.duration );
					container.removeClass( settings.classes.open );

				}

			});

			// Listen for change events on the original select element
			el.on( "change", function ( e, src ) {

				var index = this.selectedIndex;

				var dist_from_top = $(anchors.get(self.selectedIndex)).offset().top+(list.offset().top-$(anchors.get(0)).offset().top)-list.offset().top;
				if ((dist_from_top - list.scrollTop())>list.height() || (dist_from_top - list.scrollTop())<0) {
					list.clearQueue().animate({
						scrollTop: dist_from_top - list.height()/2
					}, 300);
				}

				// Don't do anything if the event was originally propagated by this plugin
				if ( src && src.sl ) {

					return;

				}

				anchors
					.removeClass( settings.classes.selected );

				placeholder.text( $(options.get(index)).text() );
				$( anchors.get(index) ).addClass( settings.classes.selected );

			});

			// Get anchors list
			anchors = list.find("a");

			// Set initial placeholder text
			placeholder.text( $(options.get(el[0].selectedIndex)).text() );

			// Set initial anchor styling
			$( anchors.get(el[0].selectedIndex) ).addClass( settings.classes.selected );

			if ( settings.autoWidth ) {

				placeholder.width( el.width() );

			}

			// Add our elements to the page
			container.append( placeholderContainer, list );
			el
				.hide()
				.after( container )
				.data( "sl", true );

			// Add a height offset to the list
			list
				.hide()
				.css( "top", container.outerHeight() );

		});

		// return the jQuery object
		return this;

	};

}( jQuery ));
<commit_msg>Auto scrolling to "selected" item in list<commit_after>/**
 * Selectify - an unobtrusive select overlay that reproduces the
 * native <select> element experience Pretty Well(TM).
 *
 * by James Ducker <james.ducker+js@gmail.com>
 *
 * Works with jQuery >= 1.7
 *
 * "Add-and-forget" - manipulate your underlying <select> element
 * and changes are immediately propagated to Selectify's overlay.
 *
 * Project home: https://github.com/jamesinc/selectify
 * Issue tracker: https://github.com/jamesinc/selectify/issues
 */
( function ( $ ) {

	"use strict";

	// Got some default options up in here.
	var defaults = {
		duration: 50,
		autoWidth: true,
		btnText: "▼",
		classes: {
			container: "sl-container",
			placeholderContainer: "sl-placeholder-container",
			placeholder: "sl-placeholder",
			btn: "sl-button",
			options: "sl-options",
			selected: "sl-selected",
			open: "sl-open"
		}
	};

	/**
	 * Call against any select element or collection containing select elements
	 * Non-select elements will be ignored.
	 * @param  {Object} options Options to pass to the plugin.
	 *                          Valid options are:
	 *                          duration	(number, milliseconds) slide up/slide
	 *                                      down animation duration.
	 *                          classes.container
	 *                                      Class name of the container element
	 *                          classes.placeholder
	 *                                      Class name of the placeholder element
	 *                                      (holds the current value of the select)
	 *                          classes.options
	 *                                      Class name of the dropdown
	 *                                      container
 element
	 *                          classes.selected
	 *                                      Class name of the option that is currently
	 *                                      selected. Applied to  inside

	 *                                      inside options
.
	 *                          classes.open
	 *                                      Added to container element when the
	 *                                      options list is open.
	 *
	 * @return {jQuery}         The jQuery object.
	 */
	$.fn.selectify = function ( options ) {

		var settings,
			// Maintain a dictionary of values in the select
			// so they can be matched as the user types
			Dictionary = function ( select ) {

				var optionsHtml = "",
					searchString = "",
					searchTimer = null,
					dict = { },
					clearSearchString = function ( ) {

						searchString = "";

					},
					// JS doesn't notify us when 3rd-party code modifies the options
					// list, so we get the options HTML as a string and compare it
					// to the last one we stored. If they're different, we rebuild
					// the dictionary.
					rebuild = function ( ) {

						if ( select.html() !== optionsHtml ) {

							$.each( select.find( "option" ), function ( ) {

								var el = $( this );

								if ( el.text().length ) {

									dict[ el.text() ] = el.val();

								}

							});

							optionsHtml = select.html();

						}

					},
					// Restarts the countdown to clear the search string
					resetSearch = function ( ) {

						clearTimeout( searchTimer );

						searchTimer = setTimeout( clearSearchString, 1000 );

					};

				return {

					find: function ( text ) {

						var textLength,
							selectedOption = $( select.find("option").get(select.get(0).selectedIndex) ),
							result = false;

						rebuild();
						resetSearch();

						searchString += text;
						textLength = searchString.length;

						if ( !selectedOption.length || selectedOption.text().substr(0, textLength).toUpperCase() !== searchString ) {

							if ( textLength ) {

								$.each( dict, function ( key, value ) {

									if ( key.substr(0, textLength).toUpperCase() === searchString ) {

										result = value;
										return false;

									}

								});

							}

						}

						return result;

					},
					clear: function ( ) {

						clearTimeout( searchTimer );
						clearSearchString();

					}

				};

			};

		options = options || { };

		// Merge defaults and options to make settings
		settings = $.extend( true, { }, defaults, options );

		// Setup each <select> in the provided collection.
		this.each( function ( ) {

			var el = $( this ),
				dict = new Dictionary( el ),
				self = this,
				options,
				container, placeholderContainer,
				placeholder, btn, list, anchors;

			// If current element is not a <select> element, skip it.
			if ( !el.is("select") ) {

				return;

			}

			// Check to see if this element has already been selectified(TM)
			if ( el.data("sl") === true ) {

				// Remove old selectifications from the element
				el.next( "." + settings.classes.container ).remove();

			}

			// Get collection of <option> elements
			options = el.find( "option" );

			// Create container element for the new UI
			container = $( "
", {
				"class": settings.classes.container,
				css: {
					"position": "relative"
				}
			});

			placeholderContainer = $( "
", {
				"class": settings.classes.placeholderContainer,
				tabindex: 0
			});

			// Create placeholder element to display the select's
			// current value
			placeholder = $( "
", {
				"class": settings.classes.placeholder,
			});

			btn = $( "
", {
				"class": settings.classes.btn,
				text: settings.btnText,
				css: {
					position: "absolute",
					right: 0,
					top: 0,
					height: "100%"
				}
			});

			placeholderContainer.append( placeholder, btn );

			// Create the list element for options
			list = $( "
", {
				"class": settings.classes.options,
				css: {
					position: "absolute",
					left: 0
				}
			});

			// Go through each option and add it to the list
			options.each( function ( ) {

				var self = $( this ),
					a = $( "", {
						href: "#",
						text: self.text(),
						css: {
							"white-space": "nowrap"
						},
						click: function ( e ) {

							e.preventDefault();

							if ( el.val() !== self.val() ) {

								placeholder.text( self.text() );
								list.find( "a" ).removeClass( settings.classes.selected );
								a.addClass( settings.classes.selected );
								el
									.val( self.val() )
									.trigger( "change", [ { sl: true }] );

							}

							list.slideUp( settings.duration );
							container.removeClass( settings.classes.open );

						}

					}),
					li = $( "
" );

				li.append( a );
				list.append( li );

			});

			// Handle list open/close.
			placeholderContainer.on( "click", function ( e ) {

				e.preventDefault();

				list.slideToggle( settings.duration );
				container.toggleClass( settings.classes.open );
				el.trigger( "change" );

			});

			placeholderContainer.on( "keydown", function ( e ) {

				var key = e.which,
					keyChar = String.fromCharCode( key ),
					match,
					intercept = false,
					currentIndex = self.selectedIndex;

				// Down arrow, right arrow
				if ( key === 40 || key === 39 ) {

					intercept = true;

					self.selectedIndex = Math.min( options.length - 1, self.selectedIndex + 1 );

				}

				// Up arrow, left arrow
				if ( key === 38 || key === 37 ) {

					intercept = true;

					self.selectedIndex = Math.max( 0, self.selectedIndex - 1 );

				}

				// Stop processing if it's a command key (i.e. directional arrow)
				if ( intercept ) {

					e.preventDefault();

					if ( currentIndex !== self.selectedIndex ) {

						dict.clear();
						el.trigger( "change" );

					}

					return false;

				}

				// Enter key, space bar
				// Causes the options list to close (if it's open)
				if ( key === 13 || key === 32 ) {

					list.slideUp( settings.duration );

				}

				// Match other keystrokes - used for typing values into the select
				// and having it match them
				if ( /[a-zA-Z0-9-_ !@#$%^&*\(\)+\/\\?><;:'"|]/.test(keyChar) ) {

					match = dict.find( keyChar );

					if ( match && el.val() !== match ) {

						el
							.val( match )
							.trigger( "change" );

					}

				}

			});

			placeholderContainer.on( "selectstart", function ( e ) {

				return false;

			});

			// Close the list if the user clicks elsewhere on the page.
			$( document ).on( "mouseup", function ( e ) {

				if ( !container.has( e.target ).length ) {

					list.slideUp( settings.duration );
					container.removeClass( settings.classes.open );

				}

			});

			// Listen for change events on the original select element
			el.on( "change", function ( e, src ) {

				var index = this.selectedIndex;

				var dist_from_top = $(anchors.get(self.selectedIndex)).offset().top+(list.offset().top-$(anchors.get(0)).offset().top)-list.offset().top;
				if ((dist_from_top - list.scrollTop())>list.height() || (dist_from_top - list.scrollTop())<0) {
					list.clearQueue().animate({
						scrollTop: dist_from_top - list.height()/2
					}, 300);
				}

				// Don't do anything if the event was originally propagated by this plugin
				if ( src && src.sl ) {

					return;

				}

				anchors
					.removeClass( settings.classes.selected );

				placeholder.text( $(options.get(index)).text() );
				$( anchors.get(index) ).addClass( settings.classes.selected );

			});

			// Get anchors list
			anchors = list.find("a");

			// Set initial placeholder text
			placeholder.text( $(options.get(el[0].selectedIndex)).text() );

			// Set initial anchor styling
			$( anchors.get(el[0].selectedIndex) ).addClass( settings.classes.selected );

			if ( settings.autoWidth ) {

				placeholder.width( el.width() );

			}

			// Add our elements to the page
			container.append( placeholderContainer, list );
			el
				.hide()
				.after( container )
				.data( "sl", true );

			// Add a height offset to the list
			list
				.hide()
				.css( "top", container.outerHeight() );

		});

		// return the jQuery object
		return this;

	};

}( jQuery ));
<|endoftext|>


<commit_before>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule MenuView
 */
'use strict';

import React, { PropTypes } from 'react';
import {
  Animated,
  Alert,
  Clipboard,
  Dimensions,
  Easing,
  Image,
  NativeModules,
  PixelRatio,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TouchableHighlight,
  TouchableOpacity,
  View,
} from 'react-native';

let { ExponentKernel } = NativeModules;

import Expo, { Constants } from 'expo';
import { Ionicons } from '@expo/vector-icons';
import ResponsiveImage from '@expo/react-native-responsive-image';

import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import DevIndicator from '../Home/components/DevIndicator';
import ExStore from 'ExStore';
import FriendlyUrls from 'FriendlyUrls';

let SCREEN_WIDTH = Dimensions.get('window').width;
let MENU_NARROW_SCREEN = SCREEN_WIDTH < 375;

const AnimatedBlurView = Animated.createAnimatedComponent(Expo.BlurView);

export default class MenuView extends React.Component {
  static propTypes = {
    task: PropTypes.object.isRequired,
    shouldFadeIn: PropTypes.bool,
    isNuxFinished: PropTypes.bool,
  };

  constructor(props, context) {
    super(props, context);

    this.state = {
      transitionIn: new Animated.Value(props.shouldFadeIn ? 0 : 1),
      enableDevMenuTools: false,
      devMenuItems: {},
    };
  }

  async componentDidMount() {
    this._mounted = true;
    if (this.props.shouldFadeIn) {
      requestAnimationFrame(() => {
        this._mounted &&
          Animated.timing(this.state.transitionIn, {
            easing: Easing.inOut(Easing.quad),
            toValue: 1,
            duration: 100,
          }).start();
      });
    }
    this.forceStatusBarUpdateAsync();
    let enableDevMenuTools = await ExponentKernel.doesCurrentTaskEnableDevtools();
    let devMenuItems = await ExponentKernel.getDevMenuItemsToShow();
    if (this._mounted) {
      this.setState({ enableDevMenuTools, devMenuItems });
    }
  }

  componentWillUnmount() {
    this.restoreStatusBar();
    this._mounted = false;
  }

  forceStatusBarUpdateAsync = async () => {
    if (NativeModules.StatusBarManager._captureProperties) {
      this._statusBarValuesToRestore = await NativeModules.StatusBarManager._captureProperties();
      // HACK: StatusBar only updates changed props.
      // because MenuView typically lives under a different RN bridge, its stack of StatusBar
      // props does not necessarily reflect what the user is seeing.
      // so we force StatusBar to clear its state and update all props when we mount.
      StatusBar._currentValues = null;
    }
  };
  restoreStatusBar = () => {
    if (
      NativeModules.StatusBarManager._applyPropertiesAndForget &&
      this._statusBarValuesToRestore
    ) {
      NativeModules.StatusBarManager._applyPropertiesAndForget(
        this._statusBarValuesToRestore
      );
    }
  };
  render() {
    let scale = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [1.05, 1],
    });
    let intensity = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [0, 85],
    });
    let copyUrlButton;
    if (this.props.task && this.props.task.manifestUrl) {
      copyUrlButton = this._renderButton(
        'copy',
        'Copy Link',
        this._copyTaskUrl,
        require('../Assets/ios-menu-copy.png')
      );
    }

    return (
      <AnimatedBlurView
        style={styles.container}
        tint="light"
        intensity={intensity}>
        <StatusBar barStyle="default" />
        <Animated.View
          style={[
            styles.overlay,
            { opacity: this.state.transitionIn, transform: [{ scale }] },
          ]}>
          <ScrollView>
            {this.props.isNuxFinished
              ? this._renderTaskInfoRow()
              : this._renderNUXRow()}
            <View style={styles.separator} />
            <View style={styles.buttonContainer}>
              {this._renderButton(
                'refresh',
                'Refresh',
                Browser.refresh,
                require('../Assets/ios-menu-refresh.png')
              )}
              {copyUrlButton}
              {this._renderButton(
                'home',
                'Go to Expo Home',
                this._goToHome,
                require('../Assets/ios-menu-home.png')
              )}
            </View>
            {this._maybeRenderDevMenuTools()}
            <TouchableHighlight
              style={styles.closeButton}
              onPress={this._onPressClose}
              underlayColor="#eee"
              hitSlop={{ top: 4, bottom: 8, left: 8, right: 4 }}>
              <Ionicons
                name="md-close"
                size={20}
                style={styles.closeButtonIcon}
              />
            </TouchableHighlight>
          </ScrollView>
        </Animated.View>
      </AnimatedBlurView>
    );
  }

  _renderNUXRow() {
    let tooltipMessage;
    if (Expo.Constants.isDevice) {
      tooltipMessage = 'Shake your device to show this menu.';
    } else {
      tooltipMessage = 'In iPhone Simulator, press \u2318D to show this menu.';
    }
    let headingStyles = MENU_NARROW_SCREEN
      ? [styles.nuxHeading, styles.nuxHeadingNarrow]
      : styles.nuxHeading;
    return (
      <View style={styles.nuxRow}>
        <View style={styles.nuxHeadingRow}>
          <ResponsiveImage
            sources={{
              2: {
                uri:
                  'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
              },
              3: {
                uri:
                  'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
              },
            }}
            style={styles.nuxLogo}
          />
          <Text style={headingStyles}>
            Welcome to Expo!
          </Text>
        </View>
        <Text style={styles.nuxTooltip}>
          {tooltipMessage}
        </Text>
        <TouchableOpacity
          style={styles.nuxButton}
          onPress={this._onPressFinishNux}>
          <Text style={styles.nuxButtonLabel}>
            Got it
          </Text>
        </TouchableOpacity>
      </View>
    );
  }

  _renderTaskInfoRow() {
    let { task } = this.props;
    let taskUrl;
    if (task.loadingError) {
      taskUrl = task.loadingError.originalUrl;
    } else {
      taskUrl = task.manifestUrl
        ? FriendlyUrls.toFriendlyString(task.manifestUrl)
        : '';
    }

    let iconUrl = task.manifest && task.manifest.get('iconUrl');
    let taskName = task.manifest && task.manifest.get('name');

    let icon = iconUrl
      ? <Image source={{ uri: iconUrl }} style={styles.taskIcon} />
      : <ResponsiveImage
          resizeMode="contain"
          sources={{
            2: {
              uri:
                'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
            },
            3: {
              uri:
                'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
            },
          }}
          style={styles.taskIcon}
        />;
    let taskNameStyles = taskName
      ? styles.taskName
      : [styles.taskName, { color: '#c5c6c7' }];
    return (
      <View style={styles.taskMetaRow}>
        <View style={styles.taskIconColumn}>
          {icon}
        </View>
        <View style={styles.taskInfoColumn}>
          <Text style={taskNameStyles} numberOfLines={1}>
            {taskName ? taskName : 'Untitled Experience'}
          </Text>
          <Text style={[styles.taskUrl]} numberOfLines={1}>
            {taskUrl}
          </Text>
          {this._maybeRenderDevServerName()}
        </View>
      </View>
    );
  }

  _maybeRenderDevServerName() {
    let { task } = this.props;
    let devServerName =
      task.manifest && task.manifest.getIn(['developer', 'tool']);
    if (devServerName) {
      // XDE is upper
      if (devServerName === 'xde') {
        devServerName = devServerName.toUpperCase();
      }
      return (
        <View style={{ flexDirection: 'row' }}>
          <DevIndicator style={{ marginTop: 4.5, marginRight: 7 }} />
          <Text style={styles.taskDevServerName}>{devServerName}</Text>
        </View>
      );
    }
    return null;
  }

  _maybeRenderDevMenuTools() {
    if (this.state.enableDevMenuTools && this.state.devMenuItems) {
      return (
        <View>
          <View style={styles.separator} />
          <View style={styles.buttonContainer}>
            {Object.keys(this.state.devMenuItems).map(key => {
              return this._renderDevMenuItem(key, this.state.devMenuItems[key]);
            })}
          </View>
        </View>
      );
    }
    return null;
  }

  _renderDevMenuItem(key, item) {
    let { label, isEnabled, detail } = item;
    if (isEnabled) {
      return this._renderButton(
        key,
        label,
        () => {
          this._onPressDevMenuButton(key);
        },
        null,
        true
      );
    } else {
      const detailButton = detail
        ? this._renderDevMenuDetailButton(label, detail)
        : nil;
      return (
        <View style={[styles.button, styles.buttonWithSeparator]} key={key}>
          <View style={styles.buttonIcon} />
          <Text style={[styles.buttonText, { color: '#9ca0a6' }]}>
            {label}
          </Text>
          {detailButton}
        </View>
      );
    }
  }

  _renderDevMenuDetailButton(title, detail) {
    return (
      <TouchableOpacity
        onPress={() => {
          Alert.alert(title, detail);
        }}>
        <Ionicons
          name="ios-information-circle"
          size={20}
          style={{ color: '#9ca0a6', marginVertical: 10 }}
        />
      </TouchableOpacity>
    );
  }

  _renderButton(key, text, onPress, iconSource, withSeparator) {
    let icon, buttonStyles;
    if (iconSource) {
      icon = <Image style={styles.buttonIcon} source={iconSource} />;
    } else {
      icon = <View style={styles.buttonIcon} />;
    }
    if (withSeparator) {
      buttonStyles = [styles.button, styles.buttonWithSeparator];
    } else {
      buttonStyles = styles.button;
    }
    return (
      <TouchableOpacity key={key} style={buttonStyles} onPress={onPress}>
        {icon}
        <Text style={styles.buttonText}>
          {text}
        </Text>
      </TouchableOpacity>
    );
  }

  _onPressFinishNux = () => {
    ExStore.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
  _onPressClose = () => {
    if (this.props.isNuxFinished) {
      ExStore.dispatch(BrowserActions.showMenuAsync(false));
    }
  };
  _goToHome = () => {
    ExStore.dispatch(BrowserActions.foregroundHomeAsync());
  };
  _copyTaskUrl = () => {
    Clipboard.setString(this.props.task.manifestUrl);
  };
  _onPressDevMenuButton = key => {
    ExponentKernel.selectDevMenuItemWithKey(key);
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
}

let styles = StyleSheet.create({
  container: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
    justifyContent: 'center',
    flexDirection: 'row',
  },
  overlay: {
    flex: 1,
    backgroundColor: 'transparent',
    marginTop: Constants.statusBarHeight,
  },
  closeButtonIcon: {
    color: '#49a7e8',
  },
  closeButton: {
    position: 'absolute',
    right: 10,
    top: 10,
    paddingVertical: 2,
    paddingHorizontal: 6,
    borderRadius: 2,
  },
  taskMetaRow: {
    flexDirection: 'row',
    paddingHorizontal: 14,
    paddingBottom: 12,
  },
  taskInfoColumn: {
    flex: 4,
    justifyContent: 'center',
  },
  taskIconColumn: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  taskName: {
    color: '#595c68',
    backgroundColor: 'transparent',
    fontWeight: '700',
    fontSize: 16,
    marginTop: 14,
    marginBottom: 1,
    marginRight: 24,
  },
  taskUrl: {
    color: '#9ca0a6',
    backgroundColor: 'transparent',
    marginRight: 16,
    marginBottom: 2,
    marginTop: 1,
    fontSize: 12,
  },
  taskIcon: {
    width: 52,
    height: 52,
    marginTop: 12,
    marginRight: 10,
    alignSelf: 'center',
    backgroundColor: 'transparent',
  },
  taskDevServerName: {
    fontSize: 12,
    color: '#9ca0a6',
    fontWeight: '700',
  },
  separator: {
    borderColor: '#d5d6d7',
    borderTopWidth: 1 / PixelRatio.get(),
    backgroundColor: '#f0f0f1',
    height: 12,
    marginVertical: 4,
    marginHorizontal: -1,
  },
  buttonContainer: {
    backgroundColor: 'transparent',
  },
  button: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
  },
  buttonWithSeparator: {
    borderBottomWidth: StyleSheet.hairlineWidth * 2,
    borderBottomColor: '#f4f4f5',
  },
  buttonIcon: {
    width: 16,
    height: 16,
    marginVertical: 12,
    marginLeft: 20,
    alignSelf: 'flex-start',
  },
  buttonText: {
    color: '#595c68',
    fontSize: 14,
    textAlign: 'left',
    marginVertical: 12,
    marginRight: 5,
    paddingHorizontal: 12,
    fontWeight: '700',
  },
  nuxRow: {
    paddingHorizontal: 12,
  },
  nuxHeadingRow: {
    flexDirection: 'row',
    marginTop: 16,
    marginRight: 16,
    marginBottom: 8,
  },
  nuxLogo: {
    width: 47 * 0.7,
    height: 40 * 0.7,
    marginRight: 12,
    marginLeft: 8,
    alignSelf: 'flex-start',
  },
  nuxHeading: {
    flex: 1,
    color: '#595c68',
    fontWeight: '700',
    fontSize: 22,
  },
  nuxHeadingNarrow: {
    fontSize: 18,
    marginTop: 2,
  },
  nuxTooltip: {
    color: '#595c68',
    marginRight: 16,
    marginVertical: 4,
    fontSize: 16,
  },
  nuxButton: {
    alignItems: 'center',
    marginVertical: 12,
    paddingVertical: 10,
    backgroundColor: '#056ecf',
    borderRadius: 3,
  },
  nuxButtonLabel: {
    color: '#ffffff',
    fontSize: 16,
  },
});
<commit_msg>Use native driver in MenuView (#359)<commit_after>/**
 * Copyright 2015-present 650 Industries. All rights reserved.
 *
 * @providesModule MenuView
 */
'use strict';

import React, { PropTypes } from 'react';
import {
  Animated,
  Alert,
  Clipboard,
  Dimensions,
  Easing,
  Image,
  NativeModules,
  PixelRatio,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TouchableHighlight,
  TouchableOpacity,
  View,
} from 'react-native';

let { ExponentKernel } = NativeModules;

import Expo, { Constants } from 'expo';
import { Ionicons } from '@expo/vector-icons';
import ResponsiveImage from '@expo/react-native-responsive-image';

import Browser from 'Browser';
import BrowserActions from 'BrowserActions';
import DevIndicator from '../Home/components/DevIndicator';
import ExStore from 'ExStore';
import FriendlyUrls from 'FriendlyUrls';

let SCREEN_WIDTH = Dimensions.get('window').width;
let MENU_NARROW_SCREEN = SCREEN_WIDTH < 375;

const AnimatedBlurView = Animated.createAnimatedComponent(Expo.BlurView);

export default class MenuView extends React.Component {
  static propTypes = {
    task: PropTypes.object.isRequired,
    shouldFadeIn: PropTypes.bool,
    isNuxFinished: PropTypes.bool,
  };

  constructor(props, context) {
    super(props, context);

    this.state = {
      transitionIn: new Animated.Value(props.shouldFadeIn ? 0 : 1),
      enableDevMenuTools: false,
      devMenuItems: {},
    };
  }

  async componentDidMount() {
    this._mounted = true;
    if (this.props.shouldFadeIn) {
      requestAnimationFrame(() => {
        this._mounted &&
          Animated.timing(this.state.transitionIn, {
            easing: Easing.inOut(Easing.quad),
            toValue: 1,
            duration: 100,
            useNativeDriver: true,
          }).start();
      });
    }
    this.forceStatusBarUpdateAsync();
    let enableDevMenuTools = await ExponentKernel.doesCurrentTaskEnableDevtools();
    let devMenuItems = await ExponentKernel.getDevMenuItemsToShow();
    if (this._mounted) {
      this.setState({ enableDevMenuTools, devMenuItems });
    }
  }

  componentWillUnmount() {
    this.restoreStatusBar();
    this._mounted = false;
  }

  forceStatusBarUpdateAsync = async () => {
    if (NativeModules.StatusBarManager._captureProperties) {
      this._statusBarValuesToRestore = await NativeModules.StatusBarManager._captureProperties();
      // HACK: StatusBar only updates changed props.
      // because MenuView typically lives under a different RN bridge, its stack of StatusBar
      // props does not necessarily reflect what the user is seeing.
      // so we force StatusBar to clear its state and update all props when we mount.
      StatusBar._currentValues = null;
    }
  };
  restoreStatusBar = () => {
    if (
      NativeModules.StatusBarManager._applyPropertiesAndForget &&
      this._statusBarValuesToRestore
    ) {
      NativeModules.StatusBarManager._applyPropertiesAndForget(
        this._statusBarValuesToRestore
      );
    }
  };
  render() {
    let scale = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [1.05, 1],
    });
    let intensity = this.state.transitionIn.interpolate({
      inputRange: [0, 1],
      outputRange: [0, 85],
    });
    let copyUrlButton;
    if (this.props.task && this.props.task.manifestUrl) {
      copyUrlButton = this._renderButton(
        'copy',
        'Copy Link',
        this._copyTaskUrl,
        require('../Assets/ios-menu-copy.png')
      );
    }

    return (
      <AnimatedBlurView
        style={styles.container}
        tint="light"
        intensity={intensity}>
        <StatusBar barStyle="default" />
        <ScrollView style={styles.overlay}>
          <Animated.View
            style={{
              opacity: this.state.transitionIn,
              transform: [{ scale }],
            }}>
            {this.props.isNuxFinished
              ? this._renderTaskInfoRow()
              : this._renderNUXRow()}
            <View style={styles.separator} />
            <View style={styles.buttonContainer}>
              {this._renderButton(
                'refresh',
                'Refresh',
                Browser.refresh,
                require('../Assets/ios-menu-refresh.png')
              )}
              {copyUrlButton}
              {this._renderButton(
                'home',
                'Go to Expo Home',
                this._goToHome,
                require('../Assets/ios-menu-home.png')
              )}
            </View>
            {this._maybeRenderDevMenuTools()}
            <TouchableHighlight
              style={styles.closeButton}
              onPress={this._onPressClose}
              underlayColor="#eee"
              hitSlop={{ top: 4, bottom: 8, left: 8, right: 4 }}>
              <Ionicons
                name="md-close"
                size={20}
                style={styles.closeButtonIcon}
              />
            </TouchableHighlight>
          </Animated.View>
        </ScrollView>
      </AnimatedBlurView>
    );
  }

  _renderNUXRow() {
    let tooltipMessage;
    if (Expo.Constants.isDevice) {
      tooltipMessage = 'Shake your device to show this menu.';
    } else {
      tooltipMessage = 'In iPhone Simulator, press \u2318D to show this menu.';
    }
    let headingStyles = MENU_NARROW_SCREEN
      ? [styles.nuxHeading, styles.nuxHeadingNarrow]
      : styles.nuxHeading;
    return (
      <View style={styles.nuxRow}>
        <View style={styles.nuxHeadingRow}>
          <ResponsiveImage
            sources={{
              2: {
                uri:
                  'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
              },
              3: {
                uri:
                  'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
              },
            }}
            style={styles.nuxLogo}
          />
          <Text style={headingStyles}>
            Welcome to Expo!
          </Text>
        </View>
        <Text style={styles.nuxTooltip}>
          {tooltipMessage}
        </Text>
        <TouchableOpacity
          style={styles.nuxButton}
          onPress={this._onPressFinishNux}>
          <Text style={styles.nuxButtonLabel}>
            Got it
          </Text>
        </TouchableOpacity>
      </View>
    );
  }

  _renderTaskInfoRow() {
    let { task } = this.props;
    let taskUrl;
    if (task.loadingError) {
      taskUrl = task.loadingError.originalUrl;
    } else {
      taskUrl = task.manifestUrl
        ? FriendlyUrls.toFriendlyString(task.manifestUrl)
        : '';
    }

    let iconUrl = task.manifest && task.manifest.get('iconUrl');
    let taskName = task.manifest && task.manifest.get('name');

    let icon = iconUrl
      ? <Image source={{ uri: iconUrl }} style={styles.taskIcon} />
      : <ResponsiveImage
          resizeMode="contain"
          sources={{
            2: {
              uri:
                'https://s3.amazonaws.com/exp-us-standard/exponent-icon@2x.png',
            },
            3: {
              uri:
                'https://s3.amazonaws.com/exp-us-standard/exponent-icon@3x.png',
            },
          }}
          style={styles.taskIcon}
        />;
    let taskNameStyles = taskName
      ? styles.taskName
      : [styles.taskName, { color: '#c5c6c7' }];
    return (
      <View style={styles.taskMetaRow}>
        <View style={styles.taskIconColumn}>
          {icon}
        </View>
        <View style={styles.taskInfoColumn}>
          <Text style={taskNameStyles} numberOfLines={1}>
            {taskName ? taskName : 'Untitled Experience'}
          </Text>
          <Text style={[styles.taskUrl]} numberOfLines={1}>
            {taskUrl}
          </Text>
          {this._maybeRenderDevServerName()}
        </View>
      </View>
    );
  }

  _maybeRenderDevServerName() {
    let { task } = this.props;
    let devServerName =
      task.manifest && task.manifest.getIn(['developer', 'tool']);
    if (devServerName) {
      // XDE is upper
      if (devServerName === 'xde') {
        devServerName = devServerName.toUpperCase();
      }
      return (
        <View style={{ flexDirection: 'row' }}>
          <DevIndicator style={{ marginTop: 4.5, marginRight: 7 }} />
          <Text style={styles.taskDevServerName}>{devServerName}</Text>
        </View>
      );
    }
    return null;
  }

  _maybeRenderDevMenuTools() {
    if (this.state.enableDevMenuTools && this.state.devMenuItems) {
      return (
        <View>
          <View style={styles.separator} />
          <View style={styles.buttonContainer}>
            {Object.keys(this.state.devMenuItems).map(key => {
              return this._renderDevMenuItem(key, this.state.devMenuItems[key]);
            })}
          </View>
        </View>
      );
    }
    return null;
  }

  _renderDevMenuItem(key, item) {
    let { label, isEnabled, detail } = item;
    if (isEnabled) {
      return this._renderButton(
        key,
        label,
        () => {
          this._onPressDevMenuButton(key);
        },
        null,
        true
      );
    } else {
      const detailButton = detail
        ? this._renderDevMenuDetailButton(label, detail)
        : nil;
      return (
        <View style={[styles.button, styles.buttonWithSeparator]} key={key}>
          <View style={styles.buttonIcon} />
          <Text style={[styles.buttonText, { color: '#9ca0a6' }]}>
            {label}
          </Text>
          {detailButton}
        </View>
      );
    }
  }

  _renderDevMenuDetailButton(title, detail) {
    return (
      <TouchableOpacity
        onPress={() => {
          Alert.alert(title, detail);
        }}>
        <Ionicons
          name="ios-information-circle"
          size={20}
          style={{ color: '#9ca0a6', marginVertical: 10 }}
        />
      </TouchableOpacity>
    );
  }

  _renderButton(key, text, onPress, iconSource, withSeparator) {
    let icon, buttonStyles;
    if (iconSource) {
      icon = <Image style={styles.buttonIcon} source={iconSource} />;
    } else {
      icon = <View style={styles.buttonIcon} />;
    }
    if (withSeparator) {
      buttonStyles = [styles.button, styles.buttonWithSeparator];
    } else {
      buttonStyles = styles.button;
    }
    return (
      <TouchableOpacity key={key} style={buttonStyles} onPress={onPress}>
        {icon}
        <Text style={styles.buttonText}>
          {text}
        </Text>
      </TouchableOpacity>
    );
  }

  _onPressFinishNux = () => {
    ExStore.dispatch(BrowserActions.setIsNuxFinishedAsync(true));
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
  _onPressClose = () => {
    if (this.props.isNuxFinished) {
      ExStore.dispatch(BrowserActions.showMenuAsync(false));
    }
  };
  _goToHome = () => {
    ExStore.dispatch(BrowserActions.foregroundHomeAsync());
  };
  _copyTaskUrl = () => {
    Clipboard.setString(this.props.task.manifestUrl);
  };
  _onPressDevMenuButton = key => {
    ExponentKernel.selectDevMenuItemWithKey(key);
    ExStore.dispatch(BrowserActions.showMenuAsync(false));
  };
}

let styles = StyleSheet.create({
  container: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
    justifyContent: 'center',
    flexDirection: 'row',
  },
  overlay: {
    flex: 1,
    backgroundColor: 'transparent',
    marginTop: Constants.statusBarHeight,
  },
  closeButtonIcon: {
    color: '#49a7e8',
  },
  closeButton: {
    position: 'absolute',
    right: 10,
    top: 10,
    paddingVertical: 2,
    paddingHorizontal: 6,
    borderRadius: 2,
  },
  taskMetaRow: {
    flexDirection: 'row',
    paddingHorizontal: 14,
    paddingBottom: 12,
  },
  taskInfoColumn: {
    flex: 4,
    justifyContent: 'center',
  },
  taskIconColumn: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  taskName: {
    color: '#595c68',
    backgroundColor: 'transparent',
    fontWeight: '700',
    fontSize: 16,
    marginTop: 14,
    marginBottom: 1,
    marginRight: 24,
  },
  taskUrl: {
    color: '#9ca0a6',
    backgroundColor: 'transparent',
    marginRight: 16,
    marginBottom: 2,
    marginTop: 1,
    fontSize: 12,
  },
  taskIcon: {
    width: 52,
    height: 52,
    marginTop: 12,
    marginRight: 10,
    alignSelf: 'center',
    backgroundColor: 'transparent',
  },
  taskDevServerName: {
    fontSize: 12,
    color: '#9ca0a6',
    fontWeight: '700',
  },
  separator: {
    borderColor: '#d5d6d7',
    borderTopWidth: 1 / PixelRatio.get(),
    backgroundColor: '#f0f0f1',
    height: 12,
    marginVertical: 4,
    marginHorizontal: -1,
  },
  buttonContainer: {
    backgroundColor: 'transparent',
  },
  button: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
  },
  buttonWithSeparator: {
    borderBottomWidth: StyleSheet.hairlineWidth * 2,
    borderBottomColor: '#f4f4f5',
  },
  buttonIcon: {
    width: 16,
    height: 16,
    marginVertical: 12,
    marginLeft: 20,
    alignSelf: 'flex-start',
  },
  buttonText: {
    color: '#595c68',
    fontSize: 14,
    textAlign: 'left',
    marginVertical: 12,
    marginRight: 5,
    paddingHorizontal: 12,
    fontWeight: '700',
  },
  nuxRow: {
    paddingHorizontal: 12,
  },
  nuxHeadingRow: {
    flexDirection: 'row',
    marginTop: 16,
    marginRight: 16,
    marginBottom: 8,
  },
  nuxLogo: {
    width: 47 * 0.7,
    height: 40 * 0.7,
    marginRight: 12,
    marginLeft: 8,
    alignSelf: 'flex-start',
  },
  nuxHeading: {
    flex: 1,
    color: '#595c68',
    fontWeight: '700',
    fontSize: 22,
  },
  nuxHeadingNarrow: {
    fontSize: 18,
    marginTop: 2,
  },
  nuxTooltip: {
    color: '#595c68',
    marginRight: 16,
    marginVertical: 4,
    fontSize: 16,
  },
  nuxButton: {
    alignItems: 'center',
    marginVertical: 12,
    paddingVertical: 10,
    backgroundColor: '#056ecf',
    borderRadius: 3,
  },
  nuxButtonLabel: {
    color: '#ffffff',
    fontSize: 16,
  },
});
<|endoftext|>


<commit_before>// Copyright 2015-2017, University of Colorado Boulder

/**
 * Displays a Property of type {number} in a background rectangle.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TNumberDisplay = require( 'SCENERY_PHET/TNumberDisplay' );

  // valid values for options.align
  var ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberDisplay( numberProperty, numberRange, options ) {

    options = _.extend( {
      align: 'right', // see ALIGN_VALUES

      /// {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',
      font: new PhetFont( 20 ),
      decimalPlaces: 0,
      xMargin: 8,
      yMargin: 2,
      cornerRadius: 0,
      numberFill: 'black',
      numberMaxWidth: 200,
      backgroundFill: 'white',
      backgroundStroke: 'lightGray',
      backgroundLineWidth: 1,
      tandem: Tandem.tandemRequired(),
      phetioType: TNumberDisplay
    }, options );

    // validate options
    assert && assert( options.valuePattern.indexOf( '{0}' ) !== -1, '{0} missing from valuePattern: ' + options.valuePattern );
    assert && assert( _.includes( ALIGN_VALUES, options.align ), 'invalid align: ' + options.align );

    var self = this;

    // determine the widest value
    var minString = Util.toFixed( numberRange.min, options.decimalPlaces );
    var maxString = Util.toFixed( numberRange.max, options.decimalPlaces );
    var widestString = StringUtils.format( options.valuePattern, ( ( minString.length > maxString.length ) ? minString : maxString ) );

    // value
    this.valueNode = new Text( widestString, {
      font: options.font,
      fill: options.numberFill,
      maxWidth: options.numberMaxWidth,
      tandem: options.tandem.createTandem( 'valueNode' )
    } );

    // @private background
    this.backgroundNode = new Rectangle( 0, 0, this.valueNode.width + 2 * options.xMargin, this.valueNode.height + 2 * options.yMargin, {
      cornerRadius: options.cornerRadius,
      fill: options.backgroundFill,
      stroke: options.backgroundStroke,
      lineWidth: options.backgroundLineWidth
    } );
    this.valueNode.centerY = this.backgroundNode.centerY;

    options.children = [ this.backgroundNode, this.valueNode ];

    // display the value
    var numberObserver = function( value ) {

      // update the value
      self.valueNode.text = StringUtils.format( options.valuePattern, Util.toFixed( value, options.decimalPlaces ) );

      // horizontally align value in background
      if ( options.align === 'center' ) {
        self.valueNode.centerX = self.backgroundNode.centerX;
      }
      else if ( options.align === 'left' ) {
        self.valueNode.left = self.backgroundNode.left + options.xMargin;
      }
      else { // right
        self.valueNode.right = self.backgroundNode.right - options.xMargin;
      }
    };
    numberProperty.link( numberObserver );

    // @private called by dispose
    this.disposeNumberDisplay = function() {
      numberProperty.unlink( numberObserver );
    };

    Node.call( this, options );
  }

  sceneryPhet.register( 'NumberDisplay', NumberDisplay );

  return inherit( Node, NumberDisplay, {

    // @public
    dispose: function() {
      this.disposeNumberDisplay();
      Node.prototype.dispose.call( this );
    },

    /**
     * Sets the number text font.
     * @param {Font} font
     * @public
     */
    setNumberFont: function( font ) {
      this.valueNode.font = font;
    },
    set numberFont( value ) { this.setNumberFont( value ); },

    /**
     * Sets the number text fill.
     * @param {Color|string} fill
     * @public
     */
    setNumberFill: function( fill ) {
      this.valueNode.fill = fill;
    },
    set numberFill( value ) { this.setNumberFill( value ); },

    /**
     * Sets the background fill.
     * @param {Color|string} fill
     * @public
     */
    setBackgroundFill: function( fill ) {
      this.backgroundNode.fill = fill;
    },
    set backgroundFill( value ) { this.setBackgroundFill( value ); },

    /**
     * Sets the background stroke.
     * @param {Color|string} stroke
     * @public
     */
    setBackgroundStroke: function( stroke ) {
      this.backgroundNode.stroke = stroke;
    },
    set backgroundStroke( value ) { this.setBackgroundStroke( value ); }
  } );
} );
<commit_msg>remove assertion that fails with stringTest, https://github.com/phetsims/scenery-phet/issues/296<commit_after>// Copyright 2015-2017, University of Colorado Boulder

/**
 * Displays a Property of type {number} in a background rectangle.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TNumberDisplay = require( 'SCENERY_PHET/TNumberDisplay' );

  // valid values for options.align
  var ALIGN_VALUES = [ 'center', 'left', 'right' ];

  /**
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberDisplay( numberProperty, numberRange, options ) {

    options = _.extend( {
      align: 'right', // see ALIGN_VALUES

      /// {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',
      font: new PhetFont( 20 ),
      decimalPlaces: 0,
      xMargin: 8,
      yMargin: 2,
      cornerRadius: 0,
      numberFill: 'black',
      numberMaxWidth: 200,
      backgroundFill: 'white',
      backgroundStroke: 'lightGray',
      backgroundLineWidth: 1,
      tandem: Tandem.tandemRequired(),
      phetioType: TNumberDisplay
    }, options );

    // validate options
    assert && assert( _.includes( ALIGN_VALUES, options.align ), 'invalid align: ' + options.align );

    var self = this;

    // determine the widest value
    var minString = Util.toFixed( numberRange.min, options.decimalPlaces );
    var maxString = Util.toFixed( numberRange.max, options.decimalPlaces );
    var widestString = StringUtils.format( options.valuePattern, ( ( minString.length > maxString.length ) ? minString : maxString ) );

    // value
    this.valueNode = new Text( widestString, {
      font: options.font,
      fill: options.numberFill,
      maxWidth: options.numberMaxWidth,
      tandem: options.tandem.createTandem( 'valueNode' )
    } );

    // @private background
    this.backgroundNode = new Rectangle( 0, 0, this.valueNode.width + 2 * options.xMargin, this.valueNode.height + 2 * options.yMargin, {
      cornerRadius: options.cornerRadius,
      fill: options.backgroundFill,
      stroke: options.backgroundStroke,
      lineWidth: options.backgroundLineWidth
    } );
    this.valueNode.centerY = this.backgroundNode.centerY;

    options.children = [ this.backgroundNode, this.valueNode ];

    // display the value
    var numberObserver = function( value ) {

      // update the value
      self.valueNode.text = StringUtils.format( options.valuePattern, Util.toFixed( value, options.decimalPlaces ) );

      // horizontally align value in background
      if ( options.align === 'center' ) {
        self.valueNode.centerX = self.backgroundNode.centerX;
      }
      else if ( options.align === 'left' ) {
        self.valueNode.left = self.backgroundNode.left + options.xMargin;
      }
      else { // right
        self.valueNode.right = self.backgroundNode.right - options.xMargin;
      }
    };
    numberProperty.link( numberObserver );

    // @private called by dispose
    this.disposeNumberDisplay = function() {
      numberProperty.unlink( numberObserver );
    };

    Node.call( this, options );
  }

  sceneryPhet.register( 'NumberDisplay', NumberDisplay );

  return inherit( Node, NumberDisplay, {

    // @public
    dispose: function() {
      this.disposeNumberDisplay();
      Node.prototype.dispose.call( this );
    },

    /**
     * Sets the number text font.
     * @param {Font} font
     * @public
     */
    setNumberFont: function( font ) {
      this.valueNode.font = font;
    },
    set numberFont( value ) { this.setNumberFont( value ); },

    /**
     * Sets the number text fill.
     * @param {Color|string} fill
     * @public
     */
    setNumberFill: function( fill ) {
      this.valueNode.fill = fill;
    },
    set numberFill( value ) { this.setNumberFill( value ); },

    /**
     * Sets the background fill.
     * @param {Color|string} fill
     * @public
     */
    setBackgroundFill: function( fill ) {
      this.backgroundNode.fill = fill;
    },
    set backgroundFill( value ) { this.setBackgroundFill( value ); },

    /**
     * Sets the background stroke.
     * @param {Color|string} stroke
     * @public
     */
    setBackgroundStroke: function( stroke ) {
      this.backgroundNode.stroke = stroke;
    },
    set backgroundStroke( value ) { this.setBackgroundStroke( value ); }
  } );
} );
<|endoftext|>


<commit_before>import { LightningElement, track, wire } from 'lwc';

import stgHealthCheckErrorLastRunDate from '@salesforce/label/c.stgHealthCheckErrorLastRunDate';

import getHealthCheckViewModel from '@salesforce/apex/HealthCheckController.getHealthCheckViewModel';
import updateHealthCheckLastRunDate from '@salesforce/apex/HealthCheckController.updateHealthCheckLastRunDate';

export default class HealthCheck extends LightningElement {
    @track expanded = true;
    @track totalChecks = 0;
    @track passedChecks = 0;
    @track lastRunDate = '';
    @track isDisplayHealthCheckGroup = false;

    @track healthCheckDefinitionsToDisplayList = [];

    LabelReference = {
        stgHealthCheckErrorLastRunDate
    }

    handleHealthCheckRun(){
        updateHealthCheckLastRunDate()
            .then(result => {
                this.lastRunDate = result;
                this.isDisplayHealthCheckGroup = true;
            })
            .catch(error => {
                this.lastRunDate = this.LabelReference.stgHealthCheckErrorLastRunDate;
            });
    }

    @wire(getHealthCheckViewModel)
    healthCheckViewModel({error, data}){
        if (data){
            this.lastRunDate = data.lastRunDate;
            this.healthCheckDefinitionsToDisplayList = data.healthCheckDefinitionList;
        } else if (error){
            console.log('error retrieving health check view model: ', error);
        }
    }

    get displayHealthCheck(){
        return !(!this.isDisplayHealthCheckGroup || !this.healthCheckDefinitionsToDisplayList);
    }

}<commit_msg>Update src/lwc/healthCheck/healthCheck.js<commit_after>import { LightningElement, track, wire } from 'lwc';

import stgHealthCheckErrorLastRunDate from '@salesforce/label/c.stgHealthCheckErrorLastRunDate';

import getHealthCheckViewModel from '@salesforce/apex/HealthCheckController.getHealthCheckViewModel';
import updateHealthCheckLastRunDate from '@salesforce/apex/HealthCheckController.updateHealthCheckLastRunDate';

export default class HealthCheck extends LightningElement {
    @track expanded = true;
    @track totalChecks = 0;
    @track passedChecks = 0;
    @track lastRunDate = '';
    @track isDisplayHealthCheckGroup = false;

    @track healthCheckDefinitionsToDisplayList = [];

    LabelReference = {
        stgHealthCheckErrorLastRunDate
    }

    handleHealthCheckRun(){
        updateHealthCheckLastRunDate()
            .then(result => {
                this.lastRunDate = result;
                this.isDisplayHealthCheckGroup = true;
            })
            .catch(error => {
                this.lastRunDate = this.LabelReference.stgHealthCheckErrorLastRunDate;
            });
    }

    @wire(getHealthCheckViewModel)
    healthCheckViewModel({error, data}){
        if (data){
            this.lastRunDate = data.lastRunDate;
            this.healthCheckDefinitionsToDisplayList = data.healthCheckDefinitionList;
        } else if (error){
            //console.log('error retrieving health check view model: ', error);
        }
    }

    get displayHealthCheck(){
        return !(!this.isDisplayHealthCheckGroup || !this.healthCheckDefinitionsToDisplayList);
    }

}
<|endoftext|>


<commit_before>'use strict';

var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var AppConstants = require('../constants/AppConstants');
var PedigreeConstants = require('../constants/PedigreeConstants');
var _ = require('lodash');
var Pedigree = require('../core/Pedigree.js');
var Member = require('../core/Member.js');
var Nest = require('../core/Nest.js');

var Individual = Member.Individual;

var CHANGE_EVENT = 'change';

// TODO: at some point this shoudl be replaced by a call to backend sever.
var _data = require('../../../examples/example.json');

var _counter = 0;
var _pedigree, _focus;

var _newId = function() {
  _counter += 1;
  return _counter;
};

var _addSpouse = function() {
  if (_focus === undefined) {
    // do nothing
    console.log('no member is selected.');
  } else {
    var member, spouse, spouseData, nest;

    member =  _pedigree.members[_focus];
    spouseData = {
      _id: _newId()
    };

    switch(member.gender()) {
      case PedigreeConstants.Gender.Male:
        spouseData.gender = PedigreeConstants.Gender.Female;
        spouse = new Individual(spouseData);
        nest = new Nest(member, spouse);
        break;
      case PedigreeConstants.Gender.Female:
        spouseData.gender = PedigreeConstants.Gender.Male;
        spouse = new Individual(spouseData);
        nest = new Nest(spouse, member);
        break;
      default:
      case PedigreeConstants.Gender.Unknown:
        spouseData.gender = PedigreeConstants.Gender.Unknown;
        spouse = new Individual(spouseData);
        nest = new Nest(member, spouse);
        break;
    }

    _pedigree.members[spouse._id] = spouse;
    _pedigree.nests.push(nest);
  }
};

var AppStore = _.extend(EventEmitter.prototype, {

  getData: function(){
    // initialize the counter.
    _counter = _.max(_.pluck(_data.members, "_id"));

    // initialize pedigree.
    if (_pedigree === undefined) {
      _pedigree = new Pedigree(_data);
    }

    return {
      "pedigree": _pedigree,
      "focus": _focus
    };
  },

  emitChange: function(){
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback){
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback){
    this.removeListener(CHANGE_EVENT, callback);
  }
});

AppDispatcher.register(function(payload){
  var action = payload.action;
  console.log('STORE DISPATCHER REGISTER', action);

  switch(action.actionType) {
    case AppConstants.CHANGE_FOCUS:
      _focus = action.focus;
      break;
    case AppConstants.ADD_SPOUSE:
      _addSpouse();
      break;
    case AppConstants.UPDATE_MEMBER:
      _pedigree.members[action.data._id] = _.omit(action.data, "_id");
      break;
  }

  AppStore.emitChange();

});

module.exports = AppStore;
<commit_msg>Fixed the bug on save<commit_after>'use strict';

var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var AppConstants = require('../constants/AppConstants');
var PedigreeConstants = require('../constants/PedigreeConstants');
var _ = require('lodash');
var Pedigree = require('../core/Pedigree.js');
var Member = require('../core/Member.js');
var Nest = require('../core/Nest.js');

var Individual = Member.Individual;

var CHANGE_EVENT = 'change';

// TODO: at some point this shoudl be replaced by a call to backend sever.
var _data = require('../../../examples/example.json');

var _counter = 0;
var _pedigree, _focus;

var _newId = function() {
  _counter += 1;
  return _counter;
};

var _addSpouse = function() {
  if (_focus === undefined) {
    // do nothing
    console.log('no member is selected.');
  } else {
    var member, spouse, spouseData, nest;

    member =  _pedigree.members[_focus];
    spouseData = {
      _id: _newId()
    };

    switch(member.gender()) {
      case PedigreeConstants.Gender.Male:
        spouseData.gender = PedigreeConstants.Gender.Female;
        spouse = new Individual(spouseData);
        nest = new Nest(member, spouse);
        break;
      case PedigreeConstants.Gender.Female:
        spouseData.gender = PedigreeConstants.Gender.Male;
        spouse = new Individual(spouseData);
        nest = new Nest(spouse, member);
        break;
      default:
      case PedigreeConstants.Gender.Unknown:
        spouseData.gender = PedigreeConstants.Gender.Unknown;
        spouse = new Individual(spouseData);
        nest = new Nest(member, spouse);
        break;
    }

    _pedigree.members[spouse._id] = spouse;
    _pedigree.nests.push(nest);
  }
};

var AppStore = _.extend(EventEmitter.prototype, {

  getData: function(){
    // initialize the counter.
    _counter = _.max(_.pluck(_data.members, "_id"));

    // initialize pedigree.
    if (_pedigree === undefined) {
      _pedigree = new Pedigree(_data);
    }

    return {
      "pedigree": _pedigree,
      "focus": _focus
    };
  },

  emitChange: function(){
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback){
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback){
    this.removeListener(CHANGE_EVENT, callback);
  }
});

AppDispatcher.register(function(payload){
  var action = payload.action;
  console.log('STORE DISPATCHER REGISTER', action);

  switch(action.actionType) {
    case AppConstants.CHANGE_FOCUS:
      _focus = action.focus;
      break;
    case AppConstants.ADD_SPOUSE:
      _addSpouse();
      break;
    case AppConstants.UPDATE_MEMBER:
      _pedigree.members[_focus].data = action.data;
      break;
  }

  AppStore.emitChange();

});

module.exports = AppStore;
<|endoftext|>


<commit_before>var selectASharedMapChoiceText = 'Select a shared map';

$(document).ready(function() {
    initialiseAddMapPopup();
});

var initialiseAddMapPopup = function() {
    var $mapSelectionContainer = $('#map-selection-container');
    var $sharedMapSelectionButton = $('#sharedMapSelectionButton');
    $sharedMapSelectionButton.text(selectASharedMapChoiceText);
    $sharedMapSelectionButton.click(handleSharedMapSelectionButton);
    var $actionLink = $('#action-a');
    $actionLink.click(handleSharedMapSelectionLink);
    var $actionLinks = $('.shared-map-choice');
    $actionLinks.click(handleSharedMapSelectionLink);
    $mapSelectionContainer.removeClass('hidden');

    var teamKey = 'demo';
    getFreshMapInfo(teamKey)
    .then(function(retrievedSharedMapInfo) {
        var sharedMapInfoJson = JSON.stringify(retrievedSharedMapInfo);
        if (sharedMapInfoJson) {
            var $sharedMapsDropdown = $('shared-maps-dropdown');
            for (var index = 0; index < sharedMapInfoJson.mapNames.length; index++) {
                var mapName = sharedMapInfoJson.mapNames[index];
                // None
                var $mapOption = $('a', {
                    id: "foo",
                    "class": "a",
                    text: mapName
                });
                $mapOption.appendTo($sharedMapsDropdown);

            }
        }
    });
};

var handleSharedMapSelectionLink = function(event) {
    event.preventDefault();
    var $sharedMapSelectionButton = $('#sharedMapSelectionButton');
    var sharedMapOption = event.target;
    var sharedMapName = sharedMapOption.innerText;
    $sharedMapSelectionButton.text(sharedMapName);

    getSharedMapPopupItemsUsingPromise().then(function() {

    });

};

var handleSharedMapSelectionButton = function(event) {

};
<commit_msg>update<commit_after>var selectASharedMapChoiceText = 'Select a shared map';

$(document).ready(function() {
    initialiseAddMapPopup();
});

var initialiseAddMapPopup = function() {
    var $mapSelectionContainer = $('#map-selection-container');
    var $sharedMapSelectionButton = $('#sharedMapSelectionButton');
    $sharedMapSelectionButton.text(selectASharedMapChoiceText);
    $sharedMapSelectionButton.click(handleSharedMapSelectionButton);
    var $actionLink = $('#action-a');
    $actionLink.click(handleSharedMapSelectionLink);
    var $actionLinks = $('.shared-map-choice');
    $actionLinks.click(handleSharedMapSelectionLink);
    $mapSelectionContainer.removeClass('hidden');

    var teamKey = 'demo';
    getFreshMapInfo(teamKey)
    .then(function(sharedMapInfo) {
        //var sharedMapInfoJson = JSON.stringify(sharedMapInfo);
        if (sharedMapInfo) {
            var $sharedMapsDropdown = $('shared-maps-dropdown');
            for (var index = 0; index < sharedMapInfo.mapNames.length; index++) {
                var mapName = sharedMapInfo.mapNames[index];
                // None
                var $mapOption = $('a', {
                    id: "foo",
                    "class": "a",
                    text: mapName
                });
                $mapOption.appendTo($sharedMapsDropdown);

            }
        }
    });
};

var handleSharedMapSelectionLink = function(event) {
    event.preventDefault();
    var $sharedMapSelectionButton = $('#sharedMapSelectionButton');
    var sharedMapOption = event.target;
    var sharedMapName = sharedMapOption.innerText;
    $sharedMapSelectionButton.text(sharedMapName);

    getSharedMapPopupItemsUsingPromise().then(function() {

    });

};

var handleSharedMapSelectionButton = function(event) {

};
<|endoftext|>


<commit_before>/*global define,$ */
/*jslint browser:true,sloppy:true,nomen:true,unparam:true,plusplus:true */
/** @license
 | Copyright 2015 Esri
 |
 | Licensed under the Apache License, Version 2.0 (the "License");
 | you may not use this file except in compliance with the License.
 | You may obtain a copy of the License at
 |
 |    http://www.apache.org/licenses/LICENSE-2.0
 |
 | Unless required by applicable law or agreed to in writing, software
 | distributed under the License is distributed on an "AS IS" BASIS,
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | See the License for the specific language governing permissions and
 | limitations under the License.
 */
//============================================================================================================================//
define(function () {
    return {

        //--------------------------------------------------------------------------------------------------------------------//

        // Available upon return
        contribLevels: [],

        // Available after init's parametersReady deferred
        urlParams: {},
        appParams: {  // Provide fallback values in case neither the configuration file nor the online app can be retrieved
            webmap: "",
            title: "Photo Survey",
            splashText: "The Property Survey application can be used to conduct rapid damage assessments, inventory blighted properties, target reappraisal efforts, and identify structures that could pose safety concerns.",
            splashBackgroundUrl: "images/splash.jpg",
            helpText: "",
            contributorLevels: ",100,,200,,400,,800,,1600,",
            showFacebook: "true",
            showGooglePlus: "true",
            showTwitter: "true",
            surveyorNameField: "",
            bestPhotoField: ""
        },

        // Available after init's featureServiceReady deferred
        webmapParams: {},
        opLayer: null,
        featureSvcParams: {},

        // Available after init's surveyReady deferred
        survey: [],

        //--------------------------------------------------------------------------------------------------------------------//

        logElapsedTime: function (tag, startMs) {  //???
            var ticks = (new Date()).getTime() - startMs;
            console.log(tag + ": " + ticks);
        },

        init: function () {
            var self = this;
            var url;
            var startMs = (new Date()).getTime();  //???

            var parametersReady = $.Deferred();
            var featureServiceReady = $.Deferred();
            var surveyReady = $.Deferred();

            // Get the URL parameters
            var params = window.location.search;
            if (params.length > 0 && params[0] === "?") {
                params = params.substring(1).split("&");
                $.map(params, function (item, index) {
                    var paramParts = item.split("=");
                    self.urlParams[paramParts[0].toLowerCase()] = paramParts[1];
                });
            }

            // We have two sources of app parameters: an online app indicated by the appId URL parameter
            // and a configuration file. We want both because the online app only includes parameters that
            // were changed from the defaults.
            var fileAppDeferred = $.Deferred();
            $.getJSON("js/configuration.json", function (data) {
                self.logElapsedTime("resolve config file", startMs);  //???
                fileAppDeferred.resolve(data);
            });

            var onlineAppDeferred = $.Deferred();
            if (self.urlParams.appid) {
                $.getJSON("http://www.arcgis.com/sharing/content/items/" + self.urlParams.appid + "/data?f=json", function (data) {
                    self.logElapsedTime("resolve online config", startMs);  //???
                    onlineAppDeferred.resolve(data);
                });
            } else {
                self.logElapsedTime("no online config", startMs);  //???
                onlineAppDeferred.resolve({});
            }

            // After getting both parameter sets, we overwrite the barebones parameters defined above first with the file
            // values and then with the online app values
            $.when(fileAppDeferred, onlineAppDeferred).done(function (fileAppData, onlineAppData) {
                if (fileAppData && fileAppData.values) {
                    fileAppData = fileAppData.values;
                } else {
                    fileAppData = {};
                }
                if (onlineAppData && onlineAppData.values) {
                    onlineAppData = onlineAppData.values;
                } else {
                    onlineAppData = {};
                }
                self.appParams = $.extend(self.appParams, fileAppData, onlineAppData);
                self.logElapsedTime("merged configs", startMs);  //???

                // GA URL-supplied webmap overrides a configured one
                if (self.urlParams.webmap) {
                    self.appParams.webmap = self.urlParams.webmap;
                }

                parametersReady.resolve();

                // Get the app's webmap's data
                if (self.appParams.webmap) {
                    $.getJSON("http://www.arcgis.com/sharing/content/items/" + self.appParams.webmap + "/data?f=json", function (data) {
                        self.webmapParams = data || {};
                        self.logElapsedTime("have webmap", startMs);  //???

                        if (self.webmapParams && self.webmapParams.operationalLayers && self.webmapParams.operationalLayers.length > 0) {
                            self.opLayer = self.webmapParams.operationalLayers[0];

                        // Get the app's webmap's feature service's data
                            $.getJSON(self.opLayer.url + "?f=json", function (data) {
                                self.featureSvcParams = data || {};
                                featureServiceReady.resolve();
                                self.logElapsedTime("have feature svc", startMs);  //???

                                if (self.featureSvcParams.fields) {
                                    // Create dictionary of fields with their domains and nullability; skip fields without domains
                                    var fieldDomains = {};
                                    $.each(self.featureSvcParams.fields, function (idx, field) {
                                        if (field.domain && field.domain.codedValues) {
                                            fieldDomains[field.name] = {
                                                domain: $.map(field.domain.codedValues, function (item, index) {
                                                    return item.code
                                                }).join("|"),
                                                important: !field.nullable
                                            }
                                        }
                                    });

                                    // Parse survey
                                    // e.g.,
Is there a Structure on the Property? {Structure} {button}

Is the lot overgrown? {Lot} {button}

Foundation type
                                    //  : {FoundationType} {radio}

                                    //

Is there roof damage? {RoofDamage} {button}

Is the exterior damaged?
                                    //  {ExteriorDamage} {button}

Is there graffiti? {Graffiti} {button}

                                    //  Are there boarded windows/doors? {Boarded} {button}

                                    //   1. split on
 and then
 (lists are enclosed in

 sets)
                                    var taggedSurveyLines = [];
                                    var descriptionSplitP = self.opLayer.popupInfo.description.split("
");
                                    $.each(descriptionSplitP, function (idx, line) {
                                        $.merge(taggedSurveyLines, line.split("
"));
                                    });

                                    //   2. remove all html tags (could have , , ,
,
,
, , , ,
,
                                    // and their closures included or explicit)
                                    var surveyLines = [];
                                    $.each(taggedSurveyLines, function (idx, line) {
                                        var cleanedLine = $(line).text().trim();
                                        if (cleanedLine.length > 0) {
                                            surveyLines.push(cleanedLine);
                                        }
                                    });

                                    //   3. Separate into question, field, and style
                                    //      e.g., "Is there a Structure on the Property? {Structure} {button}"
                                    $.each(surveyLines, function (idx, line) {
                                        var paramParts = line.split("{");
                                        var trimmedParts = [];
                                        $.each(paramParts, function (idx, part) {
                                            var trimmed = part.replace("}", "").trim();
                                            if (trimmed.length > 0) {
                                                trimmedParts.push(trimmed);
                                            }
                                        });

                                        // Should have three parts now: question, field, style; we can add in the question's
                                        // domain and importance from the fieldDomain dictionary created just above
                                        if (trimmedParts.length === 3) {
                                            var fieldName = trimmedParts[1];
                                            if (fieldDomains[fieldName]) {
                                                var surveyQuestion = {
                                                    question: trimmedParts[0],
                                                    field: fieldName,
                                                    style: trimmedParts[2],
                                                    domain: fieldDomains[fieldName].domain,
                                                    important: fieldDomains[fieldName].important
                                                };
                                                self.survey.push(surveyQuestion);
                                            }
                                        }
                                    });

                                    surveyReady.resolve();
                                }
                            });
                        }
                    });
                }
            });

            // Expand the contributor level definition into an array for easier lookup.
            // The display uses five stars, so we support six contributor levels separated by five
            // >0 values in increasing order, e.g., "A,100,B,200,C,400,D,800,E,1600,F". This string
            // defines contributor levels labeled "A" through "F", with "A" standing for 0 to 99
            // (i.e., separator 100 minus one), "B" 100 through 199, "C" 200 through 399, etc.
            var contributorLevelsDataList = self.appParams.contributorLevels.split(",");
            if (contributorLevelsDataList.length === 11) {
                try {
                    var i, label, minimumSurveysNeeded;
                    for (i = 0; i < contributorLevelsDataList.length; i += 2) {
                        label = contributorLevelsDataList[i];
                        minimumSurveysNeeded = i === 0 ? 0 :
                            Number.parseInt(contributorLevelsDataList[i - 1]);
                        self.contribLevels.push({
                            "label": label,
                            "minimumSurveysNeeded": minimumSurveysNeeded
                        });
                    }
                } catch (ignore) {
                    self.contribLevels = [];
                }
            }

            return {
                "parametersReady": parametersReady,
                "featureServiceReady" : featureServiceReady,
                "surveyReady" : surveyReady
            };
        }

    };
});
<commit_msg>Added social media app params and normalized booleans<commit_after>/*global define,$ */
/*jslint browser:true,sloppy:true,nomen:true,unparam:true,plusplus:true */
/** @license
 | Copyright 2015 Esri
 |
 | Licensed under the Apache License, Version 2.0 (the "License");
 | you may not use this file except in compliance with the License.
 | You may obtain a copy of the License at
 |
 |    http://www.apache.org/licenses/LICENSE-2.0
 |
 | Unless required by applicable law or agreed to in writing, software
 | distributed under the License is distributed on an "AS IS" BASIS,
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | See the License for the specific language governing permissions and
 | limitations under the License.
 */
//============================================================================================================================//
define(function () {
    return {

        //--------------------------------------------------------------------------------------------------------------------//

        // Available upon return
        contribLevels: [],

        // Available after init's parametersReady deferred
        urlParams: {},
        appParams: {  // Provide fallback values in case neither the configuration file nor the online app can be retrieved
            webmap: "",
            title: "Photo Survey",
            splashText: "The Property Survey application can be used to conduct rapid damage assessments, inventory blighted properties, target reappraisal efforts, and identify structures that could pose safety concerns.",
            splashBackgroundUrl: "images/splash.jpg",
            helpText: "",
            contributorLevels: ",100,,200,,400,,800,,1600,",

            showFacebook: "true",
            showGooglePlus: "true",
            showTwitter: "true",
            facebookAppId: "",
            facebookAppScope: "public_profile",
            googleplusClientId: "",
            twitterSigninUrl: "https://utility.arcgis.com/tproxy/signin",
            twitterUserUrl: "https://utility.arcgis.com/tproxy/proxy/1.1/account/verify_credentials.json?q=&include_entities=true&skip_status=true&locale=en",
            twitterCallbackPage: "/oauth-callback.html",

            surveyorNameField: "",
            bestPhotoField: ""
        },

        // Available after init's featureServiceReady deferred
        webmapParams: {},
        opLayer: null,
        featureSvcParams: {},

        // Available after init's surveyReady deferred
        survey: [],

        //--------------------------------------------------------------------------------------------------------------------//

        logElapsedTime: function (tag, startMs) {  //???
            var ticks = (new Date()).getTime() - startMs;
            console.log(tag + ": " + ticks);
        },

        init: function () {
            var self = this;
            var url;
            var startMs = (new Date()).getTime();  //???

            var parametersReady = $.Deferred();
            var featureServiceReady = $.Deferred();
            var surveyReady = $.Deferred();

            // Get the URL parameters
            var params = window.location.search;
            if (params.length > 0 && params[0] === "?") {
                params = params.substring(1).split("&");
                $.map(params, function (item, index) {
                    var paramParts = item.split("=");
                    self.urlParams[paramParts[0].toLowerCase()] = paramParts[1];
                });
            }

            // We have two sources of app parameters: an online app indicated by the appId URL parameter
            // and a configuration file. We want both because the online app only includes parameters that
            // were changed from the defaults.
            var fileAppDeferred = $.Deferred();
            $.getJSON("js/configuration.json", function (data) {
                self.logElapsedTime("resolve config file", startMs);  //???
                fileAppDeferred.resolve(data);
            });

            var onlineAppDeferred = $.Deferred();
            if (self.urlParams.appid) {
                $.getJSON("http://www.arcgis.com/sharing/content/items/" + self.urlParams.appid + "/data?f=json", function (data) {
                    self.logElapsedTime("resolve online config", startMs);  //???
                    onlineAppDeferred.resolve(data);
                });
            } else {
                self.logElapsedTime("no online config", startMs);  //???
                onlineAppDeferred.resolve({});
            }

            // After getting both parameter sets, we overwrite the barebones parameters defined above first with the file
            // values and then with the online app values
            $.when(fileAppDeferred, onlineAppDeferred).done(function (fileAppData, onlineAppData) {
                if (fileAppData && fileAppData.values) {
                    fileAppData = fileAppData.values;
                } else {
                    fileAppData = {};
                }
                if (onlineAppData && onlineAppData.values) {
                    onlineAppData = onlineAppData.values;
                } else {
                    onlineAppData = {};
                }
                self.appParams = $.extend(self.appParams, fileAppData, onlineAppData);

                // Normalize booleans
                self.appParams.showFacebook = self._toBoolean(self.appParams.showFacebook);
                self.appParams.showGooglePlus = self._toBoolean(self.appParams.showGooglePlus);
                self.appParams.showTwitter = self._toBoolean(self.appParams.showTwitter);

                self.logElapsedTime("merged configs", startMs);  //???

                // GA URL-supplied webmap overrides a configured one
                if (self.urlParams.webmap) {
                    self.appParams.webmap = self.urlParams.webmap;
                }

                parametersReady.resolve();

                // Get the app's webmap's data
                if (self.appParams.webmap) {
                    $.getJSON("http://www.arcgis.com/sharing/content/items/" + self.appParams.webmap + "/data?f=json", function (data) {
                        self.webmapParams = data || {};
                        self.logElapsedTime("have webmap", startMs);  //???

                        if (self.webmapParams && self.webmapParams.operationalLayers && self.webmapParams.operationalLayers.length > 0) {
                            self.opLayer = self.webmapParams.operationalLayers[0];

                        // Get the app's webmap's feature service's data
                            $.getJSON(self.opLayer.url + "?f=json", function (data) {
                                self.featureSvcParams = data || {};
                                featureServiceReady.resolve();
                                self.logElapsedTime("have feature svc", startMs);  //???

                                if (self.featureSvcParams.fields) {
                                    // Create dictionary of fields with their domains and nullability; skip fields without domains
                                    var fieldDomains = {};
                                    $.each(self.featureSvcParams.fields, function (idx, field) {
                                        if (field.domain && field.domain.codedValues) {
                                            fieldDomains[field.name] = {
                                                domain: $.map(field.domain.codedValues, function (item, index) {
                                                    return item.code
                                                }).join("|"),
                                                important: !field.nullable
                                            }
                                        }
                                    });

                                    // Parse survey
                                    // e.g.,
Is there a Structure on the Property? {Structure} {button}

Is the lot overgrown? {Lot} {button}

Foundation type
                                    //  : {FoundationType} {radio}

                                    //

Is there roof damage? {RoofDamage} {button}

Is the exterior damaged?
                                    //  {ExteriorDamage} {button}

Is there graffiti? {Graffiti} {button}

                                    //  Are there boarded windows/doors? {Boarded} {button}

                                    //   1. split on
 and then
 (lists are enclosed in

 sets)
                                    var taggedSurveyLines = [];
                                    var descriptionSplitP = self.opLayer.popupInfo.description.split("
");
                                    $.each(descriptionSplitP, function (idx, line) {
                                        $.merge(taggedSurveyLines, line.split("
"));
                                    });

                                    //   2. remove all html tags (could have , , ,
,
,
, , , ,
,
                                    // and their closures included or explicit)
                                    var surveyLines = [];
                                    $.each(taggedSurveyLines, function (idx, line) {
                                        var cleanedLine = $(line).text().trim();
                                        if (cleanedLine.length > 0) {
                                            surveyLines.push(cleanedLine);
                                        }
                                    });

                                    //   3. Separate into question, field, and style
                                    //      e.g., "Is there a Structure on the Property? {Structure} {button}"
                                    $.each(surveyLines, function (idx, line) {
                                        var paramParts = line.split("{");
                                        var trimmedParts = [];
                                        $.each(paramParts, function (idx, part) {
                                            var trimmed = part.replace("}", "").trim();
                                            if (trimmed.length > 0) {
                                                trimmedParts.push(trimmed);
                                            }
                                        });

                                        // Should have three parts now: question, field, style; we can add in the question's
                                        // domain and importance from the fieldDomain dictionary created just above
                                        if (trimmedParts.length === 3) {
                                            var fieldName = trimmedParts[1];
                                            if (fieldDomains[fieldName]) {
                                                var surveyQuestion = {
                                                    question: trimmedParts[0],
                                                    field: fieldName,
                                                    style: trimmedParts[2],
                                                    domain: fieldDomains[fieldName].domain,
                                                    important: fieldDomains[fieldName].important
                                                };
                                                self.survey.push(surveyQuestion);
                                            }
                                        }
                                    });

                                    surveyReady.resolve();
                                }
                            });
                        }
                    });
                }
            });

            // Expand the contributor level definition into an array for easier lookup.
            // The display uses five stars, so we support six contributor levels separated by five
            // >0 values in increasing order, e.g., "A,100,B,200,C,400,D,800,E,1600,F". This string
            // defines contributor levels labeled "A" through "F", with "A" standing for 0 to 99
            // (i.e., separator 100 minus one), "B" 100 through 199, "C" 200 through 399, etc.
            var contributorLevelsDataList = self.appParams.contributorLevels.split(",");
            if (contributorLevelsDataList.length === 11) {
                try {
                    var i, label, minimumSurveysNeeded;
                    for (i = 0; i < contributorLevelsDataList.length; i += 2) {
                        label = contributorLevelsDataList[i];
                        minimumSurveysNeeded = i === 0 ? 0 :
                            Number.parseInt(contributorLevelsDataList[i - 1]);
                        self.contribLevels.push({
                            "label": label,
                            "minimumSurveysNeeded": minimumSurveysNeeded
                        });
                    }
                } catch (ignore) {
                    self.contribLevels = [];
                }
            }

            return {
                "parametersReady": parametersReady,
                "featureServiceReady" : featureServiceReady,
                "surveyReady" : surveyReady
            };
        },

        //--------------------------------------------------------------------------------------------------------------------//

        /** Normalizes a boolean value to true or false.
         * @param {boolean|string} boolValue A true or false
         *        value that is returned directly or a string
         *        "true" or "false" (case-insensitive) that
         *        is checked and returned; if neither a
         *        a boolean or a usable string, falls back to
         *        defaultValue
         * @param {boolean} [defaultValue] A true or false
         *        that is returned if boolValue can't be
         *        used; if not defined, true is returned
         * @memberOf js.LGObject#
         */
        _toBoolean: function (boolValue, defaultValue) {
            var lowercaseValue;

            // Shortcut true|false
            if (boolValue === true) {
                return true;
            }
            if (boolValue === false) {
                return false;
            }

            // Handle a true|false string
            if (typeof boolValue === "string") {
                lowercaseValue = boolValue.toLowerCase();
                if (lowercaseValue === "true") {
                    return true;
                }
                if (lowercaseValue === "false") {
                    return false;
                }
            }
            // Fall back to default
            if (defaultValue === undefined) {
                return true;
            }
            return defaultValue;
        }

    };
});
<|endoftext|>