<commit_before>//Slider
(function( $ ){

    "use strict";

    //STARTOF -- SLIDER
    var PagesSlider = function (slider, options) {
        options = options || {};
        //options
        this.slideCount = options.slideCount || this.activeSlidesCount;
        this.sensitivity = options.sensitivity || 5;
        this.nextClass = options.nextClass || 'ins-next';
        this.prevClass = options.prevClass || 'ins-prev';
        this.debounceTime = options.debounceTime || 0;
        this.slideTime = options.slideTime || 400;
        this.disableClass = options.disableClass || 'ins-arrow-disabled';

        this.slider = slider;
        this.content = slider.children().first();
        this.currentIndex = 0;
        //TODO: Add exception classes (via options) eg. this.content.children(':not(.clearfix)')
        this.pages = this.content.children();

        var firstSlide = this.pages.first(),
            totalWidth = 0,
            offsetWidth = options.offsetWidth || 0, //OPTIONS
            marginRight = parseFloat(firstSlide.css('margin-right')) || 0,
            marginLeft = parseFloat(firstSlide.css('margin-left')) || 0,
            effectiveSlideWidth = firstSlide.width() + marginRight + marginLeft;

        this.pages.each(function (index, page) {
            totalWidth += $(page).width() + marginRight + marginLeft;
        });
        this.content.width(totalWidth  + offsetWidth);
        this.activeSlidesCount = options.activeSlidesCount || Math.ceil(this.slider.width() / effectiveSlideWidth ); //OPTIONS
        this.slider.width( effectiveSlideWidth * this.activeSlidesCount );

        if( options.destroyOnInit ){ //OPTIONS
            this.destroy();
        }
        this.bindEvents();
        this.slideActive();

    };
    var helpers = function(){

        var debounce = function( func, delay ){
            var inDebounce;
            inDebounce = undefined;
            return function() {
                var args, context;
                clearTimeout(inDebounce);
                context = this;
                args = arguments;
                return inDebounce = setTimeout(function() {
                    return func.apply(context, arguments);
                }, delay);
            };
        }

        var getBrowserEvent = function( event ){
            return (event.originalEvent.changedTouches && event.originalEvent.changedTouches[0].clientX) || event.originalEvent.pageX || event.clientX;
        }

        return {
            debounce: debounce,
            getBrowserEvent: getBrowserEvent
        }
    }()

    $.extend(PagesSlider.prototype, {

        bindEvents: function () {

            this._removeTransition = $.proxy(this.removeTransition, this);
            this._startDrag = $.proxy(this.startDrag, this);
            this._doDrag = $.proxy(this.doDrag, this);
            this._endDrag = $.proxy(this.endDrag, this);
            this._next = $.proxy(this.next, this);
            this._prev = $.proxy(this.prev, this);
            this._keyHandler = $.proxy(this.keyHandler, this);

            $('.' + this.nextClass).on('click',helpers.debounce(this._next,this.debounceTime));
            $('.' + this.prevClass).on('click', helpers.debounce(this._prev,this.debounceTime));

            this.content
                .on('mousedown.insider touchstart.insider', this._startDrag)
                .on('transitionend', this._removeTransition)
            $('body')
                .on('mousemove.insider touchmove.insider', this._doDrag)
                .on('mouseup.insider touchend.insider', this._endDrag)
                .on('keydown.insider',this._keyHandler);

        },

        destroy: function () {
            this.content
                .off('mousedown.insider touchstart.insider', this._startDrag)
                .off('transitionend', this._removeTransition);
            $('body')
                .off('mousemove.insider touchmove.insider', this._doDrag)
                .off('mouseup.insider touchend.insider', this._endDrag)
                .off('keydown.insider',this._keyHandler);
        },

        startDrag: function (event) {
            this.enableDrag = true;
            this.dragStartX = helpers.getBrowserEvent( event );
        },

        doDrag: function (event) {
            if (this.enableDrag) {
                var position = this.pages.eq(this.currentIndex).position();
                var delta = helpers.getBrowserEvent( event ) - this.dragStartX;

                this.content.css('transform', 'translate3d(' + (delta - position.left) + 'px, 0, 0)');
                event.preventDefault();
            }
        },

        endDrag: function (event) {
            if (this.enableDrag) {
                this.enableDrag = false;

                var delta = helpers.getBrowserEvent( event ) - this.dragStartX;
                if (Math.abs(delta) > this.slider.width() / this.sensitivity) {
                    if (delta < 0) {
                        this.next();
                    } else {
                        this.prev();
                    }
                } else {
                    this.current();
                }
            }
        },

        removeTransition: function() {
            this.content.css('transition', 'none');
        },

        goToIndex: function (index) {
            var position = this.pages.eq(index).position();
            if( !position ){
                return;
            }
            this.content
                .css('transition', 'all '+this.slideTime +'ms ease')
                .css('transform', 'translate3d(' + (-1 * (position.left)) + 'px, 0, 0)');

            this.currentIndex = index;
            this.slideActive( index, index + this.activeSlidesCount );
        },

        current: function () {
            this.goToIndex(this.currentIndex);
        },

        next: function () {
            var nextIndex = this.currentIndex + this.slideCount;
            var remainingSlides = this.getActiveSlides().last().nextAll().length;

            if (this.currentIndex >= this.pages.length - 1 || this.pages.last().hasClass('active')) {
                // TODO: Make options config to support continuously lopping slider
                // this.goToIndex(0)
                this.current();
            } else {

                if( remainingSlides <= this.slideCount ){
                    nextIndex = this.currentIndex + remainingSlides;
                    $('.' + this.nextClass).addClass( this.disableClass );
                }else{
                   this.removeDisable();
                }
                this.goToIndex( nextIndex );
            }

        },

        prev: function () {
            var prevIndex = this.currentIndex - this.slideCount;
            var remainingSlides = this.getActiveSlides().first().prevAll().length;

            if (this.currentIndex <= 0) {
                this.current();
            } else {
                if( remainingSlides <= this.slideCount ){
                    prevIndex = this.currentIndex - remainingSlides;
                    $('.' + this.prevClass).addClass( this.disableClass );
                }else{
                    this.removeDisable();
                }
                this.goToIndex( prevIndex );
            }
        },

        slideActive: function( start, end ){
            start = start || 0;
            end = end || this.activeSlidesCount;
            this.pages.removeClass('active');
            for( var i = start; i < end; i++ ){
                this.pages.eq(i).addClass('active');
            }
        },

        keyHandler: function( e ){
            e = e || window.event;
            if (e.keyCode == '37') {
                // left arrow
                this.prev();
            }
            else if (e.keyCode == '39') {
                // right arrow
                this.next();
            }
        },

        getActiveSlides: function(){
            return this.pages.filter(function(i,e){ return $(e).hasClass('active')})
        },

        removeDisable: function(){
            $('.' + this.prevClass + ',' + '.' + this.nextClass).removeClass( this.disableClass );
        }
    });
    (function($) {
        $.fn.slider = function(options) {
            this.each(function(index, slider) {
                var $this = $(slider);
                var pagesSlider = new PagesSlider($this,options);
                $this.data('pagesSlider', pagesSlider);
            });
            return this;
        };
    })($);
    //ENDOF -- SLIDER

}( sQuery ));

