Changed: Squaremap configs support LiveAtlas & Squaremap Markers
This commit is contained in:
243
main/squaremap/web/js/addons/Ellipse.js
Normal file
243
main/squaremap/web/js/addons/Ellipse.js
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Copyright 2014 JD Fergason
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// https://github.com/jdfergason/Leaflet.Ellipse
|
||||
|
||||
L.SVG.include ({
|
||||
_updateEllipse: function (layer) {
|
||||
var rx = layer._radiusX,
|
||||
ry = layer._radiusY,
|
||||
phi = layer._tiltDeg,
|
||||
endPoint = layer._endPointParams;
|
||||
|
||||
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
|
||||
'A' + rx + ',' + ry + ',' + phi + ',' +
|
||||
endPoint.largeArc + ',' + endPoint.sweep + ',' +
|
||||
endPoint.x1 + ',' + endPoint.y1 + ' z';
|
||||
this._setPath(layer, d);
|
||||
}
|
||||
});
|
||||
|
||||
L.Canvas.include ({
|
||||
_updateEllipse: function (layer) {
|
||||
if (layer._empty()) { return; }
|
||||
|
||||
var p = layer._point,
|
||||
ctx = this._ctx,
|
||||
r = layer._radiusX,
|
||||
s = (layer._radiusY || r) / r;
|
||||
|
||||
// this breaks "preferCanvas: true"
|
||||
//this._drawnLayers[layer._leaflet_id] = layer;
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.translate(p.x, p.y);
|
||||
if (layer._tilt !== 0) {
|
||||
ctx.rotate( layer._tilt );
|
||||
}
|
||||
if (s !== 1) {
|
||||
ctx.scale(1, s);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, r, 0, Math.PI * 2);
|
||||
ctx.restore();
|
||||
|
||||
this._fillStroke(ctx, layer);
|
||||
},
|
||||
});
|
||||
|
||||
L.Ellipse = L.Path.extend({
|
||||
|
||||
options: {
|
||||
fill: true,
|
||||
startAngle: 0,
|
||||
endAngle: 359.9
|
||||
},
|
||||
|
||||
initialize: function (latlng, radii, tilt, options) {
|
||||
|
||||
L.setOptions(this, options);
|
||||
this._latlng = L.latLng(latlng);
|
||||
|
||||
if (tilt) {
|
||||
this._tiltDeg = tilt;
|
||||
} else {
|
||||
this._tiltDeg = 0;
|
||||
}
|
||||
|
||||
if (radii) {
|
||||
this._mRadiusX = radii[0];
|
||||
this._mRadiusY = radii[1];
|
||||
}
|
||||
},
|
||||
|
||||
setRadius: function (radii) {
|
||||
this._mRadiusX = radii[0];
|
||||
this._mRadiusY = radii[1];
|
||||
return this.redraw();
|
||||
},
|
||||
|
||||
getRadius: function () {
|
||||
return new L.point(this._mRadiusX, this._mRadiusY);
|
||||
},
|
||||
|
||||
setTilt: function (tilt) {
|
||||
this._tiltDeg = tilt;
|
||||
return this.redraw();
|
||||
},
|
||||
|
||||
getBounds: function () {
|
||||
// TODO respect tilt (bounds are too big)
|
||||
var lngRadius = this._getLngRadius(),
|
||||
latRadius = this._getLatRadius(),
|
||||
latlng = this._latlng;
|
||||
|
||||
return new L.LatLngBounds(
|
||||
[latlng.lat - latRadius, latlng.lng - lngRadius],
|
||||
[latlng.lat + latRadius, latlng.lng + lngRadius]);
|
||||
},
|
||||
|
||||
// @method setLatLng(latLng: LatLng): this
|
||||
// Sets the position of a circle marker to a new location.
|
||||
setLatLng: function (latlng) {
|
||||
this._latlng = L.latLng(latlng);
|
||||
this.redraw();
|
||||
return this.fire('move', {latlng: this._latlng});
|
||||
},
|
||||
|
||||
// @method getLatLng(): LatLng
|
||||
// Returns the current geographical position of the circle marker
|
||||
getLatLng: function () {
|
||||
return this._latlng;
|
||||
},
|
||||
|
||||
setStyle: L.Path.prototype.setStyle,
|
||||
|
||||
_project: function () {
|
||||
var lngRadius = this._getLngRadius(),
|
||||
latRadius = this._getLatRadius(),
|
||||
latlng = this._latlng,
|
||||
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
|
||||
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
|
||||
|
||||
this._point = this._map.latLngToLayerPoint(latlng);
|
||||
this._radiusX = Math.max(this._point.x - pointLeft.x, 1) * this._map.options.scale;
|
||||
this._radiusY = Math.max(pointBelow.y - this._point.y, 1) * this._map.options.scale;
|
||||
this._tilt = Math.PI * this._tiltDeg / 180;
|
||||
this._endPointParams = this._centerPointToEndPoint();
|
||||
this._updateBounds();
|
||||
},
|
||||
|
||||
_updateBounds: function () {
|
||||
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
|
||||
var sin = Math.sin(this._tilt);
|
||||
var cos = Math.cos(this._tilt);
|
||||
var sinSquare = sin * sin;
|
||||
var cosSquare = cos * cos;
|
||||
var aSquare = this._radiusX * this._radiusX;
|
||||
var bSquare = this._radiusY * this._radiusY;
|
||||
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
|
||||
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
|
||||
var w = this._clickTolerance();
|
||||
var p = [halfWidth + w, halfHeight + w];
|
||||
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
if (this._map) {
|
||||
this._updatePath();
|
||||
}
|
||||
},
|
||||
|
||||
_updatePath: function () {
|
||||
this._renderer._updateEllipse(this);
|
||||
},
|
||||
|
||||
_getLatRadius: function () {
|
||||
var simpleCrs = !!this._map.options.crs.infinite;
|
||||
if(simpleCrs)
|
||||
return this._mRadiusY;
|
||||
else
|
||||
return (this._mRadiusY / 40075017) * 360;
|
||||
},
|
||||
|
||||
_getLngRadius: function () {
|
||||
var simpleCrs = !!this._map.options.crs.infinite;
|
||||
if(simpleCrs)
|
||||
return this._mRadiusX;
|
||||
else
|
||||
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
|
||||
},
|
||||
|
||||
_centerPointToEndPoint: function () {
|
||||
// Convert between center point parameterization of an ellipse
|
||||
// too SVG's end-point and sweep parameters. This is an
|
||||
// adaptation of the perl code found here:
|
||||
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
|
||||
var c = this._point,
|
||||
rx = this._radiusX,
|
||||
ry = this._radiusY,
|
||||
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
|
||||
theta1 = this.options.startAngle * (Math.PI / 180),
|
||||
delta = this.options.endAngle,
|
||||
phi = this._tiltDeg * (Math.PI / 180);
|
||||
|
||||
// Determine start and end-point coordinates
|
||||
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
|
||||
Math.sin(-phi) * ry * Math.sin(theta1);
|
||||
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
|
||||
Math.cos(phi) * ry * Math.sin(theta1);
|
||||
|
||||
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
|
||||
Math.sin(-phi) * ry * Math.sin(theta2);
|
||||
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
|
||||
Math.cos(phi) * ry * Math.sin(theta2);
|
||||
|
||||
var largeArc = (delta > 180) ? 1 : 0;
|
||||
var sweep = (delta > 0) ? 1 : 0;
|
||||
|
||||
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
|
||||
'sweep': sweep, 'x1': x1, 'y1': y1};
|
||||
},
|
||||
|
||||
_empty: function () {
|
||||
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
|
||||
},
|
||||
|
||||
_containsPoint : function (p) {
|
||||
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
|
||||
var sin = Math.sin(this._tilt);
|
||||
var cos = Math.cos(this._tilt);
|
||||
var dx = p.x - this._point.x;
|
||||
var dy = p.y - this._point.y;
|
||||
var sumA = cos * dx + sin * dy;
|
||||
var sumB = sin * dx - cos * dy;
|
||||
if (this.options.fill === false) {
|
||||
var x = this._radiusX - this.options.weight;
|
||||
var y = this._radiusY - this.options.weight;
|
||||
if (sumA * sumA / (x * x) + sumB * sumB / (y * y) <= 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
|
||||
}
|
||||
});
|
||||
|
||||
L.ellipse = function (latlng, radii, tilt, options) {
|
||||
return new L.Ellipse(latlng, radii, tilt, options);
|
||||
};
|
82
main/squaremap/web/js/addons/RotateMarker.js
Normal file
82
main/squaremap/web/js/addons/RotateMarker.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 Benjamin Becquet
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* https://github.com/bbecquet/Leaflet.RotatedMarker
|
||||
*/
|
||||
(function() {
|
||||
// save these original methods before they are overwritten
|
||||
var proto_initIcon = L.Marker.prototype._initIcon;
|
||||
var proto_setPos = L.Marker.prototype._setPos;
|
||||
|
||||
var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
|
||||
|
||||
L.Marker.addInitHook(function () {
|
||||
var iconOptions = this.options.icon && this.options.icon.options;
|
||||
var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
|
||||
if (iconAnchor) {
|
||||
iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
|
||||
}
|
||||
this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom' ;
|
||||
this.options.rotationAngle = this.options.rotationAngle || 0;
|
||||
|
||||
// Ensure marker keeps rotated during dragging
|
||||
this.on('drag', function(e) { e.target._applyRotation(); });
|
||||
});
|
||||
|
||||
L.Marker.include({
|
||||
_initIcon: function() {
|
||||
proto_initIcon.call(this);
|
||||
},
|
||||
|
||||
_setPos: function (pos) {
|
||||
proto_setPos.call(this, pos);
|
||||
this._applyRotation();
|
||||
},
|
||||
|
||||
_applyRotation: function () {
|
||||
if(this.options.rotationAngle) {
|
||||
this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin;
|
||||
|
||||
if(oldIE) {
|
||||
// for IE 9, use the 2D rotation
|
||||
this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
|
||||
} else {
|
||||
// for modern browsers, prefer the 3D accelerated version
|
||||
this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setRotationAngle: function(angle) {
|
||||
this.options.rotationAngle = angle;
|
||||
this.update();
|
||||
return this;
|
||||
},
|
||||
|
||||
setRotationOrigin: function(origin) {
|
||||
this.options.rotationOrigin = origin;
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
})();
|
106
main/squaremap/web/js/modules/LayerControl.js
Normal file
106
main/squaremap/web/js/modules/LayerControl.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import { P } from './Squaremap.js';
|
||||
import { SquaremapTileLayer } from './SquaremapTileLayer.js';
|
||||
|
||||
class LayerControl {
|
||||
constructor() {
|
||||
this.layers = new Map();
|
||||
}
|
||||
init() {
|
||||
this.currentLayer = 0;
|
||||
this.updateInterval = 60;
|
||||
|
||||
this.playersLayer = new L.LayerGroup();
|
||||
this.playersLayer.id = "players_layer";
|
||||
|
||||
this.controls = L.control.layers({}, {}, {
|
||||
position: 'topleft',
|
||||
sortLayers: true,
|
||||
sortFunction: (a, b) => {
|
||||
return a.order - b.order;
|
||||
}
|
||||
})
|
||||
.addTo(P.map);
|
||||
}
|
||||
addOverlay(name, layer, hide) {
|
||||
this.controls.addOverlay(layer, name);
|
||||
if (this.shouldHide(layer, hide) !== true) {
|
||||
layer.addTo(P.map);
|
||||
}
|
||||
}
|
||||
removeOverlay(layer) {
|
||||
this.ignoreLayer = layer;
|
||||
this.controls.removeLayer(layer);
|
||||
layer.remove();
|
||||
this.ignoreLayer = null;
|
||||
}
|
||||
shouldHide(layer, def) {
|
||||
const value = window.localStorage.getItem(`hide_${layer.id}`);
|
||||
return value == null ? def : value === 'true';
|
||||
}
|
||||
hideLayer(layer) {
|
||||
if (layer != this.ignoreLayer) {
|
||||
window.localStorage.setItem(`hide_${layer.id}`, 'true');
|
||||
}
|
||||
}
|
||||
showLayer(layer) {
|
||||
if (layer != this.ignoreLayer) {
|
||||
window.localStorage.setItem(`hide_${layer.id}`, 'false');
|
||||
}
|
||||
}
|
||||
setupTileLayers(world) {
|
||||
// setup the map tile layers
|
||||
// we need 2 layers to swap between for seamless refreshing
|
||||
if (this.tileLayer1 != null) {
|
||||
P.map.removeLayer(this.tileLayer1);
|
||||
}
|
||||
if (this.tileLayer2 != null) {
|
||||
P.map.removeLayer(this.tileLayer2);
|
||||
}
|
||||
this.tileLayer1 = this.createTileLayer(world);
|
||||
this.tileLayer2 = this.createTileLayer(world);
|
||||
|
||||
// refresh player's control
|
||||
this.removeOverlay(this.playersLayer);
|
||||
if (world.player_tracker.show_controls) {
|
||||
this.addOverlay(world.player_tracker.label,
|
||||
this.playersLayer,
|
||||
world.player_tracker.default_hidden);
|
||||
}
|
||||
this.playersLayer.order = world.player_tracker.priority;
|
||||
this.playersLayer.setZIndex(world.player_tracker.z_index);
|
||||
}
|
||||
createTileLayer(world) {
|
||||
return new SquaremapTileLayer(`tiles/${world.name}/{z}/{x}_{y}.png`, {
|
||||
tileSize: 512,
|
||||
minNativeZoom: 0,
|
||||
maxNativeZoom: world.zoom.max,
|
||||
errorTileUrl: 'images/clear.png'
|
||||
}).addTo(P.map)
|
||||
.addEventListener("load", () => {
|
||||
// when all tiles are loaded, switch to this layer
|
||||
this.switchTileLayer();
|
||||
});
|
||||
}
|
||||
updateTileLayer() {
|
||||
// redraw background tile layer
|
||||
if (this.currentLayer == 1) {
|
||||
this.tileLayer2.redraw();
|
||||
} else {
|
||||
this.tileLayer1.redraw();
|
||||
}
|
||||
}
|
||||
switchTileLayer() {
|
||||
// swap current tile layer
|
||||
if (this.currentLayer == 1) {
|
||||
this.tileLayer1.setZIndex(0);
|
||||
this.tileLayer2.setZIndex(1);
|
||||
this.currentLayer = 2;
|
||||
} else {
|
||||
this.tileLayer1.setZIndex(1);
|
||||
this.tileLayer2.setZIndex(0);
|
||||
this.currentLayer = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { LayerControl };
|
161
main/squaremap/web/js/modules/PlayerList.js
Normal file
161
main/squaremap/web/js/modules/PlayerList.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import { Player } from "./util/Player.js";
|
||||
import { P } from './Squaremap.js';
|
||||
|
||||
class PlayerList {
|
||||
constructor(json) {
|
||||
this.players = new Map();
|
||||
this.markers = new Map();
|
||||
this.following = null;
|
||||
this.firstTick = true;
|
||||
this.label = json.player_list_label;
|
||||
P.map.createPane("nameplate").style.zIndex = 1000;
|
||||
}
|
||||
tick() {
|
||||
if (P.tick_count % P.worldList.curWorld.player_tracker.update_interval == 0) {
|
||||
P.getJSON("tiles/players.json", (json) => {
|
||||
this.updatePlayerList(json.players);
|
||||
const title = `${this.label}`
|
||||
.replace(/{cur}/g, json.players.length)
|
||||
.replace(/{max}/g, json.max == null ? "???" : json.max)
|
||||
if (P.sidebar.players.legend.innerHTML !== title) {
|
||||
P.sidebar.players.legend.innerHTML = title;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
showPlayer(uuid) {
|
||||
const player = this.players.get(uuid);
|
||||
if (!P.worldList.worlds.has(player.world)) {
|
||||
return false;
|
||||
}
|
||||
P.worldList.showWorld(player.world, () => {
|
||||
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
addToList(player) {
|
||||
const head = document.createElement("img");
|
||||
head.src = player.getHeadUrl();
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.innerHTML = player.displayName
|
||||
|
||||
const link = P.createElement("a", player.uuid, this);
|
||||
link.onclick = function (e) {
|
||||
if (this.parent.showPlayer(this.id)) {
|
||||
this.parent.followPlayerMarker(this.id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
link.appendChild(head);
|
||||
link.appendChild(span);
|
||||
const fieldset = P.sidebar.players.element;
|
||||
fieldset.appendChild(link);
|
||||
Array.from(fieldset.getElementsByTagName("a"))
|
||||
.sort((a, b) => {
|
||||
return plain(a.getElementsByTagName("span")[0])
|
||||
.localeCompare(plain(b.getElementsByTagName("span")[0]));
|
||||
})
|
||||
.forEach(link => fieldset.appendChild(link));
|
||||
}
|
||||
removeFromList(player) {
|
||||
const link = document.getElementById(player.uuid);
|
||||
if (link != null) {
|
||||
link.remove();
|
||||
}
|
||||
this.players.delete(player.uuid);
|
||||
player.removeMarker();
|
||||
}
|
||||
updatePlayerList(players) {
|
||||
const playersToRemove = Array.from(this.players.keys());
|
||||
|
||||
let needsSort = false;
|
||||
|
||||
// update players from json
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
let player = this.players.get(players[i].uuid);
|
||||
if (player == null) {
|
||||
// new player
|
||||
player = new Player(players[i]);
|
||||
this.players.set(player.uuid, player);
|
||||
this.addToList(player);
|
||||
} else {
|
||||
const oldDisplayName = player.displayName;
|
||||
player.update(players[i]);
|
||||
if (oldDisplayName !== player.displayName) {
|
||||
needsSort = true;
|
||||
document.getElementById(player.uuid)
|
||||
.getElementsByTagName("span")[0]
|
||||
.innerHTML = player.displayName;
|
||||
}
|
||||
}
|
||||
playersToRemove.remove(players[i].uuid);
|
||||
}
|
||||
|
||||
// remove players not in json
|
||||
for (let i = 0; i < playersToRemove.length; i++) {
|
||||
const player = this.players.get(playersToRemove[i]);
|
||||
this.removeFromList(player);
|
||||
}
|
||||
|
||||
if (needsSort) {
|
||||
const fieldset = P.sidebar.players.element;
|
||||
Array.from(fieldset.getElementsByTagName("a"))
|
||||
.sort((a, b) => {
|
||||
return plain(a.getElementsByTagName("span")[0])
|
||||
.localeCompare(plain(b.getElementsByTagName("span")[0]));
|
||||
})
|
||||
.forEach(link => fieldset.appendChild(link));
|
||||
}
|
||||
|
||||
// first tick only
|
||||
if (this.firstTick) {
|
||||
this.firstTick = false;
|
||||
|
||||
// follow uuid from url
|
||||
const follow = P.getUrlParam("uuid", null);
|
||||
if (follow != null && this.players.get(follow) != null) {
|
||||
this.followPlayerMarker(follow);
|
||||
}
|
||||
}
|
||||
|
||||
// follow highlighted player
|
||||
if (this.following != null) {
|
||||
const player = this.players.get(this.following);
|
||||
if (player != null && P.worldList.curWorld != null) {
|
||||
if (player.world !== P.worldList.curWorld.name) {
|
||||
P.worldList.showWorld(player.world, () => {
|
||||
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||
});
|
||||
} else {
|
||||
P.map.panTo(P.toLatLng(player.x, player.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearPlayerMarkers() {
|
||||
const playersToRemove = Array.from(this.players.keys());
|
||||
for (let i = 0; i < playersToRemove.length; i++) {
|
||||
const player = this.players.get(playersToRemove[i]);
|
||||
player.removeMarker();
|
||||
}
|
||||
this.markers.clear();
|
||||
//P.layerControl.playersLayer.clearLayers();
|
||||
}
|
||||
followPlayerMarker(uuid) {
|
||||
if (this.following != null) {
|
||||
document.getElementById(this.following).classList.remove("following");
|
||||
this.following = null;
|
||||
}
|
||||
if (uuid != null) {
|
||||
this.following = uuid;
|
||||
document.getElementById(this.following).classList.add("following");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function plain(element) {
|
||||
return element.textContent || element.innerText || "";
|
||||
}
|
||||
|
||||
export { PlayerList };
|
47
main/squaremap/web/js/modules/Sidebar.js
Normal file
47
main/squaremap/web/js/modules/Sidebar.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Pin } from "./util/Pin.js";
|
||||
import { Fieldset } from "./util/Fieldset.js";
|
||||
import { P } from './Squaremap.js';
|
||||
|
||||
class Sidebar {
|
||||
constructor(json, show) {
|
||||
this.sidebar = P.createElement("div", "sidebar", this);
|
||||
this.showSidebar = show;
|
||||
if (!show) {
|
||||
this.sidebar.style.display = "none";
|
||||
}
|
||||
this.sidebar.addEventListener("click", (e) => {
|
||||
P.playerList.followPlayerMarker(null);
|
||||
});
|
||||
document.body.appendChild(this.sidebar);
|
||||
|
||||
this.pin = new Pin(json.pinned == "pinned");
|
||||
this.show(this.pin.pinned);
|
||||
if (json.pinned != "hide") {
|
||||
this.sidebar.appendChild(this.pin.element);
|
||||
}
|
||||
|
||||
this.worlds = new Fieldset("worlds", json.world_list_label);
|
||||
this.sidebar.appendChild(this.worlds.element);
|
||||
|
||||
this.players = new Fieldset("players", json.player_list_label
|
||||
.replace(/{cur}/g, 0)
|
||||
.replace(/{max}/g, 0));
|
||||
this.sidebar.appendChild(this.players.element);
|
||||
|
||||
this.sidebar.onmouseleave = () => {
|
||||
if (!this.pin.pinned) {
|
||||
this.show(false);
|
||||
}
|
||||
};
|
||||
this.sidebar.onmouseenter = () => {
|
||||
if (!this.pin.pinned) {
|
||||
this.show(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
show(show) {
|
||||
this.sidebar.className = show ? "show" : "";
|
||||
}
|
||||
}
|
||||
|
||||
export { Sidebar };
|
171
main/squaremap/web/js/modules/Squaremap.js
Normal file
171
main/squaremap/web/js/modules/Squaremap.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Sidebar } from "./Sidebar.js";
|
||||
import { PlayerList } from "./PlayerList.js";
|
||||
import { WorldList } from "./WorldList.js";
|
||||
import { UICoordinates } from "./UICoordinates.js";
|
||||
import { UILink } from "./UILink.js";
|
||||
import { LayerControl } from "./LayerControl.js";
|
||||
|
||||
class SquaremapMap {
|
||||
constructor() {
|
||||
this.map = L.map("map", {
|
||||
crs: L.CRS.Simple,
|
||||
center: [0, 0],
|
||||
attributionControl: false,
|
||||
preferCanvas: true,
|
||||
noWrap: true
|
||||
})
|
||||
.on('overlayadd', (e) => {
|
||||
this.layerControl.showLayer(e.layer);
|
||||
})
|
||||
.on('overlayremove', (e) => {
|
||||
this.layerControl.hideLayer(e.layer);
|
||||
})
|
||||
.on('click', (e) => {
|
||||
this.playerList.followPlayerMarker(null);
|
||||
})
|
||||
.on('dblclick', (e) => {
|
||||
this.playerList.followPlayerMarker(null);
|
||||
});
|
||||
|
||||
this.tick_count = 1;
|
||||
|
||||
this.layerControl = new LayerControl();
|
||||
|
||||
this.init();
|
||||
}
|
||||
loop() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
this.tick();
|
||||
this.tick_count++;
|
||||
}
|
||||
setTimeout(() => this.loop(), 1000);
|
||||
}
|
||||
tick() {
|
||||
// tick player tracker
|
||||
this.playerList.tick();
|
||||
// tick world
|
||||
this.worldList.curWorld.tick();
|
||||
}
|
||||
init() {
|
||||
this.getJSON("tiles/settings.json", (json) => {
|
||||
this.layerControl.init();
|
||||
|
||||
this.title = json.ui.title;
|
||||
this.sidebar = new Sidebar(json.ui.sidebar, this.getUrlParam("show_sidebar", "true") === "true");
|
||||
this.playerList = new PlayerList(json.ui.sidebar);
|
||||
this.worldList = new WorldList(json.worlds);
|
||||
this.coordinates = new UICoordinates(json.ui.coordinates, this.getUrlParam("show_coordinates", "true") === "true");
|
||||
this.uiLink = new UILink(json.ui.link, this.getUrlParam("show_link_button", "true") === "true");
|
||||
|
||||
this.showControls = this.getUrlParam("show_controls", "true") === "true"
|
||||
if (!this.showControls) {
|
||||
let controlLayers = document.getElementsByClassName('leaflet-top leaflet-left');
|
||||
controlLayers[0].style.display = "none";
|
||||
}
|
||||
|
||||
this.worldList.loadInitialWorld(json, (world) => {
|
||||
this.loop();
|
||||
this.centerOn(
|
||||
this.getUrlParam("x", world.spawn.x),
|
||||
this.getUrlParam("z", world.spawn.z),
|
||||
this.getUrlParam("zoom", world.zoom.def));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
centerOn(x, z, zoom) {
|
||||
this.map.setView(this.toLatLng(x, z), zoom);
|
||||
this.uiLink.update();
|
||||
return this.map;
|
||||
}
|
||||
toLatLng(x, z) {
|
||||
return L.latLng(this.pixelsToMeters(-z), this.pixelsToMeters(x));
|
||||
//return this.map.unproject([x, z], this.worldList.curWorld.zoom.max);
|
||||
}
|
||||
toPoint(latlng) {
|
||||
return L.point(this.metersToPixels(latlng.lng), this.metersToPixels(-latlng.lat));
|
||||
//return this.map.project(latlng, this.worldList.curWorld.zoom.max);
|
||||
}
|
||||
pixelsToMeters(num) {
|
||||
return num * this.scale;
|
||||
}
|
||||
metersToPixels(num) {
|
||||
return num / this.scale;
|
||||
}
|
||||
setScale(zoom) {
|
||||
this.scale = (1 / Math.pow(2, zoom));
|
||||
// store this on map for ellipse
|
||||
this.map.options.scale = this.scale;
|
||||
}
|
||||
createElement(tag, id, parent) {
|
||||
const element = document.createElement(tag);
|
||||
element.id = id;
|
||||
element.parent = parent;
|
||||
return element;
|
||||
}
|
||||
createTextElement(tag, text) {
|
||||
const element = document.createElement(tag);
|
||||
element.appendChild(document.createTextNode(text));
|
||||
return element;
|
||||
}
|
||||
getJSON(url, fn) {
|
||||
fetch(url, {cache: "no-store"})
|
||||
.then(async res => {
|
||||
if (res.ok) {
|
||||
fn(await res.json());
|
||||
}
|
||||
});
|
||||
}
|
||||
getUrlParam(query, def) {
|
||||
const url = window.location.search.substring(1);
|
||||
const vars = url.split('&');
|
||||
for (let i = 0; i < vars.length; i++) {
|
||||
const param = vars[i].split('=');
|
||||
if (param[0] === query) {
|
||||
const value = param[1] === undefined ? '' : decodeURIComponent(param[1]);
|
||||
return value === '' ? def : value;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
getUrlFromView() {
|
||||
const center = this.toPoint(this.map.getCenter());
|
||||
const zoom = this.map.getZoom();
|
||||
const x = Math.floor(center.x);
|
||||
const z = Math.floor(center.y);
|
||||
const following = this.playerList.following ? `&uuid=${this.playerList.following}` : '';
|
||||
let link = `?world=${this.worldList.curWorld.name}&zoom=${zoom}&x=${x}&z=${z}${following}`;
|
||||
if (!this.showControls) {
|
||||
link += "&show_controls=false"
|
||||
}
|
||||
if (!this.uiLink.showLinkButton) {
|
||||
link += "&show_link_button=false"
|
||||
}
|
||||
if (!this.coordinates.showCoordinates) {
|
||||
link += "&show_coordinates=false"
|
||||
}
|
||||
if (!this.sidebar.showSidebar) {
|
||||
link += "&show_sidebar=false"
|
||||
}
|
||||
return link
|
||||
}
|
||||
updateBrowserUrl(url) {
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const P = new SquaremapMap();
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/3955096
|
||||
Array.prototype.remove = function() {
|
||||
var what, a = arguments, L = a.length, ax;
|
||||
while (L && this.length) {
|
||||
what = a[--L];
|
||||
while ((ax = this.indexOf(what)) !== -1) {
|
||||
this.splice(ax, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
43
main/squaremap/web/js/modules/SquaremapTileLayer.js
Normal file
43
main/squaremap/web/js/modules/SquaremapTileLayer.js
Normal file
@@ -0,0 +1,43 @@
|
||||
export var SquaremapTileLayer = L.TileLayer.extend({
|
||||
|
||||
// @method createTile(coords: Object, done?: Function): HTMLElement
|
||||
// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
|
||||
// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
|
||||
// callback is called when the tile has been loaded.
|
||||
createTile: function (coords, done) {
|
||||
var tile = document.createElement('img');
|
||||
|
||||
L.DomEvent.on(tile, 'load', () => {
|
||||
//Once image has loaded revoke the object URL as we don't need it anymore
|
||||
URL.revokeObjectURL(tile.src);
|
||||
this._tileOnLoad(done, tile)
|
||||
});
|
||||
L.DomEvent.on(tile, 'error', L.Util.bind(this._tileOnError, this, done, tile));
|
||||
|
||||
if (this.options.crossOrigin || this.options.crossOrigin === '') {
|
||||
tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
|
||||
}
|
||||
|
||||
tile.alt = '';
|
||||
tile.setAttribute('role', 'presentation');
|
||||
|
||||
//Retrieve image via a fetch instead of just setting the src
|
||||
//This works around the fact that browsers usually don't make a request for an image that was previously loaded,
|
||||
//without resorting to changing the URL (which would break caching).
|
||||
fetch(this.getTileUrl(coords))
|
||||
.then(res => {
|
||||
//Call leaflet's error handler if request fails for some reason
|
||||
if (!res.ok) {
|
||||
this._tileOnError(done, tile, null);
|
||||
return;
|
||||
}
|
||||
|
||||
//Get image data and convert into object URL so it can be used as a src
|
||||
//Leaflet's onload listener will take it from here
|
||||
res.blob().then(blob => tile.src = URL.createObjectURL(blob));
|
||||
})
|
||||
.catch(() => this._tileOnError(done, tile, null));
|
||||
|
||||
return tile;
|
||||
}
|
||||
});
|
44
main/squaremap/web/js/modules/UICoordinates.js
Normal file
44
main/squaremap/web/js/modules/UICoordinates.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { P } from './Squaremap.js';
|
||||
|
||||
class UICoordinates {
|
||||
constructor(json, show) {
|
||||
const Coords = L.Control.extend({
|
||||
_container: null,
|
||||
options: {
|
||||
position: 'bottomleft'
|
||||
},
|
||||
onAdd: function () {
|
||||
const coords = L.DomUtil.create('div', 'leaflet-control-layers coordinates');
|
||||
this._coords = coords;
|
||||
if (!show) {
|
||||
this._coords.style.display = "none";
|
||||
}
|
||||
return coords;
|
||||
},
|
||||
update: function (html, point) {
|
||||
this.x = point == null ? "---" : Math.floor(point.x);
|
||||
this.z = point == null ? "---" : Math.floor(point.y);
|
||||
if (html != null) {
|
||||
this._coords.innerHTML = html
|
||||
.replace(/{x}/g, this.x)
|
||||
.replace(/{z}/g, this.z);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.showCoordinates = show;
|
||||
this.html = json.html == null ? "undefined" : json.html;
|
||||
this.coords = new Coords();
|
||||
P.map.addControl(this.coords)
|
||||
.addEventListener('mousemove', (event) => {
|
||||
if (P.worldList.curWorld != null) {
|
||||
this.coords.update(this.html, P.toPoint(event.latlng));
|
||||
}
|
||||
});
|
||||
if (!json.enabled) {
|
||||
this.coords._coords.style.display = "none";
|
||||
}
|
||||
this.coords.update(this.html);
|
||||
}
|
||||
}
|
||||
|
||||
export { UICoordinates };
|
39
main/squaremap/web/js/modules/UILink.js
Normal file
39
main/squaremap/web/js/modules/UILink.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { P } from './Squaremap.js';
|
||||
|
||||
class UILink {
|
||||
constructor(json, show) {
|
||||
const Link = L.Control.extend({
|
||||
_container: null,
|
||||
options: {
|
||||
position: 'bottomleft'
|
||||
},
|
||||
onAdd: function () {
|
||||
const link = L.DomUtil.create('div', 'leaflet-control-layers link');
|
||||
this._link = link;
|
||||
if (!show) {
|
||||
this._link.style.display = "none";
|
||||
}
|
||||
this.update();
|
||||
return link;
|
||||
},
|
||||
update: function() {
|
||||
const url = P.worldList.curWorld == null ? "" : P.getUrlFromView();
|
||||
//P.updateBrowserUrl(url); // this spams browser history
|
||||
this._link.innerHTML = `<a href='${url}'><img src='images/clear.png'/></a>`;
|
||||
}
|
||||
});
|
||||
this.showLinkButton = show;
|
||||
this.link = new Link();
|
||||
P.map.addControl(this.link)
|
||||
.addEventListener('move', () => this.update())
|
||||
.addEventListener('zoom', () => this.update());
|
||||
if (!json.enabled) {
|
||||
this.link._link.style.display = "none";
|
||||
}
|
||||
}
|
||||
update() {
|
||||
this.link.update();
|
||||
}
|
||||
}
|
||||
|
||||
export { UILink };
|
97
main/squaremap/web/js/modules/WorldList.js
Normal file
97
main/squaremap/web/js/modules/WorldList.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { World } from "./util/World.js";
|
||||
import { P } from './Squaremap.js';
|
||||
|
||||
class WorldList {
|
||||
constructor(json) {
|
||||
// get worlds from json
|
||||
const unorderedMap = new Map();
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
const world = new World(json[i]);
|
||||
unorderedMap.set(world.name, world);
|
||||
}
|
||||
|
||||
// sort worlds by order
|
||||
this.worlds = new Map([...unorderedMap].sort((a, b) => a[1].order - b[1].order));
|
||||
|
||||
// set up world list link elements
|
||||
for (const [name, world] of this.worlds) {
|
||||
const link = P.createElement("a", name, this);
|
||||
link.onclick = function () {
|
||||
const curWorld = this.parent.curWorld;
|
||||
if (curWorld.name == name) {
|
||||
P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
|
||||
return;
|
||||
}
|
||||
P.playerList.clearPlayerMarkers();
|
||||
this.parent.loadWorld(name, (world) => {
|
||||
P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
|
||||
});
|
||||
};
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = this.getIcon(world);
|
||||
|
||||
link.appendChild(img);
|
||||
link.appendChild(P.createTextElement("span", world.display_name));
|
||||
|
||||
P.sidebar.worlds.element.appendChild(link);
|
||||
}
|
||||
}
|
||||
getIcon(world) {
|
||||
if (world.icon != null && world.icon != "") {
|
||||
return `images/icon/${world.icon}.png`;
|
||||
}
|
||||
switch (world.type) {
|
||||
case "nether":
|
||||
return "images/icon/red-cube-smol.png";
|
||||
case "the_end":
|
||||
return "images/icon/purple-cube-smol.png";
|
||||
case "normal":
|
||||
default:
|
||||
return "images/icon/green-cube-smol.png";
|
||||
}
|
||||
}
|
||||
loadInitialWorld(json, callback) {
|
||||
let updateUrl = false
|
||||
let name = P.getUrlParam("world", null)
|
||||
if (name != null) {
|
||||
const world = this.worlds.get(name);
|
||||
if (world == null) {
|
||||
updateUrl = true
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
name = json.worlds.sort((a, b) => a.order - b.order)[0].name
|
||||
}
|
||||
this.loadWorld(name, (a) => {
|
||||
callback(a)
|
||||
if (updateUrl) {
|
||||
P.updateBrowserUrl(`?world=${this.curWorld.name}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
loadWorld(name, callback) {
|
||||
// unload current world
|
||||
if (this.curWorld != null) {
|
||||
this.curWorld.unload();
|
||||
}
|
||||
|
||||
// load new world
|
||||
const world = this.worlds.get(name);
|
||||
this.curWorld = world;
|
||||
world.load(callback);
|
||||
}
|
||||
showWorld(world, callback) {
|
||||
if (this.curWorld.name == world) {
|
||||
if (callback != null) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.loadWorld(world, callback);
|
||||
P.updateBrowserUrl(P.getUrlFromView());
|
||||
}
|
||||
}
|
||||
|
||||
export { WorldList };
|
11
main/squaremap/web/js/modules/util/Fieldset.js
Normal file
11
main/squaremap/web/js/modules/util/Fieldset.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { P } from '../Squaremap.js';
|
||||
|
||||
class Fieldset {
|
||||
constructor(id, title) {
|
||||
this.element = P.createElement("fieldset", id);
|
||||
this.legend = P.createTextElement("legend", title);
|
||||
this.element.appendChild(this.legend);
|
||||
}
|
||||
}
|
||||
|
||||
export { Fieldset };
|
163
main/squaremap/web/js/modules/util/Markers.js
Normal file
163
main/squaremap/web/js/modules/util/Markers.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { P } from '../Squaremap.js';
|
||||
|
||||
class Marker {
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.id = this.opts.pop("id");
|
||||
this.popup = this.opts.pop("popup");
|
||||
this.popup_sticky = true;
|
||||
this.tooltip = this.opts.pop("tooltip");
|
||||
this.tooltip_sticky = true;
|
||||
}
|
||||
init() {
|
||||
if (this.popup != null) {
|
||||
if (this.popup_sticky) {
|
||||
this.marker.on('click', (e) => {
|
||||
L.popup({
|
||||
direction: this.opts.pop("tooltip_direction", "top")
|
||||
})
|
||||
.setLatLng(P.toLatLng(P.coordinates.coords.x, P.coordinates.coords.z))
|
||||
.setContent(this.popup)
|
||||
.openOn(P.map);
|
||||
});
|
||||
} else {
|
||||
this.marker.bindPopup(() => this.popup, {
|
||||
direction: this.opts.pop("tooltip_direction", "top")
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.tooltip != null) {
|
||||
this.marker.bindTooltip(() => this.tooltip, {
|
||||
direction: this.opts.pop("tooltip_direction", "top"),
|
||||
sticky: this.tooltip_sticky
|
||||
});
|
||||
}
|
||||
for (const key in this.opts) {
|
||||
this.marker.options[key] = this.opts[key];
|
||||
}
|
||||
}
|
||||
addTo(layer) {
|
||||
this.marker.remove();
|
||||
this.marker.addTo(layer);
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
constructor(json) {
|
||||
for (const prop in json) {
|
||||
this[prop] = json[prop];
|
||||
}
|
||||
}
|
||||
pop(key, def) {
|
||||
const val = this[key];
|
||||
delete this[key];
|
||||
return val == null ? def : val;
|
||||
}
|
||||
}
|
||||
|
||||
class Rectangle extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const points = this.opts.pop("points");
|
||||
this.marker = L.rectangle([P.toLatLng(points[0].x, points[0].z), P.toLatLng(points[1].x, points[1].z)]);
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
class PolyLine extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const points = this.opts.pop("points");
|
||||
const outer = [];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
if (Symbol.iterator in Object(points[i])) {
|
||||
const inner = [];
|
||||
for (let j = 0; j < points[i].length; j++) {
|
||||
inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
|
||||
}
|
||||
outer.push(inner);
|
||||
} else {
|
||||
outer.push(P.toLatLng(points[i].x, points[i].z));
|
||||
}
|
||||
}
|
||||
this.marker = L.polyline(outer);
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
class Polygon extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const points = this.opts.pop("points");
|
||||
const outer = [];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
if (Symbol.iterator in Object(points[i])) {
|
||||
const inner = [];
|
||||
for (let j = 0; j < points[i].length; j++) {
|
||||
if (Symbol.iterator in Object(points[i][j])) {
|
||||
const inner2 = [];
|
||||
for (let k = 0; k < points[i][j].length; k++) {
|
||||
inner2.push(P.toLatLng(points[i][j][k].x, points[i][j][k].z));
|
||||
}
|
||||
inner.push(inner2);
|
||||
} else {
|
||||
inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
|
||||
}
|
||||
}
|
||||
outer.push(inner);
|
||||
} else {
|
||||
outer.push(P.toLatLng(points[i].x, points[i].z));
|
||||
}
|
||||
}
|
||||
this.marker = L.polygon(outer);
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
class Circle extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const center = this.opts.pop("center");
|
||||
const radius = this.opts.pop("radius");
|
||||
this.marker = L.circle(P.toLatLng(center.x, center.z), {
|
||||
radius: P.pixelsToMeters(radius)
|
||||
});
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
class Ellipse extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const center = this.opts.pop("center");
|
||||
const radiusX = this.opts.pop("radiusX");
|
||||
const radiusZ = this.opts.pop("radiusZ");
|
||||
const tilt = 0;
|
||||
this.marker = L.ellipse(P.toLatLng(center.x, center.z), [radiusX, radiusZ], tilt);
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
class Icon extends Marker {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const point = this.opts.pop("point");
|
||||
const size = this.opts.pop("size");
|
||||
const anchor = this.opts.pop("anchor");
|
||||
const tooltipAnchor = this.opts.pop("tooltip_anchor", L.point(0, -size.z / 2));
|
||||
this.marker = L.marker(P.toLatLng(point.x, point.z), {
|
||||
icon: L.icon({
|
||||
iconUrl: `images/icon/registered/${opts.pop("icon")}.png`,
|
||||
iconSize: [size.x, size.z],
|
||||
iconAnchor: [anchor.x, anchor.z],
|
||||
popupAnchor: [tooltipAnchor.x, tooltipAnchor.z],
|
||||
tooltipAnchor: [tooltipAnchor.x, tooltipAnchor.z]
|
||||
})
|
||||
});
|
||||
this.popup_sticky = false;
|
||||
this.tooltip_sticky = false;
|
||||
super.init();
|
||||
}
|
||||
}
|
||||
|
||||
export { Marker, Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon };
|
23
main/squaremap/web/js/modules/util/Pin.js
Normal file
23
main/squaremap/web/js/modules/util/Pin.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { P } from '../Squaremap.js';
|
||||
|
||||
class Pin {
|
||||
constructor(def) {
|
||||
this.pinned = def;
|
||||
|
||||
this.element = P.createElement("img", "pin", this);
|
||||
|
||||
this.element.onclick = () => this.toggle();
|
||||
|
||||
this.pin(this.pinned);
|
||||
}
|
||||
toggle() {
|
||||
this.pin(!this.pinned);
|
||||
}
|
||||
pin(pin) {
|
||||
this.pinned = pin;
|
||||
this.element.className = pin ? "pinned" : "unpinned";
|
||||
this.element.src = `images/${this.element.className}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
export { Pin };
|
98
main/squaremap/web/js/modules/util/Player.js
Normal file
98
main/squaremap/web/js/modules/util/Player.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { P } from '../Squaremap.js';
|
||||
|
||||
class Player {
|
||||
constructor(json) {
|
||||
this.name = json.name;
|
||||
this.uuid = json.uuid;
|
||||
this.world = json.world;
|
||||
this.displayName = json.display_name !== undefined ? json.display_name : json.name;
|
||||
this.x = 0;
|
||||
this.z = 0;
|
||||
this.armor = 0;
|
||||
this.health = 20;
|
||||
this.tooltip = L.tooltip({
|
||||
permanent: true,
|
||||
direction: "right",
|
||||
offset: [10, 0],
|
||||
pane: "nameplate"
|
||||
});
|
||||
this.marker = L.marker(P.toLatLng(json.x, json.z), {
|
||||
icon: L.icon({
|
||||
iconUrl: 'images/icon/player.png',
|
||||
iconSize: [17, 16],
|
||||
iconAnchor: [8, 9],
|
||||
tooltipAnchor: [0, 0]
|
||||
}),
|
||||
rotationAngle: (180 + json.yaw)
|
||||
});
|
||||
if (P.worldList.curWorld.player_tracker.nameplates.enabled) {
|
||||
this.updateNameplate(json);
|
||||
this.marker.bindTooltip(this.tooltip);
|
||||
}
|
||||
}
|
||||
getHeadUrl() {
|
||||
return P.worldList.curWorld.player_tracker.nameplates.heads_url
|
||||
.replace(/{uuid}/g, this.uuid)
|
||||
.replace(/{name}/g, this.name);
|
||||
}
|
||||
updateNameplate(player) {
|
||||
let headImg = "";
|
||||
let armorImg = "";
|
||||
let healthImg = "";
|
||||
if (P.worldList.curWorld.player_tracker.nameplates.show_heads) {
|
||||
headImg = `<img src='${this.getHeadUrl()}' class="head" />`;
|
||||
}
|
||||
if (P.worldList.curWorld.player_tracker.nameplates.show_armor && player.armor != null) {
|
||||
armorImg = `<img src="images/armor/${Math.min(Math.max(player.armor, 0), 20)}.png" class="armor" />`;
|
||||
}
|
||||
if (P.worldList.curWorld.player_tracker.nameplates.show_health && player.health != null) {
|
||||
healthImg = `<img src="images/health/${Math.min(Math.max(player.health, 0), 20)}.png" class="health" />`;
|
||||
}
|
||||
this.tooltip.setContent(`<ul><li>${headImg}</li><li>${this.displayName}${healthImg}${armorImg}</li>`);
|
||||
}
|
||||
update(player) {
|
||||
this.x = player.x;
|
||||
this.z = player.z;
|
||||
this.world = player.world;
|
||||
this.armor = player.armor;
|
||||
this.health = player.health;
|
||||
this.displayName = player.display_name !== undefined ? player.display_name : player.name;
|
||||
const link = document.getElementById(player.uuid);
|
||||
const img = link.getElementsByTagName("img")[0];
|
||||
const span = link.getElementsByTagName("span")[0];
|
||||
if (P.worldList.curWorld.name == player.world) {
|
||||
if (P.worldList.curWorld.player_tracker.enabled) {
|
||||
this.addMarker();
|
||||
}
|
||||
const latlng = P.toLatLng(player.x, player.z);
|
||||
if (!this.marker.getLatLng().equals(latlng)) {
|
||||
this.marker.setLatLng(latlng);
|
||||
}
|
||||
const angle = 180 + player.yaw;
|
||||
if (this.marker.options.rotationAngle != angle) {
|
||||
this.marker.setRotationAngle(angle);
|
||||
}
|
||||
img.classList.remove("other-world");
|
||||
span.classList.remove("other-world");
|
||||
} else {
|
||||
this.removeMarker();
|
||||
img.classList.add("other-world");
|
||||
span.classList.add("other-world");
|
||||
}
|
||||
this.updateNameplate(player);
|
||||
}
|
||||
removeMarker() {
|
||||
this.marker.remove();
|
||||
P.playerList.markers.delete(this.uuid);
|
||||
P.map.removeLayer(this.marker);
|
||||
P.layerControl.playersLayer.removeLayer(this.marker);
|
||||
}
|
||||
addMarker() {
|
||||
if (!P.playerList.markers.has(this.uuid)) {
|
||||
this.marker.addTo(P.layerControl.playersLayer);
|
||||
P.playerList.markers.set(this.uuid, this.marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Player };
|
145
main/squaremap/web/js/modules/util/World.js
Normal file
145
main/squaremap/web/js/modules/util/World.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import { Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon } from "./Markers.js";
|
||||
import { P } from '../Squaremap.js';
|
||||
|
||||
class World {
|
||||
constructor(json) {
|
||||
this.name = json.name;
|
||||
this.order = json.order;
|
||||
this.icon = json.icon;
|
||||
this.type = json.type;
|
||||
this.display_name = json.display_name;
|
||||
this.markerLayers = new Map();
|
||||
this.player_tracker = {};
|
||||
this.marker_update_interval = 5;
|
||||
this.tiles_update_interval = 15;
|
||||
}
|
||||
tick() {
|
||||
// refresh map tile layer
|
||||
if (P.tick_count % this.tiles_update_interval == 0) {
|
||||
P.layerControl.updateTileLayer();
|
||||
}
|
||||
// load and draw markers
|
||||
if (P.tick_count % this.marker_update_interval == 0) {
|
||||
P.getJSON(`tiles/${this.name}/markers.json`, (json) => {
|
||||
if (this === P.worldList.curWorld) {
|
||||
this.markers(json);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
unload() {
|
||||
P.playerList.clearPlayerMarkers();
|
||||
const keys = Array.from(this.markerLayers.keys());
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const layer = this.markerLayers.get(keys[i]);
|
||||
P.layerControl.controls.removeLayer(layer);
|
||||
layer.remove();
|
||||
this.markerLayers.delete(keys[i]);
|
||||
}
|
||||
}
|
||||
load(callback) {
|
||||
P.getJSON(`tiles/${this.name}/settings.json`, (json) => {
|
||||
this.player_tracker = json.player_tracker;
|
||||
this.zoom = json.zoom;
|
||||
this.spawn = json.spawn;
|
||||
this.marker_update_interval = json.marker_update_interval;
|
||||
this.tiles_update_interval = json.tiles_update_interval;
|
||||
|
||||
// set the scale for our projection calculations
|
||||
P.setScale(this.zoom.max);
|
||||
|
||||
// set center and zoom
|
||||
P.centerOn(this.spawn.x, this.spawn.z, this.zoom.def)
|
||||
.setMinZoom(0) // extra zoom out doesn't work :(
|
||||
.setMaxZoom(this.zoom.max + this.zoom.extra);
|
||||
|
||||
// update page title
|
||||
document.title = P.title
|
||||
.replace(/{world}/g, this.display_name);
|
||||
|
||||
// setup background
|
||||
document.getElementById("map").style.background = this.getBackground();
|
||||
|
||||
// setup tile layers
|
||||
P.layerControl.setupTileLayers(this);
|
||||
|
||||
// force clear player markers
|
||||
P.playerList.clearPlayerMarkers();
|
||||
|
||||
// tick now, reset counter
|
||||
P.tick_count = 0;
|
||||
P.tick();
|
||||
|
||||
// force clear player markers
|
||||
P.playerList.clearPlayerMarkers();
|
||||
|
||||
if (callback != null) {
|
||||
callback(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
getBackground() {
|
||||
switch (this.type) {
|
||||
case "nether":
|
||||
return "url('images/nether_sky.png')";
|
||||
case "the_end":
|
||||
return "url('images/end_sky.png')";
|
||||
case "normal":
|
||||
default:
|
||||
return "url('images/overworld_sky.png')";
|
||||
}
|
||||
}
|
||||
markers(json) {
|
||||
// check if json is iterable
|
||||
if (json == null || !(Symbol.iterator in Object(json))) {
|
||||
return;
|
||||
}
|
||||
// iterate layers
|
||||
for (const entry of json) {
|
||||
// check if layer exists and needs updating
|
||||
let layer = this.markerLayers.get(entry.id);
|
||||
if (layer != null) {
|
||||
if (layer.timestamp === entry.timestamp) {
|
||||
continue; // skip
|
||||
}
|
||||
// clear existing layer to rebuild
|
||||
P.layerControl.removeOverlay(layer);
|
||||
// TODO
|
||||
// implement marker tracker instead of clearing
|
||||
// to reduce possible client side lag
|
||||
}
|
||||
|
||||
// setup the layer
|
||||
layer = new L.LayerGroup();
|
||||
layer.order = entry.order;
|
||||
layer.id = entry.id;
|
||||
layer.timestamp = entry.timestamp;
|
||||
layer.setZIndex(entry.z_index);
|
||||
this.markerLayers.set(layer.id, layer);
|
||||
|
||||
// setup the layer control
|
||||
if (entry.control === true) {
|
||||
P.layerControl.addOverlay(entry.name, layer, entry.hide);
|
||||
}
|
||||
|
||||
// setup the markers
|
||||
for (const shape in entry.markers) {
|
||||
let marker;
|
||||
const opts = new Options(entry.markers[shape]);
|
||||
switch(opts.pop("type")) {
|
||||
case "rectangle": marker = new Rectangle(opts); break;
|
||||
case "polyline": marker = new PolyLine(opts); break;
|
||||
case "polygon": marker = new Polygon(opts); break;
|
||||
case "circle": marker = new Circle(opts); break;
|
||||
case "ellipse": marker = new Ellipse(opts); break;
|
||||
case "icon": marker = new Icon(opts); break;
|
||||
}
|
||||
if (marker != null) {
|
||||
marker.addTo(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { World };
|
Reference in New Issue
Block a user