<commit_before>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`cpr-multi-selector-item__icon ${selected ? "cps-bg-primary-green +selected" : ""}`}>

{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

function nearest(element, className) {
	if (!element || !element.className) return false;
	return element.className.indexOf(className) > -1 || nearest(element.parentElement, className);
}

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				if (!nearest(e.target, 'cpr-multi-selector')) {
					if (this.state.dialogDisplayed)
						this.props.onBlur && this.props.onBlur();
					this.setState({
						dialogDisplayed: false,
						searchValue: ''
					});
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		});

		setTimeout(this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput(e.target.value),
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? '+highlighted'
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (

{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (

{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`cpr-multi-selector-item ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector('.cpr-multi-selector__dialog');

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector('.cpr-multi-selector__dialog__input').focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div key={i} className="cpr-multi-selector__pill" title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (

					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control cpr-multi-selector__dialog__input`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

						{this.getSearchItems(this.props.items)}
						{this.state.searchValue &&

								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }} className='cpr-multi-selector'>
				<input type="input" className="cpr-multi-selector__hidden-input" onFocus={this.displayDialog}/>

					{pills}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<commit_msg>Button changes (#120)<commit_after>import React from 'react';
import {without, includes, union, isNull, find} from 'lodash';

function DefaultItemComponent(props) {
	const item = props.item;
	const getItemTitle = props.getItemTitle;
	const selectedItems = props.selectedItems;
	const selected = includes(selectedItems, item);

	return (
		<div title={`${getItemTitle(item)}`}>
			<div
				className={`cpr-multi-selector-item__icon ${selected ? "cps-bg-primary-green +selected" : ""}`}>

{`${getItemTitle(item)}`}

	)
}

DefaultItemComponent.propTypes = {
	item: React.PropTypes.any.isRequired,
	getItemTitle: React.PropTypes.func,
	selectedItems: React.PropTypes.array.isRequired
};

function nearest(element, className) {
	if (!element || !element.className) return false;
	return element.className.indexOf(className) > -1 || nearest(element.parentElement, className);
}

const MultiSelector = React.createClass({
	propTypes: {
		items: React.PropTypes.array.isRequired,
		initialSelectedItems: React.PropTypes.array,
		onInputChange: React.PropTypes.func,
		onChange: React.PropTypes.func,
		getItemTitle: React.PropTypes.func,
		ItemComponent: React.PropTypes.oneOfType(
			[React.PropTypes.element, React.PropTypes.func]
		),
		placeholder: React.PropTypes.string,
		maxLength: React.PropTypes.number,
		noRestrict: React.PropTypes.bool,
		pressEnterToAddPhrase: React.PropTypes.string,
		noResultsPhrase: React.PropTypes.string,
	},

	componentWillMount: function() {
		document.addEventListener('click', this.state.close);
	},

	componentWillUnmount: function() {
		document.removeEventListener('click', this.state.close);
	},

	getInitialState: function() {
		return {
			selectedItems: this.props.initialSelectedItems || [],
			mouseIndex: null,
			mouseActive: true,
			mouseFunc: null,
			dialogDisplayed: false,
			activeIndex: null,
			searchValue: '',
			close: (e) => {
				if (!nearest(e.target, 'cpr-multi-selector')) {
					if (this.state.dialogDisplayed)
						this.props.onBlur && this.props.onBlur();
					this.setState({
						dialogDisplayed: false,
						searchValue: ''
					});
				}
			},
			invalid: false,
		}
	},

	componentWillReceiveProps(nextProps) {
		this.setState({
			selectedItems: nextProps.initialSelectedItems || []
		})
	},

	displayDialog: function(e) {
		if (!this.state.dialogDisplayed)
			this.props.onFocus && this.props.onFocus();
		this.setState({
			dialogDisplayed: true
		})
	},

	removeItem: function(item, e) {
		this.setState({
			selectedItems: without(this.state.selectedItems, item)
		});

		setTimeout(this.triggerItemChange);
	},

	getItemTitle: function(item) {
		return item.label;
	},

	setActiveIndex(index) {
		this.setState({
			activeIndex: index,
			mouseIndex: null,
		}, () => {
			if (!isNull(this.state.activeIndex) && !!this.searchItems[this.state.activeIndex]) {
				this.searchItems[this.state.activeIndex].scrollIntoView();
				if (this.state.mouseActive) {
					this.setState({
						mouseActive: false,
						mouseFunc: () => {
							this.setState({
								mouseActive: true
							})
							document.removeEventListener("mousemove", this.state.mouseFunc);
						}
					}, () => {
						document.addEventListener("mousemove", this.state.mouseFunc);
					})
				}
			}
		})
	},

	handleChange: function(e) {
		e.persist();
		if (this.state.invalid) {
			this.setState({
				invalid: !this.props.validate(e.target.value),
			});
		}
		this.setState({
			disabled: this.props.disableInput(e.target.value),
		}, () => {
			this.inputChange(e.target.value);
		});
	},

	inputChange: function(newVal) {
		this.props.onInputChange && this.props.onInputChange(newVal);

		this.setState({
			searchValue: newVal
		});
	},

	keyDown: function(e) {
		const keycode = e.which;
		const activeIndex = this.state.activeIndex;
		const filterItems = this.getFilterItems(this.props.items);

		if (isNull(activeIndex) && filterItems.length !== 0) {
			this.setActiveIndex(0);
		} else if (filterItems.length === 0) {
			this.setActiveIndex(null);
		}

		if (keycode === 13) e.preventDefault();
		if(keycode === 40) { // press down key
			if (isNull(activeIndex) && filterItems.length !== 0) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex < filterItems.length - 1) {
					return this.setActiveIndex(activeIndex + 1);
				}
			}
		} else if(keycode === 38) { // press up key
			if(!activeIndex) {
				return this.setActiveIndex(0);
			} else {
				if(activeIndex > 0) {
					return this.setActiveIndex(activeIndex - 1);
				}
			}
		} else if(keycode === 13) { // press enter key
			if(!isNull(activeIndex) && filterItems.length !==0) {
				return this.selectItem(filterItems[activeIndex], e);
			} else if(this.props.noRestrict && e.currentTarget.value) {
				// if the noRestrict prop is true it adds the input as a string to the selected items on enter
				e.persist();
				return this.validateInput(e.currentTarget.value, e);
			}
		} else if(keycode === 27) { // press escape key
			return this.setState({
				activeIndex: null,
				dialogDisplayed: false
			});
		}
	},

	triggerItemChange: function() {
		if (this.props.onChange) {
			this.props.onChange.call(null, this.state.selectedItems);
		}
	},

	getSelectedClass: function(item) {
		return includes(this.state.selectedItems, item) ? '+selected' : '';
	},

	getActiveClass: function(index) {
		return this.state.activeIndex === index || this.state.mouseIndex === index
			? '+highlighted'
			: '';
	},

	getFilterItems: function(items = []) {
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		return items
			.filter((item) => {
				return getItemTitle(item).toLowerCase().indexOf(this.state.searchValue.toLowerCase()) > -1;
			})
	},

	getSearchItems: function(items = []) {
		let ItemComponent = this.props.ItemComponent || DefaultItemComponent;
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;
		let filterItems = this.getFilterItems(items);

		// Show a message that user can press enter to add new item
		if (filterItems.length === 0) {
			if (this.props.noRestrict && this.state.searchValue) {
				const pressEnterToAddPhrase = this.props.pressEnterToAddPhrase || 'Press Enter to add';
				return (

{pressEnterToAddPhrase} "{this.state.searchValue}"

				)
			} else {
				const noResultsPhrase = this.props.noResultsPhrase || 'No items found.';
				return (

{noResultsPhrase}

				)
			}
		}

		// If noRestrict & the search term doesn't have an exact match, append an additional "result" for the new item
		// This is to allow adding of new items when the search term has matching filtered items but not an exact match
		if (this.props.noRestrict && this.state.searchValue
		&& !find(filterItems, (item) => getItemTitle(item).toLowerCase() === this.state.searchValue.toLowerCase())) {
			filterItems.push(this.state.searchValue);
		}

		return filterItems.map((item, index) => {
			return (

 {
						if (this.searchItems) {
							this.searchItems[index] = ref;
						} else {
							this.searchItems = [];
							this.searchItems[index] = ref;
						}
					}}
					onMouseOver={() => {
						if (this.state.mouseActive) {
							this.setState({
								mouseIndex: index
							})
						}
					}}
					className={`cpr-multi-selector-item ${this.getSelectedClass(item)} ${this.getActiveClass(index)}`}
					onClick={this.selectItem.bind(this, item)}>
					<ItemComponent item={item} selectedItems={this.state.selectedItems} getItemTitle={getItemTitle}/>

			)
		})
	},

	selectItem: function(item, e) {
		let selectedItems = this.state.selectedItems;

		if(includes(selectedItems, item)) {
			this.setState({
				selectedItems: without(selectedItems, item),
				invalid: false,
			}, this.triggerItemChange);
		} else {
			this.setState({
				selectedItems: union(selectedItems, [ item ]),
				invalid: false,
			}, this.triggerItemChange);
		}

		if (e && e.currentTarget) {
			e.currentTarget.value = '';
			this.inputChange('');
		}
	},

	validateInput: function(input, e) {
		if (this.state.disabled) return;
		if (this.props.validate) {
			const valid = this.props.validate(input);
			if (valid) this.selectItem(input, e);
			else this.setState({invalid: true});
		} else {
			this.selectItem(input, e);
		}
	},

	positionDialog: function() {
		setTimeout(() => {
			let el = this.el;
			let height = el.clientHeight;
			let dialog = el.querySelector('.cpr-multi-selector__dialog');

			if (dialog) {
				dialog.style.top = (height + 1) + 'px';
				el.querySelector('.cpr-multi-selector__dialog__input').focus();
			}
		}, 100);
	},

	prevent: function(e){
		if(e.which === 13) e.preventDefault();
	},

	render: function() {
		//Get getItemTitle is the function that should be passed in to decide what `pill` will display on selection.
		let getItemTitle = this.props.getItemTitle || this.getItemTitle;

		let pills = this.state.selectedItems
			.map((item, i) => {
				return (
					<div key={i} className="cpr-multi-selector__pill" title={`${getItemTitle(item)}`}>

							{getItemTitle(item)}

							 {
									e.stopPropagation();
									this.removeItem(item)
								}}
								className="cps-icon cps-icon-sm-neg">

				);
			});

		let dialog;

		if (this.state.dialogDisplayed) {
			let placeholder = this.props.placeholder ? this.props.placeholder : "Type a collaborators name...";
			let maxLength = this.props.maxLength;
			dialog = (

					<div
						className={`${this.state.invalid ? "cps-has-error" : ""}`}
						style={{padding: "16px", borderBottom: "1px solid #E9E9E9"}}>
						<input
							onChange={this.handleChange}
							onKeyDown={this.keyDown}
							className={`cps-form-control cpr-multi-selector__dialog__input`}
							placeholder={placeholder}
							{...(maxLength ? {maxLength} : {})}/>
						{this.state.invalid &&
							{this.props.invalidMsg}}

						{this.getSearchItems(this.props.items)}

						{(this.state.searchValue && this.props.doneButton) &&

								<button
									disabled={this.state.diabled}
									onClick={this.validateInput.bind(this, this.state.searchValue)}
									className={`cps-btn +primary ${this.state.disabled ? "+disabled" : ""}`}>
									DONE
								</button>

}

			)

			this.positionDialog();
		}

		return (

 { if (el) this.el = el }} className='cpr-multi-selector'>
				<input type="input" className="cpr-multi-selector__hidden-input" onFocus={this.displayDialog}/>

					{pills}

				{dialog}

		)
	}
});

