<commit_before>import React from 'react';
import {
	View,
	Dimensions,
	ScrollView,
	Text,
	StyleSheet,
	TouchableOpacity,
	Platform,
	StatusBar,
	BackAndroid
} from 'react-native';
import { connect } from 'react-redux';
import ElevatedView from 'react-native-elevated-view';
import Icon from 'react-native-vector-icons/FontAwesome';
import MapView from 'react-native-maps';

import SearchBar from './SearchBar';
import SearchMap from './SearchMap';
import SearchResults from './SearchResults';
import SearchHistoryCard from './SearchHistoryCard';
import SearchSuggest from './SearchSuggest';
import SearchShuttleMenu from './SearchShuttleMenu';
import ShuttleLocationContainer from '../../containers/shuttleLocationContainer';

import { toggleRoute } from '../../actions/shuttle';
import { fetchSearch } from '../../actions/map';

import css from '../../styles/css';
import logger from '../../util/logger';
import { getPRM, gotoNavigationApp } from '../../util/general';

const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get('window').width;
const statusBarHeight = Platform.select({
	ios: 0,
	android: StatusBar.currentHeight,
});

class NearbyMapView extends React.Component {

	constructor(props) {
		super(props);

		this.state = {
			searchInput: null,
			selectedResult: 0,
			typing: false,
			allowScroll: false,
			iconStatus: 'search',
			showBar: false,
			showShuttle: true,
			showNav: false,
			showMenu: false,
			toggled: false,
			vehicles: {},
		};
	}

	componentWillMount() {
		Object.keys(this.props.shuttle_routes).forEach((key, index) => {
			this.setState({ ['route' + key] : false });
		});
	}

	componentDidMount() {
		logger.ga('View mounted: Full Map View');

		BackAndroid.addEventListener('hardwareBackPress', this.pressIcon);
	}

	componentWillReceiveProps(nextProps) {
		// Loop thru every vehicle
		Object.keys(nextProps.vehicles).forEach((key, index) => {
			if (this.state.vehicles[key]) {
				nextProps.vehicles[key].forEach((nextVehicle) => {
					this.state.vehicles[key].forEach((currVehicle) => {
						if (nextVehicle.id === currVehicle.id &&
							(nextVehicle.lat !== currVehicle.lat || nextVehicle.lon !== currVehicle.lon)) {
							// Animate vehicle movement
							currVehicle.animated.timing({
								latitude: nextVehicle.lat,
								longitude: nextVehicle.lon,
								duration: 500
							}).start();
						}
					});
				});
			} else {
				// Make Animated values
				nextProps.vehicles[key].forEach((nextVehicle) => {
					nextVehicle.animated = new MapView.AnimatedRegion({
						latitude: nextVehicle.lat,
						longitude: nextVehicle.lon,
					});
				});

				const newVehicles = this.state.vehicles;
				newVehicles[key] = nextProps.vehicles[key];

				this.setState({
					vehicles: newVehicles
				});
			}
		});

		if (this.state.iconStatus === 'load' && nextProps.search_results) {
			this.setState({
				iconStatus: 'search'
			});
		}
	}

	shouldComponentUpdate(nextProps, nextState) {
		// Don't re-render if location hasn't changed
		if (((this.props.location.coords.latitude !== nextProps.location.coords.latitude) ||
			(this.props.location.coords.longitude !== nextProps.location.coords.longitude)) ||
			this.state !== nextState ||
			this.props.search_results !== nextProps.search_results) {
			/*
			(this.state.selectedResult !== nextState.selectedResult) ||
			(this.state.iconStatus !== nextState.iconStatus) ||
			(this.state.showBar !== nextState.showBar) ||
			(this.state.showMenu !== nextState.showMenu) ||
			(this.state.route1 !== nextState.route1)) {*/

			return true;
		} else {
			return false;
		}
	}

	componentWillUnmount() {
		BackAndroid.removeEventListener('hardwareBackPress', this.pressIcon);
	}

	pressIcon = () => {
		if (this.state.iconStatus === 'back') {
			this.setState({
				iconStatus: 'search',
				showBar: (this.props.search_results !== null),
				showShuttle: true,
				showNav: true,
			});
			this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
			// this.barRef.clear();
			this.barRef.blur();
			return true;
		} else {
			return false;
		}
	}

	gotoResults = () => {
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false
		});
		this.scrollRef.scrollTo({ x: 0, y: deviceHeight - 64 - statusBarHeight, animated: true });
	}

	focusSearch = () => {
		this.scrollRef.scrollTo({ x: 0, y: 2 * (deviceHeight - 64 - statusBarHeight), animated: true });
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false,
		});
	}

	gotoShuttleSettings = () => {
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false,
		});
		this.scrollRef.scrollTo({ x: 0, y: 3 * (deviceHeight - 64 - statusBarHeight), animated: true });
	}

	updateSearch = (text) => {
		this.props.fetchSearch(text);
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
		this.barRef.blur();

		this.setState({
			searchInput: text,
			showBar: true,
			iconStatus: 'load',
			showShuttle: true,
			showNav: true,
		});
	}

	updateSearchSuggest = (text) => {
		this.props.fetchSearch(text, this.props.location);
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
		this.barRef.blur();

		this.setState({
			searchInput: text,
			showBar: true,
			iconStatus: 'load',
			showShuttle: true,
			showNav: true,
		});
	}

	updateSelectedResult = (index) => {
		const newSelect = index;
		this.setState({
			iconStatus: 'search',
			selectedResult: newSelect,
			showBar: true,
			showShuttle: true,
			showNav: true,
		});
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
	}

	toggleRoute = (value, route) => {
		this.props.toggle(route);

		const vehicles = this.state.vehicles;
		delete vehicles[route];

		this.setState({
			toggled: !this.state.toggled,
			vehicles });
	}

	render() {
		if (this.props.location.coords) {
			return (
				<View style={css.main_container}>
					{
						(this.props.search_results && this.state.showNav) ? (
							<ElevatedView
								style={{ zIndex: 2, justifyContent: 'center', alignItems: 'center', position: 'absolute', bottom: (2 * (6 + Math.round(44 * getPRM()))) + 12, right: 6, width: 50, height: 50, borderRadius: 50 / 2, backgroundColor: '#2196F3' }}
								elevation={2} // zIndex style and elevation has to match
							>
								<TouchableOpacity
									onPress={() => gotoNavigationApp(this.props.search_results[this.state.selectedResult].mkrLat, this.props.search_results[this.state.selectedResult].mkrLong)}
								>
									<Icon name={'location-arrow'} size={20} color={'white'} />
								</TouchableOpacity>
							</ElevatedView>
						) : (<ElevatedView />) // Android bug - breaks view if this is null...on RN39...check if this bug still exists in RN40 or if this can be changed to null
					}
					{
						(this.state.showShuttle) ? (
							<ElevatedView
								style={{ zIndex: 2, justifyContent: 'center', alignItems: 'center', position: 'absolute', bottom: 6 + Math.round(44 * getPRM()), right: 6, width: 50, height: 50, borderRadius: 50 / 2, backgroundColor: '#346994' }}
								elevation={2} // zIndex style and elevation has to match
							>
								<TouchableOpacity
									onPress={this.gotoShuttleSettings}
								>
									<Icon name={'bus'} size={20} color={'white'} />
								</TouchableOpacity>
							</ElevatedView>
						) : (<ElevatedView />) // Android bug
					}
					<SearchBar
						update={this.updateSearch}
						onFocus={this.focusSearch}
						pressIcon={this.pressIcon}
						iconStatus={this.state.iconStatus}
						searchInput={this.state.searchInput}
						reff={
							(ref) => { this.barRef = ref; }
						}
					/>
					<ScrollView
						ref={
							(ref) => {
								this.scrollRef = ref;
							}
						}
						showsVerticalScrollIndicator={false}
						scrollEnabled={this.state.allowScroll}
					>
						<View
							style={styles.section}
						>
							<SearchMap
								location={this.props.location}
								selectedResult={
									(this.props.search_results) ? (
										this.props.search_results[this.state.selectedResult]
									) : null
								}
								shuttle={this.props.shuttle_stops}
								vehicles={this.state.vehicles}
							/>
						</View>
						<View
							style={styles.section}
						>
							<SearchResults
								results={this.props.search_results}
								onSelect={(index) => this.updateSelectedResult(index)}
							/>
						</View>
						<View
							style={styles.section}
						>
							<SearchSuggest
								onPress={this.updateSearchSuggest}
							/>
							{(this.props.search_history.length !== 0) ? (
								<SearchHistoryCard
									pressHistory={this.updateSearch}
									data={this.props.search_history}
								/>
								) : (null)}
						</View>
						<View
							style={styles.section}
						>
							<SearchShuttleMenu
								shuttle_routes={this.props.shuttle_routes}
								onToggle={this.toggleRoute}
								toggles={this.props.toggles}
							/>
						</View>
					</ScrollView>
					{(this.state.showBar && this.props.search_results) ? (
						<ElevatedView
							style={styles.bottomBarContainer}
							elevation={5}
						>
							<TouchableOpacity
								style={styles.bottomBarContent}
								onPress={
									this.gotoResults
								}
							>
								<Text
									style={styles.bottomBarText}
								>
									See More Results
								</Text>
							</TouchableOpacity>
						</ElevatedView>
						) : (null)
					}
					<ShuttleLocationContainer />
				</View>
			);
		} else {
			return null;
		}
	}
}

const mapStateToProps = (state, props) => (
	{
		location: state.location.position,
		locationPermission: state.location.permission,
		toggles: state.shuttle.toggles,
		shuttle_routes: state.shuttle.routes,
		shuttle_stops: state.shuttle.stops,
		vehicles: state.shuttle.vehicles,
		search_history: state.map.history,
		search_results: state.map.results
	}
);

const mapDispatchToProps = (dispatch, ownProps) => (
	{
		fetchSearch: (term, location) => {
			dispatch(fetchSearch(term, location));
		},
		toggle: (route) => {
			dispatch(toggleRoute(route));
		}
	}
);

module.exports = connect(mapStateToProps, mapDispatchToProps)(NearbyMapView);

const navMargin = Platform.select({
	ios: 64,
	android: 0
});

const styles = StyleSheet.create({
	main_container: { width: deviceWidth, height: deviceHeight - 64 - statusBarHeight, backgroundColor: '#EAEAEA', marginTop: navMargin },
	bottomBarContainer: { zIndex: 5, alignItems: 'center', justifyContent: 'center', position: 'absolute', bottom: 0, width: deviceWidth, height: Math.round(44 * getPRM()), borderWidth: 0, backgroundColor: 'white', },
	bottomBarContent: { flex: 1, justifyContent: 'center', alignSelf: 'stretch' },
	bottomBarText: { textAlign: 'center', },

	section: { height: deviceHeight - 64 - statusBarHeight },
});

