<commit_before>/*
 * angular-google-places-autocomplete
 *
 * Copyright (c) 2014 "kuhnza" David Kuhn
 * Licensed under the MIT license.
 * https://github.com/kuhnza/angular-google-places-autocomplete/blob/master/LICENSE
 */

 'use strict';

angular.module('google.places', [])
	/**
	 * DI wrapper around global google places library.
	 *
	 * Note: requires the Google Places API to already be loaded on the page.
	 */
	.factory('googlePlacesApi', ['$window', function ($window) {
        if (!$window.google) throw 'Global `google` var missing. Did you forget to include the places API script?';

		return $window.google;
	}])

	/**
	 * Autocomplete directive. Use like this:
	 *
	 * <input type="text" g-places-autocomplete ng-model="myScopeVar" />
	 */
	.directive('gPlacesAutocomplete',
        [ '$parse', '$compile', '$timeout', '$document', 'googlePlacesApi',
        function ($parse, $compile, $timeout, $document, google) {

            return {
                restrict: 'A',
                require: '^ngModel',
                scope: {
                    model: '=ngModel',
                    options: '=?',
                    forceSelection: '=?',
                    customPlaces: '=?'
                },
                controller: ['$scope', function ($scope) {}],
                link: function ($scope, element, attrs, controller) {
                    var keymap = {
                            tab: 9,
                            enter: 13,
                            esc: 27,
                            up: 38,
                            down: 40
                        },
                        hotkeys = [keymap.tab, keymap.enter, keymap.esc, keymap.up, keymap.down],
                        autocompleteService = new google.maps.places.AutocompleteService(),
                        placesService = new google.maps.places.PlacesService(element[0]);

                    (function init() {
                        $scope.query = '';
                        $scope.predictions = [];
                        $scope.input = element;
                        $scope.options = $scope.options || {};

                        initAutocompleteDrawer();
                        initEvents();
                        initNgModelController();
                    }());

                    function initEvents() {
                        element.bind('keydown', onKeydown);
                        element.bind('blur', onBlur);
                        element.bind('submit', onBlur);

                        $scope.$watch('selected', select);
                    }

                    function initAutocompleteDrawer() {
                        // Drawer element used to display predictions
                        var drawerElement = angular.element('

'),
                            body = angular.element($document[0].body),
                            $drawer;

                        drawerElement.attr({
                            input: 'input',
                            query: 'query',
                            predictions: 'predictions',
                            active: 'active',
                            selected: 'selected'
                        });

                        $drawer = $compile(drawerElement)($scope);
                        body.append($drawer);  // Append to DOM
                    }

                    function initNgModelController() {
                        controller.$parsers.push(parse);
                        controller.$formatters.push(format);
                        controller.$render = render;
                    }

                    function onKeydown(event) {
                        if ($scope.predictions.length === 0 || indexOf(hotkeys, event.which) === -1) {
                            return;
                        }

                        event.preventDefault();

                        if (event.which === keymap.down) {
                            $scope.active = ($scope.active + 1) % $scope.predictions.length;
                            $scope.$digest();
                        } else if (event.which === keymap.up) {
                            $scope.active = ($scope.active ? $scope.active : $scope.predictions.length) - 1;
                            $scope.$digest();
                        } else if (event.which === 13 || event.which === 9) {
                            if ($scope.forceSelection) {
                                $scope.active = ($scope.active === -1) ? 0 : $scope.active;
                            }

                            $scope.$apply(function () {
                                $scope.selected = $scope.active;

                                if ($scope.selected === -1) {
                                    clearPredictions();
                                }
                            });
                        } else if (event.which === 27) {
                            event.stopPropagation();
                            clearPredictions();
                            $scope.$digest();
                        }
                    }

                    function onBlur(event) {
                        if ($scope.predictions.length === 0) {
                            if ($scope.forceSelection) {
                                var phase = $scope.$root.$$phase;
                                var fn = function() {
                                    $scope.model = '';
                                }
                                if(phase == '$apply' || phase == '$digest') {
                                    fn();
                                } else {
                                    $scope.$apply(fn);
                                }
                            }
                            return;
                        }

                        if ($scope.forceSelection) {
                            $scope.selected = ($scope.selected === -1) ? 0 : $scope.selected;
                        }

                        $scope.$digest();

                        $scope.$apply(function () {
                            if ($scope.selected === -1) {
                                clearPredictions();
                            }
                        });
                    }

                    function select() {
                        var prediction;

                        prediction = $scope.predictions[$scope.selected];
                        if (!prediction) return;

                        if (prediction.is_custom) {
                            $scope.model = prediction.place;
                            $scope.$emit('g-places-autocomplete:select', prediction.place);
                        } else {
                            placesService.getDetails({ placeId: prediction.place_id }, function (place, status) {
                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $scope.$apply(function () {
                                        $scope.model = place;
                                        $scope.$emit('g-places-autocomplete:select', place);
                                    });
                                }
                            });
                        }

                        clearPredictions();
                    }

                    function parse(viewValue) {
                        var request;

                        if (!(viewValue && isString(viewValue))) return viewValue;

                        $scope.query = viewValue;

                        request = angular.extend({ input: viewValue }, $scope.options);
                        autocompleteService.getPlacePredictions(request, function (predictions, status) {
                            $scope.$apply(function () {
                                var customPlacePredictions;

                                clearPredictions();

                                if ($scope.customPlaces) {
                                    customPlacePredictions = getCustomPlacePredictions($scope.query);
                                    $scope.predictions.push.apply($scope.predictions, customPlacePredictions);
                                }

                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $scope.predictions.push.apply($scope.predictions, predictions);
                                }

                                if ($scope.predictions.length > 5) {
                                    $scope.predictions.length = 5;  // trim predictions down to size
                                }
                            });
                        });

                        return viewValue;
                    }

                    function format(modelValue) {
                        var viewValue = "";

                        if (isString(modelValue)) {
                            viewValue = modelValue;
                        } else if (isObject(modelValue)) {
                            viewValue = modelValue.formatted_address;
                        }

                        return viewValue;
                    }

                    function render() {
                        return element.val(controller.$viewValue);
                    }

                    function clearPredictions() {
                        $scope.active = -1;
                        $scope.selected = -1;
                        $scope.predictions.length = 0;
                    }

                    function getCustomPlacePredictions(query) {
                        var predictions = [],
                            place, match, i;

                        for (i = 0; i < $scope.customPlaces.length; i++) {
                            place = $scope.customPlaces[i];

                            match = getCustomPlaceMatches(query, place);
                            if (match.matched_substrings.length > 0) {
                                predictions.push({
                                    is_custom: true,
                                    custom_prediction_label: place.custom_prediction_label || '(Custom Non-Google Result)',  // required by https://developers.google.com/maps/terms § 10.1.1 (d)
                                    description: place.formatted_address,
                                    place: place,
                                    matched_substrings: match.matched_substrings,
                                    terms: match.terms
                                });
                            }
                        }

                        return predictions;
                    }

                    function getCustomPlaceMatches(query, place) {
                        var q = query + '',  // make a copy so we don't interfere with subsequent matches
                            terms = [],
                            matched_substrings = [],
                            fragment,
                            termFragments,
                            i;

                        termFragments = place.formatted_address.split(',');
                        for (i = 0; i < termFragments.length; i++) {
                            fragment = termFragments[i].trim();

                            if (q.length > 0) {
                                if (fragment.length >= q.length) {
                                    if (startsWith(fragment, q)) {
                                        matched_substrings.push({ length: q.length, offset: i });
                                    }
                                    q = '';  // no more matching to do
                                } else {
                                    if (startsWith(q, fragment)) {
                                        matched_substrings.push({ length: fragment.length, offset: i });
                                        q = q.replace(fragment, '').trim();
                                    } else {
                                        q = '';  // no more matching to do
                                    }
                                }
                            }

                            terms.push({
                                value: fragment,
                                offset: place.formatted_address.indexOf(fragment)
                            });
                        }

                        return {
                            matched_substrings: matched_substrings,
                            terms: terms
                        };
                    }

                    function isString(val) {
                        return Object.prototype.toString.call(val) == '[object String]';
                    }

                    function isObject(val) {
                        return Object.prototype.toString.call(val) == '[object Object]';
                    }

                    function indexOf(array, item) {
                        var i, length;

                        if (array == null) return -1;

                        length = array.length;
                        for (i = 0; i < length; i++) {
                            if (array[i] === item) return i;
                        }
                        return -1;
                    }

                    function startsWith(string1, string2) {
                        return toLower(string1).lastIndexOf(toLower(string2), 0) === 0;
                    }

                    function toLower(string) {
                        return (string == null) ? "" : string.toLowerCase();
                    }
                }
            }
        }
    ])

    .directive('gPlacesAutocompleteDrawer', ['$window', '$document', function ($window, $document) {
        var TEMPLATE = [
            '
',
            '
',
            '
',
            '
'
        ];

        return {
            restrict: 'A',
            scope:{
                input: '=',
                query: '=',
                predictions: '=',
                active: '=',
                selected: '='
            },
            template: TEMPLATE.join(''),
            link: function ($scope, element) {
                element.bind('mousedown', function (event) {
                    event.preventDefault();  // prevent blur event from firing when clicking selection
                });

                $scope.isOpen = function () {
                    return $scope.predictions.length > 0;
                };

                $scope.isActive = function (index) {
                    return $scope.active === index;
                };

                $scope.selectActive = function (index) {
                    $scope.active = index;
                };

                $scope.selectPrediction = function (index) {
                    $scope.selected = index;
                };

                $scope.$watch('predictions', function () {
                    $scope.position = getDrawerPosition($scope.input);
                }, true);

                function getDrawerPosition(element) {
                    var domEl = element[0],
                        rect = domEl.getBoundingClientRect(),
                        docEl = $document[0].documentElement,
                        body = $document[0].body,
                        scrollTop = $window.pageYOffset || docEl.scrollTop || body.scrollTop,
                        scrollLeft = $window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

                    return {
                        width: rect.width,
                        height: rect.height,
                        top: rect.top + rect.height + scrollTop,
                        left: rect.left + scrollLeft
                    };
                }
            }
        }
    }])

    .directive('gPlacesAutocompletePrediction', [function () {
        var TEMPLATE = [
            '',
            '',
            '{{term.value | trailingComma:!$last}} ',
            ' {{prediction.custom_prediction_label}}'
        ];

        return {
            restrict: 'A',
            scope:{
                index:'=',
                prediction:'=',
                query:'='
            },
            template: TEMPLATE.join('')
        }
    }])

    .filter('highlightMatched', ['$sce', function ($sce) {
        return function (prediction) {
            var matchedPortion = '',
                unmatchedPortion = '',
                matched;

            if (prediction.matched_substrings.length > 0 && prediction.terms.length > 0) {
                matched = prediction.matched_substrings[0];
                matchedPortion = prediction.terms[0].value.substr(matched.offset, matched.length);
                unmatchedPortion = prediction.terms[0].value.substr(matched.offset + matched.length);
            }

            return $sce.trustAsHtml('' + matchedPortion + '' + unmatchedPortion);
        }
    }])

    .filter('unmatchedTermsOnly', [function () {
        return function (terms, prediction) {
            var i, term, filtered = [];

            for (i = 0; i < terms.length; i++) {
                term = terms[i];
                if (prediction.matched_substrings.length > 0 && term.offset > prediction.matched_substrings[0].length) {
                    filtered.push(term);
                }
            }

            return filtered;
        }
    }])

    .filter('trailingComma', [function () {
        return function (input, condition) {
            return (condition) ? input + ',' : input;
        }
    }]);

