diff --git a/main/.gitignore b/main/.gitignore index 170ea4a..a267f0d 100644 --- a/main/.gitignore +++ b/main/.gitignore @@ -1,4 +1,3 @@ /data /squaremap/data/ /squaremap/locale/ -/squaremap/web/ diff --git a/main/mods.txt b/main/mods.txt index 0fc2ed4..b729383 100644 --- a/main/mods.txt +++ b/main/mods.txt @@ -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 diff --git a/main/squaremap/config.yml b/main/squaremap/config.yml index 6bca1d7..ad91cc5 100644 --- a/main/squaremap/config.yml +++ b/main/squaremap/config.yml @@ -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 diff --git a/main/squaremap/images/blank.png b/main/squaremap/images/blank.png new file mode 100644 index 0000000..e0d1092 Binary files /dev/null and b/main/squaremap/images/blank.png differ diff --git a/main/squaremap/web/css/styles.css b/main/squaremap/web/css/styles.css new file mode 100644 index 0000000..23dd3b3 --- /dev/null +++ b/main/squaremap/web/css/styles.css @@ -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); +} diff --git a/main/squaremap/web/favicon.ico b/main/squaremap/web/favicon.ico new file mode 100644 index 0000000..08aca0a Binary files /dev/null and b/main/squaremap/web/favicon.ico differ diff --git a/main/squaremap/web/images/armor/0.png b/main/squaremap/web/images/armor/0.png new file mode 100644 index 0000000..26f6c1b Binary files /dev/null and b/main/squaremap/web/images/armor/0.png differ diff --git a/main/squaremap/web/images/armor/1.png b/main/squaremap/web/images/armor/1.png new file mode 100644 index 0000000..76fec47 Binary files /dev/null and b/main/squaremap/web/images/armor/1.png differ diff --git a/main/squaremap/web/images/armor/10.png b/main/squaremap/web/images/armor/10.png new file mode 100644 index 0000000..886f8b5 Binary files /dev/null and b/main/squaremap/web/images/armor/10.png differ diff --git a/main/squaremap/web/images/armor/11.png b/main/squaremap/web/images/armor/11.png new file mode 100644 index 0000000..fddeef6 Binary files /dev/null and b/main/squaremap/web/images/armor/11.png differ diff --git a/main/squaremap/web/images/armor/12.png b/main/squaremap/web/images/armor/12.png new file mode 100644 index 0000000..8094700 Binary files /dev/null and b/main/squaremap/web/images/armor/12.png differ diff --git a/main/squaremap/web/images/armor/13.png b/main/squaremap/web/images/armor/13.png new file mode 100644 index 0000000..9509cd0 Binary files /dev/null and b/main/squaremap/web/images/armor/13.png differ diff --git a/main/squaremap/web/images/armor/14.png b/main/squaremap/web/images/armor/14.png new file mode 100644 index 0000000..e184853 Binary files /dev/null and b/main/squaremap/web/images/armor/14.png differ diff --git a/main/squaremap/web/images/armor/15.png b/main/squaremap/web/images/armor/15.png new file mode 100644 index 0000000..129c698 Binary files /dev/null and b/main/squaremap/web/images/armor/15.png differ diff --git a/main/squaremap/web/images/armor/16.png b/main/squaremap/web/images/armor/16.png new file mode 100644 index 0000000..d5e1d55 Binary files /dev/null and b/main/squaremap/web/images/armor/16.png differ diff --git a/main/squaremap/web/images/armor/17.png b/main/squaremap/web/images/armor/17.png new file mode 100644 index 0000000..74c3d09 Binary files /dev/null and b/main/squaremap/web/images/armor/17.png differ diff --git a/main/squaremap/web/images/armor/18.png b/main/squaremap/web/images/armor/18.png new file mode 100644 index 0000000..f44231f Binary files /dev/null and b/main/squaremap/web/images/armor/18.png differ diff --git a/main/squaremap/web/images/armor/19.png b/main/squaremap/web/images/armor/19.png new file mode 100644 index 0000000..52dc495 Binary files /dev/null and b/main/squaremap/web/images/armor/19.png differ diff --git a/main/squaremap/web/images/armor/2.png b/main/squaremap/web/images/armor/2.png new file mode 100644 index 0000000..d13303f Binary files /dev/null and b/main/squaremap/web/images/armor/2.png differ diff --git a/main/squaremap/web/images/armor/20.png b/main/squaremap/web/images/armor/20.png new file mode 100644 index 0000000..0dac29e Binary files /dev/null and b/main/squaremap/web/images/armor/20.png differ diff --git a/main/squaremap/web/images/armor/3.png b/main/squaremap/web/images/armor/3.png new file mode 100644 index 0000000..363980f Binary files /dev/null and b/main/squaremap/web/images/armor/3.png differ diff --git a/main/squaremap/web/images/armor/4.png b/main/squaremap/web/images/armor/4.png new file mode 100644 index 0000000..3bc6bfd Binary files /dev/null and b/main/squaremap/web/images/armor/4.png differ diff --git a/main/squaremap/web/images/armor/5.png b/main/squaremap/web/images/armor/5.png new file mode 100644 index 0000000..b852a47 Binary files /dev/null and b/main/squaremap/web/images/armor/5.png differ diff --git a/main/squaremap/web/images/armor/6.png b/main/squaremap/web/images/armor/6.png new file mode 100644 index 0000000..4cb8f93 Binary files /dev/null and b/main/squaremap/web/images/armor/6.png differ diff --git a/main/squaremap/web/images/armor/7.png b/main/squaremap/web/images/armor/7.png new file mode 100644 index 0000000..6ebdb7f Binary files /dev/null and b/main/squaremap/web/images/armor/7.png differ diff --git a/main/squaremap/web/images/armor/8.png b/main/squaremap/web/images/armor/8.png new file mode 100644 index 0000000..b7d85ef Binary files /dev/null and b/main/squaremap/web/images/armor/8.png differ diff --git a/main/squaremap/web/images/armor/9.png b/main/squaremap/web/images/armor/9.png new file mode 100644 index 0000000..3789511 Binary files /dev/null and b/main/squaremap/web/images/armor/9.png differ diff --git a/main/squaremap/web/images/blank.png b/main/squaremap/web/images/blank.png new file mode 100644 index 0000000..e0d1092 Binary files /dev/null and b/main/squaremap/web/images/blank.png differ diff --git a/main/squaremap/web/images/clear.png b/main/squaremap/web/images/clear.png new file mode 100644 index 0000000..c07a578 Binary files /dev/null and b/main/squaremap/web/images/clear.png differ diff --git a/main/squaremap/web/images/end_sky.png b/main/squaremap/web/images/end_sky.png new file mode 100644 index 0000000..b4c62df Binary files /dev/null and b/main/squaremap/web/images/end_sky.png differ diff --git a/main/squaremap/web/images/foliage.png b/main/squaremap/web/images/foliage.png new file mode 100644 index 0000000..3517423 Binary files /dev/null and b/main/squaremap/web/images/foliage.png differ diff --git a/main/squaremap/web/images/grass.png b/main/squaremap/web/images/grass.png new file mode 100644 index 0000000..f59dd38 Binary files /dev/null and b/main/squaremap/web/images/grass.png differ diff --git a/main/squaremap/web/images/health/0.png b/main/squaremap/web/images/health/0.png new file mode 100644 index 0000000..c4c61d1 Binary files /dev/null and b/main/squaremap/web/images/health/0.png differ diff --git a/main/squaremap/web/images/health/1.png b/main/squaremap/web/images/health/1.png new file mode 100644 index 0000000..5059581 Binary files /dev/null and b/main/squaremap/web/images/health/1.png differ diff --git a/main/squaremap/web/images/health/10.png b/main/squaremap/web/images/health/10.png new file mode 100644 index 0000000..fae8bb5 Binary files /dev/null and b/main/squaremap/web/images/health/10.png differ diff --git a/main/squaremap/web/images/health/11.png b/main/squaremap/web/images/health/11.png new file mode 100644 index 0000000..5ed932f Binary files /dev/null and b/main/squaremap/web/images/health/11.png differ diff --git a/main/squaremap/web/images/health/12.png b/main/squaremap/web/images/health/12.png new file mode 100644 index 0000000..f566a95 Binary files /dev/null and b/main/squaremap/web/images/health/12.png differ diff --git a/main/squaremap/web/images/health/13.png b/main/squaremap/web/images/health/13.png new file mode 100644 index 0000000..9fd5c2f Binary files /dev/null and b/main/squaremap/web/images/health/13.png differ diff --git a/main/squaremap/web/images/health/14.png b/main/squaremap/web/images/health/14.png new file mode 100644 index 0000000..b524ad3 Binary files /dev/null and b/main/squaremap/web/images/health/14.png differ diff --git a/main/squaremap/web/images/health/15.png b/main/squaremap/web/images/health/15.png new file mode 100644 index 0000000..b979d24 Binary files /dev/null and b/main/squaremap/web/images/health/15.png differ diff --git a/main/squaremap/web/images/health/16.png b/main/squaremap/web/images/health/16.png new file mode 100644 index 0000000..d03a7a6 Binary files /dev/null and b/main/squaremap/web/images/health/16.png differ diff --git a/main/squaremap/web/images/health/17.png b/main/squaremap/web/images/health/17.png new file mode 100644 index 0000000..6d8f392 Binary files /dev/null and b/main/squaremap/web/images/health/17.png differ diff --git a/main/squaremap/web/images/health/18.png b/main/squaremap/web/images/health/18.png new file mode 100644 index 0000000..565d5ea Binary files /dev/null and b/main/squaremap/web/images/health/18.png differ diff --git a/main/squaremap/web/images/health/19.png b/main/squaremap/web/images/health/19.png new file mode 100644 index 0000000..2fc469d Binary files /dev/null and b/main/squaremap/web/images/health/19.png differ diff --git a/main/squaremap/web/images/health/2.png b/main/squaremap/web/images/health/2.png new file mode 100644 index 0000000..0f29c14 Binary files /dev/null and b/main/squaremap/web/images/health/2.png differ diff --git a/main/squaremap/web/images/health/20.png b/main/squaremap/web/images/health/20.png new file mode 100644 index 0000000..cd2218c Binary files /dev/null and b/main/squaremap/web/images/health/20.png differ diff --git a/main/squaremap/web/images/health/3.png b/main/squaremap/web/images/health/3.png new file mode 100644 index 0000000..25dcc42 Binary files /dev/null and b/main/squaremap/web/images/health/3.png differ diff --git a/main/squaremap/web/images/health/4.png b/main/squaremap/web/images/health/4.png new file mode 100644 index 0000000..917c99f Binary files /dev/null and b/main/squaremap/web/images/health/4.png differ diff --git a/main/squaremap/web/images/health/5.png b/main/squaremap/web/images/health/5.png new file mode 100644 index 0000000..a5651a0 Binary files /dev/null and b/main/squaremap/web/images/health/5.png differ diff --git a/main/squaremap/web/images/health/6.png b/main/squaremap/web/images/health/6.png new file mode 100644 index 0000000..389f6fe Binary files /dev/null and b/main/squaremap/web/images/health/6.png differ diff --git a/main/squaremap/web/images/health/7.png b/main/squaremap/web/images/health/7.png new file mode 100644 index 0000000..74e4e11 Binary files /dev/null and b/main/squaremap/web/images/health/7.png differ diff --git a/main/squaremap/web/images/health/8.png b/main/squaremap/web/images/health/8.png new file mode 100644 index 0000000..4c80f03 Binary files /dev/null and b/main/squaremap/web/images/health/8.png differ diff --git a/main/squaremap/web/images/health/9.png b/main/squaremap/web/images/health/9.png new file mode 100644 index 0000000..2e34889 Binary files /dev/null and b/main/squaremap/web/images/health/9.png differ diff --git a/main/squaremap/web/images/icon/blue-cube-smol.png b/main/squaremap/web/images/icon/blue-cube-smol.png new file mode 100644 index 0000000..d27a57c Binary files /dev/null and b/main/squaremap/web/images/icon/blue-cube-smol.png differ diff --git a/main/squaremap/web/images/icon/green-cube-smol.png b/main/squaremap/web/images/icon/green-cube-smol.png new file mode 100644 index 0000000..91843c1 Binary files /dev/null and b/main/squaremap/web/images/icon/green-cube-smol.png differ diff --git a/main/squaremap/web/images/icon/player.png b/main/squaremap/web/images/icon/player.png new file mode 100644 index 0000000..e85d77d Binary files /dev/null and b/main/squaremap/web/images/icon/player.png differ diff --git a/main/squaremap/web/images/icon/purple-cube-smol.png b/main/squaremap/web/images/icon/purple-cube-smol.png new file mode 100644 index 0000000..e65a598 Binary files /dev/null and b/main/squaremap/web/images/icon/purple-cube-smol.png differ diff --git a/main/squaremap/web/images/icon/red-cube-smol.png b/main/squaremap/web/images/icon/red-cube-smol.png new file mode 100644 index 0000000..7b924d7 Binary files /dev/null and b/main/squaremap/web/images/icon/red-cube-smol.png differ diff --git a/main/squaremap/web/images/icon/registered/squaremap-spawn_icon.png b/main/squaremap/web/images/icon/registered/squaremap-spawn_icon.png new file mode 100644 index 0000000..5d6fe4a Binary files /dev/null and b/main/squaremap/web/images/icon/registered/squaremap-spawn_icon.png differ diff --git a/main/squaremap/web/images/icon/spawn.png b/main/squaremap/web/images/icon/spawn.png new file mode 100644 index 0000000..b90a578 Binary files /dev/null and b/main/squaremap/web/images/icon/spawn.png differ diff --git a/main/squaremap/web/images/link.png b/main/squaremap/web/images/link.png new file mode 100644 index 0000000..6b9ea56 Binary files /dev/null and b/main/squaremap/web/images/link.png differ diff --git a/main/squaremap/web/images/nether_sky.png b/main/squaremap/web/images/nether_sky.png new file mode 100644 index 0000000..7649404 Binary files /dev/null and b/main/squaremap/web/images/nether_sky.png differ diff --git a/main/squaremap/web/images/og.png b/main/squaremap/web/images/og.png new file mode 100644 index 0000000..2f6cedf Binary files /dev/null and b/main/squaremap/web/images/og.png differ diff --git a/main/squaremap/web/images/overworld_sky.png b/main/squaremap/web/images/overworld_sky.png new file mode 100644 index 0000000..76008f6 Binary files /dev/null and b/main/squaremap/web/images/overworld_sky.png differ diff --git a/main/squaremap/web/images/pinned.png b/main/squaremap/web/images/pinned.png new file mode 100644 index 0000000..4798c16 Binary files /dev/null and b/main/squaremap/web/images/pinned.png differ diff --git a/main/squaremap/web/images/unpinned.png b/main/squaremap/web/images/unpinned.png new file mode 100644 index 0000000..2e60cb8 Binary files /dev/null and b/main/squaremap/web/images/unpinned.png differ diff --git a/main/squaremap/web/index.html b/main/squaremap/web/index.html new file mode 100644 index 0000000..9668669 --- /dev/null +++ b/main/squaremap/web/index.html @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + LiveAtlas + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + +
+ + + + + diff --git a/main/squaremap/web/js/addons/Ellipse.js b/main/squaremap/web/js/addons/Ellipse.js new file mode 100644 index 0000000..4e9b21e --- /dev/null +++ b/main/squaremap/web/js/addons/Ellipse.js @@ -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); +}; diff --git a/main/squaremap/web/js/addons/RotateMarker.js b/main/squaremap/web/js/addons/RotateMarker.js new file mode 100644 index 0000000..4592358 --- /dev/null +++ b/main/squaremap/web/js/addons/RotateMarker.js @@ -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; + } + }); +})(); diff --git a/main/squaremap/web/js/modules/LayerControl.js b/main/squaremap/web/js/modules/LayerControl.js new file mode 100644 index 0000000..fc8c66e --- /dev/null +++ b/main/squaremap/web/js/modules/LayerControl.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/PlayerList.js b/main/squaremap/web/js/modules/PlayerList.js new file mode 100644 index 0000000..1d33db4 --- /dev/null +++ b/main/squaremap/web/js/modules/PlayerList.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/Sidebar.js b/main/squaremap/web/js/modules/Sidebar.js new file mode 100644 index 0000000..6eea37b --- /dev/null +++ b/main/squaremap/web/js/modules/Sidebar.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/Squaremap.js b/main/squaremap/web/js/modules/Squaremap.js new file mode 100644 index 0000000..3e48cc7 --- /dev/null +++ b/main/squaremap/web/js/modules/Squaremap.js @@ -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; +}; diff --git a/main/squaremap/web/js/modules/SquaremapTileLayer.js b/main/squaremap/web/js/modules/SquaremapTileLayer.js new file mode 100644 index 0000000..d2877fe --- /dev/null +++ b/main/squaremap/web/js/modules/SquaremapTileLayer.js @@ -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 `` 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; + } +}); diff --git a/main/squaremap/web/js/modules/UICoordinates.js b/main/squaremap/web/js/modules/UICoordinates.js new file mode 100644 index 0000000..fe88d4a --- /dev/null +++ b/main/squaremap/web/js/modules/UICoordinates.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/UILink.js b/main/squaremap/web/js/modules/UILink.js new file mode 100644 index 0000000..ef13d12 --- /dev/null +++ b/main/squaremap/web/js/modules/UILink.js @@ -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 = ``; + } + }); + 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 }; diff --git a/main/squaremap/web/js/modules/WorldList.js b/main/squaremap/web/js/modules/WorldList.js new file mode 100644 index 0000000..ba19424 --- /dev/null +++ b/main/squaremap/web/js/modules/WorldList.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/util/Fieldset.js b/main/squaremap/web/js/modules/util/Fieldset.js new file mode 100644 index 0000000..6adbd31 --- /dev/null +++ b/main/squaremap/web/js/modules/util/Fieldset.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/util/Markers.js b/main/squaremap/web/js/modules/util/Markers.js new file mode 100644 index 0000000..6c15df9 --- /dev/null +++ b/main/squaremap/web/js/modules/util/Markers.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/util/Pin.js b/main/squaremap/web/js/modules/util/Pin.js new file mode 100644 index 0000000..c5de4e6 --- /dev/null +++ b/main/squaremap/web/js/modules/util/Pin.js @@ -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 }; diff --git a/main/squaremap/web/js/modules/util/Player.js b/main/squaremap/web/js/modules/util/Player.js new file mode 100644 index 0000000..2656781 --- /dev/null +++ b/main/squaremap/web/js/modules/util/Player.js @@ -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 = ``; + } + if (P.worldList.curWorld.player_tracker.nameplates.show_armor && player.armor != null) { + armorImg = ``; + } + if (P.worldList.curWorld.player_tracker.nameplates.show_health && player.health != null) { + healthImg = ``; + } + this.tooltip.setContent(`