<commit_before>var map;
var userLocation;
var userMarker;

$(function() {
    console.log("Document is ready!");
});

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {
            lat: -34.397,
            lng: 150.644
        },
        zoom: 8
    });

    map.addListener('click', function(event) {
        console.log(event.latLng.lat() + ", " + event.latLng.lng())
    });

    map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(
        document.getElementById('alternative'));

    navigator.geolocation.getCurrentPosition(function(position) {
        console.log(position)

        // userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude)
        // userLocation = new google.maps.LatLng(25.015384309623297, 121.53891563415527)
        userLocation = new google.maps.LatLng(25.033661079435696, 121.51153564453125)

        map.setCenter(userLocation);
        map.setZoom(14);

        addUserMarker(userLocation, map);

        // Testing
        // calcRoute(userLocation, aMapsLatLng(25.1, 121.56), map)

        // Testing
        getBlockWithinDistance(1)

    });

    // Testing
    // drawLinesOnMap([aLocation(25.02, 121.534), aLocation(25.03, 121.534)], map)
    // drawCarDataOnMap(carData, map)

    // Testing
    // drawCrossRoadsMarkersOnMap(map)

    // Testing
    getDataFromServer()
}

function addUserMarker(location, map) {
    userMarker = new google.maps.Marker({
        map: map,
        position: location,
        title: "My Position"
    });
    // console.log(location, map);
}

// Helper --> {"lat":lat, "lng":lng}
function aLocation(lat, lng) {
    return {
        "lat": lat,
        "lng": lng
    }
}

// Helper (121, 25) --> google.maps.LatLng
function aMapsLatLng(lat, lng) {
    return new google.maps.LatLng(lat, lng)
}

// ==== Basic helper ====
function drawLinesOnMap(path, map, color) {
    var lines = new google.maps.Polyline({
        path: path,
        geodesic: true,
        strokeColor: color,
        strokeOpacity: 0.7,
        strokeWeight: 4
    });
    lines.setMap(map);
}

var directionsService;
var directionsRender;

function calcRoute(originLoc, destinationLoc, map) {

    directionsService = new google.maps.DirectionsService();
    directionsRender = new google.maps.DirectionsRenderer();
    directionsRender.setMap(map);

    var request = {
        origin: originLoc,
        destination: destinationLoc,
        optimizeWaypoints: true,
        travelMode: google.maps.TravelMode.WALKING
    }

    directionsService.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            directionsRender.setDirections(response);
        }
    });
}

function drawAMakerOnMap(coordsStringArray, map, title, image) {
    var latLng = aLocation(parseFloat(coordsStringArray[0]), parseFloat(coordsStringArray[1]));

    var marker = new google.maps.Marker({
        position: latLng,
        map: map,
        title: title,
        icon: image
    });

}

// ==== Draw events ====
function drawCrossRoadMarkerOnMap(coordsStringArray, map) {

    drawAMakerOnMap(coordsStringArray, map, '交叉路口', '/static/img/cir3.png')

}

function drawCrossRoadsMarkersOnMap(map) {

    for (var i = 0; i < cross_road_data.length; i++) {
        drawCrossRoadMarkerOnMap(cross_road_data[i], map)
    };

}

function drawCarDataOnMap(liveCarData, map) {

    var results = liveCarData;

    for (var i = 0; i < results.length; i++) {

        var color = "#3eb076" // green
        if (parseFloat(results[i].AvgOcc) > 7) {
            color = "#bb3d15"
        }

        drawLinesOnMap([aLocation(parseFloat(results[i].StartWgsY), parseFloat(results[i].StartWgsX)), aLocation(parseFloat(results[i].EndWgsY), parseFloat(results[i].EndWgsX))], map, color)
    };

}

//
function addCarsDataToGridLine(aLiveCarData) {
    var startLng = parseFloat(aLiveCarData.StartWgsY)
    var startLat = parseFloat(aLiveCarData.StartWgsX)
    var endLng = parseFloat(aLiveCarData.EndWgsY)
    var endLat = parseFloat(aLiveCarData.EndWgsX)
    classifyLine({
        lat: startLat,
        lng: startLng
    }, {
        lat: endLat,
        lng: endLng
    }, parseFloat(aLiveCarData.AvgOcc))
}

function drawConstructionSiteOnMap(constructionData, map) {

    for (var i = 0; i < constructionData.length; i++) {
        drawAMakerOnMap([constructionData[i].X, constructionData[i].Y], map, '施工地點', '/static/img/construction_small.png')
    };

}

function drawCrimeSiteOnMap(crimeData, map) {
    for (var i = 0; i < crimeData.length; i++) {
        drawAMakerOnMap([crimeData[i].lat, crimeData[i].lng], map, '常犯罪地點', '/static/img/danger_small.png')
    };

}

function drawParkOnMap(parkData, map) {
    for (var i = 0; i < parkData.length; i++) {
        drawAMakerOnMap([parkData[i].lat, parkData[i].lng], map, '公園', '')
    };
}

// ==== Get Live Data ====
function getDataFromServer() {
    console.log('get server data')
    $.get('http://localhost:3000/getdata', function(result) {
        // console.log(result)

        result = JSON.parse(result)

        var carsData = result.cars.data
        var crime = result.crime.data
        var park = result.park.data
        var constructions = result.constructions.data
        var uv = result.uv.data
        var airquality = result.airquality.data

        // console.log("carsData: " + carsData)

        // Draw all on map
        drawCarDataOnMap(carsData, map)
        drawConstructionSiteOnMap(constructions, map)
        drawCrimeSiteOnMap(crime, map)

        // Add to grid system
        for (var i = 0; i < constructions.length; i++) {
            addConstructionSiteToGrid(constructions[i])
        };

        for (var i = 0; i < crime.length; i++) {
            addCrimeSiteToGrid(crime[i])
        };

        for (var i = 0; i < carsData.length; i++) {
            addCarsDataToGridLine(carsData[i])
        };

        // console.log(JSON.stringify(grid))
    })
}

function gf(string) {
    return parseFloat(string)
}

// Type: Construction: 1, Crime: 2,
function addConstructionSiteToGrid(aConstruction) {
    classify(gf(aConstruction.X), gf(aConstruction.Y), 1)
}

function addCrimeSiteToGrid(aCrimeSiteData) {
    classify(gf(aCrimeSiteData.lat), gf(aCrimeSiteData.lng), 2)
}

function getBlockWithinDistance(distanceWithinAsKm) {

    directionsService = new google.maps.DirectionsService();
    directionsRender = new google.maps.DirectionsRenderer();

    var list = []

    var candidateLegs = []
    var currentHighestScore = 0

    for (var i = 0; i < cross_road_data.length; i++) {

        var itsDistance = calcCrow(userLocation.lat(), userLocation.lng(), parseFloat(cross_road_data[i][0]), parseFloat(cross_road_data[i][1]));

        if (itsDistance <= distanceWithinAsKm) {
            console.log('smaller')

            console.log()

            // drawAMakerOnMap([cross_road_data[i][0], cross_road_data[i][1]], map, 'Smaller', '')

            var request = {
                origin: aMapsLatLng(userLocation.lat(), userLocation.lng()),
                destination: aMapsLatLng(parseFloat(cross_road_data[i][0]), parseFloat(cross_road_data[i][1])),
                optimizeWaypoints: true,
                travelMode: google.maps.TravelMode.WALKING
            }

            directionsService.route(request, function(response, status) {
                if (status == google.maps.DirectionsStatus.OK) {
                    var sublist = [];

                    var allGridsForThisLeg = []

                    for (var j = 0; j < response.routes[0].legs[0].steps.length; j++) {

                        var startPoint = response.routes[0].legs[0].steps[j].start_point;
                        var endPoint = response.routes[0].legs[0].steps[j].end_point;

                        sublist.push({
                            start: startPoint,
                            end: endPoint
                        });

                        var legGrid = getGridsThrough({
                                "lat": startPoint.G,
                                "lng": startPoint.K
                            }, {
                                "lat": endPoint.G,
                                "lng": endPoint.K
                            })
                            // console.log("legGrid step" + j + ": " + JSON.stringify(legGrid))
                            // console.log(startPoint + endPoint)

                        allGridsForThisLeg = _.union(allGridsForThisLeg, legGrid)

                    }

                    var itsScore = calculateScoreFromAllGrids(allGridsForThisLeg);

                    var scoreAndLeg = {
                        score: itsScore,
                        leg: sublist,
                        polylines: []
                    }

                    if (itsScore >= currentHighestScore) {
                        currentHighestScore = itsScore
                        console.log('Current highest score: ' + currentHighestScore)
                            // Remove all polylines
                        for (var k = 0; k < candidateLegs.length; k++) {
                            for (var h = 0; h < candidateLegs[k].polylines.length; h++) {
                                candidateLegs[k].polylines[h].setMap(null);
                            };
                        };
                    }

                    // console.log("score: " + itsScore)
                    // directionsRender.setDirections(response)

                    // Create polylines
                    for (var k = 0; k < sublist.length; k++) {
                        sublist[k]

                        var thePath = [aLocation(sublist[k].start.G, sublist[k].start.K), aLocation(sublist[k].end.G, sublist[k].end.K)]

                        var lines = new google.maps.Polyline({
                            path: thePath,
                            geodesic: true,
                            strokeColor: "#7bacfa",
                            strokeOpacity: 1,
                            strokeWeight: 6
                        });

                        if (itsScore == currentHighestScore) {
                            lines.setMap(map);
                        } else {
                            lines.setMap(null);
                        }

                        scoreAndLeg.polylines.push(lines)
                    };

                    console.log(scoreAndLeg)

                    candidateLegs.push(scoreAndLeg)

                    // console.log("All grid: " + allGridsForThisLeg)

                    list.push(sublist);
                    // console.log(JSON.stringify(list))
                } else {
                    console.log('Google Maps route failed ' + status)
                }
            });
        }
    };

}

function calculateScoreFromAllGrids(allGrids) {
    var finalScore = 100;
    //console.log('grid:',grid);
    //console.log('allGrids',allGrids);
    for (var i = 0; i < allGrids.length; i++) {
        var key = allGrids[i];
        if (!(key in grid)) {
            //console.log('key not found in grid')
        } else {
            var event = grid[key]["event"];
            var traffic = grid[key]["traffic"];

            var type1Count = 0;
            var type2Count = 0;
            for (var j = 0; j < event.length; j++) {
                if (event[j]["type"] == '1') {
                    type1Count++
                } else {
                    type2Count++
                }
            };

            finalScore = finalScore - (3 * type2Count) - (1 * type1Count) - (3 * traffic)
        }

        var score = grid[key]
    };
    return finalScore
}

//This function takes in latitude and longitude of two location and returns the distance between them as the crow flies (in km)
function calcCrow(lat1, lon1, lat2, lon2) {
    var R = 6371; // km
    var dLat = toRad(lat2 - lat1);
    var dLon = toRad(lon2 - lon1);
    var lat1 = toRad(lat1);
    var lat2 = toRad(lat2);

    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
}

// Converts numeric degrees to radians
function toRad(Value) {
    return Value * Math.PI / 180;
}

// Button action
function changeRoute() {
    console.log('adfasdflllll')
}