<commit_msg>No result handling<commit_after>import React from 'react';
import {
	View,
	Dimensions,
	ScrollView,
	Text,
	StyleSheet,
	TouchableOpacity,
	Platform,
	StatusBar,
	BackAndroid
} from 'react-native';
import { connect } from 'react-redux';
import ElevatedView from 'react-native-elevated-view';
import Icon from 'react-native-vector-icons/FontAwesome';
import MapView from 'react-native-maps';

import SearchBar from './SearchBar';
import SearchMap from './SearchMap';
import SearchResults from './SearchResults';
import SearchHistoryCard from './SearchHistoryCard';
import SearchSuggest from './SearchSuggest';
import SearchShuttleMenu from './SearchShuttleMenu';
import ShuttleLocationContainer from '../../containers/shuttleLocationContainer';

import { toggleRoute } from '../../actions/shuttle';
import { fetchSearch } from '../../actions/map';

import css from '../../styles/css';
import logger from '../../util/logger';
import { getPRM, gotoNavigationApp } from '../../util/general';

const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get('window').width;
const statusBarHeight = Platform.select({
	ios: 0,
	android: StatusBar.currentHeight,
});

class NearbyMapView extends React.Component {

	constructor(props) {
		super(props);

		this.state = {
			searchInput: null,
			selectedResult: 0,
			typing: false,
			allowScroll: false,
			iconStatus: 'search',
			showBar: false,
			showShuttle: true,
			showNav: false,
			showMenu: false,
			toggled: false,
			vehicles: {},
		};
	}

	componentWillMount() {
		Object.keys(this.props.shuttle_routes).forEach((key, index) => {
			this.setState({ ['route' + key] : false });
		});
	}

	componentDidMount() {
		logger.ga('View mounted: Full Map View');

		BackAndroid.addEventListener('hardwareBackPress', this.pressIcon);
	}

	componentWillReceiveProps(nextProps) {
		// Loop thru every vehicle
		Object.keys(nextProps.vehicles).forEach((key, index) => {
			if (this.state.vehicles[key]) {
				nextProps.vehicles[key].forEach((nextVehicle) => {
					this.state.vehicles[key].forEach((currVehicle) => {
						if (nextVehicle.id === currVehicle.id &&
							(nextVehicle.lat !== currVehicle.lat || nextVehicle.lon !== currVehicle.lon)) {
							// Animate vehicle movement
							currVehicle.animated.timing({
								latitude: nextVehicle.lat,
								longitude: nextVehicle.lon,
								duration: 500
							}).start();
						}
					});
				});
			} else {
				// Make Animated values
				nextProps.vehicles[key].forEach((nextVehicle) => {
					nextVehicle.animated = new MapView.AnimatedRegion({
						latitude: nextVehicle.lat,
						longitude: nextVehicle.lon,
					});
				});

				const newVehicles = this.state.vehicles;
				newVehicles[key] = nextProps.vehicles[key];

				this.setState({
					vehicles: newVehicles
				});
			}
		});

		if (this.state.iconStatus === 'load' && nextProps.search_results) {
			this.setState({
				iconStatus: 'search'
			});
		}
	}

	shouldComponentUpdate(nextProps, nextState) {
		// Don't re-render if location hasn't changed
		if (((this.props.location.coords.latitude !== nextProps.location.coords.latitude) ||
			(this.props.location.coords.longitude !== nextProps.location.coords.longitude)) ||
			this.state !== nextState ||
			this.props.search_results !== nextProps.search_results) {
			/*
			(this.state.selectedResult !== nextState.selectedResult) ||
			(this.state.iconStatus !== nextState.iconStatus) ||
			(this.state.showBar !== nextState.showBar) ||
			(this.state.showMenu !== nextState.showMenu) ||
			(this.state.route1 !== nextState.route1)) {*/

			return true;
		} else {
			return false;
		}
	}

	componentWillUnmount() {
		BackAndroid.removeEventListener('hardwareBackPress', this.pressIcon);
		clearTimeout(this.timer);
	}

	pressIcon = () => {
		if (this.state.iconStatus === 'back') {
			this.setState({
				iconStatus: 'search',
				showBar: (this.props.search_results !== null),
				showShuttle: true,
				showNav: true,
			});
			this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
			// this.barRef.clear();
			this.barRef.blur();
			return true;
		} else {
			return false;
		}
	}

	gotoResults = () => {
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false
		});
		this.scrollRef.scrollTo({ x: 0, y: deviceHeight - 64 - statusBarHeight, animated: true });
	}

	focusSearch = () => {
		this.scrollRef.scrollTo({ x: 0, y: 2 * (deviceHeight - 64 - statusBarHeight), animated: true });
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false,
		});
	}

	gotoShuttleSettings = () => {
		this.setState({
			iconStatus: 'back',
			showBar: false,
			showShuttle: false,
			showNav: false,
		});
		this.scrollRef.scrollTo({ x: 0, y: 3 * (deviceHeight - 64 - statusBarHeight), animated: true });
	}

	updateSearch = (text) => {
		this.props.fetchSearch(text);
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
		this.barRef.blur();

		this.setState({
			searchInput: text,
			showBar: true,
			iconStatus: 'load',
			showShuttle: true,
			showNav: true,
			selectedResult: 0
		});

		this.timer = setTimeout(this.searchTimeout, 5000);
	}

	searchTimeout = () => {
		if (!this.props.search_results) {
			this.setState({
				searchInput: 'No Results Found',
				iconStatus: 'search'
			});
		}
	}

	updateSearchSuggest = (text) => {
		this.props.fetchSearch(text, this.props.location);
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
		this.barRef.blur();

		this.setState({
			searchInput: text,
			showBar: true,
			iconStatus: 'load',
			showShuttle: true,
			showNav: true,
			selectedResult: 0
		});
	}

	updateSelectedResult = (index) => {
		const newSelect = index;
		this.setState({
			iconStatus: 'search',
			selectedResult: newSelect,
			showBar: true,
			showShuttle: true,
			showNav: true,
		});
		this.scrollRef.scrollTo({ x: 0, y: 0, animated: true });
	}

	toggleRoute = (value, route) => {
		this.props.toggle(route);

		const vehicles = this.state.vehicles;
		delete vehicles[route];

		this.setState({
			toggled: !this.state.toggled,
			vehicles });
	}

	render() {
		if (this.props.location.coords) {
			return (
				<View style={css.main_container}>
					{
						(this.props.search_results && this.state.showNav) ? (
							<ElevatedView
								style={{ zIndex: 2, justifyContent: 'center', alignItems: 'center', position: 'absolute', bottom: (2 * (6 + Math.round(44 * getPRM()))) + 12, right: 6, width: 50, height: 50, borderRadius: 50 / 2, backgroundColor: '#2196F3' }}
								elevation={2} // zIndex style and elevation has to match
							>
								<TouchableOpacity
									onPress={() => gotoNavigationApp(this.props.search_results[this.state.selectedResult].mkrLat, this.props.search_results[this.state.selectedResult].mkrLong)}
								>
									<Icon name={'location-arrow'} size={20} color={'white'} />
								</TouchableOpacity>
							</ElevatedView>
						) : (<ElevatedView />) // Android bug - breaks view if this is null...on RN39...check if this bug still exists in RN40 or if this can be changed to null
					}
					{
						(this.state.showShuttle) ? (
							<ElevatedView
								style={{ zIndex: 2, justifyContent: 'center', alignItems: 'center', position: 'absolute', bottom: 6 + Math.round(44 * getPRM()), right: 6, width: 50, height: 50, borderRadius: 50 / 2, backgroundColor: '#346994' }}
								elevation={2} // zIndex style and elevation has to match
							>
								<TouchableOpacity
									onPress={this.gotoShuttleSettings}
								>
									<Icon name={'bus'} size={20} color={'white'} />
								</TouchableOpacity>
							</ElevatedView>
						) : (<ElevatedView />) // Android bug
					}
					<SearchBar
						update={this.updateSearch}
						onFocus={this.focusSearch}
						pressIcon={this.pressIcon}
						iconStatus={this.state.iconStatus}
						searchInput={this.state.searchInput}
						reff={
							(ref) => { this.barRef = ref; }
						}
					/>
					<ScrollView
						ref={
							(ref) => {
								this.scrollRef = ref;
							}
						}
						showsVerticalScrollIndicator={false}
						scrollEnabled={this.state.allowScroll}
					>
						<View
							style={styles.section}
						>
							<SearchMap
								location={this.props.location}
								selectedResult={
									(this.props.search_results) ? (
										this.props.search_results[this.state.selectedResult]
									) : null
								}
								shuttle={this.props.shuttle_stops}
								vehicles={this.state.vehicles}
							/>
						</View>
						<View
							style={styles.section}
						>
							<SearchResults
								results={this.props.search_results}
								onSelect={(index) => this.updateSelectedResult(index)}
							/>
						</View>
						<View
							style={styles.section}
						>
							<SearchSuggest
								onPress={this.updateSearchSuggest}
							/>
							{(this.props.search_history.length !== 0) ? (
								<SearchHistoryCard
									pressHistory={this.updateSearch}
									data={this.props.search_history}
								/>
								) : (null)}
						</View>
						<View
							style={styles.section}
						>
							<SearchShuttleMenu
								shuttle_routes={this.props.shuttle_routes}
								onToggle={this.toggleRoute}
								toggles={this.props.toggles}
							/>
						</View>
					</ScrollView>
					{(this.state.showBar && this.props.search_results) ? (
						<ElevatedView
							style={styles.bottomBarContainer}
							elevation={5}
						>
							<TouchableOpacity
								style={styles.bottomBarContent}
								onPress={
									this.gotoResults
								}
							>
								<Text
									style={styles.bottomBarText}
								>
									See More Results
								</Text>
							</TouchableOpacity>
						</ElevatedView>
						) : (null)
					}
					<ShuttleLocationContainer />
				</View>
			);
		} else {
			return null;
		}
	}
}

const mapStateToProps = (state, props) => (
	{
		location: state.location.position,
		locationPermission: state.location.permission,
		toggles: state.shuttle.toggles,
		shuttle_routes: state.shuttle.routes,
		shuttle_stops: state.shuttle.stops,
		vehicles: state.shuttle.vehicles,
		search_history: state.map.history,
		search_results: state.map.results
	}
);

const mapDispatchToProps = (dispatch, ownProps) => (
	{
		fetchSearch: (term, location) => {
			dispatch(fetchSearch(term, location));
		},
		toggle: (route) => {
			dispatch(toggleRoute(route));
		}
	}
);