<commit_msg>fixed options wrong place bug<commit_after>//Slider
(function( $ ){

    "use strict";
    var helpers = function(){

        var debounce = function( func, delay ){
            var inDebounce;
            inDebounce = undefined;
            return function() {
                var args, context;
                clearTimeout(inDebounce);
                context = this;
                args = arguments;
                return inDebounce = setTimeout(function() {
                    return func.apply(context, arguments);
                }, delay);
            };
        }

        var getBrowserEvent = function( event ){
            return (event.originalEvent.changedTouches && event.originalEvent.changedTouches[0].clientX) || event.originalEvent.pageX || event.clientX;
        }

        var getTotalWidth = function( pages, marginLeft, marginRight ){
            var totalWidth = 0;
            pages.each(function (index, page) {
                totalWidth += $(page).width() + marginRight + marginLeft;
            });
            return totalWidth;
        }

        return {
            debounce: debounce,
            getBrowserEvent: getBrowserEvent,
            getTotalWidth: getTotalWidth
        }
    }()

    //STARTOF -- SLIDER
    var PagesSlider = function (slider, options) {
        options = options || {};

        this.slider = slider;
        this.content = slider.children().first();
        this.currentIndex = 0;
        //TODO: Add exception classes (via options) eg. this.content.children(':not(.clearfix)')
        this.pages = this.content.children();

        var firstSlide = this.pages.first(),
            offsetWidth = options.offsetWidth || 0, //OPTIONS
            marginRight = parseFloat(firstSlide.css('margin-right')) || 0,
            marginLeft = parseFloat(firstSlide.css('margin-left')) || 0,
            totalWidth = helpers.getTotalWidth( this.pages, marginLeft, marginRight ),
            effectiveSlideWidth = firstSlide.width() + marginRight + marginLeft;

        this.content.width(totalWidth  + offsetWidth);
        this.activeSlidesCount = options.activeSlidesCount || Math.ceil(this.slider.width() / effectiveSlideWidth ); //OPTIONS
        this.slider.width( effectiveSlideWidth * this.activeSlidesCount );

        //options
        this.slideCount = options.slideCount || this.activeSlidesCount;
        this.sensitivity = options.sensitivity || 5;
        this.nextClass = options.nextClass || 'ins-next';
        this.prevClass = options.prevClass || 'ins-prev';
        this.debounceTime = options.debounceTime || 0;
        this.slideTime = options.slideTime || 400;
        this.disableClass = options.disableClass || 'ins-arrow-disabled';
        this.destroyOnInit = options.destroyOnInit || false;

        this.bindEvents();
        this.slideActive();

    };

    $.extend(PagesSlider.prototype, {

        bindEvents: function () {

            this._removeTransition = $.proxy(this.removeTransition, this);
            this._startDrag = $.proxy(this.startDrag, this);
            this._doDrag = $.proxy(this.doDrag, this);
            this._endDrag = $.proxy(this.endDrag, this);
            this._next = $.proxy(this.next, this);
            this._prev = $.proxy(this.prev, this);
            this._keyHandler = $.proxy(this.keyHandler, this);
            this._destroy = $.proxy(this.destroy, this);

            this.destroyOnInit ? this._destroy() : false;

            $('.' + this.nextClass).on('click',helpers.debounce(this._next,this.debounceTime));
            $('.' + this.prevClass).on('click', helpers.debounce(this._prev,this.debounceTime));

            this.content
                .on('mousedown.insider touchstart.insider', this._startDrag)
                .on('transitionend', this._removeTransition)
            $('body')
                .on('mousemove.insider touchmove.insider', this._doDrag)
                .on('mouseup.insider touchend.insider', this._endDrag)
                .on('keydown.insider',this._keyHandler);

        },

        destroy: function () {
            this.content
                .off('mousedown.insider touchstart.insider', this._startDrag)
                .off('transitionend', this._removeTransition);
            $('body')
                .off('mousemove.insider touchmove.insider', this._doDrag)
                .off('mouseup.insider touchend.insider', this._endDrag)
                .off('keydown.insider',this._keyHandler);
        },

        startDrag: function (event) {
            this.enableDrag = true;
            this.dragStartX = helpers.getBrowserEvent( event );
        },

        doDrag: function (event) {
            if (this.enableDrag) {
                var position = this.pages.eq(this.currentIndex).position();
                var delta = helpers.getBrowserEvent( event ) - this.dragStartX;

                this.content.css('transform', 'translate3d(' + (delta - position.left) + 'px, 0, 0)');
                event.preventDefault();
            }
        },

        endDrag: function (event) {
            if (this.enableDrag) {
                this.enableDrag = false;

                var delta = helpers.getBrowserEvent( event ) - this.dragStartX;
                if (Math.abs(delta) > this.slider.width() / this.sensitivity) {
                    if (delta < 0) {
                        this.next();
                    } else {
                        this.prev();
                    }
                } else {
                    this.current();
                }
            }
        },

        removeTransition: function() {
            this.content.css('transition', 'none');
        },

        goToIndex: function (index) {
            var position = this.pages.eq(index).position();
            if( !position ){
                return;
            }
            this.content
                .css('transition', 'all '+this.slideTime +'ms ease')
                .css('transform', 'translate3d(' + (-1 * (position.left)) + 'px, 0, 0)');

            this.currentIndex = index;
            this.slideActive( index, index + this.activeSlidesCount );
        },

        current: function () {
            this.goToIndex(this.currentIndex);
        },

        next: function () {
            var nextIndex = this.currentIndex + this.slideCount;
            var remainingSlides = this.getActiveSlides().last().nextAll().length;

            if (this.currentIndex >= this.pages.length - 1 || this.pages.last().hasClass('active')) {
                // TODO: Make options config to support continuously lopping slider
                // this.goToIndex(0)
                this.current();
            } else {

                if( remainingSlides <= this.slideCount ){
                    nextIndex = this.currentIndex + remainingSlides;
                    $('.' + this.nextClass).addClass( this.disableClass );
                }else{
                   this.removeDisable();
                }
                this.goToIndex( nextIndex );
            }

        },

        prev: function () {
            var prevIndex = this.currentIndex - this.slideCount;
            var remainingSlides = this.getActiveSlides().first().prevAll().length;

            if (this.currentIndex <= 0) {
                this.current();
            } else {
                if( remainingSlides <= this.slideCount ){
                    prevIndex = this.currentIndex - remainingSlides;
                    $('.' + this.prevClass).addClass( this.disableClass );
                }else{
                    this.removeDisable();
                }
                this.goToIndex( prevIndex );
            }
        },

        slideActive: function( start, end ){
            start = start || 0;
            end = end || this.activeSlidesCount;
            this.pages.removeClass('active');
            for( var i = start; i < end; i++ ){
                this.pages.eq(i).addClass('active');
            }
        },

        keyHandler: function( e ){
            e = e || window.event;
            if (e.keyCode == '37') {
                // left arrow
                this.prev();
            }
            else if (e.keyCode == '39') {
                // right arrow
                this.next();
            }
        },

        getActiveSlides: function(){
            return this.pages.filter(function(i,e){ return $(e).hasClass('active')})
        },

        removeDisable: function(){
            $('.' + this.prevClass + ',' + '.' + this.nextClass).removeClass( this.disableClass );
        }
    });
    (function($) {
        $.fn.slider = function(options) {
            this.each(function(index, slider) {
                var $this = $(slider);
                var pagesSlider = new PagesSlider($this,options);
                $this.data('pagesSlider', pagesSlider);
            });
            return this;
        };
    })($);
    //ENDOF -- SLIDER

}( sQuery ));
<|endoftext|>