<commit_msg>#4: Force the predictions drawer position update<commit_after>/*
 * angular-google-places-autocomplete
 *
 * Copyright (c) 2014 "kuhnza" David Kuhn
 * Licensed under the MIT license.
 * https://github.com/kuhnza/angular-google-places-autocomplete/blob/master/LICENSE
 */

 'use strict';

angular.module('google.places', [])
	/**
	 * DI wrapper around global google places library.
	 *
	 * Note: requires the Google Places API to already be loaded on the page.
	 */
	.factory('googlePlacesApi', ['$window', function ($window) {
        if (!$window.google) throw 'Global `google` var missing. Did you forget to include the places API script?';

		return $window.google;
	}])

	/**
	 * Autocomplete directive. Use like this:
	 *
	 * <input type="text" g-places-autocomplete ng-model="myScopeVar" />
	 */
	.directive('gPlacesAutocomplete',
        [ '$parse', '$compile', '$timeout', '$document', 'googlePlacesApi',
        function ($parse, $compile, $timeout, $document, google) {

            return {
                restrict: 'A',
                require: '^ngModel',
                scope: {
                    model: '=ngModel',
                    options: '=?',
                    forceSelection: '=?',
                    customPlaces: '=?'
                },
                controller: ['$scope', function ($scope) {}],
                link: function ($scope, element, attrs, controller) {
                    var keymap = {
                            tab: 9,
                            enter: 13,
                            esc: 27,
                            up: 38,
                            down: 40
                        },
                        hotkeys = [keymap.tab, keymap.enter, keymap.esc, keymap.up, keymap.down],
                        autocompleteService = new google.maps.places.AutocompleteService(),
                        placesService = new google.maps.places.PlacesService(element[0]);

                    (function init() {
                        $scope.query = '';
                        $scope.predictions = [];
                        $scope.input = element;
                        $scope.options = $scope.options || {};

                        initAutocompleteDrawer();
                        initEvents();
                        initNgModelController();
                    }());

                    function initEvents() {
                        element.bind('keydown', onKeydown);
                        element.bind('blur', onBlur);
                        element.bind('submit', onBlur);

                        $scope.$watch('selected', select);
                    }

                    function initAutocompleteDrawer() {
                        // Drawer element used to display predictions
                        var drawerElement = angular.element('

'),
                            body = angular.element($document[0].body),
                            $drawer;

                        drawerElement.attr({
                            input: 'input',
                            query: 'query',
                            predictions: 'predictions',
                            active: 'active',
                            selected: 'selected'
                        });

                        $drawer = $compile(drawerElement)($scope);
                        body.append($drawer);  // Append to DOM
                    }

                    function initNgModelController() {
                        controller.$parsers.push(parse);
                        controller.$formatters.push(format);
                        controller.$render = render;
                    }

                    function onKeydown(event) {
                        if ($scope.predictions.length === 0 || indexOf(hotkeys, event.which) === -1) {
                            return;
                        }

                        event.preventDefault();

                        if (event.which === keymap.down) {
                            $scope.active = ($scope.active + 1) % $scope.predictions.length;
                            $scope.$digest();
                        } else if (event.which === keymap.up) {
                            $scope.active = ($scope.active ? $scope.active : $scope.predictions.length) - 1;
                            $scope.$digest();
                        } else if (event.which === 13 || event.which === 9) {
                            if ($scope.forceSelection) {
                                $scope.active = ($scope.active === -1) ? 0 : $scope.active;
                            }

                            $scope.$apply(function () {
                                $scope.selected = $scope.active;

                                if ($scope.selected === -1) {
                                    clearPredictions();
                                }
                            });
                        } else if (event.which === 27) {
                            event.stopPropagation();
                            clearPredictions();
                            $scope.$digest();
                        }
                    }

                    function onBlur(event) {
                        if ($scope.predictions.length === 0) {
                            if ($scope.forceSelection) {
                                var phase = $scope.$root.$$phase;
                                var fn = function() {
                                    $scope.model = '';
                                }
                                if(phase == '$apply' || phase == '$digest') {
                                    fn();
                                } else {
                                    $scope.$apply(fn);
                                }
                            }
                            return;
                        }

                        if ($scope.forceSelection) {
                            $scope.selected = ($scope.selected === -1) ? 0 : $scope.selected;
                        }

                        $scope.$digest();

                        $scope.$apply(function () {
                            if ($scope.selected === -1) {
                                clearPredictions();
                            }
                        });
                    }

                    function select() {
                        var prediction;

                        prediction = $scope.predictions[$scope.selected];
                        if (!prediction) return;

                        if (prediction.is_custom) {
                            $scope.model = prediction.place;
                            $scope.$emit('g-places-autocomplete:select', prediction.place);
                        } else {
                            placesService.getDetails({ placeId: prediction.place_id }, function (place, status) {
                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $scope.$apply(function () {
                                        $scope.model = place;
                                        $scope.$emit('g-places-autocomplete:select', place);
                                    });
                                }
                            });
                        }

                        clearPredictions();
                    }

                    function parse(viewValue) {
                        var request;

                        if (!(viewValue && isString(viewValue))) return viewValue;

                        $scope.query = viewValue;

                        request = angular.extend({ input: viewValue }, $scope.options);
                        autocompleteService.getPlacePredictions(request, function (predictions, status) {
                            $scope.$apply(function () {
                                var customPlacePredictions;

                                clearPredictions();

                                if ($scope.customPlaces) {
                                    customPlacePredictions = getCustomPlacePredictions($scope.query);
                                    $scope.predictions.push.apply($scope.predictions, customPlacePredictions);
                                }

                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $scope.predictions.push.apply($scope.predictions, predictions);
                                }

                                if ($scope.predictions.length > 5) {
                                    $scope.predictions.length = 5;  // trim predictions down to size
                                }
                            });
                        });

                        return viewValue;
                    }

                    function format(modelValue) {
                        var viewValue = "";

                        if (isString(modelValue)) {
                            viewValue = modelValue;
                        } else if (isObject(modelValue)) {
                            viewValue = modelValue.formatted_address;
                        }

                        return viewValue;
                    }

                    function render() {
                        return element.val(controller.$viewValue);
                    }

                    function clearPredictions() {
                        $scope.active = -1;
                        $scope.selected = -1;
                        $scope.predictions = [];
                    }

                    function getCustomPlacePredictions(query) {
                        var predictions = [],
                            place, match, i;

                        for (i = 0; i < $scope.customPlaces.length; i++) {
                            place = $scope.customPlaces[i];

                            match = getCustomPlaceMatches(query, place);
                            if (match.matched_substrings.length > 0) {
                                predictions.push({
                                    is_custom: true,
                                    custom_prediction_label: place.custom_prediction_label || '(Custom Non-Google Result)',  // required by https://developers.google.com/maps/terms § 10.1.1 (d)
                                    description: place.formatted_address,
                                    place: place,
                                    matched_substrings: match.matched_substrings,
                                    terms: match.terms
                                });
                            }
                        }

                        return predictions;
                    }

                    function getCustomPlaceMatches(query, place) {
                        var q = query + '',  // make a copy so we don't interfere with subsequent matches
                            terms = [],
                            matched_substrings = [],
                            fragment,
                            termFragments,
                            i;

                        termFragments = place.formatted_address.split(',');
                        for (i = 0; i < termFragments.length; i++) {
                            fragment = termFragments[i].trim();

                            if (q.length > 0) {
                                if (fragment.length >= q.length) {
                                    if (startsWith(fragment, q)) {
                                        matched_substrings.push({ length: q.length, offset: i });
                                    }
                                    q = '';  // no more matching to do
                                } else {
                                    if (startsWith(q, fragment)) {
                                        matched_substrings.push({ length: fragment.length, offset: i });
                                        q = q.replace(fragment, '').trim();
                                    } else {
                                        q = '';  // no more matching to do
                                    }
                                }
                            }

                            terms.push({
                                value: fragment,
                                offset: place.formatted_address.indexOf(fragment)
                            });
                        }

                        return {
                            matched_substrings: matched_substrings,
                            terms: terms
                        };
                    }

                    function isString(val) {
                        return Object.prototype.toString.call(val) == '[object String]';
                    }

                    function isObject(val) {
                        return Object.prototype.toString.call(val) == '[object Object]';
                    }

                    function indexOf(array, item) {
                        var i, length;

                        if (array == null) return -1;

                        length = array.length;
                        for (i = 0; i < length; i++) {
                            if (array[i] === item) return i;
                        }
                        return -1;
                    }

                    function startsWith(string1, string2) {
                        return toLower(string1).lastIndexOf(toLower(string2), 0) === 0;
                    }

                    function toLower(string) {
                        return (string == null) ? "" : string.toLowerCase();
                    }
                }
            }
        }
    ])

    .directive('gPlacesAutocompleteDrawer', ['$window', '$document', function ($window, $document) {
        var TEMPLATE = [
            '
',
            '
',
            '
',
            '
'
        ];

        return {
            restrict: 'A',
            scope:{
                input: '=',
                query: '=',
                predictions: '=',
                active: '=',
                selected: '='
            },
            template: TEMPLATE.join(''),
            link: function ($scope, element) {
                element.bind('mousedown', function (event) {
                    event.preventDefault();  // prevent blur event from firing when clicking selection
                });

                $scope.isOpen = function () {
                    return $scope.predictions.length > 0;
                };

                $scope.isActive = function (index) {
                    return $scope.active === index;
                };

                $scope.selectActive = function (index) {
                    $scope.active = index;
                };

                $scope.selectPrediction = function (index) {
                    $scope.selected = index;
                };

                $scope.$watch('predictions', function () {
                    $scope.position = getDrawerPosition($scope.input);
                }, true);

                function getDrawerPosition(element) {
                    var domEl = element[0],
                        rect = domEl.getBoundingClientRect(),
                        docEl = $document[0].documentElement,
                        body = $document[0].body,
                        scrollTop = $window.pageYOffset || docEl.scrollTop || body.scrollTop,
                        scrollLeft = $window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

                    return {
                        width: rect.width,
                        height: rect.height,
                        top: rect.top + rect.height + scrollTop,
                        left: rect.left + scrollLeft
                    };
                }
            }
        }
    }])

    .directive('gPlacesAutocompletePrediction', [function () {
        var TEMPLATE = [
            '',
            '',
            '{{term.value | trailingComma:!$last}} ',
            ' {{prediction.custom_prediction_label}}'
        ];

        return {
            restrict: 'A',
            scope:{
                index:'=',
                prediction:'=',
                query:'='
            },
            template: TEMPLATE.join('')
        }
    }])

    .filter('highlightMatched', ['$sce', function ($sce) {
        return function (prediction) {
            var matchedPortion = '',
                unmatchedPortion = '',
                matched;

            if (prediction.matched_substrings.length > 0 && prediction.terms.length > 0) {
                matched = prediction.matched_substrings[0];
                matchedPortion = prediction.terms[0].value.substr(matched.offset, matched.length);
                unmatchedPortion = prediction.terms[0].value.substr(matched.offset + matched.length);
            }

            return $sce.trustAsHtml('' + matchedPortion + '' + unmatchedPortion);
        }
    }])

    .filter('unmatchedTermsOnly', [function () {
        return function (terms, prediction) {
            var i, term, filtered = [];

            for (i = 0; i < terms.length; i++) {
                term = terms[i];
                if (prediction.matched_substrings.length > 0 && term.offset > prediction.matched_substrings[0].length) {
                    filtered.push(term);
                }
            }

            return filtered;
        }
    }])

    .filter('trailingComma', [function () {
        return function (input, condition) {
            return (condition) ? input + ',' : input;
        }
    }]);

