Changed: Squaremap configs support LiveAtlas & Squaremap Markers
1
main/.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
/data
|
/data
|
||||||
/squaremap/data/
|
/squaremap/data/
|
||||||
/squaremap/locale/
|
/squaremap/locale/
|
||||||
/squaremap/web/
|
|
||||||
|
@ -37,6 +37,7 @@ https://cdn.modrinth.com/data/1u6JkXh5/versions/vBzkrSYP/worldedit-mod-7.3.6.jar
|
|||||||
|
|
||||||
# Squaremap
|
# Squaremap
|
||||||
https://cdn.modrinth.com/data/PFb7ZqK6/versions/RerxbGKf/squaremap-fabric-mc1.21.1-1.3.2.jar
|
https://cdn.modrinth.com/data/PFb7ZqK6/versions/RerxbGKf/squaremap-fabric-mc1.21.1-1.3.2.jar
|
||||||
|
https://github.com/SentixDev/squaremarker/releases/download/1.21.1-v1.0.6/squaremarker-fabric-mc1.21.1-1.0.6.jar
|
||||||
|
|
||||||
# World Guard
|
# World Guard
|
||||||
https://cdn.modrinth.com/data/py6EMmAJ/versions/xpvSS4oW/yawp-0.0.2.10-alpha2.jar
|
https://cdn.modrinth.com/data/py6EMmAJ/versions/xpvSS4oW/yawp-0.0.2.10-alpha2.jar
|
||||||
|
@ -13,7 +13,7 @@ settings:
|
|||||||
web-address: http://localhost:8080
|
web-address: http://localhost:8080
|
||||||
web-directory:
|
web-directory:
|
||||||
path: web
|
path: web
|
||||||
auto-update: true
|
auto-update: false
|
||||||
image-quality:
|
image-quality:
|
||||||
compress-images:
|
compress-images:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
BIN
main/squaremap/images/blank.png
Normal file
After Width: | Height: | Size: 167 B |
172
main/squaremap/web/css/styles.css
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #000000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
ul, li {
|
||||||
|
list-style-type: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
img.leaflet-tile {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: -moz-crisp-edges
|
||||||
|
}
|
||||||
|
div.leaflet-nameplate-pane div {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 5px 1px;
|
||||||
|
margin: 0;
|
||||||
|
border-color: rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
div.leaflet-nameplate-pane div:before {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
div.leaflet-nameplate-pane div img.head {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
div.leaflet-nameplate-pane div img.armor,
|
||||||
|
div.leaflet-nameplate-pane div img.health {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
div.leaflet-nameplate-pane div,
|
||||||
|
div.leaflet-marker-pane img {
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
.leaflet-bottom.leaflet-left .link,
|
||||||
|
.leaflet-bottom.leaflet-left .coordinates {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
div.leaflet-control-layers.link img {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
vertical-align: bottom;
|
||||||
|
bottom: 0;
|
||||||
|
background-image: url("../images/link.png");
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
}
|
||||||
|
div.leaflet-control-layers.coordinates {
|
||||||
|
vertical-align: bottom;
|
||||||
|
padding: 2px 5px;
|
||||||
|
line-height: 14px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
#sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 100vh;
|
||||||
|
padding-left: 10px;
|
||||||
|
z-index: 10000;
|
||||||
|
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
border-left: 1px solid #000000;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: right;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
#sidebar.show {
|
||||||
|
width: 200px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
#sidebar fieldset {
|
||||||
|
clear: right;
|
||||||
|
margin: 30px 0;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid rgba(196, 196, 196, 0.4);
|
||||||
|
text-align: left;
|
||||||
|
transition: all 0.25s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#worlds {
|
||||||
|
margin: -15px 0 0;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players::-webkit-scrollbar-track {
|
||||||
|
-webkit-box-shadow: inset 0 0 0;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
border-right: 2px solid rgba(112, 128, 144, 0.75);
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players::-webkit-scrollbar-thumb:hover {
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 0;
|
||||||
|
background-color: rgba(112, 128, 144, 0.75);
|
||||||
|
}
|
||||||
|
#sidebar legend {
|
||||||
|
display: block;
|
||||||
|
margin: 10px 5px;
|
||||||
|
}
|
||||||
|
#sidebar fieldset a,
|
||||||
|
#sidebar fieldset a:visited {
|
||||||
|
display: block;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
#sidebar fieldset a.following {
|
||||||
|
background-color: rgba(128, 128, 255, 0.25);
|
||||||
|
}
|
||||||
|
#sidebar fieldset a:hover,
|
||||||
|
#sidebar fieldset a.following:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#sidebar fieldset a .other-world {
|
||||||
|
filter: brightness(50%) !important;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
#sidebar fieldset a img {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-right: 10px;
|
||||||
|
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
|
||||||
|
-webkit-filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
|
||||||
|
}
|
||||||
|
#sidebar fieldset#players a {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#sidebar #pin {
|
||||||
|
position: relative;
|
||||||
|
right: -160px;
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
margin: 5px 5px 10px;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0);
|
||||||
|
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
|
||||||
|
-webkit-filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#sidebar #pin.pinned:hover {
|
||||||
|
background: rgba(0, 128, 0, 0.5);
|
||||||
|
border: 1px solid rgba(0, 128, 0, 0.75);
|
||||||
|
}
|
||||||
|
#sidebar #pin.unpinned:hover {
|
||||||
|
background: rgba(128, 0, 0, 0.5);
|
||||||
|
border: 1px solid rgba(128, 0, 0, 0.75);
|
||||||
|
}
|
BIN
main/squaremap/web/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
main/squaremap/web/images/armor/0.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
main/squaremap/web/images/armor/1.png
Normal file
After Width: | Height: | Size: 667 B |
BIN
main/squaremap/web/images/armor/10.png
Normal file
After Width: | Height: | Size: 662 B |
BIN
main/squaremap/web/images/armor/11.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
main/squaremap/web/images/armor/12.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
main/squaremap/web/images/armor/13.png
Normal file
After Width: | Height: | Size: 684 B |
BIN
main/squaremap/web/images/armor/14.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
main/squaremap/web/images/armor/15.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
main/squaremap/web/images/armor/16.png
Normal file
After Width: | Height: | Size: 669 B |
BIN
main/squaremap/web/images/armor/17.png
Normal file
After Width: | Height: | Size: 681 B |
BIN
main/squaremap/web/images/armor/18.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
main/squaremap/web/images/armor/19.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
main/squaremap/web/images/armor/2.png
Normal file
After Width: | Height: | Size: 665 B |
BIN
main/squaremap/web/images/armor/20.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
main/squaremap/web/images/armor/3.png
Normal file
After Width: | Height: | Size: 690 B |
BIN
main/squaremap/web/images/armor/4.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
main/squaremap/web/images/armor/5.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
main/squaremap/web/images/armor/6.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
main/squaremap/web/images/armor/7.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
main/squaremap/web/images/armor/8.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
main/squaremap/web/images/armor/9.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
main/squaremap/web/images/blank.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
main/squaremap/web/images/clear.png
Normal file
After Width: | Height: | Size: 527 B |
BIN
main/squaremap/web/images/end_sky.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
main/squaremap/web/images/foliage.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
main/squaremap/web/images/grass.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
main/squaremap/web/images/health/0.png
Normal file
After Width: | Height: | Size: 617 B |
BIN
main/squaremap/web/images/health/1.png
Normal file
After Width: | Height: | Size: 660 B |
BIN
main/squaremap/web/images/health/10.png
Normal file
After Width: | Height: | Size: 678 B |
BIN
main/squaremap/web/images/health/11.png
Normal file
After Width: | Height: | Size: 684 B |
BIN
main/squaremap/web/images/health/12.png
Normal file
After Width: | Height: | Size: 681 B |
BIN
main/squaremap/web/images/health/13.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
main/squaremap/web/images/health/14.png
Normal file
After Width: | Height: | Size: 683 B |
BIN
main/squaremap/web/images/health/15.png
Normal file
After Width: | Height: | Size: 683 B |
BIN
main/squaremap/web/images/health/16.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
main/squaremap/web/images/health/17.png
Normal file
After Width: | Height: | Size: 686 B |
BIN
main/squaremap/web/images/health/18.png
Normal file
After Width: | Height: | Size: 677 B |
BIN
main/squaremap/web/images/health/19.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
main/squaremap/web/images/health/2.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
main/squaremap/web/images/health/20.png
Normal file
After Width: | Height: | Size: 634 B |
BIN
main/squaremap/web/images/health/3.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
main/squaremap/web/images/health/4.png
Normal file
After Width: | Height: | Size: 677 B |
BIN
main/squaremap/web/images/health/5.png
Normal file
After Width: | Height: | Size: 683 B |
BIN
main/squaremap/web/images/health/6.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
main/squaremap/web/images/health/7.png
Normal file
After Width: | Height: | Size: 684 B |
BIN
main/squaremap/web/images/health/8.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
main/squaremap/web/images/health/9.png
Normal file
After Width: | Height: | Size: 677 B |
BIN
main/squaremap/web/images/icon/blue-cube-smol.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
main/squaremap/web/images/icon/green-cube-smol.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
main/squaremap/web/images/icon/player.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
main/squaremap/web/images/icon/purple-cube-smol.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
main/squaremap/web/images/icon/red-cube-smol.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 160 B |
BIN
main/squaremap/web/images/icon/spawn.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
main/squaremap/web/images/link.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
main/squaremap/web/images/nether_sky.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
main/squaremap/web/images/og.png
Normal file
After Width: | Height: | Size: 309 KiB |
BIN
main/squaremap/web/images/overworld_sky.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
main/squaremap/web/images/pinned.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
main/squaremap/web/images/unpinned.png
Normal file
After Width: | Height: | Size: 11 KiB |
404
main/squaremap/web/index.html
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="preload" href="https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrE.woff2" as="font" crossorigin="anonymous">
|
||||||
|
<link rel="preload" href="https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVsEpbCIPrE.woff2" as="font" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
||||||
|
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
|
||||||
|
<meta name="theme-color" content="#222222">
|
||||||
|
<link rel="manifest" href="./live-atlas/favicons/site.webmanifest">
|
||||||
|
<link rel="icon" href="./live-atlas/favicons/favicon.svg">
|
||||||
|
<link rel=”mask-icon” href="./live-atlas/favicons/mask.svg" color="#cccccc">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="./live-atlas/favicons/apple-touch-icon.png">
|
||||||
|
|
||||||
|
<meta name="keywords" content="minecraft, map, dynamic, liveatlas" />
|
||||||
|
<meta name="description" content="Minecraft Dynamic Map" />
|
||||||
|
|
||||||
|
<title>LiveAtlas</title>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.liveAtlasConfig = {
|
||||||
|
// By default LiveAtlas looks for a dynmap standalone/config.js file
|
||||||
|
// This configuration can be used instead to support Pl3xmap and Squaremap installations as well as multiple servers (external webserver required)
|
||||||
|
// To configure multiple servers, see https://github.com/JLyne/LiveAtlas/wiki/Configuring-Multiple-Servers.
|
||||||
|
|
||||||
|
// Example Squaremap internal webserver configuration
|
||||||
|
servers: {
|
||||||
|
squaremap: {
|
||||||
|
squaremap: window.location.pathname
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Example Pl3xmap internal webserver configuration
|
||||||
|
// servers: {
|
||||||
|
// pl3xmap: {
|
||||||
|
// pl3xmap: window.location.pathname
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Example Overviewer configuration
|
||||||
|
// servers: {
|
||||||
|
// overviewer: {
|
||||||
|
// overviewer: window.location.pathname
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Example Dynmap internal webserver configuration without using standalone/config.js
|
||||||
|
// servers: {
|
||||||
|
// dynmap: {
|
||||||
|
// dynmap: {
|
||||||
|
// configuration: 'standalone/dynmap_config.json?_={timestamp}',
|
||||||
|
// update: 'standalone/dynmap_{world}.json?_={timestamp}',
|
||||||
|
// sendmessage: 'standalone/sendmessage.php',
|
||||||
|
// login: 'standalone/login.php',
|
||||||
|
// register: 'standalone/register.php',
|
||||||
|
// tiles: 'tiles/',
|
||||||
|
// markers: 'tiles/'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// Example multiple Dynmap servers on external webserver configuration
|
||||||
|
// servers: {
|
||||||
|
// creative: {
|
||||||
|
// label: 'Creative',
|
||||||
|
// dynmap: {
|
||||||
|
// configuration: 'http://dynmap.local/standalone/creative/MySQL_configuration.php',
|
||||||
|
// update: 'http://dynmap.local/standalone/creative/MySQL_update.php?world={world}&ts={timestamp}',
|
||||||
|
// sendmessage: 'http://dynmap.local/standalone/creative/MySQL_sendmessage.php',
|
||||||
|
// login: 'http://dynmap.local/standalone/creative/MySQL_login.php',
|
||||||
|
// register: 'http://dynmap.local/standalone/creative/MySQL_register.php',
|
||||||
|
// tiles: 'http://dynmap.local/standalone/creative/MySQL_tiles.php?tile=',
|
||||||
|
// markers: 'http://dynmap.local/standalone/creative/MySQL_markers.php?marker='
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// survival: {
|
||||||
|
// label: 'Survival',
|
||||||
|
// dynmap: {
|
||||||
|
// configuration: 'http://dynmap.local/standalone/survival/MySQL_configuration.php',
|
||||||
|
// update: 'http://dynmap.local/standalone/survival/MySQL_update.php?world={world}&ts={timestamp}',
|
||||||
|
// sendmessage: 'http://dynmap.local/standalone/survival/MySQL_sendmessage.php',
|
||||||
|
// login: 'http://dynmap.local/standalone/survival/MySQL_login.php',
|
||||||
|
// register: 'http://dynmap.local/standalone/survival/MySQL_register.php',
|
||||||
|
// tiles: 'http://dynmap.local/standalone/survival/MySQL_tiles.php?tile=',
|
||||||
|
// markers: 'http://dynmap.local/standalone/survival/MySQL_markers.php?marker='
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// These messages are used throughout LiveAtlas and can be translated here
|
||||||
|
// If a message you want to translate isn't here, it is likely controlled by dynmap itself
|
||||||
|
// see https://github.com/webbukkit/dynmap/wiki/Configuration.txt
|
||||||
|
messages: {
|
||||||
|
chatNoMessages: 'No chat messages yet...',
|
||||||
|
chatTitle: 'Chat',
|
||||||
|
chatLogin: 'Please login to send chat messages',
|
||||||
|
chatSend: 'Send',
|
||||||
|
chatPlaceholder: 'Type your chat message here...',
|
||||||
|
chatErrorUnknown: 'Unexpected error while sending chat message',
|
||||||
|
chatErrorDisabled: 'Chat is not enabled',
|
||||||
|
serversHeading: 'Servers',
|
||||||
|
markersHeading: 'Markers',
|
||||||
|
markersSearchPlaceholder: 'Search markers...',
|
||||||
|
markersSkeleton: 'No markers exist for the current world',
|
||||||
|
markersSetSkeleton: 'This marker set is empty',
|
||||||
|
markersSearchSkeleton: 'No matching markers found',
|
||||||
|
markersUnnamed: '(Unnamed marker)',
|
||||||
|
worldsSkeleton: 'No maps have been configured',
|
||||||
|
playersSkeleton: 'No players are currently online',
|
||||||
|
playersTitle: 'Click to center on player\nDouble-click to follow player',
|
||||||
|
playersTitleHidden: 'This player is currently hidden from the map\nDouble-click to follow player when they become visible',
|
||||||
|
playersTitleOtherWorld: 'This player is in another world.\nClick to center on player\nDouble-click to follow player',
|
||||||
|
playersSearchPlaceholder: 'Search players...',
|
||||||
|
playersSearchSkeleton: 'No matching players found',
|
||||||
|
followingHeading: 'Following',
|
||||||
|
followingUnfollow: 'Unfollow',
|
||||||
|
followingTitleUnfollow: 'Stop following this player',
|
||||||
|
followingHidden: 'Currently hidden',
|
||||||
|
linkTitle: 'Copy link to current location',
|
||||||
|
loadingTitle: 'Loading...',
|
||||||
|
locationRegion: 'Region',
|
||||||
|
locationChunk: 'Chunk',
|
||||||
|
contextMenuCopyLink: 'Copy link to here',
|
||||||
|
contextMenuCenterHere: 'Center here',
|
||||||
|
toggleTitle: 'Click to toggle this section',
|
||||||
|
mapTitle: 'Map - Use the arrow keys to pan the map',
|
||||||
|
layersTitle: 'Layers',
|
||||||
|
copyToClipboardSuccess: 'Copied to clipboard',
|
||||||
|
copyToClipboardError: 'Unable to copy to clipboard',
|
||||||
|
|
||||||
|
loginTitle: 'Login/Register',
|
||||||
|
loginHeading: 'Existing User',
|
||||||
|
loginUsernameLabel: 'Username',
|
||||||
|
loginPasswordLabel: 'Password',
|
||||||
|
loginSubmit: 'Login',
|
||||||
|
loginErrorUnknown: 'Unexpected error while logging in',
|
||||||
|
loginErrorDisabled: 'Logging in is disabled on this server',
|
||||||
|
loginErrorIncorrect: 'Incorrect username or password',
|
||||||
|
loginSuccess: 'Logged in successfully',
|
||||||
|
|
||||||
|
registerHeading: 'New User',
|
||||||
|
registerDescription: `Enter your username and password, along with your registration code.
|
||||||
|
|
||||||
|
You can get a registration code by running /dynmap webregister in-game.`,
|
||||||
|
registerConfirmPasswordLabel: 'Confirm Password',
|
||||||
|
registerCodeLabel: 'Registration Code',
|
||||||
|
registerSubmit: 'Register',
|
||||||
|
registerErrorUnknown: 'Unexpected error during registration',
|
||||||
|
registerErrorDisabled: 'Registration is disabled on this server',
|
||||||
|
registerErrorVerifyFailed: 'The entered passwords do not match',
|
||||||
|
registerErrorIncorrect: 'Registration failed, please check the entered details are correct',
|
||||||
|
|
||||||
|
logoutTitle: 'Logout',
|
||||||
|
logoutErrorUnknown: 'Unexpected error while logging out',
|
||||||
|
logoutSuccess: 'Logged out successfully',
|
||||||
|
|
||||||
|
closeTitle: 'Close',
|
||||||
|
showMore: 'Show more'
|
||||||
|
},
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
// If true, player markers will always be displayed in front of other marker types
|
||||||
|
playersAboveMarkers: true,
|
||||||
|
|
||||||
|
// Whether to enable the player list search box
|
||||||
|
playersSearch: true,
|
||||||
|
|
||||||
|
// Use more compact pre-2.0 player marker style
|
||||||
|
compactPlayerMarkers: false,
|
||||||
|
|
||||||
|
// Disable the map right click menu
|
||||||
|
disableContextMenu: false,
|
||||||
|
|
||||||
|
// Disable the markers button and list
|
||||||
|
disableMarkerUI: false,
|
||||||
|
|
||||||
|
// Custom URL to redirect to when logging in is required
|
||||||
|
// This URL will need to handle the login process and redirect users back to LiveAtlas
|
||||||
|
customLoginUrl: null
|
||||||
|
},
|
||||||
|
|
||||||
|
// Config version. Do not modify.
|
||||||
|
version: 1
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Theme colours */
|
||||||
|
:root {
|
||||||
|
--background-base: #222222; /* Foreground UI elements */
|
||||||
|
--background-dark: #121212; /* Body/Splash screen/Shadows */
|
||||||
|
--background-light: #363636; /* Scrollbars/inputs */
|
||||||
|
--background-error: #771616; /* Errors */
|
||||||
|
--background-marker: var(--background-dark); /* Map markers */
|
||||||
|
|
||||||
|
--background-disabled: #555555; /* Disabled controls */
|
||||||
|
--background-hover: #363636; /* :hovered buttons/menu items */
|
||||||
|
--background-active: #6d6d6d; /* Button :active */
|
||||||
|
--background-selected: #BDBDBD; /* Selected buttons/menu items */
|
||||||
|
|
||||||
|
--outline-focus: #eeeeee; /* :focus outline */
|
||||||
|
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
--border-color: #333333; /* Control borders */
|
||||||
|
--border-error: #cc0e0e;
|
||||||
|
--box-shadow: 2px 2px 0px #111111;
|
||||||
|
|
||||||
|
--text-base: rgba(255, 255, 255, 0.7); /* Normal text */
|
||||||
|
--text-emphasis: rgba(255, 255, 255, 0.87); /* Chat messages/:focus inputs */
|
||||||
|
--text-subtle: rgba(255, 255, 255, 0.5); /* Skeletons/secondary text */
|
||||||
|
--text-disabled: var(--text-subtle); /* Disabled controls */
|
||||||
|
--text-marker: var(--text-base); /* Map markers */
|
||||||
|
|
||||||
|
--text-hover: var(--text-base); /* Text in :hover buttons */
|
||||||
|
--text-active: var(--text-base); /* Text in :active buttons */
|
||||||
|
--text-selected: var(--background-base); /* Text in selected buttons */
|
||||||
|
--text-shadow: 0.1rem 0.1rem #000000; /* Text in selected buttons */
|
||||||
|
|
||||||
|
--text-night: #ddffff; /* Clock time at night */
|
||||||
|
--text-day: #ffdd33; /* Clock time in day */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--background-light) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:hover, *:focus-within {
|
||||||
|
scrollbar-color: var(--background-hover) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--background-light);
|
||||||
|
border: 2px solid #000000;
|
||||||
|
border-radius: 2rem;
|
||||||
|
transition: background 1s ease-in;
|
||||||
|
padding-right: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:hover::-webkit-scrollbar-thumb, *:focus-within::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: var(--background-dark);
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 62.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Raleway,
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Helvetica,
|
||||||
|
Arial,
|
||||||
|
sans-serif,
|
||||||
|
'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji';
|
||||||
|
color: var(--text-base);
|
||||||
|
text-shadow: var(--text-shadow);
|
||||||
|
letter-spacing: 0.02rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
noscript {
|
||||||
|
color: var(--text-base);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash, noscript {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
transition: 0.3s opacity linear;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: var(--background-dark);
|
||||||
|
cursor: wait;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
padding: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash__spinner {
|
||||||
|
margin-top: 4rem;
|
||||||
|
animation: fade 0.5s linear 1s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash__error {
|
||||||
|
margin-top: 2rem;
|
||||||
|
transition: opacity 0.5s ease-in;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 60rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash__error-message {
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: var(--background-error);
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash__error[aria-hidden=true] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" crossorigin src="./live-atlas/assets/index.a82d2095.js"></script>
|
||||||
|
<link rel="modulepreload" href="./live-atlas/assets/vendor.d0ab50b1.js">
|
||||||
|
<link rel="stylesheet" href="./live-atlas/assets/index.1de34b3a.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="splash">
|
||||||
|
<svg id="splash__logo" width="200" height="200" viewBox="0 0 268.83 266.53" aria-hidden="true" fill="rgba(255, 255, 255, 0.7)">
|
||||||
|
<path d="M5.235.662C2.153.606 0 2.945 0 6.912v216.577c0 5.288 3.828 11.41 8.582 13.725l57.835 28.165c4.755 2.316 8.582-.078 8.582-5.367V43.434c0-5.288-3.827-11.41-8.582-13.727L8.582 1.544C7.394.965 6.262.681 5.235.662zm32.283 135.96c14.394 0 26.062 11.669 26.062 26.063 0 14.394-24.167 59.55-26.062 57.654-1.854 1.854-26.063-43.26-26.063-57.654 0-14.394 11.669-26.063 26.063-26.063zM202.388 1.013l57.833 28.165c4.755 2.315 8.583 8.437 8.583 13.726v216.58c0 5.29-3.828 7.683-8.583 5.367l-57.833-28.164c-4.755-2.316-8.583-8.438-8.583-13.727V6.38c0-5.289 3.828-7.682 8.583-5.367zM172.012.39c-1.051-.035-2.209.191-3.426.709l-68.342 29.053c-4.867 2.07-8.786 7.993-8.786 13.282V260.01c0 5.29 3.919 7.88 8.786 5.811l68.342-29.053c4.867-2.07 8.786-7.991 8.786-13.28V6.91c0-3.967-2.204-6.417-5.36-6.521zm-36.949 41.216c14.394 0 26.063 11.668 26.063 26.062 0 14.394-24.168 59.55-26.063 57.655C133.209 127.177 109 82.063 109 67.668c0-14.394 11.669-26.062 26.063-26.062z"/>
|
||||||
|
<path d="M48.573 162.689a11.056 11.056 0 0 1-11.056 11.056 11.056 11.056 0 0 1-11.056-11.056 11.056 11.056 0 0 1 11.056-11.056 11.056 11.056 0 0 1 11.056 11.056zM146.12 67.669a11.056 11.056 0 0 1-11.057 11.056 11.056 11.056 0 0 1-11.056-11.056 11.056 11.056 0 0 1 11.056-11.056 11.056 11.056 0 0 1 11.056 11.056z"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg id="splash__spinner" width="38" height="38" viewBox="0 0 38 38" stroke="#fff" aria-label="LiveAtlas is loading">
|
||||||
|
<g transform="translate(1 1)" stroke-width="2" fill="none">
|
||||||
|
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
|
||||||
|
<path d="M36 18c0-9.94-8.06-18-18-18">
|
||||||
|
<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/>
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div id="splash__error" aria-hidden="true">
|
||||||
|
<span id="splash__error-message" role="alert"></span>
|
||||||
|
<span id="splash__error-retry" aria-live="polite"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<strong>LiveAtlas requires JavaScript to work.<br />Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<main id="app" aria-hidden="true"></main>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
if(!window.liveAtlasLoaded) {
|
||||||
|
document.getElementById('splash__error').setAttribute('aria-hidden', 'false');
|
||||||
|
document.getElementById('splash__error-message').innerText = 'Required LiveAtlas files are missing or failed to load.\nPlease reinstall LiveAtlas.';
|
||||||
|
document.getElementById('splash__spinner').style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
243
main/squaremap/web/js/addons/Ellipse.js
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 JD Fergason
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://github.com/jdfergason/Leaflet.Ellipse
|
||||||
|
|
||||||
|
L.SVG.include ({
|
||||||
|
_updateEllipse: function (layer) {
|
||||||
|
var rx = layer._radiusX,
|
||||||
|
ry = layer._radiusY,
|
||||||
|
phi = layer._tiltDeg,
|
||||||
|
endPoint = layer._endPointParams;
|
||||||
|
|
||||||
|
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
|
||||||
|
'A' + rx + ',' + ry + ',' + phi + ',' +
|
||||||
|
endPoint.largeArc + ',' + endPoint.sweep + ',' +
|
||||||
|
endPoint.x1 + ',' + endPoint.y1 + ' z';
|
||||||
|
this._setPath(layer, d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Canvas.include ({
|
||||||
|
_updateEllipse: function (layer) {
|
||||||
|
if (layer._empty()) { return; }
|
||||||
|
|
||||||
|
var p = layer._point,
|
||||||
|
ctx = this._ctx,
|
||||||
|
r = layer._radiusX,
|
||||||
|
s = (layer._radiusY || r) / r;
|
||||||
|
|
||||||
|
// this breaks "preferCanvas: true"
|
||||||
|
//this._drawnLayers[layer._leaflet_id] = layer;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
ctx.translate(p.x, p.y);
|
||||||
|
if (layer._tilt !== 0) {
|
||||||
|
ctx.rotate( layer._tilt );
|
||||||
|
}
|
||||||
|
if (s !== 1) {
|
||||||
|
ctx.scale(1, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, r, 0, Math.PI * 2);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
this._fillStroke(ctx, layer);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Ellipse = L.Path.extend({
|
||||||
|
|
||||||
|
options: {
|
||||||
|
fill: true,
|
||||||
|
startAngle: 0,
|
||||||
|
endAngle: 359.9
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (latlng, radii, tilt, options) {
|
||||||
|
|
||||||
|
L.setOptions(this, options);
|
||||||
|
this._latlng = L.latLng(latlng);
|
||||||
|
|
||||||
|
if (tilt) {
|
||||||
|
this._tiltDeg = tilt;
|
||||||
|
} else {
|
||||||
|
this._tiltDeg = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radii) {
|
||||||
|
this._mRadiusX = radii[0];
|
||||||
|
this._mRadiusY = radii[1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setRadius: function (radii) {
|
||||||
|
this._mRadiusX = radii[0];
|
||||||
|
this._mRadiusY = radii[1];
|
||||||
|
return this.redraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
getRadius: function () {
|
||||||
|
return new L.point(this._mRadiusX, this._mRadiusY);
|
||||||
|
},
|
||||||
|
|
||||||
|
setTilt: function (tilt) {
|
||||||
|
this._tiltDeg = tilt;
|
||||||
|
return this.redraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds: function () {
|
||||||
|
// TODO respect tilt (bounds are too big)
|
||||||
|
var lngRadius = this._getLngRadius(),
|
||||||
|
latRadius = this._getLatRadius(),
|
||||||
|
latlng = this._latlng;
|
||||||
|
|
||||||
|
return new L.LatLngBounds(
|
||||||
|
[latlng.lat - latRadius, latlng.lng - lngRadius],
|
||||||
|
[latlng.lat + latRadius, latlng.lng + lngRadius]);
|
||||||
|
},
|
||||||
|
|
||||||
|
// @method setLatLng(latLng: LatLng): this
|
||||||
|
// Sets the position of a circle marker to a new location.
|
||||||
|
setLatLng: function (latlng) {
|
||||||
|
this._latlng = L.latLng(latlng);
|
||||||
|
this.redraw();
|
||||||
|
return this.fire('move', {latlng: this._latlng});
|
||||||
|
},
|
||||||
|
|
||||||
|
// @method getLatLng(): LatLng
|
||||||
|
// Returns the current geographical position of the circle marker
|
||||||
|
getLatLng: function () {
|
||||||
|
return this._latlng;
|
||||||
|
},
|
||||||
|
|
||||||
|
setStyle: L.Path.prototype.setStyle,
|
||||||
|
|
||||||
|
_project: function () {
|
||||||
|
var lngRadius = this._getLngRadius(),
|
||||||
|
latRadius = this._getLatRadius(),
|
||||||
|
latlng = this._latlng,
|
||||||
|
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
|
||||||
|
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
|
||||||
|
|
||||||
|
this._point = this._map.latLngToLayerPoint(latlng);
|
||||||
|
this._radiusX = Math.max(this._point.x - pointLeft.x, 1) * this._map.options.scale;
|
||||||
|
this._radiusY = Math.max(pointBelow.y - this._point.y, 1) * this._map.options.scale;
|
||||||
|
this._tilt = Math.PI * this._tiltDeg / 180;
|
||||||
|
this._endPointParams = this._centerPointToEndPoint();
|
||||||
|
this._updateBounds();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateBounds: function () {
|
||||||
|
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
|
||||||
|
var sin = Math.sin(this._tilt);
|
||||||
|
var cos = Math.cos(this._tilt);
|
||||||
|
var sinSquare = sin * sin;
|
||||||
|
var cosSquare = cos * cos;
|
||||||
|
var aSquare = this._radiusX * this._radiusX;
|
||||||
|
var bSquare = this._radiusY * this._radiusY;
|
||||||
|
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
|
||||||
|
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
|
||||||
|
var w = this._clickTolerance();
|
||||||
|
var p = [halfWidth + w, halfHeight + w];
|
||||||
|
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
|
||||||
|
},
|
||||||
|
|
||||||
|
_update: function () {
|
||||||
|
if (this._map) {
|
||||||
|
this._updatePath();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updatePath: function () {
|
||||||
|
this._renderer._updateEllipse(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLatRadius: function () {
|
||||||
|
var simpleCrs = !!this._map.options.crs.infinite;
|
||||||
|
if(simpleCrs)
|
||||||
|
return this._mRadiusY;
|
||||||
|
else
|
||||||
|
return (this._mRadiusY / 40075017) * 360;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLngRadius: function () {
|
||||||
|
var simpleCrs = !!this._map.options.crs.infinite;
|
||||||
|
if(simpleCrs)
|
||||||
|
return this._mRadiusX;
|
||||||
|
else
|
||||||
|
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
|
||||||
|
},
|
||||||
|
|
||||||
|
_centerPointToEndPoint: function () {
|
||||||
|
// Convert between center point parameterization of an ellipse
|
||||||
|
// too SVG's end-point and sweep parameters. This is an
|
||||||
|
// adaptation of the perl code found here:
|
||||||
|
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
|
||||||
|
var c = this._point,
|
||||||
|
rx = this._radiusX,
|
||||||
|
ry = this._radiusY,
|
||||||
|
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
|
||||||
|
theta1 = this.options.startAngle * (Math.PI / 180),
|
||||||
|
delta = this.options.endAngle,
|
||||||
|
phi = this._tiltDeg * (Math.PI / 180);
|
||||||
|
|
||||||
|
// Determine start and end-point coordinates
|
||||||
|
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
|
||||||
|
Math.sin(-phi) * ry * Math.sin(theta1);
|
||||||
|
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
|
||||||
|
Math.cos(phi) * ry * Math.sin(theta1);
|
||||||
|
|
||||||
|
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
|
||||||
|
Math.sin(-phi) * ry * Math.sin(theta2);
|
||||||
|
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
|
||||||
|
Math.cos(phi) * ry * Math.sin(theta2);
|
||||||
|
|
||||||
|
var largeArc = (delta > 180) ? 1 : 0;
|
||||||
|
var sweep = (delta > 0) ? 1 : 0;
|
||||||
|
|
||||||
|
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
|
||||||
|
'sweep': sweep, 'x1': x1, 'y1': y1};
|
||||||
|
},
|
||||||
|
|
||||||
|
_empty: function () {
|
||||||
|
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
|
||||||
|
},
|
||||||
|
|
||||||
|
_containsPoint : function (p) {
|
||||||
|
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
|
||||||
|
var sin = Math.sin(this._tilt);
|
||||||
|
var cos = Math.cos(this._tilt);
|
||||||
|
var dx = p.x - this._point.x;
|
||||||
|
var dy = p.y - this._point.y;
|
||||||
|
var sumA = cos * dx + sin * dy;
|
||||||
|
var sumB = sin * dx - cos * dy;
|
||||||
|
if (this.options.fill === false) {
|
||||||
|
var x = this._radiusX - this.options.weight;
|
||||||
|
var y = this._radiusY - this.options.weight;
|
||||||
|
if (sumA * sumA / (x * x) + sumB * sumB / (y * y) <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.ellipse = function (latlng, radii, tilt, options) {
|
||||||
|
return new L.Ellipse(latlng, radii, tilt, options);
|
||||||
|
};
|
82
main/squaremap/web/js/addons/RotateMarker.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Benjamin Becquet
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* https://github.com/bbecquet/Leaflet.RotatedMarker
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
// save these original methods before they are overwritten
|
||||||
|
var proto_initIcon = L.Marker.prototype._initIcon;
|
||||||
|
var proto_setPos = L.Marker.prototype._setPos;
|
||||||
|
|
||||||
|
var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
|
||||||
|
|
||||||
|
L.Marker.addInitHook(function () {
|
||||||
|
var iconOptions = this.options.icon && this.options.icon.options;
|
||||||
|
var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
|
||||||
|
if (iconAnchor) {
|
||||||
|
iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
|
||||||
|
}
|
||||||
|
this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom' ;
|
||||||
|
this.options.rotationAngle = this.options.rotationAngle || 0;
|
||||||
|
|
||||||
|
// Ensure marker keeps rotated during dragging
|
||||||
|
this.on('drag', function(e) { e.target._applyRotation(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Marker.include({
|
||||||
|
_initIcon: function() {
|
||||||
|
proto_initIcon.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setPos: function (pos) {
|
||||||
|
proto_setPos.call(this, pos);
|
||||||
|
this._applyRotation();
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyRotation: function () {
|
||||||
|
if(this.options.rotationAngle) {
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin;
|
||||||
|
|
||||||
|
if(oldIE) {
|
||||||
|
// for IE 9, use the 2D rotation
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
|
||||||
|
} else {
|
||||||
|
// for modern browsers, prefer the 3D accelerated version
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setRotationAngle: function(angle) {
|
||||||
|
this.options.rotationAngle = angle;
|
||||||
|
this.update();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
setRotationOrigin: function(origin) {
|
||||||
|
this.options.rotationOrigin = origin;
|
||||||
|
this.update();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
106
main/squaremap/web/js/modules/LayerControl.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { P } from './Squaremap.js';
|
||||||
|
import { SquaremapTileLayer } from './SquaremapTileLayer.js';
|
||||||
|
|
||||||
|
class LayerControl {
|
||||||
|
constructor() {
|
||||||
|
this.layers = new Map();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.currentLayer = 0;
|
||||||
|
this.updateInterval = 60;
|
||||||
|
|
||||||
|
this.playersLayer = new L.LayerGroup();
|
||||||
|
this.playersLayer.id = "players_layer";
|
||||||
|
|
||||||
|
this.controls = L.control.layers({}, {}, {
|
||||||
|
position: 'topleft',
|
||||||
|
sortLayers: true,
|
||||||
|
sortFunction: (a, b) => {
|
||||||
|
return a.order - b.order;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(P.map);
|
||||||
|
}
|
||||||
|
addOverlay(name, layer, hide) {
|
||||||
|
this.controls.addOverlay(layer, name);
|
||||||
|
if (this.shouldHide(layer, hide) !== true) {
|
||||||
|
layer.addTo(P.map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeOverlay(layer) {
|
||||||
|
this.ignoreLayer = layer;
|
||||||
|
this.controls.removeLayer(layer);
|
||||||
|
layer.remove();
|
||||||
|
this.ignoreLayer = null;
|
||||||
|
}
|
||||||
|
shouldHide(layer, def) {
|
||||||
|
const value = window.localStorage.getItem(`hide_${layer.id}`);
|
||||||
|
return value == null ? def : value === 'true';
|
||||||
|
}
|
||||||
|
hideLayer(layer) {
|
||||||
|
if (layer != this.ignoreLayer) {
|
||||||
|
window.localStorage.setItem(`hide_${layer.id}`, 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showLayer(layer) {
|
||||||
|
if (layer != this.ignoreLayer) {
|
||||||
|
window.localStorage.setItem(`hide_${layer.id}`, 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setupTileLayers(world) {
|
||||||
|
// setup the map tile layers
|
||||||
|
// we need 2 layers to swap between for seamless refreshing
|
||||||
|
if (this.tileLayer1 != null) {
|
||||||
|
P.map.removeLayer(this.tileLayer1);
|
||||||
|
}
|
||||||
|
if (this.tileLayer2 != null) {
|
||||||
|
P.map.removeLayer(this.tileLayer2);
|
||||||
|
}
|
||||||
|
this.tileLayer1 = this.createTileLayer(world);
|
||||||
|
this.tileLayer2 = this.createTileLayer(world);
|
||||||
|
|
||||||
|
// refresh player's control
|
||||||
|
this.removeOverlay(this.playersLayer);
|
||||||
|
if (world.player_tracker.show_controls) {
|
||||||
|
this.addOverlay(world.player_tracker.label,
|
||||||
|
this.playersLayer,
|
||||||
|
world.player_tracker.default_hidden);
|
||||||
|
}
|
||||||
|
this.playersLayer.order = world.player_tracker.priority;
|
||||||
|
this.playersLayer.setZIndex(world.player_tracker.z_index);
|
||||||
|
}
|
||||||
|
createTileLayer(world) {
|
||||||
|
return new SquaremapTileLayer(`tiles/${world.name}/{z}/{x}_{y}.png`, {
|
||||||
|
tileSize: 512,
|
||||||
|
minNativeZoom: 0,
|
||||||
|
maxNativeZoom: world.zoom.max,
|
||||||
|
errorTileUrl: 'images/clear.png'
|
||||||
|
}).addTo(P.map)
|
||||||
|
.addEventListener("load", () => {
|
||||||
|
// when all tiles are loaded, switch to this layer
|
||||||
|
this.switchTileLayer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateTileLayer() {
|
||||||
|
// redraw background tile layer
|
||||||
|
if (this.currentLayer == 1) {
|
||||||
|
this.tileLayer2.redraw();
|
||||||
|
} else {
|
||||||
|
this.tileLayer1.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switchTileLayer() {
|
||||||
|
// swap current tile layer
|
||||||
|
if (this.currentLayer == 1) {
|
||||||
|
this.tileLayer1.setZIndex(0);
|
||||||
|
this.tileLayer2.setZIndex(1);
|
||||||
|
this.currentLayer = 2;
|
||||||
|
} else {
|
||||||
|
this.tileLayer1.setZIndex(1);
|
||||||
|
this.tileLayer2.setZIndex(0);
|
||||||
|
this.currentLayer = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LayerControl };
|
161
main/squaremap/web/js/modules/PlayerList.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { Player } from "./util/Player.js";
|
||||||
|
import { P } from './Squaremap.js';
|
||||||
|
|
||||||
|
class PlayerList {
|
||||||
|
constructor(json) {
|
||||||
|
this.players = new Map();
|
||||||
|
this.markers = new Map();
|
||||||
|
this.following = null;
|
||||||
|
this.firstTick = true;
|
||||||
|
this.label = json.player_list_label;
|
||||||
|
P.map.createPane("nameplate").style.zIndex = 1000;
|
||||||
|
}
|
||||||
|
tick() {
|
||||||
|
if (P.tick_count % P.worldList.curWorld.player_tracker.update_interval == 0) {
|
||||||
|
P.getJSON("tiles/players.json", (json) => {
|
||||||
|
this.updatePlayerList(json.players);
|
||||||
|
const title = `${this.label}`
|
||||||
|
.replace(/{cur}/g, json.players.length)
|
||||||
|
.replace(/{max}/g, json.max == null ? "???" : json.max)
|
||||||
|
if (P.sidebar.players.legend.innerHTML !== title) {
|
||||||
|
P.sidebar.players.legend.innerHTML = title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showPlayer(uuid) {
|
||||||
|
const player = this.players.get(uuid);
|
||||||
|
if (!P.worldList.worlds.has(player.world)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
P.worldList.showWorld(player.world, () => {
|
||||||
|
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
addToList(player) {
|
||||||
|
const head = document.createElement("img");
|
||||||
|
head.src = player.getHeadUrl();
|
||||||
|
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.innerHTML = player.displayName
|
||||||
|
|
||||||
|
const link = P.createElement("a", player.uuid, this);
|
||||||
|
link.onclick = function (e) {
|
||||||
|
if (this.parent.showPlayer(this.id)) {
|
||||||
|
this.parent.followPlayerMarker(this.id);
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
link.appendChild(head);
|
||||||
|
link.appendChild(span);
|
||||||
|
const fieldset = P.sidebar.players.element;
|
||||||
|
fieldset.appendChild(link);
|
||||||
|
Array.from(fieldset.getElementsByTagName("a"))
|
||||||
|
.sort((a, b) => {
|
||||||
|
return plain(a.getElementsByTagName("span")[0])
|
||||||
|
.localeCompare(plain(b.getElementsByTagName("span")[0]));
|
||||||
|
})
|
||||||
|
.forEach(link => fieldset.appendChild(link));
|
||||||
|
}
|
||||||
|
removeFromList(player) {
|
||||||
|
const link = document.getElementById(player.uuid);
|
||||||
|
if (link != null) {
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
this.players.delete(player.uuid);
|
||||||
|
player.removeMarker();
|
||||||
|
}
|
||||||
|
updatePlayerList(players) {
|
||||||
|
const playersToRemove = Array.from(this.players.keys());
|
||||||
|
|
||||||
|
let needsSort = false;
|
||||||
|
|
||||||
|
// update players from json
|
||||||
|
for (let i = 0; i < players.length; i++) {
|
||||||
|
let player = this.players.get(players[i].uuid);
|
||||||
|
if (player == null) {
|
||||||
|
// new player
|
||||||
|
player = new Player(players[i]);
|
||||||
|
this.players.set(player.uuid, player);
|
||||||
|
this.addToList(player);
|
||||||
|
} else {
|
||||||
|
const oldDisplayName = player.displayName;
|
||||||
|
player.update(players[i]);
|
||||||
|
if (oldDisplayName !== player.displayName) {
|
||||||
|
needsSort = true;
|
||||||
|
document.getElementById(player.uuid)
|
||||||
|
.getElementsByTagName("span")[0]
|
||||||
|
.innerHTML = player.displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playersToRemove.remove(players[i].uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove players not in json
|
||||||
|
for (let i = 0; i < playersToRemove.length; i++) {
|
||||||
|
const player = this.players.get(playersToRemove[i]);
|
||||||
|
this.removeFromList(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsSort) {
|
||||||
|
const fieldset = P.sidebar.players.element;
|
||||||
|
Array.from(fieldset.getElementsByTagName("a"))
|
||||||
|
.sort((a, b) => {
|
||||||
|
return plain(a.getElementsByTagName("span")[0])
|
||||||
|
.localeCompare(plain(b.getElementsByTagName("span")[0]));
|
||||||
|
})
|
||||||
|
.forEach(link => fieldset.appendChild(link));
|
||||||
|
}
|
||||||
|
|
||||||
|
// first tick only
|
||||||
|
if (this.firstTick) {
|
||||||
|
this.firstTick = false;
|
||||||
|
|
||||||
|
// follow uuid from url
|
||||||
|
const follow = P.getUrlParam("uuid", null);
|
||||||
|
if (follow != null && this.players.get(follow) != null) {
|
||||||
|
this.followPlayerMarker(follow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// follow highlighted player
|
||||||
|
if (this.following != null) {
|
||||||
|
const player = this.players.get(this.following);
|
||||||
|
if (player != null && P.worldList.curWorld != null) {
|
||||||
|
if (player.world !== P.worldList.curWorld.name) {
|
||||||
|
P.worldList.showWorld(player.world, () => {
|
||||||
|
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearPlayerMarkers() {
|
||||||
|
const playersToRemove = Array.from(this.players.keys());
|
||||||
|
for (let i = 0; i < playersToRemove.length; i++) {
|
||||||
|
const player = this.players.get(playersToRemove[i]);
|
||||||
|
player.removeMarker();
|
||||||
|
}
|
||||||
|
this.markers.clear();
|
||||||
|
//P.layerControl.playersLayer.clearLayers();
|
||||||
|
}
|
||||||
|
followPlayerMarker(uuid) {
|
||||||
|
if (this.following != null) {
|
||||||
|
document.getElementById(this.following).classList.remove("following");
|
||||||
|
this.following = null;
|
||||||
|
}
|
||||||
|
if (uuid != null) {
|
||||||
|
this.following = uuid;
|
||||||
|
document.getElementById(this.following).classList.add("following");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function plain(element) {
|
||||||
|
return element.textContent || element.innerText || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PlayerList };
|
47
main/squaremap/web/js/modules/Sidebar.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Pin } from "./util/Pin.js";
|
||||||
|
import { Fieldset } from "./util/Fieldset.js";
|
||||||
|
import { P } from './Squaremap.js';
|
||||||
|
|
||||||
|
class Sidebar {
|
||||||
|
constructor(json, show) {
|
||||||
|
this.sidebar = P.createElement("div", "sidebar", this);
|
||||||
|
this.showSidebar = show;
|
||||||
|
if (!show) {
|
||||||
|
this.sidebar.style.display = "none";
|
||||||
|
}
|
||||||
|
this.sidebar.addEventListener("click", (e) => {
|
||||||
|
P.playerList.followPlayerMarker(null);
|
||||||
|
});
|
||||||
|
document.body.appendChild(this.sidebar);
|
||||||
|
|
||||||
|
this.pin = new Pin(json.pinned == "pinned");
|
||||||
|
this.show(this.pin.pinned);
|
||||||
|
if (json.pinned != "hide") {
|
||||||
|
this.sidebar.appendChild(this.pin.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.worlds = new Fieldset("worlds", json.world_list_label);
|
||||||
|
this.sidebar.appendChild(this.worlds.element);
|
||||||
|
|
||||||
|
this.players = new Fieldset("players", json.player_list_label
|
||||||
|
.replace(/{cur}/g, 0)
|
||||||
|
.replace(/{max}/g, 0));
|
||||||
|
this.sidebar.appendChild(this.players.element);
|
||||||
|
|
||||||
|
this.sidebar.onmouseleave = () => {
|
||||||
|
if (!this.pin.pinned) {
|
||||||
|
this.show(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.sidebar.onmouseenter = () => {
|
||||||
|
if (!this.pin.pinned) {
|
||||||
|
this.show(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
show(show) {
|
||||||
|
this.sidebar.className = show ? "show" : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sidebar };
|
171
main/squaremap/web/js/modules/Squaremap.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { Sidebar } from "./Sidebar.js";
|
||||||
|
import { PlayerList } from "./PlayerList.js";
|
||||||
|
import { WorldList } from "./WorldList.js";
|
||||||
|
import { UICoordinates } from "./UICoordinates.js";
|
||||||
|
import { UILink } from "./UILink.js";
|
||||||
|
import { LayerControl } from "./LayerControl.js";
|
||||||
|
|
||||||
|
class SquaremapMap {
|
||||||
|
constructor() {
|
||||||
|
this.map = L.map("map", {
|
||||||
|
crs: L.CRS.Simple,
|
||||||
|
center: [0, 0],
|
||||||
|
attributionControl: false,
|
||||||
|
preferCanvas: true,
|
||||||
|
noWrap: true
|
||||||
|
})
|
||||||
|
.on('overlayadd', (e) => {
|
||||||
|
this.layerControl.showLayer(e.layer);
|
||||||
|
})
|
||||||
|
.on('overlayremove', (e) => {
|
||||||
|
this.layerControl.hideLayer(e.layer);
|
||||||
|
})
|
||||||
|
.on('click', (e) => {
|
||||||
|
this.playerList.followPlayerMarker(null);
|
||||||
|
})
|
||||||
|
.on('dblclick', (e) => {
|
||||||
|
this.playerList.followPlayerMarker(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tick_count = 1;
|
||||||
|
|
||||||
|
this.layerControl = new LayerControl();
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
loop() {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
this.tick();
|
||||||
|
this.tick_count++;
|
||||||
|
}
|
||||||
|
setTimeout(() => this.loop(), 1000);
|
||||||
|
}
|
||||||
|
tick() {
|
||||||
|
// tick player tracker
|
||||||
|
this.playerList.tick();
|
||||||
|
// tick world
|
||||||
|
this.worldList.curWorld.tick();
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
this.getJSON("tiles/settings.json", (json) => {
|
||||||
|
this.layerControl.init();
|
||||||
|
|
||||||
|
this.title = json.ui.title;
|
||||||
|
this.sidebar = new Sidebar(json.ui.sidebar, this.getUrlParam("show_sidebar", "true") === "true");
|
||||||
|
this.playerList = new PlayerList(json.ui.sidebar);
|
||||||
|
this.worldList = new WorldList(json.worlds);
|
||||||
|
this.coordinates = new UICoordinates(json.ui.coordinates, this.getUrlParam("show_coordinates", "true") === "true");
|
||||||
|
this.uiLink = new UILink(json.ui.link, this.getUrlParam("show_link_button", "true") === "true");
|
||||||
|
|
||||||
|
this.showControls = this.getUrlParam("show_controls", "true") === "true"
|
||||||
|
if (!this.showControls) {
|
||||||
|
let controlLayers = document.getElementsByClassName('leaflet-top leaflet-left');
|
||||||
|
controlLayers[0].style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.worldList.loadInitialWorld(json, (world) => {
|
||||||
|
this.loop();
|
||||||
|
this.centerOn(
|
||||||
|
this.getUrlParam("x", world.spawn.x),
|
||||||
|
this.getUrlParam("z", world.spawn.z),
|
||||||
|
this.getUrlParam("zoom", world.zoom.def));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
centerOn(x, z, zoom) {
|
||||||
|
this.map.setView(this.toLatLng(x, z), zoom);
|
||||||
|
this.uiLink.update();
|
||||||
|
return this.map;
|
||||||
|
}
|
||||||
|
toLatLng(x, z) {
|
||||||
|
return L.latLng(this.pixelsToMeters(-z), this.pixelsToMeters(x));
|
||||||
|
//return this.map.unproject([x, z], this.worldList.curWorld.zoom.max);
|
||||||
|
}
|
||||||
|
toPoint(latlng) {
|
||||||
|
return L.point(this.metersToPixels(latlng.lng), this.metersToPixels(-latlng.lat));
|
||||||
|
//return this.map.project(latlng, this.worldList.curWorld.zoom.max);
|
||||||
|
}
|
||||||
|
pixelsToMeters(num) {
|
||||||
|
return num * this.scale;
|
||||||
|
}
|
||||||
|
metersToPixels(num) {
|
||||||
|
return num / this.scale;
|
||||||
|
}
|
||||||
|
setScale(zoom) {
|
||||||
|
this.scale = (1 / Math.pow(2, zoom));
|
||||||
|
// store this on map for ellipse
|
||||||
|
this.map.options.scale = this.scale;
|
||||||
|
}
|
||||||
|
createElement(tag, id, parent) {
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
element.id = id;
|
||||||
|
element.parent = parent;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
createTextElement(tag, text) {
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
element.appendChild(document.createTextNode(text));
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
getJSON(url, fn) {
|
||||||
|
fetch(url, {cache: "no-store"})
|
||||||
|
.then(async res => {
|
||||||
|
if (res.ok) {
|
||||||
|
fn(await res.json());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getUrlParam(query, def) {
|
||||||
|
const url = window.location.search.substring(1);
|
||||||
|
const vars = url.split('&');
|
||||||
|
for (let i = 0; i < vars.length; i++) {
|
||||||
|
const param = vars[i].split('=');
|
||||||
|
if (param[0] === query) {
|
||||||
|
const value = param[1] === undefined ? '' : decodeURIComponent(param[1]);
|
||||||
|
return value === '' ? def : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
getUrlFromView() {
|
||||||
|
const center = this.toPoint(this.map.getCenter());
|
||||||
|
const zoom = this.map.getZoom();
|
||||||
|
const x = Math.floor(center.x);
|
||||||
|
const z = Math.floor(center.y);
|
||||||
|
const following = this.playerList.following ? `&uuid=${this.playerList.following}` : '';
|
||||||
|
let link = `?world=${this.worldList.curWorld.name}&zoom=${zoom}&x=${x}&z=${z}${following}`;
|
||||||
|
if (!this.showControls) {
|
||||||
|
link += "&show_controls=false"
|
||||||
|
}
|
||||||
|
if (!this.uiLink.showLinkButton) {
|
||||||
|
link += "&show_link_button=false"
|
||||||
|
}
|
||||||
|
if (!this.coordinates.showCoordinates) {
|
||||||
|
link += "&show_coordinates=false"
|
||||||
|
}
|
||||||
|
if (!this.sidebar.showSidebar) {
|
||||||
|
link += "&show_sidebar=false"
|
||||||
|
}
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
updateBrowserUrl(url) {
|
||||||
|
window.history.replaceState(null, "", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const P = new SquaremapMap();
|
||||||
|
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/3955096
|
||||||
|
Array.prototype.remove = function() {
|
||||||
|
var what, a = arguments, L = a.length, ax;
|
||||||
|
while (L && this.length) {
|
||||||
|
what = a[--L];
|
||||||
|
while ((ax = this.indexOf(what)) !== -1) {
|
||||||
|
this.splice(ax, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
43
main/squaremap/web/js/modules/SquaremapTileLayer.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export var SquaremapTileLayer = L.TileLayer.extend({
|
||||||
|
|
||||||
|
// @method createTile(coords: Object, done?: Function): HTMLElement
|
||||||
|
// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
|
||||||
|
// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
|
||||||
|
// callback is called when the tile has been loaded.
|
||||||
|
createTile: function (coords, done) {
|
||||||
|
var tile = document.createElement('img');
|
||||||
|
|
||||||
|
L.DomEvent.on(tile, 'load', () => {
|
||||||
|
//Once image has loaded revoke the object URL as we don't need it anymore
|
||||||
|
URL.revokeObjectURL(tile.src);
|
||||||
|
this._tileOnLoad(done, tile)
|
||||||
|
});
|
||||||
|
L.DomEvent.on(tile, 'error', L.Util.bind(this._tileOnError, this, done, tile));
|
||||||
|
|
||||||
|
if (this.options.crossOrigin || this.options.crossOrigin === '') {
|
||||||
|
tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.alt = '';
|
||||||
|
tile.setAttribute('role', 'presentation');
|
||||||
|
|
||||||
|
//Retrieve image via a fetch instead of just setting the src
|
||||||
|
//This works around the fact that browsers usually don't make a request for an image that was previously loaded,
|
||||||
|
//without resorting to changing the URL (which would break caching).
|
||||||
|
fetch(this.getTileUrl(coords))
|
||||||
|
.then(res => {
|
||||||
|
//Call leaflet's error handler if request fails for some reason
|
||||||
|
if (!res.ok) {
|
||||||
|
this._tileOnError(done, tile, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get image data and convert into object URL so it can be used as a src
|
||||||
|
//Leaflet's onload listener will take it from here
|
||||||
|
res.blob().then(blob => tile.src = URL.createObjectURL(blob));
|
||||||
|
})
|
||||||
|
.catch(() => this._tileOnError(done, tile, null));
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
});
|
44
main/squaremap/web/js/modules/UICoordinates.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { P } from './Squaremap.js';
|
||||||
|
|
||||||
|
class UICoordinates {
|
||||||
|
constructor(json, show) {
|
||||||
|
const Coords = L.Control.extend({
|
||||||
|
_container: null,
|
||||||
|
options: {
|
||||||
|
position: 'bottomleft'
|
||||||
|
},
|
||||||
|
onAdd: function () {
|
||||||
|
const coords = L.DomUtil.create('div', 'leaflet-control-layers coordinates');
|
||||||
|
this._coords = coords;
|
||||||
|
if (!show) {
|
||||||
|
this._coords.style.display = "none";
|
||||||
|
}
|
||||||
|
return coords;
|
||||||
|
},
|
||||||
|
update: function (html, point) {
|
||||||
|
this.x = point == null ? "---" : Math.floor(point.x);
|
||||||
|
this.z = point == null ? "---" : Math.floor(point.y);
|
||||||
|
if (html != null) {
|
||||||
|
this._coords.innerHTML = html
|
||||||
|
.replace(/{x}/g, this.x)
|
||||||
|
.replace(/{z}/g, this.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.showCoordinates = show;
|
||||||
|
this.html = json.html == null ? "undefined" : json.html;
|
||||||
|
this.coords = new Coords();
|
||||||
|
P.map.addControl(this.coords)
|
||||||
|
.addEventListener('mousemove', (event) => {
|
||||||
|
if (P.worldList.curWorld != null) {
|
||||||
|
this.coords.update(this.html, P.toPoint(event.latlng));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!json.enabled) {
|
||||||
|
this.coords._coords.style.display = "none";
|
||||||
|
}
|
||||||
|
this.coords.update(this.html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { UICoordinates };
|
39
main/squaremap/web/js/modules/UILink.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { P } from './Squaremap.js';
|
||||||
|
|
||||||
|
class UILink {
|
||||||
|
constructor(json, show) {
|
||||||
|
const Link = L.Control.extend({
|
||||||
|
_container: null,
|
||||||
|
options: {
|
||||||
|
position: 'bottomleft'
|
||||||
|
},
|
||||||
|
onAdd: function () {
|
||||||
|
const link = L.DomUtil.create('div', 'leaflet-control-layers link');
|
||||||
|
this._link = link;
|
||||||
|
if (!show) {
|
||||||
|
this._link.style.display = "none";
|
||||||
|
}
|
||||||
|
this.update();
|
||||||
|
return link;
|
||||||
|
},
|
||||||
|
update: function() {
|
||||||
|
const url = P.worldList.curWorld == null ? "" : P.getUrlFromView();
|
||||||
|
//P.updateBrowserUrl(url); // this spams browser history
|
||||||
|
this._link.innerHTML = `<a href='${url}'><img src='images/clear.png'/></a>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.showLinkButton = show;
|
||||||
|
this.link = new Link();
|
||||||
|
P.map.addControl(this.link)
|
||||||
|
.addEventListener('move', () => this.update())
|
||||||
|
.addEventListener('zoom', () => this.update());
|
||||||
|
if (!json.enabled) {
|
||||||
|
this.link._link.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.link.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { UILink };
|
97
main/squaremap/web/js/modules/WorldList.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { World } from "./util/World.js";
|
||||||
|
import { P } from './Squaremap.js';
|
||||||
|
|
||||||
|
class WorldList {
|
||||||
|
constructor(json) {
|
||||||
|
// get worlds from json
|
||||||
|
const unorderedMap = new Map();
|
||||||
|
for (let i = 0; i < json.length; i++) {
|
||||||
|
const world = new World(json[i]);
|
||||||
|
unorderedMap.set(world.name, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort worlds by order
|
||||||
|
this.worlds = new Map([...unorderedMap].sort((a, b) => a[1].order - b[1].order));
|
||||||
|
|
||||||
|
// set up world list link elements
|
||||||
|
for (const [name, world] of this.worlds) {
|
||||||
|
const link = P.createElement("a", name, this);
|
||||||
|
link.onclick = function () {
|
||||||
|
const curWorld = this.parent.curWorld;
|
||||||
|
if (curWorld.name == name) {
|
||||||
|
P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
P.playerList.clearPlayerMarkers();
|
||||||
|
this.parent.loadWorld(name, (world) => {
|
||||||
|
P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = this.getIcon(world);
|
||||||
|
|
||||||
|
link.appendChild(img);
|
||||||
|
link.appendChild(P.createTextElement("span", world.display_name));
|
||||||
|
|
||||||
|
P.sidebar.worlds.element.appendChild(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getIcon(world) {
|
||||||
|
if (world.icon != null && world.icon != "") {
|
||||||
|
return `images/icon/${world.icon}.png`;
|
||||||
|
}
|
||||||
|
switch (world.type) {
|
||||||
|
case "nether":
|
||||||
|
return "images/icon/red-cube-smol.png";
|
||||||
|
case "the_end":
|
||||||
|
return "images/icon/purple-cube-smol.png";
|
||||||
|
case "normal":
|
||||||
|
default:
|
||||||
|
return "images/icon/green-cube-smol.png";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadInitialWorld(json, callback) {
|
||||||
|
let updateUrl = false
|
||||||
|
let name = P.getUrlParam("world", null)
|
||||||
|
if (name != null) {
|
||||||
|
const world = this.worlds.get(name);
|
||||||
|
if (world == null) {
|
||||||
|
updateUrl = true
|
||||||
|
name = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name == null) {
|
||||||
|
name = json.worlds.sort((a, b) => a.order - b.order)[0].name
|
||||||
|
}
|
||||||
|
this.loadWorld(name, (a) => {
|
||||||
|
callback(a)
|
||||||
|
if (updateUrl) {
|
||||||
|
P.updateBrowserUrl(`?world=${this.curWorld.name}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loadWorld(name, callback) {
|
||||||
|
// unload current world
|
||||||
|
if (this.curWorld != null) {
|
||||||
|
this.curWorld.unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// load new world
|
||||||
|
const world = this.worlds.get(name);
|
||||||
|
this.curWorld = world;
|
||||||
|
world.load(callback);
|
||||||
|
}
|
||||||
|
showWorld(world, callback) {
|
||||||
|
if (this.curWorld.name == world) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadWorld(world, callback);
|
||||||
|
P.updateBrowserUrl(P.getUrlFromView());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorldList };
|
11
main/squaremap/web/js/modules/util/Fieldset.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { P } from '../Squaremap.js';
|
||||||
|
|
||||||
|
class Fieldset {
|
||||||
|
constructor(id, title) {
|
||||||
|
this.element = P.createElement("fieldset", id);
|
||||||
|
this.legend = P.createTextElement("legend", title);
|
||||||
|
this.element.appendChild(this.legend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Fieldset };
|
163
main/squaremap/web/js/modules/util/Markers.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { P } from '../Squaremap.js';
|
||||||
|
|
||||||
|
class Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
this.opts = opts;
|
||||||
|
this.id = this.opts.pop("id");
|
||||||
|
this.popup = this.opts.pop("popup");
|
||||||
|
this.popup_sticky = true;
|
||||||
|
this.tooltip = this.opts.pop("tooltip");
|
||||||
|
this.tooltip_sticky = true;
|
||||||
|
}
|
||||||
|
init() {
|
||||||
|
if (this.popup != null) {
|
||||||
|
if (this.popup_sticky) {
|
||||||
|
this.marker.on('click', (e) => {
|
||||||
|
L.popup({
|
||||||
|
direction: this.opts.pop("tooltip_direction", "top")
|
||||||
|
})
|
||||||
|
.setLatLng(P.toLatLng(P.coordinates.coords.x, P.coordinates.coords.z))
|
||||||
|
.setContent(this.popup)
|
||||||
|
.openOn(P.map);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.marker.bindPopup(() => this.popup, {
|
||||||
|
direction: this.opts.pop("tooltip_direction", "top")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.tooltip != null) {
|
||||||
|
this.marker.bindTooltip(() => this.tooltip, {
|
||||||
|
direction: this.opts.pop("tooltip_direction", "top"),
|
||||||
|
sticky: this.tooltip_sticky
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const key in this.opts) {
|
||||||
|
this.marker.options[key] = this.opts[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTo(layer) {
|
||||||
|
this.marker.remove();
|
||||||
|
this.marker.addTo(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Options {
|
||||||
|
constructor(json) {
|
||||||
|
for (const prop in json) {
|
||||||
|
this[prop] = json[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pop(key, def) {
|
||||||
|
const val = this[key];
|
||||||
|
delete this[key];
|
||||||
|
return val == null ? def : val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rectangle extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const points = this.opts.pop("points");
|
||||||
|
this.marker = L.rectangle([P.toLatLng(points[0].x, points[0].z), P.toLatLng(points[1].x, points[1].z)]);
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PolyLine extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const points = this.opts.pop("points");
|
||||||
|
const outer = [];
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
if (Symbol.iterator in Object(points[i])) {
|
||||||
|
const inner = [];
|
||||||
|
for (let j = 0; j < points[i].length; j++) {
|
||||||
|
inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
|
||||||
|
}
|
||||||
|
outer.push(inner);
|
||||||
|
} else {
|
||||||
|
outer.push(P.toLatLng(points[i].x, points[i].z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.marker = L.polyline(outer);
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Polygon extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const points = this.opts.pop("points");
|
||||||
|
const outer = [];
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
if (Symbol.iterator in Object(points[i])) {
|
||||||
|
const inner = [];
|
||||||
|
for (let j = 0; j < points[i].length; j++) {
|
||||||
|
if (Symbol.iterator in Object(points[i][j])) {
|
||||||
|
const inner2 = [];
|
||||||
|
for (let k = 0; k < points[i][j].length; k++) {
|
||||||
|
inner2.push(P.toLatLng(points[i][j][k].x, points[i][j][k].z));
|
||||||
|
}
|
||||||
|
inner.push(inner2);
|
||||||
|
} else {
|
||||||
|
inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outer.push(inner);
|
||||||
|
} else {
|
||||||
|
outer.push(P.toLatLng(points[i].x, points[i].z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.marker = L.polygon(outer);
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Circle extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const center = this.opts.pop("center");
|
||||||
|
const radius = this.opts.pop("radius");
|
||||||
|
this.marker = L.circle(P.toLatLng(center.x, center.z), {
|
||||||
|
radius: P.pixelsToMeters(radius)
|
||||||
|
});
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ellipse extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const center = this.opts.pop("center");
|
||||||
|
const radiusX = this.opts.pop("radiusX");
|
||||||
|
const radiusZ = this.opts.pop("radiusZ");
|
||||||
|
const tilt = 0;
|
||||||
|
this.marker = L.ellipse(P.toLatLng(center.x, center.z), [radiusX, radiusZ], tilt);
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Icon extends Marker {
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts);
|
||||||
|
const point = this.opts.pop("point");
|
||||||
|
const size = this.opts.pop("size");
|
||||||
|
const anchor = this.opts.pop("anchor");
|
||||||
|
const tooltipAnchor = this.opts.pop("tooltip_anchor", L.point(0, -size.z / 2));
|
||||||
|
this.marker = L.marker(P.toLatLng(point.x, point.z), {
|
||||||
|
icon: L.icon({
|
||||||
|
iconUrl: `images/icon/registered/${opts.pop("icon")}.png`,
|
||||||
|
iconSize: [size.x, size.z],
|
||||||
|
iconAnchor: [anchor.x, anchor.z],
|
||||||
|
popupAnchor: [tooltipAnchor.x, tooltipAnchor.z],
|
||||||
|
tooltipAnchor: [tooltipAnchor.x, tooltipAnchor.z]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this.popup_sticky = false;
|
||||||
|
this.tooltip_sticky = false;
|
||||||
|
super.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Marker, Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon };
|
23
main/squaremap/web/js/modules/util/Pin.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { P } from '../Squaremap.js';
|
||||||
|
|
||||||
|
class Pin {
|
||||||
|
constructor(def) {
|
||||||
|
this.pinned = def;
|
||||||
|
|
||||||
|
this.element = P.createElement("img", "pin", this);
|
||||||
|
|
||||||
|
this.element.onclick = () => this.toggle();
|
||||||
|
|
||||||
|
this.pin(this.pinned);
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
this.pin(!this.pinned);
|
||||||
|
}
|
||||||
|
pin(pin) {
|
||||||
|
this.pinned = pin;
|
||||||
|
this.element.className = pin ? "pinned" : "unpinned";
|
||||||
|
this.element.src = `images/${this.element.className}.png`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Pin };
|
98
main/squaremap/web/js/modules/util/Player.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { P } from '../Squaremap.js';
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
constructor(json) {
|
||||||
|
this.name = json.name;
|
||||||
|
this.uuid = json.uuid;
|
||||||
|
this.world = json.world;
|
||||||
|
this.displayName = json.display_name !== undefined ? json.display_name : json.name;
|
||||||
|
this.x = 0;
|
||||||
|
this.z = 0;
|
||||||
|
this.armor = 0;
|
||||||
|
this.health = 20;
|
||||||
|
this.tooltip = L.tooltip({
|
||||||
|
permanent: true,
|
||||||
|
direction: "right",
|
||||||
|
offset: [10, 0],
|
||||||
|
pane: "nameplate"
|
||||||
|
});
|
||||||
|
this.marker = L.marker(P.toLatLng(json.x, json.z), {
|
||||||
|
icon: L.icon({
|
||||||
|
iconUrl: 'images/icon/player.png',
|
||||||
|
iconSize: [17, 16],
|
||||||
|
iconAnchor: [8, 9],
|
||||||
|
tooltipAnchor: [0, 0]
|
||||||
|
}),
|
||||||
|
rotationAngle: (180 + json.yaw)
|
||||||
|
});
|
||||||
|
if (P.worldList.curWorld.player_tracker.nameplates.enabled) {
|
||||||
|
this.updateNameplate(json);
|
||||||
|
this.marker.bindTooltip(this.tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getHeadUrl() {
|
||||||
|
return P.worldList.curWorld.player_tracker.nameplates.heads_url
|
||||||
|
.replace(/{uuid}/g, this.uuid)
|
||||||
|
.replace(/{name}/g, this.name);
|
||||||
|
}
|
||||||
|
updateNameplate(player) {
|
||||||
|
let headImg = "";
|
||||||
|
let armorImg = "";
|
||||||
|
let healthImg = "";
|
||||||
|
if (P.worldList.curWorld.player_tracker.nameplates.show_heads) {
|
||||||
|
headImg = `<img src='${this.getHeadUrl()}' class="head" />`;
|
||||||
|
}
|
||||||
|
if (P.worldList.curWorld.player_tracker.nameplates.show_armor && player.armor != null) {
|
||||||
|
armorImg = `<img src="images/armor/${Math.min(Math.max(player.armor, 0), 20)}.png" class="armor" />`;
|
||||||
|
}
|
||||||
|
if (P.worldList.curWorld.player_tracker.nameplates.show_health && player.health != null) {
|
||||||
|
healthImg = `<img src="images/health/${Math.min(Math.max(player.health, 0), 20)}.png" class="health" />`;
|
||||||
|
}
|
||||||
|
this.tooltip.setContent(`<ul><li>${headImg}</li><li>${this.displayName}${healthImg}${armorImg}</li>`);
|
||||||
|
}
|
||||||
|
update(player) {
|
||||||
|
this.x = player.x;
|
||||||
|
this.z = player.z;
|
||||||
|
this.world = player.world;
|
||||||
|
this.armor = player.armor;
|
||||||
|
this.health = player.health;
|
||||||
|
this.displayName = player.display_name !== undefined ? player.display_name : player.name;
|
||||||
|
const link = document.getElementById(player.uuid);
|
||||||
|
const img = link.getElementsByTagName("img")[0];
|
||||||
|
const span = link.getElementsByTagName("span")[0];
|
||||||
|
if (P.worldList.curWorld.name == player.world) {
|
||||||
|
if (P.worldList.curWorld.player_tracker.enabled) {
|
||||||
|
this.addMarker();
|
||||||
|
}
|
||||||
|
const latlng = P.toLatLng(player.x, player.z);
|
||||||
|
if (!this.marker.getLatLng().equals(latlng)) {
|
||||||
|
this.marker.setLatLng(latlng);
|
||||||
|
}
|
||||||
|
const angle = 180 + player.yaw;
|
||||||
|
if (this.marker.options.rotationAngle != angle) {
|
||||||
|
this.marker.setRotationAngle(angle);
|
||||||
|
}
|
||||||
|
img.classList.remove("other-world");
|
||||||
|
span.classList.remove("other-world");
|
||||||
|
} else {
|
||||||
|
this.removeMarker();
|
||||||
|
img.classList.add("other-world");
|
||||||
|
span.classList.add("other-world");
|
||||||
|
}
|
||||||
|
this.updateNameplate(player);
|
||||||
|
}
|
||||||
|
removeMarker() {
|
||||||
|
this.marker.remove();
|
||||||
|
P.playerList.markers.delete(this.uuid);
|
||||||
|
P.map.removeLayer(this.marker);
|
||||||
|
P.layerControl.playersLayer.removeLayer(this.marker);
|
||||||
|
}
|
||||||
|
addMarker() {
|
||||||
|
if (!P.playerList.markers.has(this.uuid)) {
|
||||||
|
this.marker.addTo(P.layerControl.playersLayer);
|
||||||
|
P.playerList.markers.set(this.uuid, this.marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Player };
|
145
main/squaremap/web/js/modules/util/World.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon } from "./Markers.js";
|
||||||
|
import { P } from '../Squaremap.js';
|
||||||
|
|
||||||
|
class World {
|
||||||
|
constructor(json) {
|
||||||
|
this.name = json.name;
|
||||||
|
this.order = json.order;
|
||||||
|
this.icon = json.icon;
|
||||||
|
this.type = json.type;
|
||||||
|
this.display_name = json.display_name;
|
||||||
|
this.markerLayers = new Map();
|
||||||
|
this.player_tracker = {};
|
||||||
|
this.marker_update_interval = 5;
|
||||||
|
this.tiles_update_interval = 15;
|
||||||
|
}
|
||||||
|
tick() {
|
||||||
|
// refresh map tile layer
|
||||||
|
if (P.tick_count % this.tiles_update_interval == 0) {
|
||||||
|
P.layerControl.updateTileLayer();
|
||||||
|
}
|
||||||
|
// load and draw markers
|
||||||
|
if (P.tick_count % this.marker_update_interval == 0) {
|
||||||
|
P.getJSON(`tiles/${this.name}/markers.json`, (json) => {
|
||||||
|
if (this === P.worldList.curWorld) {
|
||||||
|
this.markers(json);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unload() {
|
||||||
|
P.playerList.clearPlayerMarkers();
|
||||||
|
const keys = Array.from(this.markerLayers.keys());
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const layer = this.markerLayers.get(keys[i]);
|
||||||
|
P.layerControl.controls.removeLayer(layer);
|
||||||
|
layer.remove();
|
||||||
|
this.markerLayers.delete(keys[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load(callback) {
|
||||||
|
P.getJSON(`tiles/${this.name}/settings.json`, (json) => {
|
||||||
|
this.player_tracker = json.player_tracker;
|
||||||
|
this.zoom = json.zoom;
|
||||||
|
this.spawn = json.spawn;
|
||||||
|
this.marker_update_interval = json.marker_update_interval;
|
||||||
|
this.tiles_update_interval = json.tiles_update_interval;
|
||||||
|
|
||||||
|
// set the scale for our projection calculations
|
||||||
|
P.setScale(this.zoom.max);
|
||||||
|
|
||||||
|
// set center and zoom
|
||||||
|
P.centerOn(this.spawn.x, this.spawn.z, this.zoom.def)
|
||||||
|
.setMinZoom(0) // extra zoom out doesn't work :(
|
||||||
|
.setMaxZoom(this.zoom.max + this.zoom.extra);
|
||||||
|
|
||||||
|
// update page title
|
||||||
|
document.title = P.title
|
||||||
|
.replace(/{world}/g, this.display_name);
|
||||||
|
|
||||||
|
// setup background
|
||||||
|
document.getElementById("map").style.background = this.getBackground();
|
||||||
|
|
||||||
|
// setup tile layers
|
||||||
|
P.layerControl.setupTileLayers(this);
|
||||||
|
|
||||||
|
// force clear player markers
|
||||||
|
P.playerList.clearPlayerMarkers();
|
||||||
|
|
||||||
|
// tick now, reset counter
|
||||||
|
P.tick_count = 0;
|
||||||
|
P.tick();
|
||||||
|
|
||||||
|
// force clear player markers
|
||||||
|
P.playerList.clearPlayerMarkers();
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
callback(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getBackground() {
|
||||||
|
switch (this.type) {
|
||||||
|
case "nether":
|
||||||
|
return "url('images/nether_sky.png')";
|
||||||
|
case "the_end":
|
||||||
|
return "url('images/end_sky.png')";
|
||||||
|
case "normal":
|
||||||
|
default:
|
||||||
|
return "url('images/overworld_sky.png')";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markers(json) {
|
||||||
|
// check if json is iterable
|
||||||
|
if (json == null || !(Symbol.iterator in Object(json))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// iterate layers
|
||||||
|
for (const entry of json) {
|
||||||
|
// check if layer exists and needs updating
|
||||||
|
let layer = this.markerLayers.get(entry.id);
|
||||||
|
if (layer != null) {
|
||||||
|
if (layer.timestamp === entry.timestamp) {
|
||||||
|
continue; // skip
|
||||||
|
}
|
||||||
|
// clear existing layer to rebuild
|
||||||
|
P.layerControl.removeOverlay(layer);
|
||||||
|
// TODO
|
||||||
|
// implement marker tracker instead of clearing
|
||||||
|
// to reduce possible client side lag
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the layer
|
||||||
|
layer = new L.LayerGroup();
|
||||||
|
layer.order = entry.order;
|
||||||
|
layer.id = entry.id;
|
||||||
|
layer.timestamp = entry.timestamp;
|
||||||
|
layer.setZIndex(entry.z_index);
|
||||||
|
this.markerLayers.set(layer.id, layer);
|
||||||
|
|
||||||
|
// setup the layer control
|
||||||
|
if (entry.control === true) {
|
||||||
|
P.layerControl.addOverlay(entry.name, layer, entry.hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the markers
|
||||||
|
for (const shape in entry.markers) {
|
||||||
|
let marker;
|
||||||
|
const opts = new Options(entry.markers[shape]);
|
||||||
|
switch(opts.pop("type")) {
|
||||||
|
case "rectangle": marker = new Rectangle(opts); break;
|
||||||
|
case "polyline": marker = new PolyLine(opts); break;
|
||||||
|
case "polygon": marker = new Polygon(opts); break;
|
||||||
|
case "circle": marker = new Circle(opts); break;
|
||||||
|
case "ellipse": marker = new Ellipse(opts); break;
|
||||||
|
case "icon": marker = new Icon(opts); break;
|
||||||
|
}
|
||||||
|
if (marker != null) {
|
||||||
|
marker.addTo(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { World };
|
111
main/squaremap/web/live-atlas/assets/index.1de34b3a.css
Normal file
59
main/squaremap/web/live-atlas/assets/index.a82d2095.js
Normal file
70
main/squaremap/web/live-atlas/assets/vendor.d0ab50b1.js
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
main/squaremap/web/live-atlas/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
17
main/squaremap/web/live-atlas/favicons/favicon.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg viewBox="0 0 270.06 267.09" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-.49951 -4.5791)">
|
||||||
|
<style>
|
||||||
|
path {
|
||||||
|
fill: #222;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path {
|
||||||
|
fill: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<path transform="scale(.75)" d="m0.66602 9.7227v298.96c0.046934 2.8741 0.25213 2.6741 2.535 3.6641l110.65 47.987c3.8989 1.918 3.6147 1.7537 3.6147-2.3528v-297.68c-0.0605-3.8542 0.32855-4.1331-2.8087-5.4937l-110.14-47.768c-2.988-1.1299-3.8517-1.741-3.8517 2.6812zm65.271 177.53c19.088 0 34.562 15.922 34.562 35.562 2.2e-4 6.4076-1.6821 12.696-4.8691 18.201l-25.939 55.648c-1.8045 3.8714-3.0529 3.8185-5.0156-0.0742l-28.344-55.426c-0.68826-1.1762-1.3097-2.3924-1.8613-3.6426l-0.21875-0.42578 0.03711 8e-3c-1.9214-4.5044-2.9137-9.3701-2.9141-14.289 0-19.641 15.474-35.562 34.562-35.562zm0 20.625c-8.0178-6e-5 -14.518 6.6877-14.518 14.938s6.4998 14.938 14.518 14.938c8.0178 6e-5 14.518-6.6877 14.518-14.938s-6.4998-14.938-14.518-14.938z"/>
|
||||||
|
<path transform="scale(.75)" d="m236.43 8.5914-86.609 38.801c-9.3133 4.1592-8.1971 3.54-8.1971 14.952v297.52c-0.0201 2.6943-0.30938 2.8029 2.1555 1.7437l97.364-41.839c2.3162-0.98051 2.0784-1.6006 2.0784-3.5142v-303.35c0.15129-9.1976 0.75524-7.3665-6.7914-4.3133zm-44.714 54.614c19.088 0 34.562 15.922 34.562 35.562-1.9e-4 6.4108-1.6846 12.702-4.875 18.209l-25.934 55.643c-1.8045 3.8714-3.0529 3.8185-5.0156-0.0742l-28.357-55.453c-0.6778-1.1591-1.2908-2.357-1.8359-3.5879l-0.23243-0.45508 0.0391 8e-3c-1.9214-4.5044-2.9136-9.3699-2.9141-14.289 0-19.641 15.474-35.562 34.562-35.562zm0 20.625c-8.0178-6e-5 -14.518 6.6877-14.518 14.938 0 8.2498 6.4998 14.938 14.518 14.938 8.0178 7e-5 14.518-6.6877 14.518-14.938 0-8.2498-6.4998-14.938-14.518-14.938z"/>
|
||||||
|
<path d="m267.64 36.871-59.959-29.533c-6.555-3.2203-6.5798-4.161-6.4472 2.3579v225.51c-0.0251 3.9614 0.03 3.9662 3.1935 5.4609l62.579 29.567c3.8853 1.693 3.4322 1.9822 3.5316-1.5506v-224.87c0.0476-4.7459 0.23478-5.4796-2.8976-6.934z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
7
main/squaremap/web/live-atlas/favicons/mask.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg viewBox="0 0 270.06 267.09" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-.49951 -4.5791)">
|
||||||
|
<path transform="scale(.75)" d="m0.66602 9.7227v298.96c0.046934 2.8741 0.25213 2.6741 2.535 3.6641l110.65 47.987c3.8989 1.918 3.6147 1.7537 3.6147-2.3528v-297.68c-0.0605-3.8542 0.32855-4.1331-2.8087-5.4937l-110.14-47.768c-2.988-1.1299-3.8517-1.741-3.8517 2.6812zm65.271 177.53c19.088 0 34.562 15.922 34.562 35.562 2.2e-4 6.4076-1.6821 12.696-4.8691 18.201l-25.939 55.648c-1.8045 3.8714-3.0529 3.8185-5.0156-0.0742l-28.344-55.426c-0.68826-1.1762-1.3097-2.3924-1.8613-3.6426l-0.21875-0.42578 0.03711 8e-3c-1.9214-4.5044-2.9137-9.3701-2.9141-14.289 0-19.641 15.474-35.562 34.562-35.562zm0 20.625c-8.0178-6e-5 -14.518 6.6877-14.518 14.938s6.4998 14.938 14.518 14.938c8.0178 6e-5 14.518-6.6877 14.518-14.938s-6.4998-14.938-14.518-14.938z"/>
|
||||||
|
<path transform="scale(.75)" d="m236.43 8.5914-86.609 38.801c-9.3133 4.1592-8.1971 3.54-8.1971 14.952v297.52c-0.0201 2.6943-0.30938 2.8029 2.1555 1.7437l97.364-41.839c2.3162-0.98051 2.0784-1.6006 2.0784-3.5142v-303.35c0.15129-9.1976 0.75524-7.3665-6.7914-4.3133zm-44.714 54.614c19.088 0 34.562 15.922 34.562 35.562-1.9e-4 6.4108-1.6846 12.702-4.875 18.209l-25.934 55.643c-1.8045 3.8714-3.0529 3.8185-5.0156-0.0742l-28.357-55.453c-0.6778-1.1591-1.2908-2.357-1.8359-3.5879l-0.23243-0.45508 0.0391 8e-3c-1.9214-4.5044-2.9136-9.3699-2.9141-14.289 0-19.641 15.474-35.562 34.562-35.562zm0 20.625c-8.0178-6e-5 -14.518 6.6877-14.518 14.938 0 8.2498 6.4998 14.938 14.518 14.938 8.0178 7e-5 14.518-6.6877 14.518-14.938 0-8.2498-6.4998-14.938-14.518-14.938z"/>
|
||||||
|
<path d="m267.64 36.871-59.959-29.533c-6.555-3.2203-6.5798-4.161-6.4472 2.3579v225.51c-0.0251 3.9614 0.03 3.9662 3.1935 5.4609l62.579 29.567c3.8853 1.693 3.4322 1.9822 3.5316-1.5506v-224.87c0.0476-4.7459 0.23478-5.4796-2.8976-6.934z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
14
main/squaremap/web/live-atlas/favicons/site.webmanifest
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "Minecraft Dynamic Map",
|
||||||
|
"short_name": "Minecraft Dynamic Map",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#222222",
|
||||||
|
"background_color": "#222222",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
BIN
main/squaremap/web/tiles/minecraft_overworld/0/-1_-1.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/0/-1_0.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/0/-2_0.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/0/-3_0.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/0/0_-1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/0/0_0.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/1/-1_-1.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/1/-1_0.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/1/-2_0.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
main/squaremap/web/tiles/minecraft_overworld/1/-3_0.png
Normal file
After Width: | Height: | Size: 8.6 KiB |