module.exports = connect(mapStateToProps, mapDispatchToProps)(NearbyMapView);

const navMargin = Platform.select({
	ios: 64,
	android: 0
});

const styles = StyleSheet.create({
	main_container: { width: deviceWidth, height: deviceHeight - 64 - statusBarHeight, backgroundColor: '#EAEAEA', marginTop: navMargin },
	bottomBarContainer: { zIndex: 5, alignItems: 'center', justifyContent: 'center', position: 'absolute', bottom: 0, width: deviceWidth, height: Math.round(44 * getPRM()), borderWidth: 0, backgroundColor: 'white', },
	bottomBarContent: { flex: 1, justifyContent: 'center', alignSelf: 'stretch' },
	bottomBarText: { textAlign: 'center', },

	section: { height: deviceHeight - 64 - statusBarHeight },
});

<|endoftext|>


<commit_before>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2010 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('mirosubs.video.AbstractVideoPlayer');

/**
 * Abstract base class for video player implementations. Any video player
 * used with MiroSubs must implement the abstract methods defined by this
 * class.
 * @constructor
 */
mirosubs.video.AbstractVideoPlayer = function(videoSource) {
    goog.ui.Component.call(this);
    this.videoSource_ = videoSource;
    this.noUpdateEvents_ = false;
    this.dimensionsKnown_ = false;
    this.isLoadingStopped_ = false;
    /**
     * If the video's loading is stopped (i.e., it is no longer buffering),
     * we may have had to alter the video source or playhead time to force
     * the buffering to actually cease.  While stopped, this variable will
     * store the playhead time we were previously at, so it can be restored.
     * @type{number}
     */
    this.storedPlayheadTime_ = 0;
    mirosubs.video.AbstractVideoPlayer.players.push(this);
};
goog.inherits(mirosubs.video.AbstractVideoPlayer, goog.ui.Component);
mirosubs.video.AbstractVideoPlayer.PROGRESS_INTERVAL = 500;
mirosubs.video.AbstractVideoPlayer.TIMEUPDATE_INTERVAL = 80;

mirosubs.video.AbstractVideoPlayer.logger_ =
    goog.debug.Logger.getLogger('AbstractVideoPlayer');

mirosubs.video.AbstractVideoPlayer.players = [];

/**
 *
 * Used for flash-based video players that don't have a size specified.
 */
mirosubs.video.AbstractVideoPlayer.DEFAULT_SIZE = new goog.math.Size(480, 360);
/**
 *
 * Used for all video players in the dialog.
 */
mirosubs.video.AbstractVideoPlayer.DIALOG_SIZE = new goog.math.Size(400, 300);

mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadFn = function() {
    return goog.bind(this.getPlayheadTime, this);
};
mirosubs.video.AbstractVideoPlayer.prototype.isPaused = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if paused, loading is stopped";
    return this.isPausedInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.isPausedInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.isPlaying = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if playing, loading is stopped";
    return this.isPlayingInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.isPlayingInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.videoEnded = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if video ended, loading is stopped";
    return this.videoEndedInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.videoEndedInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.play = function(opt_suppressEvent) {
    if (this.isLoadingStopped_)
	throw new "can't play, loading is stopped";
    if (!opt_suppressEvent)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.PLAY_CALLED);
    this.playInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.playInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.pause = function(opt_suppressEvent) {
    if (this.isLoadingStopped_)
	throw new "can't pause, loading is stopped";
    if (!opt_suppressEvent)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.PAUSE_CALLED);
    this.pauseInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.pauseInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.togglePause = function() {
    if (this.isLoadingStopped_)
	throw new "can't toggle pause, loading is stopped";

    if (!this.isPlaying())
        this.play();
    else
        this.pause();
};
mirosubs.video.AbstractVideoPlayer.prototype.isLoadingStopped = function() {
  return this.isLoadingStopped_;
};
/**
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.setLoadingStopped = function(isLoadingStopped) {
  this.isLoadingStopped_ = isLoadingStopped;
};
mirosubs.video.AbstractVideoPlayer.prototype.stopLoading = function() {
    if (!this.isLoadingStopped_) {
	this.pause();
	this.storedPlayheadTime_ = this.getPlayheadTime();
	if (this.stopLoadingInternal()) {
	    this.isLoadingStopped_ = true;
	}
    }
};
mirosubs.video.AbstractVideoPlayer.prototype.stopLoadingInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.resumeLoading = function() {
    if (this.isLoadingStopped_) {
	this.resumeLoadingInternal(this.storedPlayheadTime_);
	this.storedPlayheadTime_ = null;
    }
};
mirosubs.video.AbstractVideoPlayer.prototype.resumeLoadingInternal = function(playheadTime) {
    goog.abstractMethod();
};
/**
 * @protected
 * Must be called by subclasses once they know their dimensions.
 */