if (window && !window.MultiSelector) window.MultiSelector = MultiSelector;

export default MultiSelector;
<|endoftext|>


<commit_before>//__BEGIN_LICENSE__
//Copyright (c) 2015, United States Government, as represented by the
//Administrator of the National Aeronautics and Space Administration.
//All rights reserved.

//The xGDS platform is 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.
//__END_LICENSE__

jQuery(function($) {
	var windowWidth = $(window).width();
	$(window).resize(function()  {
		if (windowWidth != $(window).width()) {
			return;
		}
	});
});

var xgds_video = xgds_video || {};
$.extend(xgds_video,{
	defaultTimeFormat: "MM/DD/YY HH:mm:ss z",
	seekHelper: function(seekTimeStr) {
		/**
		 * Used by both seekCallBack and seekFromUrlOffset
		 * to seek all players to given time.
		 */
		var seekTime = xgds_video.seekTimeParser(seekTimeStr);

		if (seekTime != null){
			var newTime = moment(playback.currentTime); // start with the current date

			// update the time
			newTime.hour(seekTime.hour());
			newTime.minute(seekTime.minute());
			newTime.second(seekTime.second());

			// check the time is within range
			if (xgds_video.options.episode.endTime != undefined){
				var range = new DateRange(xgds_video.options.episode.startTime,
					xgds_video.options.episode.endTime);
				if (range.contains(newTime)) {
					xgds_video.seekAllPlayersToTime(newTime);
					return;
				}
			}
			alert("Invalid jump to time.");
		}

	},

	initializeEpisodeTimes: function(episode) {
		/**
		 * convert episode start/end time to javascript dateTime
		 */
		if (_.isEmpty(episode)) {
			return;
		}
		if (episode.startTime) {
			episode.startTime = getLocalTime(episode.startTime, xgds_video.options.timeZone); //moment(episode.startTime);
		}
		if (episode.endTime) {
			episode.endTime = getLocalTime(episode.endTime, xgds_video.options.timeZone); //moment(episode.endTime);
		}
	},

	seekTimeParser: function(input) {
		/**
		 * Helper to parse seektime into hours, minutes, seconds
		 */
		var parsedMoment = moment(input,'MM/DD/YY HH:mm:ss');
		if (!parsedMoment.isValid()){
			parsedMoment = moment(input,'HH:mm:ss');
		}
		return parsedMoment;
	},

	padNum: function(num, size) {
		var s = num + '';
		while (s.length < size) {
			s = '0' + s;
		}
		return s;
	},

	getFilePaths:function(flightName, sourceShortName, segments) {
		/**
		 * Helper that returns file paths of video segments with same source
		 */
		var filePaths = [];
		$.each(segments, function(id) {
			var segment = segments[id];
			var indexFileUrl = xgds_video.options.indexFileUrl.replace('flightName', flightName);
			indexFileUrl = indexFileUrl.replace('sourceShortName', sourceShortName);
			indexFileUrl = indexFileUrl.replace('segmentIndex', xgds_video.padNum(segment.segNumber, 3));
			indexFileUrl = indexFileUrl + segment.indexFileName;
			filePaths.push(indexFileUrl);
		});
		return filePaths;
	},

	getSliderTime: function() {
		if (!_.isUndefined(xgds_video.masterSlider)) {
			var result = new moment(xgds_video.masterSlider.slider('value') * 1000);
			result.tz(xgds_video.options.timeZone);
			return result;
		} else {
			return new moment(); // TODO this is probably not right you may be on delay
		}
	},

	setSliderTimeLabel:function(datetimeMoment) {
		var timeString = getLocalTimeString(datetimeMoment, xgds_video.options.timeZone, xgds_video.defaultTimeFormat);
                //console.log(timeString);
		$('#sliderTimeLabel').text(timeString);
	},

	setPlayerTimeLabel:function(datetime, sourceName) {
		/**
		 * Set test site time of the player
		 */
		var timeString = getLocalTimeString(moment(datetime), xgds_video.options.timeZone, xgds_video.defaultTimeFormat);
		$('#' + sourceName + 'Label').html(timeString);
	},

	withinRange:function(position, offset) {
		return ((position < offset + 20) && (position > offset - 20));
	},

	getPlaylistIdxAndOffset:function(datetime, source) {
		/**
		 * Find the playlist item index and offset the current time
		 * falls under for this player.
		 */
		if (_.isUndefined(datetime)) {
			return false;
		}
		var playlistIdx = null;
		var offset = null;
		var nowMoment = moment(datetime);
		var segments = xgds_video.options.displaySegments[source];
		for (var i = 0; i < segments.length; i++ ) {
			var segment = segments[i];
			if (_.isUndefined(segment.startTime)){
				break;
			}
			if (nowMoment.isBefore(segment.startTime)) {
				break;
			} else if (nowMoment.isSame(segment.startTime)){
				playlistIdx = i;
				offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
				break;
			} else if (nowMoment.isAfter(segment.startTime)){
				if (_.isUndefined(segment.endTime) || !segment.endTime.isValid()){
					playlistIdx = i;
					offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
					break;
				} else if (nowMoment.isBefore(segment.endTime)){
					playlistIdx = i;
					offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
					break;
				}
			}
		}

		if ((playlistIdx != null) && (offset != null)) {
			return {index: playlistIdx, offset: offset};
		}
		return false;
	},
	addPendingSeekAction: function(source, offset, player) {
		var actionObj = {action: player.seek,
						 arg: offset };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}

	},
	addPendingPauseAction: function(source, player) {
		var actionObj = {action: player.pause,
						 arg: true };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}
	},
	addPendingPlayAction: function(source, player) {
		var actionObj = {action: player.play,
						 arg: true };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}
	},
	setPlaylistAndSeek:function(source, index, offset) {
		/**
		 * Ensures that seeking to a playlist item and offset works on both
		 * html 5 and flash.
		 * Example: setPlaylistAndSeek('ROV', 1, 120)
		 */
		var player = jwplayer(source);
		var lastState = player.getState();
		var currentIndex = player.getPlaylistIndex();
		var currentOffset = 0;
		var playlistLoaded = false;
		if (currentIndex == index){
			playlistLoaded = true;
			var playerPosition = player.getPosition();
			currentOffset = Math.round(playerPosition);
			if (currentOffset == offset){
				return;
			}
		}
		if (!playlistLoaded){
			player.playlistItem(index);
			lastState = player.getState();
		}
		try {
			if (lastState !== 'playing'){
				xgds_video.addPendingSeekAction(source, offset, player);
				//xgds_video.addPendingPauseAction(source, player);
			} else {
				player.seek(offset);
			}
		} catch (err){
			console.log(err);
		}
	},
	jumpLocks: new Set([]),
	jumpToPosition:function(currentTime, source, seekValues) {
		/**
		 * Given current time in javascript datetime,
		 * find the playlist item and the offset (seconds) and seek to there.
		 */
		var result = false;
		if (_.isUndefined(source)){
			for (var source in xgds_video.options.displaySegments) {
				var player = jwplayer(source);
				if (player != undefined) {
					result |= xgds_video.jumpToPosition(currentTime, source, seekValues);
				}
			}
			return result;
		}
		if (xgds_video.jumpLocks.has(source)) {
			return result;
		}
		xgds_video.jumpLocks.add(source);
		if (_.isUndefined(seekValues)) {
			seekValues = xgds_video.getPlaylistIdxAndOffset(currentTime, source);
		}
		var player = jwplayer(source);
		//currentTime falls in one of the segments.
		if (!_.isUndefined(seekValues) && seekValues != false) {
			xgds_video.setPlaylistAndSeek(source, seekValues.index, seekValues.offset);
			player.play(true);
			if (!xgds_video.options.playFlag) {
				player.pause(true);
			}
			var updateTime = xgds_video.getPlayerVideoTime(source);
			xgds_video.setPlayerTimeLabel(updateTime, source);
			result = true;

		} else { //current time is not in the playable range.
			//pause the player
			if ((player.getState() == 'playing') || (player.getState() == 'idle')) {
				player.pause(true);
			}
			result = 'unplayable';
		}
		xgds_video.jumpLocks.delete(source);
		return result;
	},

	getNextAvailableSegment:function(currentTime) {
		var nearestSeg = null;
		var minDelta = Number.MAX_VALUE;
		for (var source in xgds_video.options.displaySegments) {
			// see if the source spans the time
			if (currentTime.isSameOrAfter(xgds_video.options.displaySegments[source].startTime) && currentTime.isSameOrBefore(xgds_video.options.displaySegments[source].endTime)) {
				var segments = xgds_video.options.displaySegments[source];
				for (var id=0; id<segments.length; id++) {
					// see if this segment starts before the time.

					var segment = segments[id];
					if (currentTime.isSameOrAfter(segment.startTime) && currentTime.isSameOrBefore(segment.endTime)) {
						var delta = currentTime.diff(segment.startTime);

						if ((delta < minDelta) && (delta >= 0)) {
							minDelta = delta;
							nearestSeg = segment;
						}
					}
				}
			}
		}
		if (nearestSeg == null) {
			return {'time': currentTime, 'source': ''};
		} else {
			return {'time': nearestSeg.startTime, 'source': nearestSeg.source.shortName}; // need to seek to this time.
		}
	},

	onSegmentComplete:function(player) {
		/**
		 * When the segment is complete, go to the next available segment.
		 */
		//awaken idle players.
		var time = xgds_video.getSliderTime();
		xgds_video.awakenIdlePlayers(time, player.id);
		xgds_video.onTimeController(player);
		// if all other players are paused, go the the next available segment and play.
		if (xgds_video.allPaused()) {
			var time = xgds_video.getPlayerVideoTime(player.id);
			var seekTime = xgds_video.getNextAvailableSegment(time);
			xgds_video.seekAllPlayersToTime(seekTime['time']);
		}
	},

	allPaused: function() {
		/**
		 * Returns true if all players are paused or idle.
		 */
		for (var source in xgds_video.options.displaySegments) {
			var state = jwplayer(source).getState();
			if ((state != 'paused') && (state != 'idle')) {
				return false;
			}
		}
		return true;
	},

	showStillViewer: function(groupName, source, timestring) {
		/**
		 * Show still viewer when user clicks the "Still" button
		 */
		// if source name is already appended to groupName, don't add it again
		if (groupName.substr(groupName.length-3, 3) == source) {
			window.open(videoStillViewerUrl + "/" + groupName + "/" + timestring, "_blank");
		} else {
			window.open(videoStillViewerUrl + "/" + groupName + "_" + source + "/" +
					timestring, "_blank");
		}
	},

	getUrlFormatPlayerTime: function(source) {
		/**
		 * Returns Date/Time formatted for use in still frame URL
		 */
		var timestamp = getPlayerVideoTime(source);
		var urlFormatTimestamp = timestamp.getUTCFullYear() + "-" + xgds_video.padNum(timestamp.getUTCMonth()+1,2) + "-" +
		padNum(timestamp.getUTCDate(), 2) + "_" + xgds_video.padNum(timestamp.getUTCHours(),2) + '-' +
		padNum(timestamp.getUTCMinutes(), 2) + '-' + xgds_video.padNum(timestamp.getUTCSeconds(), 2);
		return urlFormatTimestamp
	},

	getPlayerVideoTime:function(source) {
		/**
		 * Helper for returning current test site time from the jwplayer.
		 */
		var segments = xgds_video.options.displaySegments[source];
		var player = jwplayer(source);
		var index = player.getPlaylistIndex();
		var offset = player.getPosition();
		var currentTime = undefined;
		if (offset < 0){
			// we are in delayed live mode, subtract the time from real world now time)
			currentTime = moment();
			// the video chunks are different sizes so we won't know the lag from the jwplayer.
			// instead use our setting.
			offset = - xgds_video.options.video_lag_seconds;
		} else {
			currentTime = moment(segments[index].startTime);
		}

		currentTime.add(offset, 'seconds');
		return currentTime;
	},

	seekAllPlayersToTime: function(datetime) {
		if (!_.isNull(datetime)) {
			xgds_video.jumpToPosition(datetime);
			xgds_video.setSliderTime(datetime);
		} else {
			console.log('Seek all players to time: DATETIME IS NULL?');
		}
	},

	awakenIdlePlayers: function(datetime, exceptThisPlayer) {
		if (_.isUndefined(datetime)) {
			return;
		}
		for (var source in xgds_video.options.displaySegments) {
			if (source != exceptThisPlayer) {
				var state = jwplayer(source).getState();
				if ((state == 'idle') || (state == 'paused')) {
					xgds_video.jumpToPosition(datetime, source, undefined);
				}
			}
		}
	}

});

