<commit_before>import rbush from 'rbush';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';

import { geoExtent, geoVecAdd, geoVecScale } from '../geo';
import { qaError } from '../osm';
import { services } from './index';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';

var tiler = utilTiler();
var dispatch = d3_dispatch('loaded');

var _erCache;
var _erZoom = 14;

var _impOsmUrls = {
    ow: 'https://directionofflow.skobbler.net/directionOfFlowService',
    mr: 'https://missingroads.skobbler.net/missingGeoService',
    tr: 'https://turnrestrictionservice.skobbler.net/turnRestrictionService'
};

function abortRequest(i) {
    Object.values(i).forEach(function(v) {
        if (v) {
            v.abort();
        }
    });
}

function abortUnwantedRequests(cache, tiles) {
    Object.keys(cache.inflightTile).forEach(function(k) {
        var wanted = tiles.find(function(tile) { return k === tile.id; });
        if (!wanted) {
            abortRequest(cache.inflightTile[k]);
            delete cache.inflightTile[k];
        }
    });
}

function encodeErrorRtree(d) {
    return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
}

// replace or remove error from rtree
function updateRtree(item, replace) {
    _erCache.rtree.remove(item, function isEql(a, b) {
        return a.data.id === b.data.id;
    });

    if (replace) {
        _erCache.rtree.insert(item);
    }
}

function linkErrorObject(d) {
    return '' + d + '';
}

function linkEntity(d) {
    return '' + d + '';
}

function pointAverage(points) {
    if (points.length) {
        return geoVecScale(points.reduce(geoVecAdd, [0,0]), 1 / points.length);
    } else {
        return [0,0];
    }
}

function relativeBearing(p1, p2) {
    var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
    if (angle < 0) {
        angle += 2 * Math.PI;
    }

    // Return degrees
    return angle * 180 / Math.PI;
}

// Assuming range [0,360)
function cardinalDirection(bearing) {
    var dir = 45 * Math.round(bearing / 45);
    var compass = {
        0: 'north',
        45: 'northeast',
        90: 'east',
        135: 'southeast',
        180: 'south',
        225: 'southwest',
        270: 'west',
        315: 'northwest',
        360: 'north'
    };

    return t('QA.improveOSM.directions.' + compass[dir]);
}

// Errors shouldn't obscure eachother
function preventCoincident(loc, bumpUp) {
    var coincident = false;
    do {
        // first time, move marker up. after that, move marker right.
        var delta = coincident ? [0.00001, 0] : (bumpUp ? [0, 0.00001] : [0, 0]);
        loc = geoVecAdd(loc, delta);
        var bbox = geoExtent(loc).bbox();
        coincident = _erCache.rtree.search(bbox).length;
    } while (coincident);

    return loc;
}

export default {
    init: function() {
        if (!_erCache) {
            this.reset();
        }

        this.event = utilRebind(this, dispatch, 'on');
    },

    reset: function() {
        if (_erCache) {
            Object.values(_erCache.inflightTile).forEach(abortRequest);
        }
        _erCache = {
            data: {},
            loadedTile: {},
            inflightTile: {},
            inflightPost: {},
            closed: {},
            rtree: rbush()
        };
    },

    loadErrors: function(projection) {
        var options = {
            client: 'iD',
            status: 'OPEN',
            zoom: '19' // Use a high zoom so that clusters aren't returned
        };

        // determine the needed tiles to cover the view
        var tiles = tiler
            .zoomExtent([_erZoom, _erZoom])
            .getTiles(projection);

        // abort inflight requests that are no longer needed
        abortUnwantedRequests(_erCache, tiles);

        // issue new requests..
        tiles.forEach(function(tile) {
            if (_erCache.loadedTile[tile.id] || _erCache.inflightTile[tile.id]) return;

            var rect = tile.extent.rectangle();
            var params = Object.assign({}, options, { east: rect[0], south: rect[3], west: rect[2], north: rect[1] });

            // 3 separate requests to store for each tile
            var requests = {};

            Object.keys(_impOsmUrls).forEach(function(k) {
                var v = _impOsmUrls[k];
                // We exclude WATER from missing geometry as it doesn't seem useful
                // We use most confident one-way and turn restrictions only, still have false positives
                var kParams = Object.assign({},
                    params,
                    (k === 'mr') ? { type: 'PARKING,ROAD,BOTH,PATH' } : { confidenceLevel: 'C1' }
                );
                var url = v + '/search?' + utilQsString(kParams);

                requests[k] = d3_json(url,
                    function(err, data) {
                        delete _erCache.inflightTile[tile.id];

                        if (err) return;
                        _erCache.loadedTile[tile.id] = true;

                        // Road segments at high zoom == oneways
                        if (data.roadSegments) {
                            data.roadSegments.forEach(function(feature) {
                                // Position error at the approximate middle of the segment
                                var points = feature.points;
                                var mid = points.length / 2;
                                var loc;

                                // Even number of points, find midpoint of the middle two
                                // Odd number of points, use position of very middle point
                                if (mid % 1 === 0) {
                                    loc = pointAverage([points[mid - 1], points[mid]]);
                                } else {
                                    mid = points[Math.floor(mid)];
                                    loc = [mid.lon, mid.lat];
                                }

                                // One-ways can land on same segment in opposite direction
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { // this is used to post changes to the error
                                        wayId: feature.wayId,
                                        fromNodeId: feature.fromNodeId,
                                        toNodeId: feature.toNodeId
                                    },
                                    object_id: feature.wayId,
                                    object_type: 'way',
                                    status: feature.status
                                });

                                // Variables used in the description
                                d.replacements = {
                                    percentage: feature.percentOfTrips,
                                    num_trips: feature.numberOfTrips,
                                    highway: linkErrorObject(t('QA.keepRight.error_parts.highway')),
                                    from_node: linkEntity('n' + feature.fromNodeId),
                                    to_node: linkEntity('n' + feature.toNodeId)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Tiles at high zoom == missing roads
                        if (data.tiles) {
                            data.tiles.forEach(function(feature) {
                                var geoType = feature.type.toLowerCase();

                                // Average of recorded points should land on the missing geometry
                                // Missing geometry could happen to land on another error
                                var loc = pointAverage(feature.points);
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k + '-' + geoType,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { x: feature.x, y: feature.y },
                                    status: feature.status
                                });

                                d.replacements = {
                                    num_trips: feature.numberOfTrips,
                                    geometry_type: t('QA.improveOSM.geometry_types.' + geoType)
                                };

                                // -1 trips indicates data came from a 3rd party
                                if (feature.numberOfTrips === -1) {
                                    d.desc = t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
                                }

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Entities at high zoom == turn restrictions
                        if (data.entities) {
                            data.entities.forEach(function(feature) {
                                // Turn restrictions could be missing at same junction
                                // We also want to bump the error up so node is accessible
                                var loc = feature.point;
                                loc = preventCoincident([loc.lon, loc.lat], true);

                                // Elements are presented in a strange way
                                var ids = feature.id.split(',');
                                var from_way = ids[0];
                                var via_node = ids[3];
                                var to_way = ids[2].split(':')[1];

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: feature.id,
                                    object_id: via_node,
                                    object_type: 'node',
                                    status: feature.status
                                });

                                // Travel direction along from_way clarifies the turn restriction
                                var p1 = feature.segments[0].points[0];
                                var p2 = feature.segments[0].points[1];

                                var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));

                                // Variables used in the description
                                d.replacements = {
                                    num_passed: feature.numberOfPasses,
                                    num_trips: feature.segments[0].numberOfTrips,
                                    turn_restriction: feature.turnType.toLowerCase(),
                                    from_way: linkEntity('w' + from_way),
                                    to_way: linkEntity('w' + to_way),
                                    travel_direction: dir_of_travel,
                                    junction: linkErrorObject(t('QA.keepRight.error_parts.this_node'))
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }
                    }
                );
            });

            _erCache.inflightTile[tile.id] = requests;
            dispatch.call('loaded');
        });
    },

    getComments: function(d, callback) {
        // If comments already retrieved no need to do so again
        if (d.comments !== undefined) { return callback({}, d); }

        var key = d.error_key;
        var qParams = {};

        if (key === 'ow') {
            qParams = d.identifier;
        } else if (key === 'mr') {
            qParams.tileX = d.identifier.x;
            qParams.tileY = d.identifier.y;
        } else if (key === 'tr') {
            qParams.targetId = d.identifier;
        }

        var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams);

        var that = this;
        d3_json(url, function(err, data) {
            // comments are served newest to oldest
            var comments = data.comments ? data.comments.reverse() : [];

            that.replaceError(d.update({
                comments: comments
            }));
            return callback(err, d);
        });
    },

    postUpdate: function(d, callback) {
        if (!services.osm.authenticated()) { // Username required in payload
            return callback({ message: 'Not Authenticated', status: -3}, d);
        }
        if (_erCache.inflightPost[d.id]) {
            return callback({ message: 'Error update already inflight', status: -2 }, d);
        }

        var that = this;

        // Payload can only be sent once username is established
        services.osm.userDetails(sendPayload);

        function sendPayload(err, user) {
            if (err) { return callback(err, d); }

            var key = d.error_key;
            var url = _impOsmUrls[key] + '/comment';
            var payload = {
                username: user.display_name
            };

            // Each error type has different data for identification
            if (key === 'ow') {
                payload.roadSegments = [ d.identifier ];
            } else if (key === 'mr') {
                payload.tiles = [ d.identifier ];
            } else if (key === 'tr') {
                payload.targetIds = [ d.identifier ];
            }

            if (d.newStatus !== undefined) {
                payload.status = d.newStatus;
                payload.text = 'status changed';
            }

            // Comment take place of default text
            if (d.newComment !== undefined) {
                payload.text = d.newComment;
            }

            _erCache.inflightPost[d.id] = d3_request(url)
                .header('Content-Type', 'application/json')
                .post(JSON.stringify(payload), function(err) {
                    delete _erCache.inflightPost[d.id];

                    // Unsuccessful response status, keep issue open
                    if (err.status !== 200) { return callback(err, d); }

                    // Just a comment, update error in cache
                    if (d.newStatus === undefined) {
                        var now = new Date();
                        var comments = d.comments ? d.comments : [];

                        comments.push({
                            username: payload.username,
                            text: payload.text,
                            timestamp: now.getTime() / 1000
                        });

                        that.replaceError(d.update({
                            comments: comments,
                            newComment: undefined
                        }));
                    } else {
                        that.removeError(d);

                        if (d.newStatus === 'SOLVED') {
                            // No pretty identifier, so we just use coordinates
                            var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
                            _erCache.closed[key + ':' + closedID] = true;
                        }
                    }

                    return callback(err, d);
                });
        }
    },

    // get all cached errors covering the viewport
    getErrors: function(projection) {
        var viewport = projection.clipExtent();
        var min = [viewport[0][0], viewport[1][1]];
        var max = [viewport[1][0], viewport[0][1]];
        var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();

        return _erCache.rtree.search(bbox).map(function(d) {
            return d.data;
        });
    },

    // get a single error from the cache
    getError: function(id) {
        return _erCache.data[id];
    },

    // replace a single error in the cache
    replaceError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        _erCache.data[error.id] = error;
        updateRtree(encodeErrorRtree(error), true); // true = replace
        return error;
    },

    // remove a single error from the cache
    removeError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        delete _erCache.data[error.id];
        updateRtree(encodeErrorRtree(error), false); // false = remove
    },

    // Used to populate `closed:improveosm` changeset tag
    getClosedIDs: function() {
        return Object.keys(_erCache.closed).sort();
    }
};
<commit_msg>Fix point averaging code in improveOSM service re: https://github.com/openstreetmap/iD/commit/4a8c20c56e4549da94555958acd34f649ca18fe9#r32961438<commit_after>import rbush from 'rbush';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';

