<commit_before>import widgets from 'jupyter-js-widgets'
import _ from 'underscore'

import GoogleMapsLoader from 'google-maps'

function reloadGoogleMaps(configuration) {
    GoogleMapsLoader.release();
    GoogleMapsLoader.LIBRARIES = ["visualization"] ;
    if (configuration["api_key"] !== null &&
        configuration["api_key"] !== undefined) {
            GoogleMapsLoader.KEY = configuration["api_key"];
    };
}

reloadGoogleMaps({}) ;

function gPointToList(gpoint) {
    return [gpoint.lat(), gpoint.lng()]
}

function gBoundsToList(gbounds) {
    const sw = gPointToList(gbounds.getSouthWest())
    const ne = gPointToList(gbounds.getNorthEast())
    return [sw, ne]
}

// Mixins

const ConfigurationMixin = {
    load_configuration() {
        const model_configuration = this.model.get("configuration")
        reloadGoogleMaps(model_configuration)
    }
}

// Views

const GMapsLayerView = widgets.WidgetView.extend({
    initialize(parameters) {
        GMapsLayerView.__super__.initialize.apply(this, arguments)
        this.map_view = this.options.map_view
    }
})

export const DirectionsLayerView = GMapsLayerView.extend({
    render() {
        const rendererOptions = { map: this.map_view.map }

        this.directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions)

        const model_data = this.model.get("data");
        const orig = this.get_orig(model_data);
        const dest = this.get_dest(model_data);
        const wps = this.get_wps(model_data);

        const request = {
            origin: orig,
            destination: dest,
            waypoints: wps,
            travelMode: google.maps.TravelMode.DRIVING
        };

        const directionsService = new google.maps.DirectionsService();

        directionsService.route(request, (response, status) => {
            // print to the browser console (mostly for debugging)
            console.log("Direction service returned: ", status) ;
            // set a flag in the model
            this.model.set("layer_status", status) ;
            this.touch() ; // push `layer_status` changes to the model
            if (status == google.maps.DirectionsStatus.OK) {
                this.response = this.directionsDisplay ;
                this.directionsDisplay.setDirections(response);
            }
        });
    },

    add_to_map_view(map_view) { },

    get_orig(model_data) {
        const first_point = _.first(model_data)
        return new google.maps.LatLng(first_point[0], first_point[1])
    },

    get_dest(model_data) {
        const last_point = _.last(model_data)
        return new google.maps.LatLng(last_point[0], last_point[1])
    },

    get_wps(model_data) {
        const without_first = _.tail(model_data)
        const without_last = _.initial(without_first)
        const data_as_google = _.map(without_last, (point) => {
            const google_point = new google.maps.LatLng(point[0], point[1])
            return {location: google_point}
        })
        return data_as_google
    }
})

const HeatmapLayerBaseView = GMapsLayerView.extend({
    render() {
        this.model_events() ;
        GoogleMapsLoader.load((google) => {
            this.heatmap = new google.maps.visualization.HeatmapLayer({
                data: this.get_data(),
                radius: this.model.get("point_radius"),
                maxIntensity: this.model.get("max_intensity")
            }) ;
        });
    },

    add_to_map_view(map_view) {
        this.heatmap.setMap(map_view.map)
    },

    model_events() {
        this.model.on("change:point_radius", this.update_radius, this)
        this.model.on("change:max_intensity", this.update_max_intensity, this)
    },

    get_data() {},

    update_radius() {
        this.heatmap.set('radius', this.model.get('point_radius'));
    },

    update_max_intensity() {
        this.heatmap.set('maxIntensity', this.model.get('max_intensity'));
    }

})

export const SimpleHeatmapLayerView = HeatmapLayerBaseView.extend({
    get_data() {
        const data = this.model.get("data");
        const data_as_google = new google.maps.MVCArray(
            _.map(data, (point) => {
                return new google.maps.LatLng(point[0], point[1]);
            })
        );
        return data_as_google
    }
});

export const WeightedHeatmapLayerView = HeatmapLayerBaseView.extend({
    get_data() {
        const data = this.model.get("data");
        const data_as_google = new google.maps.MVCArray(
            _.map(data, (weighted_point) => {
                const location = new google.maps.LatLng(
                    weighted_point[0], weighted_point[1]);
                const weight = weighted_point[2];
                return { location: location, weight: weight };
            })
        );
        return data_as_google;
    }
})

export const PlainmapView = widgets.DOMWidgetView.extend({
    render() {
        this.load_configuration();
        this.el.style["width"] = this.model.get("width");
        this.el.style["height"] = this.model.get("height");

        const initial_bounds = this.model.get("data_bounds");

        this.layer_views = new widgets.ViewList(this.add_layer_model, null, this);
        this.model_events() ;

        this.on("displayed", () => {
            GoogleMapsLoader.load((google) => {
                this.map = new google.maps.Map(this.el) ;
                this.update_bounds(initial_bounds);

                this.layer_views.update(this.model.get("layers"));

                // hack to force the map to redraw
                setTimeout(() => {
                    google.maps.event.trigger(this.map, 'resize') ;
                }, 1000);
            })
        })
    },

    model_events() {
        this.model.on("change:data_bounds", this.update_bounds, this);
    },

    gmaps_events() {},

    update_bounds() {
        const model_bounds = this.model.get("data_bounds");
        const bounds_bl = new google.maps.LatLng(
            model_bounds[0][0], model_bounds[0][1]);
        const bounds_tr = new google.maps.LatLng(
            model_bounds[1][0], model_bounds[1][1]);
        const bounds = new google.maps.LatLngBounds(bounds_bl, bounds_tr)
        this.map.fitBounds(bounds);
    },

    add_layer_model(child_model) {
        return this.create_child_view(
            child_model, {map_view: this}
        ).then((child_view) => {
            child_view.add_to_map_view(this) ;
            return child_view;
        })
    },

})

_.extend(PlainmapView.prototype, ConfigurationMixin);

// Models

export const GMapsLayerModel = widgets.WidgetModel.extend({
    defaults: _.extend({}, widgets.WidgetModel.prototype.defaults, {
        _view_name : 'GMapsLayerView',
        _model_name : 'GMapsLayerModel',
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
    })
});

export const DirectionsLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "DirectionsLayerView",
        _model_name: "DirectionsLayerModel"
    })
});

export const SimpleHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SimpleHeatmapLayerView",
        _model_name: "SimpleHeatmapLayerModel"
    })
});

export const WeightedHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "WeightedHeatmapLayerView",
        _model_name: "WeightedHeatmapLayerModel"
    })
});

export const PlainmapModel = widgets.DOMWidgetModel.extend({
    defaults: _.extend({}, widgets.DOMWidgetModel.prototype.defaults, {
        _view_name: "PlainmapView",
        _model_name: "PlainmapModel",
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
        width: "600px",
        height: "400px"

    })
}, {
    serializers: _.extend({
            layers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
});
<commit_msg>Follow JS conventions for variable names<commit_after>import widgets from 'jupyter-js-widgets'
import _ from 'underscore'

import GoogleMapsLoader from 'google-maps'

function reloadGoogleMaps(configuration) {
    GoogleMapsLoader.release();
    GoogleMapsLoader.LIBRARIES = ["visualization"] ;
    if (configuration["api_key"] !== null &&
        configuration["api_key"] !== undefined) {
            GoogleMapsLoader.KEY = configuration["api_key"];
    };
}

reloadGoogleMaps({}) ;

function gPointToList(gpoint) {
    return [gpoint.lat(), gpoint.lng()]
}

function gBoundsToList(gbounds) {
    const sw = gPointToList(gbounds.getSouthWest())
    const ne = gPointToList(gbounds.getNorthEast())
    return [sw, ne]
}

// Mixins

const ConfigurationMixin = {
    loadConfiguration() {
        const modelConfiguration = this.model.get("configuration")
        reloadGoogleMaps(modelConfiguration)
    }
}

// Views

const GMapsLayerView = widgets.WidgetView.extend({
    initialize(parameters) {
        GMapsLayerView.__super__.initialize.apply(this, arguments)
        this.mapView = this.options.mapView
    }
})

export const DirectionsLayerView = GMapsLayerView.extend({
    render() {
        const rendererOptions = { map: this.mapView.map }

        this.directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions)

        const modelData = this.model.get("data");

        const request = {
            origin: this.getOrigin(modelData),
            destination: this.getDestination(modelData),
            waypoints: this.getWaypoints(modelData),
            travelMode: google.maps.TravelMode.DRIVING
        };

        const directionsService = new google.maps.DirectionsService();

        directionsService.route(request, (response, status) => {
            // print to the browser console (mostly for debugging)
            console.log(`Direction service returned: ${status}`) ;
            // set a flag in the model
            this.model.set("layer_status", status) ;
            this.touch() ; // push `layer_status` changes to the model
            if (status == google.maps.DirectionsStatus.OK) {
                this.response = this.directionsDisplay ;
                this.directionsDisplay.setDirections(response);
            }
        });
    },

    addToMapView(mapView) { },

    getOrigin(modelData) {
        const [lat, lng] = _.first(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getDestination(modelData) {
        const [lat, lng] = _.last(modelData)
        return new google.maps.LatLng(lat, lng)
    },

    getWaypoints(modelData) {
        const withoutFirst = _.tail(modelData)
        const withoutLast = _.initial(withoutFirst)
        const dataAsGoogle = withoutLast.map(([lat, lng]) => {
            return {location: new google.maps.LatLng(lat, lng)}
        })
        return dataAsGoogle
    }
})

const HeatmapLayerBaseView = GMapsLayerView.extend({
    render() {
        this.modelEvents() ;
        GoogleMapsLoader.load((google) => {
            this.heatmap = new google.maps.visualization.HeatmapLayer({
                data: this.getData(),
                radius: this.model.get("point_radius"),
                maxIntensity: this.model.get("max_intensity")
            }) ;
        });
    },

    addToMapView(mapView) {
        this.heatmap.setMap(mapView.map)
    },

    modelEvents() {
        this.model.on("change:point_radius", this.updateRadius, this)
        this.model.on("change:max_intensity", this.updateMaxIntensity, this)
    },

    get_data() {},

    updateRadius() {
        this.heatmap.set('radius', this.model.get('point_radius'));
    },

    updateMaxIntensity() {
        this.heatmap.set('maxIntensity', this.model.get('max_intensity'));
    }

})

export const SimpleHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data")
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng]) => new google.maps.LatLng(lat, lng))
        )
        return dataAsGoogle
    }
});