<commit_msg>default index of playlist to 0<commit_after>//__BEGIN_LICENSE__
//Copyright (c) 2015, United States Government, as represented by the
//Administrator of the National Aeronautics and Space Administration.
//All rights reserved.

//The xGDS platform is 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.
//__END_LICENSE__

jQuery(function($) {
	var windowWidth = $(window).width();
	$(window).resize(function()  {
		if (windowWidth != $(window).width()) {
			return;
		}
	});
});

var xgds_video = xgds_video || {};
$.extend(xgds_video,{
	defaultTimeFormat: "MM/DD/YY HH:mm:ss z",
	seekHelper: function(seekTimeStr) {
		/**
		 * Used by both seekCallBack and seekFromUrlOffset
		 * to seek all players to given time.
		 */
		var seekTime = xgds_video.seekTimeParser(seekTimeStr);

		if (seekTime != null){
			var newTime = moment(playback.currentTime); // start with the current date

			// update the time
			newTime.hour(seekTime.hour());
			newTime.minute(seekTime.minute());
			newTime.second(seekTime.second());

			// check the time is within range
			if (xgds_video.options.episode.endTime != undefined){
				var range = new DateRange(xgds_video.options.episode.startTime,
					xgds_video.options.episode.endTime);
				if (range.contains(newTime)) {
					xgds_video.seekAllPlayersToTime(newTime);
					return;
				}
			}
			alert("Invalid jump to time.");
		}

	},

	initializeEpisodeTimes: function(episode) {
		/**
		 * convert episode start/end time to javascript dateTime
		 */
		if (_.isEmpty(episode)) {
			return;
		}
		if (episode.startTime) {
			episode.startTime = getLocalTime(episode.startTime, xgds_video.options.timeZone); //moment(episode.startTime);
		}
		if (episode.endTime) {
			episode.endTime = getLocalTime(episode.endTime, xgds_video.options.timeZone); //moment(episode.endTime);
		}
	},

	seekTimeParser: function(input) {
		/**
		 * Helper to parse seektime into hours, minutes, seconds
		 */
		var parsedMoment = moment(input,'MM/DD/YY HH:mm:ss');
		if (!parsedMoment.isValid()){
			parsedMoment = moment(input,'HH:mm:ss');
		}
		return parsedMoment;
	},

	padNum: function(num, size) {
		var s = num + '';
		while (s.length < size) {
			s = '0' + s;
		}
		return s;
	},

	getFilePaths:function(flightName, sourceShortName, segments) {
		/**
		 * Helper that returns file paths of video segments with same source
		 */
		var filePaths = [];
		$.each(segments, function(id) {
			var segment = segments[id];
			var indexFileUrl = xgds_video.options.indexFileUrl.replace('flightName', flightName);
			indexFileUrl = indexFileUrl.replace('sourceShortName', sourceShortName);
			indexFileUrl = indexFileUrl.replace('segmentIndex', xgds_video.padNum(segment.segNumber, 3));
			indexFileUrl = indexFileUrl + segment.indexFileName;
			filePaths.push(indexFileUrl);
		});
		return filePaths;
	},

	getSliderTime: function() {
		if (!_.isUndefined(xgds_video.masterSlider)) {
			var result = new moment(xgds_video.masterSlider.slider('value') * 1000);
			result.tz(xgds_video.options.timeZone);
			return result;
		} else {
			return new moment(); // TODO this is probably not right you may be on delay
		}
	},

	setSliderTimeLabel:function(datetimeMoment) {
		var timeString = getLocalTimeString(datetimeMoment, xgds_video.options.timeZone, xgds_video.defaultTimeFormat);
                //console.log(timeString);
		$('#sliderTimeLabel').text(timeString);
	},

	setPlayerTimeLabel:function(datetime, sourceName) {
		/**
		 * Set test site time of the player
		 */
		var timeString = getLocalTimeString(moment(datetime), xgds_video.options.timeZone, xgds_video.defaultTimeFormat);
		$('#' + sourceName + 'Label').html(timeString);
	},

	withinRange:function(position, offset) {
		return ((position < offset + 20) && (position > offset - 20));
	},

	getPlaylistIdxAndOffset:function(datetime, source) {
		/**
		 * Find the playlist item index and offset the current time
		 * falls under for this player.
		 */
		if (_.isUndefined(datetime)) {
			return false;
		}
		var playlistIdx = null;
		var offset = null;
		var nowMoment = moment(datetime);
		var segments = xgds_video.options.displaySegments[source];
		for (var i = 0; i < segments.length; i++ ) {
			var segment = segments[i];
			if (_.isUndefined(segment.startTime)){
				break;
			}
			if (nowMoment.isBefore(segment.startTime)) {
				break;
			} else if (nowMoment.isSame(segment.startTime)){
				playlistIdx = i;
				offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
				break;
			} else if (nowMoment.isAfter(segment.startTime)){
				if (_.isUndefined(segment.endTime) || !segment.endTime.isValid()){
					playlistIdx = i;
					offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
					break;
				} else if (nowMoment.isBefore(segment.endTime)){
					playlistIdx = i;
					offset = Math.round(nowMoment.diff(segment.startTime, 'seconds'));
					break;
				}
			}
		}

		if ((playlistIdx != null) && (offset != null)) {
			return {index: playlistIdx, offset: offset};
		}
		return false;
	},
	addPendingSeekAction: function(source, offset, player) {
		var actionObj = {action: player.seek,
						 arg: offset };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}

	},
	addPendingPauseAction: function(source, player) {
		var actionObj = {action: player.pause,
						 arg: true };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}
	},
	addPendingPlayAction: function(source, player) {
		var actionObj = {action: player.play,
						 arg: true };
		if (xgds_video.pendingPlayerActions[source] == undefined){
			xgds_video.pendingPlayerActions[source] = [actionObj];
		} else {
			xgds_video.pendingPlayerActions[source].push(actionObj);
		}
	},
	setPlaylistAndSeek:function(source, index, offset) {
		/**
		 * Ensures that seeking to a playlist item and offset works on both
		 * html 5 and flash.
		 * Example: setPlaylistAndSeek('ROV', 1, 120)
		 */
		var player = jwplayer(source);
		var lastState = player.getState();
		var currentIndex = player.getPlaylistIndex();
		var currentOffset = 0;
		var playlistLoaded = false;
		if (currentIndex == index){
			playlistLoaded = true;
			var playerPosition = player.getPosition();
			currentOffset = Math.round(playerPosition);
			if (currentOffset == offset){
				return;
			}
		}
		if (!playlistLoaded){
			player.playlistItem(index);
			lastState = player.getState();
		}
		try {
			if (lastState !== 'playing'){
				xgds_video.addPendingSeekAction(source, offset, player);
				//xgds_video.addPendingPauseAction(source, player);
			} else {
				player.seek(offset);
			}
		} catch (err){
			console.log(err);
		}
	},
	jumpLocks: new Set([]),
	jumpToPosition:function(currentTime, source, seekValues) {
		/**
		 * Given current time in javascript datetime,
		 * find the playlist item and the offset (seconds) and seek to there.
		 */
		var result = false;
		if (_.isUndefined(source)){
			for (var source in xgds_video.options.displaySegments) {
				var player = jwplayer(source);
				if (player != undefined) {
					result |= xgds_video.jumpToPosition(currentTime, source, seekValues);
				}
			}
			return result;
		}
		if (xgds_video.jumpLocks.has(source)) {
			return result;
		}
		xgds_video.jumpLocks.add(source);
		if (_.isUndefined(seekValues)) {
			seekValues = xgds_video.getPlaylistIdxAndOffset(currentTime, source);
		}
		var player = jwplayer(source);
		//currentTime falls in one of the segments.
		if (!_.isUndefined(seekValues) && seekValues != false) {
			xgds_video.setPlaylistAndSeek(source, seekValues.index, seekValues.offset);
			player.play(true);
			if (!xgds_video.options.playFlag) {
				player.pause(true);
			}
			var updateTime = xgds_video.getPlayerVideoTime(source);
			xgds_video.setPlayerTimeLabel(updateTime, source);
			result = true;

		} else { //current time is not in the playable range.
			//pause the player
			if ((player.getState() == 'playing') || (player.getState() == 'idle')) {
				player.pause(true);
			}
			result = 'unplayable';
		}
		xgds_video.jumpLocks.delete(source);
		return result;
	},

	getNextAvailableSegment:function(currentTime) {
		var nearestSeg = null;
		var minDelta = Number.MAX_VALUE;
		for (var source in xgds_video.options.displaySegments) {
			// see if the source spans the time
			if (currentTime.isSameOrAfter(xgds_video.options.displaySegments[source].startTime) && currentTime.isSameOrBefore(xgds_video.options.displaySegments[source].endTime)) {
				var segments = xgds_video.options.displaySegments[source];
				for (var id=0; id<segments.length; id++) {
					// see if this segment starts before the time.

					var segment = segments[id];
					if (currentTime.isSameOrAfter(segment.startTime) && currentTime.isSameOrBefore(segment.endTime)) {
						var delta = currentTime.diff(segment.startTime);

						if ((delta < minDelta) && (delta >= 0)) {
							minDelta = delta;
							nearestSeg = segment;
						}
					}
				}
			}
		}
		if (nearestSeg == null) {
			return {'time': currentTime, 'source': ''};
		} else {
			return {'time': nearestSeg.startTime, 'source': nearestSeg.source.shortName}; // need to seek to this time.
		}
	},

	onSegmentComplete:function(player) {
		/**
		 * When the segment is complete, go to the next available segment.
		 */
		//awaken idle players.
		var time = xgds_video.getSliderTime();
		xgds_video.awakenIdlePlayers(time, player.id);
		xgds_video.onTimeController(player);
		// if all other players are paused, go the the next available segment and play.
		if (xgds_video.allPaused()) {
			var time = xgds_video.getPlayerVideoTime(player.id);
			var seekTime = xgds_video.getNextAvailableSegment(time);
			xgds_video.seekAllPlayersToTime(seekTime['time']);
		}
	},

	allPaused: function() {
		/**
		 * Returns true if all players are paused or idle.
		 */
		for (var source in xgds_video.options.displaySegments) {
			var state = jwplayer(source).getState();
			if ((state != 'paused') && (state != 'idle')) {
				return false;
			}
		}
		return true;
	},

	showStillViewer: function(groupName, source, timestring) {
		/**
		 * Show still viewer when user clicks the "Still" button
		 */
		// if source name is already appended to groupName, don't add it again
		if (groupName.substr(groupName.length-3, 3) == source) {
			window.open(videoStillViewerUrl + "/" + groupName + "/" + timestring, "_blank");
		} else {
			window.open(videoStillViewerUrl + "/" + groupName + "_" + source + "/" +
					timestring, "_blank");
		}
	},

	getUrlFormatPlayerTime: function(source) {
		/**
		 * Returns Date/Time formatted for use in still frame URL
		 */
		var timestamp = getPlayerVideoTime(source);
		var urlFormatTimestamp = timestamp.getUTCFullYear() + "-" + xgds_video.padNum(timestamp.getUTCMonth()+1,2) + "-" +
		padNum(timestamp.getUTCDate(), 2) + "_" + xgds_video.padNum(timestamp.getUTCHours(),2) + '-' +
		padNum(timestamp.getUTCMinutes(), 2) + '-' + xgds_video.padNum(timestamp.getUTCSeconds(), 2);
		return urlFormatTimestamp
	},

	getPlayerVideoTime:function(source) {
		/**
		 * Helper for returning current test site time from the jwplayer.
		 */
		var segments = xgds_video.options.displaySegments[source];
		var player = jwplayer(source);
		var index = player.getPlaylistIndex();
		if (_.isUndefined(index)) {
			index = 0;
		}
		var offset = player.getPosition();
		var currentTime = undefined;
		if (offset < 0){
			// we are in delayed live mode, subtract the time from real world now time)
			currentTime = moment();
			// the video chunks are different sizes so we won't know the lag from the jwplayer.
			// instead use our setting.
			offset = - xgds_video.options.video_lag_seconds;
		} else {
			currentTime = moment(segments[index].startTime);
		}

		currentTime.add(offset, 'seconds');
		return currentTime;
	},

	seekAllPlayersToTime: function(datetime) {
		if (!_.isNull(datetime)) {
			xgds_video.jumpToPosition(datetime);
			xgds_video.setSliderTime(datetime);
		} else {
			console.log('Seek all players to time: DATETIME IS NULL?');
		}
	},

	awakenIdlePlayers: function(datetime, exceptThisPlayer) {
		if (_.isUndefined(datetime)) {
			return;
		}
		for (var source in xgds_video.options.displaySegments) {
			if (source != exceptThisPlayer) {
				var state = jwplayer(source).getState();
				if ((state == 'idle') || (state == 'paused')) {
					xgds_video.jumpToPosition(datetime, source, undefined);
				}
			}
		}
	}

});