import { geoExtent, geoVecAdd, geoVecScale } from '../geo';
import { qaError } from '../osm';
import { services } from './index';
import { t } from '../util/locale';
import { utilRebind, utilTiler, utilQsString } from '../util';

var tiler = utilTiler();
var dispatch = d3_dispatch('loaded');

var _erCache;
var _erZoom = 14;

var _impOsmUrls = {
    ow: 'https://directionofflow.skobbler.net/directionOfFlowService',
    mr: 'https://missingroads.skobbler.net/missingGeoService',
    tr: 'https://turnrestrictionservice.skobbler.net/turnRestrictionService'
};

function abortRequest(i) {
    Object.values(i).forEach(function(v) {
        if (v) {
            v.abort();
        }
    });
}

function abortUnwantedRequests(cache, tiles) {
    Object.keys(cache.inflightTile).forEach(function(k) {
        var wanted = tiles.find(function(tile) { return k === tile.id; });
        if (!wanted) {
            abortRequest(cache.inflightTile[k]);
            delete cache.inflightTile[k];
        }
    });
}

function encodeErrorRtree(d) {
    return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
}

// replace or remove error from rtree
function updateRtree(item, replace) {
    _erCache.rtree.remove(item, function isEql(a, b) {
        return a.data.id === b.data.id;
    });

    if (replace) {
        _erCache.rtree.insert(item);
    }
}

function linkErrorObject(d) {
    return '' + d + '';
}

function linkEntity(d) {
    return '' + d + '';
}

function pointAverage(points) {
    if (points.length) {
        var sum = points.reduce(function(acc, point) {
            return geoVecAdd(acc, [point.lon, point.lat]);
        }, [0,0]);
        return geoVecScale(sum, 1 / points.length);
    } else {
        return [0,0];
    }
}

function relativeBearing(p1, p2) {
    var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
    if (angle < 0) {
        angle += 2 * Math.PI;
    }

    // Return degrees
    return angle * 180 / Math.PI;
}

// Assuming range [0,360)
function cardinalDirection(bearing) {
    var dir = 45 * Math.round(bearing / 45);
    var compass = {
        0: 'north',
        45: 'northeast',
        90: 'east',
        135: 'southeast',
        180: 'south',
        225: 'southwest',
        270: 'west',
        315: 'northwest',
        360: 'north'
    };

    return t('QA.improveOSM.directions.' + compass[dir]);
}

// Errors shouldn't obscure eachother
function preventCoincident(loc, bumpUp) {
    var coincident = false;
    do {
        // first time, move marker up. after that, move marker right.
        var delta = coincident ? [0.00001, 0] : (bumpUp ? [0, 0.00001] : [0, 0]);
        loc = geoVecAdd(loc, delta);
        var bbox = geoExtent(loc).bbox();
        coincident = _erCache.rtree.search(bbox).length;
    } while (coincident);

    return loc;
}