<commit_msg>chagne route<commit_after>var map;
var userLocation;
var userMarker;

var candidateLegs = []
var currentHighestScore = 0

$(function() {
    console.log("Document is ready!");
});

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {
            lat: -34.397,
            lng: 150.644
        },
        zoom: 8
    });

    map.addListener('click', function(event) {
        console.log(event.latLng.lat() + ", " + event.latLng.lng())
    });

    map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(
        document.getElementById('alternative'));

    navigator.geolocation.getCurrentPosition(function(position) {
        console.log(position)

        // userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude)
        // userLocation = new google.maps.LatLng(25.015384309623297, 121.53891563415527)
        userLocation = new google.maps.LatLng(25.033661079435696, 121.51153564453125)

        map.setCenter(userLocation);
        map.setZoom(14);

        addUserMarker(userLocation, map);

        // Testing
        // calcRoute(userLocation, aMapsLatLng(25.1, 121.56), map)

        // Testing
        getBlockWithinDistance(1)

    });

    // Testing
    // drawLinesOnMap([aLocation(25.02, 121.534), aLocation(25.03, 121.534)], map)
    // drawCarDataOnMap(carData, map)

    // Testing
    // drawCrossRoadsMarkersOnMap(map)

    // Testing
    getDataFromServer()
}

function addUserMarker(location, map) {
    userMarker = new google.maps.Marker({
        map: map,
        position: location,
        title: "My Position"
    });
    // console.log(location, map);
}

// Helper --> {"lat":lat, "lng":lng}
function aLocation(lat, lng) {
    return {
        "lat": lat,
        "lng": lng
    }
}

// Helper (121, 25) --> google.maps.LatLng
function aMapsLatLng(lat, lng) {
    return new google.maps.LatLng(lat, lng)
}

// ==== Basic helper ====
function drawLinesOnMap(path, map, color) {
    var lines = new google.maps.Polyline({
        path: path,
        geodesic: true,
        strokeColor: color,
        strokeOpacity: 0.7,
        strokeWeight: 4
    });
    lines.setMap(map);
}

var directionsService;
var directionsRender;

function calcRoute(originLoc, destinationLoc, map) {

    directionsService = new google.maps.DirectionsService();
    directionsRender = new google.maps.DirectionsRenderer();
    directionsRender.setMap(map);

    var request = {
        origin: originLoc,
        destination: destinationLoc,
        optimizeWaypoints: true,
        travelMode: google.maps.TravelMode.WALKING
    }

    directionsService.route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
            directionsRender.setDirections(response);
        }
    });
}

function drawAMakerOnMap(coordsStringArray, map, title, image) {
    var latLng = aLocation(parseFloat(coordsStringArray[0]), parseFloat(coordsStringArray[1]));

    var marker = new google.maps.Marker({
        position: latLng,
        map: map,
        title: title,
        icon: image
    });

}

// ==== Draw events ====
function drawCrossRoadMarkerOnMap(coordsStringArray, map) {

    drawAMakerOnMap(coordsStringArray, map, '交叉路口', '/static/img/cir3.png')

}

function drawCrossRoadsMarkersOnMap(map) {

    for (var i = 0; i < cross_road_data.length; i++) {
        drawCrossRoadMarkerOnMap(cross_road_data[i], map)
    };

}

function drawCarDataOnMap(liveCarData, map) {

    var results = liveCarData;

    for (var i = 0; i < results.length; i++) {

        var color = "#3eb076" // green
        if (parseFloat(results[i].AvgOcc) > 7) {
            color = "#bb3d15"
        }

        drawLinesOnMap([aLocation(parseFloat(results[i].StartWgsY), parseFloat(results[i].StartWgsX)), aLocation(parseFloat(results[i].EndWgsY), parseFloat(results[i].EndWgsX))], map, color)
    };

}

//
function addCarsDataToGridLine(aLiveCarData) {
    var startLng = parseFloat(aLiveCarData.StartWgsY)
    var startLat = parseFloat(aLiveCarData.StartWgsX)
    var endLng = parseFloat(aLiveCarData.EndWgsY)
    var endLat = parseFloat(aLiveCarData.EndWgsX)
    classifyLine({
        lat: startLat,
        lng: startLng
    }, {
        lat: endLat,
        lng: endLng
    }, parseFloat(aLiveCarData.AvgOcc))
}

function drawConstructionSiteOnMap(constructionData, map) {

    for (var i = 0; i < constructionData.length; i++) {
        drawAMakerOnMap([constructionData[i].X, constructionData[i].Y], map, '施工地點', '/static/img/construction_small.png')
    };

}

function drawCrimeSiteOnMap(crimeData, map) {
    for (var i = 0; i < crimeData.length; i++) {
        drawAMakerOnMap([crimeData[i].lat, crimeData[i].lng], map, '常犯罪地點', '/static/img/danger_small.png')
    };

}

function drawParkOnMap(parkData, map) {
    for (var i = 0; i < parkData.length; i++) {
        drawAMakerOnMap([parkData[i].lat, parkData[i].lng], map, '公園', '')
    };
}

// ==== Get Live Data ====
function getDataFromServer() {
    console.log('get server data')
    $.get('http://localhost:3000/getdata', function(result) {
        // console.log(result)

        result = JSON.parse(result)

        var carsData = result.cars.data
        var crime = result.crime.data
        var park = result.park.data
        var constructions = result.constructions.data
        var uv = result.uv.data
        var airquality = result.airquality.data

        // console.log("carsData: " + carsData)

        // Draw all on map
        drawCarDataOnMap(carsData, map)
        drawConstructionSiteOnMap(constructions, map)
        drawCrimeSiteOnMap(crime, map)

        // Add to grid system
        for (var i = 0; i < constructions.length; i++) {
            addConstructionSiteToGrid(constructions[i])
        };

        for (var i = 0; i < crime.length; i++) {
            addCrimeSiteToGrid(crime[i])
        };

        for (var i = 0; i < carsData.length; i++) {
            addCarsDataToGridLine(carsData[i])
        };

        // console.log(JSON.stringify(grid))
    })
}

function gf(string) {
    return parseFloat(string)
}

// Type: Construction: 1, Crime: 2,
function addConstructionSiteToGrid(aConstruction) {
    classify(gf(aConstruction.X), gf(aConstruction.Y), 1)
}

function addCrimeSiteToGrid(aCrimeSiteData) {
    classify(gf(aCrimeSiteData.lat), gf(aCrimeSiteData.lng), 2)
}

function getBlockWithinDistance(distanceWithinAsKm) {

    directionsService = new google.maps.DirectionsService();
    directionsRender = new google.maps.DirectionsRenderer();

    var list = []

    for (var i = 0; i < cross_road_data.length; i++) {

        var itsDistance = calcCrow(userLocation.lat(), userLocation.lng(), parseFloat(cross_road_data[i][0]), parseFloat(cross_road_data[i][1]));

        if (itsDistance <= distanceWithinAsKm) {
            console.log('smaller')

            console.log()

            // drawAMakerOnMap([cross_road_data[i][0], cross_road_data[i][1]], map, 'Smaller', '')

            var request = {
                origin: aMapsLatLng(userLocation.lat(), userLocation.lng()),
                destination: aMapsLatLng(parseFloat(cross_road_data[i][0]), parseFloat(cross_road_data[i][1])),
                optimizeWaypoints: true,
                travelMode: google.maps.TravelMode.WALKING
            }

            directionsService.route(request, function(response, status) {
                if (status == google.maps.DirectionsStatus.OK) {
                    var sublist = [];

                    var allGridsForThisLeg = []

                    for (var j = 0; j < response.routes[0].legs[0].steps.length; j++) {

                        var startPoint = response.routes[0].legs[0].steps[j].start_point;
                        var endPoint = response.routes[0].legs[0].steps[j].end_point;

                        sublist.push({
                            start: startPoint,
                            end: endPoint
                        });

                        var legGrid = getGridsThrough({
                                "lat": startPoint.G,
                                "lng": startPoint.K
                            }, {
                                "lat": endPoint.G,
                                "lng": endPoint.K
                            })
                            // console.log("legGrid step" + j + ": " + JSON.stringify(legGrid))
                            // console.log(startPoint + endPoint)

                        allGridsForThisLeg = _.union(allGridsForThisLeg, legGrid)

                    }

                    var itsScore = calculateScoreFromAllGrids(allGridsForThisLeg);

                    var scoreAndLeg = {
                        score: itsScore,
                        leg: sublist,
                        polylines: []
                    }

                    if (itsScore >= currentHighestScore) {
                        currentHighestScore = itsScore
                        console.log('Current highest score: ' + currentHighestScore)
                            // Remove all polylines
                        for (var k = 0; k < candidateLegs.length; k++) {
                            for (var h = 0; h < candidateLegs[k].polylines.length; h++) {
                                candidateLegs[k].polylines[h].setMap(null);
                            };
                        };
                    }

                    // console.log("score: " + itsScore)
                    // directionsRender.setDirections(response)

                    // Create polylines
                    for (var k = 0; k < sublist.length; k++) {
                        sublist[k]

                        var thePath = [aLocation(sublist[k].start.G, sublist[k].start.K), aLocation(sublist[k].end.G, sublist[k].end.K)]

                        var lines = new google.maps.Polyline({
                            path: thePath,
                            geodesic: true,
                            strokeColor: "#7bacfa",
                            strokeOpacity: 1,
                            strokeWeight: 6
                        });

                        if (itsScore == currentHighestScore) {
                            lines.setMap(map);
                        } else {
                            lines.setMap(null);
                        }

                        scoreAndLeg.polylines.push(lines)
                    };

                    console.log(scoreAndLeg)

                    candidateLegs.push(scoreAndLeg)

                    // console.log("All grid: " + allGridsForThisLeg)

                    list.push(sublist);
                    // console.log(JSON.stringify(list))
                } else {
                    console.log('Google Maps route failed ' + status)
                }
            });
        }
    };

}

function calculateScoreFromAllGrids(allGrids) {
    var finalScore = 100;
    //console.log('grid:',grid);
    //console.log('allGrids',allGrids);
    for (var i = 0; i < allGrids.length; i++) {
        var key = allGrids[i];
        if (!(key in grid)) {
            //console.log('key not found in grid')
        } else {
            var event = grid[key]["event"];
            var traffic = grid[key]["traffic"];

            var type1Count = 0;
            var type2Count = 0;
            for (var j = 0; j < event.length; j++) {
                if (event[j]["type"] == '1') {
                    type1Count++
                } else {
                    type2Count++
                }
            };

            finalScore = finalScore - (3 * type2Count) - (1 * type1Count) - (3 * traffic)
        }

        var score = grid[key]
    };
    return finalScore
}

//This function takes in latitude and longitude of two location and returns the distance between them as the crow flies (in km)
function calcCrow(lat1, lon1, lat2, lon2) {
    var R = 6371; // km
    var dLat = toRad(lat2 - lat1);
    var dLon = toRad(lon2 - lon1);
    var lat1 = toRad(lat1);
    var lat2 = toRad(lat2);

    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
}

// Converts numeric degrees to radians
function toRad(Value) {
    return Value * Math.PI / 180;
}