<|endoftext|>


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

/**
 * A scenery node that looks like a key pad and allows the user to enter a number. The entered number is not
 * displayed by this node - it is intended to be used in conjunction with a separate display of some sort.
 *
 * @author John Blanco
 * @author Andrey Zelenkov (MLearner)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var BackspaceIcon = require( 'SCENERY_PHET/BackspaceIcon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   * @param {Object} [options]
   * @constructor
   */
  function NumberKeypad( options ) {

    options = _.extend( {
      buttonFont: new PhetFont( { size: 20 } ),
      minButtonWidth: 35,
      minButtonHeight: 35,
      decimalPointKey: false,
      xSpacing: 10,
      ySpacing: 10,
      keyColor: 'white',

      //TODO replace with a function that accepts or rejects entry, https://github.com/phetsims/scenery-phet/issues/272
      maxDigits: 8, // Maximum number of digits that the user may enter
      valueStringProperty: new Property( '' )
    }, options );

    var self = this;

    // @public (read only) - sequence of key values entered by the user
    this.valueStringProperty = options.valueStringProperty;

    // @private - when true, the next key press will clear valueStringProperty
    this.armedForNewEntry = false;

    // options for keys
    var keyOptions = {
      minWidth: options.minButtonWidth,
      minHeight: options.minButtonHeight,
      baseColor: options.keyColor,
      font: options.buttonFont,
      maxDigits: options.maxDigits
    };

    // create the backspace key
    var backspaceIcon = new BackspaceIcon();
    backspaceIcon.scale( Math.min( options.minButtonWidth / backspaceIcon.width * 0.7, ( options.minButtonHeight * 0.65 ) / backspaceIcon.height ) );
    var backspaceKey = new RectangularPushButton( {
      content: backspaceIcon,
      minWidth: keyOptions.minWidth,
      minHeight: keyOptions.minHeight,
      xMargin: 1,
      baseColor: keyOptions.baseColor,
      listener: function() {
        if ( self.valueStringProperty.value.length > 0 ) {

          // remove the last digit from the current digit string
          var shortenedDigitString = self.valueStringProperty.value.slice( 0, -1 );

          if ( self.armedForNewEntry ) {
            self.valueStringProperty.reset(); // this reset will trigger the state change that we want in the game
            self.armedForNewEntry = false;
          }

          // set the new shortened value
          self.valueStringProperty.value = shortenedDigitString;
        }
      }
    } );

    //TODO allow client to provide this function, https://github.com/phetsims/scenery-phet/issues/272
    /**
     * Creates the new string that results from pressing a key.
     * @param {string} keyString - string associated with the key that was pressed
     * @param {string} valueString - string that corresponds to the sequence of keys that have been pressed
     * @returns {string} the result
     */
    var processKeyString = function( keyString, valueString ) {

      var hasDecimalPoint = valueString.indexOf( '.' ) !== -1;
      var numberOfDigits = hasDecimalPoint ? valueString.length - 1 : valueString.length;

      var newValueString;
      if ( self.valueStringProperty.value === '0' && keyString === '0' ) {
        // ignore multiple leading zeros
      }
      else if ( self.valueStringProperty.value === '0' && keyString !== '0' && keyString !== '.' ) {

        // replace a leading 0 that's not followed by a decimal point with this key
        newValueString = keyString;
      }
      else if ( keyString !== '.' && numberOfDigits < keyOptions.maxDigits ) {

        // constrain to maxDigits
        newValueString = valueString + keyString;
      }
      else if ( keyString === '.' && self.valueStringProperty.value.indexOf( '.' ) === -1 ) {

        // allow one decimal point
        newValueString = valueString + keyString;
      }
      else {

        // ignore keyString
        newValueString = valueString;
      }

      return newValueString;
    };

    /**
     * Called when a key is pressed.
     * @param {string} keyString - string associated with the key that was pressed
     */
    var keyCallback = function( keyString ) {

      // If armed for new entry, clear the existing string.
      if ( self.armedForNewEntry ) {
        self.valueStringProperty.reset();
        self.armedForNewEntry = false;
      }

      // process the keyString
      self.valueStringProperty.value = processKeyString( keyString, self.valueStringProperty.value );
    };

    // create the bottom row of keys, which can vary based on options
    var bottomRowChildren = [];
    if ( options.decimalPointKey ) {

      // add a decimal point key plus a normal width zero key
      bottomRowChildren.push( createKey( '.', keyCallback, keyOptions ) );
      bottomRowChildren.push( createKey( '0', keyCallback, keyOptions ) );
    }
    else {

      // add a double-width zero key instead of the decimal point key
      var doubleRowButtonKeySpec = _.extend( {}, keyOptions, { minWidth: keyOptions.minWidth * 2 + options.xSpacing } );
      bottomRowChildren.push( createKey( '0', keyCallback, doubleRowButtonKeySpec ) );
    }
    bottomRowChildren.push( backspaceKey );

    // add the rest of the keys
    VBox.call( this, {
      spacing: options.ySpacing,
      children: [
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '7', keyCallback, keyOptions ),
            createKey( '8', keyCallback, keyOptions ),
            createKey( '9', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '4', keyCallback, keyOptions ),
            createKey( '5', keyCallback, keyOptions ),
            createKey( '6', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '1', keyCallback, keyOptions ),
            createKey( '2', keyCallback, keyOptions ),
            createKey( '3', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: bottomRowChildren
        } )
      ]
    } );

    this.mutate( options );
  }

  sceneryPhet.register( 'NumberKeypad', NumberKeypad );

  /**
   * Creates a key for the keypad.
   *
   * @param {string} keyString - string that appears on the key
   * @param {function(string)} callback - called when the key is pressed
   * @param {Object} keyOptions - see RectangularPushButton.options
   * @returns {*}
   */
  function createKey( keyString, callback, keyOptions ) {
    return new RectangularPushButton( {
      content: new Text( keyString, { font: keyOptions.font } ),
      baseColor: keyOptions.baseColor,
      minWidth: keyOptions.minWidth,
      minHeight: keyOptions.minHeight,
      xMargin: 5,
      yMargin: 5,
      listener: function() { callback( keyString ); }
    } );
  }

  return inherit( VBox, NumberKeypad, {

    //TODO implement dispose

    /**
     * Clear anything that has been accumulated in the valueStringProperty field.
     * @public
     */
    clear: function() {
      this.valueStringProperty.reset();
    },

    /**
     * Set the keypad such that any new digit entry will clear the existing string and start over.
     * @public
     */
    armForNewEntry: function() {
      this.armedForNewEntry = true;
    }
  } );
} );
<commit_msg>update copyright<commit_after>// Copyright 2015-2016, University of Colorado Boulder