export default {
    init: function() {
        if (!_erCache) {
            this.reset();
        }

        this.event = utilRebind(this, dispatch, 'on');
    },

    reset: function() {
        if (_erCache) {
            Object.values(_erCache.inflightTile).forEach(abortRequest);
        }
        _erCache = {
            data: {},
            loadedTile: {},
            inflightTile: {},
            inflightPost: {},
            closed: {},
            rtree: rbush()
        };
    },

    loadErrors: function(projection) {
        var options = {
            client: 'iD',
            status: 'OPEN',
            zoom: '19' // Use a high zoom so that clusters aren't returned
        };

        // determine the needed tiles to cover the view
        var tiles = tiler
            .zoomExtent([_erZoom, _erZoom])
            .getTiles(projection);

        // abort inflight requests that are no longer needed
        abortUnwantedRequests(_erCache, tiles);

        // issue new requests..
        tiles.forEach(function(tile) {
            if (_erCache.loadedTile[tile.id] || _erCache.inflightTile[tile.id]) return;

            var rect = tile.extent.rectangle();
            var params = Object.assign({}, options, { east: rect[0], south: rect[3], west: rect[2], north: rect[1] });

            // 3 separate requests to store for each tile
            var requests = {};

            Object.keys(_impOsmUrls).forEach(function(k) {
                var v = _impOsmUrls[k];
                // We exclude WATER from missing geometry as it doesn't seem useful
                // We use most confident one-way and turn restrictions only, still have false positives
                var kParams = Object.assign({},
                    params,
                    (k === 'mr') ? { type: 'PARKING,ROAD,BOTH,PATH' } : { confidenceLevel: 'C1' }
                );
                var url = v + '/search?' + utilQsString(kParams);

                requests[k] = d3_json(url,
                    function(err, data) {
                        delete _erCache.inflightTile[tile.id];

                        if (err) return;
                        _erCache.loadedTile[tile.id] = true;

                        // Road segments at high zoom == oneways
                        if (data.roadSegments) {
                            data.roadSegments.forEach(function(feature) {
                                // Position error at the approximate middle of the segment
                                var points = feature.points;
                                var mid = points.length / 2;
                                var loc;

                                // Even number of points, find midpoint of the middle two
                                // Odd number of points, use position of very middle point
                                if (mid % 1 === 0) {
                                    loc = pointAverage([points[mid - 1], points[mid]]);
                                } else {
                                    mid = points[Math.floor(mid)];
                                    loc = [mid.lon, mid.lat];
                                }

                                // One-ways can land on same segment in opposite direction
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { // this is used to post changes to the error
                                        wayId: feature.wayId,
                                        fromNodeId: feature.fromNodeId,
                                        toNodeId: feature.toNodeId
                                    },
                                    object_id: feature.wayId,
                                    object_type: 'way',
                                    status: feature.status
                                });

                                // Variables used in the description
                                d.replacements = {
                                    percentage: feature.percentOfTrips,
                                    num_trips: feature.numberOfTrips,
                                    highway: linkErrorObject(t('QA.keepRight.error_parts.highway')),
                                    from_node: linkEntity('n' + feature.fromNodeId),
                                    to_node: linkEntity('n' + feature.toNodeId)
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Tiles at high zoom == missing roads
                        if (data.tiles) {
                            data.tiles.forEach(function(feature) {
                                var geoType = feature.type.toLowerCase();

                                // Average of recorded points should land on the missing geometry
                                // Missing geometry could happen to land on another error
                                var loc = pointAverage(feature.points);
                                loc = preventCoincident(loc, false);

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k + '-' + geoType,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: { x: feature.x, y: feature.y },
                                    status: feature.status
                                });

                                d.replacements = {
                                    num_trips: feature.numberOfTrips,
                                    geometry_type: t('QA.improveOSM.geometry_types.' + geoType)
                                };

                                // -1 trips indicates data came from a 3rd party
                                if (feature.numberOfTrips === -1) {
                                    d.desc = t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
                                }

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }

                        // Entities at high zoom == turn restrictions
                        if (data.entities) {
                            data.entities.forEach(function(feature) {
                                // Turn restrictions could be missing at same junction
                                // We also want to bump the error up so node is accessible
                                var loc = feature.point;
                                loc = preventCoincident([loc.lon, loc.lat], true);

                                // Elements are presented in a strange way
                                var ids = feature.id.split(',');
                                var from_way = ids[0];
                                var via_node = ids[3];
                                var to_way = ids[2].split(':')[1];

                                var d = new qaError({
                                    // Info required for every error
                                    loc: loc,
                                    service: 'improveOSM',
                                    error_type: k,
                                    // Extra details needed for this service
                                    error_key: k,
                                    identifier: feature.id,
                                    object_id: via_node,
                                    object_type: 'node',
                                    status: feature.status
                                });

                                // Travel direction along from_way clarifies the turn restriction
                                var p1 = feature.segments[0].points[0];
                                var p2 = feature.segments[0].points[1];

                                var dir_of_travel = cardinalDirection(relativeBearing(p1, p2));

                                // Variables used in the description
                                d.replacements = {
                                    num_passed: feature.numberOfPasses,
                                    num_trips: feature.segments[0].numberOfTrips,
                                    turn_restriction: feature.turnType.toLowerCase(),
                                    from_way: linkEntity('w' + from_way),
                                    to_way: linkEntity('w' + to_way),
                                    travel_direction: dir_of_travel,
                                    junction: linkErrorObject(t('QA.keepRight.error_parts.this_node'))
                                };

                                _erCache.data[d.id] = d;
                                _erCache.rtree.insert(encodeErrorRtree(d));
                            });
                        }
                    }
                );
            });

            _erCache.inflightTile[tile.id] = requests;
            dispatch.call('loaded');
        });
    },

    getComments: function(d, callback) {
        // If comments already retrieved no need to do so again
        if (d.comments !== undefined) { return callback({}, d); }

        var key = d.error_key;
        var qParams = {};

        if (key === 'ow') {
            qParams = d.identifier;
        } else if (key === 'mr') {
            qParams.tileX = d.identifier.x;
            qParams.tileY = d.identifier.y;
        } else if (key === 'tr') {
            qParams.targetId = d.identifier;
        }

        var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams);

        var that = this;
        d3_json(url, function(err, data) {
            // comments are served newest to oldest
            var comments = data.comments ? data.comments.reverse() : [];

            that.replaceError(d.update({
                comments: comments
            }));
            return callback(err, d);
        });
    },

    postUpdate: function(d, callback) {
        if (!services.osm.authenticated()) { // Username required in payload
            return callback({ message: 'Not Authenticated', status: -3}, d);
        }
        if (_erCache.inflightPost[d.id]) {
            return callback({ message: 'Error update already inflight', status: -2 }, d);
        }

        var that = this;

        // Payload can only be sent once username is established
        services.osm.userDetails(sendPayload);

        function sendPayload(err, user) {
            if (err) { return callback(err, d); }

            var key = d.error_key;
            var url = _impOsmUrls[key] + '/comment';
            var payload = {
                username: user.display_name
            };

            // Each error type has different data for identification
            if (key === 'ow') {
                payload.roadSegments = [ d.identifier ];
            } else if (key === 'mr') {
                payload.tiles = [ d.identifier ];
            } else if (key === 'tr') {
                payload.targetIds = [ d.identifier ];
            }

            if (d.newStatus !== undefined) {
                payload.status = d.newStatus;
                payload.text = 'status changed';
            }

            // Comment take place of default text
            if (d.newComment !== undefined) {
                payload.text = d.newComment;
            }

            _erCache.inflightPost[d.id] = d3_request(url)
                .header('Content-Type', 'application/json')
                .post(JSON.stringify(payload), function(err) {
                    delete _erCache.inflightPost[d.id];

                    // Unsuccessful response status, keep issue open
                    if (err.status !== 200) { return callback(err, d); }

                    // Just a comment, update error in cache
                    if (d.newStatus === undefined) {
                        var now = new Date();
                        var comments = d.comments ? d.comments : [];

                        comments.push({
                            username: payload.username,
                            text: payload.text,
                            timestamp: now.getTime() / 1000
                        });

                        that.replaceError(d.update({
                            comments: comments,
                            newComment: undefined
                        }));
                    } else {
                        that.removeError(d);

                        if (d.newStatus === 'SOLVED') {
                            // No pretty identifier, so we just use coordinates
                            var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
                            _erCache.closed[key + ':' + closedID] = true;
                        }
                    }

                    return callback(err, d);
                });
        }
    },

    // get all cached errors covering the viewport
    getErrors: function(projection) {
        var viewport = projection.clipExtent();
        var min = [viewport[0][0], viewport[1][1]];
        var max = [viewport[1][0], viewport[0][1]];
        var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();

        return _erCache.rtree.search(bbox).map(function(d) {
            return d.data;
        });
    },

    // get a single error from the cache
    getError: function(id) {
        return _erCache.data[id];
    },

    // replace a single error in the cache
    replaceError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        _erCache.data[error.id] = error;
        updateRtree(encodeErrorRtree(error), true); // true = replace
        return error;
    },

    // remove a single error from the cache
    removeError: function(error) {
        if (!(error instanceof qaError) || !error.id) return;

        delete _erCache.data[error.id];
        updateRtree(encodeErrorRtree(error), false); // false = remove
    },

    // Used to populate `closed:improveosm` changeset tag
    getClosedIDs: function() {
        return Object.keys(_erCache.closed).sort();
    }
};
<|endoftext|>


<commit_before>/** @jsx dom */

import {mount,div} from '../helpers'
import {component,render,deku,dom} from '../../'
import assert from 'assert'