export const WeightedHeatmapLayerView = HeatmapLayerBaseView.extend({
    getData() {
        const data = this.model.get("data");
        const dataAsGoogle = new google.maps.MVCArray(
            data.map(([lat, lng, weight]) => {
                const location = new google.maps.LatLng(lat, lng)
                return { location: location, weight: weight }
            })
        );
        return dataAsGoogle
    }
})

export const PlainmapView = widgets.DOMWidgetView.extend({
    render() {
        this.loadConfiguration();
        this.el.style["width"] = this.model.get("width");
        this.el.style["height"] = this.model.get("height");

        const initialBounds = this.model.get("data_bounds");

        this.layerViews = new widgets.ViewList(this.addLayerModel, null, this);
        this.modelEvents() ;

        this.on("displayed", () => {
            GoogleMapsLoader.load((google) => {
                this.map = new google.maps.Map(this.el) ;
                this.updateBounds(initialBounds);

                this.layerViews.update(this.model.get("layers"));

                // hack to force the map to redraw
                setTimeout(() => {
                    google.maps.event.trigger(this.map, 'resize') ;
                }, 1000);
            })
        })
    },

    modelEvents() {
        this.model.on("change:data_bounds", this.updateBounds, this);
    },

    updateBounds() {
        const [[latBL, lngBL], [latTR, lngTR]] = this.model.get("data_bounds")
        const boundBL = new google.maps.LatLng(latBL, lngBL)
        const boundTR = new google.maps.LatLng(latTR, lngTR)
        const bounds = new google.maps.LatLngBounds(boundBL, boundTR)
        this.map.fitBounds(bounds);
    },

    addLayerModel(childModel) {
        return this.create_child_view(
            childModel, {mapView: this}
        ).then((childView) => {
            childView.addToMapView(this) ;
            return childView;
        })
    },

})

_.extend(PlainmapView.prototype, ConfigurationMixin);

// Models

export const GMapsLayerModel = widgets.WidgetModel.extend({
    defaults: _.extend({}, widgets.WidgetModel.prototype.defaults, {
        _view_name : 'GMapsLayerView',
        _model_name : 'GMapsLayerModel',
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
    })
});

export const DirectionsLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "DirectionsLayerView",
        _model_name: "DirectionsLayerModel"
    })
});

export const SimpleHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "SimpleHeatmapLayerView",
        _model_name: "SimpleHeatmapLayerModel"
    })
});

export const WeightedHeatmapLayerModel = GMapsLayerModel.extend({
    defaults: _.extend({}, GMapsLayerModel.prototype.defaults, {
        _view_name: "WeightedHeatmapLayerView",
        _model_name: "WeightedHeatmapLayerModel"
    })
});

export const PlainmapModel = widgets.DOMWidgetModel.extend({
    defaults: _.extend({}, widgets.DOMWidgetModel.prototype.defaults, {
        _view_name: "PlainmapView",
        _model_name: "PlainmapModel",
        _view_module : 'jupyter-gmaps',
        _model_module : 'jupyter-gmaps',
        width: "600px",
        height: "400px"

    })
}, {
    serializers: _.extend({
            layers: {deserialize: widgets.unpack_models}
    }, widgets.DOMWidgetModel.serializers)
});
<|endoftext|>


<commit_before>/*
	a simple test to check whether URL RegEx works as expected

	 RegEx for URL format by Diego Perini
	 https://gist.github.com/dperini/729294

	 So far, this seems to be the most complete regex for URL formats
	 https://mathiasbynens.be/demo/url-regex
 */
var urlRegEx = /^((?:(?:https?|ftp):\/\/)|)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;

