Merged in feature/MAPG-103-map-editor-ui-elements (pull request #88)

Feature/MAPG-103 map editor ui elements
This commit is contained in:
Bence Pőcze 2020-06-09 19:40:33 +00:00
commit 16bcbaecbe
16 changed files with 720 additions and 193 deletions

View File

@ -19,13 +19,14 @@ Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsCont
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']); $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']); $routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
$routeCollection->get('position-json', '{mapId}/position.json', [MapGuesser\Controller\PositionController::class, 'getPosition']); $routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\PositionController::class, 'evaluateGuess']); $routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
}); });
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.maps', 'maps', [MapGuesser\Controller\MapAdminController::class, 'getMaps']); $routeCollection->get('admin.maps', 'maps', [MapGuesser\Controller\MapAdminController::class, 'getMaps']);
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']); $routeCollection->get('admin.mapEditor', 'mapEditor/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']); $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)); $match = Container::$routeCollection->match($method, explode('/', $url));

View File

@ -1,13 +1,3 @@
#roundInfo {
line-height: inherit;
text-align: right;
}
#roundInfo p {
font-size: 16px;
line-height: inherit;
}
#panorama { #panorama {
width: 100%; width: 100%;
height: calc(100% - 40px); height: calc(100% - 40px);
@ -110,6 +100,9 @@
} }
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {
#mapName {
display: none;
}
#showGuessButtonContainer { #showGuessButtonContainer {
position: absolute; position: absolute;
left: 20px; left: 20px;

View File

@ -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 { #map {
width: 100%; width: 100%;
height: calc(100% - 50px); height: calc(100% - 40px);
z-index: 1; z-index: 1;
} }
.leaflet-container {
cursor: crosshair;
}
#panorama { #panorama {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
@ -24,53 +42,65 @@
#control { #control {
position: absolute; position: absolute;
top: 50px;
right: 10px;
width: 125px; width: 125px;
z-index: 3; z-index: 3;
} }
#placeControl { #placeControl {
position: absolute; position: absolute;
right: 10px;
z-index: 3; z-index: 3;
width: 100px; width: 100px;
visibility: hidden; visibility: hidden;
} }
#deleteButton {
display: none;
}
@media screen and (max-width: 999px) and (min-height: 600px) { @media screen and (max-width: 999px) and (min-height: 600px) {
#metadata {
width: calc(100% - 155px);
}
#map.selected { #map.selected {
height: calc(50% - 25px); height: calc(50% - 20px);
} }
#panorama, #noPano { #panorama, #noPano {
left: 0; left: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
height: calc(50% - 25px); height: calc(50% - 20px);
}
#control {
right: 10px;
top: 60px;
} }
#placeControl { #placeControl {
right: 10px; top: calc(50% + 30px);
top: calc(50% + 35px);
} }
} }
@media screen and (min-width: 1000px), (max-height: 599px) { @media screen and (min-width: 1000px), (max-height: 599px) {
#metadata {
width: calc(50% - 20px);
}
#metadata.selected {
top: 95px;
}
#map.selected { #map.selected {
width: 50%; width: 50%;
} }
#panorama, #noPano { #panorama, #noPano {
top: 50px; top: 40px;
bottom: 0; bottom: 0;
right: 0; right: 0;
width: 50%; width: 50%;
} }
#control, #placeControl { #placeControl {
right: 10px; top: 50px;
top: 60px; }
#modified.selected {
right: calc(50% + 10px);
} }
#control.selected { #control.selected {
right: calc(50% + 10px); right: calc(50% + 10px);
top: 60px;
} }
} }

View File