<commit_before>(function() {
	'use strict';

	var L = require('leaflet');
	L.Routing = L.Routing || {};
	L.extend(L.Routing, require('./L.Routing.Autocomplete'));
	L.extend(L.Routing, require('./L.Routing.Waypoint'));

	function selectInputText(input) {
		if (input.setSelectionRange) {
			// On iOS, select() doesn't work
			input.setSelectionRange(0, 9999);
		} else {
			// On at least IE8, setSeleectionRange doesn't exist
			input.select();
		}
	}

	L.Routing.Plan = L.Class.extend({
		includes: L.Mixin.Events,

		options: {
			dragStyles: [
				{color: 'black', opacity: 0.15, weight: 9},
				{color: 'white', opacity: 0.8, weight: 6},
				{color: 'red', opacity: 1, weight: 2, dashArray: '7,12'}
			],
			draggableWaypoints: true,
			addWaypoints: true,
			maxGeocoderTolerance: 200,
			autocompleteOptions: {},
			geocodersClassName: '',
			geocoderPlaceholder: function(i, numberWaypoints) {
				return i === 0 ?
					'Start' :
					(i < numberWaypoints - 1 ?
									'Via ' + i :
									'End');
			},
			geocoderClass: function() {
				return '';
			},
			createGeocoder: function() {
				var container = L.DomUtil.create('div', ''),
					input = L.DomUtil.create('input', '', container),
					remove = L.DomUtil.create('span', 'leaflet-routing-remove-waypoint', container);

				return {
					container: container,
					input: input,
					closeButton: remove
				};
			},
			waypointNameFallback: function(latLng) {
				var ns = latLng.lat < 0 ? 'S' : 'N',
				    ew = latLng.lng < 0 ? 'W' : 'E',
				    lat = (Math.round(Math.abs(latLng.lat) * 10000) / 10000).toString(),
				    lng = (Math.round(Math.abs(latLng.lng) * 10000) / 10000).toString();
				return ns + lat + ', ' + ew + lng;
			}
		},

		initialize: function(waypoints, options) {
			L.Util.setOptions(this, options);
			this._waypoints = [];
			this.setWaypoints(waypoints);
		},

		isReady: function() {
			var i;
			for (i = 0; i < this._waypoints.length; i++) {
				if (!this._waypoints[i].latLng) {
					return false;
				}
			}

			return true;
		},

		getWaypoints: function() {
			var i,
				wps = [];

			for (i = 0; i < this._waypoints.length; i++) {
				wps.push(this._waypoints[i]);
			}

			return wps;
		},

		setWaypoints: function(waypoints) {
			var args = [0, this._waypoints.length].concat(waypoints);
			this.spliceWaypoints.apply(this, args);
			return this;
		},

		spliceWaypoints: function() {
			var args = [arguments[0], arguments[1]],
			    i,
			    wp;

			for (i = 2; i < arguments.length; i++) {
				args.push(arguments[i] && arguments[i].hasOwnProperty('latLng') ? arguments[i] : L.Routing.waypoint(arguments[i]));
			}

			[].splice.apply(this._waypoints, args);

			while (this._waypoints.length < 2) {
				wp = L.Routing.waypoint();
				this._waypoints.push(wp);
				args.push(wp);
			}

			this._updateMarkers();
			this._fireChanged.apply(this, args);
		},

		onAdd: function(map) {
			this._map = map;
			this._updateMarkers();
		},

		onRemove: function() {
			var i;
			this._removeMarkers();

			if (this._newWp) {
				for (i = 0; i < this._newWp.lines.length; i++) {
					this._map.removeLayer(this._newWp.lines[i]);
				}
			}

			delete this._map;
		},

		createGeocoders: function() {
			var container = L.DomUtil.create('div', 'leaflet-routing-geocoders ' + this.options.geocodersClassName),
				waypoints = this._waypoints,
			    i,
			    geocoderElem,
			    addWpBtn;

			this._geocoderContainer = container;
			this._geocoderElems = [];

			for (i = 0; i < waypoints.length; i++) {
				geocoderElem = this._createGeocoder(i);
				container.appendChild(geocoderElem.container);
				this._geocoderElems.push(geocoderElem);
			}

			addWpBtn = L.DomUtil.create('button', '', container);
			addWpBtn.setAttribute('type', 'button');
			addWpBtn.innerHTML = '+';
			if (this.options.addWaypoints) {
				L.DomEvent.addListener(addWpBtn, 'click', function() {
					this.spliceWaypoints(waypoints.length, 0, null);
				}, this);
			} else {
				addWpBtn.style.display = 'none';
			}

			this.on('waypointsspliced', this._updateGeocoders);

			return container;
		},

		_createGeocoder: function(i) {
			var nWps = this._waypoints.length,
				g = this.options.createGeocoder(i, nWps),
				closeButton = g.closeButton,
				geocoderInput = g.input,
				wp = this._waypoints[i];
			geocoderInput.setAttribute('placeholder', this.options.geocoderPlaceholder(i, nWps));
			geocoderInput.className = this.options.geocoderClass(i, nWps);

			this._updateWaypointName(i, geocoderInput);
			// This has to be here, or geocoder's value will not be properly
			// initialized.
			// TODO: look into why and make _updateWaypointName fix this.
			geocoderInput.value = wp.name;

			L.DomEvent.addListener(geocoderInput, 'click', function() {
				selectInputText(this);
			}, geocoderInput);

			if (closeButton) {
				L.DomEvent.addListener(closeButton, 'click', function() {
					this.spliceWaypoints(i, 1);
				}, this);
			}

			new L.Routing.Autocomplete(geocoderInput, function(r) {
					geocoderInput.value = r.name;
					wp.name = r.name;
					wp.latLng = r.center;
					this._updateMarkers();
					this._fireChanged();
					this._focusGeocoder(i + 1);
				}, this, L.extend({
					resultFn: this.options.geocoder.geocode,
					resultContext: this.options.geocoder,
					autocompleteFn: this.options.geocoder.suggest,
					autocompleteContext: this.options.geocoder
				}, this.options.autocompleteOptions));

			return g;
		},

		_updateGeocoders: function(e) {
			var newElems = [],
			    i,
			    geocoderElem,
			    beforeElem;

			// Determine where to insert geocoders for new waypoints
			if (e.index >= this._geocoderElems.length) {
				// lastChild is the "add new wp" button
				beforeElem = this._geocoderContainer.lastChild;
			} else {
				beforeElem = this._geocoderElems[e.index].container;
			}

			// Insert new geocoders for new waypoints
			for (i = 0; i < e.added.length; i++) {
				geocoderElem = this._createGeocoder(e.index + i);
				this._geocoderContainer.insertBefore(geocoderElem.container, beforeElem);
				newElems.push(geocoderElem);
			}
			//newElems.reverse();

			for (i = e.index; i < e.index + e.nRemoved; i++) {
				this._geocoderContainer.removeChild(this._geocoderElems[i].container);
			}

			newElems.splice(0, 0, e.index, e.nRemoved);
			[].splice.apply(this._geocoderElems, newElems);

			for (i = 0; i < this._geocoderElems.length; i++) {
				this._geocoderElems[i].input.placeholder = this.options.geocoderPlaceholder(i, this._waypoints.length);
				this._geocoderElems[i].input.className = this.options.geocoderClass(i, this._waypoints.length);
			}
		},

		_updateGeocoder: function(i, geocoderElem) {
			var wp = this._waypoints[i],
			    value = wp && wp.name ? wp.name : '';
			if (geocoderElem) {
				geocoderElem.value = value;
			}
		},

		_updateWaypointName: function(i, geocoderElem, force) {
			var wp = this._waypoints[i],
					wpCoords;

			wp.name = wp.name || '';

			if (wp.latLng && (force || !wp.name)) {
				wpCoords = this.options.waypointNameFallback(wp.latLng);
				if (this.options.geocoder && this.options.geocoder.reverse) {
					this.options.geocoder.reverse(wp.latLng, 67108864 /* zoom 18 */, function(rs) {
						if (rs.length > 0 && rs[0].center.distanceTo(wp.latLng) < this.options.maxGeocoderTolerance) {
							wp.name = rs[0].name;
						} else {
							wp.name = wpCoords;
						}
						this._updateGeocoder(i, geocoderElem);
					}, this);
				} else {
					wp.name = wpCoords;
				}

				this._updateGeocoder(i, geocoderElem);
			}

		},

		_removeMarkers: function() {
			var i;
			if (this._markers) {
				for (i = 0; i < this._markers.length; i++) {
					if (this._markers[i]) {
						this._map.removeLayer(this._markers[i]);
					}
				}
			}
			this._markers = [];
		},

		_updateMarkers: function() {
			var i,
			    icon,
			    m;

			if (!this._map) {
				return;
			}

			this._removeMarkers();

			for (i = 0; i < this._waypoints.length; i++) {
				if (this._waypoints[i].latLng) {
					icon = (typeof(this.options.waypointIcon) === 'function') ?
						this.options.waypointIcon(i, this._waypoints[i].name, this._waypoints.length) :
						this.options.waypointIcon;
					m = this._createMarker(icon, i);
					if (this.options.draggableWaypoints) {
						this._hookWaypointEvents(m, i);
					}
				} else {
					m = null;
				}
				this._markers.push(m);
			}
		},

		_createMarker: function(icon, i) {
			var options = {
				draggable: this.options.draggableWaypoints
			};
			if (icon) {
				options.icon = icon;
			}

			return L.marker(this._waypoints[i].latLng, options).addTo(this._map);
		},

		_fireChanged: function() {
			this.fire('waypointschanged', {waypoints: this.getWaypoints()});

			if (arguments.length >= 2) {
				this.fire('waypointsspliced', {
					index: Array.prototype.shift.call(arguments),
					nRemoved: Array.prototype.shift.call(arguments),
					added: arguments
				});
			}
		},

		_hookWaypointEvents: function(m, i) {
			m.on('dragstart', function(e) {
				this.fire('waypointdragstart', this._createWaypointEvent(i, e));
			}, this);
			m.on('drag', function(e) {
				this._waypoints[i].latLng = e.target.getLatLng();
				this.fire('waypointdrag', this._createWaypointEvent(i, e));
			}, this);
			m.on('dragend', function(e) {
				this._waypoints[i].latLng = e.target.getLatLng();
				this._waypoints[i].name = '';
				this._updateWaypointName(i, this._geocoderElems && this._geocoderElems[i].input, true);
				this.fire('waypointdragend', this._createWaypointEvent(i, e));
				this._fireChanged();
			}, this);
		},

		_createWaypointEvent: function(i, e) {
			return {index: i, latlng: e.target.getLatLng()};
		},

		dragNewWaypoint: function(e) {
			var i;
			this._newWp = {
				afterIndex: e.afterIndex,
				marker: L.marker(e.latlng).addTo(this._map),
				lines: []
			};

			for (i = 0; i < this.options.dragStyles.length; i++) {
				this._newWp.lines.push(L.polyline([
					this._waypoints[e.afterIndex].latLng,
					e.latlng,
					this._waypoints[e.afterIndex + 1].latLng
				], this.options.dragStyles[i]).addTo(this._map));
			}

			this._markers.splice(e.afterIndex + 1, 0, this._newWp.marker);
			this._map.on('mousemove', this._onDragNewWp, this);
			this._map.on('mouseup', this._onWpRelease, this);
		},

		_onDragNewWp: function(e) {
			var i;
			this._newWp.marker.setLatLng(e.latlng);
			for (i = 0; i < this._newWp.lines.length; i++) {
				this._newWp.lines[i].spliceLatLngs(1, 1, e.latlng);
			}
		},

		_onWpRelease: function(e) {
			var i;
			this._map.off('mouseup', this._onWpRelease, this);
			this._map.off('mousemove', this._onDragNewWp, this);
			for (i = 0; i < this._newWp.lines.length; i++) {
				this._map.removeLayer(this._newWp.lines[i]);
			}
			this.spliceWaypoints(this._newWp.afterIndex + 1, 0, e.latlng);
			delete this._newWp;
		},

		_focusGeocoder: function(i) {
			var input;
			if (this._geocoderElems[i]) {
				input = this._geocoderElems[i].input;
				input.focus();
				selectInputText(input);
			} else {
				document.activeElement.blur();
			}
		}
	});

	L.Routing.plan = function(waypoints, options) {
		return new L.Routing.Plan(waypoints, options);
	};

	module.exports = L.Routing;
})();
<commit_msg>feat(plan): Add class name option for add button<commit_after>(function() {
	'use strict';

	var L = require('leaflet');
	L.Routing = L.Routing || {};
	L.extend(L.Routing, require('./L.Routing.Autocomplete'));
	L.extend(L.Routing, require('./L.Routing.Waypoint'));

	function selectInputText(input) {
		if (input.setSelectionRange) {
			// On iOS, select() doesn't work
			input.setSelectionRange(0, 9999);
		} else {
			// On at least IE8, setSeleectionRange doesn't exist
			input.select();
		}
	}

	L.Routing.Plan = L.Class.extend({
		includes: L.Mixin.Events,

		options: {
			dragStyles: [
				{color: 'black', opacity: 0.15, weight: 9},
				{color: 'white', opacity: 0.8, weight: 6},
				{color: 'red', opacity: 1, weight: 2, dashArray: '7,12'}
			],
			draggableWaypoints: true,
			addWaypoints: true,
			addButtonClassName: '',
			maxGeocoderTolerance: 200,
			autocompleteOptions: {},
			geocodersClassName: '',
			geocoderPlaceholder: function(i, numberWaypoints) {
				return i === 0 ?
					'Start' :
					(i < numberWaypoints - 1 ?
									'Via ' + i :
									'End');
			},
			geocoderClass: function() {
				return '';
			},
			createGeocoder: function() {
				var container = L.DomUtil.create('div', ''),
					input = L.DomUtil.create('input', '', container),
					remove = L.DomUtil.create('span', 'leaflet-routing-remove-waypoint', container);

				return {
					container: container,
					input: input,
					closeButton: remove
				};
			},
			waypointNameFallback: function(latLng) {
				var ns = latLng.lat < 0 ? 'S' : 'N',
				    ew = latLng.lng < 0 ? 'W' : 'E',
				    lat = (Math.round(Math.abs(latLng.lat) * 10000) / 10000).toString(),
				    lng = (Math.round(Math.abs(latLng.lng) * 10000) / 10000).toString();
				return ns + lat + ', ' + ew + lng;
			}
		},

		initialize: function(waypoints, options) {
			L.Util.setOptions(this, options);
			this._waypoints = [];
			this.setWaypoints(waypoints);
		},

		isReady: function() {
			var i;
			for (i = 0; i < this._waypoints.length; i++) {
				if (!this._waypoints[i].latLng) {
					return false;
				}
			}

			return true;
		},

		getWaypoints: function() {
			var i,
				wps = [];

			for (i = 0; i < this._waypoints.length; i++) {
				wps.push(this._waypoints[i]);
			}

			return wps;
		},

		setWaypoints: function(waypoints) {
			var args = [0, this._waypoints.length].concat(waypoints);
			this.spliceWaypoints.apply(this, args);
			return this;
		},

		spliceWaypoints: function() {
			var args = [arguments[0], arguments[1]],
			    i,
			    wp;

			for (i = 2; i < arguments.length; i++) {
				args.push(arguments[i] && arguments[i].hasOwnProperty('latLng') ? arguments[i] : L.Routing.waypoint(arguments[i]));
			}

			[].splice.apply(this._waypoints, args);

			while (this._waypoints.length < 2) {
				wp = L.Routing.waypoint();
				this._waypoints.push(wp);
				args.push(wp);
			}

			this._updateMarkers();
			this._fireChanged.apply(this, args);
		},

		onAdd: function(map) {
			this._map = map;
			this._updateMarkers();
		},

		onRemove: function() {
			var i;
			this._removeMarkers();

			if (this._newWp) {
				for (i = 0; i < this._newWp.lines.length; i++) {
					this._map.removeLayer(this._newWp.lines[i]);
				}
			}

			delete this._map;
		},

		createGeocoders: function() {
			var container = L.DomUtil.create('div', 'leaflet-routing-geocoders ' + this.options.geocodersClassName),
				waypoints = this._waypoints,
			    i,
			    geocoderElem,
			    addWpBtn;

			this._geocoderContainer = container;
			this._geocoderElems = [];

			for (i = 0; i < waypoints.length; i++) {
				geocoderElem = this._createGeocoder(i);
				container.appendChild(geocoderElem.container);
				this._geocoderElems.push(geocoderElem);
			}

			addWpBtn = L.DomUtil.create('button', this.options.addButtonClassName, container);
			addWpBtn.setAttribute('type', 'button');
			addWpBtn.innerHTML = '+';
			if (this.options.addWaypoints) {
				L.DomEvent.addListener(addWpBtn, 'click', function() {
					this.spliceWaypoints(waypoints.length, 0, null);
				}, this);
			} else {
				addWpBtn.style.display = 'none';
			}

			this.on('waypointsspliced', this._updateGeocoders);

			return container;
		},

		_createGeocoder: function(i) {
			var nWps = this._waypoints.length,
				g = this.options.createGeocoder(i, nWps),
				closeButton = g.closeButton,
				geocoderInput = g.input,
				wp = this._waypoints[i];
			geocoderInput.setAttribute('placeholder', this.options.geocoderPlaceholder(i, nWps));
			geocoderInput.className = this.options.geocoderClass(i, nWps);

			this._updateWaypointName(i, geocoderInput);
			// This has to be here, or geocoder's value will not be properly
			// initialized.
			// TODO: look into why and make _updateWaypointName fix this.
			geocoderInput.value = wp.name;

			L.DomEvent.addListener(geocoderInput, 'click', function() {
				selectInputText(this);
			}, geocoderInput);

			if (closeButton) {
				L.DomEvent.addListener(closeButton, 'click', function() {
					this.spliceWaypoints(i, 1);
				}, this);
			}

			new L.Routing.Autocomplete(geocoderInput, function(r) {
					geocoderInput.value = r.name;
					wp.name = r.name;
					wp.latLng = r.center;
					this._updateMarkers();
					this._fireChanged();
					this._focusGeocoder(i + 1);
				}, this, L.extend({
					resultFn: this.options.geocoder.geocode,
					resultContext: this.options.geocoder,
					autocompleteFn: this.options.geocoder.suggest,
					autocompleteContext: this.options.geocoder
				}, this.options.autocompleteOptions));

			return g;
		},

		_updateGeocoders: function(e) {
			var newElems = [],
			    i,
			    geocoderElem,
			    beforeElem;

			// Determine where to insert geocoders for new waypoints
			if (e.index >= this._geocoderElems.length) {
				// lastChild is the "add new wp" button
				beforeElem = this._geocoderContainer.lastChild;
			} else {
				beforeElem = this._geocoderElems[e.index].container;
			}

			// Insert new geocoders for new waypoints
			for (i = 0; i < e.added.length; i++) {
				geocoderElem = this._createGeocoder(e.index + i);
				this._geocoderContainer.insertBefore(geocoderElem.container, beforeElem);
				newElems.push(geocoderElem);
			}
			//newElems.reverse();

			for (i = e.index; i < e.index + e.nRemoved; i++) {
				this._geocoderContainer.removeChild(this._geocoderElems[i].container);
			}

			newElems.splice(0, 0, e.index, e.nRemoved);
			[].splice.apply(this._geocoderElems, newElems);

			for (i = 0; i < this._geocoderElems.length; i++) {
				this._geocoderElems[i].input.placeholder = this.options.geocoderPlaceholder(i, this._waypoints.length);
				this._geocoderElems[i].input.className = this.options.geocoderClass(i, this._waypoints.length);
			}
		},

		_updateGeocoder: function(i, geocoderElem) {
			var wp = this._waypoints[i],
			    value = wp && wp.name ? wp.name : '';
			if (geocoderElem) {
				geocoderElem.value = value;
			}
		},

		_updateWaypointName: function(i, geocoderElem, force) {
			var wp = this._waypoints[i],
					wpCoords;

			wp.name = wp.name || '';

			if (wp.latLng && (force || !wp.name)) {
				wpCoords = this.options.waypointNameFallback(wp.latLng);
				if (this.options.geocoder && this.options.geocoder.reverse) {
					this.options.geocoder.reverse(wp.latLng, 67108864 /* zoom 18 */, function(rs) {
						if (rs.length > 0 && rs[0].center.distanceTo(wp.latLng) < this.options.maxGeocoderTolerance) {
							wp.name = rs[0].name;
						} else {
							wp.name = wpCoords;
						}
						this._updateGeocoder(i, geocoderElem);
					}, this);
				} else {
					wp.name = wpCoords;
				}

				this._updateGeocoder(i, geocoderElem);
			}

		},

		_removeMarkers: function() {
			var i;
			if (this._markers) {
				for (i = 0; i < this._markers.length; i++) {
					if (this._markers[i]) {
						this._map.removeLayer(this._markers[i]);
					}
				}
			}
			this._markers = [];
		},

		_updateMarkers: function() {
			var i,
			    icon,
			    m;

			if (!this._map) {
				return;
			}

			this._removeMarkers();

			for (i = 0; i < this._waypoints.length; i++) {
				if (this._waypoints[i].latLng) {
					icon = (typeof(this.options.waypointIcon) === 'function') ?
						this.options.waypointIcon(i, this._waypoints[i].name, this._waypoints.length) :
						this.options.waypointIcon;
					m = this._createMarker(icon, i);
					if (this.options.draggableWaypoints) {
						this._hookWaypointEvents(m, i);
					}
				} else {
					m = null;
				}
				this._markers.push(m);
			}
		},

		_createMarker: function(icon, i) {
			var options = {
				draggable: this.options.draggableWaypoints
			};
			if (icon) {
				options.icon = icon;
			}

			return L.marker(this._waypoints[i].latLng, options).addTo(this._map);
		},

		_fireChanged: function() {
			this.fire('waypointschanged', {waypoints: this.getWaypoints()});

			if (arguments.length >= 2) {
				this.fire('waypointsspliced', {
					index: Array.prototype.shift.call(arguments),
					nRemoved: Array.prototype.shift.call(arguments),
					added: arguments
				});
			}
		},

		_hookWaypointEvents: function(m, i) {
			m.on('dragstart', function(e) {
				this.fire('waypointdragstart', this._createWaypointEvent(i, e));
			}, this);
			m.on('drag', function(e) {
				this._waypoints[i].latLng = e.target.getLatLng();
				this.fire('waypointdrag', this._createWaypointEvent(i, e));
			}, this);
			m.on('dragend', function(e) {
				this._waypoints[i].latLng = e.target.getLatLng();
				this._waypoints[i].name = '';
				this._updateWaypointName(i, this._geocoderElems && this._geocoderElems[i].input, true);
				this.fire('waypointdragend', this._createWaypointEvent(i, e));
				this._fireChanged();
			}, this);
		},

		_createWaypointEvent: function(i, e) {
			return {index: i, latlng: e.target.getLatLng()};
		},

		dragNewWaypoint: function(e) {
			var i;
			this._newWp = {
				afterIndex: e.afterIndex,
				marker: L.marker(e.latlng).addTo(this._map),
				lines: []
			};

			for (i = 0; i < this.options.dragStyles.length; i++) {
				this._newWp.lines.push(L.polyline([
					this._waypoints[e.afterIndex].latLng,
					e.latlng,
					this._waypoints[e.afterIndex + 1].latLng
				], this.options.dragStyles[i]).addTo(this._map));
			}

			this._markers.splice(e.afterIndex + 1, 0, this._newWp.marker);
			this._map.on('mousemove', this._onDragNewWp, this);
			this._map.on('mouseup', this._onWpRelease, this);
		},

		_onDragNewWp: function(e) {
			var i;
			this._newWp.marker.setLatLng(e.latlng);
			for (i = 0; i < this._newWp.lines.length; i++) {
				this._newWp.lines[i].spliceLatLngs(1, 1, e.latlng);
			}
		},

		_onWpRelease: function(e) {
			var i;
			this._map.off('mouseup', this._onWpRelease, this);
			this._map.off('mousemove', this._onDragNewWp, this);
			for (i = 0; i < this._newWp.lines.length; i++) {
				this._map.removeLayer(this._newWp.lines[i]);
			}
			this.spliceWaypoints(this._newWp.afterIndex + 1, 0, e.latlng);
			delete this._newWp;
		},

		_focusGeocoder: function(i) {
			var input;
			if (this._geocoderElems[i]) {
				input = this._geocoderElems[i].input;
				input.focus();
				selectInputText(input);
			} else {
				document.activeElement.blur();
			}
		}
	});

	L.Routing.plan = function(waypoints, options) {
		return new L.Routing.Plan(waypoints, options);
	};

	module.exports = L.Routing;
})();
<|endoftext|>