var domainsToTest = [
	{ "url": "list.com", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah/", "expectedResult": true },
	{ "url": "115.68.20.68", "expectedResult": true },
	{ "url": "http://www.google.com", "expectedResult": true },
	{ "url": "https://www.facebook.com/", "expectedResult": true },
	{ "url": "ftp://ftp.com", "expectedResult": true },
	{ "url": "10.88.186.68", "expectedResult": true },
	{ "url": "to.do.so:1155", "expectedResult": true },
	{ "url": "whatever:8088", "expectedResult": false },
	{ "url": "http://foo.com/blah_blah", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah/", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah_(wikipedia)", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah_(wikipedia)_(again)", "expectedResult": true },
	{ "url": "http://www.example.com/wpstyle/?p=364", "expectedResult": true },
	{ "url": "https://www.example.com/foo/?bar=baz&inga=42&quux", "expectedResult": true },
	{ "url": "http://✪df.ws/123", "expectedResult": true },
	{ "url": "http://userid:password@example.com:8080", "expectedResult": true },
	{ "url": "http://userid:password@example.com:8080/", "expectedResult": true },
	{ "url": "http://userid@example.com", "expectedResult": true },
	{ "url": "http://userid@example.com/", "expectedResult": true },
	{ "url": "http://userid@example.com:8080", "expectedResult": true },
	{ "url": "http://userid@example.com:8080/", "expectedResult": true },
	{ "url": "http://userid:password@example.com", "expectedResult": true },
	{ "url": "http://userid:password@example.com/", "expectedResult": true },
	{ "url": "http://142.42.1.1/", "expectedResult": true },
	{ "url": "http://142.42.1.1:8080/", "expectedResult": true },
	{ "url": "http://➡.ws/䨹", "expectedResult": true },
	{ "url": "http://⌘.ws", "expectedResult": true },
	{ "url": "http://⌘.ws/", "expectedResult": true },
	{ "url": "http://foo.com/blah_(wikipedia)#cite-1", "expectedResult": true },
	{ "url": "http://foo.com/blah_(wikipedia)_blah#cite-1", "expectedResult": true },
	{ "url": "http://foo.com/unicode_(✪)_in_parens", "expectedResult": true },
	{ "url": "http://foo.com/(something)?after=parens", "expectedResult": true },
	{ "url": "http://☺.damowmow.com/", "expectedResult": true },
	{ "url": "http://code.google.com/events/#&product=browser", "expectedResult": true },
	{ "url": "http://j.mp", "expectedResult": true },
	{ "url": "ftp://foo.bar/baz", "expectedResult": true },
	{ "url": "http://foo.bar/?q=Test%20URL-encoded%20stuff", "expectedResult": true },
	{ "url": "http://مثال.إختبار", "expectedResult": true },
	{ "url": "http://例子.测试", "expectedResult": true },
	{ "url": "http://उदाहरण.परीक्षा", "expectedResult": true },
	{ "url": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", "expectedResult": true },
	{ "url": "http://1337.net", "expectedResult": true },
	{ "url": "http://a.b-c.de", "expectedResult": true },
	{ "url": "http://223.255.255.254", "expectedResult": true },
	{ "url": "http://", "expectedResult": false },
	{ "url": "http://.", "expectedResult": false },
	{ "url": "http://..", "expectedResult": false },
	{ "url": "http://../", "expectedResult": false },
	{ "url": "http://?", "expectedResult": false },
	{ "url": "http://??", "expectedResult": false },
	{ "url": "http://??/", "expectedResult": false },
	{ "url": "http://#", "expectedResult": false },
	{ "url": "http://##", "expectedResult": false },
	{ "url": "http://##/", "expectedResult": false },
	{ "url": "http://foo.bar?q=Spaces should be encoded", "expectedResult": false },
	{ "url": "//", "expectedResult": false },
	{ "url": "//a", "expectedResult": false },
	{ "url": "///a", "expectedResult": false },
	{ "url": "///", "expectedResult": false },
	{ "url": "http:///a", "expectedResult": false },
	{ "url": "foo.com", "expectedResult": true },
	{ "url": "rdar://1234", "expectedResult": false },
	{ "url": "h://test", "expectedResult": false },
	{ "url": "http:// shouldfail.com", "expectedResult": false },
	{ "url": ":// should fail", "expectedResult": false },
	{ "url": "http://foo.bar/foo(bar)baz quux", "expectedResult": false },
	{ "url": "ftps://foo.bar/", "expectedResult": false },
	{ "url": "http://-error-.invalid/", "expectedResult": false },
	{ "url": "http://a.b--c.de/", "expectedResult": false },
	{ "url": "http://-a.b.co", "expectedResult": false },
	{ "url": "http://a.b-.co", "expectedResult": false },
	{ "url": "http://0.0.0.0", "expectedResult": false },
	{ "url": "http://10.1.1.0", "expectedResult": false },
	{ "url": "http://10.1.1.255", "expectedResult": false },
	{ "url": "http://224.1.1.1", "expectedResult": false },
	{ "url": "http://1.1.1.1.1", "expectedResult": false },
	{ "url": "http://123.123.123", "expectedResult": false },
	{ "url": "http://3628126748", "expectedResult": false },
	{ "url": "http://.www.foo.bar/", "expectedResult": false },
	{ "url": "http://www.foo.bar./", "expectedResult": false },
	{ "url": "http://.www.foo.bar./", "expectedResult": false },
	{ "url": "http://10.1.1.1", "expectedResult": false },
	{ "url": "http://10.1.1.254", "expectedResult": false }
];

var passCount = 0;
var failCount = 0;

for (var i = 0; i < domainsToTest.length; i++) {
	var domainToTest = domainsToTest[i].url;
	var expectedResult = domainsToTest[i].expectedResult;
	var actualResult = urlRegEx.test(domainToTest);
	var testResult = (expectedResult === actualResult);

	// increase pass/fail counts
	if (testResult) passCount++;
	else failCount++;

	// only print result if pass fails
	if (testResult === false) {
		console.log("====================================================");
		console.log("Testing " + domainToTest);
		console.log("Result: " + (testResult ? "PASS" : "FAIL"));
		console.log("Expected: " + expectedResult + ", Actual: " + actualResult);
	}
}

console.log("====================================================");
console.log("                       SUMMARY                      ");
console.log("            Pass: " + passCount + ", Fail: " + failCount + ", Total: " + domainsToTest.length);
console.log("====================================================");<commit_msg>update test script usage comment<commit_after>/*
	a simple test to check whether URL RegEx works as expected

	 RegEx for URL format by Diego Perini
	 https://gist.github.com/dperini/729294

	 So far, this seems to be the most complete regex for URL formats
	 https://mathiasbynens.be/demo/url-regex

	 Test script usage:
	 $ nodejs URLRegExTest.js
 */
var urlRegEx = /^((?:(?:https?|ftp):\/\/)|)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i;

var domainsToTest = [
	{ "url": "list.com", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah/", "expectedResult": true },
	{ "url": "115.68.20.68", "expectedResult": true },
	{ "url": "http://www.google.com", "expectedResult": true },
	{ "url": "https://www.facebook.com/", "expectedResult": true },
	{ "url": "ftp://ftp.com", "expectedResult": true },
	{ "url": "10.88.186.68", "expectedResult": true },
	{ "url": "to.do.so:1155", "expectedResult": true },
	{ "url": "whatever:8088", "expectedResult": false },
	{ "url": "http://foo.com/blah_blah", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah/", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah_(wikipedia)", "expectedResult": true },
	{ "url": "http://foo.com/blah_blah_(wikipedia)_(again)", "expectedResult": true },
	{ "url": "http://www.example.com/wpstyle/?p=364", "expectedResult": true },
	{ "url": "https://www.example.com/foo/?bar=baz&inga=42&quux", "expectedResult": true },
	{ "url": "http://✪df.ws/123", "expectedResult": true },
	{ "url": "http://userid:password@example.com:8080", "expectedResult": true },
	{ "url": "http://userid:password@example.com:8080/", "expectedResult": true },
	{ "url": "http://userid@example.com", "expectedResult": true },
	{ "url": "http://userid@example.com/", "expectedResult": true },
	{ "url": "http://userid@example.com:8080", "expectedResult": true },
	{ "url": "http://userid@example.com:8080/", "expectedResult": true },
	{ "url": "http://userid:password@example.com", "expectedResult": true },
	{ "url": "http://userid:password@example.com/", "expectedResult": true },
	{ "url": "http://142.42.1.1/", "expectedResult": true },
	{ "url": "http://142.42.1.1:8080/", "expectedResult": true },
	{ "url": "http://➡.ws/䨹", "expectedResult": true },
	{ "url": "http://⌘.ws", "expectedResult": true },
	{ "url": "http://⌘.ws/", "expectedResult": true },
	{ "url": "http://foo.com/blah_(wikipedia)#cite-1", "expectedResult": true },
	{ "url": "http://foo.com/blah_(wikipedia)_blah#cite-1", "expectedResult": true },
	{ "url": "http://foo.com/unicode_(✪)_in_parens", "expectedResult": true },
	{ "url": "http://foo.com/(something)?after=parens", "expectedResult": true },
	{ "url": "http://☺.damowmow.com/", "expectedResult": true },
	{ "url": "http://code.google.com/events/#&product=browser", "expectedResult": true },
	{ "url": "http://j.mp", "expectedResult": true },
	{ "url": "ftp://foo.bar/baz", "expectedResult": true },
	{ "url": "http://foo.bar/?q=Test%20URL-encoded%20stuff", "expectedResult": true },
	{ "url": "http://مثال.إختبار", "expectedResult": true },
	{ "url": "http://例子.测试", "expectedResult": true },
	{ "url": "http://उदाहरण.परीक्षा", "expectedResult": true },
	{ "url": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", "expectedResult": true },
	{ "url": "http://1337.net", "expectedResult": true },
	{ "url": "http://a.b-c.de", "expectedResult": true },
	{ "url": "http://223.255.255.254", "expectedResult": true },
	{ "url": "http://", "expectedResult": false },
	{ "url": "http://.", "expectedResult": false },
	{ "url": "http://..", "expectedResult": false },
	{ "url": "http://../", "expectedResult": false },
	{ "url": "http://?", "expectedResult": false },
	{ "url": "http://??", "expectedResult": false },
	{ "url": "http://??/", "expectedResult": false },
	{ "url": "http://#", "expectedResult": false },
	{ "url": "http://##", "expectedResult": false },
	{ "url": "http://##/", "expectedResult": false },
	{ "url": "http://foo.bar?q=Spaces should be encoded", "expectedResult": false },
	{ "url": "//", "expectedResult": false },
	{ "url": "//a", "expectedResult": false },
	{ "url": "///a", "expectedResult": false },
	{ "url": "///", "expectedResult": false },
	{ "url": "http:///a", "expectedResult": false },
	{ "url": "foo.com", "expectedResult": true },
	{ "url": "rdar://1234", "expectedResult": false },
	{ "url": "h://test", "expectedResult": false },
	{ "url": "http:// shouldfail.com", "expectedResult": false },
	{ "url": ":// should fail", "expectedResult": false },
	{ "url": "http://foo.bar/foo(bar)baz quux", "expectedResult": false },
	{ "url": "ftps://foo.bar/", "expectedResult": false },
	{ "url": "http://-error-.invalid/", "expectedResult": false },
	{ "url": "http://a.b--c.de/", "expectedResult": false },
	{ "url": "http://-a.b.co", "expectedResult": false },
	{ "url": "http://a.b-.co", "expectedResult": false },
	{ "url": "http://0.0.0.0", "expectedResult": false },
	{ "url": "http://10.1.1.0", "expectedResult": false },
	{ "url": "http://10.1.1.255", "expectedResult": false },
	{ "url": "http://224.1.1.1", "expectedResult": false },
	{ "url": "http://1.1.1.1.1", "expectedResult": false },
	{ "url": "http://123.123.123", "expectedResult": false },
	{ "url": "http://3628126748", "expectedResult": false },
	{ "url": "http://.www.foo.bar/", "expectedResult": false },
	{ "url": "http://www.foo.bar./", "expectedResult": false },
	{ "url": "http://.www.foo.bar./", "expectedResult": false },
	{ "url": "http://10.1.1.1", "expectedResult": false },
	{ "url": "http://10.1.1.254", "expectedResult": false }
];

var passCount = 0;
var failCount = 0;

for (var i = 0; i < domainsToTest.length; i++) {
	var domainToTest = domainsToTest[i].url;
	var expectedResult = domainsToTest[i].expectedResult;
	var actualResult = urlRegEx.test(domainToTest);
	var testResult = (expectedResult === actualResult);

	// increase pass/fail counts
	if (testResult) passCount++;
	else failCount++;

	// only print result if pass fails
	if (testResult === false) {
		console.log("====================================================");
		console.log("Testing " + domainToTest);
		console.log("Result: " + (testResult ? "PASS" : "FAIL"));
		console.log("Expected: " + expectedResult + ", Actual: " + actualResult);
	}
}

console.log("====================================================");
console.log("                       SUMMARY                      ");
console.log("            Pass: " + passCount + ", Fail: " + failCount + ", Total: " + domainsToTest.length);
console.log("====================================================");<|endoftext|>


<commit_before>
// Test function to check if RGBA value pixel is what we expected
function TestRGBAPixel(parser,x,y,r,g,b,a)
{
	var pix=parser.getPixelValueOnDemand(x,y);
	var rgba= parser.getRGBAPixelValue(pix)
	console.log("x=" + x + " y=" + y + " pixels="+ pix  +" " + rgba);
	if (rgba[0] == r && rgba[1] == g && rgba[2] == b && rgba[3] == a)
		return true;
	else
	  return false;
}
function testGeotiffParser(data) {

	var parser = new GeotiffParser();

	// parseHeader to read TIff / Geotiff parameters
	parser.parseHeader(data);

	parser.consoleTiffProperty();

	// If a GeoTiff retrieve the BBOX
	console.log("isGeotiff=" +  parser.isGeotiff());
	if (parser.isGeotiff())
	{
		console.log("CRS Code:" + parser.getCRSCode());

		var ul=  parser.ImageToPCS(0,0);
		var ur=  parser.ImageToPCS(parser.imageWidth,0);
		var ll=  parser.ImageToPCS(0,parser.imageLength);
		var lr=  parser.ImageToPCS(parser.imageWidth,parser.imageLength);
		if (ul[0]==1) console.log("UL=" +  ul[1],ul[2]); else console.log("UL= failure" );
		if (ur[0]==1) console.log("UR=" +  ur[1],ur[2]); else console.log("UR= failure" );
		if (ll[0]==1) console.log("LL=" +  ll[1],ll[2]); else console.log("LL= failure" );
		if (lr[0]==1) console.log("LR=" +  lr[1],lr[2]); else console.log("LR= failure" );

	}

	console.log("Try first part of the function getPixelValueOnDemand " );
	var pix=parser.getPixelValueOnDemand(123,0);
	console.log("x=" + 123 + " y=" + 0 + " pixels="+ pix  +" " + parser.getRGBAPixelValue(pix));
	/*//console.log("x=" + 11 + " y=" + 10 + " pixels="+ parser.getPixelValueOnDemand(11,10));
	console.log("x=" + 120 + " y=" + 2 + " pixels="+ parser.getPixelValueOnDemand(120,2));
	// Get the buffer of pixels
	// Get a sample of pixels value
	var pixels = parser.loadPixels();
	console.log("x=" + 10 + " y=" + 10 + " pixels="+ parser.getPixelValue(pixels,10,10) );*/

	// Test Flag_T24.tiff
	console.log(TestRGBAPixel(parser, 81,10,248,0,248,1));
	console.log(TestRGBAPixel(parser, 112,10,0,248,248,1));
	console.log(TestRGBAPixel(parser, 113,10,0,0,248,1));
	console.log(TestRGBAPixel(parser, 81,11,248,248,0,1));
	console.log(TestRGBAPixel(parser, 82,11,0,248,0,1));
	console.log(TestRGBAPixel(parser, 81,42,248,0,0,1));
	console.log(TestRGBAPixel(parser, 117,49,248,0,248,1));

}

function LoadGeotiffParser(data,canvas) {

	var parser = new GeotiffParser();

	// parseHeader to read TIff / Geotiff parameters
	parser.parseHeader(data);
	var tiffCanvas = parser.toCanvas(canvas,0,0,124,124);

	return tiffCanvas;
}
	<commit_msg>Update of the test<commit_after>
// Test function to check if RGBA value pixel is what we expected
function TestRGBAPixel(parser,x,y,r,g,b,a)
{
	var pix=parser.getPixelValueOnDemand(x,y);
	var rgba= parser.getRGBAPixelValue(pix)
	console.log("x=" + x + " y=" + y + " pixels="+ pix  +" " + rgba);
	if (rgba[0] == r && rgba[1] == g && rgba[2] == b && rgba[3] == a)
		return true;
	else
	  return false;
}
function testGeotiffParser(data) {

	var parser = new GeotiffParser();

	// parseHeader to read TIff / Geotiff parameters
	parser.parseHeader(data);

	parser.consoleTiffProperty();

	// If a GeoTiff retrieve the BBOX
	console.log("isGeotiff=" +  parser.isGeotiff());
	if (parser.isGeotiff())
	{
		console.log("CRS Code:" + parser.getCRSCode());

		var ul=  parser.ImageToPCS(0,0);
		var ur=  parser.ImageToPCS(parser.imageWidth,0);
		var ll=  parser.ImageToPCS(0,parser.imageLength);
		var lr=  parser.ImageToPCS(parser.imageWidth,parser.imageLength);
		if (ul[0]==1) console.log("UL=" +  ul[1],ul[2]); else console.log("UL= failure" );
		if (ur[0]==1) console.log("UR=" +  ur[1],ur[2]); else console.log("UR= failure" );
		if (ll[0]==1) console.log("LL=" +  ll[1],ll[2]); else console.log("LL= failure" );
		if (lr[0]==1) console.log("LR=" +  lr[1],lr[2]); else console.log("LR= failure" );

	}

	console.log("Try first part of the function getPixelValueOnDemand " );
	var pix=parser.getPixelValueOnDemand(123,0);
	console.log("x=" + 123 + " y=" + 0 + " pixels="+ pix  +" " + parser.getRGBAPixelValue(pix));
	/*//console.log("x=" + 11 + " y=" + 10 + " pixels="+ parser.getPixelValueOnDemand(11,10));
	console.log("x=" + 120 + " y=" + 2 + " pixels="+ parser.getPixelValueOnDemand(120,2));
	// Get the buffer of pixels
	// Get a sample of pixels value
	var pixels = parser.loadPixels();
	console.log("x=" + 10 + " y=" + 10 + " pixels="+ parser.getPixelValue(pixels,10,10) );*/

}
// Test Flag_T24.tiff
// SubTest for Flag_T24.tiff
function testFlag_T24_Color(parser) {

	console.log(TestRGBAPixel(parser, 81,10,248,0,248,1));
	console.log(TestRGBAPixel(parser, 112,10,0,248,248,1));
	console.log(TestRGBAPixel(parser, 113,10,0,0,248,1));
	console.log(TestRGBAPixel(parser, 81,11,248,248,0,1));
	console.log(TestRGBAPixel(parser, 82,11,0,248,0,1));
	console.log(TestRGBAPixel(parser, 81,42,248,0,0,1));
	console.log(TestRGBAPixel(parser, 117,49,248,0,248,1));
}

function LoadGeotiffParser(data,canvas) {

	var parser = new GeotiffParser();

	// parseHeader to read TIff / Geotiff parameters
	parser.parseHeader(data);
	var tiffCanvas = parser.toCanvas(canvas,0,0,parser.imageWidth,parser.imageLength);

	return tiffCanvas;
}
	<|endoftext|>


<commit_before>// Copyright 2013-2020, University of Colorado Boulder

/**
 * Creates and references a stylesheet that can be built up while Scenery is loading.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import scenery from '../scenery.js';

const styleElement = document.createElement( 'style' );
styleElement.type = 'text/css';
document.head.appendChild( styleElement );

const stylesheet = document.styleSheets[ document.styleSheets.length - 1 ];
assert && assert( stylesheet.disabled === false );

export default scenery.register( 'SceneryStyle', {
  stylesheet: stylesheet,
  styleElement: styleElement,

  addRule: function( ruleString ) {
    // using a this reference so it doesn't need to be a closure
    this.stylesheet.insertRule( ruleString, 0 );
  }
} );<commit_msg>Separate inherit from export, see https://github.com/phetsims/chipper/issues/848<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/**
 * Creates and references a stylesheet that can be built up while Scenery is loading.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import scenery from '../scenery.js';

const styleElement = document.createElement( 'style' );
styleElement.type = 'text/css';
document.head.appendChild( styleElement );

const stylesheet = document.styleSheets[ document.styleSheets.length - 1 ];
assert && assert( stylesheet.disabled === false );

const SceneryStyle = {
  stylesheet: stylesheet,
  styleElement: styleElement,

  addRule: function( ruleString ) {
    // using a this reference so it doesn't need to be a closure
    this.stylesheet.insertRule( ruleString, 0 );
  }
};
scenery.register( 'SceneryStyle', SceneryStyle );
export default SceneryStyle;<|endoftext|>


<commit_before>(function(jQuery, window) {
	"use strict";

	var win = jQuery(window);
	var doc = jQuery(document);
	var rules = [];
	var rem = /(\d*\.?\d+)r?em/;
	var rpercent = /(\d*\.?\d+)%/;
	var width, height, scrollTop, scrollHeight, fontSize;

	var types = {
		number: function(n) { return n; },

		function: function(fn) { return fn(); },

		string: function(string) {
			var data, n;

			data = rem.exec(value);
			if (data) {
				n = parseFloat(data[1]);
				return getFontSize() * n;
			}

			data = rpercent.exec(value);
			if (data) {
				n = parseFloat(data[1]) / 100;
				return width * n;
			}

			throw new Error('[window.breakpoint] \'' + value + '\' cannot be parsed as rem, em or %.');
		}
	};

	var tests = {
		minWidth: function(value)  { return width >= types[typeof value](value); },
		maxWidth: function(value)  { return width <  types[typeof value](value); },
		minHeight: function(value) { return height >= types[typeof value](value); },
		maxHeight: function(value) { return height <  types[typeof value](value); },
		minScrollTop: function(value) { return scrollTop >= types[typeof value](value); },
		maxScrollTop: function(value) { return scrollTop <  types[typeof value](value); },
		minScrollBottom: function(value) { return (scrollHeight - height - scrollTop) >= types[typeof value](value); },
		maxScrollBottom: function(value) { return (scrollHeight - height - scrollTop) <  types[typeof value](value); }
	};

	function getStyle(node, name) {
		return window.getComputedStyle ?
			window
			.getComputedStyle(node, null)
			.getPropertyValue(name) :
			0 ;
	}

	function getFontSize() {
		return fontSize ||
			(fontSize = parseFloat(getStyle(document.documentElement, "font-size"), 10));
	}

	function media(query, fn1, fn2) {
		var rule = {};

		rule.query = query;
		rule.enter = fn1;
		rule.exit = fn2;
		rules.push(rule);

		return query;
	}

	function testProperty(property, value) {
		cutoffs[property]
	}

	function test(query) {
		var key;

		for (key in query) {
			if (!query.hasOwnProperty(key)) { continue; }
			if (!tests[key](query[key])) { return false; }
		}

		return true;
	}

	function update() {
		var l = rules.length;
		var rule;

		// Run exiting rules
		while (l--) {
			rule = rules[l];

			if (rule.state && !test(rule.query)) {
				rule.state = false;
				rule.exit && rule.exit();
			}
		}

		l = rules.length;

		// Run entering rules
		while (l--) {
			rule = rules[l];

			if (!rule.state && test(rule.query)) {
				rule.state = true;
				rule.enter && rule.enter();
			}
		}
	}

	function scroll(e) {
		scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
		update();
	}

	function resize(e) {
		width = window.innerWidth;
		height = window.innerHeight;
		scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
		update();
	}

	win
	.on('scroll', scroll)
	.on('resize', resize);

	doc
	.ready(update)
	.on('DOMContentLoaded', update);

	width = window.innerWidth;
	height = window.innerHeight;
	scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
	scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;

	window.breakpoint = media;
})(jQuery, window);
<commit_msg>window.breakpoint<commit_after>(function(jQuery, window) {
	"use strict";

	var win = jQuery(window);
	var doc = jQuery(document);
	var rules = [];
	var rem = /(\d*\.?\d+)r?em/;
	var rpercent = /(\d*\.?\d+)%/;
	var width, height, scrollTop, scrollHeight, fontSize;

	var types = {
		number: function(n) { return n; },

		function: function(fn) { return fn(); },

		string: function(string) {
			var data, n;

			data = rem.exec(value);
			if (data) {
				n = parseFloat(data[1]);
				return getFontSize() * n;
			}

			data = rpercent.exec(value);
			if (data) {
				n = parseFloat(data[1]) / 100;
				return width * n;
			}

			throw new Error('[window.breakpoint] \'' + value + '\' cannot be parsed as rem, em or %.');
		}
	};

	var tests = {
		minWidth: function(value)  { return width >= types[typeof value](value); },
		maxWidth: function(value)  { return width <  types[typeof value](value); },
		minHeight: function(value) { return height >= types[typeof value](value); },
		maxHeight: function(value) { return height <  types[typeof value](value); },
		minScrollTop: function(value) { return scrollTop >= types[typeof value](value); },
		maxScrollTop: function(value) { return scrollTop <  types[typeof value](value); },
		minScrollBottom: function(value) { return (scrollHeight - height - scrollTop) >= types[typeof value](value); },
		maxScrollBottom: function(value) { return (scrollHeight - height - scrollTop) <  types[typeof value](value); }
	};

	function getStyle(node, name) {
		return window.getComputedStyle ?
			window
			.getComputedStyle(node, null)
			.getPropertyValue(name) :
			0 ;
	}

	function getFontSize() {
		return fontSize ||
			(fontSize = parseFloat(getStyle(document.documentElement, "font-size"), 10));
	}

	function media(query, fn1, fn2) {
		var rule = {};

		rule.query = query;
		rule.enter = fn1;
		rule.exit = fn2;
		rules.push(rule);

		return query;
	}

	function testProperty(property, value) {
		cutoffs[property]
	}

	function test(query) {
		var keys = Object.keys(query);
		var n = keys.length;
		var key;

		if (keys.length === 0) { return false; }

		while (n--) {
			key = keys[n];
			if (!tests[key](query[key])) { return false; }
		}

		return true;
	}

	function update() {
		var l = rules.length;
		var rule;

		// Run exiting rules
		while (l--) {
			rule = rules[l];

			if (rule.state && !test(rule.query)) {
				rule.state = false;
				rule.exit && rule.exit();
			}
		}

		l = rules.length;

		// Run entering rules
		while (l--) {
			rule = rules[l];

			if (!rule.state && test(rule.query)) {
				rule.state = true;
				rule.enter && rule.enter();
			}
		}
	}

	function scroll(e) {
		scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
		update();
	}

	function resize(e) {
		width = window.innerWidth;
		height = window.innerHeight;
		scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
		update();
	}

	win
	.on('scroll', scroll)
	.on('resize', resize);

	doc
	.ready(update)
	.on('DOMContentLoaded', update);

	width = window.innerWidth;
	height = window.innerHeight;
	scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
	scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;

	window.breakpoint = media;
})(jQuery, window);
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { getAriaLabel, getTabsClass } from './utils';
import TabLink from './TabLink';

class ControlledTabs extends PureComponent {
  static defaultProps = {
    renderInactiveTabs: false,
    scoped: false,
    styled: false,
  }

  static propTypes = {
    /**
     * Id of the currently active tab
     */
    activeTab: PropTypes.string.isRequired,
    /**
     * One or more Tab components
     */
    children: PropTypes.node.isRequired,
    /**
     * Callback triggered when the Tab is changed. Receives (nextIndex)
     */
    onChangeTab: PropTypes.func.isRequired,
    /**
     * When this is true, inActive tabs will be rendered and hidden with .slds-hide instead of returning null
     */
    renderInactiveTabs: PropTypes.bool,
    /**
     * Renders scoped variant
     */
    scoped: PropTypes.bool,
    /**
     * Renders Card-like variant
     */
    styled: PropTypes.bool,
  };

  static getPanelState(children) {
    const positionReducer = (acc, curr, i) => ({
      ...acc,
      [`${curr.props.id}`]: i,
    });

    const childrenArray = React.Children.toArray(children);

    const nextState = {
      positions: childrenArray.reduce(positionReducer, {}),
      focusedTab: false,
      length: childrenArray.length,
    };

    return nextState;
  }

  constructor(props) {
    super(props);
    this.state = ControlledTabs.getPanelState(props.children);
    /** refs are used to focus links after keyboard navigation occured */
    this.linkNodes = new Map();
  }

  componentWillReceiveProps({ children: nextChildren }) {
    if (ControlledTabs.getPanelState(nextChildren) !== ControlledTabs.getPanelState(this.props.children)) {
      this.setState(ControlledTabs.getPanelState(nextChildren));
    }
  }

  onChangeTab(nextActiveTab) {
    const { activeTab, onChangeTab } = this.props;
    if (nextActiveTab !== activeTab) {
      this.setState({ focusedTab: nextActiveTab });
      onChangeTab(nextActiveTab);
    }
  }

  onBlur = ({ currentTarget }) => {
    /** Workaround to only fire blur when the whole navigation is unfocused */
    setTimeout(() => {
      if (!currentTarget.contains(document.activeElement)) {
        this.setState({ focusedTab: false });
      }
    }, 0);
  }

  onLinkKeyUp = (evt) => {
    const linkNodes = this.linkNodes;

    const { key } = evt;
    const isLeftKey = key === 'ArrowLeft';
    const isRightKey = key === 'ArrowRight';
    if (!isLeftKey && !isRightKey) return;

    const { length } = this.state;
    const currentPosition = this.getCurrentPosition();

    /* eslint-disable no-nested-ternary */
    const nextIndex = isLeftKey
      ? (currentPosition > 0) ? currentPosition - 1 : Math.max(length - 1, 0)
      : (currentPosition < length - 1) ? currentPosition + 1 : 0;
    /* eslint-enable */

    const nextId = this.getIdForPosition(nextIndex);

    this.onChangeTab(nextId);
    /** Accesses the ref defined in <TabLink /> */
    const linkNode = linkNodes.get(nextId);
    if (linkNode && linkNode.link) { linkNode.link.focus(); }
  }

  getCurrentPosition() {
    const { activeTab } = this.props;
    const { positions } = this.state;
    return positions[activeTab];
  }

  getIdForPosition(position) {
    const { positions } = this.state;
    return Object.keys(positions).find(key => positions[key] === position);
  }

  getLinkRefSetter(id) {
    return (c) => { this.linkNodes.set(id, c); };
  }

  getOnLinkFocus(id) {
    return () => { this.setState({ focusedTab: id }); };
  }

  getOnLinkClick(id) {
    return (evt) => {
      evt.stopPropagation();
      this.onChangeTab(id);
    };
  }

  renderLink = (child) => {
    const { focusedTab } = this.state;
    const { activeTab, scoped } = this.props;
    const { id, renderTitle, title } = child.props;

    const isActive = activeTab === id;
    const isFocused = focusedTab === id;

    return (
      <TabLink
        isActive={isActive}
        isFocused={isFocused}
        id={id}
        onFocus={this.getOnLinkFocus(id)}
        onKeyUp={this.onLinkKeyUp}
        /* Focus is triggered before click but after mouseDown, using mouseDown to prevent a render-cycle */
        onMouseDown={this.getOnLinkClick(id)}
        ref={this.getLinkRefSetter(id)}
        scoped={scoped}
        title={title}
      >
        {renderTitle ? renderTitle({ id, isActive, isFocused, title }) : title}
      </TabLink>
    );
  }

  renderActiveTab = (child) => {
    const { activeTab, renderInactiveTabs, scoped } = this.props;
    const { id } = child.props;
    const isActive = id === activeTab;
    const hideTab = !isActive && !renderInactiveTabs;

    return React.cloneElement(child, {
      'aria-labelledby': getAriaLabel(child.props.id),
      isActive: hideTab ? null : isActive,
      scoped
    });
  }

  render() {
    const { scoped, styled, children } = this.props;

    return (
      <div
        className={cx(
          getTabsClass('', scoped),
          { 'slds-tabs_card': styled }
        )}
      >
        <ul
          className={getTabsClass('__nav', scoped)}
          onBlur={this.onBlur}
          role="tablist"
        >
          {React.Children.map(children, this.renderLink)}
        </ul>
        {React.Children.map(children, this.renderActiveTab)}
      </div>
    );
  }
}

export default ControlledTabs;
<commit_msg>Prevent Tabs componentWillReceiveProps from always triggering (#285)<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { isEqual } from 'lodash';
import { getAriaLabel, getTabsClass } from './utils';
import TabLink from './TabLink';

class ControlledTabs extends PureComponent {
  static defaultProps = {
    renderInactiveTabs: false,
    scoped: false,
    styled: false,
  }

  static propTypes = {
    /**
     * Id of the currently active tab
     */
    activeTab: PropTypes.string.isRequired,
    /**
     * One or more Tab components
     */
    children: PropTypes.node.isRequired,
    /**
     * Callback triggered when the Tab is changed. Receives (nextIndex)
     */
    onChangeTab: PropTypes.func.isRequired,
    /**
     * When this is true, inActive tabs will be rendered and hidden with .slds-hide instead of returning null
     */
    renderInactiveTabs: PropTypes.bool,
    /**
     * Renders scoped variant
     */
    scoped: PropTypes.bool,
    /**
     * Renders Card-like variant
     */
    styled: PropTypes.bool,
  };

  static getPanelState(children) {
    const positionReducer = (acc, curr, i) => ({
      ...acc,
      [`${curr.props.id}`]: i,
    });

    const childrenArray = React.Children.toArray(children);

    const nextState = {
      positions: childrenArray.reduce(positionReducer, {}),
      focusedTab: false,
      length: childrenArray.length,
    };

    return nextState;
  }

  constructor(props) {
    super(props);
    this.state = ControlledTabs.getPanelState(props.children);
    /** refs are used to focus links after keyboard navigation occured */
    this.linkNodes = new Map();
  }

  componentWillReceiveProps({ children: nextChildren }) {
    const nextChildrenArr = React.Children.toArray(nextChildren);
    const prevChildrenArr = React.Children.toArray(this.children);

    const isEqualLength = nextChildrenArr.length !== prevChildrenArr.length;
    const nextState = ControlledTabs.getPanelState(nextChildrenArr);

    if (!isEqualLength || !isEqual(nextState.positions, this.state.positions)) {
      this.setState(nextState);
    }
  }

  onChangeTab(nextActiveTab) {
    const { activeTab, onChangeTab } = this.props;
    if (nextActiveTab !== activeTab) {
      this.setState({ focusedTab: nextActiveTab });
      onChangeTab(nextActiveTab);
    }
  }

  onBlur = ({ currentTarget }) => {
    /** Workaround to only fire blur when the whole navigation is unfocused */
    setTimeout(() => {
      if (!currentTarget.contains(document.activeElement)) {
        this.setState({ focusedTab: false });
      }
    }, 0);
  }

  onLinkKeyUp = (evt) => {
    const linkNodes = this.linkNodes;

    const { key } = evt;
    const isLeftKey = key === 'ArrowLeft';
    const isRightKey = key === 'ArrowRight';
    if (!isLeftKey && !isRightKey) return;

    const { length } = this.state;
    const currentPosition = this.getCurrentPosition();

    /* eslint-disable no-nested-ternary */
    const nextIndex = isLeftKey
      ? (currentPosition > 0) ? currentPosition - 1 : Math.max(length - 1, 0)
      : (currentPosition < length - 1) ? currentPosition + 1 : 0;
    /* eslint-enable */

    const nextId = this.getIdForPosition(nextIndex);

    this.onChangeTab(nextId);
    /** Accesses the ref defined in <TabLink /> */
    const linkNode = linkNodes.get(nextId);
    if (linkNode && linkNode.link) { linkNode.link.focus(); }
  }

  getCurrentPosition() {
    const { activeTab } = this.props;
    const { positions } = this.state;
    return positions[activeTab];
  }

  getIdForPosition(position) {
    const { positions } = this.state;
    return Object.keys(positions).find(key => positions[key] === position);
  }

  getLinkRefSetter(id) {
    return (c) => { this.linkNodes.set(id, c); };
  }

  getOnLinkFocus(id) {
    return () => { this.setState({ focusedTab: id }); };
  }

  getOnLinkClick(id) {
    return (evt) => {
      evt.stopPropagation();
      this.onChangeTab(id);
    };
  }

  renderLink = (child) => {
    const { focusedTab } = this.state;
    const { activeTab, scoped } = this.props;
    const { id, renderTitle, title } = child.props;

    const isActive = activeTab === id;
    const isFocused = focusedTab === id;

    return (
      <TabLink
        isActive={isActive}
        isFocused={isFocused}
        id={id}
        onFocus={this.getOnLinkFocus(id)}
        onKeyUp={this.onLinkKeyUp}
        /* Focus is triggered before click but after mouseDown, using mouseDown to prevent a render-cycle */
        onMouseDown={this.getOnLinkClick(id)}
        ref={this.getLinkRefSetter(id)}
        scoped={scoped}
        title={title}
      >
        {renderTitle ? renderTitle({ id, isActive, isFocused, title }) : title}
      </TabLink>
    );
  }

  renderActiveTab = (child) => {
    const { activeTab, renderInactiveTabs, scoped } = this.props;
    const { id } = child.props;
    const isActive = id === activeTab;
    const hideTab = !isActive && !renderInactiveTabs;

    return React.cloneElement(child, {
      'aria-labelledby': getAriaLabel(child.props.id),
      isActive: hideTab ? null : isActive,
      scoped
    });
  }

  render() {
    const { scoped, styled, children } = this.props;

    return (
      <div
        className={cx(
          getTabsClass('', scoped),
          { 'slds-tabs_card': styled }
        )}
      >
        <ul
          className={getTabsClass('__nav', scoped)}
          onBlur={this.onBlur}
          role="tablist"
        >
          {React.Children.map(children, this.renderLink)}
        </ul>
        {React.Children.map(children, this.renderActiveTab)}
      </div>
    );
  }
}

export default ControlledTabs;
<|endoftext|>


<commit_before>/**
 * class SweepPruneBehavior < Behavior
 *
 * `Physics.behavior('sweep-prune')`.
 *
 * Sweep and Prune implementation for broad phase collision detection.
 *
 * This massively improves the speed of collision detection. It's set up to always be used with [[BodyCollisionDetection]], and [[BodyImpulseResponse]].
 *
 * Additional options include:
 * - channel: The channel to publish collision candidates to. (default: `collisions:candidates`)
 **/
Physics.behavior('sweep-prune', function( parent ){

    var uid = 1;

    // Get a unique numeric id for internal use
    var getUniqueId = function getUniqueId(){

        return uid++;
    };

    // add z: 2 to get this to work in 3D
    var dof = { x: 0, y: 1 }; // degrees of freedom
    // change to "3" to get it to work in 3D
    var maxDof = 2;

    var pairHash = Physics.util.pairHash;

    return {

        // extended
        init: function( options ){

            parent.init.call( this );
            this.options.defaults({
                channel: 'collisions:candidates' //default channel
            });
            this.options( options );

            this.encounters = [];
            this.candidates = [];

            this.clear();
        },

        /**
         * SweepPruneBehavior#clear()
         *
         * Refresh tracking data
         **/
        clear: function(){

            this.tracked = [];
            this.pairs = []; // pairs selected as candidate collisions by broad phase
            this.intervalLists = []; // stores lists of aabb projection intervals to be sorted

            // init intervalLists
            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                this.intervalLists[ xyz ] = [];
            }
        },

        // extended
        connect: function( world ){

            world.on( 'add:body', this.trackBody, this );
            world.on( 'remove:body', this.untrackBody, this );
            world.on( 'integrate:velocities', this.sweep, this );

            // add current bodies
            var bodies = world.getBodies();
            for ( var i = 0, l = bodies.length; i < l; ++i ){

                this.trackBody({ body: bodies[ i ] });
            }
        },

        // extended
        disconnect: function( world ){

            world.off( 'add:body', this.trackBody );
            world.off( 'remove:body', this.untrackBody );
            world.off( 'integrate:velocities', this.sweep );
            this.clear();
        },

        /** internal
         * SweepPruneBehavior#broadPhase() -> Array
         * + (Array): The candidate data of overlapping aabbs
         *
         * Execute the broad phase and get candidate collisions
         **/
        broadPhase: function(){

            this.updateIntervals();
            this.sortIntervalLists();
            return this.checkOverlaps();
        },

        /** internal
         * SweepPruneBehavior#sortIntervalLists()
         *
         * Simple insertion sort for each axis
         **/
        sortIntervalLists: function(){

            var list
                ,len
                ,i
                ,hole
                ,bound
                ,boundVal
                ,left
                ,leftVal
                ,axis
                ;

            // for each axis...
            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                // get the intervals for that axis
                list = this.intervalLists[ xyz ];
                i = 0;
                len = list.length;
                axis = xyz;

                // for each interval bound...
                while ( (++i) < len ){

                    // store bound
                    bound = list[ i ];
                    boundVal = bound.val.get( axis );
                    hole = i;

                    left = list[ hole - 1 ];
                    leftVal = left && left.val.get( axis );

                    // while others are greater than bound...
                    while (
                        hole > 0 &&
                        (
                            leftVal > boundVal ||
                            // if it's an equality, only move it over if
                            // the hole was created by a minimum
                            // and the previous is a maximum
                            // so that we detect contacts also
                            leftVal === boundVal &&
                            ( left.type && !bound.type )
                        )
                    ) {

                        // move others greater than bound to the right
                        list[ hole ] = left;
                        hole--;
                        left = list[ hole - 1 ];
                        leftVal = left && left.val.get( axis );
                    }

                    // insert bound in the hole
                    list[ hole ] = bound;
                }
            }
        },

        /** internal
         * SweepPruneBehavior#getPair( tr1, tr2, doCreate ) -> Object
         * - tr1 (Object): First tracker
         * - tr2 (Object): Second tracker
         * - doCreate (Boolean): Create if not found
         * + (Object): Pair object or null if not found
         *
         * Get a pair object for the tracker objects
         **/
        getPair: function(tr1, tr2, doCreate){

            var hash = pairHash( tr1.id, tr2.id );

            if ( hash === false ){
                return null;
            }

            var c = this.pairs[ hash ];

            if ( !c ){

                if ( !doCreate ){
                    return null;
                }

                c = this.pairs[ hash ] = {
                    bodyA: tr1.body,
                    bodyB: tr2.body,
                    flag: 1
                };
            }

            return c;
        },

        // getPair: function(tr1, tr2, doCreate){

        //     var hash = Math.min(tr1.id, tr2.id) // = pairHash( tr1.id, tr2.id )
        //         ,other = Math.max(tr1.id, tr2.id)
        //         ,first
        //         ,c
        //         ;

        //     if ( hash === false ){
        //         return null;
        //     }

        //     first = this.pairs[ hash ];

        //     if ( !first ){
        //         if ( !doCreate ){
        //             return null;
        //         }

        //         first = this.pairs[ hash ] = [];
        //     }

        //     c = first[ other ];

        //     if ( !c ){

        //         if ( !doCreate ){
        //             return null;
        //         }

        //         c = first[ other ] = {
        //             bodyA: tr1.body,
        //             bodyB: tr2.body,
        //             flag: 1
        //         };
        //     }

        //     return c;
        // },

        /** internal
         * SweepPruneBehavior#checkOverlaps() -> Array
         * + (Array): List of candidate collisions
         *
         * Check each axis for overlaps of bodies AABBs
         **/
        checkOverlaps: function(){

            var isX
                ,hash
                ,tr1
                ,tr2
                ,bound
                ,list
                ,len
                ,i
                ,j
                ,c
                // determine which axis is the last we need to check
                ,collisionFlag = 1 << (dof.z + 1) << (dof.y + 1) << (dof.x + 1)
                ,encounters = this.encounters
                ,enclen = 0
                ,candidates = this.candidates
                ;

            Physics.util.clearArray( encounters );
            Physics.util.clearArray( candidates );

            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                // is the x coord
                isX = (xyz === 0);
                // get the interval list for this axis
                list = this.intervalLists[ xyz ];

                // for each interval bound
                for ( i = 0, len = list.length; i < len; i++ ){

                    bound = list[ i ];
                    tr1 = bound.tracker;

                    if ( bound.type ){

                        // is a max

                        j = enclen;

                        for ( j = enclen - 1; j >= 0; j-- ){

                            tr2 = encounters[ j ];

                            // if they are the same tracked interval
                            if ( tr2 === tr1 ){

                                // remove the interval from the encounters list
                                // faster than .splice()
                                if ( j < enclen - 1 ) {

                                    encounters[ j ] = encounters.pop();

                                } else {

                                    // encountered a max right after a min... no overlap
                                    encounters.pop();
                                }

                                enclen--;

                            } else {

                                // check if we have flagged this pair before
                                // if it's the x axis, create a pair
                                c = this.getPair( tr1, tr2, isX );

                                if ( c ){

                                    if ( c.flag > collisionFlag ){
                                        c.flag = 1;
                                    }

                                    // if it's greater than the axis index, set the flag
                                    // to = 0.
                                    // if not, increment the flag by one.
                                    c.flag = c.flag << (xyz + 1);

                                    // c.flag will equal collisionFlag
                                    // if we've incremented the flag
                                    // enough that all axes are overlapping
                                    if ( c.flag === collisionFlag ){

                                        // overlaps on all axes.
                                        // add it to possible collision
                                        // candidates list for narrow phase

                                        candidates.push( c );
                                    }
                                }
                            }
                        }

                    } else {

                        // is a min
                        // just add this minimum to the encounters list
                        enclen = encounters.push( tr1 );
                    }
                }
            }

            return candidates;
        },

        /** internal
         * SweepPruneBehavior#updateIntervals()
         *
         * Update position intervals on each axis
         **/
        updateIntervals: function(){

            var tr
                ,intr
                ,scratch = Physics.scratchpad()
                ,pos = scratch.vector()
                ,aabb
                ,span = scratch.vector()
                ,list = this.tracked
                ,i = list.length
                ;

            // for all tracked bodies
            while ( (--i) >= 0 ){

                tr = list[ i ];
                intr = tr.interval;
                pos.clone( tr.body.state.pos );
                aabb = tr.body.aabb();
                span.set( aabb.hw, aabb.hh );

                // copy the position (plus or minus) the aabb half-dimensions
                // into the min/max intervals
                intr.min.val.clone( pos ).vsub( span );
                intr.max.val.clone( pos ).vadd( span );
            }

            scratch.done();
        },

        /** internal
         * SweepPruneBehavior#trackBody( data )
         * - data (Object): Event data
         *
         * Event callback to add body to list of those tracked by sweep and prune
         **/
        trackBody: function( data ){

            var body = data.body
                ,tracker = {

                    id: getUniqueId(),
                    body: body
                }
                ,intr = {

                    min: {
                        type: false, //min
                        val: Physics.vector(),
                        tracker: tracker
                    },

                    max: {
                        type: true, //max
                        val: Physics.vector(),
                        tracker: tracker
                    }
                }
                ;

            tracker.interval = intr;
            this.tracked.push( tracker );

            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                this.intervalLists[ xyz ].push( intr.min, intr.max );
            }
        },

        /** internal
         * SweepPruneBehavior#untrackBody( data )
         * - data (Object): Event data
         *
         * Event callback to remove body from list of those tracked
         **/
        untrackBody: function( data ){

            var body = data.body
                ,list
                ,minmax
                ,trackedList = this.tracked
                ,tracker
                ,count
                ;

            for ( var i = 0, l = trackedList.length; i < l; ++i ){

                tracker = trackedList[ i ];

                if ( tracker.body === body ){

                    // remove the tracker at this index
                    trackedList.splice(i, 1);

                    for ( var xyz = 0; xyz < maxDof; ++xyz ){

                        count = 0;
                        list = this.intervalLists[ xyz ];

                        for ( var j = 0, m = list.length; j < m; ++j ){

                            minmax = list[ j ];

                            if ( minmax === tracker.interval.min || minmax === tracker.interval.max ){

                                // remove interval from list
                                list.splice(j, 1);
                                j--;
                                l--;

                                if (count > 0){
                                    break;
                                }

                                count++;
                            }
                        }
                    }

                    break;
                }
            }
        },

        /** internal
         * SweepPruneBehavior#sweep( data )
         * - data (Object): Event data
         *
         * Event callback to sweep and publish event if any candidate collisions are found
         **/
        sweep: function( data ){

            var self = this
                ,candidates
                ;

            candidates = self.broadPhase();

            if ( candidates.length ){

                this._world.emit( this.options.channel, {
                    candidates: candidates
                });
            }
        }
    };
});
<commit_msg>fix sweep-prune not resetting between steps<commit_after>/**
 * class SweepPruneBehavior < Behavior
 *
 * `Physics.behavior('sweep-prune')`.
 *
 * Sweep and Prune implementation for broad phase collision detection.
 *
 * This massively improves the speed of collision detection. It's set up to always be used with [[BodyCollisionDetection]], and [[BodyImpulseResponse]].
 *
 * Additional options include:
 * - channel: The channel to publish collision candidates to. (default: `collisions:candidates`)
 **/