<|endoftext|>


<commit_before>/**
 * ------------------------------------------
 * 桌面接口实现文件
 * @version  1.0
 * @author   yuqijun(yuqijun@corp.netease.com)
 * ------------------------------------------
 */
var f = function() {
    // variable declaration
    var _ = NEJ.P,
        _o = NEJ.O,
        _r = NEJ.R,
        _e = _('nej.e'),
        _u = _('nej.u'),
        _t = _('nej.ut'),
        _v = _('nej.v'),
        _c = _('nej.c'),
        _n = _('nej.n'),
        _p = _('nej.n');
    /**
     * 计算窗体位置
     * @param {Object} _size
     * @param {String} _align    窗体所处位置        默认为居中
     *                             topleft
     *                             topright
     *                             bottomleft
     *                             bottomright
     *
     */
    _p._$getWindowPos = function(_size,_align){
        var _position = _n._$exec('os.getSystemInfo', 'desktop');
        var _pos ={};
        switch(_align){
            case 'topleft':
                _pos.x =0;
                _pos.y =0;
                break;
            case 'topright':
                _pos.x = _position.workArea.width - _size.width;
                _pos.y = 0;
                break;
            case 'bottomleft':
                _pos.x = 0;
                _pos.y = _position.workArea.height - _size.height;
                break;
            case 'bottomright':
                _pos.x = _position.workArea.width - _size.width;
                _pos.y = _position.workArea.height - _size.height;
                break;
            default:
                _pos.x = Math.floor((_position.workArea.width - _size.width) / 2);
                _pos.y = Math.floor((_position.workArea.height - _size.height) / 2);
                break;
        }
        return _pos;
    };
    /**
     * 窗体信息
     * @param {Object} _winInfo
     *                         x        体窗x坐标
     *                         y        窗体y坐标
     *                         width    窗体宽度
     *                         height    窗体高度
     *                         topmost    是否是一直最上层
     */
    _p._$setPosition = function(_winInfo){
        _p._$showWindow();
        if(!!_winInfo.sizeLimit){
            _p._$sizeLimit(_winInfo.sizeLimit);
        }
        _n._$exec('winhelper.setWindowPosition', _winInfo);
    };
    /**
     * 设置窗体最小拖动宽度和高度
     * @param    {Obejct}    _sizeLimit
     *                                 min
     *                                     width    最小宽度
     *                                     height    最小高度
     *                                 max
     *                                     width    最大宽度
     *                                     height    最大高度
     */
    _p._$sizeLimit = function(_sizeLimit){
        var _sizeMin,_sizeMax;
        if(!!_sizeLimit.min){
            _sizeMin ={};
            _sizeMin.x = _sizeLimit.min.width;
            _sizeMin.y = _sizeLimit.min.height;
        }
        if(!!_sizeLimit.max){
            _sizeMax ={};
            _sizeMax.x = _sizeLimit.max.width;
            _sizeMax.y = _sizeLimit.max.height;
        }
        _n._$exec('winhelper.setWindowSizeLimit', _sizeMin, _sizeMax);
    };
    /**
     * 显示窗体,但窗体不会放在最上层
     */
    _p._$showWindow = function(){
        _n._$exec('winhelper.showWindow', 'show');
    };
    /**
     * 将窗体置于最前
     */
    _p._$bringToTop = function() {
        _n._$exec('winhelper.bringWindowToTop');
    };
    /**
     * 隐藏窗体
     */
    _p._$hideWindow = function(){
        _n._$exec('winhelper.showWindow', 'hide');
    };
    /**
     * 程序退出
     */
    _p._$appExit = function(){
        var _trayIcon = _n._$exec('app.getTrayIcon');
        if (!!_trayIcon)
          _trayIcon.uninstall();
        _n._$exec('app.exit');
    };
    /**
     * 获取托盘对象
     * @return {Object}        托盘对象
     */
    _p._$getTrayIcon = function(){
        return _n._$exec('app.getTrayIcon');
    };
    /**
     * 程序退出
     * @param    {Array}        菜单列表结构如下：
     *                         [{menu:true,separator:false,text:'播放',enable:true,menu_id:1000,children:null}]
     */
    _p._$popMenu = function(_menu){
        _n._$exec('winhelper.popUpMenu',JSON.stringify(_menu));
    };
    /**
     * 气泡
     * @param    {Object}    _balloonInfo
     *                                         title        气泡标题
     *                                         cnt            气泡内容
     *                                         hasSound    是否播放声音
     *                                         delay        延时多少时间
     *                                         icon        气泡图标
     */
    _p._$popBalloon = function(_balloonInfo){
        var _trayIcon = _n._$exec('app.getTrayIcon');
        _trayIcon.popBalloon({
            title : _balloonInfo.title,
            text : _balloonInfo.cnt,
            icon : _trayIcon.icon,
            hasSound : _balloonInfo.hasSound,
            delayTime : _balloonInfo.delay
        });
    };
    /**
     * 设置任务栏图标和任务栏title
     * @param    _iconInfo
     *                     url        任务栏图标地址，如果没有icon从托盘里取icon地址
     *                     title    任务栏标题
     */
    _p._$setTaskIcon = function(_iconInfo){
        if(!_iconInfo.url){
            var _trayIcon = _n._$exec('app.getTrayIcon');
            _n._$exec('winhelper.setWindowIconFromLocalFile', _trayIcon.icon);
        }
        else
            _n._$exec('winhelper.setWindowIconFromLocalFile', _iconInfo.url);
        _n._$exec('winhelper.setWindowTitle', _iconInfo.title||'');
    };
    /**
     * 在浏览器中打开url
     * @param    {String}    _url    网页地址,必须是网页上地址http：//开头
     * @return    {Void}
     */
    _p._$openInNavigate = function(_url) {
        _n._$exec('os.navigateExternal', _url);
    };
    /**
     * 在新窗体中打开页面
     *
     * @param {String}
     *            _url 页面地址
     * @param {Object}
     *            _info
     *                    x         x坐标
     *                    y         y坐标
     *                    width     窗体宽度
     *                    height     窗体高度
     * @param {Object}
     *            _setting
     *                    visible                 打开窗体后是否可见
     *                    resizable                 窗体是否可缩放
     *                    taskbarButton           窗体是否要在任务栏显示
     * @param {Boolean}
     *            _openInNewWindow 强制在新窗体中打开
     * @return {Obejct} 新开窗的引用
     */
    _p._$open = (function() {
            var _windowHander = {};
            return function(_url, _info, _setting, _openInNewWindow){
                var _absoluteUrl = _u._$absolute(_url,location.href);
                if (!!_info.center) {
                    var _pos = _p._$getWindowPos(_info);
                    _info.x = _pos.x;
                    _info.y = _pos.y;
                }
                if (!!_openInNewWindow) {
                    return _n._$exec('winhelper.launchWindow', _absoluteUrl, _info,
                            _setting);
                } else {
                    var _page = _absoluteUrl;
                    if (!_windowHander[_page])
                        _windowHander[_page] = _n._$exec('winhelper.launchWindow',
                                _absoluteUrl, _info, _setting);
                    else if (!!_windowHander[_page] && !_windowHander[_page].location)
                        _windowHander[_page] = _n._$exec('winhelper.launchWindow',
                                _absoluteUrl, _info, _setting);
                    else
                        _windowHander[_page].location.href = _absoluteUrl;
                    return _windowHander[_page];
                }
        };
    })();
    /**
     * 设置开机启动
     * @param    {String}    程序代号
     * @param    {String}    开机启动时参数
     * 设置后开机启动会是　xxx-startup=auto  xxx是程序名
     */
    _p._$setAutoRun = function(){
        _n._$exec('app.setAutoRun', _name, _param);
    };
    /**
     * 取消开机启动
     * @return {String}    _name    程序代号
     */
    _p._$cancelAutoRun = function(_name){
        _n._$exec('app.cancelAutoRun', _name);
    };
    /**
     * 获取是否设置了开机启动
     *
     * @param {String}
     *            _name 程序名字
     * @param {Boolean}
     *            是否已设置
     */
    _p._$getAutoRunState = function(_name) {
        return _n._$exec('app.getAutoRunState', _name);
    };
    /**
     * 移除指定域上的cookie
     *
     * @param {String}
     *            _url 指定域 例：http://163.com
     * @param {String}
     *            _cookieName cookie名
     */
    _p._$removeCookie = function(_url, _cookieName) {
        _n._$exec('browser.removeCookie', _url, _cookieName);
    };
    /**
     * 移动cookie
     *
     * @param {String}
     *            _fromDomain 源域 例：163.com
     * @param {String}
     *            _targetDomain 目标域 例：126.com
     * @param {String}
     *            _cookieName cookie名
     */
    _p._$moveCookie = function(_fromDomain, _targetDomain, _cookieName) {
        _n._$exec('browser.moveCookie', _fromDomain, _targetDomain,
                _cookieName);
    };
    /**
     * 设置指定域上的cookie
     *
     * @param {String}
     *            _url 指定域 例：http://.163.com
     * @param {String
     *            _cookieName cookie名
     * @param {String}
     *            _cookie cookie值
     */
    _p._$setCookie = function(_url, _cookieName, _cookie) {
        _n._$exec('browser.setCookie', _url, _cookieName, _cookie);
    };
    /**
     * 点击节点移动窗体
     * 由于native的drag是鼠标按下时进行拖动
     * 但native又没要在mouseup时形成一个完整的鼠标click事件，所以普通的click事件在这些节点上都是无效的
     * 有多少个节点可以拖动，就添加多少个节点，只有事件源是这些节点的会触发拖动，事件源是他的子节点触发的不会拖动
     * @param {Element}
     *            _elm 节点
     */
    _p._$onDragWindow = (function() {
        var _dragElm =[];
        return function(_elm){
            var _elm = _e._$get(_elm);
            _dragElm.push(_elm);
            _v._$addEvent(_elm, 'mousedown', function(_event){
                var _elm = _v._$getElement(_event);
                var _index = _u._$indexOf(_dragElm, _elm);
                if (_event.which == 1 && _index != -1)
                    _n._$exec('winhelper.dragWindow');
            });
        };
    })();
    /**
     * 获取桌面的大小
     *
     * @return {Object} 桌面大小
     */
    _p._$geDesktopSize = function() {
        var _position = _n._$exec('os.getSystemInfo', 'desktop');
        if(!!_position)
            return;
        else
            return _position.workArea;
    };
    /**
     * 获取当前窗体的位置信息
     *
     * @returns {Object} _info height 窗体高度 width 窗体宽度 x 窗体x坐标 y 窗体y坐标
     */
    _p._$getWindowInfo = function() {
        var _info = _n._$exec('winhelper.getWindowPosition');
        return _info;
    };
};
NEJ.define('{lib}native/cef/api.js', ['{lib}native/command.js','{lib}base/util.js','{lib}base/element.js'], f);<commit_msg>bugfix cef api<commit_after>/**
 * ------------------------------------------
 * 桌面接口实现文件
 * @version  1.0
 * @author   yuqijun(yuqijun@corp.netease.com)
 * ------------------------------------------
 */