/**
 * A scenery node that looks like a key pad and allows the user to enter a number. The entered number is not
 * displayed by this node - it is intended to be used in conjunction with a separate display of some sort.
 *
 * @author John Blanco
 * @author Andrey Zelenkov (MLearner)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var BackspaceIcon = require( 'SCENERY_PHET/BackspaceIcon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   * @param {Object} [options]
   * @constructor
   */
  function NumberKeypad( options ) {

    options = _.extend( {
      buttonFont: new PhetFont( { size: 20 } ),
      minButtonWidth: 35,
      minButtonHeight: 35,
      decimalPointKey: false,
      xSpacing: 10,
      ySpacing: 10,
      keyColor: 'white',

      //TODO replace with a function that accepts or rejects entry, https://github.com/phetsims/scenery-phet/issues/272
      maxDigits: 8, // Maximum number of digits that the user may enter
      valueStringProperty: new Property( '' )
    }, options );

    var self = this;

    // @public (read only) - sequence of key values entered by the user
    this.valueStringProperty = options.valueStringProperty;

    // @private - when true, the next key press will clear valueStringProperty
    this.armedForNewEntry = false;

    // options for keys
    var keyOptions = {
      minWidth: options.minButtonWidth,
      minHeight: options.minButtonHeight,
      baseColor: options.keyColor,
      font: options.buttonFont,
      maxDigits: options.maxDigits
    };

    // create the backspace key
    var backspaceIcon = new BackspaceIcon();
    backspaceIcon.scale( Math.min( options.minButtonWidth / backspaceIcon.width * 0.7, ( options.minButtonHeight * 0.65 ) / backspaceIcon.height ) );
    var backspaceKey = new RectangularPushButton( {
      content: backspaceIcon,
      minWidth: keyOptions.minWidth,
      minHeight: keyOptions.minHeight,
      xMargin: 1,
      baseColor: keyOptions.baseColor,
      listener: function() {
        if ( self.valueStringProperty.value.length > 0 ) {

          // remove the last digit from the current digit string
          var shortenedDigitString = self.valueStringProperty.value.slice( 0, -1 );

          if ( self.armedForNewEntry ) {
            self.valueStringProperty.reset(); // this reset will trigger the state change that we want in the game
            self.armedForNewEntry = false;
          }

          // set the new shortened value
          self.valueStringProperty.value = shortenedDigitString;
        }
      }
    } );

    //TODO allow client to provide this function, https://github.com/phetsims/scenery-phet/issues/272
    /**
     * Creates the new string that results from pressing a key.
     * @param {string} keyString - string associated with the key that was pressed
     * @param {string} valueString - string that corresponds to the sequence of keys that have been pressed
     * @returns {string} the result
     */
    var processKeyString = function( keyString, valueString ) {

      var hasDecimalPoint = valueString.indexOf( '.' ) !== -1;
      var numberOfDigits = hasDecimalPoint ? valueString.length - 1 : valueString.length;

      var newValueString;
      if ( self.valueStringProperty.value === '0' && keyString === '0' ) {
        // ignore multiple leading zeros
      }
      else if ( self.valueStringProperty.value === '0' && keyString !== '0' && keyString !== '.' ) {

        // replace a leading 0 that's not followed by a decimal point with this key
        newValueString = keyString;
      }
      else if ( keyString !== '.' && numberOfDigits < keyOptions.maxDigits ) {

        // constrain to maxDigits
        newValueString = valueString + keyString;
      }
      else if ( keyString === '.' && self.valueStringProperty.value.indexOf( '.' ) === -1 ) {

        // allow one decimal point
        newValueString = valueString + keyString;
      }
      else {

        // ignore keyString
        newValueString = valueString;
      }

      return newValueString;
    };

    /**
     * Called when a key is pressed.
     * @param {string} keyString - string associated with the key that was pressed
     */
    var keyCallback = function( keyString ) {

      // If armed for new entry, clear the existing string.
      if ( self.armedForNewEntry ) {
        self.valueStringProperty.reset();
        self.armedForNewEntry = false;
      }

      // process the keyString
      self.valueStringProperty.value = processKeyString( keyString, self.valueStringProperty.value );
    };

    // create the bottom row of keys, which can vary based on options
    var bottomRowChildren = [];
    if ( options.decimalPointKey ) {

      // add a decimal point key plus a normal width zero key
      bottomRowChildren.push( createKey( '.', keyCallback, keyOptions ) );
      bottomRowChildren.push( createKey( '0', keyCallback, keyOptions ) );
    }
    else {

      // add a double-width zero key instead of the decimal point key
      var doubleRowButtonKeySpec = _.extend( {}, keyOptions, { minWidth: keyOptions.minWidth * 2 + options.xSpacing } );
      bottomRowChildren.push( createKey( '0', keyCallback, doubleRowButtonKeySpec ) );
    }
    bottomRowChildren.push( backspaceKey );

    // add the rest of the keys
    VBox.call( this, {
      spacing: options.ySpacing,
      children: [
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '7', keyCallback, keyOptions ),
            createKey( '8', keyCallback, keyOptions ),
            createKey( '9', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '4', keyCallback, keyOptions ),
            createKey( '5', keyCallback, keyOptions ),
            createKey( '6', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: [
            createKey( '1', keyCallback, keyOptions ),
            createKey( '2', keyCallback, keyOptions ),
            createKey( '3', keyCallback, keyOptions )
          ]
        } ),
        new HBox( {
          spacing: options.xSpacing,
          children: bottomRowChildren
        } )
      ]
    } );

    this.mutate( options );
  }

  sceneryPhet.register( 'NumberKeypad', NumberKeypad );

  /**
   * Creates a key for the keypad.
   *
   * @param {string} keyString - string that appears on the key
   * @param {function(string)} callback - called when the key is pressed
   * @param {Object} keyOptions - see RectangularPushButton.options
   * @returns {*}
   */
  function createKey( keyString, callback, keyOptions ) {
    return new RectangularPushButton( {
      content: new Text( keyString, { font: keyOptions.font } ),
      baseColor: keyOptions.baseColor,
      minWidth: keyOptions.minWidth,
      minHeight: keyOptions.minHeight,
      xMargin: 5,
      yMargin: 5,
      listener: function() { callback( keyString ); }
    } );
  }

  return inherit( VBox, NumberKeypad, {

    //TODO implement dispose

    /**
     * Clear anything that has been accumulated in the valueStringProperty field.
     * @public
     */
    clear: function() {
      this.valueStringProperty.reset();
    },

    /**
     * Set the keypad such that any new digit entry will clear the existing string and start over.
     * @public
     */
    armForNewEntry: function() {
      this.armedForNewEntry = true;
    }
  } );
} );
<|endoftext|>


<commit_before>var registry = require('plugman-registry')
  , config = require('../config');