mirosubs.video.AbstractVideoPlayer.prototype.setDimensionsKnownInternal = function() {
    this.dimensionsKnown_ = true;
    this.dispatchEvent(
        mirosubs.video.AbstractVideoPlayer.EventType.DIMENSIONS_KNOWN);
};
mirosubs.video.AbstractVideoPlayer.prototype.areDimensionsKnown = function() {
    return this.dimensionsKnown_;
};
mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadTime = function() {
    if (this.isLoadingStopped_)
	return this.storedPlayheadTime_;
    return this.getPlayheadTimeInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadTimeInternal = goog.abstractMethod;
/**
 *
 *
 */
mirosubs.video.AbstractVideoPlayer.prototype.playWithNoUpdateEvents =
    function(timeToStart, secondsToPlay)
{
    this.noUpdateEvents_ = true;
    if (this.noUpdatePreTime_ == null)
        this.noUpdatePreTime_ = this.getPlayheadTime();
    this.setPlayheadTime(timeToStart);
    this.play();
    this.noUpdateStartTime_ = timeToStart;
    this.noUpdateEndTime_ = timeToStart + secondsToPlay;
};
/**
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.dispatchEndedEvent = function() {
    this.dispatchEvent(mirosubs.video.AbstractVideoPlayer.EventType.PLAY_ENDED);
};
mirosubs.video.AbstractVideoPlayer.prototype.sendTimeUpdateInternal =
    function()
{
    if (!this.noUpdateEvents_)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.TIMEUPDATE);
    else {
        if (this.ignoreTimeUpdate_)
            return;
        if (this.getPlayheadTime() >= this.noUpdateEndTime_) {
            this.ignoreTimeUpdate_ = true;
            this.setPlayheadTime(this.noUpdatePreTime_);
            this.noUpdatePreTime_ = null;
            this.pause();
            this.ignoreTimeUpdate_ = false;
            this.noUpdateEvents_ = false;
        }
    }
}
/**
 * @returns {number} video duration in seconds. Returns 0 if duration isn't
 *     available yet.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getDuration = goog.abstractMethod;
/**
 * @returns {int} Number of buffered ranges.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedLength = goog.abstractMethod;
/**
 * @returns {number} Start of buffered range with index
 */
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedStart = function(index) {
    goog.abstractMethod();
};
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedEnd = function(index) {
    goog.abstractMethod();
};
/**
 * @return {number} 0.0 to 1.0
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVolume = goog.abstractMethod;
/**
 * @return {Element} The video element. Used to check to see if a video on the
 *     page has been wrapped in a player yet or not.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVideoElement = goog.abstractMethod;

/**
 *
 * @param {number} volume A number between 0.0 and 1.0
 */
mirosubs.video.AbstractVideoPlayer.prototype.setVolume = function(volume) {
    goog.abstractMethod();
};
mirosubs.video.AbstractVideoPlayer.prototype.getVideoSource = function() {
    return this.videoSource_;
};
mirosubs.video.AbstractVideoPlayer.prototype.setPlayheadTime = function(playheadTime) {
    goog.abstractMethod();
};
/**
 *
 * @param {String} text Caption text to display in video. null for blank.
 */
mirosubs.video.AbstractVideoPlayer.prototype.showCaptionText = function(text) {
    mirosubs.video.AbstractVideoPlayer.logger_.info(
        'showing sub: ' + text);
    if (text == null || text == "") {
        if (this.captionElem_ != null) {
            goog.dom.removeNode(this.captionElem_);
            this.captionElem_ = null;
            if (this.captionBgElem_ != null) {
                goog.dom.removeNode(this.captionBgElem_);
                this.captionBgElem_ = null;
            }
        }
    }
    else
        this.setCaption_(
            goog.string.newLineToBr(goog.string.htmlEscape(text)),
            this.needsIFrame());
};

mirosubs.video.AbstractVideoPlayer.prototype.setCaption_ =
    function(htmlText, needsIFrame)
{
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var $s = goog.style;
    var videoOffsetParent = $s.getOffsetParent(this.getElement());
    if (!videoOffsetParent)
        videoOffsetParent = goog.dom.getOwnerDocument(this.getElement()).body;
    var offsetPosition = $s.getPosition(this.getElement());
    var size = $s.getSize(this.getElement());
    if (!this.captionElem_) {
        this.captionElem_ = $d('div', 'mirosubs-captionDiv');
        if (needsIFrame)
            mirosubs.setVisibility(this.captionElem_, false);
        goog.dom.appendChild(videoOffsetParent, this.captionElem_);
    }
    this.captionElem_.innerHTML = htmlText;
    var captionSize = goog.style.getSize(this.captionElem_);
    var captionPosition = new goog.math.Coordinate(
        offsetPosition.x + (size.width - captionSize.width) / 2,
        offsetPosition.y + size.height - captionSize.height - 40);
    goog.style.setPosition(this.captionElem_, captionPosition);
    if (needsIFrame) {
        if (!this.captionBgElem_) {
            this.captionBgElem_ = $d('iframe', 'mirosubs-captionDivBg');
            mirosubs.setVisibility(this.captionBgElem_, false);
            this.getDomHelper().insertSiblingBefore(
                this.captionBgElem_, this.captionElem_);
        }
        $s.setPosition(this.captionBgElem_, captionPosition);
        $s.setSize(this.captionBgElem_, captionSize);
        mirosubs.setVisibility(this.captionBgElem_, true);
        mirosubs.setVisibility(this.captionElem_, true);
    }
};

/**
 * Override for video players that need to show an iframe behind captions for
 * certain browsers (e.g. flash on linux/ff)
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.needsIFrame = function() {
    return false;
};
/**
 *
 * @protected
 * @return {goog.math.Size} size of the video
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVideoSize = goog.abstractMethod;

/**
 * Video player events
 * @enum {string}
 */
mirosubs.video.AbstractVideoPlayer.EventType = {
    /** dispatched when playback starts or resumes. */
    PLAY : 'videoplay',
    PLAY_CALLED : 'videoplaycalled',
    /** dispatched when the video finishes playing. */
    PLAY_ENDED : 'videoplayended',
    /** dispatched when playback is paused. */
    PAUSE : 'videopause',
    PAUSE_CALLED : 'videopausecalled',
    /** dispatched when media data is fetched */
    PROGRESS : 'videoprogress',
    /**
     * dispatched when playhead time changes, either as a result
     *  of normal playback or otherwise.
     */
    TIMEUPDATE : 'videotimeupdate',
    /**
     * dispatched when video dimensions are known.
     */
    DIMENSIONS_KNOWN: 'dimensionsknown'
};
<commit_msg>No longer using goog.style.getOffsetParent to get offsetParent for video element. See http://bugzilla.pculture.org/show_bug.cgi?id=15221<commit_after>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2010 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('mirosubs.video.AbstractVideoPlayer');

/**
 * Abstract base class for video player implementations. Any video player
 * used with MiroSubs must implement the abstract methods defined by this
 * class.
 * @constructor
 */
mirosubs.video.AbstractVideoPlayer = function(videoSource) {
    goog.ui.Component.call(this);
    this.videoSource_ = videoSource;
    this.noUpdateEvents_ = false;
    this.dimensionsKnown_ = false;
    this.isLoadingStopped_ = false;
    /**
     * If the video's loading is stopped (i.e., it is no longer buffering),
     * we may have had to alter the video source or playhead time to force
     * the buffering to actually cease.  While stopped, this variable will
     * store the playhead time we were previously at, so it can be restored.
     * @type{number}
     */
    this.storedPlayheadTime_ = 0;
    mirosubs.video.AbstractVideoPlayer.players.push(this);
};
goog.inherits(mirosubs.video.AbstractVideoPlayer, goog.ui.Component);
mirosubs.video.AbstractVideoPlayer.PROGRESS_INTERVAL = 500;
mirosubs.video.AbstractVideoPlayer.TIMEUPDATE_INTERVAL = 80;

mirosubs.video.AbstractVideoPlayer.logger_ =
    goog.debug.Logger.getLogger('AbstractVideoPlayer');

mirosubs.video.AbstractVideoPlayer.players = [];

/**
 *
 * Used for flash-based video players that don't have a size specified.
 */
mirosubs.video.AbstractVideoPlayer.DEFAULT_SIZE = new goog.math.Size(480, 360);
/**
 *
 * Used for all video players in the dialog.
 */
mirosubs.video.AbstractVideoPlayer.DIALOG_SIZE = new goog.math.Size(400, 300);

mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadFn = function() {
    return goog.bind(this.getPlayheadTime, this);
};
mirosubs.video.AbstractVideoPlayer.prototype.isPaused = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if paused, loading is stopped";
    return this.isPausedInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.isPausedInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.isPlaying = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if playing, loading is stopped";
    return this.isPlayingInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.isPlayingInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.videoEnded = function() {
    if (this.isLoadingStopped_)
	throw new "can't check if video ended, loading is stopped";
    return this.videoEndedInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.videoEndedInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.play = function(opt_suppressEvent) {
    if (this.isLoadingStopped_)
	throw new "can't play, loading is stopped";
    if (!opt_suppressEvent)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.PLAY_CALLED);
    this.playInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.playInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.pause = function(opt_suppressEvent) {
    if (this.isLoadingStopped_)
	throw new "can't pause, loading is stopped";
    if (!opt_suppressEvent)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.PAUSE_CALLED);
    this.pauseInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.pauseInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.togglePause = function() {
    if (this.isLoadingStopped_)
	throw new "can't toggle pause, loading is stopped";

    if (!this.isPlaying())
        this.play();
    else
        this.pause();
};
mirosubs.video.AbstractVideoPlayer.prototype.isLoadingStopped = function() {
  return this.isLoadingStopped_;
};
/**
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.setLoadingStopped = function(isLoadingStopped) {
  this.isLoadingStopped_ = isLoadingStopped;
};
mirosubs.video.AbstractVideoPlayer.prototype.stopLoading = function() {
    if (!this.isLoadingStopped_) {
	this.pause();
	this.storedPlayheadTime_ = this.getPlayheadTime();
	if (this.stopLoadingInternal()) {
	    this.isLoadingStopped_ = true;
	}
    }
};
mirosubs.video.AbstractVideoPlayer.prototype.stopLoadingInternal = goog.abstractMethod;
mirosubs.video.AbstractVideoPlayer.prototype.resumeLoading = function() {
    if (this.isLoadingStopped_) {
	this.resumeLoadingInternal(this.storedPlayheadTime_);
	this.storedPlayheadTime_ = null;
    }
};
mirosubs.video.AbstractVideoPlayer.prototype.resumeLoadingInternal = function(playheadTime) {
    goog.abstractMethod();
};
/**
 * @protected
 * Must be called by subclasses once they know their dimensions.
 */
mirosubs.video.AbstractVideoPlayer.prototype.setDimensionsKnownInternal = function() {
    this.dimensionsKnown_ = true;
    this.dispatchEvent(
        mirosubs.video.AbstractVideoPlayer.EventType.DIMENSIONS_KNOWN);
};
mirosubs.video.AbstractVideoPlayer.prototype.areDimensionsKnown = function() {
    return this.dimensionsKnown_;
};
mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadTime = function() {
    if (this.isLoadingStopped_)
	return this.storedPlayheadTime_;
    return this.getPlayheadTimeInternal();
};
mirosubs.video.AbstractVideoPlayer.prototype.getPlayheadTimeInternal = goog.abstractMethod;
/**
 *
 *
 */
mirosubs.video.AbstractVideoPlayer.prototype.playWithNoUpdateEvents =
    function(timeToStart, secondsToPlay)
{
    this.noUpdateEvents_ = true;
    if (this.noUpdatePreTime_ == null)
        this.noUpdatePreTime_ = this.getPlayheadTime();
    this.setPlayheadTime(timeToStart);
    this.play();
    this.noUpdateStartTime_ = timeToStart;
    this.noUpdateEndTime_ = timeToStart + secondsToPlay;
};
/**
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.dispatchEndedEvent = function() {
    this.dispatchEvent(mirosubs.video.AbstractVideoPlayer.EventType.PLAY_ENDED);
};
mirosubs.video.AbstractVideoPlayer.prototype.sendTimeUpdateInternal =
    function()
{
    if (!this.noUpdateEvents_)
        this.dispatchEvent(
            mirosubs.video.AbstractVideoPlayer.EventType.TIMEUPDATE);
    else {
        if (this.ignoreTimeUpdate_)
            return;
        if (this.getPlayheadTime() >= this.noUpdateEndTime_) {
            this.ignoreTimeUpdate_ = true;
            this.setPlayheadTime(this.noUpdatePreTime_);
            this.noUpdatePreTime_ = null;
            this.pause();
            this.ignoreTimeUpdate_ = false;
            this.noUpdateEvents_ = false;
        }
    }
}
/**
 * @returns {number} video duration in seconds. Returns 0 if duration isn't
 *     available yet.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getDuration = goog.abstractMethod;
/**
 * @returns {int} Number of buffered ranges.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedLength = goog.abstractMethod;
/**
 * @returns {number} Start of buffered range with index
 */
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedStart = function(index) {
    goog.abstractMethod();
};
mirosubs.video.AbstractVideoPlayer.prototype.getBufferedEnd = function(index) {
    goog.abstractMethod();
};
/**
 * @return {number} 0.0 to 1.0
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVolume = goog.abstractMethod;
/**
 * @return {Element} The video element. Used to check to see if a video on the
 *     page has been wrapped in a player yet or not.
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVideoElement = goog.abstractMethod;

/**
 *
 * @param {number} volume A number between 0.0 and 1.0
 */
mirosubs.video.AbstractVideoPlayer.prototype.setVolume = function(volume) {
    goog.abstractMethod();
};
mirosubs.video.AbstractVideoPlayer.prototype.getVideoSource = function() {
    return this.videoSource_;
};
mirosubs.video.AbstractVideoPlayer.prototype.setPlayheadTime = function(playheadTime) {
    goog.abstractMethod();
};
/**
 *
 * @param {String} text Caption text to display in video. null for blank.
 */
mirosubs.video.AbstractVideoPlayer.prototype.showCaptionText = function(text) {
    mirosubs.video.AbstractVideoPlayer.logger_.info(
        'showing sub: ' + text);
    if (text == null || text == "") {
        if (this.captionElem_ != null) {
            goog.dom.removeNode(this.captionElem_);
            this.captionElem_ = null;
            if (this.captionBgElem_ != null) {
                goog.dom.removeNode(this.captionBgElem_);
                this.captionBgElem_ = null;
            }
        }
    }
    else
        this.setCaption_(
            goog.string.newLineToBr(goog.string.htmlEscape(text)),
            this.needsIFrame());
};

mirosubs.video.AbstractVideoPlayer.prototype.setCaption_ =
    function(htmlText, needsIFrame)
{
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var $s = goog.style;
    var videoOffsetParent = this.getElement().offsetParent;
    if (!videoOffsetParent)
        videoOffsetParent = goog.dom.getOwnerDocument(this.getElement()).body;
    var offsetPosition = $s.getPosition(this.getElement());
    var size = $s.getSize(this.getElement());
    if (!this.captionElem_) {
        this.captionElem_ = $d('div', 'mirosubs-captionDiv');
        if (needsIFrame)
            mirosubs.setVisibility(this.captionElem_, false);
        goog.dom.appendChild(videoOffsetParent, this.captionElem_);
    }
    this.captionElem_.innerHTML = htmlText;
    var captionSize = goog.style.getSize(this.captionElem_);
    var captionPosition = new goog.math.Coordinate(
        offsetPosition.x + (size.width - captionSize.width) / 2,
        offsetPosition.y + size.height - captionSize.height - 40);
    goog.style.setPosition(this.captionElem_, captionPosition);
    if (needsIFrame) {
        if (!this.captionBgElem_) {
            this.captionBgElem_ = $d('iframe', 'mirosubs-captionDivBg');
            mirosubs.setVisibility(this.captionBgElem_, false);
            this.getDomHelper().insertSiblingBefore(
                this.captionBgElem_, this.captionElem_);
        }
        $s.setPosition(this.captionBgElem_, captionPosition);
        $s.setSize(this.captionBgElem_, captionSize);
        mirosubs.setVisibility(this.captionBgElem_, true);
        mirosubs.setVisibility(this.captionElem_, true);
    }
};

/**
 * Override for video players that need to show an iframe behind captions for
 * certain browsers (e.g. flash on linux/ff)
 * @protected
 */
mirosubs.video.AbstractVideoPlayer.prototype.needsIFrame = function() {
    return false;
};
/**
 *
 * @protected
 * @return {goog.math.Size} size of the video
 */
mirosubs.video.AbstractVideoPlayer.prototype.getVideoSize = goog.abstractMethod;

/**
 * Video player events
 * @enum {string}
 */
mirosubs.video.AbstractVideoPlayer.EventType = {
    /** dispatched when playback starts or resumes. */
    PLAY : 'videoplay',
    PLAY_CALLED : 'videoplaycalled',
    /** dispatched when the video finishes playing. */
    PLAY_ENDED : 'videoplayended',
    /** dispatched when playback is paused. */
    PAUSE : 'videopause',
    PAUSE_CALLED : 'videopausecalled',
    /** dispatched when media data is fetched */
    PROGRESS : 'videoprogress',
    /**
     * dispatched when playhead time changes, either as a result
     *  of normal playback or otherwise.
     */
    TIMEUPDATE : 'videotimeupdate',
    /**
     * dispatched when video dimensions are known.
     */
    DIMENSIONS_KNOWN: 'dimensionsknown'
};
<|endoftext|>


<commit_before>lingcod.Manipulator = function(gex, form, render_target, div){

    this.altitude = 100;
    var json = false;
    var data = form.find('.json').html();
    if(data){
        json = JSON.parse(data);
    }
    this.needed = true;
    // Return false if manipulations are not needed, else proceed.
    if(!json || !json.manipulators){
        this.needed = false;
        return false;
    }
    this.div = div;
    this.shape_;
    this.manipulators_ = json.manipulators;
    this.gex_ = gex;
    this.form_ = form;
    this.render_target_ = render_target;

    // Fill in the form with content from map.html
    this.render_target_.html($('#geopanel').html());

    var self = this;

    // Setup event listeners
    this.render_target_.find('.draw_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.drawNewShape_();
        }
    });

    this.render_target_.find('div.manipulated .edit_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.editExistingShape_();
        }
    });

    this.render_target_.find('.done_editing').click(function(){
        self.render_target_.find('.done_editing').addClass('disabled');
        self.finishedEditingCallback_();
    });

    this.render_target_.find('div.edit .edit_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.editExistingShape_();
        }
    });

    // Figure out if there is an existing shape in the form, or if a new one
    // needs to be drawn
    if(this.form_.find('#id_geometry_orig').val()){
        this.enterExistingShapeState_();
    }else{
        this.enterNewState_();
    }
}