@ -98,7 +98,7 @@ sub {
} }
svg.inline, img.inline { svg.inline, img.inline {
display: inline-block; display: inline;
width: 1em; width: 1em;
height: 1em; height: 1em;
vertical-align: -0.15em; vertical-align: -0.15em;
@ -228,6 +228,24 @@ div.header.small {
line-height: 40px; 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 { div.main {
padding: 6px 12px; padding: 6px 12px;
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
(function () { (function () {
var Core = { var Game = {
NUMBER_OF_ROUNDS: 5, NUMBER_OF_ROUNDS: 5,
MAX_SCORE: 1000, MAX_SCORE: 1000,
@ -17,54 +17,54 @@
initialize: function () { initialize: function () {
document.getElementById('loading').style.visibility = 'visible'; document.getElementById('loading').style.visibility = 'visible';
document.getElementById('cover').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'; document.getElementById('currentScoreSum').innerHTML = '0/0';
Core.map.setOptions({ Game.map.setOptions({
draggableCursor: 'crosshair' draggableCursor: 'crosshair'
}); });
Core.map.fitBounds(mapBounds); Game.map.fitBounds(mapBounds);
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = 'json'; xhr.responseType = 'json';
xhr.onload = function () { xhr.onload = function () {
document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('cover').style.visibility = 'hidden';
if (this.response.error) { if (this.response.error) {
//TODO: handle this error //TODO: handle this error
return; return;
} }
document.getElementById('loading').style.visibility = 'hidden'; Game.panoId = this.response.panoId;
document.getElementById('cover').style.visibility = 'hidden';
Core.panoId = this.response.panoId;
if (this.response.history) { if (this.response.history) {
for (var i = 0; i < this.response.history.length; ++i) { for (var i = 0; i < this.response.history.length; ++i) {
var round = this.response.history[i]; var round = this.response.history[i];
Core.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null }); Game.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
Core.addRealGuessPair(round.position, round.guessPosition, true); Game.addRealGuessPair(round.position, round.guessPosition, true);
Core.scoreSum += round.score; Game.scoreSum += round.score;
} }
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);
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_SCORE); 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(); xhr.send();
}, },
resetGame: function () { reset: function () {
if (Core.guessMarker) { if (Game.guessMarker) {
Core.guessMarker.setMap(null); Game.guessMarker.setMap(null);
Core.guessMarker = null; Game.guessMarker = null;
} }
for (var i = 0; i < Core.rounds.length; ++i) { for (var i = 0; i < Game.rounds.length; ++i) {
var round = Core.rounds[i]; var round = Game.rounds[i];
if (round.realMarker && round.guessMarker && round.line) { if (round.realMarker && round.guessMarker && round.line) {
round.realMarker.setMap(null); round.realMarker.setMap(null);
@ -73,8 +73,8 @@
} }
} }
Core.rounds = []; Game.rounds = [];
Core.scoreSum = 0; Game.scoreSum = 0;
var distanceInfo = document.getElementById('distanceInfo'); var distanceInfo = document.getElementById('distanceInfo');
distanceInfo.children[0].style.display = null; distanceInfo.children[0].style.display = null;
@ -89,14 +89,14 @@
document.getElementById('guess').style.visibility = null; document.getElementById('guess').style.visibility = null;
document.getElementById('guess').classList.remove('result'); document.getElementById('guess').classList.remove('result');
Core.initialize(); Game.initialize();
}, },
resetRound: function () { resetRound: function () {
document.getElementById('scoreBar').style.width = null; document.getElementById('scoreBar').style.width = null;
if (Core.rounds.length > 0) { if (Game.rounds.length > 0) {
var lastRound = Core.rounds[Core.rounds.length - 1]; var lastRound = Game.rounds[Game.rounds.length - 1];
lastRound.realMarker.setVisible(false); lastRound.realMarker.setVisible(false);
lastRound.guessMarker.setVisible(false); lastRound.guessMarker.setVisible(false);
@ -108,20 +108,20 @@
document.getElementById('guess').style.visibility = null; document.getElementById('guess').style.visibility = null;
document.getElementById('guess').classList.remove('result') document.getElementById('guess').classList.remove('result')
Core.map.setOptions({ Game.map.setOptions({
draggableCursor: 'crosshair' draggableCursor: 'crosshair'
}); });
Core.map.fitBounds(mapBounds); Game.map.fitBounds(mapBounds);
Core.startNewRound(); Game.startNewRound();
}, },
startNewRound: function () { 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) { handleErrorResponse: function (error) {
@ -132,7 +132,7 @@
xhr.onload = function () { xhr.onload = function () {
mapBounds = this.response.bounds; mapBounds = this.response.bounds;
Core.resetGame(); Game.reset();
}; };
xhr.open('GET', '/game/' + mapId + '/json', true); xhr.open('GET', '/game/' + mapId + '/json', true);
@ -140,20 +140,25 @@
}, },
loadPano: function (panoId) { loadPano: function (panoId) {
if (Core.adaptGuess) { if (Game.adaptGuess) {
document.getElementById('guess').classList.add('adapt'); document.getElementById('guess').classList.add('adapt');
} }
Core.panorama.setPov({ heading: 0, pitch: 0 }); Game.panorama.setPov({ heading: 0, pitch: 0 });
Core.panorama.setZoom(0); Game.panorama.setZoom(0);
Core.panorama.setPano(panoId); Game.panorama.setPano(panoId);
}, },
evaluateGuess: function () { evaluateGuess: function () {
var guessPosition = Core.guessMarker.getPosition().toJSON(); if (!Game.guessMarker) {
Core.rounds[Core.rounds.length - 1].guessPosition = guessPosition; 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('guess').classList.remove('adapt');
} }
document.getElementById('loading').style.visibility = 'visible'; document.getElementById('loading').style.visibility = 'visible';
@ -167,45 +172,45 @@
xhr.responseType = 'json'; xhr.responseType = 'json';
xhr.onload = function () { xhr.onload = function () {
if (this.response.error) { if (this.response.error) {
Core.handleErrorResponse(this.response.error); Game.handleErrorResponse(this.response.error);
return; return;
} }
Core.guessMarker.setMap(null); Game.guessMarker.setMap(null);
Core.guessMarker = null; Game.guessMarker = null;
document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('guess').classList.add('result'); document.getElementById('guess').classList.add('result');
Core.scoreSum += this.response.result.score; Game.scoreSum += this.response.result.score;
document.getElementById('currentScoreSum').innerHTML = String(Core.scoreSum) + '/' + String(Core.rounds.length * Core.MAX_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; Game.rounds[Game.rounds.length - 1].position = this.response.result.position;
Core.addRealGuessPair(this.response.result.position, guessPosition); Game.addRealGuessPair(this.response.result.position, guessPosition);
var resultBounds = new google.maps.LatLngBounds(); var resultBounds = new google.maps.LatLngBounds();
resultBounds.extend(this.response.result.position); resultBounds.extend(this.response.result.position);
resultBounds.extend(guessPosition); resultBounds.extend(guessPosition);
Core.map.setOptions({ Game.map.setOptions({
draggableCursor: 'grab' draggableCursor: 'grab'
}); });
Core.map.fitBounds(resultBounds); Game.map.fitBounds(resultBounds);
document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance); document.getElementById('distance').innerHTML = Util.printDistanceForHuman(this.response.result.distance);
document.getElementById('score').innerHTML = this.response.result.score; 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'); var scoreBar = document.getElementById('scoreBar');
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor; scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
scoreBar.style.width = scoreBarProperties.width; 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('continueButton').style.display = 'none';
document.getElementById('showSummaryButton').style.display = 'block'; document.getElementById('showSummaryButton').style.display = 'block';
} }
Core.panoId = this.response.panoId; Game.panoId = this.response.panoId;
}; };
xhr.open('POST', '/game/' + mapId + '/guess.json', true); xhr.open('POST', '/game/' + mapId + '/guess.json', true);
@ -213,14 +218,14 @@
}, },
addRealGuessPair: function (position, guessPosition, hidden) { 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({ round.realMarker = new google.maps.Marker({
map: Core.map, map: Game.map,
visible: !hidden, visible: !hidden,
position: position, position: position,
title: 'Open in Google Maps', title: 'Open in Google Maps',
zIndex: Core.rounds.length * 2, zIndex: Game.rounds.length * 2,
clickable: true, clickable: true,
draggable: false, draggable: false,
icon: { icon: {
@ -236,10 +241,10 @@
}); });
round.guessMarker = new google.maps.Marker({ round.guessMarker = new google.maps.Marker({
map: Core.map, map: Game.map,
visible: !hidden, visible: !hidden,
position: guessPosition, position: guessPosition,
zIndex: Core.rounds.length, zIndex: Game.rounds.length,
clickable: false, clickable: false,
draggable: false, draggable: false,
icon: { icon: {
@ -259,7 +264,7 @@
}); });
round.line = new google.maps.Polyline({ round.line = new google.maps.Polyline({
map: Core.map, map: Game.map,
visible: !hidden, visible: !hidden,
path: [ path: [
position, position,
@ -310,8 +315,8 @@
var resultBounds = new google.maps.LatLngBounds(); var resultBounds = new google.maps.LatLngBounds();
for (var i = 0; i < Core.rounds.length; ++i) { for (var i = 0; i < Game.rounds.length; ++i) {
var round = Core.rounds[i]; var round = Game.rounds[i];
round.realMarker.setIcon({ round.realMarker.setIcon({
url: '/static/img/markers/marker-green-empty.svg', url: '/static/img/markers/marker-green-empty.svg',
@ -335,32 +340,32 @@
resultBounds.extend(round.guessPosition); 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'); var scoreBar = document.getElementById('scoreBar');
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor; scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
scoreBar.style.width = scoreBarProperties.width; scoreBar.style.width = scoreBarProperties.width;
}, },
rewriteGoogleLink: function () { rewriteGoogleLink: function () {
if (!Core.googleLink) { if (!Game.googleLink) {
var anchors = document.getElementById('panorama').getElementsByTagName('a'); var anchors = document.getElementById('panorama').getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) { for (var i = 0; i < anchors.length; i++) {
var a = anchors[i]; var a = anchors[i];
if (a.href.indexOf('maps.google.com/maps') !== -1) { if (a.href.indexOf('maps.google.com/maps') !== -1) {
Core.googleLink = a; Game.googleLink = a;
break; break;
} }
} }
} }
setTimeout(function () { setTimeout(function () {
if (Core.googleLink) { if (Game.googleLink) {
Core.googleLink.title = 'Google Maps'; Game.googleLink.title = 'Google Maps';
Core.googleLink.href = 'https://maps.google.com/maps'; Game.googleLink.href = 'https://maps.google.com/maps';
} }
}, 1); }, 1);
} }
@ -381,27 +386,27 @@
}; };
if (!('ontouchstart' in document.documentElement)) { 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, disableDefaultUI: true,
clickableIcons: false, clickableIcons: false,
draggingCursor: 'grabbing' draggingCursor: 'grabbing'
}); });
Core.map.addListener('click', function (e) { Game.map.addListener('click', function (e) {
if (Core.rounds[Core.rounds.length - 1].guessPosition) { if (Game.rounds[Game.rounds.length - 1].guessPosition) {
return; return;
} }
if (Core.guessMarker) { if (Game.guessMarker) {
Core.guessMarker.setPosition(e.latLng); Game.guessMarker.setPosition(e.latLng);
return; return;
} }
Core.guessMarker = new google.maps.Marker({ Game.guessMarker = new google.maps.Marker({
map: Core.map, map: Game.map,
position: e.latLng, position: e.latLng,
clickable: false, clickable: false,
draggable: true, draggable: true,
@ -424,22 +429,22 @@
document.getElementById('guessButton').disabled = false; 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, disableDefaultUI: true,
linksControl: true, linksControl: true,
showRoadLabels: false, showRoadLabels: false,
motionTracking: false motionTracking: false
}); });
Core.panorama.addListener('position_changed', function () { Game.panorama.addListener('position_changed', function () {
Core.rewriteGoogleLink(); Game.rewriteGoogleLink();
}); });
Core.panorama.addListener('pov_changed', function () { Game.panorama.addListener('pov_changed', function () {
Core.rewriteGoogleLink(); Game.rewriteGoogleLink();
}); });
Core.initialize(); Game.initialize();
document.getElementById('showGuessButton').onclick = function () { document.getElementById('showGuessButton').onclick = function () {
this.style.visibility = 'hidden'; this.style.visibility = 'hidden';
@ -452,24 +457,18 @@
} }
document.getElementById('guessButton').onclick = function () { document.getElementById('guessButton').onclick = function () {
if (!Core.guessMarker) { Game.evaluateGuess();
return;
}
this.disabled = true;
Core.evaluateGuess();
} }
document.getElementById('continueButton').onclick = function () { document.getElementById('continueButton').onclick = function () {
Core.resetRound(); Game.resetRound();
} }
document.getElementById('showSummaryButton').onclick = function () { document.getElementById('showSummaryButton').onclick = function () {
Core.showSummary(); Game.showSummary();
} }
document.getElementById('startNewGameButton').onclick = function () { document.getElementById('startNewGameButton').onclick = function () {
Core.resetGame(); Game.reset();
} }
})(); })();

View File

@ -1,8 +1,30 @@
'use strict';
(function () { (function () {
var MapEditor = { var MapEditor = {
metadata: {
name: null,
description: null
},
map: null, map: null,
panorama: null, panorama: null,
selectedMarker: 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) { getPlace: function (placeId, marker) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -13,28 +35,85 @@
if (!this.response.panoId) { if (!this.response.panoId) {
document.getElementById('noPano').style.visibility = 'visible'; document.getElementById('noPano').style.visibility = 'visible';
marker.noPano = true; places[marker.placeId].panoId = -1;
places[marker.placeId].noPano = true;
return; 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.open('GET', '/admin/place.json/' + placeId, true);
xhr.send(); xhr.send();
}, },
loadPano: function (panoId) { loadPano: function (panoId, pov) {
MapEditor.panorama.setVisible(true); MapEditor.panorama.setVisible(true);
MapEditor.panorama.setPov({ heading: 0, pitch: 0 }); MapEditor.panorama.setPov({ heading: pov.heading, pitch: pov.pitch });
MapEditor.panorama.setZoom(0); MapEditor.panorama.setZoom(pov.zoom);
MapEditor.panorama.setPano(panoId); MapEditor.panorama.setPano(panoId);
}, },
select: function (marker) { loadPanoForNewPlace: function (panoLocationData) {
document.getElementById('loading').style.visibility = 'visible'; 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('map').classList.add('selected');
document.getElementById('control').classList.add('selected'); document.getElementById('control').classList.add('selected');
document.getElementById('noPano').style.visibility = 'hidden'; document.getElementById('noPano').style.visibility = 'hidden';
@ -44,24 +123,195 @@
MapEditor.resetSelected(); MapEditor.resetSelected();
MapEditor.selectedMarker = marker; MapEditor.selectedMarker = marker;
marker.setIcon(IconCollection.iconBlue);
marker.setZIndexOffset(2000);
MapEditor.map.invalidateSize(true); MapEditor.map.invalidateSize(true);
MapEditor.map.panTo(marker.getLatLng()); MapEditor.map.panTo(marker.getLatLng());
MapEditor.panorama.setVisible(false); MapEditor.panorama.setVisible(false);
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); 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) { if (!MapEditor.selectedMarker) {
return; return;
} }
MapEditor.selectedMarker.setIcon(MapEditor.selectedMarker.noPano ? IconCollection.iconRed : IconCollection.iconGreen); var placeId = MapEditor.selectedMarker.placeId
if (places[placeId].id && !del) {
MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
MapEditor.selectedMarker.setZIndexOffset(1000); 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 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(); var highResData = Util.getHighResData();
L.tileLayer(tileUrl, { L.tileLayer(tileUrl, {
@ -113,22 +376,23 @@
MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east })); 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) { for (var placeId in places) {
var marker = L.marker({ lat: places[i].lat, lng: places[i].lng }, { if (!places.hasOwnProperty(placeId)) {
icon: places[i].noPano ? IconCollection.iconRed : IconCollection.iconGreen, continue;
}
var place = places[placeId];
var marker = L.marker({ lat: place.lat, lng: place.lng }, {
icon: place.noPano ? IconCollection.iconRed : IconCollection.iconGreen,
zIndexOffset: 1000 zIndexOffset: 1000
}) })
.addTo(MapEditor.map) .addTo(MapEditor.map)
.on('click', function () { .on('click', function () {
if (MapEditor.selectedMarker === this) {
return;
}
MapEditor.select(this); MapEditor.select(this);
}); });
marker.placeId = places[i].id; marker.placeId = place.id;
marker.noPano = places[i].noPano;
} }
MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), { MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
@ -140,16 +404,42 @@
motionTracking: false motionTracking: false
}); });
document.getElementById('cancelButton').onclick = function () { document.getElementById('mapName').onclick = function (e) {
document.getElementById('map').classList.remove('selected'); e.preventDefault();
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(); var metadata = document.getElementById('metadata');
MapEditor.selectedMarker = null;
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();
}; };
})(); })();

View File

@ -7,14 +7,18 @@ use MapGuesser\Util\Geo\Bounds;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Repository\MapRepository;
class GameController class GameController
{ {
private IRequest $request; private IRequest $request;
private MapRepository $mapRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->mapRepository = new MapRepository();
} }
public function getGame(): IContent public function getGame(): IContent
@ -33,7 +37,9 @@ class GameController
private function prepareGame(int $mapId) 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(); $session = $this->request->session();
@ -45,19 +51,6 @@ class GameController
]); ]);
} }
return ['mapId' => $mapId, 'bounds' => $bounds->toArray()]; return ['mapId' => $mapId, 'mapName' => $map['name'], '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;
} }
} }

View File

@ -6,7 +6,7 @@ use MapGuesser\Response\JsonContent;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Repository\PlaceRepository; use MapGuesser\Repository\PlaceRepository;
class PositionController class GameFlowController
{ {
const NUMBER_OF_ROUNDS = 5; const NUMBER_OF_ROUNDS = 5;
const MAX_SCORE = 1000; const MAX_SCORE = 1000;
@ -21,7 +21,7 @@ class PositionController
$this->placeRepository = new PlaceRepository(); $this->placeRepository = new PlaceRepository();
} }
public function getPosition(): IContent public function getNewPlace(): IContent
{ {
$mapId = (int) $this->request->query('mapId'); $mapId = (int) $this->request->query('mapId');
@ -33,11 +33,11 @@ class PositionController
} }
if (count($state['rounds']) === 0) { if (count($state['rounds']) === 0) {
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId); $place = $this->placeRepository->getForMapWithValidPano($mapId);
$state['rounds'][] = $newPosition; $state['rounds'][] = $place;
$session->set('state', $state); $session->set('state', $state);
$data = ['panoId' => $newPosition['panoId']]; $data = ['panoId' => $place['panoId']];
} else { } else {
$rounds = count($state['rounds']); $rounds = count($state['rounds']);
$last = $state['rounds'][$rounds - 1]; $last = $state['rounds'][$rounds - 1];
@ -93,11 +93,11 @@ class PositionController
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]); $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
} }
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude); $place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
$state['rounds'][] = $newPosition; $state['rounds'][] = $place;
$session->set('state', $state); $session->set('state', $state);
$panoId = $newPosition['panoId']; $panoId = $place['panoId'];
} else { } else {
$state['rounds'] = []; $state['rounds'] = [];
$session->set('state', $state); $session->set('state', $state);

View File

@ -1,25 +1,32 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use DateTime;
use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select; use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Authentication\IUser; use MapGuesser\Interfaces\Authentication\IUser;
use MapGuesser\Interfaces\Authorization\ISecured; use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Database\IResultSet; use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\PlaceRepository; use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds; use MapGuesser\Util\Geo\Bounds;
use MapGuesser\Util\Geo\Position;
class MapAdminController implements ISecured class MapAdminController implements ISecured
{ {
private IRequest $request; private IRequest $request;
private MapRepository $mapRepository;
private PlaceRepository $placeRepository; private PlaceRepository $placeRepository;
public function __construct(IRequest $request) public function __construct(IRequest $request)
{ {
$this->request = $request; $this->request = $request;
$this->mapRepository = new MapRepository();
$this->placeRepository = new PlaceRepository(); $this->placeRepository = new PlaceRepository();
} }
@ -41,15 +48,15 @@ class MapAdminController implements ISecured
{ {
$mapId = (int) $this->request->query('mapId'); $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); $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); return new HtmlContent('admin/map_editor', $data);
} }
public function getPlace() public function getPlace(): IContent
{ {
$placeId = (int) $this->request->query('placeId'); $placeId = (int) $this->request->query('placeId');
@ -59,34 +66,113 @@ class MapAdminController implements ISecured
return new JsonContent($data); return new JsonContent($data);
} }
private function getMapBounds(int $mapId): Bounds public function saveMap(): IContent
{ {
$select = new Select(\Container::$dbConnection, 'maps'); $mapId = (int) $this->request->query('mapId');
$select->columns(['bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
$select->whereId($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; 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 private function &getPlaces(int $mapId): array
{ {
$select = new Select(\Container::$dbConnection, 'places'); $select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']); $select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
$select->where('map_id', '=', $mapId); $select->where('map_id', '=', $mapId);
$select->orderBy('lng');
$result = $select->execute(); $result = $select->execute();
$places = []; $places = [];
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) { while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
//$panoId = ???
//$pov = ???
$noPano = $place['pano_id_cached_timestamp'] && $place['pano_id_cached'] === null; $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; return $places;

View File

@ -59,6 +59,11 @@ class Modify
return $this; return $this;
} }
public function getId()
{
return $this->attributes[$this->idName];
}
public function save(): void public function save(): void
{ {
if (isset($this->attributes[$this->idName])) { if (isset($this->attributes[$this->idName])) {

View 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);
}
}

View File

@ -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 private function selectFromDbById(int $placeId): array
{ {
$select = new Select(\Container::$dbConnection, 'places'); $select = new Select(\Container::$dbConnection, 'places');
@ -66,7 +93,7 @@ class PlaceRepository
$select->where('id', 'NOT IN', $exclude); $select->where('id', 'NOT IN', $exclude);
$select->where('map_id', '=', $mapId); $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); $randomOffset = random_int(0, $numberOfPlaces - 1);
$select->orderBy('id'); $select->orderBy('id');

View File

@ -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 public function calculateApproximateArea(): float
{ {
$dLat = $this->northLat - $this->southLat; $dLat = $this->northLat - $this->southLat;

View File

@ -1,10 +1,55 @@
<?php $cssFiles = ['/static/node_modules/leaflet/dist/leaflet.css', '/static/css/map_editor.css']; ?> <?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/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="map"></div>
<div id="control"> <div id="control">
<button id="saveButton" class="fullWidth">Save</button> <button id="saveButton" class="fullWidth" disabled>Save</button>
<a class="button gray fullWidth marginTop" href="/admin/maps" title="Back to maps">Back to maps</a>
</div> </div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="noPano"> <div id="noPano">
@ -12,7 +57,7 @@
</div> </div>
<div id="placeControl"> <div id="placeControl">
<button id="applyButton" class="fullWidth">Apply</button> <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> <button id="deleteButton" class="red fullWidth marginTop">Delete</button>
</div> </div>
<script> <script>

View File

@ -8,7 +8,11 @@
<span>MapGuesser</span> <span>MapGuesser</span>
</a> </a>
</h1> </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> </div>
<div id="cover"></div> <div id="cover"></div>

View File

@ -10,7 +10,7 @@
<link href="<?= $cssFile ?>" rel="stylesheet"> <link href="<?= $cssFile ?>" rel="stylesheet">
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?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="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="96x96" href="/static/img/favicon/96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon/32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon/32x32.png">