var f = function() {
    // variable declaration
    var _ = NEJ.P,
        _o = NEJ.O,
        _r = NEJ.R,
        _e = _('nej.e'),
        _u = _('nej.u'),
        _t = _('nej.ut'),
        _v = _('nej.v'),
        _c = _('nej.c'),
        _n = _('nej.n'),
        _p = _('nej.n');
    /**
     * 计算窗体位置
     * @param {Object} _size
     * @param {String} _align    窗体所处位置        默认为居中
     *                             topleft
     *                             topright
     *                             bottomleft
     *                             bottomright
     *
     */
    _p._$getWindowPos = function(_size,_align){
        var _position = _n._$exec('os.getSystemInfo', 'desktop')||_o,
            _wkarea = _position.workArea||_o,_pos ={};
        switch(_align){
            case 'topleft':
                _pos.x =0;
                _pos.y =0;
                break;
            case 'topright':
                _pos.x = _wkarea.width - _size.width;
                _pos.y = 0;
                break;
            case 'bottomleft':
                _pos.x = 0;
                _pos.y = _wkarea.height - _size.height;
                break;
            case 'bottomright':
                _pos.x = _wkarea.width - _size.width;
                _pos.y = _wkarea.height - _size.height;
                break;
            default:
                _pos.x = Math.floor((_wkarea.width - _size.width) / 2);
                _pos.y = Math.floor((_wkarea.height - _size.height) / 2);
                break;
        }
        return _pos;
    };
    /**
     * 窗体信息
     * @param {Object} _winInfo
     *                         x        体窗x坐标
     *                         y        窗体y坐标
     *                         width    窗体宽度
     *                         height    窗体高度
     *                         topmost    是否是一直最上层
     */
    _p._$setPosition = function(_winInfo){
        _p._$showWindow();
        if(!!_winInfo.sizeLimit){
            _p._$sizeLimit(_winInfo.sizeLimit);
        }
        _n._$exec('winhelper.setWindowPosition', _winInfo);
    };
    /**
     * 设置窗体最小拖动宽度和高度
     * @param    {Obejct}    _sizeLimit
     *                                 min
     *                                     width    最小宽度
     *                                     height    最小高度
     *                                 max
     *                                     width    最大宽度
     *                                     height    最大高度
     */
    _p._$sizeLimit = function(_sizeLimit){
        var _sizeMin,_sizeMax;
        if(!!_sizeLimit.min){
            _sizeMin ={};
            _sizeMin.x = _sizeLimit.min.width;
            _sizeMin.y = _sizeLimit.min.height;
        }
        if(!!_sizeLimit.max){
            _sizeMax ={};
            _sizeMax.x = _sizeLimit.max.width;
            _sizeMax.y = _sizeLimit.max.height;
        }
        _n._$exec('winhelper.setWindowSizeLimit', _sizeMin, _sizeMax);
    };
    /**
     * 显示窗体,但窗体不会放在最上层
     */
    _p._$showWindow = function(){
        _n._$exec('winhelper.showWindow', 'show');
    };
    /**
     * 将窗体置于最前
     */
    _p._$bringToTop = function() {
        _n._$exec('winhelper.bringWindowToTop');
    };
    /**
     * 隐藏窗体
     */
    _p._$hideWindow = function(){
        _n._$exec('winhelper.showWindow', 'hide');
    };
    /**
     * 程序退出
     */
    _p._$appExit = function(){
        var _trayIcon = _n._$exec('app.getTrayIcon');
        if (!!_trayIcon)
          _trayIcon.uninstall();
        _n._$exec('app.exit');
    };
    /**
     * 获取托盘对象
     * @return {Object}        托盘对象
     */
    _p._$getTrayIcon = function(){
        return _n._$exec('app.getTrayIcon');
    };
    /**
     * 程序退出
     * @param    {Array}        菜单列表结构如下：
     *                         [{menu:true,separator:false,text:'播放',enable:true,menu_id:1000,children:null}]
     */
    _p._$popMenu = function(_menu){
        _n._$exec('winhelper.popUpMenu',JSON.stringify(_menu));
    };
    /**
     * 气泡
     * @param    {Object}    _balloonInfo
     *                                         title        气泡标题
     *                                         cnt            气泡内容
     *                                         hasSound    是否播放声音
     *                                         delay        延时多少时间
     *                                         icon        气泡图标
     */
    _p._$popBalloon = function(_balloonInfo){
        var _trayIcon = _n._$exec('app.getTrayIcon');
        _trayIcon.popBalloon({
            title : _balloonInfo.title,
            text : _balloonInfo.cnt,
            icon : _trayIcon.icon,
            hasSound : _balloonInfo.hasSound,
            delayTime : _balloonInfo.delay
        });
    };
    /**
     * 设置任务栏图标和任务栏title
     * @param    _iconInfo
     *                     url        任务栏图标地址，如果没有icon从托盘里取icon地址
     *                     title    任务栏标题
     */
    _p._$setTaskIcon = function(_iconInfo){
        if(!_iconInfo.url){
            var _trayIcon = _n._$exec('app.getTrayIcon');
            _n._$exec('winhelper.setWindowIconFromLocalFile', _trayIcon.icon);
        }
        else
            _n._$exec('winhelper.setWindowIconFromLocalFile', _iconInfo.url);
        _n._$exec('winhelper.setWindowTitle', _iconInfo.title||'');
    };
    /**
     * 在浏览器中打开url
     * @param    {String}    _url    网页地址,必须是网页上地址http：//开头
     * @return    {Void}
     */
    _p._$openInNavigate = function(_url) {
        _n._$exec('os.navigateExternal', _url);
    };
    /**
     * 在新窗体中打开页面
     *
     * @param {String}
     *            _url 页面地址
     * @param {Object}
     *            _info
     *                    x         x坐标
     *                    y         y坐标
     *                    width     窗体宽度
     *                    height     窗体高度
     * @param {Object}
     *            _setting
     *                    visible                 打开窗体后是否可见
     *                    resizable                 窗体是否可缩放
     *                    taskbarButton           窗体是否要在任务栏显示
     * @param {Boolean}
     *            _openInNewWindow 强制在新窗体中打开
     * @return {Obejct} 新开窗的引用
     */
    _p._$open = (function() {
            var _windowHander = {};
            return function(_url, _info, _setting, _openInNewWindow){
                var _absoluteUrl = _u._$absolute(_url,location.href);
                if (!!_info.center) {
                    var _pos = _p._$getWindowPos(_info);
                    _info.x = _pos.x;
                    _info.y = _pos.y;
                }
                if (!!_openInNewWindow) {
                    return _n._$exec('winhelper.launchWindow', _absoluteUrl, _info,
                            _setting);
                } else {
                    var _page = _absoluteUrl;
                    if (!_windowHander[_page])
                        _windowHander[_page] = _n._$exec('winhelper.launchWindow',
                                _absoluteUrl, _info, _setting);
                    else if (!!_windowHander[_page] && !_windowHander[_page].location)
                        _windowHander[_page] = _n._$exec('winhelper.launchWindow',
                                _absoluteUrl, _info, _setting);
                    else
                        _windowHander[_page].location.href = _absoluteUrl;
                    return _windowHander[_page];
                }
        };
    })();
    /**
     * 设置开机启动
     * @param    {String}    程序代号
     * @param    {String}    开机启动时参数
     * 设置后开机启动会是　xxx-startup=auto  xxx是程序名
     */
    _p._$setAutoRun = function(){
        _n._$exec('app.setAutoRun', _name, _param);
    };
    /**
     * 取消开机启动
     * @return {String}    _name    程序代号
     */
    _p._$cancelAutoRun = function(_name){
        _n._$exec('app.cancelAutoRun', _name);
    };
    /**
     * 获取是否设置了开机启动
     *
     * @param {String}
     *            _name 程序名字
     * @param {Boolean}
     *            是否已设置
     */
    _p._$getAutoRunState = function(_name) {
        return _n._$exec('app.getAutoRunState', _name);
    };
    /**
     * 移除指定域上的cookie
     *
     * @param {String}
     *            _url 指定域 例：http://163.com
     * @param {String}
     *            _cookieName cookie名
     */
    _p._$removeCookie = function(_url, _cookieName) {
        _n._$exec('browser.removeCookie', _url, _cookieName);
    };
    /**
     * 移动cookie
     *
     * @param {String}
     *            _fromDomain 源域 例：163.com
     * @param {String}
     *            _targetDomain 目标域 例：126.com
     * @param {String}
     *            _cookieName cookie名
     */
    _p._$moveCookie = function(_fromDomain, _targetDomain, _cookieName) {
        _n._$exec('browser.moveCookie', _fromDomain, _targetDomain,
                _cookieName);
    };
    /**
     * 设置指定域上的cookie
     *
     * @param {String}
     *            _url 指定域 例：http://.163.com
     * @param {String
     *            _cookieName cookie名
     * @param {String}
     *            _cookie cookie值
     */
    _p._$setCookie = function(_url, _cookieName, _cookie) {
        _n._$exec('browser.setCookie', _url, _cookieName, _cookie);
    };
    /**
     * 点击节点移动窗体
     * 由于native的drag是鼠标按下时进行拖动
     * 但native又没要在mouseup时形成一个完整的鼠标click事件，所以普通的click事件在这些节点上都是无效的
     * 有多少个节点可以拖动，就添加多少个节点，只有事件源是这些节点的会触发拖动，事件源是他的子节点触发的不会拖动
     * @param {Element}
     *            _elm 节点
     */
    _p._$onDragWindow = (function() {
        var _dragElm =[];
        return function(_elm){
            var _elm = _e._$get(_elm);
            _dragElm.push(_elm);
            _v._$addEvent(_elm, 'mousedown', function(_event){
                var _elm = _v._$getElement(_event);
                var _index = _u._$indexOf(_dragElm, _elm);
                if (_event.which == 1 && _index != -1)
                    _n._$exec('winhelper.dragWindow');
            });
        };
    })();
    /**
     * 获取桌面的大小
     *
     * @return {Object} 桌面大小
     */
    _p._$geDesktopSize = function() {
        var _position = _n._$exec('os.getSystemInfo', 'desktop');
        if(!!_position)
            return;
        else
            return _position.workArea;
    };
    /**
     * 获取当前窗体的位置信息
     *
     * @returns {Object} _info height 窗体高度 width 窗体宽度 x 窗体x坐标 y 窗体y坐标
     */
    _p._$getWindowInfo = function() {
        var _info = _n._$exec('winhelper.getWindowPosition');
        return _info;
    };
};
NEJ.define('{lib}native/cef/api.js', ['{lib}native/command.js','{lib}base/util.js','{lib}base/element.js'], f);<|endoftext|>