<commit_before>/* eslint-disable no-unused-expressions */

import React from 'react'
import { render, unmountComponentAtNode } from 'react-dom'

import ReactPlayer from '../../src/ReactPlayer'

const { describe, it, expect, beforeEach, afterEach } = window

const TEST_URLS = [
  {
    name: 'FilePlayer',
    url: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv',
    switchTo: 'http://www.sample-videos.com/audio/mp3/crowd-cheering.mp3',
    error: 'http://example.com/error.ogv',
    onSeek: true
  },
  {
    name: 'FilePlayer (multiple string sources)',
    url: [
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv',
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm'
    ]
  },
  {
    name: 'FilePlayer (multiple object sources)',
    url: [
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4', type: 'video/mp4' },
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv', type: 'video/ogv' },
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm', type: 'video/webm' }
    ]
  },
  {
    name: 'FilePlayer (HLS)',
    url: 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8'
  },
  {
    name: 'FilePlayer (DASH)',
    url: 'http://dash.edgesuite.net/envivio/EnvivioDash3/manifest.mpd'
  },
  {
    name: 'YouTube',
    url: 'https://www.youtube.com/watch?v=M7lc1UVf-VE',
    switchTo: 'https://www.youtube.com/watch?v=oUFJJNQGwhk',
    error: 'https://www.youtube.com/watch?v=xxxxxxxxxxx'
  },
  {
    name: 'SoundCloud',
    url: 'https://soundcloud.com/miami-nights-1984/accelerated',
    switchTo: 'https://soundcloud.com/tycho/tycho-awake',
    error: 'https://soundcloud.com/xxxxxxxxxxx/xxxxxxxxxxx'
  },
  {
    name: 'Facebook',
    url: 'https://www.facebook.com/facebook/videos/10153231379946729/',
    switchTo: 'https://www.facebook.com/FacebookDevelopers/videos/10152454700553553/'
  },
  {
    name: 'Vimeo',
    url: 'https://vimeo.com/90509568',
    switchTo: 'https://vimeo.com/169599296',
    error: 'http://vimeo.com/00000000',
    onSeek: true
  },
  {
    name: 'Twitch',
    url: 'https://www.twitch.tv/videos/106400740',
    switchTo: 'https://www.twitch.tv/videos/175705374'
  },
  {
    name: 'Streamable',
    url: 'https://streamable.com/moo',
    switchTo: 'https://streamable.com/ifjh'
  },
  {
    name: 'Wistia',
    url: 'https://home.wistia.com/medias/e4a27b971d',
    switchTo: 'https://home.wistia.com/medias/29b0fbf547',
    onSeek: true
  },
  {
    name: 'DailyMotion',
    url: 'https://www.dailymotion.com/video/x5e9eog',
    switchTo: 'https://www.dailymotion.com/video/x61xx3z',
    error: 'http://www.dailymotion.com/video/x6c0xvb',
    onSeek: true
  }
]