it('should fire mount events on sub-components', function(done){
  const App = {
    propTypes: {
      foo: {
        source: 'foo'
      }
    },

    render(comp) {
      let {props} = comp;
      let {foo} = props;

      return (

          {foo || 'App'}

      );
    }
  }

  var el = document.createElement('div');
  var app = deku(<App/>);
  render(app, el);
  app.set('foo', 'bar');
  requestAnimationFrame(function(){
    assert.equal(el.innerHTML, '
bar
');
    done();
  });
});
<commit_msg>named test<commit_after>/** @jsx dom */

import {mount,div} from '../helpers'
import {component,render,deku,dom} from '../../'
import assert from 'assert'

it('should set source without property type', function(done){
  const App = {
    propTypes: {
      foo: {
        source: 'foo'
      }
    },

    render(comp) {
      let {props} = comp;
      let {foo} = props;

      return (

          {foo || 'App'}

      );
    }
  }

  var el = document.createElement('div');
  var app = deku(<App/>);
  render(app, el);
  app.set('foo', 'bar');
  requestAnimationFrame(function(){
    assert.equal(el.innerHTML, '
bar
');
    done();
  });
});
<|endoftext|>


<commit_before>const webpack = require('webpack');

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');

const NotifyPlugin = require('notify-webpack-plugin');

const ASSET_NAME_PATTERN = 'static/[name]-[hash:6].[ext]';

const package = require('../package.json');

module.exports = (env) => {
  const isDev = env === 'dev';
  let version = package.version.toString();
  if (process.env.TRAVIS_BUILD_NUMBER) {
    version += "." + process.env.TRAVIS_BUILD_NUMBER;
  }

  const config = {
    entry: {
      main: './src/index.js',
      authReturn: './src/authReturn.js',
    },

    output: {
      path: path.resolve(__dirname, './dist'),
      filename: '[name]-[chunkhash:6].js',
      chunkFilename: 'chunk-[id]-[name]-[chunkhash:6].js'
    },

    devServer: {
      contentBase: path.resolve(__dirname, './src'),
      publicPath: '/',
      https: true,
      host: '0.0.0.0',
      hot: false,
      //headers: { "X-Custom-Header": "yes" }
    },

    devtool: 'cheap-module-source-map',

    stats: 'errors-only',

    performance: {
      hints: false
    },

    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
        }, {
          test: /\.json$/,
          loader: 'json-loader'
        }, {
          test: /\.html$/,
          use: [
            {
              loader: 'file-loader',
              options: { name: ASSET_NAME_PATTERN },
            },
            'extract-loader',
            'html-loader'
          ],
        }, {
          test: /\.(png|eot|svg|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/,
          loader: 'url-loader',
          options: {
            limit: 5 * 1024, // only inline if less than 5kb
            name: ASSET_NAME_PATTERN
          },
        }, {
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract({
            fallbackLoader: 'style-loader',
            loader: 'css-loader!sass-loader',
          }),
        }
      ],
    },

    resolve: {
      extensions: ['.js', '.json'],

      alias: {
        app: path.resolve('./src'),
      }
    },

    plugins: [
      new CleanWebpackPlugin(['dist'], {
        root: path.resolve('./'),
      }),

      new NotifyPlugin('DIM', !isDev),

      new ExtractTextPlugin('styles-[hash:6].css'),

      new HtmlWebpackPlugin({
        inject: false,
        filename: 'index.html',
        template: '!handlebars-loader!src/index.html',
      }),

      new HtmlWebpackPlugin({
        inject: false,
        filename: 'return.html',
        template: '!handlebars-loader!src/return.html',
      }),

      new CopyWebpackPlugin([
        {
          from: './node_modules/zip-js/WebContent',
          to: 'static/zipjs',
          ignore: ['tests/**/*'],
        },

        { from: './src/extension-scripts/main.js', to: 'extension-scripts/' },
        { from: './src/manifest.json' },
        { from: `./icons/${env}/icon128.png` },
        { from: `./icons/${env}/icon16.png` },
        { from: `./icons/${env}/icon19.png` },
        { from: `./icons/${env}/icon38.png` },
        { from: `./icons/${env}/icon48.png` },
        { from: `./icons/${env}/favicon-16x16.png` },
        { from: `./icons/${env}/favicon-32x32.png` },
        { from: `./icons/${env}/favicon-96x96.png` },

        // TODO: Quick hack to get elemental damage icon for StoreItem
        { from: './src/images/arc.png', to: 'images' },
        { from: './src/images/solar.png', to: 'images' },
        { from: './src/images/void.png', to: 'images' },
      ]),

      new webpack.DefinePlugin({
        $DIM_VERSION: JSON.stringify(version),
        $DIM_FLAVOR: JSON.stringify(env),
        $DIM_CHANGELOG: JSON.stringify(`https://github.com/DestinyItemManager/DIM/blob/${env === 'release' ? 'master' : 'dev'}/CHANGELOG.md${env === 'release' ? '' : '#next'}`),
        $DIM_API_KEY: JSON.stringify(process.env.API_KEY),
        $DIM_AUTH_URL: JSON.stringify(process.env.AUTH_URL)
      }),

      new Visualizer(),
    ],

    node: {
      fs: 'empty',
      net: 'empty',
      tls: 'empty'
    },
  };

  if (!isDev) {
    // Bail and fail hard on first error
    config.bail = true;
    config.stats = 'verbose';

    // The sql.js library doesnt work at all (reports no tables) when minified,
    // so we exclude it from the regular minification
    // FYI, uglification runs on final chunks rather than individual modules
    config.plugins.push(new webpack.optimize.UglifyJsPlugin({
      exclude: /-sqlLib-/, // ensure the sqlLib chunk doesnt get minifed
      compress: { warnings: false, },
      output: { comments: false, },
      sourceMap: true,
    }));

    config.plugins.push(new webpack.optimize.UglifyJsPlugin({
      test: /-sqlLib-/, // run only for the sql.js chunk
      compress: false,
      output: { comments: false, },
      sourceMap: true,
    }));
  }

  return config;
};
<commit_msg>Fixing hound issue with reserve word.<commit_after>const webpack = require('webpack');

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');

const NotifyPlugin = require('notify-webpack-plugin');

const ASSET_NAME_PATTERN = 'static/[name]-[hash:6].[ext]';

const packageJson = require('../package.json');

module.exports = (env) => {
  const isDev = env === 'dev';
  let version = packageJson.version.toString();
  if (process.env.TRAVIS_BUILD_NUMBER) {
    version += "." + process.env.TRAVIS_BUILD_NUMBER;
  }

  const config = {
    entry: {
      main: './src/index.js',
      authReturn: './src/authReturn.js',
    },

    output: {
      path: path.resolve(__dirname, './dist'),
      filename: '[name]-[chunkhash:6].js',
      chunkFilename: 'chunk-[id]-[name]-[chunkhash:6].js'
    },

    devServer: {
      contentBase: path.resolve(__dirname, './src'),
      publicPath: '/',
      https: true,
      host: '0.0.0.0',
      hot: false,
      //headers: { "X-Custom-Header": "yes" }
    },

    devtool: 'cheap-module-source-map',

    stats: 'errors-only',

    performance: {
      hints: false
    },

    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
        }, {
          test: /\.json$/,
          loader: 'json-loader'
        }, {
          test: /\.html$/,
          use: [
            {
              loader: 'file-loader',
              options: { name: ASSET_NAME_PATTERN },
            },
            'extract-loader',
            'html-loader'
          ],
        }, {
          test: /\.(png|eot|svg|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/,
          loader: 'url-loader',
          options: {
            limit: 5 * 1024, // only inline if less than 5kb
            name: ASSET_NAME_PATTERN
          },
        }, {
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract({
            fallbackLoader: 'style-loader',
            loader: 'css-loader!sass-loader',
          }),
        }
      ],
    },

    resolve: {
      extensions: ['.js', '.json'],

      alias: {
        app: path.resolve('./src'),
      }
    },

    plugins: [
      new CleanWebpackPlugin(['dist'], {
        root: path.resolve('./'),
      }),

      new NotifyPlugin('DIM', !isDev),

      new ExtractTextPlugin('styles-[hash:6].css'),

      new HtmlWebpackPlugin({
        inject: false,
        filename: 'index.html',
        template: '!handlebars-loader!src/index.html',
      }),

      new HtmlWebpackPlugin({
        inject: false,
        filename: 'return.html',
        template: '!handlebars-loader!src/return.html',
      }),

      new CopyWebpackPlugin([
        {
          from: './node_modules/zip-js/WebContent',
          to: 'static/zipjs',
          ignore: ['tests/**/*'],
        },

        { from: './src/extension-scripts/main.js', to: 'extension-scripts/' },
        { from: './src/manifest.json' },
        { from: `./icons/${env}/icon128.png` },
        { from: `./icons/${env}/icon16.png` },
        { from: `./icons/${env}/icon19.png` },
        { from: `./icons/${env}/icon38.png` },
        { from: `./icons/${env}/icon48.png` },
        { from: `./icons/${env}/favicon-16x16.png` },
        { from: `./icons/${env}/favicon-32x32.png` },
        { from: `./icons/${env}/favicon-96x96.png` },

        // TODO: Quick hack to get elemental damage icon for StoreItem
        { from: './src/images/arc.png', to: 'images' },
        { from: './src/images/solar.png', to: 'images' },
        { from: './src/images/void.png', to: 'images' },
      ]),

      new webpack.DefinePlugin({
        $DIM_VERSION: JSON.stringify(version),
        $DIM_FLAVOR: JSON.stringify(env),
        $DIM_CHANGELOG: JSON.stringify(`https://github.com/DestinyItemManager/DIM/blob/${env === 'release' ? 'master' : 'dev'}/CHANGELOG.md${env === 'release' ? '' : '#next'}`),
        $DIM_API_KEY: JSON.stringify(process.env.API_KEY),
        $DIM_AUTH_URL: JSON.stringify(process.env.AUTH_URL)
      }),

      new Visualizer(),
    ],

    node: {
      fs: 'empty',
      net: 'empty',
      tls: 'empty'
    },
  };

  if (!isDev) {
    // Bail and fail hard on first error
    config.bail = true;
    config.stats = 'verbose';

    // The sql.js library doesnt work at all (reports no tables) when minified,
    // so we exclude it from the regular minification
    // FYI, uglification runs on final chunks rather than individual modules
    config.plugins.push(new webpack.optimize.UglifyJsPlugin({
      exclude: /-sqlLib-/, // ensure the sqlLib chunk doesnt get minifed
      compress: { warnings: false, },
      output: { comments: false, },
      sourceMap: true,
    }));

    config.plugins.push(new webpack.optimize.UglifyJsPlugin({
      test: /-sqlLib-/, // run only for the sql.js chunk
      compress: false,
      output: { comments: false, },
      sourceMap: true,
    }));
  }

  return config;
};
<|endoftext|>


<commit_before>var test = require('tape'),
	Frame = require('../lib/frame');

test("access column from hidden property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	t.equals(JSON.stringify(frame._cols["a"]), JSON.stringify(a));
});

test("access keys from hidden property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];
	var k = ["one", "two"];

	var frame = new Frame({
			"a" : a,
			"b" : b
		},
		{
			"a" : k
	});

	t.equals(JSON.stringify(frame._keys["a"]), JSON.stringify(k));
});

test("row based constructor creates columns correctly", function(t){
	t.plan(2);
	var rows = [
		{"a" : 0, "b" : 1},
		{"a" : 0, "b" : 2},
		{"a" : 0, "b" : 2},
		{"a" : 1, "b" : 3},
		{"a" : 1, "b" : 1},
		{"a" : 0, "b" : 3},
		{"a" : 1, "b" : 4},
		{"a" : 0, "b" : 2},
		{"a" : 1, "b" : 1},
	];

	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame(rows);

	t.equals(JSON.stringify(frame._cols["a"]), JSON.stringify(a));
	t.equals(JSON.stringify(frame._cols["b"]), JSON.stringify(b));
});