<commit_before>/* TODO flow */
import React from 'react';
import { TabNavigator, TabBarTop } from 'react-navigation';
import { FormattedMessage } from 'react-intl';

import tabsOptions from '../styles/tabs';
import UnreadStreamsContainer from '../unread/UnreadStreamsContainer';
import SubscriptionsContainer from '../streams/SubscriptionsContainer';
import StreamsContainer from '../subscriptions/SubscriptionsContainer';

export default TabNavigator(
  {
    unread: {
      screen: props => <UnreadStreamsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: <FormattedMessage id="Unread" defaultMessage="Unread" />,
      },
    },
    subscribed: {
      screen: props => <SubscriptionsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: <FormattedMessage id="Subscribed" defaultMessage="Subscribed" />,
      },
    },
    streams: {
      screen: props => <StreamsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: <FormattedMessage id="All streams" defaultMessage="All streams" />,
      },
    },
  },
  tabsOptions(TabBarTop, 'top', true),
);
<commit_msg>Fix: Stream tabs label not visible in dark theme. (#1306)<commit_after>/* TODO flow */
import React from 'react';
import { Text } from 'react-native';
import { TabNavigator, TabBarTop } from 'react-navigation';
import { FormattedMessage } from 'react-intl';

import tabsOptions from '../styles/tabs';
import UnreadStreamsContainer from '../unread/UnreadStreamsContainer';
import SubscriptionsContainer from '../streams/SubscriptionsContainer';
import StreamsContainer from '../subscriptions/SubscriptionsContainer';

export default TabNavigator(
  {
    unread: {
      screen: props => <UnreadStreamsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: props => (
          <Text style={{ color: props.tintColor }}>
            <FormattedMessage id="Unread" defaultMessage="Unread" />
          </Text>
        ),
      },
    },
    subscribed: {
      screen: props => <SubscriptionsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: props => (
          <Text style={{ color: props.tintColor }}>
            <FormattedMessage id="Subscribed" defaultMessage="Subscribed" />
          </Text>
        ),
      },
    },
    streams: {
      screen: props => <StreamsContainer {...props.screenProps} />,
      navigationOptions: {
        tabBarLabel: props => (
          <Text style={{ color: props.tintColor }}>
            <FormattedMessage id="All streams" defaultMessage="All streams" />
          </Text>
        ),
      },
    },
  },
  tabsOptions(TabBarTop, 'top', true),
);
<|endoftext|>