Physics.behavior('sweep-prune', function( parent ){

    var uid = 1;

    // Get a unique numeric id for internal use
    var getUniqueId = function getUniqueId(){

        return uid++;
    };

    // add z: 2 to get this to work in 3D
    var dof = { x: 0, y: 1 }; // degrees of freedom
    // change to "3" to get it to work in 3D
    var maxDof = 2;

    var pairHash = Physics.util.pairHash;

    return {

        // extended
        init: function( options ){

            parent.init.call( this );
            this.options.defaults({
                channel: 'collisions:candidates' //default channel
            });
            this.options( options );

            this.encounters = [];
            this.candidates = [];

            this.clear();
        },

        /**
         * SweepPruneBehavior#clear()
         *
         * Refresh tracking data
         **/
        clear: function(){

            this.tracked = [];
            this.pairs = []; // pairs selected as candidate collisions by broad phase
            this.intervalLists = []; // stores lists of aabb projection intervals to be sorted

            // init intervalLists
            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                this.intervalLists[ xyz ] = [];
            }
        },

        // extended
        connect: function( world ){

            world.on( 'add:body', this.trackBody, this );
            world.on( 'remove:body', this.untrackBody, this );
            world.on( 'integrate:velocities', this.sweep, this );

            // add current bodies
            var bodies = world.getBodies();
            for ( var i = 0, l = bodies.length; i < l; ++i ){

                this.trackBody({ body: bodies[ i ] });
            }
        },

        // extended
        disconnect: function( world ){

            world.off( 'add:body', this.trackBody );
            world.off( 'remove:body', this.untrackBody );
            world.off( 'integrate:velocities', this.sweep );
            this.clear();
        },

        /** internal
         * SweepPruneBehavior#broadPhase() -> Array
         * + (Array): The candidate data of overlapping aabbs
         *
         * Execute the broad phase and get candidate collisions
         **/
        broadPhase: function(){

            this.updateIntervals();
            this.sortIntervalLists();
            return this.checkOverlaps();
        },

        /** internal
         * SweepPruneBehavior#sortIntervalLists()
         *
         * Simple insertion sort for each axis
         **/
        sortIntervalLists: function(){

            var list
                ,len
                ,i
                ,hole
                ,bound
                ,boundVal
                ,left
                ,leftVal
                ,axis
                ;

            // for each axis...
            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                // get the intervals for that axis
                list = this.intervalLists[ xyz ];
                i = 0;
                len = list.length;
                axis = xyz;

                // for each interval bound...
                while ( (++i) < len ){

                    // store bound
                    bound = list[ i ];
                    boundVal = bound.val.get( axis );
                    hole = i;

                    left = list[ hole - 1 ];
                    leftVal = left && left.val.get( axis );

                    // while others are greater than bound...
                    while (
                        hole > 0 &&
                        (
                            leftVal > boundVal ||
                            // if it's an equality, only move it over if
                            // the hole was created by a minimum
                            // and the previous is a maximum
                            // so that we detect contacts also
                            leftVal === boundVal &&
                            ( left.type && !bound.type )
                        )
                    ) {

                        // move others greater than bound to the right
                        list[ hole ] = left;
                        hole--;
                        left = list[ hole - 1 ];
                        leftVal = left && left.val.get( axis );
                    }

                    // insert bound in the hole
                    list[ hole ] = bound;
                }
            }
        },

        /** internal
         * SweepPruneBehavior#getPair( tr1, tr2, doCreate ) -> Object
         * - tr1 (Object): First tracker
         * - tr2 (Object): Second tracker
         * - doCreate (Boolean): Create if not found
         * + (Object): Pair object or null if not found
         *
         * Get a pair object for the tracker objects
         **/
        getPair: function(tr1, tr2, doCreate){

            var hash = pairHash( tr1.id, tr2.id );

            if ( hash === false ){
                return null;
            }

            var c = this.pairs[ hash ];

            if ( !c ){

                if ( !doCreate ){
                    return null;
                }

                c = this.pairs[ hash ] = {
                    bodyA: tr1.body,
                    bodyB: tr2.body,
                    flag: 1
                };
            }

            if ( doCreate){
                c.flag = 1;
            }

            return c;
        },

        // getPair: function(tr1, tr2, doCreate){

        //     var hash = Math.min(tr1.id, tr2.id) // = pairHash( tr1.id, tr2.id )
        //         ,other = Math.max(tr1.id, tr2.id)
        //         ,first
        //         ,c
        //         ;

        //     if ( hash === false ){
        //         return null;
        //     }

        //     first = this.pairs[ hash ];

        //     if ( !first ){
        //         if ( !doCreate ){
        //             return null;
        //         }

        //         first = this.pairs[ hash ] = [];
        //     }

        //     c = first[ other ];

        //     if ( !c ){

        //         if ( !doCreate ){
        //             return null;
        //         }

        //         c = first[ other ] = {
        //             bodyA: tr1.body,
        //             bodyB: tr2.body,
        //             flag: 1
        //         };
        //     }

        //     return c;
        // },

        /** internal
         * SweepPruneBehavior#checkOverlaps() -> Array
         * + (Array): List of candidate collisions
         *
         * Check each axis for overlaps of bodies AABBs
         **/
        checkOverlaps: function(){

            var isX
                ,hash
                ,tr1
                ,tr2
                ,bound
                ,list
                ,len
                ,i
                ,j
                ,c
                // determine which axis is the last we need to check
                ,collisionFlag = 1 << (dof.z + 1) << (dof.y + 1) << (dof.x + 1)
                ,encounters = this.encounters
                ,enclen = 0
                ,candidates = this.candidates
                ;

            Physics.util.clearArray( encounters );
            Physics.util.clearArray( candidates );

            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                // is the x coord
                isX = (xyz === 0);
                // get the interval list for this axis
                list = this.intervalLists[ xyz ];

                // for each interval bound
                for ( i = 0, len = list.length; i < len; i++ ){

                    bound = list[ i ];
                    tr1 = bound.tracker;

                    if ( bound.type ){

                        // is a max

                        j = enclen;

                        for ( j = enclen - 1; j >= 0; j-- ){

                            tr2 = encounters[ j ];

                            // if they are the same tracked interval
                            if ( tr2 === tr1 ){

                                // remove the interval from the encounters list
                                // faster than .splice()
                                if ( j < enclen - 1 ) {

                                    encounters[ j ] = encounters.pop();

                                } else {

                                    // encountered a max right after a min... no overlap
                                    encounters.pop();
                                }

                                enclen--;

                            } else {

                                // check if we have flagged this pair before
                                // if it's the x axis, create a pair
                                c = this.getPair( tr1, tr2, isX );

                                if ( c ){

                                    // if it's greater than the axis index, set the flag
                                    // to = 0.
                                    // if not, increment the flag by one.
                                    c.flag = c.flag << (xyz + 1);

                                    // c.flag will equal collisionFlag
                                    // if we've incremented the flag
                                    // enough that all axes are overlapping
                                    if ( c.flag === collisionFlag ){

                                        // overlaps on all axes.
                                        // add it to possible collision
                                        // candidates list for narrow phase

                                        candidates.push( c );
                                    }
                                }
                            }
                        }

                    } else {

                        // is a min
                        // just add this minimum to the encounters list
                        enclen = encounters.push( tr1 );
                    }
                }
            }

            return candidates;
        },

        /** internal
         * SweepPruneBehavior#updateIntervals()
         *
         * Update position intervals on each axis
         **/
        updateIntervals: function(){

            var tr
                ,intr
                ,scratch = Physics.scratchpad()
                ,pos = scratch.vector()
                ,aabb
                ,span = scratch.vector()
                ,list = this.tracked
                ,i = list.length
                ;

            // for all tracked bodies
            while ( (--i) >= 0 ){

                tr = list[ i ];
                intr = tr.interval;
                pos.clone( tr.body.state.pos );
                aabb = tr.body.aabb();
                span.set( aabb.hw, aabb.hh );

                // copy the position (plus or minus) the aabb half-dimensions
                // into the min/max intervals
                intr.min.val.clone( pos ).vsub( span );
                intr.max.val.clone( pos ).vadd( span );
            }

            scratch.done();
        },

        /** internal
         * SweepPruneBehavior#trackBody( data )
         * - data (Object): Event data
         *
         * Event callback to add body to list of those tracked by sweep and prune
         **/
        trackBody: function( data ){

            var body = data.body
                ,tracker = {

                    id: getUniqueId(),
                    body: body
                }
                ,intr = {

                    min: {
                        type: false, //min
                        val: Physics.vector(),
                        tracker: tracker
                    },

                    max: {
                        type: true, //max
                        val: Physics.vector(),
                        tracker: tracker
                    }
                }
                ;

            tracker.interval = intr;
            this.tracked.push( tracker );

            for ( var xyz = 0; xyz < maxDof; ++xyz ){

                this.intervalLists[ xyz ].push( intr.min, intr.max );
            }
        },

        /** internal
         * SweepPruneBehavior#untrackBody( data )
         * - data (Object): Event data
         *
         * Event callback to remove body from list of those tracked
         **/
        untrackBody: function( data ){

            var body = data.body
                ,list
                ,minmax
                ,trackedList = this.tracked
                ,tracker
                ,count
                ;

            for ( var i = 0, l = trackedList.length; i < l; ++i ){

                tracker = trackedList[ i ];

                if ( tracker.body === body ){

                    // remove the tracker at this index
                    trackedList.splice(i, 1);

                    for ( var xyz = 0; xyz < maxDof; ++xyz ){

                        count = 0;
                        list = this.intervalLists[ xyz ];

                        for ( var j = 0, m = list.length; j < m; ++j ){

                            minmax = list[ j ];

                            if ( minmax === tracker.interval.min || minmax === tracker.interval.max ){

                                // remove interval from list
                                list.splice(j, 1);
                                j--;
                                l--;

                                if (count > 0){
                                    break;
                                }

                                count++;
                            }
                        }
                    }

                    break;
                }
            }
        },

        /** internal
         * SweepPruneBehavior#sweep( data )
         * - data (Object): Event data
         *
         * Event callback to sweep and publish event if any candidate collisions are found
         **/
        sweep: function( data ){

            var self = this
                ,candidates
                ;

            candidates = self.broadPhase();

            if ( candidates.length ){

                this._world.emit( this.options.channel, {
                    candidates: candidates
                });
            }
        }
    };
});
<|endoftext|>