lingcod.Manipulator.prototype.drawNewShape_ = function(){
    this.is_defining_shape_ = true;
    this.is_defining_new_shape_ = true;
    this.addNewShape_();
    var bounds = this.shape_.getGeometry().getOuterBoundary();
    var self = this;
    this.gex_.edit.drawLineString(bounds, {
        bounce: false,
        finishCallback: function(){
            self.finishedEditingCallback_();
        },
        drawCallback: function(i){
            var coords = bounds.getCoordinates();
            var coord = coords.get(i);
            coord.setAltitude(self.altitude);
            coords.set(i, coord);
        },
        ensureCounterClockwise: false
    });
}

lingcod.Manipulator.prototype.addNewShape_ = function(kml){
    this.clearShape_();
    if(kml){
        this.shape_ = this.gex_.pluginInstance.parseKml(kml);
        this.gex_.pluginInstance.getFeatures().appendChild(this.shape_);
    }else{
        this.shape_ = this.gex_.dom.addPlacemark({
            visibility: true,
            polygon: [],
            style: {
                line: { width: 2, color: 'ffffffff' },
                poly: { color: '8000ff00' }
            }
        });
        this.setZ(this.shape_, this.altitude);
    }
    return this.shape_;
}

lingcod.Manipulator.prototype.setZ = function(kmlObject, z){
    var geo = kmlObject.getGeometry();
    geo.setAltitudeMode(ge.ALTITUDE_ABSOLUTE);
    geo.setExtrude(true);
    var coords = geo.getOuterBoundary().getCoordinates();
    var length = coords.getLength();
    for(var i =0; i<length;i++){
        var coord = coords.get(i)
        coord.setAltitude(z);
        coords.set(i, coord);
    }
    return kmlObject;
}