<commit_before>'use strict';

var $interpolateMinErr = minErr('$interpolate');

/**
 * @ngdoc provider
 * @name $interpolateProvider
 * @function
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 * @example
<example module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value){
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value){
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length;

    /**
     * @ngdoc service
     * @name $interpolate
     * @function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
     * ```
     *
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     *    * `context`: an object against which any expressions embedded in the strings are evaluated
     *      against.
     *
     */
    function $interpolate(text, mustHaveExpression, trustedContext) {
      var startIndex,
          endIndex,
          index = 0,
          parts = [],
          length = text.length,
          hasInterpolation = false,
          fn,
          exp,
          concat = [];

      while(index < length) {
        if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
          (index != startIndex) && parts.push(text.substring(index, startIndex));
          parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
          fn.exp = exp;
          index = endIndex + endSymbolLength;
          hasInterpolation = true;
        } else {
          // we did not find anything, so we have to add the remainder to the parts array
          (index != length) && parts.push(text.substring(index));
          index = length;
        }
      }

      if (!(length = parts.length)) {
        // we added, nothing, must have been an empty string.
        parts.push('');
        length = 1;
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && parts.length > 1) {
          throw $interpolateMinErr('noconcat',
              "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
              "interpolations that concatenate multiple expressions when a trusted value is " +
              "required.  See http://docs.angularjs.org/api/ng.$sce", text);
      }

      if (!mustHaveExpression  || hasInterpolation) {
        concat.length = length;
        fn = function(context) {
          try {
            for(var i = 0, ii = length, part; i<ii; i++) {
              if (typeof (part = parts[i]) == 'function') {
                part = part(context);
                if (trustedContext) {
                  part = $sce.getTrusted(trustedContext, part);
                } else {
                  part = $sce.valueOf(part);
                }
                if (part == null) { // null || undefined
                  part = '';
                } else {
                  switch (typeof part) {
                    case 'string':
                    {
                      break;
                    }
                    case 'number':
                    {
                      part = '' + part;
                      break;
                    }
                    default:
                    {
                      part = toJson(part);
                    }
                  }
                }
              }
              concat[i] = part;
            }
            return concat.join('');
          }
          catch(err) {
            var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
                err.toString());
            $exceptionHandler(newErr);
          }
        };
        fn.exp = text;
        fn.parts = parts;
        return fn;
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<commit_msg>docs($interpolate): removed function name to match other examples<commit_after>'use strict';