<commit_before>/*globals grecaptcha*/
import Component from '../_classes/component/Component';
import Formio from '../../Formio';
import _get from 'lodash/get';
import NativePromise from 'native-promise-only';

export default class ReCaptchaComponent extends Component {
  static schema(...extend) {
    return Component.schema({
      type: 'recaptcha',
      key: 'recaptcha',
      label: 'reCAPTCHA'
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'reCAPTCHA',
      group: 'premium',
      icon: 'refresh',
      documentation: '/userguide/#recaptcha',
      weight: 40,
      schema: ReCaptchaComponent.schema()
    };
  }

  render() {
    if (this.builderMode) {
      return super.render('reCAPTCHA');
    }
    else {
      return super.render('', true);
    }
  }

  createInput() {
    if (this.builderMode) {
      // We need to see it in builder mode.
      this.append(this.text(this.name));
    }
    else {
      const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
      if (siteKey) {
        const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
        this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
      }
      else {
        console.warn('There is no Site Key specified in settings in form JSON');
      }
    }
  }

  createLabel() {
    return;
  }

  verify(actionName) {
    const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
    if (!siteKey) {
      console.warn('There is no Site Key specified in settings in form JSON');
      return;
    }
    if (!this.recaptchaApiReady) {
      const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${_get(this.root.form, 'settings.recaptcha.siteKey')}`;
      this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
    }
    if (this.recaptchaApiReady) {
      this.recaptchaVerifiedPromise = new NativePromise((resolve, reject) => {
        this.recaptchaApiReady
          .then(() => {
            grecaptcha.ready(() => {
              grecaptcha
                .execute(siteKey, {
                  action: actionName
                })
                .then((token) => {
                  return this.sendVerificationRequest(token);
                })
                .then(({ verificationResult, token }) => {
                  this.setValue({
                    ...verificationResult,
                    token,
                  });

                  return resolve(verificationResult);
                });
            });
          })
          .catch(() => {
            return reject();
          });
      });
    }
  }

  beforeSubmit() {
    if (this.recaptchaVerifiedPromise) {
      return this.recaptchaVerifiedPromise
        .then(() => super.beforeSubmit());
    }
    return super.beforeSubmit();
  }

  sendVerificationRequest(token) {
    return Formio.makeStaticRequest(`${Formio.projectUrl}/recaptcha?recaptchaToken=${token}`)
      .then((verificationResult) => ({ verificationResult, token }));
  }

  checkComponentValidity(data, dirty, row, options = {}) {
    data = data || this.rootValue;
    row = row || this.data;
    const { async = false } = options;

    // Verification could be async only
    if (!async) {
      return super.checkComponentValidity(data, dirty, row, options);
    }

    const componentData = row[this.component.key];
    if (!componentData || !componentData.token) {
      this.setCustomValidity('ReCaptcha: Token is not specified in submission');
      return NativePromise.resolve(false);
    }

    return this.hook('validateReCaptcha', componentData.token, () => NativePromise.resolve(true))
      .then((success) => success)
      .catch((err) => {
        this.setCustomValidity(err.message || err);
        return false;
      });
  }

  setValue(value) {
    const changed = this.hasChanged(value, this.dataValue);
    this.dataValue = value;
    return changed;
  }

  getValue() {
    return this.dataValue;
  }
}
<commit_msg>FIO-2888: Fixing the recaptcha so that it will not be reset if it is set.<commit_after>/*globals grecaptcha*/
import Component from '../_classes/component/Component';
import Formio from '../../Formio';
import _get from 'lodash/get';
import NativePromise from 'native-promise-only';

export default class ReCaptchaComponent extends Component {
  static schema(...extend) {
    return Component.schema({
      type: 'recaptcha',
      key: 'recaptcha',
      label: 'reCAPTCHA'
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'reCAPTCHA',
      group: 'premium',
      icon: 'refresh',
      documentation: '/userguide/#recaptcha',
      weight: 40,
      schema: ReCaptchaComponent.schema()
    };
  }

  render() {
    this.recaptchaResult = null;
    if (this.builderMode) {
      return super.render('reCAPTCHA');
    }
    else {
      return super.render('', true);
    }
  }

  createInput() {
    if (this.builderMode) {
      // We need to see it in builder mode.
      this.append(this.text(this.name));
    }
    else {
      const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
      if (siteKey) {
        const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
        this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
      }
      else {
        console.warn('There is no Site Key specified in settings in form JSON');
      }
    }
  }

  createLabel() {
    return;
  }

  verify(actionName) {
    const siteKey = _get(this.root.form, 'settings.recaptcha.siteKey');
    if (!siteKey) {
      console.warn('There is no Site Key specified in settings in form JSON');
      return;
    }
    if (!this.recaptchaApiReady) {
      const recaptchaApiScriptUrl = `https://www.google.com/recaptcha/api.js?render=${_get(this.root.form, 'settings.recaptcha.siteKey')}`;
      this.recaptchaApiReady = Formio.requireLibrary('googleRecaptcha', 'grecaptcha', recaptchaApiScriptUrl, true);
    }
    if (this.recaptchaApiReady) {
      this.recaptchaVerifiedPromise = new NativePromise((resolve, reject) => {
        this.recaptchaApiReady
          .then(() => {
            grecaptcha.ready(() => {
              grecaptcha
                .execute(siteKey, {
                  action: actionName
                })
                .then((token) => {
                  return this.sendVerificationRequest(token).then(({ verificationResult, token }) => {
                    this.recaptchaResult = {
                      ...verificationResult,
                      token,
                    };
                    this.updateValue(this.recaptchaResult);
                    return resolve(verificationResult);
                  });
                });
            });
          })
          .catch(() => {
            return reject();
          });
      });
    }
  }

  beforeSubmit() {
    if (this.recaptchaVerifiedPromise) {
      return this.recaptchaVerifiedPromise
        .then(() => super.beforeSubmit());
    }
    return super.beforeSubmit();
  }

  sendVerificationRequest(token) {
    return Formio.makeStaticRequest(`${Formio.projectUrl}/recaptcha?recaptchaToken=${token}`)
      .then((verificationResult) => ({ verificationResult, token }));
  }

  checkComponentValidity(data, dirty, row, options = {}) {
    data = data || this.rootValue;
    row = row || this.data;
    const { async = false } = options;

    // Verification could be async only
    if (!async) {
      return super.checkComponentValidity(data, dirty, row, options);
    }

    const componentData = row[this.component.key];
    if (!componentData || !componentData.token) {
      this.setCustomValidity('ReCaptcha: Token is not specified in submission');
      return NativePromise.resolve(false);
    }

    return this.hook('validateReCaptcha', componentData.token, () => NativePromise.resolve(true))
      .then((success) => success)
      .catch((err) => {
        this.setCustomValidity(err.message || err);
        return false;
      });
  }

  normalizeValue(newValue) {
    // If a recaptcha result has already been established, then do not allow it to be reset.
    return this.recaptchaResult ? this.recaptchaResult : newValue;
  }
}
<|endoftext|>