lingcod.Manipulator.prototype.finishedEditingCallback_ = function(){
    var self = this;
    this.process(this.shape_.getKml(), this.manipulators_, function(data){
        if(data.success === '1'){
            var kmlObject = self.addNewShape_(data.final_shape_kml);
            self.gex_.util.flyToObject(kmlObject, {
                boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            self.setGeometryFields_(data.user_shape, data.submitted, data.final_shape, data.final_shape_kml);
            self.enterManipulatedState_(data.html, true);
        }else{
            self.setGeometryFields_('', data.submitted, '', '');
            self.addNewShape_(data.submitted);
            self.gex_.util.flyToObject(self.shape_, {
                boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            self.enterManipulatedState_(data.html, false);
        }
    });
}

lingcod.Manipulator.prototype.setGeometryFields_ = function(original_wkt, original_kml, final_wkt, final_kml){
    this.form_.find('#id_geometry_orig').val(original_wkt);
    // Replacing the entire script tag rather than just changing the contents
    // for the benefit of IE8 (damn IE 8 !!)
    $('#geometry_final_kml').replaceWith('<script id="geometry_final_kml" type="application/vnd.google-earth.kml+xml">'+final_kml+'</script>');
    $('#geometry_orig_kml').replaceWith('<script id="geometry_orig_kml" type="application/vnd.google-earth.kml+xml">'+original_kml+'</script>');
}

lingcod.Manipulator.prototype.hideStates_ = function(){
    this.render_target_.find('div.new, div.edit, div.manipulated, div.editing').hide();
}

lingcod.Manipulator.prototype.enterManipulatedState_ = function(html, success){
    this.hideStates_();
    if(success === true){
        this.render_target_.find('div.manipulated').removeClass('error');
        this.is_invalid_geometry = false;
        this.is_defining_shape_ = false;
        this.is_defining_new_shape_ = false;
    }else{
        this.is_invalid_geometry = true;
        this.render_target_.find('div.manipulated').addClass('error');
    }
    this.render_target_.find('div.manipulated a.edit_shape').removeClass('disabled');
    this.render_target_.find('div.manipulated').show().find('>p')
        .html(html);
}

lingcod.Manipulator.prototype.isInvalidGeometry = function(){
    return this.is_invalid_geometry;
}

lingcod.Manipulator.prototype.enterNewState_ = function(){
    this.hideStates_();
    // this.is_defining_shape_ = true;
    this.render_target_.find('div.new').show();
    this.render_target_.find('a.draw_shape').removeClass('disabled');
}

lingcod.Manipulator.prototype.isDefiningNewShape = function(){
    return this.is_defining_new_shape_;
}

lingcod.Manipulator.prototype.enterEditingState_ = function(){
    this.hideStates_();
    this.is_invalid_geometry = false;
    this.is_defining_shape_ = true;
    this.is_defining_new_shape_ = false;
    this.render_target_.find('.done_editing').removeClass('disabled');
    this.render_target_.find('div.editing').show();
}

lingcod.Manipulator.prototype.enterExistingShapeState_ = function(){
    var self = this;
    this.hideStates_();
    this.is_defining_shape = false;
    this.render_target_.find('div.edit .edit_shape').removeClass('disabled');
    this.render_target_.find('div.edit').show();
    var kml = jQuery.trim($('#geometry_final_kml').html());
    if(!kml){
        var kml = jQuery.trim($('#geometry_orig_kml').html());
        this.process(kml, this.manipulators_, function(data){
            if(data.success === '1'){
                var kmlObject = self.addNewShape_(data.final_shape_kml);
                self.gex_.util.flyToObject(kmlObject, {
                    boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            }else{
                // do nothing
            }
        });
    }
    this.addNewShape_(kml);
    this.gex_.util.flyToObject(this.shape_, {
        boundsFallback: true, aspectRatio: $(this.div).width() / $(this.div).height()});
    var self = this;
}

lingcod.Manipulator.prototype.isShapeDefined = function(){
    return !!this.form_.find('#id_geometry_orig').val();
}

lingcod.Manipulator.prototype.isDefiningShape = function(){
    return this.is_defining_shape_;
}

lingcod.Manipulator.prototype.process = function(wkt, url, callback){
    $(this).trigger('processing');
	var self = this;
    $.ajax({
        url: url,
        type: 'POST',
        data: { target_shape: wkt },
        success: function(data, status){
            $(self).trigger('doneprocessing');
            if(status === 'success'){
                callback(JSON.parse(data));
            }else{
                $(self).trigger('error', "There was an error processing your shape.");
                callback({success: false, html: $('#manipulators_server_error')});
            }
        },
        error: function(data, status){
            $(self).trigger('doneprocessing');
            $(self).trigger('error', 'There was an error processing your shape.');
            callback({success: false, html: $('#manipulators_server_error').html()});
        }
    });
}

lingcod.Manipulator.prototype.editExistingShape_ = function(){
    var kml = jQuery.trim($('#geometry_orig_kml').html());
    this.addNewShape_(kml);
    this.gex_.util.flyToObject(this.shape_, {
        boundsFallback: true, aspectRatio: $(this.div).width() / $(this.div).height()});
    this.gex_.edit.editLineString(this.shape_.getGeometry().getOuterBoundary());
    this.enterEditingState_();

}

lingcod.Manipulator.prototype.clearShape_ = function(){
    if(this.shape_ && this.shape_.getParentNode()){
        this.gex_.edit.endEditLineString(this.shape_.getGeometry().getOuterBoundary());
        gex.dom.removeObject(this.shape_);
        this.shape_ = false;
    }
}

lingcod.Manipulator.prototype.destroy = function(){
    this.clearShape_();
}<commit_msg>changed argument name from wkt to kml to reflect what it really accepts<commit_after>lingcod.Manipulator = function(gex, form, render_target, div){

    this.altitude = 100;
    var json = false;
    var data = form.find('.json').html();
    if(data){
        json = JSON.parse(data);
    }
    this.needed = true;
    // Return false if manipulations are not needed, else proceed.
    if(!json || !json.manipulators){
        this.needed = false;
        return false;
    }
    this.div = div;
    this.shape_;
    this.manipulators_ = json.manipulators;
    this.gex_ = gex;
    this.form_ = form;
    this.render_target_ = render_target;

    // Fill in the form with content from map.html
    this.render_target_.html($('#geopanel').html());

    var self = this;

    // Setup event listeners
    this.render_target_.find('.draw_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.drawNewShape_();
        }
    });

    this.render_target_.find('div.manipulated .edit_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.editExistingShape_();
        }
    });

    this.render_target_.find('.done_editing').click(function(){
        self.render_target_.find('.done_editing').addClass('disabled');
        self.finishedEditingCallback_();
    });

    this.render_target_.find('div.edit .edit_shape').click(function(){
        if(!$(this).hasClass('disabled')){
            $(this).addClass('disabled');
            self.editExistingShape_();
        }
    });

    // Figure out if there is an existing shape in the form, or if a new one
    // needs to be drawn
    if(this.form_.find('#id_geometry_orig').val()){
        this.enterExistingShapeState_();
    }else{
        this.enterNewState_();
    }
}

lingcod.Manipulator.prototype.drawNewShape_ = function(){
    this.is_defining_shape_ = true;
    this.is_defining_new_shape_ = true;
    this.addNewShape_();
    var bounds = this.shape_.getGeometry().getOuterBoundary();
    var self = this;
    this.gex_.edit.drawLineString(bounds, {
        bounce: false,
        finishCallback: function(){
            self.finishedEditingCallback_();
        },
        drawCallback: function(i){
            var coords = bounds.getCoordinates();
            var coord = coords.get(i);
            coord.setAltitude(self.altitude);
            coords.set(i, coord);
        },
        ensureCounterClockwise: false
    });
}

lingcod.Manipulator.prototype.addNewShape_ = function(kml){
    this.clearShape_();
    if(kml){
        this.shape_ = this.gex_.pluginInstance.parseKml(kml);
        this.gex_.pluginInstance.getFeatures().appendChild(this.shape_);
    }else{
        this.shape_ = this.gex_.dom.addPlacemark({
            visibility: true,
            polygon: [],
            style: {
                line: { width: 2, color: 'ffffffff' },
                poly: { color: '8000ff00' }
            }
        });
        this.setZ(this.shape_, this.altitude);
    }
    return this.shape_;
}

lingcod.Manipulator.prototype.setZ = function(kmlObject, z){
    var geo = kmlObject.getGeometry();
    geo.setAltitudeMode(ge.ALTITUDE_ABSOLUTE);
    geo.setExtrude(true);
    var coords = geo.getOuterBoundary().getCoordinates();
    var length = coords.getLength();
    for(var i =0; i<length;i++){
        var coord = coords.get(i)
        coord.setAltitude(z);
        coords.set(i, coord);
    }
    return kmlObject;
}

lingcod.Manipulator.prototype.finishedEditingCallback_ = function(){
    var self = this;
    this.process(this.shape_.getKml(), this.manipulators_, function(data){
        if(data.success === '1'){
            var kmlObject = self.addNewShape_(data.final_shape_kml);
            self.gex_.util.flyToObject(kmlObject, {
                boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            self.setGeometryFields_(data.user_shape, data.submitted, data.final_shape, data.final_shape_kml);
            self.enterManipulatedState_(data.html, true);
        }else{
            self.setGeometryFields_('', data.submitted, '', '');
            self.addNewShape_(data.submitted);
            self.gex_.util.flyToObject(self.shape_, {
                boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            self.enterManipulatedState_(data.html, false);
        }
    });
}

lingcod.Manipulator.prototype.setGeometryFields_ = function(original_wkt, original_kml, final_wkt, final_kml){
    this.form_.find('#id_geometry_orig').val(original_wkt);
    // Replacing the entire script tag rather than just changing the contents
    // for the benefit of IE8 (damn IE 8 !!)
    $('#geometry_final_kml').replaceWith('<script id="geometry_final_kml" type="application/vnd.google-earth.kml+xml">'+final_kml+'</script>');
    $('#geometry_orig_kml').replaceWith('<script id="geometry_orig_kml" type="application/vnd.google-earth.kml+xml">'+original_kml+'</script>');
}

lingcod.Manipulator.prototype.hideStates_ = function(){
    this.render_target_.find('div.new, div.edit, div.manipulated, div.editing').hide();
}

lingcod.Manipulator.prototype.enterManipulatedState_ = function(html, success){
    this.hideStates_();
    if(success === true){
        this.render_target_.find('div.manipulated').removeClass('error');
        this.is_invalid_geometry = false;
        this.is_defining_shape_ = false;
        this.is_defining_new_shape_ = false;
    }else{
        this.is_invalid_geometry = true;
        this.render_target_.find('div.manipulated').addClass('error');
    }
    this.render_target_.find('div.manipulated a.edit_shape').removeClass('disabled');
    this.render_target_.find('div.manipulated').show().find('>p')
        .html(html);
}

lingcod.Manipulator.prototype.isInvalidGeometry = function(){
    return this.is_invalid_geometry;
}

lingcod.Manipulator.prototype.enterNewState_ = function(){
    this.hideStates_();
    // this.is_defining_shape_ = true;
    this.render_target_.find('div.new').show();
    this.render_target_.find('a.draw_shape').removeClass('disabled');
}

lingcod.Manipulator.prototype.isDefiningNewShape = function(){
    return this.is_defining_new_shape_;
}

lingcod.Manipulator.prototype.enterEditingState_ = function(){
    this.hideStates_();
    this.is_invalid_geometry = false;
    this.is_defining_shape_ = true;
    this.is_defining_new_shape_ = false;
    this.render_target_.find('.done_editing').removeClass('disabled');
    this.render_target_.find('div.editing').show();
}

lingcod.Manipulator.prototype.enterExistingShapeState_ = function(){
    var self = this;
    this.hideStates_();
    this.is_defining_shape = false;
    this.render_target_.find('div.edit .edit_shape').removeClass('disabled');
    this.render_target_.find('div.edit').show();
    var kml = jQuery.trim($('#geometry_final_kml').html());
    if(!kml){
        var kml = jQuery.trim($('#geometry_orig_kml').html());
        this.process(kml, this.manipulators_, function(data){
            if(data.success === '1'){
                var kmlObject = self.addNewShape_(data.final_shape_kml);
                self.gex_.util.flyToObject(kmlObject, {
                    boundsFallback: true, aspectRatio: $(self.div).width() / $(self.div).height()});
            }else{
                // do nothing
            }
        });
    }
    this.addNewShape_(kml);
    this.gex_.util.flyToObject(this.shape_, {
        boundsFallback: true, aspectRatio: $(this.div).width() / $(this.div).height()});
    var self = this;
}

lingcod.Manipulator.prototype.isShapeDefined = function(){
    return !!this.form_.find('#id_geometry_orig').val();
}

lingcod.Manipulator.prototype.isDefiningShape = function(){
    return this.is_defining_shape_;
}

lingcod.Manipulator.prototype.process = function(kml, url, callback){
    $(this).trigger('processing');
	var self = this;
    $.ajax({
        url: url,
        type: 'POST',
        data: { target_shape: kml },
        success: function(data, status){
            $(self).trigger('doneprocessing');
            if(status === 'success'){
                callback(JSON.parse(data));
            }else{
                $(self).trigger('error', "There was an error processing your shape.");
                callback({success: false, html: $('#manipulators_server_error')});
            }
        },
        error: function(data, status){
            $(self).trigger('doneprocessing');
            $(self).trigger('error', 'There was an error processing your shape.');
            callback({success: false, html: $('#manipulators_server_error').html()});
        }
    });
}

lingcod.Manipulator.prototype.editExistingShape_ = function(){
    var kml = jQuery.trim($('#geometry_orig_kml').html());
    this.addNewShape_(kml);
    this.gex_.util.flyToObject(this.shape_, {
        boundsFallback: true, aspectRatio: $(this.div).width() / $(this.div).height()});
    this.gex_.edit.editLineString(this.shape_.getGeometry().getOuterBoundary());
    this.enterEditingState_();

}

lingcod.Manipulator.prototype.clearShape_ = function(){
    if(this.shape_ && this.shape_.getParentNode()){
        this.gex_.edit.endEditLineString(this.shape_.getGeometry().getOuterBoundary());
        gex.dom.removeObject(this.shape_);
        this.shape_ = false;
    }
}

lingcod.Manipulator.prototype.destroy = function(){
    this.clearShape_();
}<|endoftext|>


<commit_before>/*
 * @name Patterns
 * @description Move the cursor over the image to draw with a software tool
 * which responds to the speed of the mouse.
 */
function setup() {
  createCanvas(710, 400);
  background(102);
}

function draw() {
  // Call the variableEllipse() method and send it the
  // parameters for the current mouse position
  // and the previous mouse position
  variableEllipse(mouseX, mouseY, pmouseX, pmouseY);
}

// The simple method variableEllipse() was created specifically
// for this program. It calculates the speed of the mouse
// and draws a small ellipse if the mouse is moving slowly
// and draws a large ellipse if the mouse is moving quickly

function variableEllipse(x, y, px, py) {
  var speed = abs(x-px) + abs(y-py);
  stroke(speed);
  ellipse(x, y, speed, speed);
}<commit_msg>Remove stray linebreak<commit_after>/*
 * @name Patterns
 * @description Move the cursor over the image to draw with a software tool
 * which responds to the speed of the mouse.
 */
function setup() {
  createCanvas(710, 400);
  background(102);
}

function draw() {
  // Call the variableEllipse() method and send it the
  // parameters for the current mouse position
  // and the previous mouse position
  variableEllipse(mouseX, mouseY, pmouseX, pmouseY);
}

// The simple method variableEllipse() was created specifically
// for this program. It calculates the speed of the mouse
// and draws a small ellipse if the mouse is moving slowly
// and draws a large ellipse if the mouse is moving quickly

function variableEllipse(x, y, px, py) {
  var speed = abs(x-px) + abs(y-py);
  stroke(speed);
  ellipse(x, y, speed, speed);
}<|endoftext|>


<commit_before>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {ChildProcess} from 'child_process';
import type {Store} from '../reducers/index.js';
import type {Logger} from '../fb-interfaces/Logger.js';
import type {DeviceType} from '../devices/BaseDevice';
import {promisify} from 'util';
import path from 'path';
import child_process from 'child_process';
const execFile = child_process.execFile;
import IOSDevice from '../devices/IOSDevice';
import iosUtil from '../fb-stubs/iOSContainerUtility';
import isProduction from '../utils/isProduction.js';
import GK from '../fb-stubs/GK';
import {registerDeviceCallbackOnPlugins} from '../utils/onRegisterDevice.js';
type iOSSimulatorDevice = {|
  state: 'Booted' | 'Shutdown' | 'Shutting Down',
  availability?: string,
  isAvailable?: 'YES' | 'NO',
  name: string,
  udid: string,
|};

type IOSDeviceParams = {udid: string, type: DeviceType, name: string};

const portforwardingClient = isProduction()
  ? path.resolve(
      __dirname,
      'PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp',
    )
  : 'PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp';

function forwardPort(port: number, multiplexChannelPort: number) {
  return execFile(portforwardingClient, [
    `-portForward=${port}`,
    `-multiplexChannelPort=${multiplexChannelPort}`,
  ]);
}
// start port forwarding server for real device connections
const portForwarders: Array<ChildProcess> = GK.get('flipper_ios_device_support')
  ? [forwardPort(8089, 8079), forwardPort(8088, 8078)]
  : [];

if (typeof window !== 'undefined') {
  window.addEventListener('beforeunload', () => {
    portForwarders.forEach(process => process.kill());
  });
}

function queryDevices(store: Store, logger: Logger): Promise<void> {
  const {connections} = store.getState();
  const currentDeviceIDs: Set<string> = new Set(
    connections.devices
      .filter(device => device instanceof IOSDevice)
      .map(device => device.serial),
  );
  return Promise.all([getActiveSimulators(), getActiveDevices()])
    .then(([a, b]) => a.concat(b))
    .then(activeDevices => {
      for (const {udid, type, name} of activeDevices) {
        if (currentDeviceIDs.has(udid)) {
          currentDeviceIDs.delete(udid);
        } else {
          logger.track('usage', 'register-device', {
            os: 'iOS',
            type: type,
            name: name,
            serial: udid,
          });
          const iOSDevice = new IOSDevice(udid, type, name);
          store.dispatch({
            type: 'REGISTER_DEVICE',
            payload: iOSDevice,
          });
          registerDeviceCallbackOnPlugins(
            store,
            store.getState().plugins.devicePlugins,
            store.getState().plugins.clientPlugins,
            iOSDevice,
          );
        }
      }

      if (currentDeviceIDs.size > 0) {
        currentDeviceIDs.forEach(id =>
          logger.track('usage', 'unregister-device', {os: 'iOS', serial: id}),
        );
        store.dispatch({
          type: 'UNREGISTER_DEVICES',
          payload: currentDeviceIDs,
        });
      }
    });
}

function getActiveSimulators(): Promise<Array<IOSDeviceParams>> {
  const deviceSetPath = process.env.DEVICE_SET_PATH
    ? ['--set', process.env.DEVICE_SET_PATH]
    : [];
  return promisify(execFile)(
    'xcrun',
    ['simctl', ...deviceSetPath, 'list', 'devices', '--json'],
    {
      encoding: 'utf8',
    },
  )
    .then(({stdout}) => JSON.parse(stdout).devices)
    .then(simulatorDevices => {
      const simulators: Array<iOSSimulatorDevice> = Object.values(
        simulatorDevices,
        // $FlowFixMe
      ).reduce((acc, cv) => acc.concat(cv), []);

      return simulators
        .filter(
          simulator =>
            simulator.state === 'Booted' &&
            // For some users "availability" is set, for others it's "isAvailable"
            // It's not clear which key is set, so we are checking both.
            (simulator.availability === '(available)' ||
              simulator.isAvailable === 'YES'),
        )
        .map(simulator => {
          return {
            udid: simulator.udid,
            type: 'emulator',
            name: simulator.name,
          };
        });
    })
    .catch(_ => []);
}

function getActiveDevices(): Promise<Array<IOSDeviceParams>> {
  return iosUtil.targets().catch(e => {
    console.error(e.message);
    return [];
  });
}

export async function getActiveDevicesAndSimulators(): Promise<
  Array<IOSDevice>,
> {
  const activeDevices: Array<Array<IOSDeviceParams>> = await Promise.all([
    getActiveSimulators(),
    getActiveDevices(),
  ]);
  const allDevices = activeDevices[0].concat(activeDevices[1]);
  return allDevices.map(device => {
    const {udid, type, name} = device;
    return new IOSDevice(udid, type, name);
  });
}

export default (store: Store, logger: Logger) => {
  // monitoring iOS devices only available on MacOS.
  if (process.platform !== 'darwin') {
    return;
  }
  queryDevices(store, logger)
    .then(() => {
      const simulatorUpdateInterval = setInterval(() => {
        queryDevices(store, logger).catch(err => {
          console.error(err);
          clearInterval(simulatorUpdateInterval);
        });
      }, 3000);
    })
    .catch(console.error);
};
<commit_msg>iOS CLI version mismatch detection<commit_after>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import type {ChildProcess} from 'child_process';
import type {Store} from '../reducers/index.js';
import type {Logger} from '../fb-interfaces/Logger.js';
import type {DeviceType} from '../devices/BaseDevice';
import {promisify} from 'util';
import path from 'path';
import child_process from 'child_process';
const execFile = child_process.execFile;
import IOSDevice from '../devices/IOSDevice';
import iosUtil from '../fb-stubs/iOSContainerUtility';
import isProduction from '../utils/isProduction.js';
import GK from '../fb-stubs/GK';
import {registerDeviceCallbackOnPlugins} from '../utils/onRegisterDevice.js';
type iOSSimulatorDevice = {|
  state: 'Booted' | 'Shutdown' | 'Shutting Down',
  availability?: string,
  isAvailable?: 'YES' | 'NO',
  name: string,
  udid: string,
|};

type IOSDeviceParams = {udid: string, type: DeviceType, name: string};

const portforwardingClient = isProduction()
  ? path.resolve(
      __dirname,
      'PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp',
    )
  : 'PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp';

function forwardPort(port: number, multiplexChannelPort: number) {
  return execFile(portforwardingClient, [
    `-portForward=${port}`,
    `-multiplexChannelPort=${multiplexChannelPort}`,
  ]);
}
// start port forwarding server for real device connections
const portForwarders: Array<ChildProcess> = GK.get('flipper_ios_device_support')
  ? [forwardPort(8089, 8079), forwardPort(8088, 8078)]
  : [];

if (typeof window !== 'undefined') {
  window.addEventListener('beforeunload', () => {
    portForwarders.forEach(process => process.kill());
  });
}

function queryDevices(store: Store, logger: Logger): Promise<void> {
  checkXcodeVersionMismatch();
  const {connections} = store.getState();
  const currentDeviceIDs: Set<string> = new Set(
    connections.devices
      .filter(device => device instanceof IOSDevice)
      .map(device => device.serial),
  );
  return Promise.all([getActiveSimulators(), getActiveDevices()])
    .then(([a, b]) => a.concat(b))
    .then(activeDevices => {
      for (const {udid, type, name} of activeDevices) {
        if (currentDeviceIDs.has(udid)) {
          currentDeviceIDs.delete(udid);
        } else {
          logger.track('usage', 'register-device', {
            os: 'iOS',
            type: type,
            name: name,
            serial: udid,
          });
          const iOSDevice = new IOSDevice(udid, type, name);
          store.dispatch({
            type: 'REGISTER_DEVICE',
            payload: iOSDevice,
          });
          registerDeviceCallbackOnPlugins(
            store,
            store.getState().plugins.devicePlugins,
            store.getState().plugins.clientPlugins,
            iOSDevice,
          );
        }
      }

      if (currentDeviceIDs.size > 0) {
        currentDeviceIDs.forEach(id =>
          logger.track('usage', 'unregister-device', {os: 'iOS', serial: id}),
        );
        store.dispatch({
          type: 'UNREGISTER_DEVICES',
          payload: currentDeviceIDs,
        });
      }
    });
}

function getActiveSimulators(): Promise<Array<IOSDeviceParams>> {
  const deviceSetPath = process.env.DEVICE_SET_PATH
    ? ['--set', process.env.DEVICE_SET_PATH]
    : [];
  return promisify(execFile)(
    'xcrun',
    ['simctl', ...deviceSetPath, 'list', 'devices', '--json'],
    {
      encoding: 'utf8',
    },
  )
    .then(({stdout}) => JSON.parse(stdout).devices)
    .then(simulatorDevices => {
      const simulators: Array<iOSSimulatorDevice> = Object.values(
        simulatorDevices,
        // $FlowFixMe
      ).reduce((acc, cv) => acc.concat(cv), []);

      return simulators
        .filter(
          simulator =>
            simulator.state === 'Booted' &&
            // For some users "availability" is set, for others it's "isAvailable"
            // It's not clear which key is set, so we are checking both.
            (simulator.availability === '(available)' ||
              simulator.isAvailable === 'YES'),
        )
        .map(simulator => {
          return {
            udid: simulator.udid,
            type: 'emulator',
            name: simulator.name,
          };
        });
    })
    .catch(_ => []);
}

function getActiveDevices(): Promise<Array<IOSDeviceParams>> {
  return iosUtil.targets().catch(e => {
    console.error(e.message);
    return [];
  });
}

let xcodeVersionMismatchFound = false;
async function checkXcodeVersionMismatch() {
  if (xcodeVersionMismatchFound) {
    return;
  }
  const exec = promisify(child_process.exec);
  try {
    let {stdout: xcodeCLIVersion} = await exec('xcode-select -p');
    xcodeCLIVersion = xcodeCLIVersion.trim();
    const {stdout} = await exec('ps aux | grep CoreSimulator');
    for (let line of stdout.split('\n')) {
      const match = line.match(
        /\/Applications\/Xcode[^/]*\.app\/Contents\/Developer/,
      );
      const runningVersion = match && match.length > 0 ? match[0].trim() : null;
      if (runningVersion && runningVersion !== xcodeCLIVersion) {
        console.error(
          `Xcode version mismatch: Simulator is running from "${runningVersion}" while Xcode CLI is "${xcodeCLIVersion}". Running "xcode-select --switch ${runningVersion}" can fix this.`,
        );
        xcodeVersionMismatchFound = true;
        break;
      }
    }
  } catch (e) {
    console.error(e);
  }
}

export async function getActiveDevicesAndSimulators(): Promise<
  Array<IOSDevice>,
> {
  const activeDevices: Array<Array<IOSDeviceParams>> = await Promise.all([
    getActiveSimulators(),
    getActiveDevices(),
  ]);
  const allDevices = activeDevices[0].concat(activeDevices[1]);
  return allDevices.map(device => {
    const {udid, type, name} = device;
    return new IOSDevice(udid, type, name);
  });
}

export default (store: Store, logger: Logger) => {
  // monitoring iOS devices only available on MacOS.
  if (process.platform !== 'darwin') {
    return;
  }
  queryDevices(store, logger)
    .then(() => {
      const simulatorUpdateInterval = setInterval(() => {
        queryDevices(store, logger).catch(err => {
          console.error(err);
          clearInterval(simulatorUpdateInterval);
        });
      }, 3000);
    })
    .catch(console.error);
};
<|endoftext|>


<commit_before>define(
  'ephox.alloy.demo.HtmlConverter',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.api.GuiFactory',
    'ephox.alloy.api.GuiTemplate',
    'ephox.alloy.api.behaviour.Representing',
    'ephox.alloy.api.ui.Button',
    'ephox.alloy.api.ui.Input',
    'ephox.numerosity.api.JSON',
    'ephox.perhaps.Option',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'ephox.sugar.api.Remove',
    'ephox.sugar.api.SelectorFind',
    'ephox.sugar.api.Value',
    'global!document'
  ],

  function (Gui, GuiFactory, GuiTemplate, Representing, Button, Input, Json, Option, Element, Insert, Remove, SelectorFind, Value, document) {
    return function () {
      var ephoxUi = SelectorFind.first('#ephox-ui').getOrDie();

    // TODO: Change this to match the simplified UI templating model we have now.

      var page = {
        uiType: 'container',
        components: [
          {
            uiType: 'custom',
            dom: {
              tag: 'p',
              innerHtml: 'Copy your HTML structure into this textarea and press Convert'
            }
          },
          Input.build({
            tag: 'textarea',
            dom: {
              styles: {
                width: '90%',
                height: '300px',
                display: 'block'
              }
            },
            uid: 'textarea-input'
          }),
          Button.build({
            dom: {
              tag: 'button',
              innerHtml: 'Convert'
            },
            action: function (button) {
              var textarea = button.getSystem().getByUid('textarea-input').getOrDie();
              var value = Representing.getValue(textarea).value;

              var output = GuiTemplate.use(
                Option.none(),
                value,
                { },
                { fields: { } }
              );

              console.log('output', output);
              var display = button.getSystem().getByUid('pre-output').getOrDie();
              var prettyprint = Json.stringify(output, null, 2);

              Remove.empty(display.element());
              Insert.append(display.element(), Element.fromText(prettyprint));
            }
          }),

          {
            uiType: 'container',
            uid: 'pre-output',
            dom: {
              tag: 'pre'
            }
          }
        ]
      };

      var root = GuiFactory.build(page);
      var gui = Gui.takeover(root);

      Insert.append(ephoxUi, gui.element());

    };
  }
);<commit_msg>VAN-44: Container.build in HtmlConverter<commit_after>define(
  'ephox.alloy.demo.HtmlConverter',

  [
    'ephox.alloy.api.Gui',
    'ephox.alloy.api.GuiFactory',
    'ephox.alloy.api.GuiTemplate',
    'ephox.alloy.api.behaviour.Representing',
    'ephox.alloy.api.ui.Button',
    'ephox.alloy.api.ui.Container',
    'ephox.alloy.api.ui.Input',
    'ephox.numerosity.api.JSON',
    'ephox.perhaps.Option',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'ephox.sugar.api.Remove',
    'ephox.sugar.api.SelectorFind',
    'ephox.sugar.api.Value',
    'global!document'
  ],

  function (Gui, GuiFactory, GuiTemplate, Representing, Button, Container, Input, Json, Option, Element, Insert, Remove, SelectorFind, Value, document) {
    return function () {
      var ephoxUi = SelectorFind.first('#ephox-ui').getOrDie();

    // TODO: Change this to match the simplified UI templating model we have now.

      var page = Container.build({
        components: [
          {
            uiType: 'custom',
            dom: {
              tag: 'p',
              innerHtml: 'Copy your HTML structure into this textarea and press Convert'
            }
          },
          Input.build({
            tag: 'textarea',
            dom: {
              styles: {
                width: '90%',
                height: '300px',
                display: 'block'
              }
            },
            uid: 'textarea-input'
          }),
          Button.build({
            dom: {
              tag: 'button',
              innerHtml: 'Convert'
            },
            action: function (button) {
              var textarea = button.getSystem().getByUid('textarea-input').getOrDie();
              var value = Representing.getValue(textarea).value;

              var output = GuiTemplate.use(
                Option.none(),
                value,
                { },
                { fields: { } }
              );

              console.log('output', output);
              var display = button.getSystem().getByUid('pre-output').getOrDie();
              var prettyprint = Json.stringify(output, null, 2);

              Remove.empty(display.element());
              Insert.append(display.element(), Element.fromText(prettyprint));
            }
          }),

          {
            uiType: 'container',
            uid: 'pre-output',
            dom: {
              tag: 'pre'
            }
          }
        ]
      });

      var root = GuiFactory.build(page);
      var gui = Gui.takeover(root);

      Insert.append(ephoxUi, gui.element());

    };
  }
);<|endoftext|>


<commit_before>
var deepEqual = require('deep-equal')
var pull = require('pull-stream')
var contpara = require('continuable-para')

// make a validation stream?
// read the latest record in the database
// check it against the incoming data,
// and then read through

function clone (obj) {
  var o = {}
  for(var k in obj) o[k] = obj[k];
  return o
}

function get (db, key) {
  return function (cb) {
    return db.get(key, cb)
  }
}

module.exports = function (ssb, opts) {

  var hash = opts.hash
  var zeros = opts.hash(new Buffer(0))
  zeros.fill(0)

  var verify = opts.verify
  var encode = opts.encode

  var validators = {}

  function validateSync (msg, prev, pub) {
    if(prev) {
      if(!deepEqual(msg.prev, hash(encode(prev)))
        && msg.sequence === prev.sequence + 1
        && msg.timestamp > prev.timestamp)
        return false
    }
    else {
      if(!deepEqual(msg.prev, zeros)
        && data.sequence === 1
        && data.timestamp > 0)
        return false
    }
    if(!deepEqual(msg.author, hash(pub.public || pub)))
      return false

    var _msg = clone(msg)
    delete _msg.signature
    return verify(pub, msg.signature, hash(encode(_msg)))
  }

  function createValidator (id) {

    var queue = [], batch = [], prev, pubKey, cbs = []

    return function (msg, cb) {

      // INIT: retrive the latest message for the given id.
      // when not in INIT state, queue any new messages.
      // DRAIN: validate all queued messages.
      // writes all valid message.
      // after writing, if there are more queued queued messages
      // goto DRAIN

      if(!queue.length && !batch.length) {
        queue.push({msg: msg, cb: cb})
        var lastDB = ssb.sublevel('lst')

        contpara(
          get(ssb, msg.prev),
          ssb.getPublicKey(msg.author),
          get(lastDB, msg.author)
        )(function (err, results) {
          prev = err ? null :results[0]
          //get PUBLIC KEY out of FIRST MESSAGE.
          pub = err ? msg.message : results[1]

          if((err ? 1 : results[2] + 1) !== msg.sequence)
            return cb(new Error('sequence out of order'))

          drain()
        })

      }
      else
        queue.push({msg: msg, cb: cb})

      function drain () {
        while(queue.length) {
          var e = queue.shift()
          cbs.push(e.cb)

          validateSync(e.msg, prev, pub)
          batch.push({
            key: hash(encode(e.msg)), value: msg, type: 'put'
          })
          prev = e.msg
        }

        ssb.batch(batch, function (err) {
          batch = []; while(cbs.length) cbs.shift()(err)
          if(queue.length) drain()
          else delete validators[id.toString('base64')]
        })
      }
    }
  }

  var v
  return v = {
    validateSync: validateSync,
    validate: function (msg, cb) {

      var id = msg.author
      var ids = id.toString('base64')
      var validator = validators[ids] =
        validators[ids] || createValidator(id)

      return validator(msg, cb)
    }
  }
}
<commit_msg>handle error cases in validation, and move drain into shared closure<commit_after>
var deepEqual = require('deep-equal')
var pull = require('pull-stream')
var contpara = require('continuable-para')
var bytewise = require('bytewise/hex').encode

// make a validation stream?
// read the latest record in the database
// check it against the incoming data,
// and then read through

function clone (obj) {
  var o = {}
  for(var k in obj) o[k] = obj[k];
  return o
}

function get (db, key) {
  return function (cb) {
    return db.get(bytewise(key), cb)
  }
}

module.exports = function (ssb, opts) {

  var lastDB = ssb.sublevel('lst')
  var hash = opts.hash
  var zeros = opts.hash(new Buffer(0))
      zeros.fill(0)

  var verify = opts.verify
  var encode = opts.encode

  var validators = {}

  function validateSync (msg, prev, pub) {
    if(prev) {
      if(!deepEqual(msg.prev, hash(encode(prev)))
        && msg.sequence === prev.sequence + 1
        && msg.timestamp > prev.timestamp)
        return false
    }
    else {
      if(!deepEqual(msg.prev, zeros)
        && msg.sequence === 1
        && msg.timestamp > 0)
        return false
    }
    if(!deepEqual(msg.author, hash(pub.public || pub)))
      return false

    var _msg = clone(msg)
    delete _msg.signature
    return verify(pub, msg.signature, hash(encode(_msg)))
  }

  function createValidator (id) {

    var queue = [], batch = [], prev, pubKey, cbs = []

    return function (msg, cb) {

      // INIT: retrive the latest message for the given id.
      // when not in INIT state, queue any new messages.
      // DRAIN: validate all queued messages.
      // writes all valid message.
      // after writing, if there are more queued queued messages
      // goto DRAIN

      if(!queue.length && !batch.length) {
        queue.push({msg: msg, cb: cb})

        contpara(
          get(ssb, msg.prev),
          ssb.getPublicKey(msg.author),
          get(lastDB, msg.author)
        )(function (err, results) {
          prev = err ? null : results[0]
          //get PUBLIC KEY out of FIRST MESSAGE.
          pub = err ? msg.message : results[1]

          var expected = err ? 1 : results[2] + 1
          if(expected !== msg.sequence)
            return cb(
              new Error('sequence out of order, expected:'
                + expected + ' got: ' + msg.sequence)
            )

          drain()
        })

      }
      else
        queue.push({msg: msg, cb: cb})
    }

    function drain () {
      while(queue.length) {
        var e = queue.shift()
        cbs.push(e.cb)

        if(validateSync(e.msg, prev, pub)) {
          batch.push({
            key: bytewise(hash(encode(e.msg))), value: e.msg, type: 'put'
          })
          prev = e.msg
        }
        else
          cb(new Error('invalid message:' + hash(encode(e.msg))))
      }

      ssb.batch(batch, function (err) {
        batch = []; while(cbs.length) cbs.shift()(err)
        if(queue.length) drain()
        else delete validators[id.toString('base64')]
      })
    }
  }

  var v
  return v = {
    validateSync: validateSync,
    validate: function (msg, cb) {

      var id = msg.author
      var ids = id.toString('base64')
      var validator = validators[ids] =
        validators[ids] || createValidator(id)

      return validator(msg, cb)
    }
  }
}
<|endoftext|>