var $interpolateMinErr = minErr('$interpolate');

/**
 * @ngdoc provider
 * @name $interpolateProvider
 * @function
 *
 * @description
 *
 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
 *
 * @example
<example module="customInterpolationApp">
<file name="index.html">

    //demo.label//

</file>
<file name="protractor.js" type="protractor">
  it('should interpolate binding with custom symbols', function() {
    expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  });
</file>
</example>
 */
function $InterpolateProvider() {
  var startSymbol = '{{';
  var endSymbol = '}}';

  /**
   * @ngdoc method
   * @name $interpolateProvider#startSymbol
   * @description
   * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
   *
   * @param {string=} value new value to set the starting symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.startSymbol = function(value){
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  /**
   * @ngdoc method
   * @name $interpolateProvider#endSymbol
   * @description
   * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
   *
   * @param {string=} value new value to set the ending symbol to.
   * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
   */
  this.endSymbol = function(value){
    if (value) {
      endSymbol = value;
      return this;
    } else {
      return endSymbol;
    }
  };

  this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
    var startSymbolLength = startSymbol.length,
        endSymbolLength = endSymbol.length;

    /**
     * @ngdoc service
     * @name $interpolate
     * @function
     *
     * @requires $parse
     * @requires $sce
     *
     * @description
     *
     * Compiles a string with markup into an interpolation function. This service is used by the
     * HTML {@link ng.$compile $compile} service for data binding. See
     * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
     * interpolation markup.
     *
     *
     * ```js
     *   var $interpolate = ...; // injected
     *   var exp = $interpolate('Hello {{name | uppercase}}!');
     *   expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
     * ```
     *
     *
     * @param {string} text The text with markup to interpolate.
     * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
     *    embedded expression in order to return an interpolation function. Strings with no
     *    embedded expression will return null for the interpolation function.
     * @param {string=} trustedContext when provided, the returned function passes the interpolated
     *    result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
     *    trustedContext)} before returning it.  Refer to the {@link ng.$sce $sce} service that
     *    provides Strict Contextual Escaping for details.
     * @returns {function(context)} an interpolation function which is used to compute the
     *    interpolated string. The function has these parameters:
     *
     *    * `context`: an object against which any expressions embedded in the strings are evaluated
     *      against.
     *
     */
    function $interpolate(text, mustHaveExpression, trustedContext) {
      var startIndex,
          endIndex,
          index = 0,
          parts = [],
          length = text.length,
          hasInterpolation = false,
          fn,
          exp,
          concat = [];

      while(index < length) {
        if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
          (index != startIndex) && parts.push(text.substring(index, startIndex));
          parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
          fn.exp = exp;
          index = endIndex + endSymbolLength;
          hasInterpolation = true;
        } else {
          // we did not find anything, so we have to add the remainder to the parts array
          (index != length) && parts.push(text.substring(index));
          index = length;
        }
      }

      if (!(length = parts.length)) {
        // we added, nothing, must have been an empty string.
        parts.push('');
        length = 1;
      }

      // Concatenating expressions makes it hard to reason about whether some combination of
      // concatenated values are unsafe to use and could easily lead to XSS.  By requiring that a
      // single expression be used for iframe[src], object[src], etc., we ensure that the value
      // that's used is assigned or constructed by some JS code somewhere that is more testable or
      // make it obvious that you bound the value to some user controlled value.  This helps reduce
      // the load when auditing for XSS issues.
      if (trustedContext && parts.length > 1) {
          throw $interpolateMinErr('noconcat',
              "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
              "interpolations that concatenate multiple expressions when a trusted value is " +
              "required.  See http://docs.angularjs.org/api/ng.$sce", text);
      }

      if (!mustHaveExpression  || hasInterpolation) {
        concat.length = length;
        fn = function(context) {
          try {
            for(var i = 0, ii = length, part; i<ii; i++) {
              if (typeof (part = parts[i]) == 'function') {
                part = part(context);
                if (trustedContext) {
                  part = $sce.getTrusted(trustedContext, part);
                } else {
                  part = $sce.valueOf(part);
                }
                if (part == null) { // null || undefined
                  part = '';
                } else {
                  switch (typeof part) {
                    case 'string':
                    {
                      break;
                    }
                    case 'number':
                    {
                      part = '' + part;
                      break;
                    }
                    default:
                    {
                      part = toJson(part);
                    }
                  }
                }
              }
              concat[i] = part;
            }
            return concat.join('');
          }
          catch(err) {
            var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
                err.toString());
            $exceptionHandler(newErr);
          }
        };
        fn.exp = text;
        fn.parts = parts;
        return fn;
      }
    }

    /**
     * @ngdoc method
     * @name $interpolate#startSymbol
     * @description
     * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
     *
     * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change
     * the symbol.
     *
     * @returns {string} start symbol.
     */
    $interpolate.startSymbol = function() {
      return startSymbol;
    };

    /**
     * @ngdoc method
     * @name $interpolate#endSymbol
     * @description
     * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
     *
     * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
     * the symbol.
     *
     * @returns {string} end symbol.
     */
    $interpolate.endSymbol = function() {
      return endSymbol;
    };

    return $interpolate;
  }];
}

<|endoftext|>