describe('ReactPlayer', () => {
  let div // Top level div to render into
  let player // Top level player var that will always contain the latest player instance

  // Test util for rendering a player
  function renderPlayer (props, onMount) {
    const ref = p => {
      if (p) {
        player = p
        if (onMount) {
          onMount()
        }
      }
    }
    // Note that playing is set to true by default
    render(<ReactPlayer ref={ref} playing {...props} />, div)
  }

  // Test util for rendering a player and then changing props after a short time
  function renderPlayerChange (props, changeProps, onChange) {
    renderPlayer({
      ...props,
      onProgress: p => {
        if (changeProps && p.playedSeconds > 3) {
          renderPlayer({ ...props, ...changeProps })
          if (onChange) {
            onChange()
          }
        }
        if (props.onProgress) {
          props.onProgress(p)
        }
      }
    })
  }

  beforeEach(() => {
    div = document.createElement('div')
    document.body.appendChild(div)
  })

  afterEach(() => {
    unmountComponentAtNode(div)
    document.body.removeChild(div)
  })

  for (let test of TEST_URLS) {
    const desc = test.skip ? describe.skip : (test.only ? describe.only : describe)
    desc(test.name, () => {
      it('canPlay', () => {
        expect(ReactPlayer.canPlay(test.url)).to.be.true
        expect(ReactPlayer.canPlay('random-string')).to.be.false
      })

      it('onReady, onStart, onPlay, onDuration, onProgress', done => {
        // Use a count object to ensure everything is called at least once
        let count = {}
        const bump = key => {
          count[key] = count[key] || 0
          count[key]++
          if (Object.keys(count).length === 5) {
            done()
          }
        }
        renderPlayer({
          url: test.url,
          onReady: () => bump('onReady'),
          onStart: () => bump('onStart'),
          onPlay: () => bump('onPlay'),
          onDuration: secs => bump('onDuration'),
          onProgress: progress => bump('onProgress')
        })
      })

      it('onPause', done => {
        const props = {
          url: test.url,
          onPause: () => done(),
          playing: true
        }
        renderPlayerChange(props, { playing: false })
      })

      it('should not play if playing is false', done => {
        renderPlayer({
          url: test.url,
          playing: false,
          onReady: () => setTimeout(done, 1000),
          onPlay: () => done('should not play if playing is false')
        })
      })

      it('plays after a delay', done => {
        const playPlayer = () => {
          renderPlayer({
            url: test.url,
            playing: true,
            onPlay: () => done()
          })
        }
        renderPlayer({
          url: test.url,
          playing: false,
          onReady: () => setTimeout(playPlayer, 1000)
        })
      })

      it.skip('volume change does not error', done => {
        renderPlayerChange(
          { url: test.url, volume: 1 },
          { volume: 0.5 },
          () => setTimeout(done, 1000)
        )
      })

      it('muted change does not error', done => {
        renderPlayerChange(
          { url: test.url, muted: false },
          { muted: true },
          () => setTimeout(done, 1000)
        )
      })

      it('playbackRate change does not error', done => {
        renderPlayerChange(
          { url: test.url, playbackRate: 1 },
          { playbackRate: 0.5 },
          () => setTimeout(done, 1000)
        )
      })

      it('renders twice without error', done => {
        const go = () => renderPlayer({ url: test.url })
        go()
        setTimeout(go, 1000)
        setTimeout(done, 2000)
      })

      if (test.switchTo) {
        it('switches URL', done => {
          renderPlayerChange(
            { url: test.url },
            { url: test.switchTo, onPlay: () => done() }
          )
        })
      }

      if (test.error) {
        it('onError', done => {
          renderPlayer({ url: test.error, onError: () => done() })
        })
      }

      it('seekTo, onEnded', done => {
        let duration
        let seeked = false
        renderPlayer({
          url: test.url,
          onDuration: d => { duration = d },
          onProgress: p => {
            if (!seeked && duration && p.playedSeconds > 1) {
              player.seekTo(duration - 1)
              seeked = true
            }
          },
          onEnded: () => done()
        })
      })

      if (test.onSeek) {
        it('onSeek', done => {
          renderPlayer({
            url: test.url,
            onProgress: p => {
              if (p.playedSeconds >= 1) {
                player.seekTo(10)
              }
            },
            onSeek: () => done()
          })
        })

        it('seekTo fraction', done => {
          renderPlayer({
            url: test.url,
            onProgress: p => {
              if (p.playedSeconds >= 1) {
                player.seekTo(0.5)
              }
            },
            onSeek: () => done()
          })
        })

        it('seekOnPlay', done => {
          renderPlayer({
            url: test.url,
            onSeek: () => done()
          }, () => player.seekTo(3))
        })
      }
    })
  }

  describe('instance methods', () => {
    beforeEach(done => {
      renderPlayer({
        url: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
        onReady: () => done()
      })
    })

    it('returns correctly', () => {
      expect(player.getInternalPlayer()).to.exist
      expect(player.getCurrentTime()).to.be.a('number')
      expect(player.getDuration()).to.be.a('number')
    })
  })

  describe('fall back to FilePlayer', () => {
    beforeEach(done => {
      renderPlayer({ url: 'http://example.com/random/path' }, () => done())
    })

    it('falls back to FilePlayer', () => {
      const video = player.getInternalPlayer()
      expect(video).to.be.a('HTMLVideoElement')
      expect(video.src).to.equal('http://example.com/random/path')
    })
  })

  describe('preloading', () => {
    beforeEach(done => {
      renderPlayer({
        url: null,
        config: {
          youtube: { preload: true },
          vimeo: { preload: true },
          dailymotion: { preload: true }
        }
      }, () => done())
    })

    it('renders with preload config', () => {
      expect(player.wrapper).to.be.a('HTMLDivElement')
      expect(player.wrapper.childNodes).to.have.length(3)
      for (let node of player.wrapper.childNodes) {
        expect(node).to.be.a('HTMLDivElement')
        expect(node.style.display).to.equal('none')
      }
    })
  })

  describe('FilePlayer tracks', () => {
    beforeEach(done => {
      renderPlayer({
        url: 'file.mp4',
        playsinline: true, // Not required but good for coverage
        config: { file: {
          tracks: [
            { kind: 'subtitles', src: 'subs/subtitles.en.vtt', srcLang: 'en', default: true },
            { kind: 'subtitles', src: 'subs/subtitles.ja.vtt', srcLang: 'ja' },
            { kind: 'subtitles', src: 'subs/subtitles.de.vtt', srcLang: 'de' }
          ]
        }}
      }, () => done())
    })

    it('renders with tracks', () => {
      expect(player.wrapper).to.be.a('HTMLDivElement')
      expect(player.wrapper.querySelectorAll('track')).to.have.length(3)
    })
  })

  // onPause being called was a bug that has been fixed
  // so skip this test for now
  it.skip('Twitch switches from video to channel', done => {
    renderPlayerChange(
      { url: 'https://www.twitch.tv/videos/106400740' },
      {
        url: 'https://www.twitch.tv/twitchdev',
        onPlay: () => done(),
        onPause: () => done()
      }
    )
  })

  it('does not error when seeking using fraction when duration is available', done => {
    renderPlayer({
      url: 'https://www.youtube.com/watch?v=M7lc1UVf-VE',
      onReady: () => {
        player.getInternalPlayer().getDuration = () => null // Pretty hacky but it does the job
        player.seekTo(0.5)
        setTimeout(done, 1000)
      }
    })
  })

  it('canPlay returns false', () => {
    expect(ReactPlayer.canPlay('http://example.com')).to.be.false
    expect(ReactPlayer.canPlay('file.txt')).to.be.false
    expect(ReactPlayer.canPlay([ 'http://example.com', 'file.txt' ])).to.be.false
  })
})
<commit_msg>Skip DailyMotion volume tests<commit_after>/* eslint-disable no-unused-expressions */

import React from 'react'
import { render, unmountComponentAtNode } from 'react-dom'

import ReactPlayer from '../../src/ReactPlayer'

const { describe, it, expect, beforeEach, afterEach } = window