// Button action
var goodLegsIndexes = []
var currentLegIndex;

function changeRoute() {
    console.log('Change route');

    goodLegsIndexes = []

    for (var i = 0; i < candidateLegs.length; i++) {
        if (candidateLegs[i].score == 100) {
            goodLegsIndexes.push(i)
        }

        for (var j = 0; j < candidateLegs[i].polylines.length; j++) {
            if (candidateLegs[i].polylines[j].map != null) {
                currentLegIndex = i
                break;
            }
        }
    };

    console.log("hahasf: " + goodLegsIndexes + "+ " + currentLegIndex)

    for (var i = 0; i < candidateLegs[currentLegIndex].polylines.length; i++) {
        candidateLegs[currentLegIndex].polylines[i].setMap(null)
    };

    for (var i = 0; i < goodLegsIndexes.length; i++) {
        if (goodLegsIndexes[i] != currentLegIndex) {
            for (var j = 0; j < candidateLegs[i].polylines.length; j++) {
                candidateLegs[i].polylines[j].setMap(map)
            };
            break;
        }
    };

    // for (var j = 0; j < candidateLegs[i].polylines.length; j++) {

    //                 candidateLegs[i].polylines[j].setMap(null)
    //             } else {
    //                 candidateLegs[i].polylines[j].setMap(map)
    //             }
    //         };

}<|endoftext|>


<commit_before>var pathToEmotions = chrome.extension.getURL('emotions/');
var pathToIcons = chrome.extension.getURL('icons/');

var recentEmotions = [];
var recentEmotionsCompare= [];

//chrome.storage.sync.clear();
var elementsCount = 0;
var clickCount = 1;
var visited = false;
var emotes = new Array();

var listeningClasses = ['im_msg_text', 'im_editable', 'dialogs_msg_text', 'wall_reply_text', 'wall_post_text', 'fc_msg'];

//parsing emotes json
var xhr = new XMLHttpRequest();
var twitchEmotes = pathToEmotions + "emotions.json";
xhr.open('GET', twitchEmotes);
xhr.send();
xhr.onload = function() {
  var data = JSON.parse(xhr.responseText);
	var emotions = data.defaultTwitch;
	var subEmotions = data.subTwitch;
	var subEmotionsArray = new Array();

  for (var subKeyWord in subEmotions) //pushing channels names into array
		subEmotionsArray.push(subKeyWord);

  get_emotes();
  emotionsUI(emotions, subEmotions, subEmotionsArray);//sending all info about emotes to create emotes window in dialogs

  function get_emotes(){
    for (var keyWord in emotions) //for every usual twitch emote
      emotes[keyWord] = {name:keyWord, id:emotions[keyWord].id};// replace keyword with image

      for (var z = 0; z<subEmotionsArray.length; z++){ //for every sub twitch emote
        var s = subEmotionsArray[z];
        var e = subEmotions[s].emotes;
        for (var y = 0; y<e.length; y++)
          emotes[e[y].name] = {name:e[y].name, id:e[y].id}
      }
    doReplace();
  }

}

function doReplace(){
  static_replace(); //static replace works only after page load
  dynamic_replace();//dynamic replace works with new dom elements
}

function static_replace(){
  for (var l = 0; l<listeningClasses.length; l++){
    var element = document.getElementsByClassName(listeningClasses[l]);
    var length = element.length;
    if (element){
      for (var i = 0; i<length; i++){
        for (key in emotes){  //for every  twitch emote
          if ((wordInString(element[i], key)) === true) //if we found emote keyword  in text
            replaceEmotions(element[i]);// replace keyword with image
        }
      }
    }
  }
}

function dynamic_replace(){
  for (var i = 0; i<listeningClasses.length; i++){ //for every class where we replace emotes
    document.arrive(('.'+listeningClasses[i]), function(event){ // arrive - little lib to help with Mutation observers coz im lazy
      var elem = this;
      for (key in emotes){
        if ((wordInString(elem, key)) === true)
          replaceEmotions(elem);
      }
    });
  }
}

function wordInString(s, word){
  var pureS = s.innerHTML;
  var check = new RegExp(word + '\\b', 'm').test(pureS);
  if (check === true)
    return true;
  return false;
}

function replaceEmotions(element){
  var img = "";
  var tempRegEx = new RegExp(key+"\\b", "g");
  element.innerHTML = element.innerHTML.replace(tempRegEx, img);
}

