Merged in develop (pull request #64)
Develop
This commit is contained in:
commit
74dd30178d
26
USED_SOFTWARE
Normal file
26
USED_SOFTWARE
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---------------------------------------------------------------
|
||||||
|
Bootstrap Icons
|
||||||
|
https://github.com/twbs/icons
|
||||||
|
---------------------------------------------------------------
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 The Bootstrap Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
---------------------------------------------------------------
|
@ -3,23 +3,29 @@
|
|||||||
require '../main.php';
|
require '../main.php';
|
||||||
|
|
||||||
// very basic routing
|
// very basic routing
|
||||||
$host = $_SERVER["REQUEST_SCHEME"] . '://' . $_SERVER["SERVER_NAME"];
|
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
|
||||||
$url = $_SERVER['REQUEST_URI'];
|
$url = $_SERVER['REQUEST_URI'];
|
||||||
if (($pos = strpos($url, '?')) !== false) {
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
$url = substr($url, 0, $pos);
|
$url = substr($url, 0, $pos);
|
||||||
}
|
}
|
||||||
switch($url) {
|
switch($url) {
|
||||||
|
case '/maps':
|
||||||
|
$controller = new MapGuesser\Controller\MapsController();
|
||||||
|
break;
|
||||||
case '/game':
|
case '/game':
|
||||||
$controller = new MapGuesser\Controller\GameController();
|
$mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
|
||||||
|
$controller = new MapGuesser\Controller\GameController($mapId);
|
||||||
break;
|
break;
|
||||||
case '/game.json':
|
case '/game.json':
|
||||||
$controller = new MapGuesser\Controller\GameController(true);
|
$mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
|
||||||
|
$controller = new MapGuesser\Controller\GameController($mapId, true);
|
||||||
break;
|
break;
|
||||||
case '/position.json':
|
case '/position.json':
|
||||||
$controller = new MapGuesser\Controller\PositionController();
|
$mapId = isset($_GET['map']) ? (int) $_GET['map'] : 0;
|
||||||
|
$controller = new MapGuesser\Controller\PositionController($mapId);
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
header('Location: ' . $host . '/game', true, 302);
|
header('Location: ' . $host . '/maps', true, 302);
|
||||||
die;
|
die;
|
||||||
default:
|
default:
|
||||||
echo 'Error 404';
|
echo 'Error 404';
|
||||||
|
@ -12,13 +12,61 @@ html, body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, button {
|
button::-moz-focus-inner, input::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, h1, h2, button, a {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1>a:link, h1>a:visited {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1>a:hover, h1>a:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, div.header.small h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, h2 {
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 12px;
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup, sub {
|
||||||
|
position: relative;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
vertical-align: top;
|
||||||
|
top: -0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
vertical-align: bottom;
|
||||||
|
bottom: -0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mono {
|
.mono {
|
||||||
@ -29,7 +77,37 @@ p {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.small {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marginTop {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marginBottom {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.inline {
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #3b5998;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, a:focus {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, a.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -39,11 +117,15 @@ button {
|
|||||||
height: 35px;
|
height: 35px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:enabled:hover, button:enabled:focus {
|
button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus {
|
||||||
background-color: #29457f;
|
background-color: #29457f;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
@ -52,19 +134,41 @@ button:disabled {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.fullWidth {
|
button.fullWidth, a.button.fullWidth {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.gray {
|
button.gray, a.button.gray {
|
||||||
background-color: #808080;
|
background-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.gray:hover, button.gray:focus {
|
button.gray:hover, button.gray:focus, a.button.gray:hover, a.button.gray:focus {
|
||||||
background-color: #555555;
|
background-color: #555555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.header {
|
||||||
|
background-color: #333333;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
padding: 0 12px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.header>div.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.header.small {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.main {
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
div.buttonContainer {
|
div.buttonContainer {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
@ -73,12 +177,49 @@ div.buttonContainer > button {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.buttonContainer.top {
|
div.mapContainer {
|
||||||
margin-bottom: 10px;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.buttonContainer.bottom {
|
div.mapItem {
|
||||||
margin-top: 10px;
|
width: 350px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.title {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.title>p.title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.inner {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.inner>div.info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.inner>div.info>p:nth-child(1) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.inner>div.info>p:nth-child(2) {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
@ -89,33 +230,36 @@ div.buttonContainer.bottom {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -32px;
|
margin-top: -32px;
|
||||||
margin-left: -32px;
|
margin-left: -32px;
|
||||||
z-index: 3;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#roundInfo {
|
#roundInfo {
|
||||||
position: absolute;
|
line-height: inherit;
|
||||||
top: 5px;
|
text-align: right;
|
||||||
left: 5px;
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
padding: 0 8px;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: solid 1px #555555;
|
|
||||||
border-radius: 3px;
|
|
||||||
opacity: 0.95;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#roundInfo p {
|
#roundInfo p {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#panorama {
|
#panorama {
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: calc(100% - 40px);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #000000;
|
||||||
|
opacity: 0.5;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
#guess {
|
#guess {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
@ -123,12 +267,15 @@ div.buttonContainer.bottom {
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#guess.result {
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
#guess>#continueButtonContainer {
|
#guess>#continueButtonContainer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.result > #closeGuessButtonContainer,
|
#guess.result>#closeGuessButtonContainer, #guess.result>#guessButtonContainer {
|
||||||
#guess.result > #guessButtonContainer {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +294,8 @@ div.buttonContainer.bottom {
|
|||||||
|
|
||||||
#resultInfo {
|
#resultInfo {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
height: 120px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
padding: 5px 20px;
|
padding: 5px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -162,8 +309,8 @@ div.buttonContainer.bottom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#resultInfo>div {
|
#resultInfo>div {
|
||||||
height: 33.33%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 33.33%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -171,6 +318,7 @@ div.buttonContainer.bottom {
|
|||||||
|
|
||||||
#resultInfo p {
|
#resultInfo p {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#distanceInfo>p:nth-child(2), #scoreInfo>p:nth-child(2) {
|
#distanceInfo>p:nth-child(2), #scoreInfo>p:nth-child(2) {
|
||||||
@ -185,8 +333,8 @@ div.buttonContainer.bottom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#scoreBar {
|
#scoreBar {
|
||||||
height: 100%;
|
|
||||||
width: 0;
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
transition-property: width;
|
transition-property: width;
|
||||||
transition-duration: 2.0s;
|
transition-duration: 2.0s;
|
||||||
@ -196,12 +344,44 @@ div.buttonContainer.bottom {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1504px) {
|
||||||
|
div.mapContainer {
|
||||||
|
grid-template-columns: auto auto auto auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1134px) and (max-width: 1503px) {
|
||||||
|
div.mapContainer {
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 764px) and (max-width: 1133px) {
|
||||||
|
div.mapContainer {
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 763px) {
|
||||||
|
div.mapContainer {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 374px) {
|
||||||
|
div.mapItem {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
|
div.header.small h1 span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
button {
|
button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#showGuessButtonContainer {
|
#showGuessButtonContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
@ -209,18 +389,15 @@ div.buttonContainer.bottom {
|
|||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess {
|
#guess {
|
||||||
|
top: 50px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
top: 40px;
|
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#map {
|
#map {
|
||||||
height: calc(100% - 90px);
|
height: calc(100% - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#scoreBarBase {
|
#scoreBarBase {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -230,13 +407,11 @@ div.buttonContainer.bottom {
|
|||||||
#showGuessButtonContainer {
|
#showGuessButtonContainer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess {
|
#guess {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 375px;
|
height: 375px;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.adapt {
|
#guess.adapt {
|
||||||
top: initial;
|
top: initial;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
@ -246,46 +421,38 @@ div.buttonContainer.bottom {
|
|||||||
transition-duration: 0.1s;
|
transition-duration: 0.1s;
|
||||||
transition-delay: 0.8s;
|
transition-delay: 0.8s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.adapt:hover {
|
#guess.adapt:hover {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 375px;
|
height: 375px;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
transition-delay: 0s;
|
transition-delay: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#closeGuessButtonContainer {
|
#closeGuessButtonContainer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#map {
|
#map {
|
||||||
height: calc(100% - 45px);
|
height: calc(100% - 45px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.result {
|
#guess.result {
|
||||||
width: initial;
|
width: initial;
|
||||||
height: initial;
|
height: initial;
|
||||||
top: 40px;
|
top: 50px;
|
||||||
left: 50px;
|
left: 50px;
|
||||||
right: 50px;
|
right: 50px;
|
||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#scoreBarBase {
|
#scoreBarBase {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-height: 424px) {
|
@media screen and (max-height: 424px) {
|
||||||
#guess {
|
#guess {
|
||||||
top: 40px;
|
top: 50px;
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.adapt:hover {
|
#guess.adapt:hover {
|
||||||
top: 40px;
|
top: 50px;
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.result {
|
#guess.result {
|
||||||
left: 20px;
|
left: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
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(Core.NUMBER_OF_ROUNDS);
|
||||||
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
||||||
|
|
||||||
@ -33,6 +34,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
|
|
||||||
Core.panoId = this.response.panoId;
|
Core.panoId = this.response.panoId;
|
||||||
|
|
||||||
@ -51,11 +53,16 @@
|
|||||||
Core.startNewRound();
|
Core.startNewRound();
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open('GET', 'position.json', true);
|
xhr.open('GET', 'position.json?map=' + mapId, true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
},
|
},
|
||||||
|
|
||||||
resetGame: function () {
|
resetGame: function () {
|
||||||
|
if (Core.guessMarker) {
|
||||||
|
Core.guessMarker.setMap(null);
|
||||||
|
Core.guessMarker = null;
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Core.rounds.length; ++i) {
|
for (var i = 0; i < Core.rounds.length; ++i) {
|
||||||
var round = Core.rounds[i];
|
var round = Core.rounds[i];
|
||||||
|
|
||||||
@ -96,6 +103,7 @@
|
|||||||
lastRound.line.setVisible(false);
|
lastRound.line.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
document.getElementById('showGuessButton').style.visibility = null;
|
document.getElementById('showGuessButton').style.visibility = null;
|
||||||
document.getElementById('guess').style.visibility = null;
|
document.getElementById('guess').style.visibility = null;
|
||||||
document.getElementById('guess').classList.remove('result')
|
document.getElementById('guess').classList.remove('result')
|
||||||
@ -127,7 +135,7 @@
|
|||||||
Core.resetGame();
|
Core.resetGame();
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open('GET', 'game.json', true);
|
xhr.open('GET', 'game.json?map=' + mapId, true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -149,6 +157,7 @@
|
|||||||
document.getElementById('guess').classList.remove('adapt');
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
}
|
}
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
document.getElementById('cover').style.visibility = 'visible';
|
||||||
|
|
||||||
var data = new FormData();
|
var data = new FormData();
|
||||||
data.append('guess', '1');
|
data.append('guess', '1');
|
||||||
@ -192,7 +201,7 @@
|
|||||||
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 (Core.rounds.length === Core.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';
|
||||||
}
|
}
|
||||||
@ -200,7 +209,7 @@
|
|||||||
Core.panoId = this.response.panoId;
|
Core.panoId = this.response.panoId;
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open('POST', 'position.json', true);
|
xhr.open('POST', 'position.json?map=' + mapId, true);
|
||||||
xhr.send(data);
|
xhr.send(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -438,7 +447,13 @@
|
|||||||
Core.resetGame();
|
Core.resetGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showing the loading animation is not possible, because we don't know if user cancelled the leave
|
||||||
|
|
||||||
window.onbeforeunload = function (e) {
|
window.onbeforeunload = function (e) {
|
||||||
|
if (Core.rounds[Core.rounds.length - 1].position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.returnValue = '';
|
e.returnValue = '';
|
||||||
};
|
};
|
@ -10,13 +10,13 @@ use MapGuesser\Interfaces\View\IView;
|
|||||||
|
|
||||||
class GameController implements IController
|
class GameController implements IController
|
||||||
{
|
{
|
||||||
|
private int $mapId;
|
||||||
|
|
||||||
private bool $jsonResponse;
|
private bool $jsonResponse;
|
||||||
|
|
||||||
// demo map
|
public function __construct(int $mapId, $jsonResponse = false)
|
||||||
private int $mapId = 1;
|
|
||||||
|
|
||||||
public function __construct($jsonResponse = false)
|
|
||||||
{
|
{
|
||||||
|
$this->mapId = $mapId;
|
||||||
$this->jsonResponse = $jsonResponse;
|
$this->jsonResponse = $jsonResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class GameController implements IController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = ['bounds' => $bounds->toArray()];
|
$data = ['mapId' => $this->mapId, 'bounds' => $bounds->toArray()];
|
||||||
|
|
||||||
if ($this->jsonResponse) {
|
if ($this->jsonResponse) {
|
||||||
return new JsonView($data);
|
return new JsonView($data);
|
||||||
|
66
src/Controller/MapsController.php
Normal file
66
src/Controller/MapsController.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Database\RawExpression;
|
||||||
|
use MapGuesser\Interfaces\Controller\IController;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\View\IView;
|
||||||
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
use MapGuesser\View\HtmlView;
|
||||||
|
|
||||||
|
class MapsController implements IController
|
||||||
|
{
|
||||||
|
public function run(): IView
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
|
$select->columns([
|
||||||
|
['maps', 'id'],
|
||||||
|
['maps', 'name'],
|
||||||
|
['maps', 'description'],
|
||||||
|
['maps', 'bound_south_lat'],
|
||||||
|
['maps', 'bound_west_lng'],
|
||||||
|
['maps', 'bound_north_lat'],
|
||||||
|
['maps', 'bound_east_lng'],
|
||||||
|
new RawExpression('COUNT(places.id) AS num_places')
|
||||||
|
]);
|
||||||
|
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
|
||||||
|
$select->orderBy('name');
|
||||||
|
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
$maps = [];
|
||||||
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
|
||||||
|
$map['area'] = $this->formatMapAreaForHuman($bounds->calculateApproximateArea());
|
||||||
|
|
||||||
|
$maps[] = $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = ['maps' => $maps];
|
||||||
|
return new HtmlView('maps', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatMapAreaForHuman(float $area): array
|
||||||
|
{
|
||||||
|
if ($area < 100000.0) {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area, 0);
|
||||||
|
$unit = 'm';
|
||||||
|
} elseif ($area < 100000000.0) {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area / 1000000.0, 0);
|
||||||
|
$unit = 'km';
|
||||||
|
} elseif ($area < 10000000000.0) {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area / 1000000.0, -2);
|
||||||
|
$unit = 'km';
|
||||||
|
} else {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area / 1000000.0, -4);
|
||||||
|
$unit = 'km';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [number_format($rounded, $digits, '.', ' '), $unit];
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,12 @@ class PositionController implements IController
|
|||||||
const NUMBER_OF_ROUNDS = 5;
|
const NUMBER_OF_ROUNDS = 5;
|
||||||
const MAX_SCORE = 1000;
|
const MAX_SCORE = 1000;
|
||||||
|
|
||||||
// demo map
|
private int $mapId;
|
||||||
private int $mapId = 1;
|
|
||||||
|
public function __construct(int $mapId)
|
||||||
|
{
|
||||||
|
$this->mapId = $mapId;
|
||||||
|
}
|
||||||
|
|
||||||
public function run(): IView
|
public function run(): IView
|
||||||
{
|
{
|
||||||
@ -120,7 +124,7 @@ class PositionController implements IController
|
|||||||
$select->where('id', 'NOT IN', $exclude);
|
$select->where('id', 'NOT IN', $exclude);
|
||||||
$select->where('map_id', '=', $this->mapId);
|
$select->where('map_id', '=', $this->mapId);
|
||||||
|
|
||||||
$numberOfPlaces = $select->count();
|
$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');
|
||||||
|
@ -4,21 +4,25 @@ class Bounds
|
|||||||
{
|
{
|
||||||
const ONE_DEGREE_OF_LATITUDE_IN_METER = 111132.954;
|
const ONE_DEGREE_OF_LATITUDE_IN_METER = 111132.954;
|
||||||
|
|
||||||
private float $southLat;
|
private float $southLat = 90.0;
|
||||||
private float $westLng;
|
private float $westLng = 180.0;
|
||||||
|
|
||||||
private float $northLat;
|
private float $northLat = -90.0;
|
||||||
private float $eastLng;
|
private float $eastLng = -180.0;
|
||||||
|
|
||||||
private bool $initialized = false;
|
public function __construct(Position $position = null)
|
||||||
|
|
||||||
public static function createWithPosition(Position $position): Bounds
|
|
||||||
{
|
{
|
||||||
$instance = new static();
|
if ($position === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$instance->initialize($position);
|
$lat = $position->getLat();
|
||||||
|
$lng = $position->getLng();
|
||||||
|
|
||||||
return $instance;
|
$this->northLat = $lat;
|
||||||
|
$this->westLng = $lng;
|
||||||
|
$this->southLat = $lat;
|
||||||
|
$this->eastLng = $lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
|
public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
|
||||||
@ -30,19 +34,11 @@ class Bounds
|
|||||||
$instance->northLat = $northLat;
|
$instance->northLat = $northLat;
|
||||||
$instance->eastLng = $eastLng;
|
$instance->eastLng = $eastLng;
|
||||||
|
|
||||||
$instance->initialized = true;
|
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function extend(Position $position): void
|
public function extend(Position $position): void
|
||||||
{
|
{
|
||||||
if (!$this->initialized) {
|
|
||||||
$this->initialize($position);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lat = $position->getLat();
|
$lat = $position->getLat();
|
||||||
$lng = $position->getLng();
|
$lng = $position->getLng();
|
||||||
|
|
||||||
@ -77,10 +73,6 @@ class Bounds
|
|||||||
|
|
||||||
public function toArray(): array
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
if (!$this->initialized) {
|
|
||||||
throw new \Exception("Bounds are not initialized!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'south' => $this->southLat,
|
'south' => $this->southLat,
|
||||||
'west' => $this->westLng,
|
'west' => $this->westLng,
|
||||||
@ -93,17 +85,4 @@ class Bounds
|
|||||||
{
|
{
|
||||||
return json_encode($this->toArray());
|
return json_encode($this->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initialize(Position $position)
|
|
||||||
{
|
|
||||||
$lat = $position->getLat();
|
|
||||||
$lng = $position->getLng();
|
|
||||||
|
|
||||||
$this->northLat = $lat;
|
|
||||||
$this->westLng = $lng;
|
|
||||||
$this->southLat = $lat;
|
|
||||||
$this->eastLng = $lng;
|
|
||||||
|
|
||||||
$this->initialized = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,19 +11,31 @@
|
|||||||
<div id="loading">
|
<div id="loading">
|
||||||
<img src="static/img/loading.svg">
|
<img src="static/img/loading.svg">
|
||||||
</div>
|
</div>
|
||||||
<div id="roundInfo">
|
<div class="header small">
|
||||||
<p>Round: <span id="currentRound" class="mono bold"></span> | Score: <span id="currentScoreSum" class="mono bold"></span></p>
|
<div class="grid">
|
||||||
|
<h1>
|
||||||
|
<a href="maps" title="Back to playable maps">
|
||||||
|
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="#28a745" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M15.817.613A.5.5 0 0 1 16 1v13a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 14.51l-4.902.98A.5.5 0 0 1 0 15V2a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0l4.902.98 4.902-.98a.5.5 0 0 1 .415.103zM10 2.41l-4-.8v11.98l4 .8V2.41zm1 11.98l4-.8V1.61l-4 .8v11.98zm-6-.8V1.61l-4 .8v11.98l4-.8z" />
|
||||||
|
</svg>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="cover"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="showGuessButtonContainer">
|
||||||
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="guess">
|
<div id="guess">
|
||||||
<div id="closeGuessButtonContainer" class="buttonContainer top">
|
<div id="closeGuessButtonContainer" class="buttonContainer marginBottom">
|
||||||
<button id="closeGuessButton" class="fullWidth gray">Close</button>
|
<button id="closeGuessButton" class="fullWidth gray">Close</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div id="guessButtonContainer" class="buttonContainer bottom">
|
<div id="guessButtonContainer" class="buttonContainer marginTop">
|
||||||
<button id="guessButton" class="fullWidth" disabled>Guess</button>
|
<button id="guessButton" class="fullWidth" disabled>Guess</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="resultInfo">
|
<div id="resultInfo">
|
||||||
@ -41,16 +53,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="continueButtonContainer" class="buttonContainer bottom">
|
<div id="continueButtonContainer" class="buttonContainer marginTop">
|
||||||
<button id="continueButton" class="fullWidth">Continue</button>
|
<button id="continueButton" class="fullWidth">Continue</button>
|
||||||
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
||||||
<button id="startNewGameButton" class="fullWidth">Start new game</button>
|
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
var mapId = '<?= $mapId ?>';
|
||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
</script>
|
</script>
|
||||||
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
|
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
|
||||||
<script src="static/js/mapguesser.js"></script>
|
<script src="static/js/game.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
70
views/maps.php
Normal file
70
views/maps.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>MapGuesser</title>
|
||||||
|
<link href="static/css/mapguesser.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&family=Roboto+Mono:wght@300;500&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loading">
|
||||||
|
<img src="static/img/loading.svg">
|
||||||
|
</div>
|
||||||
|
<div class="header">
|
||||||
|
<h1>
|
||||||
|
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="#28a745" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M15.817.613A.5.5 0 0 1 16 1v13a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 14.51l-4.902.98A.5.5 0 0 1 0 15V2a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0l4.902.98 4.902-.98a.5.5 0 0 1 .415.103zM10 2.41l-4-.8v11.98l4 .8V2.41zm1 11.98l4-.8V1.61l-4 .8v11.98zm-6-.8V1.61l-4 .8v11.98l4-.8z" />
|
||||||
|
</svg>
|
||||||
|
MapGuesser
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Playable maps</h2>
|
||||||
|
<div class="mapContainer">
|
||||||
|
<?php foreach ($maps as $map) : ?>
|
||||||
|
<div class="mapItem">
|
||||||
|
<div class="title">
|
||||||
|
<p class="title"><?= $map['name'] ?></p>
|
||||||
|
</div>
|
||||||
|
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="info">
|
||||||
|
<p>
|
||||||
|
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10zm0-7a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
|
||||||
|
</svg>
|
||||||
|
<?= $map['num_places'] ?> places
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<!-- Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. -->
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M12.5 2h-9V1h9v1zm-10 1.5v9h-1v-9h1zm11 9v-9h1v9h-1zM3.5 14h9v1h-9v-1z" />
|
||||||
|
<path fill-rule="evenodd" d="M14 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
|
||||||
|
</svg>
|
||||||
|
~ <?= $map['area'][0] ?> <?= $map['area'][1] ?><sup>2</sup>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="small justify marginTop"><?= $map['description'] ?></p>
|
||||||
|
</div>
|
||||||
|
<a class="button fullWidth" href="game?map=<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (count($maps) < 4): ?>
|
||||||
|
<?php for ($i = 0; $i < 4 - count($maps); ++$i): ?>
|
||||||
|
<div class="mapItem"></div>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function (e) {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user