Changed: Squaremap configs support LiveAtlas & Squaremap Markers

This commit is contained in:
Phil Keier 2024-12-10 22:53:03 +00:00
parent 9e6198b15a
commit b1cb21fddf
162 changed files with 2337 additions and 2 deletions

1
main/.gitignore vendored
View File

@ -1,4 +1,3 @@
/data
/squaremap/data/
/squaremap/locale/
/squaremap/web/

View File

@ -37,6 +37,7 @@ https://cdn.modrinth.com/data/1u6JkXh5/versions/vBzkrSYP/worldedit-mod-7.3.6.jar
# Squaremap
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
https://cdn.modrinth.com/data/py6EMmAJ/versions/xpvSS4oW/yawp-0.0.2.10-alpha2.jar

View File

@ -13,7 +13,7 @@ settings:
web-address: http://localhost:8080
web-directory:
path: web
auto-update: true
auto-update: false
image-quality:
compress-images:
enabled: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View 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>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View 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

View 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

View 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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Some files were not shown because too many files have changed in this diff Show More