test("access column as property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	t.equals(JSON.stringify(frame["a"]), JSON.stringify(a));
});

test("accessing column as property decodes when key is present", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];
	var k = ["one", "two"];

	var frame = new Frame({
			"a" : a,
			"b" : b
		},
		{
			"a" : k
	});

	var expected = ["one", "one", "one", "two", "two", "one", "two", "one", "two"];
	t.equals(JSON.stringify(frame["a"]), JSON.stringify(expected));
});

test("only columns are enumerable", function(t){
	t.plan(2);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	var expected = ["a", "b"];

	t.equals(JSON.stringify(Object.keys(frame)), JSON.stringify(expected));

	var found = [];

	for(name in frame){
		found.push(name);
	}

	t.equals(JSON.stringify(found), JSON.stringify(expected));
});

test("Symbol.toStringTag correctly overridden", function(t){
	t.plan(1);
	var frame = new Frame({
		"a" : [0],
		"b" : [1]
	});

	var expected = "[object Frame]";

	t.equals(Object.prototype.toString.call(frame), expected);
});
<commit_msg>add tests for column rename<commit_after>var test = require('tape'),
	Frame = require('../lib/frame');

test("access column from hidden property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	t.equals(JSON.stringify(frame._cols["a"]), JSON.stringify(a));
});

test("access keys from hidden property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];
	var k = ["one", "two"];

	var frame = new Frame({
			"a" : a,
			"b" : b
		},
		{
			"a" : k
	});

	t.equals(JSON.stringify(frame._keys["a"]), JSON.stringify(k));
});

test("row based constructor creates columns correctly", function(t){
	t.plan(2);
	var rows = [
		{"a" : 0, "b" : 1},
		{"a" : 0, "b" : 2},
		{"a" : 0, "b" : 2},
		{"a" : 1, "b" : 3},
		{"a" : 1, "b" : 1},
		{"a" : 0, "b" : 3},
		{"a" : 1, "b" : 4},
		{"a" : 0, "b" : 2},
		{"a" : 1, "b" : 1},
	];

	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame(rows);

	t.equals(JSON.stringify(frame._cols["a"]), JSON.stringify(a));
	t.equals(JSON.stringify(frame._cols["b"]), JSON.stringify(b));
});

test("access column as property", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	t.equals(JSON.stringify(frame["a"]), JSON.stringify(a));
});

test("accessing column as property decodes when key is present", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];
	var k = ["one", "two"];

	var frame = new Frame({
			"a" : a,
			"b" : b
		},
		{
			"a" : k
	});

	var expected = ["one", "one", "one", "two", "two", "one", "two", "one", "two"];
	t.equals(JSON.stringify(frame["a"]), JSON.stringify(expected));
});

test("only columns are enumerable", function(t){
	t.plan(2);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	var expected = ["a", "b"];

	t.equals(JSON.stringify(Object.keys(frame)), JSON.stringify(expected));

	var found = [];

	for(name in frame){
		found.push(name);
	}

	t.equals(JSON.stringify(found), JSON.stringify(expected));
});

test("Symbol.toStringTag correctly overridden", function(t){
	t.plan(1);
	var frame = new Frame({
		"a" : [0],
		"b" : [1]
	});

	var expected = "[object Frame]";

	t.equals(Object.prototype.toString.call(frame), expected);
});

test("rename column correctly modifies frame properties", function(t){
	t.plan(2);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	var expected = ["a", "c"];

	frame.rename("b", "c");

	t.equals(JSON.stringify(Object.keys(frame)), JSON.stringify(expected));

	var found = [];

	for(name in frame){
		found.push(name);
	}

	t.equals(JSON.stringify(found), JSON.stringify(expected));
});

test("rename column correctly adds accessor", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	});

	var expected = b;

	frame.rename("b", "c");

	t.equals(JSON.stringify(frame["c"]), JSON.stringify(expected));
});