const TEST_URLS = [
  {
    name: 'FilePlayer',
    url: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv',
    switchTo: 'http://www.sample-videos.com/audio/mp3/crowd-cheering.mp3',
    error: 'http://example.com/error.ogv',
    onSeek: true
  },
  {
    name: 'FilePlayer (multiple string sources)',
    url: [
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv',
      'http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm'
    ]
  },
  {
    name: 'FilePlayer (multiple object sources)',
    url: [
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4', type: 'video/mp4' },
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv', type: 'video/ogv' },
      { src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm', type: 'video/webm' }
    ]
  },
  {
    name: 'FilePlayer (HLS)',
    url: 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8'
  },
  {
    name: 'FilePlayer (DASH)',
    url: 'http://dash.edgesuite.net/envivio/EnvivioDash3/manifest.mpd'
  },
  {
    name: 'YouTube',
    url: 'https://www.youtube.com/watch?v=M7lc1UVf-VE',
    switchTo: 'https://www.youtube.com/watch?v=oUFJJNQGwhk',
    error: 'https://www.youtube.com/watch?v=xxxxxxxxxxx'
  },
  {
    name: 'SoundCloud',
    url: 'https://soundcloud.com/miami-nights-1984/accelerated',
    switchTo: 'https://soundcloud.com/tycho/tycho-awake',
    error: 'https://soundcloud.com/xxxxxxxxxxx/xxxxxxxxxxx'
  },
  {
    name: 'Facebook',
    url: 'https://www.facebook.com/facebook/videos/10153231379946729/',
    switchTo: 'https://www.facebook.com/FacebookDevelopers/videos/10152454700553553/'
  },
  {
    name: 'Vimeo',
    url: 'https://vimeo.com/90509568',
    switchTo: 'https://vimeo.com/169599296',
    error: 'http://vimeo.com/00000000',
    onSeek: true
  },
  {
    name: 'Twitch',
    url: 'https://www.twitch.tv/videos/106400740',
    switchTo: 'https://www.twitch.tv/videos/175705374'
  },
  {
    name: 'Streamable',
    url: 'https://streamable.com/moo',
    switchTo: 'https://streamable.com/ifjh'
  },
  {
    name: 'Wistia',
    url: 'https://home.wistia.com/medias/e4a27b971d',
    switchTo: 'https://home.wistia.com/medias/29b0fbf547',
    onSeek: true
  },
  {
    name: 'DailyMotion',
    url: 'https://www.dailymotion.com/video/x5e9eog',
    switchTo: 'https://www.dailymotion.com/video/x61xx3z',
    error: 'http://www.dailymotion.com/video/x6c0xvb',
    onSeek: true,
    skipVolumeTest: true
  }
]

describe('ReactPlayer', () => {
  let div // Top level div to render into
  let player // Top level player var that will always contain the latest player instance

  // Test util for rendering a player
  function renderPlayer (props, onMount) {
    const ref = p => {
      if (p) {
        player = p
        if (onMount) {
          onMount()
        }
      }
    }
    // Note that playing is set to true by default
    render(<ReactPlayer ref={ref} playing {...props} />, div)
  }

  // Test util for rendering a player and then changing props after a short time
  function renderPlayerChange (props, changeProps, onChange) {
    renderPlayer({
      ...props,
      onProgress: p => {
        if (changeProps && p.playedSeconds > 3) {
          renderPlayer({ ...props, ...changeProps })
          if (onChange) {
            onChange()
          }
        }
        if (props.onProgress) {
          props.onProgress(p)
        }
      }
    })
  }

  beforeEach(() => {
    div = document.createElement('div')
    document.body.appendChild(div)
  })

  afterEach(() => {
    unmountComponentAtNode(div)
    document.body.removeChild(div)
  })

  for (let test of TEST_URLS) {
    const desc = test.skip ? describe.skip : (test.only ? describe.only : describe)
    desc(test.name, () => {
      it('canPlay', () => {
        expect(ReactPlayer.canPlay(test.url)).to.be.true
        expect(ReactPlayer.canPlay('random-string')).to.be.false
      })

      it('onReady, onStart, onPlay, onDuration, onProgress', done => {
        // Use a count object to ensure everything is called at least once
        let count = {}
        const bump = key => {
          count[key] = count[key] || 0
          count[key]++
          if (Object.keys(count).length === 5) {
            done()
          }
        }
        renderPlayer({
          url: test.url,
          onReady: () => bump('onReady'),
          onStart: () => bump('onStart'),
          onPlay: () => bump('onPlay'),
          onDuration: secs => bump('onDuration'),
          onProgress: progress => bump('onProgress')
        })
      })

      it('onPause', done => {
        const props = {
          url: test.url,
          onPause: () => done(),
          playing: true
        }
        renderPlayerChange(props, { playing: false })
      })

      it('should not play if playing is false', done => {
        renderPlayer({
          url: test.url,
          playing: false,
          onReady: () => setTimeout(done, 1000),
          onPlay: () => done('should not play if playing is false')
        })
      })

      it('plays after a delay', done => {
        const playPlayer = () => {
          renderPlayer({
            url: test.url,
            playing: true,
            onPlay: () => done()
          })
        }
        renderPlayer({
          url: test.url,
          playing: false,
          onReady: () => setTimeout(playPlayer, 1000)
        })
      })

      if (!test.skipVolumeTest) {
        it('volume change does not error', done => {
          renderPlayerChange(
            { url: test.url, volume: 1 },
            { volume: 0.5 },
            () => setTimeout(done, 1000)
          )
        })
      }

      it('muted change does not error', done => {
        renderPlayerChange(
          { url: test.url, muted: false },
          { muted: true },
          () => setTimeout(done, 1000)
        )
      })

      it('playbackRate change does not error', done => {
        renderPlayerChange(
          { url: test.url, playbackRate: 1 },
          { playbackRate: 0.5 },
          () => setTimeout(done, 1000)
        )
      })

      it('renders twice without error', done => {
        const go = () => renderPlayer({ url: test.url })
        go()
        setTimeout(go, 1000)
        setTimeout(done, 2000)
      })

      if (test.switchTo) {
        it('switches URL', done => {
          renderPlayerChange(
            { url: test.url },
            { url: test.switchTo, onPlay: () => done() }
          )
        })
      }

      if (test.error) {
        it('onError', done => {
          renderPlayer({ url: test.error, onError: () => done() })
        })
      }

      it('seekTo, onEnded', done => {
        let duration
        let seeked = false
        renderPlayer({
          url: test.url,
          onDuration: d => { duration = d },
          onProgress: p => {
            if (!seeked && duration && p.playedSeconds > 1) {
              player.seekTo(duration - 1)
              seeked = true
            }
          },
          onEnded: () => done()
        })
      })

      if (test.onSeek) {
        it('onSeek', done => {
          renderPlayer({
            url: test.url,
            onProgress: p => {
              if (p.playedSeconds >= 1) {
                player.seekTo(10)
              }
            },
            onSeek: () => done()
          })
        })

        it('seekTo fraction', done => {
          renderPlayer({
            url: test.url,
            onProgress: p => {
              if (p.playedSeconds >= 1) {
                player.seekTo(0.5)
              }
            },
            onSeek: () => done()
          })
        })

        it('seekOnPlay', done => {
          renderPlayer({
            url: test.url,
            onSeek: () => done()
          }, () => player.seekTo(3))
        })
      }
    })
  }

  describe('instance methods', () => {
    beforeEach(done => {
      renderPlayer({
        url: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
        onReady: () => done()
      })
    })

    it('returns correctly', () => {
      expect(player.getInternalPlayer()).to.exist
      expect(player.getCurrentTime()).to.be.a('number')
      expect(player.getDuration()).to.be.a('number')
    })
  })

  describe('fall back to FilePlayer', () => {
    beforeEach(done => {
      renderPlayer({ url: 'http://example.com/random/path' }, () => done())
    })

    it('falls back to FilePlayer', () => {
      const video = player.getInternalPlayer()
      expect(video).to.be.a('HTMLVideoElement')
      expect(video.src).to.equal('http://example.com/random/path')
    })
  })

  describe('preloading', () => {
    beforeEach(done => {
      renderPlayer({
        url: null,
        config: {
          youtube: { preload: true },
          vimeo: { preload: true },
          dailymotion: { preload: true }
        }
      }, () => done())
    })

    it('renders with preload config', () => {
      expect(player.wrapper).to.be.a('HTMLDivElement')
      expect(player.wrapper.childNodes).to.have.length(3)
      for (let node of player.wrapper.childNodes) {
        expect(node).to.be.a('HTMLDivElement')
        expect(node.style.display).to.equal('none')
      }
    })
  })

  describe('FilePlayer tracks', () => {
    beforeEach(done => {
      renderPlayer({
        url: 'file.mp4',
        playsinline: true, // Not required but good for coverage
        config: { file: {
          tracks: [
            { kind: 'subtitles', src: 'subs/subtitles.en.vtt', srcLang: 'en', default: true },
            { kind: 'subtitles', src: 'subs/subtitles.ja.vtt', srcLang: 'ja' },
            { kind: 'subtitles', src: 'subs/subtitles.de.vtt', srcLang: 'de' }
          ]
        }}
      }, () => done())
    })

    it('renders with tracks', () => {
      expect(player.wrapper).to.be.a('HTMLDivElement')
      expect(player.wrapper.querySelectorAll('track')).to.have.length(3)
    })
  })

  // onPause being called was a bug that has been fixed
  // so skip this test for now
  it.skip('Twitch switches from video to channel', done => {
    renderPlayerChange(
      { url: 'https://www.twitch.tv/videos/106400740' },
      {
        url: 'https://www.twitch.tv/twitchdev',
        onPlay: () => done(),
        onPause: () => done()
      }
    )
  })

  it('does not error when seeking using fraction when duration is available', done => {
    renderPlayer({
      url: 'https://www.youtube.com/watch?v=M7lc1UVf-VE',
      onReady: () => {
        player.getInternalPlayer().getDuration = () => null // Pretty hacky but it does the job
        player.seekTo(0.5)
        setTimeout(done, 1000)
      }
    })
  })

  it('canPlay returns false', () => {
    expect(ReactPlayer.canPlay('http://example.com')).to.be.false
    expect(ReactPlayer.canPlay('file.txt')).to.be.false
    expect(ReactPlayer.canPlay([ 'http://example.com', 'file.txt' ])).to.be.false
  })
})
<|endoftext|>


<commit_before>// View model for the page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.app  = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetNameRapport");}, gvm);
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("HomeRapportTitle");}, gvm);
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectRapportName");}, gvm);
    gvm.homeManual = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetRapportManual");}, gvm);

    gvm.userId = null;
    gvm.courseId = null;

    gvm.formdate = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormDate");}, gvm);
    gvm.formmodules = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormModules");}, gvm);
    gvm.formworksheet = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormWorksheet");}, gvm);

    gvm.modules = ko.observableArray([]);
    gvm.assessMethod = ko.observableArray([]);

    gvm.updateModules = function(courseid) {
        $.getJSON('/api/coursestructure/' + courseid, function(data) {
            if (!$.isEmptyObject(data)) {
                gvm.modules.removeAll();

                $.each(data, function(i, item) {
                    var tblObject = {modid: item.id, modname: item.name, competences: new Array(), score: ko.observable('')};
                    if (item.doelstellingen[Object.keys(item.doelstellingen)[0]].id !== null) {
                        gvm.updateCompetences(item.doelstellingen, tblObject.competences);
                    }
                    gvm.modules.push(tblObject);
                });
            }
        });
    }

    gvm.updateCompetences = function(data, competences) {
        $.each(data, function(i, item) {
            var tblObject = {comid: item.id, comname: item.name, criterias: new Array(), score: ko.observable('')}
            if (item.criterias[Object.keys(item.criterias)[0]] !== null) {
                gvm.updateCriteria(item.criterias, tblObject.criterias);
            }
            competences.push(tblObject);
        });
    }

    gvm.updateCriteria = function(data, criteria) {
        $.each(data, function(i, item){
            var tblObject = {critid: item.id, critname: item.name, score: ko.observable('')};
            criteria.push(tblObject);
        });
    }

    gvm.getAssessMethod = function(wid) {
        $.getJSON('/api/worksheetdata/' + wid, function(data) {
            switch(data[0].assessment) {
                case 'A - E':
                    var array = ['A', 'B', 'C', 'D', 'E'];
                    fillArray(array);
                    break;
                case '1 - 10':
                    var array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
                    fillArray(array);
                    break;
                default:
                    //input field
                    break;
            }
        });
    }
}

