Merged in feature/MAPG-103-map-editor-ui-elements (pull request #88)
Feature/MAPG-103 map editor ui elements
This commit is contained in:
		
						commit
						16bcbaecbe
					
				@ -19,13 +19,14 @@ Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsCont
 | 
			
		||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
			
		||||
    $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
 | 
			
		||||
    $routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
 | 
			
		||||
    $routeCollection->get('position-json', '{mapId}/position.json', [MapGuesser\Controller\PositionController::class, 'getPosition']);
 | 
			
		||||
    $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\PositionController::class, 'evaluateGuess']);
 | 
			
		||||
    $routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
 | 
			
		||||
    $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
 | 
			
		||||
});
 | 
			
		||||
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
			
		||||
    $routeCollection->get('admin.maps', 'maps', [MapGuesser\Controller\MapAdminController::class, 'getMaps']);
 | 
			
		||||
    $routeCollection->get('admin.mapEditor', 'mapEditor/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
 | 
			
		||||
    $routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
 | 
			
		||||
    $routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
$match = Container::$routeCollection->match($method, explode('/', $url));
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,3 @@
 | 
			
		||||
#roundInfo {
 | 
			
		||||
    line-height: inherit;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#roundInfo p {
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    line-height: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#panorama {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100% - 40px);
 | 
			
		||||
@ -110,6 +100,9 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 599px) {
 | 
			
		||||
    #mapName {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    #showGuessButtonContainer {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 20px;
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,27 @@
 | 
			
		||||
#metadata {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50px;
 | 
			
		||||
    left: 10px;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
    border: solid 1px #555555;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    opacity: 0.95;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#map {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100% - 50px);
 | 
			
		||||
    height: calc(100% - 40px);
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.leaflet-container {
 | 
			
		||||
    cursor: crosshair;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#panorama {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
@ -24,53 +42,65 @@
 | 
			
		||||
 | 
			
		||||
#control {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50px;
 | 
			
		||||
    right: 10px;
 | 
			
		||||
    width: 125px;
 | 
			
		||||
    z-index: 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#placeControl {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 10px;
 | 
			
		||||
    z-index: 3;
 | 
			
		||||
    width: 100px;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#deleteButton {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 999px) and (min-height: 600px) {
 | 
			
		||||
    #metadata {
 | 
			
		||||
        width: calc(100% - 155px);
 | 
			
		||||
    }
 | 
			
		||||
    #map.selected {
 | 
			
		||||
        height: calc(50% - 25px);
 | 
			
		||||
        height: calc(50% - 20px);
 | 
			
		||||
    }
 | 
			
		||||
    #panorama, #noPano {
 | 
			
		||||
        left: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        height: calc(50% - 25px);
 | 
			
		||||
    }
 | 
			
		||||
    #control {
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 60px;
 | 
			
		||||
        height: calc(50% - 20px);
 | 
			
		||||
    }
 | 
			
		||||
    #placeControl {
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: calc(50% + 35px);
 | 
			
		||||
        top: calc(50% + 30px);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (min-width: 1000px), (max-height: 599px) {
 | 
			
		||||
    #metadata {
 | 
			
		||||
        width: calc(50% - 20px);
 | 
			
		||||
    }
 | 
			
		||||
    #metadata.selected {
 | 
			
		||||
        top: 95px;
 | 
			
		||||
    }
 | 
			
		||||
    #map.selected {
 | 
			
		||||
        width: 50%;
 | 
			
		||||
    }
 | 
			
		||||
    #panorama, #noPano {
 | 
			
		||||
        top: 50px;
 | 
			
		||||
        top: 40px;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        width: 50%;
 | 
			
		||||
    }
 | 
			
		||||
    #control, #placeControl {
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 60px;
 | 
			
		||||
    #placeControl {
 | 
			
		||||
        top: 50px;
 | 
			
		||||
    }
 | 
			
		||||
    #modified.selected {
 | 
			
		||||
        right: calc(50% + 10px);
 | 
			
		||||
    }
 | 
			
		||||
    #control.selected {
 | 
			
		||||
        right: calc(50% + 10px);
 | 
			
		||||
        top: 60px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -98,7 +98,7 @@ sub {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
svg.inline, img.inline {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    display: inline;
 | 
			
		||||
    width: 1em;
 | 
			
		||||
    height: 1em;
 | 
			
		||||
    vertical-align: -0.15em;
 | 
			
		||||
@ -228,6 +228,24 @@ div.header.small {
 | 
			
		||||
    line-height: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header>div.grid>:nth-child(2) {
 | 
			
		||||
    line-height: inherit;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header>div.grid>:nth-child(2)>span {
 | 
			
		||||
    padding-left: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header>div.grid>:nth-child(2)>span>a:link, div.header>div.grid>:nth-child(2)>span>a:visited {
 | 
			
		||||
    color: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header>div.grid>:nth-child(2)>span:not(:last-child) {
 | 
			
		||||
    border-right: solid white 1px;
 | 
			
		||||
    padding-right: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.main {
 | 
			
		||||
    padding: 6px 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
    var Core = {
 | 
			
		||||
    var Game = {
 | 
			
		||||
        NUMBER_OF_ROUNDS: 5,
 | 
			
		||||
        MAX_SCORE: 1000,
 | 
			
		||||
 | 
			
		||||
@ -17,54 +17,54 @@
 | 
			
		||||
        initialize: function () {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('cover').style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('currentRound').innerHTML = '1/' + String(Core.NUMBER_OF_ROUNDS);
 | 
			
		||||
            document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
 | 
			
		||||
            document.getElementById('currentScoreSum').innerHTML = '0/0';
 | 
			
		||||
 | 
			
		||||
            Core.map.setOptions({
 | 
			
		||||
            Game.map.setOptions({
 | 
			
		||||
                draggableCursor: 'crosshair'
 | 
			
		||||
            });
 | 
			
		||||
            Core.map.fitBounds(mapBounds);
 | 
			
		||||
            Game.map.fitBounds(mapBounds);
 | 
			
		||||
 | 
			
		||||
            var xhr = new XMLHttpRequest();
 | 
			
		||||
            xhr.responseType = 'json';
 | 
			
		||||
            xhr.onload = function () {
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
                document.getElementById('cover').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
                if (this.response.error) {
 | 
			
		||||
                    //TODO: handle this error
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
                document.getElementById('cover').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
                Core.panoId = this.response.panoId;
 | 
			
		||||
                Game.panoId = this.response.panoId;
 | 
			
		||||
 | 
			
		||||
                if (this.response.history) {
 | 
			
		||||
                    for (var i = 0; i < this.response.history.length; ++i) {
 | 
			
		||||
                        var round = this.response.history[i];
 | 
			
		||||
                        Core.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
 | 
			
		||||
                        Core.addRealGuessPair(round.position, round.guessPosition, true);
 | 
			
		||||
                        Core.scoreSum += round.score;
 | 
			
		||||
                        Game.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
 | 
			
		||||
                        Game.addRealGuessPair(round.position, round.guessPosition, true);
 | 
			
		||||
                        Game.scoreSum += round.score;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
 | 
			
		||||
                    document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE);
 | 
			
		||||
                    document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
 | 
			
		||||
                    document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Core.startNewRound();
 | 
			
		||||
                Game.startNewRound();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('GET', '/game/' + mapId + '/position.json', true);
 | 
			
		||||
            xhr.open('GET', '/game/' + mapId + '/newPlace.json', true);
 | 
			
		||||
            xhr.send();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        resetGame: function () {
 | 
			
		||||
            if (Core.guessMarker) {
 | 
			
		||||
                Core.guessMarker.setMap(null);
 | 
			
		||||
                Core.guessMarker = null;
 | 
			
		||||
        reset: function () {
 | 
			
		||||
            if (Game.guessMarker) {
 | 
			
		||||
                Game.guessMarker.setMap(null);
 | 
			
		||||
                Game.guessMarker = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < Core.rounds.length; ++i) {
 | 
			
		||||
                var round = Core.rounds[i];
 | 
			
		||||
            for (var i = 0; i < Game.rounds.length; ++i) {
 | 
			
		||||
                var round = Game.rounds[i];
 | 
			
		||||
 | 
			
		||||
                if (round.realMarker && round.guessMarker && round.line) {
 | 
			
		||||
                    round.realMarker.setMap(null);
 | 
			
		||||
@ -73,8 +73,8 @@
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Core.rounds = [];
 | 
			
		||||
            Core.scoreSum = 0;
 | 
			
		||||
            Game.rounds = [];
 | 
			
		||||
            Game.scoreSum = 0;
 | 
			
		||||
 | 
			
		||||
            var distanceInfo = document.getElementById('distanceInfo');
 | 
			
		||||
            distanceInfo.children[0].style.display = null;
 | 
			
		||||
@ -89,14 +89,14 @@
 | 
			
		||||
            document.getElementById('guess').style.visibility = null;
 | 
			
		||||
            document.getElementById('guess').classList.remove('result');
 | 
			
		||||
 | 
			
		||||
            Core.initialize();
 | 
			
		||||
            Game.initialize();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        resetRound: function () {
 | 
			
		||||
            document.getElementById('scoreBar').style.width = null;
 | 
			
		||||
 | 
			
		||||
            if (Core.rounds.length > 0) {
 | 
			
		||||
                var lastRound = Core.rounds[Core.rounds.length - 1];
 | 
			
		||||
            if (Game.rounds.length > 0) {
 | 
			
		||||
                var lastRound = Game.rounds[Game.rounds.length - 1];
 | 
			
		||||
 | 
			
		||||
                lastRound.realMarker.setVisible(false);
 | 
			
		||||
                lastRound.guessMarker.setVisible(false);
 | 
			
		||||
@ -108,20 +108,20 @@
 | 
			
		||||
            document.getElementById('guess').style.visibility = null;
 | 
			
		||||
            document.getElementById('guess').classList.remove('result')
 | 
			
		||||
 | 
			
		||||
            Core.map.setOptions({
 | 
			
		||||
            Game.map.setOptions({
 | 
			
		||||
                draggableCursor: 'crosshair'
 | 
			
		||||
            });
 | 
			
		||||
            Core.map.fitBounds(mapBounds);
 | 
			
		||||
            Game.map.fitBounds(mapBounds);
 | 
			
		||||
 | 
			
		||||
            Core.startNewRound();
 | 
			
		||||
            Game.startNewRound();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        startNewRound: function () {
 | 
			
		||||
            Core.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
 | 
			
		||||
            Game.rounds.push({ position: null, guessPosition: null, realMarker: null, guessMarker: null, line: null });
 | 
			
		||||
 | 
			
		||||
            document.getElementById('currentRound').innerHTML = String(Core.rounds.length) + '/' + String(Core.NUMBER_OF_ROUNDS);
 | 
			
		||||
            document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
 | 
			
		||||
 | 
			
		||||
            Core.loadPano(Core.panoId);
 | 
			
		||||
            Game.loadPano(Game.panoId);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        handleErrorResponse: function (error) {
 | 
			
		||||
@ -132,7 +132,7 @@
 | 
			
		||||
            xhr.onload = function () {
 | 
			
		||||
                mapBounds = this.response.bounds;
 | 
			
		||||
 | 
			
		||||
                Core.resetGame();
 | 
			
		||||
                Game.reset();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('GET', '/game/' + mapId + '/json', true);
 | 
			
		||||
@ -140,20 +140,25 @@
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        loadPano: function (panoId) {
 | 
			
		||||
            if (Core.adaptGuess) {
 | 
			
		||||
            if (Game.adaptGuess) {
 | 
			
		||||
                document.getElementById('guess').classList.add('adapt');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Core.panorama.setPov({ heading: 0, pitch: 0 });
 | 
			
		||||
            Core.panorama.setZoom(0);
 | 
			
		||||
            Core.panorama.setPano(panoId);
 | 
			
		||||
            Game.panorama.setPov({ heading: 0, pitch: 0 });
 | 
			
		||||
            Game.panorama.setZoom(0);
 | 
			
		||||
            Game.panorama.setPano(panoId);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        evaluateGuess: function () {
 | 
			
		||||
            var guessPosition = Core.guessMarker.getPosition().toJSON();
 | 
			
		||||
            Core.rounds[Core.rounds.length - 1].guessPosition = guessPosition;
 | 
			
		||||
            if (!Game.guessMarker) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (Core.adaptGuess) {
 | 
			
		||||
            var guessPosition = Game.guessMarker.getPosition().toJSON();
 | 
			
		||||
            Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
 | 
			
		||||
 | 
			
		||||
            document.getElementById('guessButton').disabled = true;
 | 
			
		||||
            if (Game.adaptGuess) {
 | 
			
		||||
                document.getElementById('guess').classList.remove('adapt');
 | 
			
		||||
            }
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
@ -167,45 +172,45 @@
 | 
			
		||||
            xhr.responseType = 'json';
 | 
			
		||||
            xhr.onload = function () {
 | 
			
		||||
                if (this.response.error) {
 | 
			
		||||
                    Core.handleErrorResponse(this.response.error);
 | 
			
		||||
                    Game.handleErrorResponse(this.response.error);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Core.guessMarker.setMap(null);
 | 
			
		||||
                Core.guessMarker = null;
 | 
			
		||||
                Game.guessMarker.setMap(null);
 | 
			
		||||
                Game.guessMarker = null;
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
                document.getElementById('guess').classList.add('result');
 | 
			
		||||
 | 
			
		||||
                Core.scoreSum += this.response.result.score;
 | 
			
		||||
                document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE);
 | 
			
		||||
                Game.scoreSum += this.response.result.score;
 | 
			
		||||
                document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
 | 
			
		||||
 | 
			
		||||
                Core.rounds[Core.rounds.length - 1].position = this.response.result.position;
 | 
			
		||||
                Core.addRealGuessPair(this.response.result.position, guessPosition);
 | 
			
		||||
                Game.rounds[Game.rounds.length - 1].position = this.response.result.position;
 | 
			
		||||
                Game.addRealGuessPair(this.response.result.position, guessPosition);
 | 
			
		||||
 | 
			
		||||
                var resultBounds = new google.maps.LatLngBounds();
 | 
			
		||||
                resultBounds.extend(this.response.result.position);
 | 
			
		||||
                resultBounds.extend(guessPosition);
 | 
			
		||||
 | 
			
		||||
                Core.map.setOptions({
 | 
			
		||||
                Game.map.setOptions({
 | 
			
		||||
                    draggableCursor: 'grab'
 | 
			
		||||
                });
 | 
			
		||||
                Core.map.fitBounds(resultBounds);
 | 
			
		||||
                Game.map.fitBounds(resultBounds);
 | 
			
		||||
 | 
			
		||||
                document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance);
 | 
			
		||||
                document.getElementById('score').innerHTML = this.response.result.score;
 | 
			
		||||
 | 
			
		||||
                var scoreBarProperties = Core.calculateScoreBarProperties(this.response.result.score, Core.MAX_SCORE);
 | 
			
		||||
                var scoreBarProperties = Game.calculateScoreBarProperties(this.response.result.score, Game.MAX_SCORE);
 | 
			
		||||
                var scoreBar = document.getElementById('scoreBar');
 | 
			
		||||
                scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
 | 
			
		||||
                scoreBar.style.width = scoreBarProperties.width;
 | 
			
		||||
 | 
			
		||||
                if (Core.rounds.length === Core.NUMBER_OF_ROUNDS) {
 | 
			
		||||
                if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
 | 
			
		||||
                    document.getElementById('continueButton').style.display = 'none';
 | 
			
		||||
                    document.getElementById('showSummaryButton').style.display = 'block';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Core.panoId = this.response.panoId;
 | 
			
		||||
                Game.panoId = this.response.panoId;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('POST', '/game/' + mapId + '/guess.json', true);
 | 
			
		||||
@ -213,14 +218,14 @@
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        addRealGuessPair: function (position, guessPosition, hidden) {
 | 
			
		||||
            var round = Core.rounds[Core.rounds.length - 1];
 | 
			
		||||
            var round = Game.rounds[Game.rounds.length - 1];
 | 
			
		||||
 | 
			
		||||
            round.realMarker = new google.maps.Marker({
 | 
			
		||||
                map: Core.map,
 | 
			
		||||
                map: Game.map,
 | 
			
		||||
                visible: !hidden,
 | 
			
		||||
                position: position,
 | 
			
		||||
                title: 'Open in Google Maps',
 | 
			
		||||
                zIndex: Core.rounds.length * 2,
 | 
			
		||||
                zIndex: Game.rounds.length * 2,
 | 
			
		||||
                clickable: true,
 | 
			
		||||
                draggable: false,
 | 
			
		||||
                icon: {
 | 
			
		||||
@ -236,10 +241,10 @@
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            round.guessMarker = new google.maps.Marker({
 | 
			
		||||
                map: Core.map,
 | 
			
		||||
                map: Game.map,
 | 
			
		||||
                visible: !hidden,
 | 
			
		||||
                position: guessPosition,
 | 
			
		||||
                zIndex: Core.rounds.length,
 | 
			
		||||
                zIndex: Game.rounds.length,
 | 
			
		||||
                clickable: false,
 | 
			
		||||
                draggable: false,
 | 
			
		||||
                icon: {
 | 
			
		||||
@ -259,7 +264,7 @@
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            round.line = new google.maps.Polyline({
 | 
			
		||||
                map: Core.map,
 | 
			
		||||
                map: Game.map,
 | 
			
		||||
                visible: !hidden,
 | 
			
		||||
                path: [
 | 
			
		||||
                    position,
 | 
			
		||||
@ -310,8 +315,8 @@
 | 
			
		||||
 | 
			
		||||
            var resultBounds = new google.maps.LatLngBounds();
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < Core.rounds.length; ++i) {
 | 
			
		||||
                var round = Core.rounds[i];
 | 
			
		||||
            for (var i = 0; i < Game.rounds.length; ++i) {
 | 
			
		||||
                var round = Game.rounds[i];
 | 
			
		||||
 | 
			
		||||
                round.realMarker.setIcon({
 | 
			
		||||
                    url: '/static/img/markers/marker-green-empty.svg',
 | 
			
		||||
@ -335,32 +340,32 @@
 | 
			
		||||
                resultBounds.extend(round.guessPosition);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Core.map.fitBounds(resultBounds);
 | 
			
		||||
            Game.map.fitBounds(resultBounds);
 | 
			
		||||
 | 
			
		||||
            document.getElementById('scoreSum').innerHTML = String(Core.scoreSum);
 | 
			
		||||
            document.getElementById('scoreSum').innerHTML = String(Game.scoreSum);
 | 
			
		||||
 | 
			
		||||
            var scoreBarProperties = Core.calculateScoreBarProperties(Core.scoreSum, Core.NUMBER_OF_ROUNDS * Core.MAX_SCORE);
 | 
			
		||||
            var scoreBarProperties = Game.calculateScoreBarProperties(Game.scoreSum, Game.NUMBER_OF_ROUNDS * Game.MAX_SCORE);
 | 
			
		||||
            var scoreBar = document.getElementById('scoreBar');
 | 
			
		||||
            scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
 | 
			
		||||
            scoreBar.style.width = scoreBarProperties.width;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        rewriteGoogleLink: function () {
 | 
			
		||||
            if (!Core.googleLink) {
 | 
			
		||||
            if (!Game.googleLink) {
 | 
			
		||||
                var anchors = document.getElementById('panorama').getElementsByTagName('a');
 | 
			
		||||
                for (var i = 0; i < anchors.length; i++) {
 | 
			
		||||
                    var a = anchors[i];
 | 
			
		||||
                    if (a.href.indexOf('maps.google.com/maps') !== -1) {
 | 
			
		||||
                        Core.googleLink = a;
 | 
			
		||||
                        Game.googleLink = a;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                if (Core.googleLink) {
 | 
			
		||||
                    Core.googleLink.title = 'Google Maps';
 | 
			
		||||
                    Core.googleLink.href = 'https://maps.google.com/maps';
 | 
			
		||||
                if (Game.googleLink) {
 | 
			
		||||
                    Game.googleLink.title = 'Google Maps';
 | 
			
		||||
                    Game.googleLink.href = 'https://maps.google.com/maps';
 | 
			
		||||
                }
 | 
			
		||||
            }, 1);
 | 
			
		||||
        }
 | 
			
		||||
@ -381,27 +386,27 @@
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!('ontouchstart' in document.documentElement)) {
 | 
			
		||||
        Core.adaptGuess = true;
 | 
			
		||||
        Game.adaptGuess = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Core.map = new google.maps.Map(document.getElementById('map'), {
 | 
			
		||||
    Game.map = new google.maps.Map(document.getElementById('map'), {
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        clickableIcons: false,
 | 
			
		||||
        draggingCursor: 'grabbing'
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.map.addListener('click', function (e) {
 | 
			
		||||
        if (Core.rounds[Core.rounds.length - 1].guessPosition) {
 | 
			
		||||
    Game.map.addListener('click', function (e) {
 | 
			
		||||
        if (Game.rounds[Game.rounds.length - 1].guessPosition) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Core.guessMarker) {
 | 
			
		||||
            Core.guessMarker.setPosition(e.latLng);
 | 
			
		||||
        if (Game.guessMarker) {
 | 
			
		||||
            Game.guessMarker.setPosition(e.latLng);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Core.guessMarker = new google.maps.Marker({
 | 
			
		||||
            map: Core.map,
 | 
			
		||||
        Game.guessMarker = new google.maps.Marker({
 | 
			
		||||
            map: Game.map,
 | 
			
		||||
            position: e.latLng,
 | 
			
		||||
            clickable: false,
 | 
			
		||||
            draggable: true,
 | 
			
		||||
@ -424,22 +429,22 @@
 | 
			
		||||
        document.getElementById('guessButton').disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
 | 
			
		||||
    Game.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
 | 
			
		||||
        disableDefaultUI: true,
 | 
			
		||||
        linksControl: true,
 | 
			
		||||
        showRoadLabels: false,
 | 
			
		||||
        motionTracking: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.panorama.addListener('position_changed', function () {
 | 
			
		||||
        Core.rewriteGoogleLink();
 | 
			
		||||
    Game.panorama.addListener('position_changed', function () {
 | 
			
		||||
        Game.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.panorama.addListener('pov_changed', function () {
 | 
			
		||||
        Core.rewriteGoogleLink();
 | 
			
		||||
    Game.panorama.addListener('pov_changed', function () {
 | 
			
		||||
        Game.rewriteGoogleLink();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Core.initialize();
 | 
			
		||||
    Game.initialize();
 | 
			
		||||
 | 
			
		||||
    document.getElementById('showGuessButton').onclick = function () {
 | 
			
		||||
        this.style.visibility = 'hidden';
 | 
			
		||||
@ -452,24 +457,18 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.getElementById('guessButton').onclick = function () {
 | 
			
		||||
        if (!Core.guessMarker) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.disabled = true;
 | 
			
		||||
 | 
			
		||||
        Core.evaluateGuess();
 | 
			
		||||
        Game.evaluateGuess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.getElementById('continueButton').onclick = function () {
 | 
			
		||||
        Core.resetRound();
 | 
			
		||||
        Game.resetRound();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.getElementById('showSummaryButton').onclick = function () {
 | 
			
		||||
        Core.showSummary();
 | 
			
		||||
        Game.showSummary();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.getElementById('startNewGameButton').onclick = function () {
 | 
			
		||||
        Core.resetGame();
 | 
			
		||||
        Game.reset();
 | 
			
		||||
    }
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,30 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
    var MapEditor = {
 | 
			
		||||
        metadata: {
 | 
			
		||||
            name: null,
 | 
			
		||||
            description: null
 | 
			
		||||
        },
 | 
			
		||||
        map: null,
 | 
			
		||||
        panorama: null,
 | 
			
		||||
        selectedMarker: null,
 | 
			
		||||
        added: {},
 | 
			
		||||
        edited: {},
 | 
			
		||||
        deleted: {},
 | 
			
		||||
 | 
			
		||||
        editMetadata: function () {
 | 
			
		||||
            var form = document.getElementById('metadataForm');
 | 
			
		||||
 | 
			
		||||
            MapEditor.metadata.name = form.elements.name.value;
 | 
			
		||||
            MapEditor.metadata.description = form.elements.description.value;
 | 
			
		||||
 | 
			
		||||
            document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
 | 
			
		||||
 | 
			
		||||
            document.getElementById('metadata').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
            document.getElementById('saveButton').disabled = false;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        getPlace: function (placeId, marker) {
 | 
			
		||||
            var xhr = new XMLHttpRequest();
 | 
			
		||||
@ -13,28 +35,85 @@
 | 
			
		||||
                if (!this.response.panoId) {
 | 
			
		||||
                    document.getElementById('noPano').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
                    marker.noPano = true;
 | 
			
		||||
                    places[marker.placeId].panoId = -1;
 | 
			
		||||
                    places[marker.placeId].noPano = true;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                MapEditor.loadPano(this.response.panoId);
 | 
			
		||||
                places[marker.placeId].panoId = this.response.panoId;
 | 
			
		||||
                places[marker.placeId].noPano = false;
 | 
			
		||||
 | 
			
		||||
                MapEditor.loadPano(this.response.panoId, places[marker.placeId].pov);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('GET', '/admin/place.json/' + placeId, true);
 | 
			
		||||
            xhr.send();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        loadPano: function (panoId) {
 | 
			
		||||
        loadPano: function (panoId, pov) {
 | 
			
		||||
            MapEditor.panorama.setVisible(true);
 | 
			
		||||
            MapEditor.panorama.setPov({ heading: 0, pitch: 0 });
 | 
			
		||||
            MapEditor.panorama.setZoom(0);
 | 
			
		||||
            MapEditor.panorama.setPov({ heading: pov.heading, pitch: pov.pitch });
 | 
			
		||||
            MapEditor.panorama.setZoom(pov.zoom);
 | 
			
		||||
            MapEditor.panorama.setPano(panoId);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        select: function (marker) {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
        loadPanoForNewPlace: function (panoLocationData) {
 | 
			
		||||
            var placeId = MapEditor.selectedMarker.placeId;
 | 
			
		||||
 | 
			
		||||
            if (!panoLocationData) {
 | 
			
		||||
                places[placeId].panoId = -1;
 | 
			
		||||
                places[placeId].noPano = true;
 | 
			
		||||
 | 
			
		||||
                document.getElementById('noPano').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var latLng = panoLocationData.latLng;
 | 
			
		||||
 | 
			
		||||
            places[placeId].panoId = panoLocationData.pano;
 | 
			
		||||
            places[placeId].lat = latLng.lat();
 | 
			
		||||
            places[placeId].lng = latLng.lng();
 | 
			
		||||
 | 
			
		||||
            MapEditor.selectedMarker.setLatLng({ lat: places[placeId].lat, lng: places[placeId].lng });
 | 
			
		||||
            MapEditor.map.panTo(MapEditor.selectedMarker.getLatLng());
 | 
			
		||||
 | 
			
		||||
            MapEditor.panorama.setVisible(true);
 | 
			
		||||
            MapEditor.panorama.setPov({ heading: 0.0, pitch: 0.0 });
 | 
			
		||||
            MapEditor.panorama.setZoom(0.0);
 | 
			
		||||
            MapEditor.panorama.setPano(panoLocationData.pano);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        requestPanoData: function (location, canBeIndoor) {
 | 
			
		||||
            var sv = new google.maps.StreetViewService();
 | 
			
		||||
 | 
			
		||||
            sv.getPanorama({
 | 
			
		||||
                location: location,
 | 
			
		||||
                preference: google.maps.StreetViewPreference.NEAREST,
 | 
			
		||||
                radius: 100,
 | 
			
		||||
                source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
 | 
			
		||||
            }, function (data, status) {
 | 
			
		||||
                var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
 | 
			
		||||
 | 
			
		||||
                if (panoLocationData === null && !canBeIndoor) {
 | 
			
		||||
                    MapEditor.requestPanoData(location, true);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
                MapEditor.loadPanoForNewPlace(panoLocationData);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        select: function (marker) {
 | 
			
		||||
            if (MapEditor.selectedMarker === marker) {
 | 
			
		||||
                MapEditor.closePlace();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.getElementById('metadata').classList.add('selected');
 | 
			
		||||
            document.getElementById('map').classList.add('selected');
 | 
			
		||||
            document.getElementById('control').classList.add('selected');
 | 
			
		||||
            document.getElementById('noPano').style.visibility = 'hidden';
 | 
			
		||||
@ -44,24 +123,195 @@
 | 
			
		||||
            MapEditor.resetSelected();
 | 
			
		||||
            MapEditor.selectedMarker = marker;
 | 
			
		||||
 | 
			
		||||
            marker.setIcon(IconCollection.iconBlue);
 | 
			
		||||
            marker.setZIndexOffset(2000);
 | 
			
		||||
 | 
			
		||||
            MapEditor.map.invalidateSize(true);
 | 
			
		||||
            MapEditor.map.panTo(marker.getLatLng());
 | 
			
		||||
 | 
			
		||||
            MapEditor.panorama.setVisible(false);
 | 
			
		||||
 | 
			
		||||
            MapEditor.getPlace(marker.placeId, marker);
 | 
			
		||||
            if (marker.placeId) {
 | 
			
		||||
                marker.setIcon(IconCollection.iconBlue);
 | 
			
		||||
                marker.setZIndexOffset(2000);
 | 
			
		||||
 | 
			
		||||
                document.getElementById('deleteButton').style.display = 'block';
 | 
			
		||||
 | 
			
		||||
                if (places[marker.placeId].panoId) {
 | 
			
		||||
                    if (places[marker.placeId].panoId === -1) {
 | 
			
		||||
                        document.getElementById('noPano').style.visibility = 'visible';
 | 
			
		||||
                    } else {
 | 
			
		||||
                        MapEditor.loadPano(places[marker.placeId].panoId, places[marker.placeId].pov);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
                MapEditor.getPlace(marker.placeId, marker);
 | 
			
		||||
            } else {
 | 
			
		||||
                marker.placeId = 'new_' + new Date().getTime();
 | 
			
		||||
 | 
			
		||||
                var latLng = marker.getLatLng();
 | 
			
		||||
 | 
			
		||||
                places[marker.placeId] = { id: null, lat: latLng.lat, lng: latLng.lng, panoId: null, pov: { heading: 0.0, pitch: 0.0, zoom: 0 }, noPano: false };
 | 
			
		||||
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
                MapEditor.requestPanoData(latLng);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        resetSelected: function () {
 | 
			
		||||
        resetSelected: function (del) {
 | 
			
		||||
            if (!MapEditor.selectedMarker) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            MapEditor.selectedMarker.setIcon(MapEditor.selectedMarker.noPano ? IconCollection.iconRed : IconCollection.iconGreen);
 | 
			
		||||
            MapEditor.selectedMarker.setZIndexOffset(1000);
 | 
			
		||||
            var placeId = MapEditor.selectedMarker.placeId
 | 
			
		||||
 | 
			
		||||
            if (places[placeId].id && !del) {
 | 
			
		||||
                MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
 | 
			
		||||
                MapEditor.selectedMarker.setZIndexOffset(1000);
 | 
			
		||||
            } else {
 | 
			
		||||
                delete places[placeId];
 | 
			
		||||
                MapEditor.map.removeLayer(MapEditor.selectedMarker);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            document.getElementById('deleteButton').style.display = 'none';
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        applyPlace: function () {
 | 
			
		||||
            var placeId = MapEditor.selectedMarker.placeId;
 | 
			
		||||
 | 
			
		||||
            if (!places[placeId].noPano) {
 | 
			
		||||
                var latLng = MapEditor.panorama.getPosition();
 | 
			
		||||
                var pov = MapEditor.panorama.getPov();
 | 
			
		||||
                var zoom = MapEditor.panorama.getZoom();
 | 
			
		||||
 | 
			
		||||
                places[placeId].lat = latLng.lat();
 | 
			
		||||
                places[placeId].lng = latLng.lng();
 | 
			
		||||
                places[placeId].panoId = MapEditor.panorama.getPano();
 | 
			
		||||
                places[placeId].pov = { heading: pov.heading, pitch: pov.pitch, zoom: zoom };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!places[placeId].id) {
 | 
			
		||||
                places[placeId].id = placeId;
 | 
			
		||||
                MapEditor.added[placeId] = places[placeId];
 | 
			
		||||
 | 
			
		||||
                document.getElementById('added').innerHTML = String(Object.keys(MapEditor.added).length);
 | 
			
		||||
 | 
			
		||||
                document.getElementById('deleteButton').style.display = 'block';
 | 
			
		||||
            } else {
 | 
			
		||||
                if (!MapEditor.added[placeId]) {
 | 
			
		||||
                    MapEditor.edited[placeId] = places[placeId];
 | 
			
		||||
 | 
			
		||||
                    document.getElementById('edited').innerHTML = String(Object.keys(MapEditor.edited).length);
 | 
			
		||||
                } else {
 | 
			
		||||
                    MapEditor.added[placeId] = places[placeId];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            MapEditor.selectedMarker.setLatLng({ lat: places[placeId].lat, lng: places[placeId].lng });
 | 
			
		||||
 | 
			
		||||
            document.getElementById('saveButton').disabled = false;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        closePlace: function (del) {
 | 
			
		||||
            document.getElementById('metadata').classList.remove('selected')
 | 
			
		||||
            document.getElementById('map').classList.remove('selected');
 | 
			
		||||
            document.getElementById('control').classList.remove('selected');
 | 
			
		||||
            document.getElementById('noPano').style.visibility = 'hidden';
 | 
			
		||||
            document.getElementById('panorama').style.visibility = 'hidden';
 | 
			
		||||
            document.getElementById('placeControl').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
            MapEditor.resetSelected(del);
 | 
			
		||||
            MapEditor.selectedMarker = null;
 | 
			
		||||
 | 
			
		||||
            MapEditor.map.invalidateSize(true);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        deletePlace: function () {
 | 
			
		||||
            var placeId = MapEditor.selectedMarker.placeId;
 | 
			
		||||
 | 
			
		||||
            if (places[placeId].id && !MapEditor.added[placeId]) {
 | 
			
		||||
                MapEditor.deleted[placeId] = places[placeId];
 | 
			
		||||
 | 
			
		||||
                document.getElementById('deleted').innerHTML = String(Object.keys(MapEditor.deleted).length);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            MapEditor.closePlace(true);
 | 
			
		||||
 | 
			
		||||
            delete MapEditor.added[placeId];
 | 
			
		||||
            delete MapEditor.edited[placeId];
 | 
			
		||||
 | 
			
		||||
            document.getElementById('added').innerHTML = String(Object.keys(MapEditor.added).length);
 | 
			
		||||
            document.getElementById('edited').innerHTML = String(Object.keys(MapEditor.edited).length);
 | 
			
		||||
 | 
			
		||||
            document.getElementById('saveButton').disabled = false;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        saveMap: function () {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
            var data = new FormData();
 | 
			
		||||
 | 
			
		||||
            if (MapEditor.metadata.name !== null) {
 | 
			
		||||
                data.append('name', MapEditor.metadata.name);
 | 
			
		||||
            }
 | 
			
		||||
            if (MapEditor.metadata.description !== null) {
 | 
			
		||||
                data.append('description', MapEditor.metadata.description);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var placeId in MapEditor.added) {
 | 
			
		||||
                if (!MapEditor.added.hasOwnProperty(placeId)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                data.append('added[]', JSON.stringify(MapEditor.added[placeId]));
 | 
			
		||||
            }
 | 
			
		||||
            for (var placeId in MapEditor.edited) {
 | 
			
		||||
                if (!MapEditor.edited.hasOwnProperty(placeId)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                data.append('edited[]', JSON.stringify(MapEditor.edited[placeId]));
 | 
			
		||||
            }
 | 
			
		||||
            for (var placeId in MapEditor.deleted) {
 | 
			
		||||
                if (!MapEditor.deleted.hasOwnProperty(placeId)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                data.append('deleted[]', JSON.stringify(MapEditor.deleted[placeId]));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var xhr = new XMLHttpRequest();
 | 
			
		||||
            xhr.responseType = 'json';
 | 
			
		||||
            xhr.onload = function () {
 | 
			
		||||
                document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
                if (this.response.error) {
 | 
			
		||||
                    //TODO: handle this error
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                MapEditor.replacePlaceIdsToReal(this.response.added);
 | 
			
		||||
 | 
			
		||||
                MapEditor.added = {};
 | 
			
		||||
                MapEditor.edited = {};
 | 
			
		||||
                MapEditor.deleted = {};
 | 
			
		||||
 | 
			
		||||
                document.getElementById('added').innerHTML = '0';
 | 
			
		||||
                document.getElementById('edited').innerHTML = '0';
 | 
			
		||||
                document.getElementById('deleted').innerHTML = '0';
 | 
			
		||||
 | 
			
		||||
                document.getElementById('saveButton').disabled = true;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            xhr.open('POST', '/admin/saveMap/' + mapId + '/json', true);
 | 
			
		||||
            xhr.send(data);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        replacePlaceIdsToReal: function (addedPlaces) {
 | 
			
		||||
            for (var i = 0; i < addedPlaces.length; ++i) {
 | 
			
		||||
                var tempId = addedPlaces[i].tempId;
 | 
			
		||||
                var placeId = addedPlaces[i].id;
 | 
			
		||||
                places[tempId].id = placeId;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -100,6 +350,19 @@
 | 
			
		||||
        zoomControl: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    MapEditor.map.on('click', function (e) {
 | 
			
		||||
        var marker = L.marker(e.latlng, {
 | 
			
		||||
            icon: IconCollection.iconBlue,
 | 
			
		||||
            zIndexOffset: 2000
 | 
			
		||||
        })
 | 
			
		||||
            .addTo(MapEditor.map)
 | 
			
		||||
            .on('click', function () {
 | 
			
		||||
                MapEditor.select(this);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        MapEditor.select(marker);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var highResData = Util.getHighResData();
 | 
			
		||||
 | 
			
		||||
    L.tileLayer(tileUrl, {
 | 
			
		||||
@ -113,22 +376,23 @@
 | 
			
		||||
 | 
			
		||||
    MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
 | 
			
		||||
 | 
			
		||||
    for (var i = 0; i < places.length; ++i) {
 | 
			
		||||
        var marker = L.marker({ lat: places[i].lat, lng: places[i].lng }, {
 | 
			
		||||
            icon: places[i].noPano ? IconCollection.iconRed : IconCollection.iconGreen,
 | 
			
		||||
    for (var placeId in places) {
 | 
			
		||||
        if (!places.hasOwnProperty(placeId)) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var place = places[placeId];
 | 
			
		||||
 | 
			
		||||
        var marker = L.marker({ lat: place.lat, lng: place.lng }, {
 | 
			
		||||
            icon: place.noPano ? IconCollection.iconRed : IconCollection.iconGreen,
 | 
			
		||||
            zIndexOffset: 1000
 | 
			
		||||
        })
 | 
			
		||||
            .addTo(MapEditor.map)
 | 
			
		||||
            .on('click', function () {
 | 
			
		||||
                if (MapEditor.selectedMarker === this) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                MapEditor.select(this);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        marker.placeId = places[i].id;
 | 
			
		||||
        marker.noPano = places[i].noPano;
 | 
			
		||||
        marker.placeId = place.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
 | 
			
		||||
@ -140,16 +404,42 @@
 | 
			
		||||
        motionTracking: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    document.getElementById('cancelButton').onclick = function () {
 | 
			
		||||
        document.getElementById('map').classList.remove('selected');
 | 
			
		||||
        document.getElementById('control').classList.remove('selected');
 | 
			
		||||
        document.getElementById('noPano').style.visibility = 'hidden';
 | 
			
		||||
        document.getElementById('panorama').style.visibility = 'hidden';
 | 
			
		||||
        document.getElementById('placeControl').style.visibility = 'hidden';
 | 
			
		||||
    document.getElementById('mapName').onclick = function (e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        MapEditor.resetSelected();
 | 
			
		||||
        MapEditor.selectedMarker = null;
 | 
			
		||||
        var metadata = document.getElementById('metadata');
 | 
			
		||||
 | 
			
		||||
        MapEditor.map.invalidateSize(true);
 | 
			
		||||
        if (metadata.style.visibility === 'visible') {
 | 
			
		||||
            metadata.style.visibility = 'hidden';
 | 
			
		||||
        } else {
 | 
			
		||||
            metadata.style.visibility = 'visible';
 | 
			
		||||
            document.getElementById('metadataForm').elements.name.select();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('metadataForm').onsubmit = function (e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        MapEditor.editMetadata();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('closeMetadataButton').onclick = function () {
 | 
			
		||||
        document.getElementById('metadata').style.visibility = 'hidden';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('saveButton').onclick = function () {
 | 
			
		||||
        MapEditor.saveMap();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('applyButton').onclick = function () {
 | 
			
		||||
        MapEditor.applyPlace();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('closeButton').onclick = function () {
 | 
			
		||||
        MapEditor.closePlace();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.getElementById('deleteButton').onclick = function () {
 | 
			
		||||
        MapEditor.deletePlace();
 | 
			
		||||
    };
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@ -7,14 +7,18 @@ use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
use MapGuesser\Response\HtmlContent;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
use MapGuesser\Repository\MapRepository;
 | 
			
		||||
 | 
			
		||||
class GameController
 | 
			
		||||
{
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    private MapRepository $mapRepository;
 | 
			
		||||
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
        $this->mapRepository = new MapRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getGame(): IContent
 | 
			
		||||
@ -33,7 +37,9 @@ class GameController
 | 
			
		||||
 | 
			
		||||
    private function prepareGame(int $mapId)
 | 
			
		||||
    {
 | 
			
		||||
        $bounds = $this->getMapBounds($mapId);
 | 
			
		||||
        $map = $this->mapRepository->getById($mapId);
 | 
			
		||||
 | 
			
		||||
        $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
@ -45,19 +51,6 @@ class GameController
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ['mapId' => $mapId, 'bounds' => $bounds->toArray()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getMapBounds(int $mapId): Bounds
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'maps');
 | 
			
		||||
        $select->columns(['bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
 | 
			
		||||
        $select->whereId($mapId);
 | 
			
		||||
 | 
			
		||||
        $map = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
 | 
			
		||||
 | 
			
		||||
        $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
 | 
			
		||||
        return $bounds;
 | 
			
		||||
        return ['mapId' => $mapId, 'mapName' => $map['name'], 'bounds' => $bounds->toArray()];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
use MapGuesser\Repository\PlaceRepository;
 | 
			
		||||
 | 
			
		||||
class PositionController
 | 
			
		||||
class GameFlowController
 | 
			
		||||
{
 | 
			
		||||
    const NUMBER_OF_ROUNDS = 5;
 | 
			
		||||
    const MAX_SCORE = 1000;
 | 
			
		||||
@ -21,7 +21,7 @@ class PositionController
 | 
			
		||||
        $this->placeRepository = new PlaceRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPosition(): IContent
 | 
			
		||||
    public function getNewPlace(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
@ -33,11 +33,11 @@ class PositionController
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (count($state['rounds']) === 0) {
 | 
			
		||||
            $newPosition = $this->placeRepository->getForMapWithValidPano($mapId);
 | 
			
		||||
            $state['rounds'][] = $newPosition;
 | 
			
		||||
            $place = $this->placeRepository->getForMapWithValidPano($mapId);
 | 
			
		||||
            $state['rounds'][] = $place;
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
 | 
			
		||||
            $data = ['panoId' => $newPosition['panoId']];
 | 
			
		||||
            $data = ['panoId' => $place['panoId']];
 | 
			
		||||
        } else {
 | 
			
		||||
            $rounds = count($state['rounds']);
 | 
			
		||||
            $last = $state['rounds'][$rounds - 1];
 | 
			
		||||
@ -93,11 +93,11 @@ class PositionController
 | 
			
		||||
                $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
 | 
			
		||||
            $state['rounds'][] = $newPosition;
 | 
			
		||||
            $place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
 | 
			
		||||
            $state['rounds'][] = $place;
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
 | 
			
		||||
            $panoId = $newPosition['panoId'];
 | 
			
		||||
            $panoId = $place['panoId'];
 | 
			
		||||
        } else {
 | 
			
		||||
            $state['rounds'] = [];
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
@ -1,25 +1,32 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use DateTime;
 | 
			
		||||
use MapGuesser\Database\Query\Modify;
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Interfaces\Authentication\IUser;
 | 
			
		||||
use MapGuesser\Interfaces\Authorization\ISecured;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
use MapGuesser\Repository\MapRepository;
 | 
			
		||||
use MapGuesser\Repository\PlaceRepository;
 | 
			
		||||
use MapGuesser\Response\HtmlContent;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
use MapGuesser\Util\Geo\Position;
 | 
			
		||||
 | 
			
		||||
class MapAdminController implements ISecured
 | 
			
		||||
{
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    private MapRepository $mapRepository;
 | 
			
		||||
 | 
			
		||||
    private PlaceRepository $placeRepository;
 | 
			
		||||
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
        $this->mapRepository = new MapRepository();
 | 
			
		||||
        $this->placeRepository = new PlaceRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -41,15 +48,15 @@ class MapAdminController implements ISecured
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
        $bounds = $this->getMapBounds($mapId);
 | 
			
		||||
 | 
			
		||||
        $map = $this->mapRepository->getById($mapId);
 | 
			
		||||
        $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
        $places = $this->getPlaces($mapId);
 | 
			
		||||
 | 
			
		||||
        $data = ['mapId' => $mapId, 'bounds' => $bounds->toArray(), 'places' => &$places];
 | 
			
		||||
        $data = ['mapId' => $mapId, 'mapName' => $map['name'], 'mapDescription' => str_replace('<br>', '\n', $map['description']), 'bounds' => $bounds->toArray(), 'places' => &$places];
 | 
			
		||||
        return new HtmlContent('admin/map_editor', $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPlace()
 | 
			
		||||
    public function getPlace(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $placeId = (int) $this->request->query('placeId');
 | 
			
		||||
 | 
			
		||||
@ -59,34 +66,113 @@ class MapAdminController implements ISecured
 | 
			
		||||
        return new JsonContent($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getMapBounds(int $mapId): Bounds
 | 
			
		||||
    public function saveMap(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'maps');
 | 
			
		||||
        $select->columns(['bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
 | 
			
		||||
        $select->whereId($mapId);
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
        $map = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
 | 
			
		||||
        if (isset($_POST['added'])) {
 | 
			
		||||
            $addedIds = [];
 | 
			
		||||
            foreach ($_POST['added'] as $placeRaw) {
 | 
			
		||||
                $placeRaw = json_decode($placeRaw, true);
 | 
			
		||||
 | 
			
		||||
        $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
 | 
			
		||||
                $addedIds[] = ['tempId' => $placeRaw['id'], $this->placeRepository->addToMap($mapId, [
 | 
			
		||||
                    'lat' => (float) $placeRaw['lat'],
 | 
			
		||||
                    'lng' => (float) $placeRaw['lng'],
 | 
			
		||||
                    'pano_id_cached_timestamp' => $placeRaw['panoId'] === -1 ? (new DateTime('-1 day'))->format('Y-m-d H:i:s') : null
 | 
			
		||||
                ])];
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $addedIds = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($_POST['edited'])) {
 | 
			
		||||
            foreach ($_POST['edited'] as $placeRaw) {
 | 
			
		||||
                $placeRaw = json_decode($placeRaw, true);
 | 
			
		||||
 | 
			
		||||
                $this->placeRepository->modify((int) $placeRaw['id'], [
 | 
			
		||||
                    'lat' => (float) $placeRaw['lat'],
 | 
			
		||||
                    'lng' => (float) $placeRaw['lng']
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($_POST['deleted'])) {
 | 
			
		||||
            foreach ($_POST['deleted'] as $placeRaw) {
 | 
			
		||||
                $placeRaw = json_decode($placeRaw, true);
 | 
			
		||||
 | 
			
		||||
                $this->placeRepository->delete($placeRaw['id']);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $mapBounds = $this->calculateMapBounds($mapId);
 | 
			
		||||
 | 
			
		||||
        $map = [
 | 
			
		||||
            'bound_south_lat' => $mapBounds->getSouthLat(),
 | 
			
		||||
            'bound_west_lng' => $mapBounds->getWestLng(),
 | 
			
		||||
            'bound_north_lat' => $mapBounds->getNorthLat(),
 | 
			
		||||
            'bound_east_lng' => $mapBounds->getEastLng()
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isset($_POST['name'])) {
 | 
			
		||||
            $map['name'] = $_POST['name'] ? $_POST['name'] : '[unnamed map]';
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($_POST['description'])) {
 | 
			
		||||
            $map['description'] = str_replace(['\n', '\r\n'], '<br>', $_POST['description']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->saveMapData($mapId, $map);
 | 
			
		||||
 | 
			
		||||
        $data = ['added' => $addedIds];
 | 
			
		||||
        return new JsonContent($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function calculateMapBounds(int $mapId): Bounds
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'places');
 | 
			
		||||
        $select->columns(['lat', 'lng']);
 | 
			
		||||
        $select->where('map_id', '=', $mapId);
 | 
			
		||||
 | 
			
		||||
        $result = $select->execute();
 | 
			
		||||
 | 
			
		||||
        $bounds = new Bounds();
 | 
			
		||||
        while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
 | 
			
		||||
            $bounds->extend(new Position($place['lat'], $place['lng']));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $bounds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function saveMapData(int $mapId, array $map): void
 | 
			
		||||
    {
 | 
			
		||||
        $modify = new Modify(\Container::$dbConnection, 'maps');
 | 
			
		||||
        $modify->setId($mapId);
 | 
			
		||||
        $modify->fill($map);
 | 
			
		||||
        $modify->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function &getPlaces(int $mapId): array
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'places');
 | 
			
		||||
        $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
 | 
			
		||||
        $select->where('map_id', '=', $mapId);
 | 
			
		||||
        $select->orderBy('lng');
 | 
			
		||||
 | 
			
		||||
        $result = $select->execute();
 | 
			
		||||
 | 
			
		||||
        $places = [];
 | 
			
		||||
 | 
			
		||||
        while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
 | 
			
		||||
            //$panoId = ???
 | 
			
		||||
            //$pov = ???
 | 
			
		||||
            $noPano = $place['pano_id_cached_timestamp'] && $place['pano_id_cached'] === null;
 | 
			
		||||
 | 
			
		||||
            $places[] = ['id' => $place['id'], 'lat' => $place['lat'], 'lng' => $place['lng'], 'noPano' => $noPano];
 | 
			
		||||
            $places[$place['id']] = [
 | 
			
		||||
                'id' => $place['id'],
 | 
			
		||||
                'lat' => $place['lat'],
 | 
			
		||||
                'lng' => $place['lng'],
 | 
			
		||||
                'panoId' => null,
 | 
			
		||||
                'pov' => ['heading' => 0.0, 'pitch' => 0.0, 'zoom' => 0.0],
 | 
			
		||||
                'noPano' => $noPano
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $places;
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,11 @@ class Modify
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getId()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->attributes[$this->idName];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (isset($this->attributes[$this->idName])) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								src/Repository/MapRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Repository/MapRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?php namespace MapGuesser\Repository;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
 | 
			
		||||
class MapRepository
 | 
			
		||||
{
 | 
			
		||||
    public function getById(int $mapId)
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'maps');
 | 
			
		||||
        $select->columns(['id', 'name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
 | 
			
		||||
        $select->whereId($mapId);
 | 
			
		||||
 | 
			
		||||
        return $select->execute()->fetch(IResultSet::FETCH_ASSOC);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -48,6 +48,33 @@ class PlaceRepository
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addToMap(int $mapId, array $place): int
 | 
			
		||||
    {
 | 
			
		||||
        $modify = new Modify(\Container::$dbConnection, 'places');
 | 
			
		||||
        $modify->set('map_id', $mapId);
 | 
			
		||||
        $modify->fill($place);
 | 
			
		||||
        $modify->save();
 | 
			
		||||
 | 
			
		||||
        return $modify->getId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function modify(int $id, array $place): void
 | 
			
		||||
    {
 | 
			
		||||
        $modify = new Modify(\Container::$dbConnection, 'places');
 | 
			
		||||
        $modify->setId($id);
 | 
			
		||||
        $modify->set('pano_id_cached', null);
 | 
			
		||||
        $modify->set('pano_id_cached_timestamp', null);
 | 
			
		||||
        $modify->fill($place);
 | 
			
		||||
        $modify->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function delete(int $id): void
 | 
			
		||||
    {
 | 
			
		||||
        $modify = new Modify(\Container::$dbConnection, 'places');
 | 
			
		||||
        $modify->setId($id);
 | 
			
		||||
        $modify->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function selectFromDbById(int $placeId): array
 | 
			
		||||
    {
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'places');
 | 
			
		||||
@ -66,7 +93,7 @@ class PlaceRepository
 | 
			
		||||
        $select->where('id', 'NOT IN', $exclude);
 | 
			
		||||
        $select->where('map_id', '=', $mapId);
 | 
			
		||||
 | 
			
		||||
        $numberOfPlaces = $select->count();// TODO: what if 0
 | 
			
		||||
        $numberOfPlaces = $select->count(); // TODO: what if 0
 | 
			
		||||
        $randomOffset = random_int(0, $numberOfPlaces - 1);
 | 
			
		||||
 | 
			
		||||
        $select->orderBy('id');
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,26 @@ class Bounds
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSouthLat(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->southLat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getWestLng(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->westLng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getNorthLat(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->northLat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getEastLng(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->eastLng;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function calculateApproximateArea(): float
 | 
			
		||||
    {
 | 
			
		||||
        $dLat = $this->northLat - $this->southLat;
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,55 @@
 | 
			
		||||
<?php $cssFiles = ['/static/node_modules/leaflet/dist/leaflet.css', '/static/css/map_editor.css']; ?>
 | 
			
		||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
 | 
			
		||||
<?php require ROOT . '/views/templates/header.php'; ?>
 | 
			
		||||
<div class="header small">
 | 
			
		||||
    <div class="grid">
 | 
			
		||||
        <h1>
 | 
			
		||||
            <a href="/admin/maps" title="Back to maps">
 | 
			
		||||
                <img class="inline" src="/static/img/icon.svg">
 | 
			
		||||
                <span>MapGuesser</span>
 | 
			
		||||
            </a>
 | 
			
		||||
        </h1>
 | 
			
		||||
        <p>
 | 
			
		||||
            <span class="bold"><a href="#" id="mapName"><?= $mapName ?></a></span><!--
 | 
			
		||||
         --><span><!--
 | 
			
		||||
                <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
 | 
			
		||||
             --><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                    <path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/>
 | 
			
		||||
                    <path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/>
 | 
			
		||||
                    <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                <span id="added" class="bold">0</span><!--
 | 
			
		||||
         --></span><!--
 | 
			
		||||
         --><span><!--
 | 
			
		||||
                <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
 | 
			
		||||
             --><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                    <path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>                    <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                <span id="edited" class="bold">0</span><!--
 | 
			
		||||
         --></span><!--
 | 
			
		||||
         --><span><!--
 | 
			
		||||
                <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
 | 
			
		||||
             --><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                    <path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/>
 | 
			
		||||
                    <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                <span id="deleted" class="bold">0</span><!--
 | 
			
		||||
         --></span>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div id="metadata">
 | 
			
		||||
    <form id="metadataForm">
 | 
			
		||||
        <input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map">
 | 
			
		||||
        <textarea  class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea>
 | 
			
		||||
        <div style="text-align: right;">
 | 
			
		||||
            <button id="closeMetadataButton" class="gray marginTop" type="button">Close</button>
 | 
			
		||||
            <button class="marginTop" type="submit">Apply</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
<div id="map"></div>
 | 
			
		||||
<div id="control">
 | 
			
		||||
    <button id="saveButton" class="fullWidth">Save</button>
 | 
			
		||||
    <a class="button gray fullWidth marginTop" href="/admin/maps" title="Back to maps">Back to maps</a>
 | 
			
		||||
    <button id="saveButton" class="fullWidth" disabled>Save</button>
 | 
			
		||||
</div>
 | 
			
		||||
<div id="panorama"></div>
 | 
			
		||||
<div id="noPano">
 | 
			
		||||
@ -12,7 +57,7 @@
 | 
			
		||||
</div>
 | 
			
		||||
<div id="placeControl">
 | 
			
		||||
    <button id="applyButton" class="fullWidth">Apply</button>
 | 
			
		||||
    <button id="cancelButton" class="gray fullWidth marginTop">Cancel</button>
 | 
			
		||||
    <button id="closeButton" class="gray fullWidth marginTop">Close</button>
 | 
			
		||||
    <button id="deleteButton" class="red fullWidth marginTop">Delete</button>
 | 
			
		||||
</div>
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,11 @@
 | 
			
		||||
                <span>MapGuesser</span>
 | 
			
		||||
            </a>
 | 
			
		||||
        </h1>
 | 
			
		||||
        <p id="roundInfo">Round: <span id="currentRound" class="mono bold"></span> | Score: <span id="currentScoreSum" class="mono bold"></span></p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <span id="mapName" class="bold"><?= $mapName ?></span><!--
 | 
			
		||||
         --><span>Round <span id="currentRound" class="bold"></span></span><!--
 | 
			
		||||
         --><span>Score <span id="currentScoreSum" class="bold"></span></span>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div id="cover"></div>
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
            <link href="<?= $cssFile ?>" rel="stylesheet">
 | 
			
		||||
        <?php endforeach; ?>
 | 
			
		||||
    <?php endif; ?>
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&family=Roboto+Mono:wght@300;500&display=swap" rel="stylesheet">
 | 
			
		||||
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
 | 
			
		||||
    <link rel="icon" type="image/png" sizes="192x192" href="/static/img/favicon/192x192.png">
 | 
			
		||||
    <link rel="icon" type="image/png" sizes="96x96" href="/static/img/favicon/96x96.png">
 | 
			
		||||
    <link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon/32x32.png">
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user