test("rename column correctly converts key", function(t){
	t.plan(1);
	var a = [0, 0, 0, 1, 1, 0, 1, 0, 1];
	var b = [1, 2, 2, 3, 1, 3, 4, 2, 1];

	var frame = new Frame({
		"a" : a,
		"b" : b
	},
	{
		"b" : ["zero", "one", "two", "three", "four"]
	});

	var expected = ["one", "two", "two", "three", "one", "three", "four", "two", "one"];

	frame.rename("b", "c");

	t.equals(JSON.stringify(frame["c"]), JSON.stringify(expected));
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import ChannelName from './ChannelName';
import { Link } from 'react-router-dom';
import MessagesType from '../constants/MessagesType';

export default class extends Component {
  getChannel(id) {
    const channels = this.props.channels;
    const ims = this.props.ims;
    if (channels && channels[id]) {
      return channels[id];
    }
    if (ims && ims[id]) {
      return ims[id];
    }
  }
  getUser(id) {
    const users = this.props.users;
    return users && users[id];
  }
  formatDate(date) {
    return new Date(date * 1000).toLocaleString();
  }
  formatText(text) {
    const entity = (str) => {
      return str.replace(/"/g, """)
        .replace(/</g, "<")
        .replace(/>/g, ">");
    };
    const channelLink = (id) => {
      const channel = this.getChannel(id);
      return `#${channel && channel.name}`;
    };
    const userLink = (id) => {
      const user = this.getUser(id);
      return `@${user && user.name}`;
    };
    const specialCommand = (command) => `@${command}`;
    const uriLink = (uri) => `<a href="${uri}" target="_blank">${uri}</a>`;
    if (text) {
      return text.replace(/<#([0-9A-Za-z]+)>/, (m, id) => channelLink(id))
        .replace(/<#([0-9A-Za-z]+)\|([0-9A-Za-z]+)>/gi, (m, id) => channelLink(id))
        .replace(/<@([0-9A-Za-z]+)>/gi, (m, id) => userLink(id))
        .replace(/<@([0-9A-Za-z]+)\|([0-9A-Za-z]+)>/gi, (m, id) => userLink(id))
        .replace(/<!(channel|everyone|group)>/gi, (m, command) => specialCommand(command))
        .replace(/<(https?:\/\/[^>]*)>/gi, (m, uri) => uriLink(entity(uri)));
    }
    return text;
  }
  messageLink(message) {
    return `/${message.channel}/${message.ts}`;
  }
  originalMessageLink(teamInfo, message) {
    const messageId = message.ts.replace('.', '');
    return `https://${teamInfo.domain}.slack.com/messages/${message.channel}/p${messageId}`;
  }
  render() {
    const createMarkup = (text) => {
      return {
        __html: this.formatText(text) || ''
      };
    };
    const SlackMessagePrototype = ({ message, icon, username, showChannel, teamInfo, text }) => {
      const channel = this.getChannel(message.channel);
      const classNames = ['slack-message'];
      if (this.props.selected) {
        classNames.push('selected');
      }

      return (

{username}

              <Link to={this.messageLink(message)}>
                {this.formatDate(message.ts)}
              </Link>

            { showChannel && channel ? (

                  <Link to={ `/${channel.id}` }>
                    <ChannelName channel={channel} />
                  </Link>

              ) : null }

              open original

      );
    };
    const botMessage = (teamInfo, message, showChannel) => {
      const attachment = _.find(message.attachments, (attachment) => attachment.text);
      const text = attachment ? attachment.text : message.text;
      const icon = message.icons ? message.icons.image_48 : (attachment ? attachment.author_icon : '');
      return <SlackMessagePrototype
          message={message}
          icon={icon}
          username={message.username}
          showChannel={showChannel}
          teamInfo={teamInfo}
          text={text}
        />
    };
    const normalMessage = (teamInfo, message, user, showChannel) => {
      return <SlackMessagePrototype
          message={message}
          icon={user && user.profile.image_48}
          username={user && user.name}
          showChannel={showChannel}
          teamInfo={teamInfo}
          text={message.text}
        />
    };

    if (this.props.message.hidden) {
      return null;
    }

    const message = this.props.message;
    const showChannel = this.props.type === MessagesType.SEARCH_MESSAGES;
    switch (this.props.message.subtype) {
      case 'bot_message':
        return botMessage(this.props.teamInfo, message, showChannel);
        break;
      default:
        return normalMessage(this.props.teamInfo, message, this.getUser(message.user), showChannel);
        break;
    }
  }
}
<commit_msg>replace not only https but also mongodb, ftp, etc...<commit_after>import React, { Component } from 'react';
import ChannelName from './ChannelName';
import { Link } from 'react-router-dom';
import MessagesType from '../constants/MessagesType';

export default class extends Component {
  getChannel(id) {
    const channels = this.props.channels;
    const ims = this.props.ims;
    if (channels && channels[id]) {
      return channels[id];
    }
    if (ims && ims[id]) {
      return ims[id];
    }
  }
  getUser(id) {
    const users = this.props.users;
    return users && users[id];
  }
  formatDate(date) {
    return new Date(date * 1000).toLocaleString();
  }
  formatText(text) {
    const entity = (str) => {
      return str.replace(/"/g, """)
        .replace(/</g, "<")
        .replace(/>/g, ">");
    };
    const channelLink = (id) => {
      const channel = this.getChannel(id);
      return `#${channel && channel.name}`;
    };
    const userLink = (id) => {
      const user = this.getUser(id);
      return `@${user && user.name}`;
    };
    const specialCommand = (command) => `@${command}`;
    const uriLink = (uri) => `<a href="${uri}" target="_blank">${uri}</a>`;
    if (text) {
      return text.replace(/<#([0-9A-Za-z]+)>/, (m, id) => channelLink(id))
        .replace(/<#([0-9A-Za-z]+)\|([0-9A-Za-z]+)>/gi, (m, id) => channelLink(id))
        .replace(/<@([0-9A-Za-z]+)>/gi, (m, id) => userLink(id))
        .replace(/<@([0-9A-Za-z]+)\|([0-9A-Za-z]+)>/gi, (m, id) => userLink(id))
        .replace(/<!(channel|everyone|group)>/gi, (m, command) => specialCommand(command))
        .replace(/<([a-z]+:\/\/[^>]*)>/gi, (m, uri) => uriLink(entity(uri)));
    }
    return text;
  }
  messageLink(message) {
    return `/${message.channel}/${message.ts}`;
  }
  originalMessageLink(teamInfo, message) {
    const messageId = message.ts.replace('.', '');
    return `https://${teamInfo.domain}.slack.com/messages/${message.channel}/p${messageId}`;
  }
  render() {
    const createMarkup = (text) => {
      return {
        __html: this.formatText(text) || ''
      };
    };
    const SlackMessagePrototype = ({ message, icon, username, showChannel, teamInfo, text }) => {
      const channel = this.getChannel(message.channel);
      const classNames = ['slack-message'];
      if (this.props.selected) {
        classNames.push('selected');
      }

      return (

{username}

              <Link to={this.messageLink(message)}>
                {this.formatDate(message.ts)}
              </Link>

            { showChannel && channel ? (

                  <Link to={ `/${channel.id}` }>
                    <ChannelName channel={channel} />
                  </Link>

              ) : null }

              open original

      );
    };
    const botMessage = (teamInfo, message, showChannel) => {
      const attachment = _.find(message.attachments, (attachment) => attachment.text);
      const text = attachment ? attachment.text : message.text;
      const icon = message.icons ? message.icons.image_48 : (attachment ? attachment.author_icon : '');
      return <SlackMessagePrototype
          message={message}
          icon={icon}
          username={message.username}
          showChannel={showChannel}
          teamInfo={teamInfo}
          text={text}
        />
    };
    const normalMessage = (teamInfo, message, user, showChannel) => {
      return <SlackMessagePrototype
          message={message}
          icon={user && user.profile.image_48}
          username={user && user.name}
          showChannel={showChannel}
          teamInfo={teamInfo}
          text={message.text}
        />
    };

    if (this.props.message.hidden) {
      return null;
    }

    const message = this.props.message;
    const showChannel = this.props.type === MessagesType.SEARCH_MESSAGES;
    switch (this.props.message.subtype) {
      case 'bot_message':
        return botMessage(this.props.teamInfo, message, showChannel);
        break;
      default:
        return normalMessage(this.props.teamInfo, message, this.getUser(message.user), showChannel);
        break;
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import Decimal from './decimal.component';

function render(props, renderer = shallow) {
  return renderer(
    <Decimal { ...props } />
  );
}

describe('Decimal', () => {
  describe('Init test', () => {
    it('returns false', () => {
      const test = false;
      expect(test).toEqual(false);
    });
  });
});
<commit_msg>Basic Spec Descs for Decimal Component<commit_after>import React from 'react';
import { shallow } from 'enzyme';
import Decimal from './decimal.component';

function render(props, renderer = shallow) {
  return renderer(
    <Decimal { ...props } />
  );
}

describe('Decimal', () => {
  describe('Input validation', () => {
    it('does not allow the user to enter letters or special characters', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('does not allow the user to enter commas after the decimal point', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('formats the commas correctly on blur', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('updates the value after increasing the precison', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('updates the value after decreasing the precison', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('does not allow the precison to be greater than 20', () => {
      const test = false;
      expect(test).toEqual(true);
    });
  });
  // DQ: DO THESE ACTUALLY GO HERE?!?!
  describe('Layout', () => {
    it('allows the input width to be set', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('sets the input width correctly if the inlineInput prop is true', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('allows the label width to be set if the inlineInput prop is true', () => {
      const test = false;
      expect(test).toEqual(true);
    });
    it('aligns the value within the input based on align prop', () => {
      const test = false;
      expect(test).toEqual(true);
    });
  });
});
<|endoftext|>


<commit_before>define(
    [
        'jquery',
        'CheckoutCom_Magento2/js/view/payment/config-loader',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/checkout-data',
        'mage/url',
        'Magento_Checkout/js/action/redirect-on-success',
        'Magento_Checkout/js/model/full-screen-loader',
        'mage/translate',
        'mage/cookies'
    ],
    function ($, Config, Quote, CheckoutData, Url, RedirectOnSuccessAction, FullScreenLoader, __) {
        'use strict';

        const KEY_CONFIG = 'checkoutcom_configuration';
        const KEY_DATA = 'checkoutcom_data';

        return {
            /**
             * Gets a field value.
             *
             * @param  {string}  methodId The method id
             * @param  {string}  field    The field
             * @return {mixed}            The value
             */
            getValue: function (methodId, field) {
                var val = null;
                if (methodId && Config.hasOwnProperty(methodId) && Config[methodId].hasOwnProperty(field)) {
                    val = Config[methodId][field]
                } else if (Config.hasOwnProperty(KEY_CONFIG) && Config[KEY_CONFIG].hasOwnProperty(field)) {
                    val = Config[KEY_CONFIG][field];
                }

                return val;
            },

            getStoreName: function () {
                return Config[KEY_DATA].store.name;
            },

            getQuoteValue: function () {
                return Config[KEY_DATA].quote.value;
            },

            getQuoteCurrency: function () {
                return Config[KEY_DATA].quote.currency;
            },

            userHasCards: function () {
                return Config[KEY_DATA].user.hasCards;
            },

            /**
             * Builds the controller URL.
             *
             * @param  {string}  path  The path
             * @return {string}
             */
            getUrl: function (path) {
                return Url.build('checkout_com/' + path);
            },

            /**
             * Customer name.
             *
             * @param  {bool} return in object format.
             * @return {mixed}  The billing address.
             */
            getCustomerName: function () {
                var billingAddress = Quote.billingAddress();
                var customerName = '';
                if (billingAddress) {
                    customerName += billingAddress.firstname;
                    customerName += ' ' + billingAddress.lastname;
                }

                return customerName;
            },

            /**
             * Billing address.
             *
             * @return {object}  The billing address.
             */
            getBillingAddress: function () {
                return Quote.billingAddress();
            },

            /**
             * @returns {string}
             */
            getEmail: function () {
                return window.checkoutConfig.customerData.email || Quote.guestEmail || CheckoutData.getValidatedEmailValue();
            },

            /**
             * @returns {void}
             */
            setEmail: function () {
                $.cookie(
                    this.getValue(null, 'email_cookie_name'),
                    this.getEmail()
                );
            },

            /**
             * @returns {object}
             */
            getPhone: function () {
                var billingAddress = Quote.billingAddress();

                return {
                    number: billingAddress.telephone
                };
            },

            /**
             * Handle error logging.
             */
            log: function (val) {
                if (this.getValue(null, 'debug')
                    && this.getValue(null, 'console_logging')
                ) {
                    console.log(val);
                }
            },

            /**
             * Show a message.
             */
            showMessage: function (type, message, methodId) {
                this.clearMessages(methodId);
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Clear all messages.
             */
            clearMessages: function (methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.hide();
                messageContainer.empty();
            },

            /**
             * Get a payment method container instance.
             */
            getMethodContainer: function (methodId) {
                return $('#' + methodId + '_container');
            },

            /**
             * Check if an URL is valid.
             */
            isUrl: function (str) {
                var pattern = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
                return pattern.test(str);
            },

            /**
             * Handle the place order button state.
             */
            allowPlaceOrder: function (buttonId, yesNo) {
                $('#' + buttonId).prop('disabled', !yesNo);
            },

            /**
             * Place a new order.
             *
             * @returns {void}
             */
            placeOrder: function (payload, methodId) {
                var self = this;

                // Start the loader
                FullScreenLoader.startLoader();

                // Send the request
                $.ajax({
                    type: 'POST',
                    url: self.getUrl('payment/placeorder'),
                    data: payload,
                    success: function (data) {
                        if (!data.success) {
                            FullScreenLoader.stopLoader();
                            self.showMessage('error', data.message, methodId);
                        } else if (data.success && data.url) {
                            // Handle 3DS redirection
                            window.location.href = data.url
                        } else {
                            // Normal redirection
                            RedirectOnSuccessAction.execute();
                        }
                    },
                    error: function (request, status, error) {
                        self.showMessage('error', error, methodId);
                        FullScreenLoader.stopLoader();
                    }
                });
            }
        };
    }
);
<commit_msg>Updated the utilites js component<commit_after>define(
    [
        'jquery',
        'CheckoutCom_Magento2/js/view/payment/config-loader',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/checkout-data',
        'mage/url',
        'Magento_Checkout/js/action/redirect-on-success',
        'Magento_Checkout/js/model/full-screen-loader',
        'mage/translate',
        'mage/cookies'
    ],
    function ($, Config, Quote, CheckoutData, Url, RedirectOnSuccessAction, FullScreenLoader, __) {
        'use strict';

        const KEY_CONFIG = 'checkoutcom_configuration';
        const KEY_DATA = 'checkoutcom_data';

        return {
            /**
             * Gets a field value.
             *
             * @param  {string}  methodId The method id
             * @param  {string}  field    The field
             * @return {mixed}            The value
             */
            getValue: function (methodId, field) {
                var val = null;
                if (methodId && Config.hasOwnProperty(methodId) && Config[methodId].hasOwnProperty(field)) {
                    val = Config[methodId][field]
                } else if (Config.hasOwnProperty(KEY_CONFIG) && Config[KEY_CONFIG].hasOwnProperty(field)) {
                    val = Config[KEY_CONFIG][field];
                }

                return val;
            },

            /**
             * Get the store name.
             *
             * @return {string}  The store name.
             */
            getStoreName: function () {
                return Config[KEY_DATA].store.name;
            },

            /**
             * Get the quote value.
             *
             * @return {float}  The quote value.
             */
            getQuoteValue: function () {
                return Config[KEY_DATA].quote.value;
            },

            /**
             * Get the quote currency.
             *
             * @return {string}  The quote currency.
             */
            getQuoteCurrency: function () {
                return Config[KEY_DATA].quote.currency;
            },

            /**
             * Checks if user has saved cards.
             *
             * @return {bool}
             */
            userHasCards: function () {
                return Config[KEY_DATA].user.hasCards;
            },

            /**
             * Get the supported cards.
             *
             * @return {array}
             */
            getSupportedCards: function () {
                return Config[KEY_DATA].cards;
            },

            /**
             * Builds the controller URL.
             *
             * @param  {string}  path  The path
             * @return {string}
             */
            getUrl: function (path) {
                return Url.build('checkout_com/' + path);
            },

            /**
             * Customer name.
             *
             * @param  {bool} return in object format.
             * @return {mixed}  The billing address.
             */
            getCustomerName: function () {
                var billingAddress = Quote.billingAddress();
                var customerName = '';
                if (billingAddress) {
                    customerName += billingAddress.firstname;
                    customerName += ' ' + billingAddress.lastname;
                }

                return customerName;
            },

            /**
             * Billing address.
             *
             * @return {object}  The billing address.
             */
            getBillingAddress: function () {
                return Quote.billingAddress();
            },

            /**
             * @returns {string}
             */
            getEmail: function () {
                return window.checkoutConfig.customerData.email || Quote.guestEmail || CheckoutData.getValidatedEmailValue();
            },

            /**
             * @returns {void}
             */
            setEmail: function () {
                $.cookie(
                    this.getValue(null, 'email_cookie_name'),
                    this.getEmail()
                );
            },

            /**
             * @returns {object}
             */
            getPhone: function () {
                var billingAddress = Quote.billingAddress();

                return {
                    number: billingAddress.telephone
                };
            },

            /**
             * Handle error logging.
             */
            log: function (val) {
                if (this.getValue(null, 'debug')
                    && this.getValue(null, 'console_logging')
                ) {
                    console.log(val);
                }
            },

            /**
             * Show a message.
             */
            showMessage: function (type, message, methodId) {
                this.clearMessages(methodId);
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Clear all messages.
             */
            clearMessages: function (methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.hide();
                messageContainer.empty();
            },

            /**
             * Get a payment method container instance.
             */
            getMethodContainer: function (methodId) {
                return $('#' + methodId + '_container');
            },

            /**
             * Check if an URL is valid.
             */
            isUrl: function (str) {
                var pattern = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
                return pattern.test(str);
            },

            /**
             * Handle the place order button state.
             */
            allowPlaceOrder: function (buttonId, yesNo) {
                $('#' + buttonId).prop('disabled', !yesNo);
            },

            /**
             * Place a new order.
             *
             * @returns {void}
             */
            placeOrder: function (payload, methodId) {
                var self = this;

                // Start the loader
                FullScreenLoader.startLoader();

                // Send the request
                $.ajax({
                    type: 'POST',
                    url: self.getUrl('payment/placeorder'),
                    data: payload,
                    success: function (data) {
                        if (!data.success) {
                            FullScreenLoader.stopLoader();
                            self.showMessage('error', data.message, methodId);
                        } else if (data.success && data.url) {
                            // Handle 3DS redirection
                            window.location.href = data.url
                        } else {
                            // Normal redirection
                            RedirectOnSuccessAction.execute();
                        }
                    },
                    error: function (request, status, error) {
                        self.showMessage('error', error, methodId);
                        FullScreenLoader.stopLoader();
                    }
                });
            }
        };
    }
);
<|endoftext|>


<commit_before>NoteSetTest = TestCase('NoteSetTest');

NoteSetTest.prototype.setUp = function() {
  /** @const */ this.firstNote = new networkStickies.Note(1, 'First note');
  /** @const */ this.secondNote = new networkStickies.Note(2, 'Second note');
  /** @const */ this.thirdNote = new networkStickies.Note(3, 'Third note');
  /** @const */ this.notes = [this.firstNote, this.secondNote, this.thirdNote];
  /** @const */ this.noteSet = new networkStickies.NoteSet(this.notes);
};

NoteSetTest.prototype.testNoteSetBlocksRepresentationExposure = function() {
  /** @const */ var differentNote =
      networkStickies.Note.createNote('A changed note');
  /** @const */ var oldFirstBody = this.firstNote.body();

  this.notes[0] = differentNote;
  /** @const */ var result = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals('Swapping out the array should not mutate NoteSet',
             result.body(), 'First note');
};

NoteSetTest.prototype.testFindNoteById = function() {
  /** @const */ var first = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals(first.body(), 'First note');

  /** @const */ var notReal = this.noteSet.findNoteById(324).getOrElse(null);
  assertNull(notReal);
};
<commit_msg>Verified more paths in noteSet<commit_after>NoteSetTest = TestCase('NoteSetTest');

NoteSetTest.prototype.setUp = function() {
  /** @const */ this.firstNote = new networkStickies.Note(1, 'First note');
  /** @const */ this.secondNote = new networkStickies.Note(2, 'Second note');
  /** @const */ this.thirdNote = new networkStickies.Note(3, 'Third note');
  /** @const */ this.notes = [this.firstNote, this.secondNote, this.thirdNote];
  /** @const */ this.noteSet = new networkStickies.NoteSet(this.notes);
};

NoteSetTest.prototype.testNoteSetBlocksRepresentationExposure = function() {
  /** @const */ var differentNote =
      networkStickies.Note.createNote('A changed note');
  /** @const */ var oldFirstBody = this.firstNote.body();

  this.notes[0] = differentNote;
  /** @const */ var result = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals('Swapping out the array should not mutate NoteSet',
             result.body(), 'First note');
};

NoteSetTest.prototype.testFindNoteById = function() {
  /** @const */ var first = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals(first.body(), 'First note');

  /** @const */ var notReal = this.noteSet.findNoteById(324).getOrElse(null);
  assertNull(notReal);
};

NoteSetTest.prototype.testEditNote = function() {
  /** @const */ var nextSet = this.noteSet.editNoteWithId(
      1, 'Edited first note');

  nextSetFirst = nextSet.findNoteById(1).getOrElse(null);
  assertEquals(nextSetFirst.body(), 'Edited first note');

  oldFirst = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals('The old note set should be unchanged',
               oldFirst.body(), 'First note');
};

NoteSetTest.prototype.testDeleteNote = function() {
  /** @const */ var nextSet = this.noteSet.deleteNoteWithId(1);

  nextSetFirst = nextSet.findNoteById(1).getOrElse(null);
  assertNull(nextSetFirst);

  oldFirst = this.noteSet.findNoteById(1).getOrElse(null);
  assertEquals('The old note set should be unchanged',
               oldFirst.body(), 'First note');
};
<|endoftext|>


<commit_before>/* Is it hot right now?
   Code for loading in local weather stats (on page load and on location update)
   James Goldie, Steefan Contractor & Mat Lipson 2017 */

/* on page load: choose a location (for now, Sydney Obs Hill), download
    assets and insert */

$(function()
{
  var default_station = "066062";
  var base_path = "output/"
  var location_menu_innerpad = 10;
  var location_request_timeout = 5000;

  /* request_station: hide the details section, download stats and plots
     for a new station and then display them _when they're ready_
     put up an error message if they don't load quickly enough */
  function request_station(station_id)
  {

    // fn and obj to track downloaded resources. should probably write a
    // self-contained object for this...
    var loaded_resources =
    {
      isit_stats: false,
      isit_ts_plot: false,
      isit_density_plot: false
    }
    function resources_loaded()
    {
      if (
        loaded_resources.isit_stats == false |
        loaded_resources.isit_ts_plot == false |
        loaded_resources.isit_density_plot == false)
      {
        return false;
      }
      else
      {
        return true;
      }
    }

    // hide the details section while we update stuff!
    $("#detail").attr("style", "display: none;");
    $("#digdeeper").attr("style", "color: #eee;");
    $("#digdeeper h3").text("Loading...");
    $("#isit_answer").text(". . .");
    $("#isit_comment").text("");

    // dl stats as json and insert on success.
    // (nb: no error callback available! need to use a timeout below)
    $.getJSON(base_path + station_id + "/stats.json", function(data)
    {
      $("#isit_answer").text(data.isit_answer);
      $("#isit_comment").text(data.isit_comment);
      $("#isit_maximum").text(data.isit_maximum);
      $("#isit_minimum").text(data.isit_minimum);
      $("#isit_current").text(data.isit_current);
      $("#isit_average").text(data.isit_average);
      $("#isit_name").text(data.isit_name);
      $("#isit_span").text(data.isit_span);

      loaded_resources.isit_stats = true;

      // enable page if all resources are loaded
      if (resources_loaded())
      {
        console.log('Isithot: All resources loaded :D');
        $("#detail").attr("style", "display: flex;");
        $("#digdeeper").attr("style", "color: #b22222;");
        $("#digdeeper h3").text("Dig deeper");
      }
    });

    // dl *each* image and insert on success
    plots = ["ts_plot", "density_plot"]
    plots.map(function(content)
    {
      var plot_target = $("#isit_" + content + " img");
      console.log('Isithot: Creating callback to load ' + content);
      var plot_building = $("<img>");
      plot_building.on('load', function()
      {
        console.log('Isithot: Inside onload callback for dummy ' + content);
        // when loading img is ready, set it to the target img
        plot_target.attr("src", $(this).attr("src"));

        // enable page if all resources are loaded
        if (resources_loaded())
        {
          console.log('Isithot: All resources loaded :D');
          $("#detail").attr("style", "display: flex;");
          $("#digdeeper").attr("style", "color: #b22222;");
          $("#digdeeper h3").text("Dig deeper");
        }
      });
      // set loading img src to trigger the download
      plot_building.attr(
        "src", base_path + station_id + "/" + content + ".png");
      console.log('Isithot: Set src attribute for ' + content + '; image ought to be loading now');

      loaded_resources["isit_" + content] = true;
    });

    // apologise if resources aren't all loaded after 10 secs
    setTimeout(function()
    {
      if (!resources_loaded())
      {
        console.log('Isithot: Resources did not load within timeout D:');
        $("#digdeeper h3").text(
          "We're having some trouble downloading the data...");
      }
    }, location_request_timeout);
  }

  /* resize_location_menu: resize the location menu according to the
     current selection */
  function resize_location_menu(new_text)
  {
    // resize the dropdown
    $("#current_location_temp_dummyopt").html(new_text);
    $("#current_location").width(
      $("#current_location_temp").width() + location_menu_innerpad);
  }

  // = on page load ===========================================================

  // on page load, populate the location menu, resize it
  // and request a default station
  $.getJSON("locations.json", function(data)
  {
    $.each(data, function(index, station) {
      $("#current_location").append(
        '<option value="' + station.id + '">' +station.label + '</option>');
    });

    resize_location_menu($("#current_location option:selected").text());
    request_station($("#current_location option:selected").val());

  });

  // = callbacks ==============================================================

  /* on new location selected:
       - update the dropdown menu
       - request new location data */
  $("#current_location").change(function() {
    console.log(this.value);
    resize_location_menu($("option:selected", this).text());
    request_station(this.value);
  });

});<commit_msg>temporary geolocation function<commit_after>/* Is it hot right now?
   Code for loading in local weather stats (on page load and on location update)
   James Goldie, Steefan Contractor & Mat Lipson 2017 */

//Geolocation
$.get("http://ipinfo.io", function (response) {
    var state = response.region;
    if (state == "New South Wales") {console.log(state);}
    else if (state == "Victoria") {console.log(state);}
    else if (state == "Queensland") {console.log(state);}
    else if (state == "Western Australia") {console.log(state);}
    else if (state == "Northern Territory") {console.log(state);}
    else if (state == "South Austrlia") {console.log(state);}
    else if (state == "Tasmania") {console.log(state);}
    else {console.log("No region")}
    // $("#state").html(response.region);
}, "jsonp");
/* on page load: choose a location (for now, Sydney Obs Hill), download
    assets and insert */

$(function()
{
  var default_station = "066062";
  var base_path = "output/"
  var location_menu_innerpad = 10;
  var location_request_timeout = 5000;

  /* request_station: hide the details section, download stats and plots
     for a new station and then display them _when they're ready_
     put up an error message if they don't load quickly enough */
  function request_station(station_id)
  {

    // fn and obj to track downloaded resources. should probably write a
    // self-contained object for this...
    var loaded_resources =
    {
      isit_stats: false,
      isit_ts_plot: false,
      isit_density_plot: false
    }
    function resources_loaded()
    {
      if (
        loaded_resources.isit_stats == false |
        loaded_resources.isit_ts_plot == false |
        loaded_resources.isit_density_plot == false)
      {
        return false;
      }
      else
      {
        return true;
      }
    }

    // hide the details section while we update stuff!
    $("#detail").attr("style", "display: none;");
    $("#digdeeper").attr("style", "color: #eee;");
    $("#digdeeper h3").text("Loading...");
    $("#isit_answer").text(". . .");
    $("#isit_comment").text("");

    // dl stats as json and insert on success.
    // (nb: no error callback available! need to use a timeout below)
    $.getJSON(base_path + station_id + "/stats.json", function(data)
    {
      $("#isit_answer").text(data.isit_answer);
      $("#isit_comment").text(data.isit_comment);
      $("#isit_maximum").text(data.isit_maximum);
      $("#isit_minimum").text(data.isit_minimum);
      $("#isit_current").text(data.isit_current);
      $("#isit_average").text(data.isit_average);
      $("#isit_name").text(data.isit_name);
      $("#isit_span").text(data.isit_span);

      loaded_resources.isit_stats = true;

      // enable page if all resources are loaded
      if (resources_loaded())
      {
        console.log('Isithot: All resources loaded :D');
        $("#detail").attr("style", "display: flex;");
        $("#digdeeper").attr("style", "color: #b22222;");
        $("#digdeeper h3").text("Dig deeper");
      }
    });

    // dl *each* image and insert on success
    plots = ["ts_plot", "density_plot"]
    plots.map(function(content)
    {
      var plot_target = $("#isit_" + content + " img");
      console.log('Isithot: Creating callback to load ' + content);
      var plot_building = $("<img>");
      plot_building.on('load', function()
      {
        console.log('Isithot: Inside onload callback for dummy ' + content);
        // when loading img is ready, set it to the target img
        plot_target.attr("src", $(this).attr("src"));

        // enable page if all resources are loaded
        if (resources_loaded())
        {
          console.log('Isithot: All resources loaded :D');
          $("#detail").attr("style", "display: flex;");
          $("#digdeeper").attr("style", "color: #b22222;");
          $("#digdeeper h3").text("Dig deeper");
        }
      });
      // set loading img src to trigger the download
      plot_building.attr(
        "src", base_path + station_id + "/" + content + ".png");
      console.log('Isithot: Set src attribute for ' + content + '; image ought to be loading now');

      loaded_resources["isit_" + content] = true;
    });

    // apologise if resources aren't all loaded after 10 secs
    setTimeout(function()
    {
      if (!resources_loaded())
      {
        console.log('Isithot: Resources did not load within timeout D:');
        $("#digdeeper h3").text(
          "We're having some trouble downloading the data...");
      }
    }, location_request_timeout);
  }

  /* resize_location_menu: resize the location menu according to the
     current selection */
  function resize_location_menu(new_text)
  {
    // resize the dropdown
    $("#current_location_temp_dummyopt").html(new_text);
    $("#current_location").width(
      $("#current_location_temp").width() + location_menu_innerpad);
  }

  // = on page load ===========================================================

  // on page load, populate the location menu, resize it
  // and request a default station
  $.getJSON("locations.json", function(data)
  {
    $.each(data, function(index, station) {
      $("#current_location").append(
        '<option value="' + station.id + '">' +station.label + '</option>');
    });

    resize_location_menu($("#current_location option:selected").text());
    request_station($("#current_location option:selected").val());

  });

  // = callbacks ==============================================================

  /* on new location selected:
       - update the dropdown menu
       - request new location data */
  $("#current_location").change(function() {
    console.log(this.value);
    resize_location_menu($("option:selected", this).text());
    request_station(this.value);
  });

});<|endoftext|>