function fillArray(array) {
    $.each(array, function(i, item) {
        viewModel.assessMethod.push({score: item});
        $('ul.dropdown-assessMethod li a').click(function(e){
            $(this).parent().parent().parent().find('.btn-assessScore span:first').text($(this).text());
            $.each(viewModel.modules, function(i, item) {
                console.log(item.score);
            });
            e.preventDefault();
        });
    });
}

function initPage() {
    $.getJSON('/api/currentuser', function(data) {
        viewModel.userId = data.id;

        viewModel.courseId = $('#storage').attr('data-value');
        viewModel.updateModules(viewModel.courseId);

        var wid = $('#header').attr('data-value');
        viewModel.getAssessMethod(wid);
    });

    $('#date').datepicker();
}
<commit_msg>worksheetassess page - assess modules with observables<commit_after>// View model for the page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.app  = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetNameRapport");}, gvm);
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("HomeRapportTitle");}, gvm);
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectRapportName");}, gvm);
    gvm.homeManual = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetRapportManual");}, gvm);

    gvm.userId = null;
    gvm.courseId = null;

    gvm.formdate = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormDate");}, gvm);
    gvm.formmodules = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormModules");}, gvm);
    gvm.formworksheet = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("FormWorksheet");}, gvm);

    gvm.modules = ko.observableArray([]);
    gvm.assessMethod = ko.observableArray([]);

    gvm.updateModules = function(courseid) {
        $.getJSON('/api/coursestructure/' + courseid, function(data) {
            if (!$.isEmptyObject(data)) {
                gvm.modules.removeAll();

                $.each(data, function(i, item) {
                    var tblObject = {modid: item.id, modname: item.name, competences: new Array(), score: ko.observable('')};
                    if (item.doelstellingen[Object.keys(item.doelstellingen)[0]].id !== null) {
                        gvm.updateCompetences(item.doelstellingen, tblObject.competences);
                    }
                    gvm.modules.push(tblObject);
                });
            }
        });
    }

    gvm.updateCompetences = function(data, competences) {
        $.each(data, function(i, item) {
            var tblObject = {comid: item.id, comname: item.name, criterias: new Array(), score: ko.observable('')}
            if (item.criterias[Object.keys(item.criterias)[0]] !== null) {
                gvm.updateCriteria(item.criterias, tblObject.criterias);
            }
            competences.push(tblObject);
        });
    }

    gvm.updateCriteria = function(data, criteria) {
        $.each(data, function(i, item){
            var tblObject = {critid: item.id, critname: item.name, score: ko.observable('')};
            criteria.push(tblObject);
        });
    }

    gvm.getAssessMethod = function(wid) {
        $.getJSON('/api/worksheetdata/' + wid, function(data) {
            switch(data[0].assessment) {
                case 'A - E':
                    var array = ['A', 'B', 'C', 'D', 'E'];
                    fillArray(array);
                    break;
                case '1 - 10':
                    var array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
                    fillArray(array);
                    break;
                default:
                    //input field
                    break;
            }
        });
    }
}

function fillArray(array) {
    $.each(array, function(i, item) {
        viewModel.assessMethod.push({score: item});
        $('ul.dropdown-assessMethod li a').click(function(e){
            $(this).parent().parent().parent().find('.btn-assessScore span:first').text($(this).text());
            $.each(viewModel.modules(), function(i, item) {
                console.log(item.score);
            });
            e.preventDefault();
        });
    });
}

function initPage() {
    $.getJSON('/api/currentuser', function(data) {
        viewModel.userId = data.id;

        viewModel.courseId = $('#storage').attr('data-value');
        viewModel.updateModules(viewModel.courseId);

        var wid = $('#header').attr('data-value');
        viewModel.getAssessMethod(wid);
    });

    $('#date').datepicker();
}
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback, useState, useEffect } from 'react';
import PropTypes from 'prop-types';

import { StyleSheet, TextInput, View, TouchableOpacity } from 'react-native';
import { MagnifyingGlass, Cancel } from './icons';
import { APP_FONT_FAMILY, SUSSOL_ORANGE } from '../globalStyles/index';
import { debounce } from '../utilities/index';

/**
 * Simple search bar - essentially a wrapper around a text input
 * with a magnifying glass icon and clear button.
 *
 * Debounces input by the user, such that the onChangeText callback
 * is only invoked once every `debounceTimeout`.
 *
 * NOTE: This component is exported MEMOIZED. See below for propsAreEqual
 * implementation.
 *
 * @param {String} color           Color of the entire component (monochrome only).
 * @param {String} onChangeText    Callback for changing text (debounced).
 * @param {String} value           Text value of the search input.
 * @param {String} placeholder     Placeholder value.
 * @param {String} autoFocus       Autofocus this component on mounting.
 * @param {String} textInputStyle  Style of the TextInput component.
 * @param {String} viewStyle       Style of the wrapping View component.
 * @param {String} debounceTimeout Time in milliseconds to debounce the onChangeText callback.
 * @param {Func}   onFocusOrBlur   Callback for onBlur and onFocus events.
 */
export const SearchBarComponent = ({
  color,
  onChangeText,
  value,
  placeholder,
  autoFocus,
  textInputStyle,
  viewStyle,
  debounceTimeout,
  onFocusOrBlur,
  ...textInputProps
}) => {
  const [textValue, setTextValue] = useState('');

  // Set the internal text value if it differs from the passed
  // value. Keeping the search bar text input up to date with
  // user input while debouncing actual filtering of data.
  useEffect(() => {
    if (value !== textValue) setTextValue(value);
  }, [value]);

  const internalViewStyle = { ...viewStyle, borderColor: color };
  const internalTextStyle = { ...textInputStyle, color };

  // To debounce a function, need to use the same function as the
  // last call. Memoize this to keep the reference semi-stable. Keeping
  // this reference is an optimization, not a requirement so can change.
  const debouncedCallback = useCallback(
    debounce(newValue => onChangeText(newValue), debounceTimeout),
    []
  );

  // On text change, set the internal text value, and call the debounced
  // callback. Keep the users input upto date, but optimize filtering.
  const onChangeTextCallback = useCallback(newValue => {
    setTextValue(newValue);
    debouncedCallback(newValue);
  }, []);

  return (
    <View style={internalViewStyle}>
      <MagnifyingGlass color={color} />
      <TextInput
        {...textInputProps}
        style={internalTextStyle}
        value={textValue}
        underlineColorAndroid="transparent"
        placeholderTextColor={color}
        placeholder={placeholder}
        onChangeText={onChangeTextCallback}
        autoFocus={autoFocus}
        onFocus={onFocusOrBlur}
        onBlur={onFocusOrBlur}
        selectTextOnFocus
      />
      {!!textValue && (
        <TouchableOpacity onPress={() => onChangeTextCallback('')}>
          <Cancel />
        </TouchableOpacity>
      )}
    </View>
  );
};

/**
 * Only re-render this component when the value or onFocusOrBlur
 * props change.
 */
const propsAreEqual = (
  { value: prevValue, onFocusOrBlur: prevOnFocusOrBlur },
  { value: nextValue, onFocusOrBlur: nextOnFocusOrBlur }
) => {
  const valuesAreEqual = prevValue === nextValue;
  const onFocusOrBlurAreEqual = prevOnFocusOrBlur === nextOnFocusOrBlur;
  return valuesAreEqual && onFocusOrBlurAreEqual;
};

export const SearchBar = React.memo(SearchBarComponent, propsAreEqual);

const defaultStyles = StyleSheet.create({
  container: {
    borderBottomWidth: 1,
    flexDirection: 'row',
    alignItems: 'center',
    marginHorizontal: 5,
  },
  textInput: {
    height: 40,
    fontSize: 20,
    fontFamily: APP_FONT_FAMILY,
    backgroundColor: 'rgba(0, 0, 0, 0)',
    flex: 1,
  },
});

SearchBarComponent.defaultProps = {
  debounceTimeout: 250,
  textInputStyle: defaultStyles.textInput,
  viewStyle: defaultStyles.container,
  color: SUSSOL_ORANGE,
  value: '',
  placeholder: '',
  autoFocus: false,
  onFocusOrBlur: null,
};

SearchBarComponent.propTypes = {
  color: PropTypes.string,
  debounceTimeout: PropTypes.number,
  onChangeText: PropTypes.func.isRequired,
  value: PropTypes.string,
  placeholder: PropTypes.string,
  autoFocus: PropTypes.bool,
  textInputStyle: PropTypes.object,
  viewStyle: PropTypes.object,
  onFocusOrBlur: PropTypes.func,
};
<commit_msg>change color to white<commit_after>/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback, useState, useEffect } from 'react';
import PropTypes from 'prop-types';