module.exports = function(search_opts, callback) {
  registry.use(config.registry, function(err) {
    registry.search(search_opts, function(err, plugins) {
        if(callback) {
            if(err) return callback(err);
            callback(plugins);
        } else {
          if(err) return console.log(err);
          for(var plugin in plugins) {
            console.log(plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
          }
        }
    });
  });
}
<commit_msg>typo in callback<commit_after>var registry = require('plugman-registry')
  , config = require('../config');

module.exports = function(search_opts, callback) {
  registry.use(config.registry, function(err) {
    registry.search(search_opts, function(err, plugins) {
        if(callback) {
            if(err) return callback(err);
            callback(null, plugins);
        } else {
          if(err) return console.log(err);
          for(var plugin in plugins) {
            console.log(plugins[plugin].name, '-', plugins[plugin].description || 'no description provided');
          }
        }
    });
  });
}
<|endoftext|>


<commit_before>// Preprocessor Class
(function () {

/**
 * @summary Data preprocessor.
 *
 * @description Data can be preprocessed before it is rendered by an adapter.
 *
 * This has several important performance considerations.  If data will be
 * rendered repeatedly or on slower browsers, it will be faster after being
 * optimized.
 *
 * First, data outside the boundaries does not need to be rendered.  Second,
 * the resolution of the data only needs to be at most the number of pixels
 * in the width of the visualization.
 *
 * Performing these optimizations will limit memory overhead, important
 * for garbage collection and performance on old browsers, as well as drawing
 * overhead, important for mobile devices, old browsers and large data sets.
 *
 * @param {Array} [data]  The data for processing.
 *
 * @memberof envision
 * @class
 */
function Preprocessor (options) {

  options = options || {};

  /**
   * Returns data.
   */
  this.getData = function () {

    if (this.bounded) bound(this);

    return this.processing;
  }

  this.reset = function () {
    this.processing = this.data;
    return this;
  }

  /**
   * Set the data object.
   */
  this.setData = function (data) {
    var
      i, length;
    if (!_.isArray(data)) throw new Error('Array expected.');
    if (data.length < 2) throw new Error('Data must contain at least two dimensions.');
    length = data[0].length;
    for (i = data.length; i--;) {
      if (!_.isArray(data[i])) throw new Error('Data dimensions must be arrays.');
      if (data[i].length !== length) throw new Error('Data dimensions must contain the same number of points.');
    }

    this.processing = data;
    this.data = data;

    return this;
  }

  if (options.data) this.setData(options.data);
}

function getStartIndex (data, min) {
  var
    i = _.sortedIndex(data, min);

  // Include point outside range when not exact match
  if (data[i] > min && i > 0) i--;

  return i;
}

function getEndIndex (data, max) {
  return _.sortedIndex(data, max);
}

function bound (that) {

  delete that.bounded;

  var
    data    = that.processing,
    length  = that.length(),
    x       = data[0],
    y       = data[1],
    min     = that.min || 0,
    max     = that.max || length,
    start   = getStartIndex(x, min),
    end     = getEndIndex(x, max);

  that.processing = [
    x.slice(start, end + 1),
    y.slice(start, end + 1)
  ];

  that.start = start;
  that.end = end;
};

Preprocessor.prototype = {

  /**
   * Returns the length of the data set.
   *
   * @return {Number} Length of the data set.
   */
  length : function () {
    return this.getData()[0].length;
  },

  /**
   * Bounds the data set at within a range.
   *
   * @param {Number} min
   * @param {Number} max
   */
  bound : function (min, max) {

    if (!_.isNumber(min) || !_.isNumber(max)) return this;

    this.min = min;
    this.max = max;
    this.bounded = true;

    return this;
  },

  /**
   * Subsample data using MinMax.
   *
   * MinMax will display the extrema of the subsample intervals.  This is
   * slower than regular interval subsampling but necessary for data that
   * is very non-homogenous.
   *
   * @param {Number} resolution
   */
  subsampleMinMax : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.processing,
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      count   = (resolution - 2) / 2,
      newX    = [],
      newY    = [],
      min     = Number.MAX_VALUE,
      max     = -Number.MAX_VALUE,
      minI    = 1,
      maxI    = 1,
      unit    = (end - start)/ count,
      position, min, max, datum, i, j;

    if (end - start + 1 > resolution) {

      newX.push(x[start]);
      newY.push(y[start]);

      position = start + unit;

      for (i = start; i < end; i++) {

        if (i === Math.round(position)) {

          position += unit;

          j = Math.min(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          j = Math.max(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          minI = i;
          min = y[minI];
          maxI = i;
          max = y[maxI];

        } else {
          if (y[i] > max) {
            max = y[i];
            maxI = i;
          }

          if (y[i] < min) {
            min = y[i];
            minI = i;
          }
        }
      }

      if (i < position) {
        newX.push(x[minI]);
        newY.push(min);
        newX.push(x[maxI]);
        newY.push(max);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.processing = [newX, newY];
      this.start = start;
      this.end = end;
    } else {
      this.bounded = true;
    }

    return this;
  },

  /**
   * Subsample data at a regular interval for resolution.
   *
   * This is the fastest subsampling and good for monotonic data and fairly
   * homogenous data (not a lot of up and down).
   *
   * @param {Number} resolution
   */
  subsample : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.processing,
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      unit    = (end - start + 1) / resolution,
      newX    = [],
      newY    = [],
      i, index;

    if (length > resolution) {

      // First
      newX.push(x[start]);
      newY.push(y[start]);

      for (i = 1; i < resolution; i++) {
        if (i * unit >= end - unit) break;
        index = Math.round(i * unit) + start;
        newX.push(x[index]);
        newY.push(y[index]);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.processing = [newX, newY];
      this.start = start;
      this.end = end;
    }

    return this;
  }
};

envision.Preprocessor = Preprocessor;

}());
<commit_msg>Fix subsampleMinMax not skipping bounding when 'bound' not called.<commit_after>// Preprocessor Class
(function () {

/**
 * @summary Data preprocessor.
 *
 * @description Data can be preprocessed before it is rendered by an adapter.
 *
 * This has several important performance considerations.  If data will be
 * rendered repeatedly or on slower browsers, it will be faster after being
 * optimized.
 *
 * First, data outside the boundaries does not need to be rendered.  Second,
 * the resolution of the data only needs to be at most the number of pixels
 * in the width of the visualization.
 *
 * Performing these optimizations will limit memory overhead, important
 * for garbage collection and performance on old browsers, as well as drawing
 * overhead, important for mobile devices, old browsers and large data sets.
 *
 * @param {Array} [data]  The data for processing.
 *
 * @memberof envision
 * @class
 */
function Preprocessor (options) {

  options = options || {};

  /**
   * Returns data.
   */
  this.getData = function () {

    if (this.bounded) bound(this);

    return this.processing;
  }

  this.reset = function () {
    this.processing = this.data;
    return this;
  }

  /**
   * Set the data object.
   */
  this.setData = function (data) {
    var
      i, length;
    if (!_.isArray(data)) throw new Error('Array expected.');
    if (data.length < 2) throw new Error('Data must contain at least two dimensions.');
    length = data[0].length;
    for (i = data.length; i--;) {
      if (!_.isArray(data[i])) throw new Error('Data dimensions must be arrays.');
      if (data[i].length !== length) throw new Error('Data dimensions must contain the same number of points.');
    }

    this.processing = data;
    this.data = data;

    return this;
  }

  if (options.data) this.setData(options.data);
}

function getStartIndex (data, min) {
  var
    i = _.sortedIndex(data, min);

  // Include point outside range when not exact match
  if (data[i] > min && i > 0) i--;

  return i;
}

function getEndIndex (data, max) {
  return _.sortedIndex(data, max);
}

function bound (that) {

  delete that.bounded;

  var
    data    = that.processing,
    length  = that.length(),
    x       = data[0],
    y       = data[1],
    min     = that.min || 0,
    max     = that.max || length,
    start   = getStartIndex(x, min),
    end     = getEndIndex(x, max);

  that.processing = [
    x.slice(start, end + 1),
    y.slice(start, end + 1)
  ];

  that.start = start;
  that.end = end;
};

Preprocessor.prototype = {

  /**
   * Returns the length of the data set.
   *
   * @return {Number} Length of the data set.
   */
  length : function () {
    return this.getData()[0].length;
  },

  /**
   * Bounds the data set at within a range.
   *
   * @param {Number} min
   * @param {Number} max
   */
  bound : function (min, max) {

    if (!_.isNumber(min) || !_.isNumber(max)) return this;

    this.min = min;
    this.max = max;
    this.bounded = true;

    return this;
  },

  /**
   * Subsample data using MinMax.
   *
   * MinMax will display the extrema of the subsample intervals.  This is
   * slower than regular interval subsampling but necessary for data that
   * is very non-homogenous.
   *
   * @param {Number} resolution
   */
  subsampleMinMax : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.processing,
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      count   = (resolution - 2) / 2,
      newX    = [],
      newY    = [],
      min     = Number.MAX_VALUE,
      max     = -Number.MAX_VALUE,
      minI    = 1,
      maxI    = 1,
      unit    = (end - start)/ count,
      position, min, max, datum, i, j;

    if (end - start + 1 > resolution) {

      newX.push(x[start]);
      newY.push(y[start]);

      position = start + unit;

      for (i = start; i < end; i++) {

        if (i === Math.round(position)) {

          position += unit;

          j = Math.min(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          j = Math.max(maxI, minI);
          newX.push(x[j]);
          newY.push(y[j]);

          minI = i;
          min = y[minI];
          maxI = i;
          max = y[maxI];

        } else {
          if (y[i] > max) {
            max = y[i];
            maxI = i;
          }

          if (y[i] < min) {
            min = y[i];
            minI = i;
          }
        }
      }

      if (i < position) {
        newX.push(x[minI]);
        newY.push(min);
        newX.push(x[maxI]);
        newY.push(max);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.processing = [newX, newY];
      this.start = start;
      this.end = end;
    } else {
      this.bounded = bounded;
    }

    return this;
  },

  /**
   * Subsample data at a regular interval for resolution.
   *
   * This is the fastest subsampling and good for monotonic data and fairly
   * homogenous data (not a lot of up and down).
   *
   * @param {Number} resolution
   */
  subsample : function (resolution) {

    var bounded = this.bounded;
    delete this.bounded;

    var
      data    = this.processing,
      length  = this.length(),
      x       = data[0],
      y       = data[1],
      start   = bounded ? getStartIndex(x, this.min) : 0,
      end     = bounded ? getEndIndex(x, this.max) : length - 1,
      unit    = (end - start + 1) / resolution,
      newX    = [],
      newY    = [],
      i, index;

    if (length > resolution) {

      // First
      newX.push(x[start]);
      newY.push(y[start]);

      for (i = 1; i < resolution; i++) {
        if (i * unit >= end - unit) break;
        index = Math.round(i * unit) + start;
        newX.push(x[index]);
        newY.push(y[index]);
      }

      // Last
      newX.push(x[end]);
      newY.push(y[end]);

      this.processing = [newX, newY];
      this.start = start;
      this.end = end;
    }

    return this;
  }
};

envision.Preprocessor = Preprocessor;

}());
<|endoftext|>


<commit_before>// (c) 2016 osmcz-app, https://github.com/osmcz/osmcz

var osmcz = osmcz || {};
osmcz.activeLayer = function (map, baseLayers, overlays, controls) {
    // -- constructor --

    var style = {
        "clickable": true,
        "color": "#00D",
        "fillColor": "#00D",

        radius: 4,
        weight: 0,
        opacity: 1,
        fillOpacity: 0.2
    };
    var hoverStyle = {
        "fillOpacity": 0.5
    };

    //The later - the more priority https://www.mapbox.com/maki/
    var icons = {
        amenity: {
            restaurant: 'restaurant',
            fuel: 'fuel',
            toilets: 'toilets',
            telephone: 'telephone',
            fast_food: 'fast-food',
            bank: 'bank',
            atm: 'bank',
            waste_disposal: 'waste-basket',
            pub: 'beer',
            post_office: 'post',
            post_box: 'post',
            pharmacy: 'hospital',
            doctors: 'hospital',
            bar: 'bar',
            cafe: 'cafe',
            car_rental: 'car',
            school: 'school',
            college: 'college',
            bicycle_parking: 'bicycle',
            university: 'college',
            library: 'library',
            theatre: 'theatre',
            public_building: 'town-hall',
        },
        highway: {
            bus_stop: 'bus'
        },
        leisure: {
            playground: 'playground',
        },
        railway: {
            station: 'rail-24',
            halt: 'rail-24',
            tram_stop: 'rail-light'
        },
        shop: {
            '*': 'shop',
            chemist: 'pharmacy',
            grocery: 'grocery',
            supermarket: 'grocery',
            convenience: 'grocery'
        },
        station: {
            subway: 'rail-metro'
        },
        tourism: {
            guest_house: 'lodging',
            hostel: 'lodging',
            hotel: 'lodging'
        },
        historic: {
            monument: 'monument',
            memorial: 'monument'
        },
        man_made: {
            surveillance: 'camera'
        },
        place: {
            city: 'square-18',
            town: 'square-stroked-16',
            village: 'circle-8'
        },
        information: {
            guidepost: 'guidepost'
        }
    };

    // url of ajax proxy server for wikipedia and wikimedia
//     var xhd_proxy_url = 'http://localhost/xhr_proxy.php';
    var xhd_proxy_url = 'http://openstreetmap.cz/xhr_proxy.php';

    function getIcon(tags) {
        var name = false;
        for (var key in tags) {
            var val = tags[key];
            if (icons[key] && icons[key][val])
                name = icons[key][val];
            else if (icons[key] && icons[key]['*'])
                name = icons[key]['*'];
        }

        var pc = name ? name.split('-') : 0;
//         var iconBaseUrl = 'https://cdn.rawgit.com/mapbox/maki/v0.5.0/renders/';
        var iconBaseUrl = 'https://cdn.rawgit.com/osmcz/maki/osmcz_v1/renders/';
//         var iconBaseUrl = 'http://localhost/maki/renders/';

        if (name && IsNumeric(pc[pc.length - 1])) {

            var icName = name.substring(0, name.length - pc[pc.length - 1].length);
            var icSize = parseInt(pc[pc.length - 1]);
            var size = [icSize, icSize];

            if (icSize <= 12) {
                var iconUrl = iconBaseUrl + icName + '12';
            } else if (icSize > 12 && icSize <= 18) {
                var iconUrl = iconBaseUrl + icName + '18';
            } else {
                var iconUrl = iconBaseUrl + icName + '24';
            }

        } else if (name && !IsNumeric(pc[pc.length - 1])) {
            var iconUrl = iconBaseUrl + name + '-18';
            var size = [18, 18];

        } else {
            var iconUrl = iconBaseUrl + 'circle-stroked-12';
            var size = [10, 10];
        }

        return L.icon({
            iconUrl: iconUrl + '.png',
            iconRetinaUrl: iconUrl + '@2x.png',
            iconSize: size,
            popupAnchor: [0, -9]
        });
    }

    var timeout;
    var permanentlyDisplayed = false;
    var marker = L.circleMarker([0,0]);

    var geojsonURL = 'http://tile.poloha.net/json/{z}/{x}/{y}';
    if (location.search.match(/active=sk/))  //temporary solution - soon merged in one endpoint
        geojsonURL = 'http://tile.poloha.net/jsonsk/{z}/{x}/{y}';

    var geojsonTileLayer = new L.TileLayer.GeoJSON(geojsonURL, {
            maxZoom: 25,
            code: 'A'
            //clipTiles: true,
            //unique: function (feature) {                return feature.osm_id;            }
        }, {
            style: style,

            pointToLayer: function (feature, latlng) {
                return L.marker(latlng, {icon: getIcon(feature.properties.tags)});
            },

            onEachFeature: function (feature, layer) {

                if (!(layer instanceof L.Point)) {
                    layer.on('click', function (event) {
//                         console.log('click', event);
                        if (event.target && event.target.feature) {
                            clearTimeout(timeout);
                            permanentlyDisplayed = true;
                            openPoiPanel(event.target.feature, event.target.options.icon.options.iconUrl);
                        }
                    });

                    layer.on('mouseover', function (event) {
                        if (permanentlyDisplayed)
                            return;

                        if (event.target && event.target.feature) {
                            clearTimeout(timeout);
                            timeout = setTimeout(function () {
                                openPoiPanel(event.target.feature, event.target.options.icon.options.iconUrl);
                            }, 100);
                        }
                    });
                    layer.on('mouseout', function (event) {
                        if (!permanentlyDisplayed) {
                            clearTimeout(timeout);
                            timeout = setTimeout(function () {
                                defaultPoiPanel();
                            }, 300);
                        }
                    });
                }
            }
        }
    );

    //add as overlay
    overlays["Aktivní vrstva"] = geojsonTileLayer;

    map.on('layeradd', function (event) {
        if (event.layer == geojsonTileLayer) {
            $('#map-container').addClass('searchbar-on');
            defaultPoiPanel();
        }
    });
    map.on('layerremove', function (event) {
        if (event.layer == geojsonTileLayer) {
            $('#map-container').removeClass('searchbar-on');
        }
    });

    //reset panel
    function resetPanel(){
        defaultPoiPanel();
        permanentlyDisplayed = false;
        map.removeLayer(marker);
    }
    $('#map-searchbar').on('click', '.close', resetPanel);
    map.on('click', resetPanel);

    function IsNumeric(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

    function openPoiPanel(feature, icon) {
        $('#map-searchbar').html(template(feature, icon));
    }

    function defaultPoiPanel() {
        $('#map-searchbar').html("Najeďte myší na bod zájmu
nebo klikněte pro trvalé zobrazení.");

    }

    function template(feature, icon) {
        var id = feature.properties.osm_id;
        var osm_type = feature.properties.osm_type;
        var general = []; // store k,v objects in array to allow sort them
        var name = {};
        var contact = {};
        var payment = {};
        var building = {};
        var wikimedia = {};
        var wikipedia = {};

        var tpl = [];
        tpl.push(permanentlyDisplayed ? '×' : '');
        tpl.push('
');
        tpl.push(' ');
        tpl.push(feature.properties.tags.name || 'Bod zájmu');
        tpl.push('
');

        $.each(feature.properties.tags, function (k, v) {
            if (k.match(/^name:/))
                name[k] = v;
            else if (k.match(/^payment:/))
                payment[k] = v;
            else if (k.match(/^contact:/) || k.match(/^phone/) || k.match(/^email/))
                contact[k] = v;
            else if (k.match(/^building/))
                building[k] = v;
            else if (k.match(/^wikimedia_commons/) && v.match(/^File:/))
                wikimedia = {k:k, v:v};
            else if (!k.match(/^addr:/) &&
                     !k.match(/^ref:ruian:/)
                    ) {
                if (k.match(/^wikipedia/)) {
                    wikipedia = {k:k, v:v};
                }
                general.push({k:k, v:v});
            }
        });

        // sort the array
        general.sort(function(a, b){
          var nameA=a.k, nameB=b.k
          if (nameA < nameB) //sort string ascending
            return -1
          if (nameA > nameB)
            return 1
          return 0 //default return value (no sorting)
          });

        if (general.length) {
            for (var i in general)
            {
                var k = general[i].k;
                var v = general[i].v;
                if (k.match(/^wikipedia/)) {
                    tpl.push('' + k + ' = '+ v + '');
                }
                else {
                    tpl.push('' + k + ' = ');
                    tpl.push(v.match(/^https?:\/\/.+/) ? ('' + v + '') : v);
                }
                tpl.push('
');
            };
        };

        var section = function (obj, label) {
            if (Object.keys(obj).length) {
                tpl.push('
' + label + '
');
                $.each(obj, function (k, v) {
                    tpl.push('' + k + ' = ');
                    tpl.push(v.match(/^https?:\/\/.+/) ? ('' + v + '') : v);
                    tpl.push('
');
                });
                tpl.pop(); //remove last br
            }
        };

        section(name, 'Další jména:');
        section(payment, 'Možnosti platby:');
        section(contact, 'Kontakty:');
        section(building, 'Budova:');

        tpl.push('
osm ID: ' + osm_type + '/' + id + '
');
        tpl.push('

');
        tpl.push('

');

        if (wikimedia.k) {
            if (feature.wikimedia) {
                setTimeout(function () { //after dom is created
                    showWikimediaCommons();
                }, 0);
            }
            else {
                var v = feature.properties.tags.wikimedia_commons;
                var url = 'https://commons.wikimedia.org/w/api.php?action=query'
                        + '&prop=imageinfo&iiprop=url&iiurlwidth=240&format=json'
                        + '&titles=' + encodeURIComponent(v);
                $.ajax({
                    url: xhd_proxy_url,
                    data: {
                        url: url
                    },
                    dataType: 'json',
                    success: function (data) {
                        feature.wikimedia = data;
                        showWikimediaCommons();
                    }
                });
            }
        }

        if (!wikimedia.k && wikipedia.k) {
            if (feature.wikipedia) {
                setTimeout(function () { //after dom is created
                    showWikimediaCommons();
                }, 0);
            }
            else {
                var v = wikipedia.v;
                var country = v.split(":")[0];
                if ( country === v )
                    country = "en"
                var url = 'https://'+ country +'.wikipedia.org/w/api.php?action=query'
                        + '&prop=pageimages&pithumbsize=240&format=json'
                        + '&titles=' + encodeURIComponent(v);
                $.ajax({
                    url: xhd_proxy_url,
                    data: {
                        url: url
                    },
                    dataType: 'json',
                    success: function (data) {
                        feature.wikipedia = data;
                        showWikimediaCommons();
                    }
                });
            }
        }

        var wcmTpl = '
'
            + ''
            + 'Foto Wikimedia Commons
'
            + ''
            + ''
            + '';

        function showWikimediaCommons() {
            var wcm = $('#wikimedia-commons');
            if (id = wcm.attr('data-osm-id') && (feature.wikimedia || feature.wikipedia)) {
                if (feature.wikimedia) {
                    var k = Object.keys(feature.wikimedia.query.pages)[0];
                    if (!feature.wikimedia.query.pages[k].imageinfo.length)
                        return
                    var descriptionshorturl = feature.wikimedia.query.pages[k].imageinfo[0].descriptionshorturl;
                    var thumburl = feature.wikimedia.query.pages[k].imageinfo[0].thumburl;
                } else {
                    var k = Object.keys(feature.wikipedia.query.pages)[0];
                    if (!feature.wikipedia.query.pages[k].pageimage)
                        return
                    var descriptionshorturl = 'https://commons.wikimedia.org/wiki/File:' + feature.wikipedia.query.pages[k].pageimage;
                    var thumburl = feature.wikipedia.query.pages[k].thumbnail.source;
                }
                wcm.html(wcmTpl.replace(/_thumburl/g, thumburl).replace(/_descriptionshorturl/g, descriptionshorturl));
            }
        };

        if (permanentlyDisplayed) {
            if (! feature.wikimedia && ! feature.wikipedia ) {
                var lon = feature.geometry.coordinates[0];
                var lat = feature.geometry.coordinates[1];

                marker.setLatLng([lat, lon]).addTo(map);

                if (feature.mapillary) {
                    setTimeout(function () { //after dom is created
                        showMapillary();
                    }, 0);
                }
                else {
                    //TODO - limit=10 & choose the best oriented photo
                    $.ajax({
                        url: 'http://api.mapillary.com/v1/im/close?lat=' + lat + '&lon=' + lon + '&distance=30&limit=1',
                        dataType: 'json',
                        jsonp: false,
                        global: false,
                        success: function (data) {
                            feature.mapillary = data;
                            showMapillary();
                        }
                    });
                }
            }
        }

        var mpTpl = '
Nejbližší foto
'
            + ''
            + ''
            + '';
        function showMapillary() {
            var mp = $('#mapillary-photo');
            if (id = mp.attr('data-osm-id') && feature.mapillary.length) {
                mp.html(mpTpl.replace(/_key/g, feature.mapillary[0].key));
            }
        };

        return tpl.join('');
    }

};
<commit_msg>active-layer: hide names + auto-codestyle<commit_after>// (c) 2016 osmcz-app, https://github.com/osmcz/osmcz

var osmcz = osmcz || {};
osmcz.activeLayer = function (map, baseLayers, overlays, controls) {
    // -- constructor --

    var style = {
        "clickable": true,
        "color": "#00D",
        "fillColor": "#00D",

        radius: 4,
        weight: 0,
        opacity: 1,
        fillOpacity: 0.2
    };
    var hoverStyle = {
        "fillOpacity": 0.5
    };

    //The later - the more priority https://www.mapbox.com/maki/
    var icons = {
        amenity: {
            restaurant: 'restaurant',
            fuel: 'fuel',
            toilets: 'toilets',
            telephone: 'telephone',
            fast_food: 'fast-food',
            bank: 'bank',
            atm: 'bank',
            waste_disposal: 'waste-basket',
            pub: 'beer',
            post_office: 'post',
            post_box: 'post',
            pharmacy: 'hospital',
            doctors: 'hospital',
            bar: 'bar',
            cafe: 'cafe',
            car_rental: 'car',
            school: 'school',
            college: 'college',
            bicycle_parking: 'bicycle',
            university: 'college',
            library: 'library',
            theatre: 'theatre',
            public_building: 'town-hall',
        },
        highway: {
            bus_stop: 'bus'
        },
        leisure: {
            playground: 'playground',
        },
        railway: {
            station: 'rail-24',
            halt: 'rail-24',
            tram_stop: 'rail-light'
        },
        shop: {
            '*': 'shop',
            chemist: 'pharmacy',
            grocery: 'grocery',
            supermarket: 'grocery',
            convenience: 'grocery'
        },
        station: {
            subway: 'rail-metro'
        },
        tourism: {
            guest_house: 'lodging',
            hostel: 'lodging',
            hotel: 'lodging'
        },
        historic: {
            monument: 'monument',
            memorial: 'monument'
        },
        man_made: {
            surveillance: 'camera'
        },
        place: {
            city: 'square-18',
            town: 'square-stroked-16',
            village: 'circle-8'
        },
        information: {
            guidepost: 'guidepost'
        }
    };

    // url of ajax proxy server for wikipedia and wikimedia
//     var xhd_proxy_url = 'http://localhost/xhr_proxy.php';
    var xhd_proxy_url = 'http://openstreetmap.cz/xhr_proxy.php';

    function getIcon(tags) {
        var name = false;
        for (var key in tags) {
            var val = tags[key];
            if (icons[key] && icons[key][val])
                name = icons[key][val];
            else if (icons[key] && icons[key]['*'])
                name = icons[key]['*'];
        }

        var pc = name ? name.split('-') : 0;
//         var iconBaseUrl = 'https://cdn.rawgit.com/mapbox/maki/v0.5.0/renders/';
        var iconBaseUrl = 'https://cdn.rawgit.com/osmcz/maki/osmcz_v1/renders/';
//         var iconBaseUrl = 'http://localhost/maki/renders/';

        if (name && IsNumeric(pc[pc.length - 1])) {

            var icName = name.substring(0, name.length - pc[pc.length - 1].length);
            var icSize = parseInt(pc[pc.length - 1]);
            var size = [icSize, icSize];

            if (icSize <= 12) {
                var iconUrl = iconBaseUrl + icName + '12';
            } else if (icSize > 12 && icSize <= 18) {
                var iconUrl = iconBaseUrl + icName + '18';
            } else {
                var iconUrl = iconBaseUrl + icName + '24';
            }

        } else if (name && !IsNumeric(pc[pc.length - 1])) {
            var iconUrl = iconBaseUrl + name + '-18';
            var size = [18, 18];

        } else {
            var iconUrl = iconBaseUrl + 'circle-stroked-12';
            var size = [10, 10];
        }

        return L.icon({
            iconUrl: iconUrl + '.png',
            iconRetinaUrl: iconUrl + '@2x.png',
            iconSize: size,
            popupAnchor: [0, -9]
        });
    }

    var timeout;
    var permanentlyDisplayed = false;
    var marker = L.circleMarker([0, 0]);

    var geojsonURL = 'http://tile.poloha.net/json/{z}/{x}/{y}';
    if (location.search.match(/active=sk/))  //temporary solution - soon merged in one endpoint
        geojsonURL = 'http://tile.poloha.net/jsonsk/{z}/{x}/{y}';

    var geojsonTileLayer = new L.TileLayer.GeoJSON(geojsonURL, {
            maxZoom: 25,
            code: 'A'
            //clipTiles: true,
            //unique: function (feature) {                return feature.osm_id;            }
        }, {
            style: style,

            pointToLayer: function (feature, latlng) {
                return L.marker(latlng, {icon: getIcon(feature.properties.tags)});
            },

            onEachFeature: function (feature, layer) {

                if (!(layer instanceof L.Point)) {
                    layer.on('click', function (event) {
//                         console.log('click', event);
                        if (event.target && event.target.feature) {
                            clearTimeout(timeout);
                            permanentlyDisplayed = true;
                            openPoiPanel(event.target.feature, event.target.options.icon.options.iconUrl);
                        }
                    });

                    layer.on('mouseover', function (event) {
                        if (permanentlyDisplayed)
                            return;

                        if (event.target && event.target.feature) {
                            clearTimeout(timeout);
                            timeout = setTimeout(function () {
                                openPoiPanel(event.target.feature, event.target.options.icon.options.iconUrl);
                            }, 100);
                        }
                    });
                    layer.on('mouseout', function (event) {
                        if (!permanentlyDisplayed) {
                            clearTimeout(timeout);
                            timeout = setTimeout(function () {
                                defaultPoiPanel();
                            }, 300);
                        }
                    });
                }
            }
        }
    );

    //add as overlay
    overlays["Aktivní vrstva"] = geojsonTileLayer;

    map.on('layeradd', function (event) {
        if (event.layer == geojsonTileLayer) {
            $('#map-container').addClass('searchbar-on');
            defaultPoiPanel();
        }
    });
    map.on('layerremove', function (event) {
        if (event.layer == geojsonTileLayer) {
            $('#map-container').removeClass('searchbar-on');
        }
    });

    //reset panel
    function resetPanel() {
        defaultPoiPanel();
        permanentlyDisplayed = false;
        map.removeLayer(marker);
    }

    $('#map-searchbar').on('click', '.close', resetPanel);
    map.on('click', resetPanel);

    function IsNumeric(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

    function openPoiPanel(feature, icon) {
        $('#map-searchbar').html(template(feature, icon));
    }

    function defaultPoiPanel() {
        $('#map-searchbar').html("Najeďte myší na bod zájmu
nebo klikněte pro trvalé zobrazení.");

    }

    function template(feature, icon) {
        var id = feature.properties.osm_id;
        var osm_type = feature.properties.osm_type;
        var general = []; // store k,v objects in array to allow sort them
        var name = {};
        var contact = {};
        var payment = {};
        var building = {};
        var wikimedia = {};
        var wikipedia = {};

        // show circle marker
        if (permanentlyDisplayed) {
            var lon = feature.geometry.coordinates[0];
            var lat = feature.geometry.coordinates[1];

            marker.setLatLng([lat, lon]).addTo(map);
        }

        var tpl = [];
        tpl.push(permanentlyDisplayed ? '×' : '');
        tpl.push('
');
        tpl.push(' ');
        tpl.push(feature.properties.tags.name || 'Bod zájmu');
        tpl.push('
');

        $.each(feature.properties.tags, function (k, v) {
            if (k.match(/^name:/))
                name[k] = v;
            else if (k.match(/^payment:/))
                payment[k] = v;
            else if (k.match(/^contact:/) || k.match(/^phone/) || k.match(/^email/))
                contact[k] = v;
            else if (k.match(/^building/))
                building[k] = v;
            else if (k.match(/^wikimedia_commons/) && v.match(/^File:/))
                wikimedia = {k: k, v: v};
            else if (!k.match(/^addr:/) && !k.match(/^ref:ruian:/)) {
                if (k.match(/^wikipedia/)) {
                    wikipedia = {k: k, v: v};
                }
                general.push({k: k, v: v});
            }
        });

        // sort the array
        general.sort(function (a, b) {
            return a.k.localeCompare(b.k);
        });

        if (general.length) {
            for (var i in general) {
                var k = general[i].k;
                var v = general[i].v;
                if (k.match(/^wikipedia/)) {
                    tpl.push('' + k + ' = ' + v + '');
                }
                else {
                    tpl.push('' + k + ' = ');
                    tpl.push(v.match(/^https?:\/\/.+/) ? ('' + v + '') : v);
                }
                tpl.push('
');
            }
        }

        var section = function (obj, label, hide) {
            if (Object.keys(obj).length) {
                hide && tpl.push('
' + label + ' zobrazit
');
                hide && tpl.push('
');

                tpl.push('
' + label + '
');
                $.each(obj, function (k, v) {
                    tpl.push('' + k + ' = ');
                    tpl.push(v.match(/^https?:\/\/.+/) ? ('' + v + '') : v);
                    tpl.push('
');
                });
                tpl.pop(); //remove last br

                hide && tpl.push('
');
            }
        };

        section(name, 'Další jména:', true);
        section(payment, 'Možnosti platby:');
        section(contact, 'Kontakty:');
        section(building, 'Budova:');

        tpl.push('
osm ID: ' + osm_type + '/' + id + '
');
        tpl.push('

');
        tpl.push('

');

        // ---------------------------------------- images ------------------------------------------
        // TODO refactor

        // show picture from wikimedia, if there is `wikimedia_commons` tag
        if (wikimedia.k) {
            if (feature.wikimedia) {
                setTimeout(function () { //after dom is created
                    showWikimediaCommons();
                }, 0);
            }
            else {
                var v = feature.properties.tags.wikimedia_commons;
                var url = 'https://commons.wikimedia.org/w/api.php?action=query'
                    + '&prop=imageinfo&iiprop=url&iiurlwidth=240&format=json'
                    + '&titles=' + encodeURIComponent(v);
                $.ajax({
                    url: xhd_proxy_url,
                    data: {
                        url: url
                    },
                    dataType: 'json',
                    success: function (data) {
                        feature.wikimedia = data;
                        showWikimediaCommons();
                    }
                });
            }
        }

        // show picture from wikipedia, if there is `wikipedia` tag
        if (!wikimedia.k && wikipedia.k) {
            if (feature.wikipedia) {
                setTimeout(function () { //after dom is created
                    showWikimediaCommons();
                }, 0);
            }
            else {
                var v = wikipedia.v;
                var country = v.split(":")[0];
                if (country === v)
                    country = "en"
                var url = 'https://' + country + '.wikipedia.org/w/api.php?action=query'
                    + '&prop=pageimages&pithumbsize=240&format=json'
                    + '&titles=' + encodeURIComponent(v);
                $.ajax({
                    url: xhd_proxy_url,
                    data: {
                        url: url
                    },
                    dataType: 'json',
                    success: function (data) {
                        feature.wikipedia = data;
                        showWikimediaCommons();
                    }
                });
            }
        }

        var wcmTpl = '
'
            + ''
            + 'Foto z Wikipedie
'
            + ''
            + ''
            + '';

        function showWikimediaCommons() {
            var wcm = $('#wikimedia-commons');
            if (id == wcm.attr('data-osm-id') && (feature.wikimedia || feature.wikipedia)) {
                if (feature.wikimedia) {
                    var k = Object.keys(feature.wikimedia.query.pages)[0];
                    if (!feature.wikimedia.query.pages[k].imageinfo.length)
                        return;
                    var descriptionshorturl = feature.wikimedia.query.pages[k].imageinfo[0].descriptionshorturl;
                    var thumburl = feature.wikimedia.query.pages[k].imageinfo[0].thumburl;
                } else {
                    var k = Object.keys(feature.wikipedia.query.pages)[0];
                    if (!feature.wikipedia.query.pages[k].pageimage)
                        return;
                    var descriptionshorturl = 'https://commons.wikimedia.org/wiki/File:' + feature.wikipedia.query.pages[k].pageimage;
                    var thumburl = feature.wikipedia.query.pages[k].thumbnail.source;
                }
                wcm.html(wcmTpl.replace(/_thumburl/g, thumburl).replace(/_descriptionshorturl/g, descriptionshorturl));
            }
        }

        // show closest mapillary photo if no photo so far
        if (permanentlyDisplayed) {
            if (!feature.wikimedia && !feature.wikipedia) {
                //TODO WP tag != existing WP photo --> chain mapillary after the WP request?

                if (feature.mapillary) {
                    setTimeout(function () { //after dom is created
                        showMapillary();
                    }, 0);
                }
                else {
                    //TODO - limit=10 & choose the best oriented photo
                    $.ajax({
                        url: 'http://api.mapillary.com/v1/im/close?lat=' + lat + '&lon=' + lon + '&distance=30&limit=1',
                        dataType: 'json',
                        jsonp: false,
                        global: false,
                        success: function (data) {
                            feature.mapillary = data;
                            showMapillary();
                        }
                    });
                }
            }
        }

        var mpTpl = '
Nejbližší foto
'
            + ''
            + ''
            + '';

        function showMapillary() {
            var mp = $('#mapillary-photo');
            if (id == mp.attr('data-osm-id') && feature.mapillary.length) {
                mp.html(mpTpl.replace(/_key/g, feature.mapillary[0].key));
            }
        }

        return tpl.join('');
    }

};
<|endoftext|>


<commit_before>var express = require('express');
var app = express();

app.use('/app', express.static(__dirname + '/public'));

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Node app is up and running to serve static HTML content.');
});<commit_msg>Changed for HA Proxy requirements.<commit_after>var express = require('express');
var app = express();

app.use('/game', express.static(__dirname + '/public'));

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Node app is up and running to serve static HTML content.');
});<|endoftext|>