function emotionsUI(emotions, subEmotions, subEmotionsArray){
var currentEmotionPosition;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {

	var currentUrl = request.data.url;
	sendResponse({response: "got it"});

	var idPattern = /(?:sel)=(\d.+)\s*/;
	var idConversationPattern = /(?:sel)=c(.+)\s*/
	var urlPattern = /https?:\/\/vk.com\/.[im][?]([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g;
	var ignoreNumbersPattern = /(\.)(.*)$/m;

	if (currentUrl.match(idPattern)){
		var currentPageID = currentUrl.match(idPattern)[1];
		var chatboxClass = $('#im_editable'+currentPageID);
	}

	if (currentUrl.match(idConversationPattern)){
		var currentPageID = currentUrl.match(idConversationPattern)[1];
		var chatboxClass = $('#im_editable20000000'+currentPageID);
	}

	var currentPageUrl = currentUrl.match(urlPattern);
	var emotionsPosition = $('#im_peer_holders');
	var value = 0;

	if (currentPageUrl == currentUrl)
		insertEmotions(currentPageID);

	if ((currentPageID != null) && (visited === false )) { // fixing scroll bug
		$('html, body').animate({scrollTop: $(document).height()}, 'fast');
		visited = true;
	}

	function insertEmotions(pageID){
		if (currentEmotionPosition !=null){
			$('.emotionsHub').remove();
		}
		var emotionHTMLLook = "
"+
									""+
										"
"+
											"
"+
												"

"+
												"

"+
												"

"+
											"
"+
											"
"+
												"

"+
												"

"+
												"

"+
											"
"+
										"
"+
								    "
";
		currentEmotionPosition = emotionsPosition.after(emotionHTMLLook);
		appendEmotionsToContainer();
		appendRecentToContainer();
	}

	function appendEmotionsToContainer(){
		var allEmotionsContainer = $('#allEmotions');
		var subEmotionsContainer = $('.tab-content .subSection');

		for (var keyWord in emotions){
			allEmotionsContainer.append("

");
			elementToClick(keyWord);
			createRecent(keyWord,emotions[keyWord].id);
		}
		for (var subKeyWord in subEmotions){
			subEmotionsContainer.append("
"+subKeyWord+"

");
			value++;
		}

		for (var i = 0; i<subEmotionsArray.length; i++){
			var s = subEmotionsArray[i];
			var e = subEmotions[s].emotes;
			for (var y = 0; y<e.length; y++){
				$('#'+s).append("

");
				elementToClick(e[y].name);
				createRecent(e[y].name, e[y].id);
			}
		}
	}

	function appendRecentToContainer(){
		chrome.storage.sync.get(null, function(result){
			if (result.storedRecentEmotions != null){
				chrome.storage.sync.set({ "storedElementsCount": sElementsCount});
				var sRecentEmotions = result.storedRecentEmotions;
				var sElementsCount = result.storedElementsCount;
				sRecentEmotions.sort(function(a,b) {
					return parseFloat(b.key) - parseFloat(a.key)
				});
				for (var i = 0; i < sRecentEmotions.length; i++){
					var KeyWord = sRecentEmotions[i].key;
					var cleanKeyWord = KeyWord.match(ignoreNumbersPattern)[2];
					var img = sRecentEmotions[i].value;
					$('#recentEmotions').append("

");
					elementToClick(cleanKeyWord);
				}
			}
		});
	}

	function createRecent(elt, image){
		var recentKey = elementsCount+"."+elt;
		$('#'+elt).click(function(){
			chrome.storage.sync.get(null, function(result){
				var storedRecentEmotions = result.storedRecentEmotions;
				var storedElementsCount = result.storedElementsCount;
				var recentKeyNew = storedElementsCount+"."+elt;
				var simpleKey = recentKey.match(ignoreNumbersPattern)[2];
				var containerLimit = 25;
				if (storedRecentEmotions != null){
					for (var i = 0; i < storedRecentEmotions.length; i++){
						var KeyWord = storedRecentEmotions[i].key;
						var cleanKeyWord = KeyWord.match(ignoreNumbersPattern)[2];
						recentEmotionsCompare.push(cleanKeyWord);
					}
					var positionInArray = $.inArray(simpleKey, recentEmotionsCompare);
					if (positionInArray === -1){
						recentEmotionsCompare = [];
						storedElementsCount++;
						if (storedElementsCount>containerLimit){
							recentKeyNew = storedElementsCount+"."+elt;
							storedRecentEmotions.splice(0, 1);
							storedRecentEmotions.push({
								key: recentKeyNew,
								value: image
							});
							chrome.storage.sync.set({"storedRecentEmotions": storedRecentEmotions, "storedElementsCount": storedElementsCount});
						} else {
							storedRecentEmotions.push({
								key: recentKeyNew,
								value: image
							});
							chrome.storage.sync.set({"storedRecentEmotions": storedRecentEmotions, "storedElementsCount": storedElementsCount});
						}
					}
					appendRecentToContainer();
				} else {
					recentEmotions.push({
							key: recentKey,
							value: image
					});
					recentEmotionsCompare.push(simpleKey);
					elementsCount++;
					chrome.storage.sync.set({"storedRecentEmotions": recentEmotions, "storedElementsCount": elementsCount});
					appendRecentToContainer();
				}
			});
		$('#recentEmotions').empty();
		});
	}

	function elementToClick(element){
		$('#'+element).click(function(){
			$('.input_back_wrap').css('display', 'none');
			chatboxClass.append(element+' ');
		});
	}

	$('.emotionsHub').hover(
	function () {
		$(this).css('cursor', 'pointer');
		$(this).find("div.emotionsContainer").stop(true,false).delay('50').fadeIn();
	},
	function () {
		$(this).find("div.emotionsContainer").delay('250').fadeOut();
	});

	$('.tabs-menu li').click(function(e) {
		e.preventDefault();
		$(this).addClass("current");
		$(this).siblings().removeClass("current");
		var tab = $(this).find('a').attr("href");
		$(".tab-content").not(tab).css("display", "none");
		$(tab).fadeIn();
	});

	$('.tab-content .subSection li').click(function(e) {
		e.preventDefault();
		var element = $(this).find('a').attr("href");
		if (($(this).attr("class"))=== "current"){
			$(this).removeClass("current");
			$(element).stop().fadeTo(300,0, function() {$(this).hide()});
		} else {
			var v = $(this).val();
			var position = v * 25;
			$(this).addClass("current");
			$(this).siblings().removeClass("current");

			$(".subSectionContent").not(element).css("display", "none");
			$(element).stop().show().fadeTo(300,1);
			$('.tab-content').animate({
				scrollTop: position
			}, 'slow');
		}
	});

	$('.tab-content').bind( 'mousewheel DOMMouseScroll', function (e) {
		var e0 = e.originalEvent,
		delta = e0.wheelDelta || -e0.detail;
		this.scrollTop += ( delta < 0 ? 1 : -1 ) * 30;
		e.preventDefault();

	});

});
}

emotionInfo('.vKappaEmotion');
emotionInfo('.col');

function emotionInfo(selector){
	$(document).on('mouseenter', selector, function(){
		var y = $(this).height();
		var p = $(this).position();
		if (selector === '.vKappaEmotion'){
			var eN = $(this).attr("ename");
			var x = $(this).width()/2;
			var t = p.top+y;
			var l = p.left+x;
		} else {
			var eN = $(this).attr("id");
			var x = $(this).width();
			var t = p.top+48;
			var l = p.left+25;
		}
		$(this).after("
" + eN + "
");
		$('.eInfo').delay(500).queue(function(){
			var temp = ($(this).width()/2)+5;
			$(this).css("margin-left", "-"+temp+"px");
			$(this).css("display","inline-block").dequeue();
		});
	});

	$(document).on('mouseleave', selector, function(){
		$('.eInfo').remove();
	});
}
<commit_msg>Delete content.js<commit_after><|endoftext|>


<commit_before><commit_msg>have users<commit_after><|endoftext|>


<commit_before>const path = require('path');
const errors = require('./index');

const defaults = {
  public: path.resolve(__dirname, 'public'),
  logger: console
};
const defaultHtmlError = path.resolve(defaults.public, 'default.html');

module.exports = function (options = {}) {
  options = Object.assign({}, defaults, options);

  if (typeof options.html === 'undefined') {
    options.html = {
      401: path.resolve(options.public, '401.html'),
      404: path.resolve(options.public, '404.html'),
      default: defaultHtmlError
    };
  }

  if (typeof options.json === 'undefined') {
    options.json = {};
  }

  return function (error, req, res, next) {
    // Log the error if it didn't come from a service method call
    if (options.logger && typeof options.logger.error === 'function' && !res.hook) {
      options.logger.error(error);
    }

    if (error.type !== 'FeathersError') {
      let oldError = error;
      error = new errors.GeneralError(oldError.message, {
        errors: oldError.errors
      });

      if (oldError.stack) {
        error.stack = oldError.stack;
      }
    }

    error.code = !isNaN(parseInt(error.code, 10)) ? parseInt(error.code, 10) : 500;
    const formatter = {};

    // If the developer passed a custom function for ALL html errors
    if (typeof options.html === 'function') {
      formatter['text/html'] = options.html;
    } else {
      let file = options.html[error.code];
      if (!file) {
        file = options.html.default || defaultHtmlError;
      }
      // If the developer passed a custom function for individual html errors
      if (typeof file === 'function') {
        formatter['text/html'] = file;
      } else {
        formatter['text/html'] = function () {
          res.set('Content-Type', 'text/html');
          res.sendFile(file);
        };
      }
    }

    // If the developer passed a custom function for ALL json errors
    if (typeof options.json === 'function') {
      formatter['application/json'] = options.json;
    } else {
      let handler = options.json[error.code] || options.json.default;
      // If the developer passed a custom function for individual json errors
      if (typeof handler === 'function') {
        formatter['application/json'] = handler;
      } else {
        // Don't show stack trace if it is a 404 error
        if (error.code === 404) {
          error.stack = null;
        }

        formatter['application/json'] = function () {
          let output = Object.assign({}, error.toJSON());

          if (process.env.NODE_ENV === 'production') {
            delete output.stack;
          }

          res.set('Content-Type', 'application/json');
          res.json(output);
        };
      }
    }

    res.status(error.code);

    const contentType = req.headers['content-type'] || '';
    const accepts = req.headers.accept || '';

    // by default just send back json
    if (contentType.indexOf('json') !== -1 || accepts.indexOf('json') !== -1) {
      formatter['application/json'](error, req, res, next);
    } else if (options.html && (contentType.indexOf('html') !== -1 || accepts.indexOf('html') !== -1)) {
      formatter['text/html'](error, req, res, next);
    } else {
      // TODO (EK): Maybe just return plain text
      formatter['application/json'](error, req, res, next);
    }
  };
};
<commit_msg>fix: Do not log as errors below a 500 response (#1256)<commit_after>const path = require('path');
const errors = require('./index');

const defaults = {
  public: path.resolve(__dirname, 'public'),
  logger: console
};
const defaultHtmlError = path.resolve(defaults.public, 'default.html');

module.exports = function (options = {}) {
  options = Object.assign({}, defaults, options);

  if (typeof options.html === 'undefined') {
    options.html = {
      401: path.resolve(options.public, '401.html'),
      404: path.resolve(options.public, '404.html'),
      default: defaultHtmlError
    };
  }

  if (typeof options.json === 'undefined') {
    options.json = {};
  }

  return function (error, req, res, next) {
    // Set the error code for HTTP processing semantics
    error.code = !isNaN(parseInt(error.code, 10)) ? parseInt(error.code, 10) : 500;

    // Log the error if it didn't come from a service method call
    if (options.logger && typeof options.logger.error === 'function' && !res.hook) {
      if (error.code >= 500) {
        options.logger.error(error);
      } else {
        options.logger.info(error);
      }
    }

    if (error.type !== 'FeathersError') {
      let oldError = error;
      error = new errors.GeneralError(oldError.message, {
        errors: oldError.errors
      });

      if (oldError.stack) {
        error.stack = oldError.stack;
      }
    }

    const formatter = {};

    // If the developer passed a custom function for ALL html errors
    if (typeof options.html === 'function') {
      formatter['text/html'] = options.html;
    } else {
      let file = options.html[error.code];
      if (!file) {
        file = options.html.default || defaultHtmlError;
      }
      // If the developer passed a custom function for individual html errors
      if (typeof file === 'function') {
        formatter['text/html'] = file;
      } else {
        formatter['text/html'] = function () {
          res.set('Content-Type', 'text/html');
          res.sendFile(file);
        };
      }
    }

    // If the developer passed a custom function for ALL json errors
    if (typeof options.json === 'function') {
      formatter['application/json'] = options.json;
    } else {
      let handler = options.json[error.code] || options.json.default;
      // If the developer passed a custom function for individual json errors
      if (typeof handler === 'function') {
        formatter['application/json'] = handler;
      } else {
        // Don't show stack trace if it is a 404 error
        if (error.code === 404) {
          error.stack = null;
        }

        formatter['application/json'] = function () {
          let output = Object.assign({}, error.toJSON());

          if (process.env.NODE_ENV === 'production') {
            delete output.stack;
          }

          res.set('Content-Type', 'application/json');
          res.json(output);
        };
      }
    }

    res.status(error.code);

    const contentType = req.headers['content-type'] || '';
    const accepts = req.headers.accept || '';

    // by default just send back json
    if (contentType.indexOf('json') !== -1 || accepts.indexOf('json') !== -1) {
      formatter['application/json'](error, req, res, next);
    } else if (options.html && (contentType.indexOf('html') !== -1 || accepts.indexOf('html') !== -1)) {
      formatter['text/html'](error, req, res, next);
    } else {
      // TODO (EK): Maybe just return plain text
      formatter['application/json'](error, req, res, next);
    }
  };
};
<|endoftext|>


<commit_before>const { iff } = require('feathers-hooks-common')
import { isNil, all } from 'ramda'

module.exports = function () {
  const app = this
  app.service('credentials').hooks(hooks)
}

const hooks = {
  after: {
    create: [
      createTaskPlan,
      iff(hasNoAuthorization, createPatchCredentialsTokenAndInviteMail)
    ]
  }
}

function createTaskPlan (hook) {
  const { agentId } = hook.result
  return hook.app.service('taskPlans').create({
    assigneeId: agentId,
    taskRecipeId: 'createProfile',
    params: {}
  })
  .then(() => hook)
}

function hasNoAuthorization (hook) {
  const { password } = hook.data
  const { googleId, facebookId, githubId, twitterId } = hook.result
  return all(isNil)([password, googleId, facebookId, githubId, twitterId])
}

function createPatchCredentialsTokenAndInviteMail (hook) {
  const { id, agentId, email } = hook.result
  return hook.app.service('tokens').create({
    agentId,
    service: 'credentials',
    method: 'patch',
    params: { serviceId: id },
    createdAt: new Date().toString()
  })
  .then((token) => {
    return hook.app.service('mailer').create({
      from: 'hello@cobuy.nz',
      to: email || 'no@email.com',
      subject: `You're invited to join Cobuy!`,
      html: `
        Hi -name-. You've been invited by -admin- to join -group- on Cobuy! Click here to set your password and start co-buying!
      `
    })
  })
  .then(() => hook)
}
<commit_msg>fix(tokens): let database default createdAt, otherwise is breaking on production postgres<commit_after>const { iff } = require('feathers-hooks-common')
import { isNil, all } from 'ramda'

module.exports = function () {
  const app = this
  app.service('credentials').hooks(hooks)
}

const hooks = {
  after: {
    create: [
      createTaskPlan,
      iff(hasNoAuthorization, createPatchCredentialsTokenAndInviteMail)
    ]
  }
}

function createTaskPlan (hook) {
  const { agentId } = hook.result
  return hook.app.service('taskPlans').create({
    assigneeId: agentId,
    taskRecipeId: 'createProfile',
    params: {}
  })
  .then(() => hook)
}

function hasNoAuthorization (hook) {
  const { password } = hook.data
  const { googleId, facebookId, githubId, twitterId } = hook.result
  return all(isNil)([password, googleId, facebookId, githubId, twitterId])
}

function createPatchCredentialsTokenAndInviteMail (hook) {
  const { id, agentId, email } = hook.result
  return hook.app.service('tokens').create({
    agentId,
    service: 'credentials',
    method: 'patch',
    params: { serviceId: id }
  })
  .then((token) => {
    return hook.app.service('mailer').create({
      from: 'hello@cobuy.nz',
      to: email || 'no@email.com',
      subject: `You're invited to join Cobuy!`,
      html: `
        Hi -name-. You've been invited by -admin- to join -group- on Cobuy! Click here to set your password and start co-buying!
      `
    })
  })
  .then(() => hook)
}
<|endoftext|>


<commit_before>const deleteButton = document.querySelector('.delete-circle');
const searchInput = document.querySelector('.search');
const tabList = document.querySelector('.tab-list');

function initializeTabs() {
  const tabs = browser.tabs.query({ currentWindow: true });
  return () => tabs;
}

// Store all the bad favIcons so we don't get loading jank if a favIcon !exist
function badFavIconCache() {
  const badIconCache = [];
  return () => badIconCache;
}

const badFavIcons = badFavIconCache();
const getAllTabs = initializeTabs();

// Initialize tabs
populateTabList();

// Set a timeout on focus so input is focused on popup
document.addEventListener('DOMContentLoaded', () =>
  setTimeout(() => searchInput.focus(), 150)
);
// Any (non-navigating) keydown should activate the search field,
window.addEventListener('keydown', handleKeyDown);
searchInput.addEventListener('change', updateSearch);
searchInput.addEventListener('keyup', updateSearch);
deleteButton.addEventListener('click', clearInput);

function handleKeyDown(event) {
  switch (event.key) {
    case "Tab":
    case "ArrowDown":
    case "ArrowUp":
      event.preventDefault();
    case "ArrowRight":
    case "ArrowLeft":
      navigateResults(event.key);
      break;
    case "Enter":
      if (document.activeElement.nodeName === "INPUT") {
        tabList.querySelector('.tab-object').focus();
      }
      switchActiveTab(document.activeElement.dataset.id);
      break;
    case "Escape":
      // This only works in chrome, in firefox it always closes the window
      if (searchInput.value.length === 0) {
        window.close();
      } else {
        clearInput();
      }
      break;
    default:
      searchInput.focus();
      break;
  }
}

function navigateResults(direction) {
  if (document.activeElement.nodeName === 'INPUT') {
    document.querySelector('.tab-object').focus();
    return;
  }

  switch (direction) {
    case 'Tab':
    case 'ArrowRight':
    case 'ArrowDown':
      const nextSibling = document.activeElement.nextElementSibling;
      if (nextSibling) {
        nextSibling.focus();
      } else {
        // Return to top if next !exist
        tabList.querySelector('.tab-object').focus();
      }
      break;
    case 'ArrowLeft':
    case 'ArrowUp':
      const prevSibling = document.activeElement.previousElementSibling;
      if (prevSibling) {
        prevSibling.focus()
      } else {
        searchInput.focus();
      };
      break;
  }
}

function updateSearch(event) {
  // If input is empty hide the button
  if (searchInput.value.length === 0) {
    deleteButton.classList.add('hidden');
  } else {
    deleteButton.classList.remove('hidden');
  }

  const query = event.target.value.toLowerCase();
  const tabFilter = (tab) => {
    // Check if tab has the query in title, url
    const queryInTitle = tab.title.toLowerCase().includes(query);
    const queryInUrl = tab.url.toLowerCase().includes(query);
    return (queryInTitle || queryInUrl);
  };
  return getAllTabs()
    .then(tabArray => tabArray.filter(tabFilter))
    .then(injectTabsInList);
}

function injectTabsInList(tabArray) {
  const wasNoResult = tabList.querySelectorAll('.tab-object').length === 0;
  const showNoResult = tabArray.length === 0;
  // Don't update dom if we're going to show no results again
  if (wasNoResult && showNoResult) return;

  clearChildren(tabList);

  // Add nodes to tabList & attach event listeners
  if (showNoResult) {
    tabList.appendChild(noResultNode());
  } else {
    tabArray.map(tabToTag).forEach(tabNode => {
      tabNode.addEventListener('click', switchTabs, true);
      tabNode.addEventListener('focus', scrollIfNeeded);
      tabList.appendChild(tabNode);
    });
  }
}

function tabToTag({ favIconUrl, title, id, url }) {
  const fallBackSvg = '/assets/file.svg';
  const favIconLink = favIconUrl
    && !isChromeLink(favIconUrl)
    && !badFavIcons().includes(favIconUrl)
      ? favIconUrl
      : fallBackSvg;
  // Just check if it's a url for now so we can shorten it
  const tabTitle = isURL(title)
    ? getBasePath(title)
    : title;

  // Create the parent div
  //

  const tabObjectNode = document.createElement('div');
  tabObjectNode.setAttribute('data-id', id);
  tabObjectNode.setAttribute('tabindex', '0');
  tabObjectNode.classList.add('tab-object');

  // favicon
  //
  const favIconNode = document.createElement('img');
  favIconNode.setAttribute('src', favIconLink);
  favIconNode.onerror = function(event) {
    badFavIcons().push(this.src);
    this.src = fallBackSvg;
  };

  // tab-info
  //

  const tabInfoNode = document.createElement('div');
  tabInfoNode.setAttribute('class', 'tab-info');

  //      ${title}
  const titleNode = document.createElement('strong');
  titleNode.classList.add('tab-title');
  titleNode.appendChild(document.createTextNode(tabTitle));

  //
${url}

  const urlNode = document.createElement('p');
  urlNode.appendChild(document.createTextNode(url));

  // Append all block elements
  tabInfoNode.appendChild(titleNode);
  tabInfoNode.appendChild(urlNode);

  tabObjectNode.appendChild(favIconNode);
  tabObjectNode.appendChild(tabInfoNode);

  return tabObjectNode;
}

function scrollIfNeeded(event) {
  // If the selected tab isn't completely visible in the scrolled view,
  // force scroll
  const {
    parentHeight,
    selectedBottom,
    selectedTop,
  } = getNodePositions(event.target.parentNode, event.target);
  const shouldScrollDown = selectedBottom > parentHeight;
  const shouldScrollUp = selectedTop < 0;

  if (!shouldScrollDown && !shouldScrollUp) return;

  if (shouldScrollDown) {
    event.target.scrollIntoView(false);
  } else if (shouldScrollUp) {
    event.target.scrollIntoView(true);
  }
}

// Get focused node's position relative to the current scrolled view
function getNodePositions(parentNode, selectedNode) {
  return {
    parentHeight: parentNode.offsetHeight,
    selectedBottom: selectedNode.offsetHeight + (
      selectedNode.offsetTop - parentNode.offsetTop
    ) - parentNode.scrollTop,
    selectedTop: (
      selectedNode.offsetTop - parentNode.offsetTop
    ) - parentNode.scrollTop,
  };
}

function noResultNode() {
  //

  //   <object type="image/svg+xml" data="/assets/alert-circle.svg"></object>
  //   No tabs found
  //

  const noResultNode = document.createElement('div');
  noResultNode.classList.add('no-result');

  const alertCircleNode = document.createElement('object');
  alertCircleNode.setAttribute('type', 'image/svg+xml');
  alertCircleNode.setAttribute('data', '/assets/alert-circle.svg');

  const strongMsgNode = document.createElement('strong');
  strongMsgNode.appendChild(document.createTextNode('No tabs found'));

  noResultNode.appendChild(alertCircleNode);
  noResultNode.appendChild(strongMsgNode);
  return noResultNode;
}

function clearChildren(node) {
  while (node.firstChild) {
    node.removeChild(node.firstChild);
  };
}

function shortenString(str) {
  return str.length >= 120
    ? str.slice(0, 90) + '...'
    : str;
}

function isChromeLink(src) {
  return /chrome:\/\//.test(src);
}

function isURL(src) {
  return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.test(src);
}

function getBasePath(src) {
  const url = new URL(src);
  return url.origin;
}

function switchTabs() {
  // this: should be the tab-object node
  const tabId = this.dataset.id;
  switchActiveTab(tabId);
}

function switchActiveTab(id) {
  const numId = parseInt(id);
  browser.tabs.update(numId, { active: true });
  window.close();
}

function clearInput() {
  searchInput.value = '';
  populateTabList();
}

function populateTabList() {
  getAllTabs().then(injectTabsInList);
}

function focusNode(node) {
  node.focus();
}
<commit_msg>Destructure `tabFilter`<commit_after>const deleteButton = document.querySelector('.delete-circle');
const searchInput = document.querySelector('.search');
const tabList = document.querySelector('.tab-list');

function initializeTabs() {
  const tabs = browser.tabs.query({ currentWindow: true });
  return () => tabs;
}

// Store all the bad favIcons so we don't get loading jank if a favIcon !exist
function badFavIconCache() {
  const badIconCache = [];
  return () => badIconCache;
}

const badFavIcons = badFavIconCache();
const getAllTabs = initializeTabs();

// Initialize tabs
populateTabList();

// Set a timeout on focus so input is focused on popup
document.addEventListener('DOMContentLoaded', () =>
  setTimeout(() => searchInput.focus(), 150)
);
// Any (non-navigating) keydown should activate the search field,
window.addEventListener('keydown', handleKeyDown);
searchInput.addEventListener('change', updateSearch);
searchInput.addEventListener('keyup', updateSearch);
deleteButton.addEventListener('click', clearInput);

function handleKeyDown(event) {
  switch (event.key) {
    case "Tab":
    case "ArrowDown":
    case "ArrowUp":
      event.preventDefault();
    case "ArrowRight":
    case "ArrowLeft":
      navigateResults(event.key);
      break;
    case "Enter":
      if (document.activeElement.nodeName === "INPUT") {
        tabList.querySelector('.tab-object').focus();
      }
      switchActiveTab(document.activeElement.dataset.id);
      break;
    case "Escape":
      // This only works in chrome, in firefox it always closes the window
      if (searchInput.value.length === 0) {
        window.close();
      } else {
        clearInput();
      }
      break;
    default:
      searchInput.focus();
      break;
  }
}

function navigateResults(direction) {
  if (document.activeElement.nodeName === 'INPUT') {
    document.querySelector('.tab-object').focus();
    return;
  }

  switch (direction) {
    case 'Tab':
    case 'ArrowRight':
    case 'ArrowDown':
      const nextSibling = document.activeElement.nextElementSibling;
      if (nextSibling) {
        nextSibling.focus();
      } else {
        // Return to top if next !exist
        tabList.querySelector('.tab-object').focus();
      }
      break;
    case 'ArrowLeft':
    case 'ArrowUp':
      const prevSibling = document.activeElement.previousElementSibling;
      if (prevSibling) {
        prevSibling.focus()
      } else {
        searchInput.focus();
      };
      break;
  }
}

function updateSearch(event) {
  // If input is empty hide the button
  if (searchInput.value.length === 0) {
    deleteButton.classList.add('hidden');
  } else {
    deleteButton.classList.remove('hidden');
  }

  const query = event.target.value.toLowerCase();
  const tabFilter = ({ title, url }) => {
    // Check if tab has the query in title, url
    const queryInTitle = title.toLowerCase().includes(query);
    const queryInUrl = url.toLowerCase().includes(query);
    return (queryInTitle || queryInUrl);
  };
  return getAllTabs()
    .then(tabArray => tabArray.filter(tabFilter))
    .then(injectTabsInList);
}

function injectTabsInList(tabArray) {
  const wasNoResult = tabList.querySelectorAll('.tab-object').length === 0;
  const showNoResult = tabArray.length === 0;
  // Don't update dom if we're going to show no results again
  if (wasNoResult && showNoResult) return;

  clearChildren(tabList);

  // Add nodes to tabList & attach event listeners
  if (showNoResult) {
    tabList.appendChild(noResultNode());
  } else {
    tabArray.map(tabToTag).forEach(tabNode => {
      tabNode.addEventListener('click', switchTabs, true);
      tabNode.addEventListener('focus', scrollIfNeeded);
      tabList.appendChild(tabNode);
    });
  }
}

function tabToTag({ favIconUrl, title, id, url }) {
  const fallBackSvg = '/assets/file.svg';
  const favIconLink = favIconUrl
    && !isChromeLink(favIconUrl)
    && !badFavIcons().includes(favIconUrl)
      ? favIconUrl
      : fallBackSvg;
  // Just check if it's a url for now so we can shorten it
  const tabTitle = isURL(title)
    ? getBasePath(title)
    : title;

  // Create the parent div
  //

  const tabObjectNode = document.createElement('div');
  tabObjectNode.setAttribute('data-id', id);
  tabObjectNode.setAttribute('tabindex', '0');
  tabObjectNode.classList.add('tab-object');

  // favicon
  //
  const favIconNode = document.createElement('img');
  favIconNode.setAttribute('src', favIconLink);
  favIconNode.onerror = function(event) {
    badFavIcons().push(this.src);
    this.src = fallBackSvg;
  };

  // tab-info
  //

  const tabInfoNode = document.createElement('div');
  tabInfoNode.setAttribute('class', 'tab-info');

  //      ${title}
  const titleNode = document.createElement('strong');
  titleNode.classList.add('tab-title');
  titleNode.appendChild(document.createTextNode(tabTitle));

  //
${url}

  const urlNode = document.createElement('p');
  urlNode.appendChild(document.createTextNode(url));

  // Append all block elements
  tabInfoNode.appendChild(titleNode);
  tabInfoNode.appendChild(urlNode);

  tabObjectNode.appendChild(favIconNode);
  tabObjectNode.appendChild(tabInfoNode);

  return tabObjectNode;
}

function scrollIfNeeded(event) {
  // If the selected tab isn't completely visible in the scrolled view,
  // force scroll
  const {
    parentHeight,
    selectedBottom,
    selectedTop,
  } = getNodePositions(event.target.parentNode, event.target);
  const shouldScrollDown = selectedBottom > parentHeight;
  const shouldScrollUp = selectedTop < 0;

  if (!shouldScrollDown && !shouldScrollUp) return;

  if (shouldScrollDown) {
    event.target.scrollIntoView(false);
  } else if (shouldScrollUp) {
    event.target.scrollIntoView(true);
  }
}

// Get focused node's position relative to the current scrolled view
function getNodePositions(parentNode, selectedNode) {
  return {
    parentHeight: parentNode.offsetHeight,
    selectedBottom: selectedNode.offsetHeight + (
      selectedNode.offsetTop - parentNode.offsetTop
    ) - parentNode.scrollTop,
    selectedTop: (
      selectedNode.offsetTop - parentNode.offsetTop
    ) - parentNode.scrollTop,
  };
}

function noResultNode() {
  //

  //   <object type="image/svg+xml" data="/assets/alert-circle.svg"></object>
  //   No tabs found
  //

  const noResultNode = document.createElement('div');
  noResultNode.classList.add('no-result');

  const alertCircleNode = document.createElement('object');
  alertCircleNode.setAttribute('type', 'image/svg+xml');
  alertCircleNode.setAttribute('data', '/assets/alert-circle.svg');

  const strongMsgNode = document.createElement('strong');
  strongMsgNode.appendChild(document.createTextNode('No tabs found'));

  noResultNode.appendChild(alertCircleNode);
  noResultNode.appendChild(strongMsgNode);
  return noResultNode;
}

function clearChildren(node) {
  while (node.firstChild) {
    node.removeChild(node.firstChild);
  };
}

function shortenString(str) {
  return str.length >= 120
    ? str.slice(0, 90) + '...'
    : str;
}

function isChromeLink(src) {
  return /chrome:\/\//.test(src);
}

function isURL(src) {
  return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.test(src);
}

function getBasePath(src) {
  const url = new URL(src);
  return url.origin;
}

function switchTabs() {
  // this: should be the tab-object node
  const tabId = this.dataset.id;
  switchActiveTab(tabId);
}

function switchActiveTab(id) {
  const numId = parseInt(id);
  browser.tabs.update(numId, { active: true });
  window.close();
}

function clearInput() {
  searchInput.value = '';
  populateTabList();
}

function populateTabList() {
  getAllTabs().then(injectTabsInList);
}

function focusNode(node) {
  node.focus();
}
<|endoftext|>


<commit_before>const { cleanJSON } = require("./src/utils");

var isNode = typeof window == "undefined";

/**
 * Expects a Fabric.js Canvas
 */
function ImageMarkupBuilder(fabricCanvas) {
  var Fabric = require("fabric").fabric;

  var Shapes = {
    Rectangle: require("./shapes/rectangle").klass,
    Circle: require("./shapes/circle").klass,
    Line: require("./shapes/line").klass,
    Arrow: require("./shapes/arrow").klass,
    Gap: require("./shapes/gap").klass,
  };

  var colorValues = {
    red: "#C1280B",
    orange: "#FF9024",
    yellow: "#F3E00E",
    green: "#16DC81",
    lightBlue: "#1BB1E9",
    blue: "#2343E8",
    darkBlue: "#2343E8",
    violet: "#DC54B7",
    pink: "#DC54B7",
    black: "#000000",
  };

  // Reference to the json object from processJSON
  var innerJSON;

  var imageOffset;
  var resizeRatio = 1;
  var finalWidth = 0;
  var markupObjects = [];

  var strokeWidth = null;
  var crop = null;

  /**
   * Array of delegate functions to draw a proper shape.
   */
  var addShapeDelegate = {
    arrow: function (data) {
      return drawLineBasedShape(Shapes.Arrow, data);
    },
    gap: function (data) {
      return drawLineBasedShape(Shapes.Gap, data);
    },
    line: function (data) {
      return drawLineBasedShape(Shapes.Line, data);
    },
    circle: drawCircle,
    rectangle: function (data) {
      // Because fabric considers top/left as the center of the rectangle.
      data.from.x -= data.size.width / 2;
      data.from.y -= data.size.height / 2;

      return drawRectangle(data);
    },
  };

  /**
   * Returns true if the given object is off the edge of the fabric canvas.
   * This is defined as whether the color stroke would be the only part of
   * the shape still visible at those coordinates.
   */
  fabricCanvas.isOffScreen = function (object) {
    var rect = object.getBoundingRect();
    return (
      rect.left + rect.width - strokeWidth * 2 < 0 ||
      rect.left + strokeWidth * 2 > this.width ||
      rect.top + rect.height - strokeWidth * 2 < 0 ||
      rect.top + strokeWidth * 2 > this.height
    );
  };

  function applyBackground(callback) {
    if (!isNode) {
      //Listen for shapes falling off the edge and delete
      fabricCanvas.on({
        "object:modified": function (e) {
          var shape = e.target;

          if (fabricCanvas.isOffScreen(shape)) remove(shape);
        }.bind(this),
      });
    }

    //Disable drag selection on canvas
    fabricCanvas.selection = false;

    if (!innerJSON.sourceFile) {
      if (!innerJSON.finalDimensions) {
        var msg = "Need source file or final dimensions to create canvas";
        throw new Error(msg);
      }

      //Apply markup to blank canvas
      applyMarkup(callback);
    } else {
      finalWidth = innerJSON.finalDimensions.width;
      if (isNode) {
        fabricCanvas.setBackgroundColor("#FFFFFF");
        require("fs").readFile(innerJSON.sourceFile, function (err, blob) {
          if (err) throw err;

          var dimensions = innerJSON.dimensions;
          var img = {
            width: dimensions.width,
            height: dimensions.height,
            originX: "left",
            originY: "top",
            src: blob,
          };

          Fabric.Image.fromObject(img, function (fimg) {
            var top = -imageOffset.y;
            if (top % 1 != 0) {
              top -= 0.5;
            }
            var left = -imageOffset.x;
            if (left % 1 != 0) {
              left -= 0.5;
            }

            fabricCanvas.add(fimg.set("top", top).set("left", left));
            applyMarkup(callback);
          });
        });
      } else {
        throw new Error("Source files not supported on frontend");
      }
    }
  }

  /**
   * Applies the given markup instructions to the fabric canvas. If this is
   * server-side, call writeCanvas() to write the results to a file. If
   * client-side, call the client-provided callback directly.
   */
  function applyMarkup(callback) {
    // strokeWidth needs to be caught now, since if it's lower in the JSON
    // it will not get picked up until it's too late.
    if (innerJSON.instructions.strokeWidth) {
      strokeWidth = innerJSON.instructions.strokeWidth;
    }

    for (var instruction in innerJSON.instructions) {
      switch (instruction) {
        case "draw":
          innerJSON.instructions.draw.forEach(function (e) {
            for (var shapeName in e) {
              var shape = e[shapeName];
              shape.shapeName = shapeName;

              switch (shapeName) {
                case "rectangle":
                  drawRectangle(shape);
                  break;
                case "circle":
                  drawCircle(shape);
                  break;
                case "line":
                  drawLineBasedShape(Shapes.Line, shape);
                  break;
                case "gap":
                  drawLineBasedShape(Shapes.Gap, shape);
                  break;
                case "arrow":
                  drawLineBasedShape(Shapes.Arrow, shape);
                  break;
                default:
                  console.error("Unsupported Shape: " + shapeName);
              }
            }
          });
          break;

        //These should already be taken care of
        case "crop":
        case "strokeWidth":
          break;
        default:
          console.error("Unsupported Instruction: " + instruction);
      }
    }

    if (isNode) {
      writeCanvas(callback);
    } else {
      callback(fabricCanvas);
    }
  }

  /**
   * Writes the processed canvas to a file.
   */
  function writeCanvas(callback) {
    fabricCanvas.renderAll();
    var outstream = require("fs").createWriteStream(innerJSON.destinationFile),
      stream = fabricCanvas.createJPEGStream({
        quality: 93,
        progressive: true,
      });

    stream.on("data", function (chunk) {
      outstream.write(chunk);
    });
    stream.on("end", function () {
      callback(fabricCanvas);
    });
  }

  /**
   * Returns a strokeWidth calculated based on the dimensions of the canvas.
   */
  function getStrokeWidth(finalWidth) {
    return strokeWidth != null
      ? strokeWidth
      : Math.max(Math.round((finalWidth / 300) * 2), 4);
  }

  function drawRectangle(shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var rect = {
      left: shape.from.x - imageOffset.x,
      top: shape.from.y - imageOffset.y,
      width: shape.size.width,
      height: shape.size.height,
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };

    //Fabric調整
    rect.top *= resizeRatio;
    rect.left *= resizeRatio;
    rect.width *= resizeRatio;
    rect.height *= resizeRatio;

    var fabricRect = new Shapes.Rectangle(rect);

    markupObjects.push(fabricRect);
    fabricCanvas.add(fabricRect);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricRect);
    }

    return fabricRect;
  }

  function drawLineBasedShape(klass, shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var line = {
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };

    var points = [
      (shape.from.x - imageOffset.x) * resizeRatio,
      (shape.from.y - imageOffset.y) * resizeRatio,
      (shape.to.x - imageOffset.x) * resizeRatio,
      (shape.to.y - imageOffset.y) * resizeRatio,
    ];

    var fabricLine = new klass(points, line);
    fabricLine.color = shape.color;

    markupObjects.push(fabricLine);
    fabricCanvas.add(fabricLine);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricLine);
    }

    return fabricLine;
  }

  function drawCircle(shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var circle = {
      left: shape.from.x - imageOffset.x,
      top: shape.from.y - imageOffset.y,
      radius: shape.radius,
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };
    circle.left *= resizeRatio;
    circle.top *= resizeRatio;
    circle.radius *= resizeRatio;

    var fabricCircle = new Shapes.Circle(circle);
    markupObjects.push(fabricCircle);
    fabricCanvas.add(fabricCircle);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricCircle);
    }

    return fabricCircle;
  }

  function locate(shape) {
    for (var i = 0; i < markupObjects.length; ++i) {
      if (markupObjects[i] === shape) {
        return i;
      }
    }

    return null;
  }

  function remove(shape) {
    var index = locate(shape);
    if (index != null) {
      fabricCanvas.remove(shape);
      markupObjects.splice(index, 1);
    }
  }

  /**
   * Performs ShapeData.type-agnostic attribute checks to ensure that the
   * necessary attributes are set in the given ShapeData object. Specifically,
   * the following operations are performed:
   *
   * - If data.x or data.y are not present, both are set to the center of
   *   the canvas. If both are present, they are scaled to the preview size
   *   of the canvas (if given).
   * - if data.color is not present, it is set to "red".
   */
  function normalizeShapeData(data) {
    data.from.x /= resizeRatio;
    data.from.y /= resizeRatio;
    data.from.x += imageOffset.x;
    data.from.y += imageOffset.y;

    if (data.to) {
      data.to.x /= resizeRatio;
      data.to.y /= resizeRatio;
      data.to.x += imageOffset.x;
      data.to.y += imageOffset.y;
    }

    if (!data.color) {
      data.color = "red";
    }
  }

  var publicInterface = {
    fabricCanvas: fabricCanvas,

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. The act of drawing is delegated to the proper
     * draw method based on its "type" attribute.
     */
    addShape: function addShape(data) {
      if (!data.type) throw "ShapeData.type is not defined.";

      normalizeShapeData(data);
      return addShapeDelegate[data.type](data);
    },

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. This ignores the "type" attribute of the object
     * and just adds a circle. To be deprecated by addShape().
     *
     * @return a reference to the tracked shape.
     */
    addCircle: function addCircle(data) {
      console.warn("Deprecated function: addCircle(). Use addShape() instead.");

      if (data.type !== "circle") data.type = "circle";
      return this.addShape(data);
    },

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. This ignores the "type" attribute of the object
     * and just adds a rectangle. To be deprecated by addShape().
     *
     * @return a reference to the tracked shape.
     */
    addRectangle: function addRectangle(data) {
      console.warn(
        "Deprecated function: addRectangle(). Use addShape() instead."
      );

      if (data.type !== "rectangle") data.type = "rectangle";
      return this.addShape(data);
    },

    /**
     * Sets the color of a tracked shape.
     */
    setColor: function setColor(shape, colorName) {
      shape.stroke = colorValues[colorName];
      shape.color = colorName;

      if (!isNode) fabricCanvas.renderAll();
    },

    /**
     * Returns an array of all objects in the fabric canvas, whether
     * tracked by the Builder or not.
     */
    getShapes: function getShapes() {
      return fabricCanvas._objects;
    },

    /**
     * Returns a reference to the currently selected shape.
     *
     * @return a reference to the currently selected shape.
     */
    getActiveShape: function getActiveShape() {
      return fabricCanvas.getActiveObject();
    },

    /**
     * Removes a specific tracked shape from the fabric canvas.
     */
    removeShape: function removeShape(shape) {
      remove(shape);
    },

    /**
     * Removes all tracked shapes from the fabric canvas.
     */
    removeShapes: function removeShapes() {
      for (var i = 0; i < markupObjects.length; ++i) {
        var shape = markupObjects[i];
        fabricCanvas.remove(shape);
      }

      markupObjects = [];
    },

    /**
     * Grows or shrinks the given shape depending on the given key
     * by the given number of pixels.
     *
     * @param shape The fabric shape to resize.
     * @param increment The number of pixels (negative or positive) to
     *  resize the shape.
     */
    incrementSize: function incrementSize(shape, increment) {
      if (!(typeof increment === "number")) {
        console.error("increment must be a number");
        return;
      }

      shape._withSizeLimitations(function () {
        shape.incrementSize(increment);
        if (fabricCanvas.isOffScreen(shape)) {
          shape.incrementSize(increment * -1);
        }
      }, true);

      shape.setCoords();
      fabricCanvas.renderAll();
    },

    /**
     * Takes a Builder-schema JSON object and performs the operations
     * listed therein, then calls the callback function.
     */
    processJSON: function processJSON(json, callback) {
      //Make sure not to render every addition on server end
      fabricCanvas.renderOnAddition = !isNode;
      fabricCanvas.uniScaleTransform = true;

      cleanJSON(json);

      innerJSON = json;

      crop = innerJSON.instructions.crop;
      imageOffset =
        typeof crop != "undefined"
          ? {
              x: crop.from.x,
              y: crop.from.y,
            }
          : {
              x: 0,
              y: 0,
            };

      if (innerJSON.resizeRatio) {
        resizeRatio = innerJSON.resizeRatio;
      }

      applyBackground(callback);
    },

    getMarkupObjects: function getMarkupObjects() {
      return markupObjects;
    },

    /**
     * Returns a markup string which can be used with ImageMarkupCall
     * to produce the same markup results as represented in this builder.
     */
    getMarkupString: function getMarkupString() {
      /**
       * Translate RGB value to applicable color string. Unknown
       * RGB values will will become black and a console.error message
       * will be written.
       */
      function translateRGBtoColorString(rgb) {
        for (var colorString in colorValues) {
          if (rgb == colorValues[colorString]) {
            return colorString;
          }
        }
        console.error("No mapping from RGB to Color String Found: " + rgb);
        return "black";
      }

      var markupString = ";";

      if (crop) {
        // Cut out cases where crop is used to offset non-4:3
        // images
        markupString +=
          "crop," +
          crop.from.x +
          "x" +
          crop.from.y +
          "," +
          crop.size.width +
          "x" +
          crop.size.height +
          ";";
      }

      for (var i = 0; i < markupObjects.length; ++i) {
        var object = markupObjects[i];

        switch (object.shapeName) {
          case "circle":
            var circleFrom = {
              x: Math.round(object.left / resizeRatio) + imageOffset.x,
              y: Math.round(object.top / resizeRatio) + imageOffset.y,
            };
            var radius = Math.round(object.getRadiusX() / resizeRatio);
            var color = translateRGBtoColorString(object.stroke);

            markupString +=
              "circle," +
              circleFrom.x +
              "x" +
              circleFrom.y +
              "," +
              radius +
              "," +
              color +
              ";";
            break;
          case "rectangle":
            var rectFrom = {
              x: Math.round(object.left / resizeRatio) + imageOffset.x,
              y: Math.round(object.top / resizeRatio) + imageOffset.y,
            };
            var size = {
              width: Math.round(object.width / resizeRatio),
              height: Math.round(object.height / resizeRatio),
            };
            var color = translateRGBtoColorString(object.stroke);

            markupString +=
              "rectangle," +
              rectFrom.x +
              "x" +
              rectFrom.y +
              "," +
              size.width +
              "x" +
              size.height +
              "," +
              color +
              ";";
            break;
          case "gap":
          case "arrow":
          case "line":
            markupString += object.toMarkup(resizeRatio, imageOffset);
            break;
          default:
            console.error("Unexpected object name: " + object.shapeName);
        }
      }

      return markupString;
    },
  };

  // Setup drag-to-draw for this canvas
  require("./src/drag_to_create").setup(publicInterface);

  return publicInterface;
}

module.exports.Builder = ImageMarkupBuilder;
<commit_msg>Make `color` variable name unique in scope<commit_after>const { cleanJSON } = require("./src/utils");

var isNode = typeof window == "undefined";

/**
 * Expects a Fabric.js Canvas
 */
function ImageMarkupBuilder(fabricCanvas) {
  var Fabric = require("fabric").fabric;

  var Shapes = {
    Rectangle: require("./shapes/rectangle").klass,
    Circle: require("./shapes/circle").klass,
    Line: require("./shapes/line").klass,
    Arrow: require("./shapes/arrow").klass,
    Gap: require("./shapes/gap").klass,
  };

  var colorValues = {
    red: "#C1280B",
    orange: "#FF9024",
    yellow: "#F3E00E",
    green: "#16DC81",
    lightBlue: "#1BB1E9",
    blue: "#2343E8",
    darkBlue: "#2343E8",
    violet: "#DC54B7",
    pink: "#DC54B7",
    black: "#000000",
  };

  // Reference to the json object from processJSON
  var innerJSON;

  var imageOffset;
  var resizeRatio = 1;
  var finalWidth = 0;
  var markupObjects = [];

  var strokeWidth = null;
  var crop = null;

  /**
   * Array of delegate functions to draw a proper shape.
   */
  var addShapeDelegate = {
    arrow: function (data) {
      return drawLineBasedShape(Shapes.Arrow, data);
    },
    gap: function (data) {
      return drawLineBasedShape(Shapes.Gap, data);
    },
    line: function (data) {
      return drawLineBasedShape(Shapes.Line, data);
    },
    circle: drawCircle,
    rectangle: function (data) {
      // Because fabric considers top/left as the center of the rectangle.
      data.from.x -= data.size.width / 2;
      data.from.y -= data.size.height / 2;

      return drawRectangle(data);
    },
  };

  /**
   * Returns true if the given object is off the edge of the fabric canvas.
   * This is defined as whether the color stroke would be the only part of
   * the shape still visible at those coordinates.
   */
  fabricCanvas.isOffScreen = function (object) {
    var rect = object.getBoundingRect();
    return (
      rect.left + rect.width - strokeWidth * 2 < 0 ||
      rect.left + strokeWidth * 2 > this.width ||
      rect.top + rect.height - strokeWidth * 2 < 0 ||
      rect.top + strokeWidth * 2 > this.height
    );
  };

  function applyBackground(callback) {
    if (!isNode) {
      //Listen for shapes falling off the edge and delete
      fabricCanvas.on({
        "object:modified": function (e) {
          var shape = e.target;

          if (fabricCanvas.isOffScreen(shape)) remove(shape);
        }.bind(this),
      });
    }

    //Disable drag selection on canvas
    fabricCanvas.selection = false;

    if (!innerJSON.sourceFile) {
      if (!innerJSON.finalDimensions) {
        var msg = "Need source file or final dimensions to create canvas";
        throw new Error(msg);
      }

      //Apply markup to blank canvas
      applyMarkup(callback);
    } else {
      finalWidth = innerJSON.finalDimensions.width;
      if (isNode) {
        fabricCanvas.setBackgroundColor("#FFFFFF");
        require("fs").readFile(innerJSON.sourceFile, function (err, blob) {
          if (err) throw err;

          var dimensions = innerJSON.dimensions;
          var img = {
            width: dimensions.width,
            height: dimensions.height,
            originX: "left",
            originY: "top",
            src: blob,
          };

          Fabric.Image.fromObject(img, function (fimg) {
            var top = -imageOffset.y;
            if (top % 1 != 0) {
              top -= 0.5;
            }
            var left = -imageOffset.x;
            if (left % 1 != 0) {
              left -= 0.5;
            }

            fabricCanvas.add(fimg.set("top", top).set("left", left));
            applyMarkup(callback);
          });
        });
      } else {
        throw new Error("Source files not supported on frontend");
      }
    }
  }

  /**
   * Applies the given markup instructions to the fabric canvas. If this is
   * server-side, call writeCanvas() to write the results to a file. If
   * client-side, call the client-provided callback directly.
   */
  function applyMarkup(callback) {
    // strokeWidth needs to be caught now, since if it's lower in the JSON
    // it will not get picked up until it's too late.
    if (innerJSON.instructions.strokeWidth) {
      strokeWidth = innerJSON.instructions.strokeWidth;
    }

    for (var instruction in innerJSON.instructions) {
      switch (instruction) {
        case "draw":
          innerJSON.instructions.draw.forEach(function (e) {
            for (var shapeName in e) {
              var shape = e[shapeName];
              shape.shapeName = shapeName;

              switch (shapeName) {
                case "rectangle":
                  drawRectangle(shape);
                  break;
                case "circle":
                  drawCircle(shape);
                  break;
                case "line":
                  drawLineBasedShape(Shapes.Line, shape);
                  break;
                case "gap":
                  drawLineBasedShape(Shapes.Gap, shape);
                  break;
                case "arrow":
                  drawLineBasedShape(Shapes.Arrow, shape);
                  break;
                default:
                  console.error("Unsupported Shape: " + shapeName);
              }
            }
          });
          break;

        //These should already be taken care of
        case "crop":
        case "strokeWidth":
          break;
        default:
          console.error("Unsupported Instruction: " + instruction);
      }
    }

    if (isNode) {
      writeCanvas(callback);
    } else {
      callback(fabricCanvas);
    }
  }

  /**
   * Writes the processed canvas to a file.
   */
  function writeCanvas(callback) {
    fabricCanvas.renderAll();
    var outstream = require("fs").createWriteStream(innerJSON.destinationFile),
      stream = fabricCanvas.createJPEGStream({
        quality: 93,
        progressive: true,
      });

    stream.on("data", function (chunk) {
      outstream.write(chunk);
    });
    stream.on("end", function () {
      callback(fabricCanvas);
    });
  }

  /**
   * Returns a strokeWidth calculated based on the dimensions of the canvas.
   */
  function getStrokeWidth(finalWidth) {
    return strokeWidth != null
      ? strokeWidth
      : Math.max(Math.round((finalWidth / 300) * 2), 4);
  }

  function drawRectangle(shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var rect = {
      left: shape.from.x - imageOffset.x,
      top: shape.from.y - imageOffset.y,
      width: shape.size.width,
      height: shape.size.height,
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };

    //Fabric調整
    rect.top *= resizeRatio;
    rect.left *= resizeRatio;
    rect.width *= resizeRatio;
    rect.height *= resizeRatio;

    var fabricRect = new Shapes.Rectangle(rect);

    markupObjects.push(fabricRect);
    fabricCanvas.add(fabricRect);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricRect);
    }

    return fabricRect;
  }

  function drawLineBasedShape(klass, shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var line = {
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };

    var points = [
      (shape.from.x - imageOffset.x) * resizeRatio,
      (shape.from.y - imageOffset.y) * resizeRatio,
      (shape.to.x - imageOffset.x) * resizeRatio,
      (shape.to.y - imageOffset.y) * resizeRatio,
    ];

    var fabricLine = new klass(points, line);
    fabricLine.color = shape.color;

    markupObjects.push(fabricLine);
    fabricCanvas.add(fabricLine);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricLine);
    }

    return fabricLine;
  }

  function drawCircle(shape) {
    shape.stroke = getStrokeWidth(finalWidth);

    var circle = {
      left: shape.from.x - imageOffset.x,
      top: shape.from.y - imageOffset.y,
      radius: shape.radius,
      borderWidth: shape.stroke,
      stroke: colorValues[shape.color],
      color: shape.color,
    };
    circle.left *= resizeRatio;
    circle.top *= resizeRatio;
    circle.radius *= resizeRatio;

    var fabricCircle = new Shapes.Circle(circle);
    markupObjects.push(fabricCircle);
    fabricCanvas.add(fabricCircle);

    if (!isNode) {
      // Set this as the active object
      fabricCanvas.setActiveObject(fabricCircle);
    }

    return fabricCircle;
  }

  function locate(shape) {
    for (var i = 0; i < markupObjects.length; ++i) {
      if (markupObjects[i] === shape) {
        return i;
      }
    }

    return null;
  }

  function remove(shape) {
    var index = locate(shape);
    if (index != null) {
      fabricCanvas.remove(shape);
      markupObjects.splice(index, 1);
    }
  }

  /**
   * Performs ShapeData.type-agnostic attribute checks to ensure that the
   * necessary attributes are set in the given ShapeData object. Specifically,
   * the following operations are performed:
   *
   * - If data.x or data.y are not present, both are set to the center of
   *   the canvas. If both are present, they are scaled to the preview size
   *   of the canvas (if given).
   * - if data.color is not present, it is set to "red".
   */
  function normalizeShapeData(data) {
    data.from.x /= resizeRatio;
    data.from.y /= resizeRatio;
    data.from.x += imageOffset.x;
    data.from.y += imageOffset.y;

    if (data.to) {
      data.to.x /= resizeRatio;
      data.to.y /= resizeRatio;
      data.to.x += imageOffset.x;
      data.to.y += imageOffset.y;
    }

    if (!data.color) {
      data.color = "red";
    }
  }

  var publicInterface = {
    fabricCanvas: fabricCanvas,

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. The act of drawing is delegated to the proper
     * draw method based on its "type" attribute.
     */
    addShape: function addShape(data) {
      if (!data.type) throw "ShapeData.type is not defined.";

      normalizeShapeData(data);
      return addShapeDelegate[data.type](data);
    },

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. This ignores the "type" attribute of the object
     * and just adds a circle. To be deprecated by addShape().
     *
     * @return a reference to the tracked shape.
     */
    addCircle: function addCircle(data) {
      console.warn("Deprecated function: addCircle(). Use addShape() instead.");

      if (data.type !== "circle") data.type = "circle";
      return this.addShape(data);
    },

    /**
     * Adds and tracks a given data object following the ShapeData schema
     * to the fabric canvas. This ignores the "type" attribute of the object
     * and just adds a rectangle. To be deprecated by addShape().
     *
     * @return a reference to the tracked shape.
     */
    addRectangle: function addRectangle(data) {
      console.warn(
        "Deprecated function: addRectangle(). Use addShape() instead."
      );

      if (data.type !== "rectangle") data.type = "rectangle";
      return this.addShape(data);
    },

    /**
     * Sets the color of a tracked shape.
     */
    setColor: function setColor(shape, colorName) {
      shape.stroke = colorValues[colorName];
      shape.color = colorName;

      if (!isNode) fabricCanvas.renderAll();
    },

    /**
     * Returns an array of all objects in the fabric canvas, whether
     * tracked by the Builder or not.
     */
    getShapes: function getShapes() {
      return fabricCanvas._objects;
    },

    /**
     * Returns a reference to the currently selected shape.
     *
     * @return a reference to the currently selected shape.
     */
    getActiveShape: function getActiveShape() {
      return fabricCanvas.getActiveObject();
    },

    /**
     * Removes a specific tracked shape from the fabric canvas.
     */
    removeShape: function removeShape(shape) {
      remove(shape);
    },

    /**
     * Removes all tracked shapes from the fabric canvas.
     */
    removeShapes: function removeShapes() {
      for (var i = 0; i < markupObjects.length; ++i) {
        var shape = markupObjects[i];
        fabricCanvas.remove(shape);
      }

      markupObjects = [];
    },

    /**
     * Grows or shrinks the given shape depending on the given key
     * by the given number of pixels.
     *
     * @param shape The fabric shape to resize.
     * @param increment The number of pixels (negative or positive) to
     *  resize the shape.
     */
    incrementSize: function incrementSize(shape, increment) {
      if (!(typeof increment === "number")) {
        console.error("increment must be a number");
        return;
      }

      shape._withSizeLimitations(function () {
        shape.incrementSize(increment);
        if (fabricCanvas.isOffScreen(shape)) {
          shape.incrementSize(increment * -1);
        }
      }, true);

      shape.setCoords();
      fabricCanvas.renderAll();
    },

    /**
     * Takes a Builder-schema JSON object and performs the operations
     * listed therein, then calls the callback function.
     */
    processJSON: function processJSON(json, callback) {
      //Make sure not to render every addition on server end
      fabricCanvas.renderOnAddition = !isNode;
      fabricCanvas.uniScaleTransform = true;

      cleanJSON(json);

      innerJSON = json;

      crop = innerJSON.instructions.crop;
      imageOffset =
        typeof crop != "undefined"
          ? {
              x: crop.from.x,
              y: crop.from.y,
            }
          : {
              x: 0,
              y: 0,
            };

      if (innerJSON.resizeRatio) {
        resizeRatio = innerJSON.resizeRatio;
      }

      applyBackground(callback);
    },

    getMarkupObjects: function getMarkupObjects() {
      return markupObjects;
    },

    /**
     * Returns a markup string which can be used with ImageMarkupCall
     * to produce the same markup results as represented in this builder.
     */
    getMarkupString: function getMarkupString() {
      /**
       * Translate RGB value to applicable color string. Unknown
       * RGB values will will become black and a console.error message
       * will be written.
       */
      function translateRGBtoColorString(rgb) {
        for (var colorString in colorValues) {
          if (rgb == colorValues[colorString]) {
            return colorString;
          }
        }
        console.error("No mapping from RGB to Color String Found: " + rgb);
        return "black";
      }

      var markupString = ";";

      if (crop) {
        // Cut out cases where crop is used to offset non-4:3
        // images
        markupString +=
          "crop," +
          crop.from.x +
          "x" +
          crop.from.y +
          "," +
          crop.size.width +
          "x" +
          crop.size.height +
          ";";
      }

      for (var i = 0; i < markupObjects.length; ++i) {
        var object = markupObjects[i];

        switch (object.shapeName) {
          case "circle":
            var circleFrom = {
              x: Math.round(object.left / resizeRatio) + imageOffset.x,
              y: Math.round(object.top / resizeRatio) + imageOffset.y,
            };
            var radius = Math.round(object.getRadiusX() / resizeRatio);
            var circleColor = translateRGBtoColorString(object.stroke);

            markupString +=
              "circle," +
              circleFrom.x +
              "x" +
              circleFrom.y +
              "," +
              radius +
              "," +
              circleColor +
              ";";
            break;
          case "rectangle":
            var rectFrom = {
              x: Math.round(object.left / resizeRatio) + imageOffset.x,
              y: Math.round(object.top / resizeRatio) + imageOffset.y,
            };
            var size = {
              width: Math.round(object.width / resizeRatio),
              height: Math.round(object.height / resizeRatio),
            };
            var rectColor = translateRGBtoColorString(object.stroke);

            markupString +=
              "rectangle," +
              rectFrom.x +
              "x" +
              rectFrom.y +
              "," +
              size.width +
              "x" +
              size.height +
              "," +
              rectColor +
              ";";
            break;
          case "gap":
          case "arrow":
          case "line":
            markupString += object.toMarkup(resizeRatio, imageOffset);
            break;
          default:
            console.error("Unexpected object name: " + object.shapeName);
        }
      }

      return markupString;
    },
  };

  // Setup drag-to-draw for this canvas
  require("./src/drag_to_create").setup(publicInterface);

  return publicInterface;
}

module.exports.Builder = ImageMarkupBuilder;
<|endoftext|>