import { StyleSheet, TextInput, View, TouchableOpacity } from 'react-native';
import { MagnifyingGlass, Cancel } from './icons';
import { APP_FONT_FAMILY, WHITE } from '../globalStyles/index';
import { debounce } from '../utilities/index';

/**
 * Simple search bar - essentially a wrapper around a text input
 * with a magnifying glass icon and clear button.
 *
 * Debounces input by the user, such that the onChangeText callback
 * is only invoked once every `debounceTimeout`.
 *
 * NOTE: This component is exported MEMOIZED. See below for propsAreEqual
 * implementation.
 *
 * @param {String} color           Color of the entire component (monochrome only).
 * @param {String} onChangeText    Callback for changing text (debounced).
 * @param {String} value           Text value of the search input.
 * @param {String} placeholder     Placeholder value.
 * @param {String} autoFocus       Autofocus this component on mounting.
 * @param {String} textInputStyle  Style of the TextInput component.
 * @param {String} viewStyle       Style of the wrapping View component.
 * @param {String} debounceTimeout Time in milliseconds to debounce the onChangeText callback.
 * @param {Func}   onFocusOrBlur   Callback for onBlur and onFocus events.
 */
export const SearchBarComponent = ({
  color,
  onChangeText,
  value,
  placeholder,
  autoFocus,
  textInputStyle,
  viewStyle,
  debounceTimeout,
  onFocusOrBlur,
  ...textInputProps
}) => {
  const [textValue, setTextValue] = useState('');

  // Set the internal text value if it differs from the passed
  // value. Keeping the search bar text input up to date with
  // user input while debouncing actual filtering of data.
  useEffect(() => {
    if (value !== textValue) setTextValue(value);
  }, [value]);

  const internalViewStyle = { ...viewStyle, borderColor: color };
  const internalTextStyle = { ...textInputStyle, color };

  // To debounce a function, need to use the same function as the
  // last call. Memoize this to keep the reference semi-stable. Keeping
  // this reference is an optimization, not a requirement so can change.
  const debouncedCallback = useCallback(
    debounce(newValue => onChangeText(newValue), debounceTimeout),
    []
  );

  // On text change, set the internal text value, and call the debounced
  // callback. Keep the users input upto date, but optimize filtering.

  const onChangeTextCallback = useCallback(newValue => {
    setTextValue(newValue);
    debouncedCallback(newValue);
  }, []);

  return (
    <View style={internalViewStyle}>
      <MagnifyingGlass color={color} />
      <TextInput
        {...textInputProps}
        style={internalTextStyle}
        value={textValue}
        underlineColorAndroid="transparent"
        placeholderTextColor={color}
        placeholder={placeholder}
        onChangeText={onChangeTextCallback}
        autoFocus={autoFocus}
        onFocus={onFocusOrBlur}
        onBlur={onFocusOrBlur}
        selectTextOnFocus
      />
      {!!textValue && (
        <TouchableOpacity onPress={() => onChangeTextCallback('')}>
          <Cancel />
        </TouchableOpacity>
      )}
    </View>
  );
};

/**
 * Only re-render this component when the value or onFocusOrBlur
 * props change.
 */
const propsAreEqual = (
  { value: prevValue, onFocusOrBlur: prevOnFocusOrBlur },
  { value: nextValue, onFocusOrBlur: nextOnFocusOrBlur }
) => {
  const valuesAreEqual = prevValue === nextValue;
  const onFocusOrBlurAreEqual = prevOnFocusOrBlur === nextOnFocusOrBlur;
  return valuesAreEqual && onFocusOrBlurAreEqual;
};

export const SearchBar = React.memo(SearchBarComponent, propsAreEqual);

const defaultStyles = StyleSheet.create({
  container: {
    borderBottomWidth: 1,
    flexDirection: 'row',
    alignItems: 'center',
    marginHorizontal: 5,
  },
  textInput: {
    height: 40,
    fontSize: 20,
    fontFamily: APP_FONT_FAMILY,
    backgroundColor: 'rgba(0, 0, 0, 0)',
    flex: 1,
  },
});

SearchBarComponent.defaultProps = {
  debounceTimeout: 250,
  textInputStyle: defaultStyles.textInput,
  viewStyle: defaultStyles.container,
  color: WHITE,
  value: '',
  placeholder: '',
  autoFocus: false,
  onFocusOrBlur: null,
};

SearchBarComponent.propTypes = {
  color: PropTypes.string,
  debounceTimeout: PropTypes.number,
  onChangeText: PropTypes.func.isRequired,
  value: PropTypes.string,
  placeholder: PropTypes.string,
  autoFocus: PropTypes.bool,
  textInputStyle: PropTypes.object,
  viewStyle: PropTypes.object,
  onFocusOrBlur: PropTypes.func,
};
<|endoftext|>


<commit_before><!DOCTYPE html>

    <title>RunningTotal</title>

<commit_msg>Updated README, added screenshot<commit_after><|endoftext|>


<commit_before>'use strict';

const functionHelper = require('./functionHelper.js');
const LambdaContext = require('./LambdaContext.js');
const { createUniqueId } = require('./utils/index.js');

// https://docs.aws.amazon.com/lambda/latest/dg/limits.html
// default function timeout in seconds
const DEFAULT_TIMEOUT = 900; // 15 min

module.exports = class LambdaFunction {
  constructor(config, options) {
    this._config = config;
    this._executionTimeEnded = null;
    this._executionTimeStarted = null;
    this._options = options;
    this._requestId = null;
  }

  _startExecutionTimer() {
    this._executionTimeStarted = new Date().getTime();
  }

  _stopExecutionTimer() {
    this._executionTimeEnded = new Date().getTime();
  }

  addEvent(event) {
    this._event = event;
  }

  getExecutionTimeInMillis() {
    return this._executionTimeEnded - this._executionTimeStarted;
  }

  getRequestId() {
    return this._requestId;
  }

  async runHandler() {
    const {
      functionName,
      lambdaName,
      memorySize,
      timeout = DEFAULT_TIMEOUT,
    } = this._config;

    this._requestId = createUniqueId();

    const lambdaContext = new LambdaContext({
      getRemainingTimeInMillis: () => {
        const timeEnd = this._executionTimeStarted + timeout * 1000;
        const time = timeEnd - new Date().getTime();

        // just return 0 for now if we are beyond alotted time (timeout)
        return time > 0 ? time : 0;
      },
      lambdaName,
      memorySize,
      requestId: this._requestId,
    });

    const contextCalled = new Promise((resolve, reject) => {
      lambdaContext.once('contextCalled', (err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data);
      });
    });

    let callback;

    const callbackCalled = new Promise((resolve, reject) => {
      callback = (err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data);
      };
    });

    const context = lambdaContext.getContext();

    this._startExecutionTimer();

    const handler = functionHelper.createHandler(this._config, this._options);

    let result;

    try {
      result = handler(this._event, context, callback);
    } catch (err) {
      // this only executes when we have an exception caused by syncronous code
      // TODO logging
      console.log(err);
      throw new Error(`Uncaught error in '${functionName}' handler.`);
    }

    // // not a Promise, which is not supported by aws
    // if (result == null || typeof result.then !== 'function') {
    //   throw new Error(`Syncronous function execution is not supported.`);
    // }

    const callbacks = [contextCalled, callbackCalled];

    // Promise was returned
    if (result != null && typeof result.then === 'function') {
      callbacks.push(result);
    }

    let callbackResult;

    try {
      callbackResult = await Promise.race(callbacks);
    } catch (err) {
      // TODO logging
      console.log(err);
      throw err;
    }

    this._stopExecutionTimer();

    return callbackResult;
  }
};
<commit_msg>Simplify, combine callback and calls from context<commit_after>'use strict';

const functionHelper = require('./functionHelper.js');
const LambdaContext = require('./LambdaContext.js');
const { createUniqueId } = require('./utils/index.js');

// https://docs.aws.amazon.com/lambda/latest/dg/limits.html
// default function timeout in seconds
const DEFAULT_TIMEOUT = 900; // 15 min

module.exports = class LambdaFunction {
  constructor(config, options) {
    this._config = config;
    this._executionTimeEnded = null;
    this._executionTimeStarted = null;
    this._options = options;
    this._requestId = null;
  }

  _startExecutionTimer() {
    this._executionTimeStarted = new Date().getTime();
  }

  _stopExecutionTimer() {
    this._executionTimeEnded = new Date().getTime();
  }

  addEvent(event) {
    this._event = event;
  }

  getExecutionTimeInMillis() {
    return this._executionTimeEnded - this._executionTimeStarted;
  }

  getRequestId() {
    return this._requestId;
  }

  async runHandler() {
    const {
      functionName,
      lambdaName,
      memorySize,
      timeout = DEFAULT_TIMEOUT,
    } = this._config;

    this._requestId = createUniqueId();

    const lambdaContext = new LambdaContext({
      getRemainingTimeInMillis: () => {
        const timeEnd = this._executionTimeStarted + timeout * 1000;
        const time = timeEnd - new Date().getTime();

        // just return 0 for now if we are beyond alotted time (timeout)
        return time > 0 ? time : 0;
      },
      lambdaName,
      memorySize,
      requestId: this._requestId,
    });

    let callback;

    const callbackCalled = new Promise((resolve, reject) => {
      callback = (err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data);
      };

      lambdaContext.once('contextCalled', (err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data);
      });
    });

    const context = lambdaContext.getContext();

    this._startExecutionTimer();

    const handler = functionHelper.createHandler(this._config, this._options);

    let result;

    try {
      result = handler(this._event, context, callback);
    } catch (err) {
      // this only executes when we have an exception caused by syncronous code
      // TODO logging
      console.log(err);
      throw new Error(`Uncaught error in '${functionName}' handler.`);
    }

    // // not a Promise, which is not supported by aws
    // if (result == null || typeof result.then !== 'function') {
    //   throw new Error(`Syncronous function execution is not supported.`);
    // }

    const callbacks = [callbackCalled];

    // Promise was returned
    if (result != null && typeof result.then === 'function') {
      callbacks.push(result);
    }

    let callbackResult;

    try {
      callbackResult = await Promise.race(callbacks);
    } catch (err) {
      // TODO logging
      console.log(err);
      throw err;
    }

    this._stopExecutionTimer();

    return callbackResult;
  }
};
<|endoftext|>