Merge branch 'pl3xmap'
# Conflicts: # src/api.ts # src/components/Map.vue # src/components/map/layer/MapLayer.vue # src/components/map/layer/MarkerSetLayer.vue # src/components/map/vector/Areas.vue # src/components/map/vector/Circles.vue # src/components/map/vector/Lines.vue # src/components/map/vector/Markers.vue # src/components/sidebar/WorldListItem.vue # src/dynmap.d.ts # src/index.d.ts # src/leaflet/icon/GenericIcon.ts # src/leaflet/layer/LiveAtlasLayerGroup.ts # src/leaflet/tileLayer/DynmapTileLayer.ts # src/leaflet/vector/LiveAtlasPolygon.ts # src/leaflet/vector/LiveAtlasPolyline.ts # src/model/LiveAtlasMapDefinition.ts # src/model/LiveAtlasProjection.ts # src/store/actions.ts # src/store/getters.ts # src/store/state.ts # src/util.ts # src/util/areas.ts # src/util/circles.ts # src/util/lines.ts # src/util/markers.ts
This commit is contained in:
commit
e07ac55c33
6
.idea/copyright/Dynmap.xml
Normal file
6
.idea/copyright/Dynmap.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright &#36;today.year James Lyne Some portions of this file were taken from https://github.com/webbukkit/dynmap. These portions are Copyright 2020 Dynmap Contributors. 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." />
|
||||
<option name="myName" value="Dynmap" />
|
||||
</copyright>
|
||||
</component>
|
6
.idea/copyright/Original.xml
Normal file
6
.idea/copyright/Original.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright &#36;today.year James Lyne 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." />
|
||||
<option name="myName" value="Original" />
|
||||
</copyright>
|
||||
</component>
|
8
.idea/copyright/profiles_settings.xml
Normal file
8
.idea/copyright/profiles_settings.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings>
|
||||
<module2copyright>
|
||||
<element module="Dynmap" copyright="Dynmap" />
|
||||
<element module="Original" copyright="Original" />
|
||||
</module2copyright>
|
||||
</settings>
|
||||
</component>
|
3
.idea/scopes/Dynmap.xml
Normal file
3
.idea/scopes/Dynmap.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="Dynmap" pattern="file:src/leaflet/tileLayer/DynmapTileLayer.ts||file:src/leaflet/control/ClockControl.ts||file:src/leaflet/control/CoordinatesControl.ts||file:src/leaflet/control/LinkControl.ts||file:src/util/areas.ts||file:src/util/circles.ts||file:src/util/lines.ts||file:src/util/markers.ts||file:src/leaflet/control/LiveAtlasLayerControl.ts||file:src/leaflet/control/LogoControl.ts||file:src/leaflet/icon/PlayerIcon.ts||file:src/leaflet/icon/GenericIcon.ts||file:src/model/LiveAtlasProjection.ts||file:src/scss/style.scss" />
|
||||
</component>
|
3
.idea/scopes/Original.xml
Normal file
3
.idea/scopes/Original.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="Original" pattern="!file:src/leaflet/control/ClockControl.ts&&!file:src/leaflet/control/CoordinatesControl.ts&&!file:src/leaflet/control/LinkControl.ts&&!file:src/leaflet/control/LogoControl.ts&&!file:src/leaflet/icon/PlayerIcon.ts&&!file:src/leaflet/icon/GenericIcon.ts&&!file:src/leaflet/tileLayer/DynmapTileLayer.ts&&!file:src/util/areas.ts&&!file:src/util/circles.ts&&!file:src/util/lines.ts&&!file:src/util/markers.ts&&!file[LiveAtlas]:standalone/*&&!file:src/model/LiveAtlasProjection.ts&&!file:src/leaflet/control/LiveAtlasLayerControl.ts&&!file[LiveAtlas]:patches/*&&!file[LiveAtlas]:public/*&&!file[LiveAtlas]:.idea/*&&!file[LiveAtlas]:.idea//*&&!file[LiveAtlas]:patches//*&&!file[LiveAtlas]:public//*&&!file[LiveAtlas]:standalone//*&&!file:FUNDING.yml&&!file:README.md&&!file:tsconfig.json&&!file:.gitignore&&!file:.env&&!file:LICENSE.md&&!file:package-lock.json&&!file:package.json&&!file:vite.config.ts&&!file:index.html&&!file:src/leaflet/control/LoadingControl.ts&&!file:src/scss/style.scss" />
|
||||
</component>
|
48
src/App.vue
48
src/App.vue
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -43,21 +43,19 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
updateInterval = computed(() => store.state.configuration.updateInterval),
|
||||
title = computed(() => store.state.configuration.title),
|
||||
currentUrl = computed(() => store.getters.url),
|
||||
currentServer = computed(() => store.state.currentServer),
|
||||
configurationHash = computed(() => store.state.configurationHash),
|
||||
chatBoxEnabled = computed(() => store.state.components.chatBox),
|
||||
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
||||
updatesEnabled = ref(false),
|
||||
updateTimeout = ref(0),
|
||||
configAttempts = ref(0),
|
||||
|
||||
loadConfiguration = async () => {
|
||||
try {
|
||||
await store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined);
|
||||
startUpdates();
|
||||
await store.dispatch(ActionTypes.START_UPDATES, undefined);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
hideSplash();
|
||||
|
||||
@ -80,36 +78,6 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
startUpdates = () => {
|
||||
updatesEnabled.value = true;
|
||||
update();
|
||||
},
|
||||
|
||||
update = async () => {
|
||||
//TODO: Error notification for repeated failures?
|
||||
try {
|
||||
await store.dispatch(ActionTypes.GET_UPDATE, undefined);
|
||||
} finally {
|
||||
if(updatesEnabled.value) {
|
||||
if(updateTimeout.value) {
|
||||
clearTimeout(updateTimeout.value);
|
||||
}
|
||||
|
||||
updateTimeout.value = setTimeout(() => update(), updateInterval.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stopUpdates = () => {
|
||||
updatesEnabled.value = false;
|
||||
|
||||
if (updateTimeout.value) {
|
||||
clearTimeout(updateTimeout.value);
|
||||
}
|
||||
|
||||
updateTimeout.value = 0;
|
||||
},
|
||||
|
||||
handleUrl = () => {
|
||||
const parsedUrl = parseUrl();
|
||||
|
||||
@ -174,7 +142,6 @@ export default defineComponent({
|
||||
watch(currentUrl, (url) => window.history.replaceState({}, '', url));
|
||||
watch(currentServer, (newServer?: LiveAtlasServerDefinition) => {
|
||||
showSplash();
|
||||
stopUpdates();
|
||||
|
||||
if(!newServer) {
|
||||
return;
|
||||
@ -182,6 +149,7 @@ export default defineComponent({
|
||||
|
||||
//Cleanup
|
||||
store.commit(MutationTypes.CLEAR_PLAYERS, undefined);
|
||||
store.commit(MutationTypes.SET_MAX_PLAYERS, 0);
|
||||
store.commit(MutationTypes.CLEAR_CURRENT_MAP, undefined);
|
||||
store.commit(MutationTypes.CLEAR_PARSED_URL, undefined);
|
||||
store.commit(MutationTypes.CLEAR_WORLDS, undefined);
|
||||
@ -190,17 +158,17 @@ export default defineComponent({
|
||||
window.history.replaceState({}, '', newServer.id);
|
||||
loadConfiguration();
|
||||
}, {deep: true});
|
||||
watch(configurationHash, (newHash, oldHash) => {
|
||||
watch(configurationHash, async (newHash, oldHash) => {
|
||||
if(newHash && oldHash) {
|
||||
showSplash();
|
||||
stopUpdates();
|
||||
store.commit(MutationTypes.CLEAR_PARSED_URL, undefined);
|
||||
loadConfiguration();
|
||||
await store.dispatch(ActionTypes.STOP_UPDATES, undefined);
|
||||
await loadConfiguration();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => loadConfiguration());
|
||||
onBeforeUnmount(() => stopUpdates());
|
||||
onBeforeUnmount(() => store.dispatch(ActionTypes.STOP_UPDATES, undefined));
|
||||
|
||||
handleUrl();
|
||||
onResize();
|
||||
|
777
src/api.ts
777
src/api.ts
@ -1,777 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
DynmapArea,
|
||||
DynmapChat,
|
||||
DynmapCircle,
|
||||
DynmapComponentConfig,
|
||||
DynmapConfigurationResponse,
|
||||
DynmapLine,
|
||||
DynmapMarker,
|
||||
DynmapMarkerSet,
|
||||
DynmapMarkerSetUpdates,
|
||||
DynmapPlayer,
|
||||
DynmapServerConfig,
|
||||
DynmapTileUpdate,
|
||||
DynmapUpdate,
|
||||
DynmapUpdateResponse,
|
||||
DynmapUpdates
|
||||
} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import ChatError from "@/errors/ChatError";
|
||||
import {LiveAtlasDimension, LiveAtlasServerMessageConfig, LiveAtlasWorldDefinition} from "@/index";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
const titleColours = /§[0-9a-f]/ig,
|
||||
netherWorldName = /_?nether(_|$)/i,
|
||||
endWorldName = /(^|_)end(_|$)/i;
|
||||
|
||||
function buildServerConfig(response: any): DynmapServerConfig {
|
||||
return {
|
||||
version: response.dynmapversion || '',
|
||||
grayHiddenPlayers: response.grayplayerswhenhidden || false,
|
||||
defaultMap: response.defaultmap || undefined,
|
||||
defaultWorld: response.defaultworld || undefined,
|
||||
defaultZoom: response.defaultzoom || 0,
|
||||
followMap: response.followmap || undefined,
|
||||
followZoom: response.followzoom || 0,
|
||||
updateInterval: response.updaterate || 3000,
|
||||
showLayerControl: response.showlayercontrol && response.showlayercontrol !== 'false', //Sent as a string for some reason
|
||||
title: response.title.replace(titleColours, '') || 'Dynmap',
|
||||
loginEnabled: response['login-enabled'] || false,
|
||||
maxPlayers: response.maxcount || 0,
|
||||
expandUI: response.sidebaropened && response.sidebaropened !== 'false', //Sent as a string for some reason
|
||||
hash: response.confighash || 0,
|
||||
};
|
||||
}
|
||||
|
||||
function buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
||||
return {
|
||||
chatPlayerJoin: response.joinmessage || '',
|
||||
chatPlayerQuit: response.quitmessage || '',
|
||||
chatAnonymousJoin: response['msg-hiddennamejoin'] || '',
|
||||
chatAnonymousQuit: response['msg-hiddennamequit'] || '',
|
||||
chatErrorNotAllowed: response['msg-chatnotallowed'] || '',
|
||||
chatErrorRequiresLogin: response['msg-chatrequireslogin'] || '',
|
||||
chatErrorCooldown: response.spammessage || '',
|
||||
worldsHeading: response['msg-maptypes'] || '',
|
||||
playersHeading: response['msg-players'] || '',
|
||||
}
|
||||
}
|
||||
|
||||
function buildWorlds(response: any): Array<LiveAtlasWorldDefinition> {
|
||||
const worlds: Map<string, LiveAtlasWorldDefinition> = new Map<string, LiveAtlasWorldDefinition>();
|
||||
|
||||
//Get all the worlds first so we can handle append_to_world properly
|
||||
(response.worlds || []).forEach((world: any) => {
|
||||
let worldType: LiveAtlasDimension = 'overworld';
|
||||
|
||||
if (netherWorldName.test(world.name) || (world.name == 'DIM-1')) {
|
||||
worldType = 'nether';
|
||||
} else if (endWorldName.test(world.name) || (world.name == 'DIM1')) {
|
||||
worldType = 'end';
|
||||
}
|
||||
|
||||
worlds.set(world.name, {
|
||||
seaLevel: world.sealevel || 64,
|
||||
name: world.name,
|
||||
dimension: worldType,
|
||||
protected: world.protected || false,
|
||||
title: world.title || '',
|
||||
height: world.height || 256,
|
||||
center: {
|
||||
x: world.center.x || 0,
|
||||
y: world.center.y || 0,
|
||||
z: world.center.z || 0
|
||||
},
|
||||
maps: new Map(),
|
||||
});
|
||||
});
|
||||
|
||||
(response.worlds || []).forEach((world: any) => {
|
||||
(world.maps || []).forEach((map: any) => {
|
||||
const worldName = map.append_to_world || world.name,
|
||||
w = worlds.get(worldName);
|
||||
|
||||
if(!w) {
|
||||
console.warn(`Ignoring map '${map.name}' associated with non-existent world '${worldName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
w.maps.set(map.name, new LiveAtlasMapDefinition({
|
||||
world: world, //Ignore append_to_world here otherwise things break
|
||||
background: map.background || '#000000',
|
||||
backgroundDay: map.backgroundday || '#000000',
|
||||
backgroundNight: map.backgroundnight || '#000000',
|
||||
icon: map.icon || undefined,
|
||||
imageFormat: map['image-format'] || 'png',
|
||||
name: map.name || '(Unnamed map)',
|
||||
nightAndDay: map.nightandday || false,
|
||||
prefix: map.prefix || '',
|
||||
protected: map.protected || false,
|
||||
title: map.title || '',
|
||||
mapToWorld: map.maptoworld || undefined,
|
||||
worldToMap: map.worldtomap || undefined,
|
||||
nativeZoomLevels: map.mapzoomout || 1,
|
||||
extraZoomLevels: map.mapzoomin || 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(worlds.values());
|
||||
}
|
||||
|
||||
function buildComponents(response: any): DynmapComponentConfig {
|
||||
const components: DynmapComponentConfig = {
|
||||
markers: {
|
||||
showLabels: false,
|
||||
},
|
||||
chatBox: undefined,
|
||||
chatBalloons: false,
|
||||
playerMarkers: undefined,
|
||||
coordinatesControl: undefined,
|
||||
linkControl: false,
|
||||
clockControl: undefined,
|
||||
logoControls: [],
|
||||
};
|
||||
|
||||
(response.components || []).forEach((component: any) => {
|
||||
const type = component.type || "unknown";
|
||||
|
||||
switch (type) {
|
||||
case "markers":
|
||||
components.markers = {
|
||||
showLabels: component.showlabel || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "playermarkers":
|
||||
components.playerMarkers = {
|
||||
hideByDefault: component.hidebydefault || false,
|
||||
layerName: component.label || "Players",
|
||||
layerPriority: component.layerprio || 0,
|
||||
showBodies: component.showplayerbody || false,
|
||||
showSkinFaces: component.showplayerfaces || false,
|
||||
showHealth: component.showplayerhealth || false,
|
||||
smallFaces: component.smallplayerfaces || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "coord":
|
||||
components.coordinatesControl = {
|
||||
showY: !(component.hidey || false),
|
||||
label: component.label || "Location: ",
|
||||
showRegion: component['show-mcr'] || false,
|
||||
showChunk: component['show-chunk'] || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "link":
|
||||
components.linkControl = true;
|
||||
|
||||
break;
|
||||
|
||||
case "digitalclock":
|
||||
components.clockControl = {
|
||||
showDigitalClock: true,
|
||||
showWeather: false,
|
||||
showTimeOfDay: false,
|
||||
}
|
||||
break;
|
||||
|
||||
case "timeofdayclock":
|
||||
components.clockControl = {
|
||||
showTimeOfDay: true,
|
||||
showDigitalClock: component.showdigitalclock || false,
|
||||
showWeather: component.showweather || false,
|
||||
}
|
||||
break;
|
||||
|
||||
case "logo":
|
||||
components.logoControls.push({
|
||||
text: component.text || '',
|
||||
url: component.linkurl || undefined,
|
||||
position: component.position.replace('-', '') || 'topleft',
|
||||
image: component.logourl || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case "chat":
|
||||
if (response.allowwebchat) {
|
||||
components.chatSending = {
|
||||
loginRequired: response['webchat-requires-login'] || false,
|
||||
maxLength: response['chatlengthlimit'] || 256,
|
||||
cooldown: response['webchat-interval'] || 5,
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "chatbox":
|
||||
components.chatBox = {
|
||||
allowUrlName: component.allowurlname || false,
|
||||
showPlayerFaces: component.showplayerfaces || false,
|
||||
messageLifetime: component.messagettl || Infinity,
|
||||
messageHistory: component.scrollback || Infinity,
|
||||
}
|
||||
break;
|
||||
|
||||
case "chatballoon":
|
||||
components.chatBalloons = true;
|
||||
}
|
||||
});
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
function buildMarkerSet(id: string, data: any): any {
|
||||
return {
|
||||
id,
|
||||
label: data.label || "Unnamed set",
|
||||
hidden: data.hide || false,
|
||||
priority: data.layerprio || 0,
|
||||
showLabels: data.showlabels || undefined,
|
||||
minZoom: typeof data.minzoom !== 'undefined' && data.minzoom > -1 ? data.minzoom : undefined,
|
||||
maxZoom: typeof data.maxzoom !== 'undefined' && data.maxzoom > -1 ? data.maxzoom : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function buildMarkers(data: any): Map<string, DynmapMarker> {
|
||||
const markers = Object.freeze(new Map()) as Map<string, DynmapMarker>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
markers.set(key, buildMarker(data[key]));
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
function buildMarker(marker: any): DynmapMarker {
|
||||
return {
|
||||
label: marker.label || '',
|
||||
location: {
|
||||
x: marker.x || 0,
|
||||
y: marker.y || 0,
|
||||
z: marker.z || 0,
|
||||
},
|
||||
dimensions: marker.dim ? marker.dim.split('x') : [16, 16],
|
||||
icon: marker.icon || "default",
|
||||
isHTML: marker.markup || false,
|
||||
minZoom: typeof marker.minzoom !== 'undefined' && marker.minzoom > -1 ? marker.minzoom : undefined,
|
||||
maxZoom: typeof marker.maxzoom !== 'undefined' && marker.maxzoom > -1 ? marker.maxzoom : undefined,
|
||||
popupContent: marker.desc || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function buildAreas(data: any): Map<string, DynmapArea> {
|
||||
const areas = Object.freeze(new Map()) as Map<string, DynmapArea>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
areas.set(key, buildArea(data[key]));
|
||||
}
|
||||
|
||||
return areas;
|
||||
}
|
||||
|
||||
function buildArea(area: any): DynmapArea {
|
||||
return {
|
||||
style: {
|
||||
color: area.color || '#ff0000',
|
||||
opacity: area.opacity || 1,
|
||||
weight: area.weight || 1,
|
||||
fillColor: area.fillcolor || '#ff0000',
|
||||
fillOpacity: area.fillopacity || 0,
|
||||
},
|
||||
label: area.label || '',
|
||||
isHTML: area.markup || false,
|
||||
x: area.x || [0, 0],
|
||||
y: [area.ybottom || 0, area.ytop || 0],
|
||||
z: area.z || [0, 0],
|
||||
minZoom: typeof area.minzoom !== 'undefined' && area.minzoom > -1 ? area.minzoom : undefined,
|
||||
maxZoom: typeof area.maxzoom !== 'undefined' && area.maxzoom > -1 ? area.maxzoom : undefined,
|
||||
popupContent: area.desc || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function buildLines(data: any): Map<string, DynmapLine> {
|
||||
const lines = Object.freeze(new Map()) as Map<string, DynmapLine>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.set(key, buildLine(data[key]));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function buildLine(line: any): DynmapLine {
|
||||
return {
|
||||
x: line.x || [0, 0],
|
||||
y: line.y || [0, 0],
|
||||
z: line.z || [0, 0],
|
||||
style: {
|
||||
color: line.color || '#ff0000',
|
||||
opacity: line.opacity || 1,
|
||||
weight: line.weight || 1,
|
||||
},
|
||||
label: line.label || '',
|
||||
isHTML: line.markup || false,
|
||||
minZoom: typeof line.minzoom !== 'undefined' && line.minzoom > -1 ? line.minzoom : undefined,
|
||||
maxZoom: typeof line.maxzoom !== 'undefined' && line.maxzoom > -1 ? line.maxzoom : undefined,
|
||||
popupContent: line.desc || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function buildCircles(data: any): Map<string, DynmapCircle> {
|
||||
const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
circles.set(key, buildCircle(data[key]));
|
||||
}
|
||||
|
||||
return circles;
|
||||
}
|
||||
|
||||
function buildCircle(circle: any): DynmapCircle {
|
||||
return {
|
||||
location: {
|
||||
x: circle.x || 0,
|
||||
y: circle.y || 0,
|
||||
z: circle.z || 0,
|
||||
},
|
||||
radius: [circle.xr || 0, circle.zr || 0],
|
||||
style: {
|
||||
fillColor: circle.fillcolor || '#ff0000',
|
||||
fillOpacity: circle.fillopacity || 0,
|
||||
color: circle.color || '#ff0000',
|
||||
opacity: circle.opacity || 1,
|
||||
weight: circle.weight || 1,
|
||||
},
|
||||
label: circle.label || '',
|
||||
isHTML: circle.markup || false,
|
||||
|
||||
minZoom: typeof circle.minzoom !== 'undefined' && circle.minzoom > -1 ? circle.minzoom : undefined,
|
||||
maxZoom: typeof circle.maxzoom !== 'undefined' && circle.maxzoom > -1 ? circle.maxzoom : undefined,
|
||||
popupContent: circle.desc || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function buildUpdates(data: Array<any>): DynmapUpdates {
|
||||
const updates = {
|
||||
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
|
||||
tiles: [] as DynmapTileUpdate[],
|
||||
chat: [] as DynmapChat[],
|
||||
},
|
||||
dropped = {
|
||||
stale: 0,
|
||||
noSet: 0,
|
||||
noId: 0,
|
||||
unknownType: 0,
|
||||
unknownCType: 0,
|
||||
incomplete: 0,
|
||||
notImplemented: 0,
|
||||
},
|
||||
lastUpdate = useStore().state.updateTimestamp;
|
||||
|
||||
let accepted = 0;
|
||||
|
||||
for (const entry of data) {
|
||||
switch (entry.type) {
|
||||
case 'component': {
|
||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.id) {
|
||||
dropped.noId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Set updates don't have a set field, the id is the set
|
||||
const set = entry.msg.startsWith("set") ? entry.id : entry.set;
|
||||
|
||||
if (!set) {
|
||||
dropped.noSet++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.ctype !== 'markers') {
|
||||
dropped.unknownCType++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!updates.markerSets.has(set)) {
|
||||
updates.markerSets.set(set, {
|
||||
areaUpdates: [],
|
||||
markerUpdates: [],
|
||||
lineUpdates: [],
|
||||
circleUpdates: [],
|
||||
removed: false,
|
||||
});
|
||||
}
|
||||
|
||||
const markerSetUpdates = updates.markerSets.get(set),
|
||||
update: DynmapUpdate = {
|
||||
id: entry.id,
|
||||
removed: entry.msg.endsWith('deleted'),
|
||||
};
|
||||
|
||||
if (entry.msg.startsWith("set")) {
|
||||
markerSetUpdates!.removed = update.removed;
|
||||
markerSetUpdates!.payload = update.removed ? undefined : buildMarkerSet(set, entry);
|
||||
} else if (entry.msg.startsWith("marker")) {
|
||||
update.payload = update.removed ? undefined : buildMarker(entry);
|
||||
markerSetUpdates!.markerUpdates.push(Object.freeze(update));
|
||||
} else if (entry.msg.startsWith("area")) {
|
||||
update.payload = update.removed ? undefined : buildArea(entry);
|
||||
markerSetUpdates!.areaUpdates.push(Object.freeze(update));
|
||||
|
||||
} else if (entry.msg.startsWith("circle")) {
|
||||
update.payload = update.removed ? undefined : buildCircle(entry);
|
||||
markerSetUpdates!.circleUpdates.push(Object.freeze(update));
|
||||
|
||||
} else if (entry.msg.startsWith("line")) {
|
||||
update.payload = update.removed ? undefined : buildLine(entry);
|
||||
markerSetUpdates!.lineUpdates.push(Object.freeze(update));
|
||||
}
|
||||
|
||||
accepted++;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'chat':
|
||||
if (!entry.message || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.source !== 'player' && entry.source !== 'web') {
|
||||
dropped.notImplemented++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'chat',
|
||||
source: entry.source || undefined,
|
||||
playerAccount: entry.account || undefined,
|
||||
playerName: entry.playerName || undefined,
|
||||
message: entry.message || "",
|
||||
timestamp: entry.timestamp,
|
||||
channel: entry.channel || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'playerjoin':
|
||||
if (!entry.account || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'playerjoin',
|
||||
playerAccount: entry.account,
|
||||
playerName: entry.playerName || "",
|
||||
timestamp: entry.timestamp || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'playerquit':
|
||||
if (!entry.account || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'playerleave',
|
||||
playerAccount: entry.account,
|
||||
playerName: entry.playerName || "",
|
||||
timestamp: entry.timestamp || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'tile':
|
||||
if (!entry.name || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.tiles.push({
|
||||
name: entry.name,
|
||||
timestamp: entry.timestamp,
|
||||
});
|
||||
|
||||
accepted++;
|
||||
break;
|
||||
|
||||
default:
|
||||
dropped.unknownType++;
|
||||
}
|
||||
}
|
||||
|
||||
//Sort chat by newest first
|
||||
updates.chat = updates.chat.sort((one, two) => {
|
||||
return two.timestamp - one.timestamp;
|
||||
});
|
||||
|
||||
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
async function fetchJSON(url: string, signal: AbortSignal) {
|
||||
let response, json;
|
||||
|
||||
try {
|
||||
response = await fetch(url, {signal});
|
||||
} catch(e) {
|
||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.warn(`Request aborted (${url}`);
|
||||
throw e;
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
throw new Error(`Network request failed`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network request failed (${response.statusText || 'Unknown'})`);
|
||||
}
|
||||
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch(e) {
|
||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.warn(`Request aborted (${url}`);
|
||||
throw e;
|
||||
} else {
|
||||
throw new Error('Request returned invalid json');
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
let configurationAbort: AbortController | undefined = undefined,
|
||||
markersAbort: AbortController | undefined = undefined,
|
||||
updateAbort: AbortController | undefined = undefined;
|
||||
|
||||
export default {
|
||||
async getConfiguration(): Promise<DynmapConfigurationResponse> {
|
||||
if(configurationAbort) {
|
||||
configurationAbort.abort();
|
||||
}
|
||||
|
||||
configurationAbort = new AbortController();
|
||||
|
||||
const response = await fetchJSON(useStore().getters.serverConfig.dynmap.configuration, configurationAbort.signal);
|
||||
|
||||
if (response.error === 'login-required') {
|
||||
throw new Error("Login required");
|
||||
} else if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
return {
|
||||
config: buildServerConfig(response),
|
||||
messages: buildMessagesConfig(response),
|
||||
worlds: buildWorlds(response),
|
||||
components: buildComponents(response),
|
||||
loggedIn: response.loggedin || false,
|
||||
}
|
||||
},
|
||||
|
||||
async getUpdate(requestId: number, world: string, timestamp: number): Promise<DynmapUpdateResponse> {
|
||||
let url = useStore().getters.serverConfig.dynmap.update;
|
||||
url = url.replace('{world}', world);
|
||||
url = url.replace('{timestamp}', timestamp.toString());
|
||||
|
||||
if(updateAbort) {
|
||||
updateAbort.abort();
|
||||
}
|
||||
|
||||
updateAbort = new AbortController();
|
||||
|
||||
const response = await fetchJSON(url, updateAbort.signal);
|
||||
const players: Set<DynmapPlayer> = new Set();
|
||||
|
||||
(response.players || []).forEach((player: any) => {
|
||||
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
|
||||
|
||||
players.add({
|
||||
account: player.account || "",
|
||||
health: player.health || 0,
|
||||
armor: player.armor || 0,
|
||||
name: player.name || "",
|
||||
sort: player.sort || 0,
|
||||
hidden: !world,
|
||||
location: {
|
||||
//Add 0.5 to position in the middle of a block
|
||||
x: !isNaN(player.x) ? player.x + 0.5 : 0,
|
||||
y: !isNaN(player.y) ? player.y : 0,
|
||||
z: !isNaN(player.z) ? player.z + 0.5 : 0,
|
||||
world: world,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Extra fake players for testing
|
||||
// for(let i = 0; i < 450; i++) {
|
||||
// players.add({
|
||||
// account: "VIDEO GAMES " + i,
|
||||
// health: Math.round(Math.random() * 10),
|
||||
// armor: Math.round(Math.random() * 10),
|
||||
// name: "VIDEO GAMES " + i,
|
||||
// sort: Math.round(Math.random() * 10),
|
||||
// hidden: false,
|
||||
// location: {
|
||||
// x: Math.round(Math.random() * 1000) - 500,
|
||||
// y: 64,
|
||||
// z: Math.round(Math.random() * 1000) - 500,
|
||||
// world: "world",
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
worldState: {
|
||||
timeOfDay: response.servertime || 0,
|
||||
thundering: response.isThundering || false,
|
||||
raining: response.hasStorm || false,
|
||||
},
|
||||
playerCount: response.count || 0,
|
||||
configHash: response.confighash || 0,
|
||||
timestamp: response.timestamp || 0,
|
||||
players,
|
||||
updates: buildUpdates(response.updates || []),
|
||||
}
|
||||
},
|
||||
|
||||
async getMarkerSets(world: string): Promise<Map<string, DynmapMarkerSet>> {
|
||||
const url = `${useStore().getters.serverConfig.dynmap.markers}_markers_/marker_${world}.json`;
|
||||
|
||||
if(markersAbort) {
|
||||
markersAbort.abort();
|
||||
}
|
||||
|
||||
markersAbort = new AbortController();
|
||||
|
||||
const response = await fetchJSON(url, markersAbort.signal);
|
||||
const sets: Map<string, DynmapMarkerSet> = new Map();
|
||||
|
||||
response.sets = response.sets || {};
|
||||
|
||||
for (const key in response.sets) {
|
||||
if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const set = response.sets[key],
|
||||
markers = buildMarkers(set.markers || {}),
|
||||
circles = buildCircles(set.circles || {}),
|
||||
areas = buildAreas(set.areas || {}),
|
||||
lines = buildLines(set.lines || {});
|
||||
|
||||
sets.set(key, {
|
||||
...buildMarkerSet(key, set),
|
||||
markers,
|
||||
circles,
|
||||
areas,
|
||||
lines,
|
||||
});
|
||||
}
|
||||
|
||||
return sets;
|
||||
},
|
||||
|
||||
sendChatMessage(message: string) {
|
||||
const store = useStore();
|
||||
|
||||
if (!store.state.components.chatSending) {
|
||||
return Promise.reject(store.state.messages.chatErrorDisabled);
|
||||
}
|
||||
|
||||
return fetch(useStore().getters.serverConfig.dynmap.sendmessage, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: null,
|
||||
message: message,
|
||||
})
|
||||
}).then((response) => {
|
||||
if (response.status === 403) { //Rate limited
|
||||
throw new ChatError(store.state.messages.chatErrorCooldown
|
||||
.replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network request failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then(response => {
|
||||
if (response.error !== 'none') {
|
||||
throw new ChatError(store.state.messages.chatErrorNotAllowed);
|
||||
}
|
||||
}).catch(e => {
|
||||
if (!(e instanceof ChatError)) {
|
||||
console.error(store.state.messages.chatErrorUnknown);
|
||||
console.trace(e);
|
||||
}
|
||||
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -44,12 +44,10 @@ import LinkControl from "@/components/map/control/LinkControl.vue";
|
||||
import ChatControl from "@/components/map/control/ChatControl.vue";
|
||||
import LogoControl from "@/components/map/control/LogoControl.vue";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||
import {LoadingControl} from "@/leaflet/control/LoadingControl";
|
||||
import MapContextMenu from "@/components/map/MapContextMenu.vue";
|
||||
import {Coordinate} from "@/index";
|
||||
import {Coordinate, LiveAtlasPlayer} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -125,7 +123,7 @@ export default defineComponent({
|
||||
followTarget: {
|
||||
handler(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.updateFollow(newValue, !oldValue || newValue.account !== oldValue.account);
|
||||
this.updateFollow(newValue, !oldValue || newValue.name !== oldValue.name);
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
@ -141,8 +139,14 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
currentMap(newValue, oldValue) {
|
||||
if(this.leaflet && newValue && oldValue) {
|
||||
const panTarget = this.scheduledPan || oldValue.latLngToLocation(this.leaflet.getCenter(), 64);
|
||||
if(this.leaflet && newValue) {
|
||||
let panTarget = this.scheduledPan;
|
||||
|
||||
if(!panTarget && oldValue) {
|
||||
panTarget = oldValue.latLngToLocation(this.leaflet.getCenter(), 64);
|
||||
} else if(!panTarget) {
|
||||
panTarget = {x: 0, y: 0, z: 0};
|
||||
}
|
||||
|
||||
if(this.scheduledZoom) {
|
||||
this.leaflet!.setZoom(this.scheduledZoom, {
|
||||
@ -165,8 +169,6 @@ export default defineComponent({
|
||||
if(newValue) {
|
||||
let location: Coordinate | null = this.scheduledPan;
|
||||
|
||||
store.dispatch(ActionTypes.GET_MARKER_SETS, undefined);
|
||||
|
||||
// Abort if follow target is present, to avoid panning twice
|
||||
if(store.state.followTarget && store.state.followTarget.location.world === newValue.name) {
|
||||
return;
|
||||
@ -280,7 +282,7 @@ export default defineComponent({
|
||||
this.leaflet.getContainer().focus();
|
||||
}
|
||||
},
|
||||
updateFollow(player: DynmapPlayer, newFollow: boolean) {
|
||||
updateFollow(player: LiveAtlasPlayer, newFollow: boolean) {
|
||||
const store = useStore(),
|
||||
followMapName = store.state.configuration.followMap,
|
||||
currentWorld = store.state.currentWorld;
|
||||
@ -288,17 +290,17 @@ export default defineComponent({
|
||||
let targetWorld = null;
|
||||
|
||||
if(!this.leaflet) {
|
||||
console.warn(`Cannot follow ${player.account}. Map not yet initialized.`);
|
||||
console.warn(`Cannot follow ${player.name}. Map not yet initialized.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(player.hidden) {
|
||||
console.warn(`Cannot follow ${player.account}. Player is hidden from the map.`);
|
||||
console.warn(`Cannot follow ${player.name}. Player is hidden from the map.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!player.location.world) {
|
||||
console.warn(`Cannot follow ${player.account}. Player isn't in a known world.`);
|
||||
console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -309,7 +311,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (!targetWorld) {
|
||||
console.warn(`Cannot follow ${player.account}. Player isn't in a known world.`);
|
||||
console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -320,7 +322,7 @@ export default defineComponent({
|
||||
if(map !== store.state.currentMap && (targetWorld !== currentWorld || newFollow)) {
|
||||
this.scheduledPan = player.location;
|
||||
|
||||
if(newFollow) {
|
||||
if(newFollow && store.state.configuration.followZoom) {
|
||||
console.log(`Setting zoom for new follow ${store.state.configuration.followZoom}`);
|
||||
this.scheduledZoom = store.state.configuration.followZoom;
|
||||
}
|
||||
@ -330,7 +332,7 @@ export default defineComponent({
|
||||
} else {
|
||||
this.leaflet!.panTo(store.state.currentMap?.locationToLatLng(player.location));
|
||||
|
||||
if(newFollow) {
|
||||
if(newFollow && store.state.configuration.followZoom) {
|
||||
console.log(`Setting zoom for new follow ${store.state.configuration.followZoom}`);
|
||||
this.leaflet!.setZoom(store.state.configuration.followZoom);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -17,8 +17,9 @@
|
||||
<template>
|
||||
<section class="sidebar" role="none" ref="sidebar">
|
||||
<header class="sidebar__buttons">
|
||||
<button v-if="mapCount > 1" :class="{'button--maps': true}" @click="toggleMaps" :title="messageWorlds"
|
||||
:aria-label="messageWorlds" :aria-expanded="mapsVisible" @keydown="handleMapsKeydown">
|
||||
<button v-if="mapCount > 1 || serverCount > 1" :class="{'button--maps': true}" @click="toggleMaps"
|
||||
:title="messageWorlds" :aria-label="messageWorlds" :aria-expanded="mapsVisible"
|
||||
@keydown="handleMapsKeydown">
|
||||
<SvgIcon name="maps"></SvgIcon>
|
||||
</button>
|
||||
<button v-if="playerMakersEnabled" :class="{'button--players': true}" @click="togglePlayers"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -24,15 +24,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, ref, onMounted, computed} from "@vue/runtime-core";
|
||||
import {DynmapChat} from "@/dynmap";
|
||||
import {getMinecraftHead} from '@/util';
|
||||
import {useStore} from "@/store";
|
||||
import defaultImage from '@/assets/images/player_face.png';
|
||||
import {LiveAtlasChat} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
message: {
|
||||
type: Object as () => DynmapChat,
|
||||
type: Object as () => LiveAtlasChat,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
@ -1,3 +1,19 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- 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.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<nav role="none" id="map-context-menu" ref="menuElement" :style="style" @keydown="handleKeydown">
|
||||
<ul class="menu" role="menu">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -18,10 +18,10 @@
|
||||
import {defineComponent, onUnmounted, computed, watch} from "@vue/runtime-core";
|
||||
import {Map} from 'leaflet';
|
||||
import {useStore} from "@/store";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {getMinecraftTime} from "@/util";
|
||||
import {DynmapTileLayer} from "@/leaflet/tileLayer/DynmapTileLayer";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import {LiveAtlasTileLayer} from "@/leaflet/tileLayer/LiveAtlasTileLayer";
|
||||
import {Pl3xmapTileLayer} from "@/leaflet/tileLayer/Pl3xmapTileLayer";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@ -40,58 +40,32 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
let updateFrame = 0,
|
||||
stopUpdateWatch: Function;
|
||||
|
||||
const store = useStore(),
|
||||
night = computed(() => getMinecraftTime(store.state.currentWorldState.timeOfDay).night),
|
||||
active = computed(() => props.map === store.state.currentMap);
|
||||
|
||||
let layer: LiveAtlasTileLayer;
|
||||
|
||||
if(store.state.currentServer?.type === 'dynmap') {
|
||||
layer = new DynmapTileLayer({
|
||||
errorTileUrl: 'images/blank.png',
|
||||
mapSettings: Object.freeze(JSON.parse(JSON.stringify(props.map))),
|
||||
night: night.value,
|
||||
}),
|
||||
pendingUpdates = computed(() => !!store.state.pendingTileUpdates.length),
|
||||
active = computed(() => props.map === store.state.currentMap),
|
||||
|
||||
enableLayer = () => {
|
||||
props.leaflet.addLayer(layer);
|
||||
|
||||
stopUpdateWatch = watch(pendingUpdates, (newValue, oldValue) => {
|
||||
if(newValue && !oldValue && !updateFrame) {
|
||||
handlePendingUpdates();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer = new Pl3xmapTileLayer({
|
||||
errorTileUrl: 'images/blank.png',
|
||||
mapSettings: Object.freeze(JSON.parse(JSON.stringify(props.map)))
|
||||
});
|
||||
}
|
||||
|
||||
const enableLayer = () => {
|
||||
props.leaflet.addLayer(layer);
|
||||
},
|
||||
|
||||
disableLayer = () => {
|
||||
layer.remove();
|
||||
|
||||
if(stopUpdateWatch) {
|
||||
stopUpdateWatch();
|
||||
}
|
||||
},
|
||||
|
||||
handlePendingUpdates = async () => {
|
||||
const updates = await useStore().dispatch(ActionTypes.POP_TILE_UPDATES, 10);
|
||||
|
||||
for(const update of updates) {
|
||||
layer.updateNamedTile(update.name, update.timestamp);
|
||||
}
|
||||
|
||||
if(pendingUpdates.value) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
|
||||
} else {
|
||||
updateFrame = 0;
|
||||
}
|
||||
};
|
||||
|
||||
watch(active, (newValue) => newValue ? enableLayer() : disableLayer());
|
||||
watch(night, (newValue) => {
|
||||
if(props.map.nightAndDay) {
|
||||
layer.setNight(newValue);
|
||||
}
|
||||
});
|
||||
|
||||
if(active.value) {
|
||||
enableLayer();
|
||||
@ -99,10 +73,6 @@ export default defineComponent({
|
||||
|
||||
onUnmounted(() => {
|
||||
disableLayer();
|
||||
|
||||
if(updateFrame) {
|
||||
cancelAnimationFrame(updateFrame);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -24,13 +24,13 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {DynmapMarkerSet} from "@/dynmap";
|
||||
import Areas from "@/components/map/vector/Areas.vue";
|
||||
import Circles from "@/components/map/vector/Circles.vue";
|
||||
import Lines from "@/components/map/vector/Lines.vue";
|
||||
import Markers from "@/components/map/vector/Markers.vue";
|
||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||
import {LiveAtlasMarkerSet} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -47,7 +47,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
markerSet: {
|
||||
type: Object as () => DynmapMarkerSet,
|
||||
type: Object as () => LiveAtlasMarkerSet,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -62,22 +62,24 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const store = useStore();
|
||||
|
||||
if(!this.componentSettings!.hideByDefault) {
|
||||
this.leaflet.getLayerManager().addLayer(
|
||||
this.layerGroup,
|
||||
true,
|
||||
useStore().state.messages.playersHeading,
|
||||
store.state.components.playerMarkers!.layerName,
|
||||
this.componentSettings!.layerPriority);
|
||||
} else {
|
||||
this.leaflet.getLayerManager().addHiddenLayer(
|
||||
this.layerGroup,
|
||||
useStore().state.messages.playersHeading,
|
||||
store.state.components.playerMarkers!.layerName,
|
||||
this.componentSettings!.layerPriority);
|
||||
}
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
this.leaflet.removeLayer(this.layerGroup);
|
||||
this.leaflet.getLayerManager().removeLayer(this.layerGroup);
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -17,15 +17,15 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed, ref, onMounted, onUnmounted} from "@vue/runtime-core";
|
||||
import {LayerGroup} from 'leaflet';
|
||||
import {DynmapChat, DynmapPlayer} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import {PlayerMarker} from "@/leaflet/marker/PlayerMarker";
|
||||
import {Popup} from "leaflet";
|
||||
import {LiveAtlasChat, LiveAtlasPlayer} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
player: {
|
||||
type: Object as () => DynmapPlayer,
|
||||
type: Object as () => LiveAtlasPlayer,
|
||||
required: true
|
||||
},
|
||||
layerGroup: {
|
||||
@ -79,7 +79,7 @@ export default defineComponent({
|
||||
|
||||
//Chat messages to show in the popup
|
||||
playerChat = computed(() => {
|
||||
const messages: DynmapChat[] = [];
|
||||
const messages: LiveAtlasChat[] = [];
|
||||
|
||||
if(!chatBalloonsEnabled.value) {
|
||||
return messages;
|
||||
@ -96,7 +96,7 @@ export default defineComponent({
|
||||
break;
|
||||
}
|
||||
|
||||
if(message.type === 'chat' && message.playerAccount === props.player.account) {
|
||||
if(message.type === 'chat' && message.playerAccount === props.player.name) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
@ -218,7 +218,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
},
|
||||
playerChat(newValue: DynmapChat[]) {
|
||||
playerChat(newValue: LiveAtlasChat[]) {
|
||||
if(!this.chatBalloonsEnabled || !this.markerVisible || !newValue.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -17,18 +17,17 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {DynmapArea, DynmapMarkerSet} from "@/dynmap";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {createArea, updateArea} from "@/util/areas";
|
||||
import {getPointConverter} from '@/util';
|
||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import {LiveAtlasArea, LiveAtlasMarkerSet} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
set: {
|
||||
type: Object as () => DynmapMarkerSet,
|
||||
type: Object as () => LiveAtlasMarkerSet,
|
||||
required: true,
|
||||
},
|
||||
layerGroup: {
|
||||
@ -50,9 +49,9 @@ export default defineComponent({
|
||||
layers = Object.freeze(new Map()) as Map<string, LiveAtlasPolygon | LiveAtlasPolyline>,
|
||||
|
||||
createAreas = () => {
|
||||
const converter = getPointConverter();
|
||||
const converter = currentMap.value!.locationToLatLng.bind(currentMap.value);
|
||||
|
||||
props.set.areas.forEach((area: DynmapArea, id: string) => {
|
||||
props.set.areas.forEach((area: LiveAtlasArea, id: string) => {
|
||||
const layer = createArea(area, converter);
|
||||
|
||||
layers.set(id, layer);
|
||||
@ -72,18 +71,17 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
handlePendingUpdates = async () => {
|
||||
const updates = await useStore().dispatch(ActionTypes.POP_AREA_UPDATES, {
|
||||
const updates = await store.dispatch(ActionTypes.POP_AREA_UPDATES, {
|
||||
markerSet: props.set.id,
|
||||
amount: 10,
|
||||
});
|
||||
|
||||
const converter = getPointConverter();
|
||||
}),
|
||||
converter = currentMap.value!.locationToLatLng.bind(currentMap.value);
|
||||
|
||||
for(const update of updates) {
|
||||
if(update.removed) {
|
||||
deleteArea(update.id);
|
||||
} else {
|
||||
const layer = updateArea(layers.get(update.id), update.payload as DynmapArea, converter)
|
||||
const layer = updateArea(layers.get(update.id), update.payload as LiveAtlasArea, converter);
|
||||
|
||||
if(!layers.has(update.id)) {
|
||||
props.layerGroup.addLayer(layer);
|
||||
@ -101,10 +99,9 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME: Prevent unnecessary repositioning when changing worlds
|
||||
watch(currentMap, (newValue) => {
|
||||
if(newValue) {
|
||||
const converter = getPointConverter();
|
||||
watch(currentMap, (newValue, oldValue) => {
|
||||
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
|
||||
const converter = newValue.locationToLatLng.bind(newValue);
|
||||
|
||||
for (const [id, area] of props.set.areas) {
|
||||
updateArea(layers.get(id), area, converter);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -17,18 +17,17 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {DynmapCircle, DynmapMarkerSet} from "@/dynmap";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {createCircle, updateCircle} from "@/util/circles";
|
||||
import {getPointConverter} from '@/util';
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
|
||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||
import {LiveAtlasCircle, LiveAtlasMarkerSet} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
set: {
|
||||
type: Object as () => DynmapMarkerSet,
|
||||
type: Object as () => LiveAtlasMarkerSet,
|
||||
required: true,
|
||||
},
|
||||
layerGroup: {
|
||||
@ -50,9 +49,9 @@ export default defineComponent({
|
||||
layers = Object.freeze(new Map<string, LiveAtlasPolyline | LiveAtlasPolygon>()),
|
||||
|
||||
createCircles = () => {
|
||||
const converter = getPointConverter();
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
props.set.circles.forEach((circle: DynmapCircle, id: string) => {
|
||||
props.set.circles.forEach((circle: LiveAtlasCircle, id: string) => {
|
||||
const layer = createCircle(circle, converter);
|
||||
|
||||
layers.set(id, layer);
|
||||
@ -75,15 +74,14 @@ export default defineComponent({
|
||||
const updates = await useStore().dispatch(ActionTypes.POP_CIRCLE_UPDATES, {
|
||||
markerSet: props.set.id,
|
||||
amount: 10,
|
||||
});
|
||||
|
||||
const converter = getPointConverter();
|
||||
}),
|
||||
converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for(const update of updates) {
|
||||
if(update.removed) {
|
||||
deleteCircle(update.id);
|
||||
} else {
|
||||
const layer = updateCircle(layers.get(update.id), update.payload as DynmapCircle, converter)
|
||||
const layer = updateCircle(layers.get(update.id), update.payload as LiveAtlasCircle, converter)
|
||||
|
||||
if(!layers.has(update.id)) {
|
||||
props.layerGroup.addLayer(layer);
|
||||
@ -101,10 +99,9 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME: Prevent unnecessary repositioning when changing worlds
|
||||
watch(currentMap, (newValue) => {
|
||||
if(newValue) {
|
||||
const converter = getPointConverter();
|
||||
watch(currentMap, (newValue, oldValue) => {
|
||||
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for (const [id, circle] of props.set.circles) {
|
||||
updateCircle(layers.get(id), circle, converter);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -17,17 +17,16 @@
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import {DynmapLine, DynmapMarkerSet} from "@/dynmap";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {createLine, updateLine} from "@/util/lines";
|
||||
import {getPointConverter} from '@/util';
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||
import {LiveAtlasLine, LiveAtlasMarkerSet} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
set: {
|
||||
type: Object as () => DynmapMarkerSet,
|
||||
type: Object as () => LiveAtlasMarkerSet,
|
||||
required: true,
|
||||
},
|
||||
layerGroup: {
|
||||
@ -49,9 +48,9 @@ export default defineComponent({
|
||||
layers = Object.freeze(new Map<string, LiveAtlasPolyline>()),
|
||||
|
||||
createLines = () => {
|
||||
const converter = getPointConverter();
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
props.set.lines.forEach((line: DynmapLine, id: string) => {
|
||||
props.set.lines.forEach((line: LiveAtlasLine, id: string) => {
|
||||
const layer = createLine(line, converter);
|
||||
|
||||
layers.set(id, layer);
|
||||
@ -74,15 +73,14 @@ export default defineComponent({
|
||||
const updates = await useStore().dispatch(ActionTypes.POP_LINE_UPDATES, {
|
||||
markerSet: props.set.id,
|
||||
amount: 10,
|
||||
});
|
||||
|
||||
const converter = getPointConverter();
|
||||
}),
|
||||
converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for(const update of updates) {
|
||||
if(update.removed) {
|
||||
deleteLine(update.id);
|
||||
} else {
|
||||
const layer = updateLine(layers.get(update.id), update.payload as DynmapLine, converter)
|
||||
const layer = updateLine(layers.get(update.id), update.payload as LiveAtlasLine, converter)
|
||||
|
||||
if(!layers.has(update.id)) {
|
||||
props.layerGroup.addLayer(layer);
|
||||
@ -100,10 +98,9 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME: Prevent unnecessary repositioning when changing worlds
|
||||
watch(currentMap, (newValue) => {
|
||||
if(newValue) {
|
||||
const converter = getPointConverter();
|
||||
watch(currentMap, (newValue, oldValue) => {
|
||||
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for (const [id, line] of props.set.lines) {
|
||||
updateLine(layers.get(id), line, converter);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -18,16 +18,15 @@
|
||||
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
|
||||
import {Marker} from 'leaflet';
|
||||
import {useStore} from "@/store";
|
||||
import {DynmapMarker, DynmapMarkerSet} from "@/dynmap";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {createMarker, updateMarker} from "@/util/markers";
|
||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||
import {getPointConverter} from "@/util";
|
||||
import {LiveAtlasMarker, LiveAtlasMarkerSet} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
set: {
|
||||
type: Object as () => DynmapMarkerSet,
|
||||
type: Object as () => LiveAtlasMarkerSet,
|
||||
required: true,
|
||||
},
|
||||
layerGroup: {
|
||||
@ -49,9 +48,9 @@ export default defineComponent({
|
||||
layers = Object.freeze(new Map()) as Map<string, Marker>,
|
||||
|
||||
createMarkers = () => {
|
||||
const converter = getPointConverter();
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
props.set.markers.forEach((marker: DynmapMarker, id: string) => {
|
||||
props.set.markers.forEach((marker: LiveAtlasMarker, id: string) => {
|
||||
const layer = createMarker(marker, converter);
|
||||
|
||||
layers.set(id, layer);
|
||||
@ -74,15 +73,14 @@ export default defineComponent({
|
||||
const updates = await useStore().dispatch(ActionTypes.POP_MARKER_UPDATES, {
|
||||
markerSet: props.set.id,
|
||||
amount: 10,
|
||||
});
|
||||
|
||||
const converter = getPointConverter();
|
||||
}),
|
||||
converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for(const update of updates) {
|
||||
if(update.removed) {
|
||||
deleteMarker(update.id);
|
||||
} else {
|
||||
const layer = updateMarker(layers.get(update.id), update.payload as DynmapMarker, converter);
|
||||
const layer = updateMarker(layers.get(update.id), update.payload as LiveAtlasMarker, converter);
|
||||
|
||||
if(!layers.has(update.id)) {
|
||||
props.layerGroup.addLayer(layer);
|
||||
@ -100,11 +98,12 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME: Prevent unnecessary repositioning when changing worlds
|
||||
watch(currentMap, (newValue) => {
|
||||
if(newValue) {
|
||||
watch(currentMap, (newValue, oldValue) => {
|
||||
if(newValue && (!oldValue || oldValue.world === newValue.world)) {
|
||||
const converter = currentMap.value!.locationToLatLng.bind(store.state.currentMap);
|
||||
|
||||
for (const [id, marker] of props.set.markers) {
|
||||
updateMarker(layers.get(id), marker, getPointConverter());
|
||||
updateMarker(layers.get(id), marker, converter);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,3 +1,19 @@
|
||||
<!--
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- 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.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section :class="{'sidebar__section': true, 'section--collapsible': true, 'section--collapsed': collapsed}">
|
||||
<h2 class="section__heading">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -21,7 +21,7 @@
|
||||
<div :class="{'following__target': true, 'following__target--hidden': target.hidden}">
|
||||
<img width="32" height="32" class="target__icon" :src="image" alt="" />
|
||||
<span class="target__info">
|
||||
<span class="target__name" v-html="target.name"></span>
|
||||
<span class="target__name" v-html="target.displayName"></span>
|
||||
<span class="target__status" v-show="target.hidden">{{ messageHidden }}</span>
|
||||
</span>
|
||||
<button class="target__unfollow" type="button" :title="messageUnfollowTitle"
|
||||
@ -31,25 +31,25 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {computed, defineComponent, onMounted, ref, watch} from "@vue/runtime-core";
|
||||
import {getMinecraftHead} from '@/util';
|
||||
import defaultImage from '@/assets/images/player_face.png';
|
||||
import {LiveAtlasPlayer} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FollowTarget',
|
||||
props: {
|
||||
target: {
|
||||
type: Object as () => DynmapPlayer,
|
||||
type: Object as () => LiveAtlasPlayer,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore(),
|
||||
image = ref(defaultImage),
|
||||
account = ref(props.target.account),
|
||||
account = ref(props.target.name),
|
||||
|
||||
heading = computed(() => store.state.messages.followingHeading),
|
||||
messageUnfollow = computed(() => store.state.messages.followingUnfollow),
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -16,13 +16,13 @@
|
||||
|
||||
<template>
|
||||
<CollapsibleSection name="players" class="players">
|
||||
<template v-slot:heading>{{ messageHeading }} [{{ players.length }}/{{ maxPlayers }}]</template>
|
||||
<template v-slot:heading>{{ messageHeading }}</template>
|
||||
<template v-slot:default>
|
||||
<div class="section__content">
|
||||
<input v-if="players && searchEnabled" id="players__search" type="text" name="search"
|
||||
v-model="searchQuery" :placeholder="messagePlayersSearchPlaceholder" @keydown="onKeydown">
|
||||
<RadioList v-if="filteredPlayers.length" aria-labelledby="players-heading">
|
||||
<PlayerListItem v-for="player in filteredPlayers" :key="player.account"
|
||||
<PlayerListItem v-for="player in filteredPlayers" :key="player.name"
|
||||
:player="player"></PlayerListItem>
|
||||
</RadioList>
|
||||
<div v-else-if="searchQuery" class="section__skeleton">{{ messageSkeletonPlayersSearch }}</div>
|
||||
@ -49,7 +49,11 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
messageHeading = computed(() => store.state.messages.playersHeading),
|
||||
messageHeading = computed(() => {
|
||||
return store.state.messages.playersHeading
|
||||
.replace('{cur}', players.value.length.toString())
|
||||
.replace('{max}', maxPlayers.value.toString());
|
||||
}),
|
||||
messageSkeletonPlayers = computed(() => store.state.messages.playersSkeleton),
|
||||
messageSkeletonPlayersSearch = computed(() => store.state.messages.playersSearchSkeleton),
|
||||
messagePlayersSearchPlaceholder = computed(() => store.state.messages.playersSearchPlaceholder),
|
||||
@ -62,10 +66,10 @@ export default defineComponent({
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
|
||||
return query ? store.state.sortedPlayers.filter(p => {
|
||||
return p.account.toLowerCase().indexOf(query) > -1;
|
||||
return p.name.toLowerCase().indexOf(query) > -1;
|
||||
}) : store.state.sortedPlayers;
|
||||
}),
|
||||
maxPlayers = computed(() => store.state.configuration.maxPlayers),
|
||||
maxPlayers = computed(() => store.state.maxPlayers || 0),
|
||||
|
||||
onKeydown = (e: KeyboardEvent) => {
|
||||
e.stopImmediatePropagation();
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -15,36 +15,36 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<input :id="`player-${player.account}`" type="radio" name="player" v-bind:value="player.account" v-model="followTarget"
|
||||
<input :id="`player-${player.name}`" type="radio" name="player" v-bind:value="player.name" v-model="followTarget"
|
||||
@click.prevent="onInputClick" />
|
||||
<label :for="`player-${player.account}`"
|
||||
<label :for="`player-${player.name}`"
|
||||
:class="{'player': true, 'player--hidden' : !!player.hidden, 'player--other-world': otherWorld}" :title="title"
|
||||
@click.prevent="onLabelClick">
|
||||
<img width="16" height="16" class="player__icon" :src="image" alt="" aria-hidden="true" />
|
||||
<span class="player__name" v-html="player.name"></span>
|
||||
<span class="player__name" v-html="player.displayName"></span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed, ref, onMounted} from 'vue';
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {getMinecraftHead} from '@/util';
|
||||
import defaultImage from '@/assets/images/player_face.png';
|
||||
import {LiveAtlasPlayer} from "@/index";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PlayerListItem',
|
||||
props: {
|
||||
player: {
|
||||
type: Object as () => DynmapPlayer,
|
||||
type: Object as () => LiveAtlasPlayer,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore(),
|
||||
otherWorld = computed(() => {
|
||||
return store.state.configuration.grayHiddenPlayers
|
||||
return store.state.components.playerMarkers?.grayHiddenPlayers
|
||||
&& !props.player.hidden
|
||||
&& (!store.state.currentWorld || store.state.currentWorld.name !== props.player.location.world);
|
||||
}),
|
||||
@ -59,7 +59,7 @@ export default defineComponent({
|
||||
}
|
||||
}),
|
||||
|
||||
followTarget = computed(() => store.state.followTarget ? store.state.followTarget.account : undefined),
|
||||
followTarget = computed(() => store.state.followTarget ? store.state.followTarget.name : undefined),
|
||||
|
||||
pan = () => {
|
||||
if(!props.player.hidden) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
@ -16,13 +16,13 @@
|
||||
|
||||
<template>
|
||||
<div class="world">
|
||||
<span class="world__name" aria-hidden="true">{{ world.title }}</span>
|
||||
<span class="world__name" aria-hidden="true">{{ world.displayName }}</span>
|
||||
<div class="world__maps menu">
|
||||
<template v-for="[key, map] in world.maps" :key="`${world.name}_${key}`">
|
||||
<input :id="`${name}-${world.name}-${key}`" type="radio" :name="name"
|
||||
v-bind:value="[world.name,map.name]" v-model="currentMap"
|
||||
:aria-labelledby="`${name}-${world.name}-${key}-label`">
|
||||
<label :id="`${name}-${world.name}-${key}-label`" class="map" :for="`${name}-${world.name}-${key}`" :title="`${world.title} - ${map.title}`">
|
||||
<label :id="`${name}-${world.name}-${key}-label`" class="map" :for="`${name}-${world.name}-${key}`" :title="`${world.displayName} - ${map.displayName}`">
|
||||
<SvgIcon :name="map.getIcon()"></SvgIcon>
|
||||
</label>
|
||||
</template>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
- Copyright 2020 James Lyne
|
||||
- Copyright 2021 James Lyne
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
- you may not use this file except in compliance with the License.
|
||||
|
181
src/dynmap.d.ts
vendored
181
src/dynmap.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,17 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {PathOptions, PointTuple, PolylineOptions} from "leaflet";
|
||||
import {CoordinatesControlOptions} from "@/leaflet/control/CoordinatesControl";
|
||||
import {LogoControlOptions} from "@/leaflet/control/LogoControl";
|
||||
import {ClockControlOptions} from "@/leaflet/control/ClockControl";
|
||||
import {
|
||||
Coordinate,
|
||||
LiveAtlasLocation,
|
||||
LiveAtlasServerMessageConfig,
|
||||
LiveAtlasWorldDefinition,
|
||||
LiveAtlasWorldState
|
||||
} from "@/index";
|
||||
import {LiveAtlasArea, LiveAtlasCircle, LiveAtlasLine, LiveAtlasMarker} from "@/index";
|
||||
|
||||
declare global {
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
@ -43,155 +33,6 @@ type DynmapUrlConfig = {
|
||||
markers: string;
|
||||
}
|
||||
|
||||
interface DynmapServerConfig {
|
||||
version: string;
|
||||
defaultMap?: string;
|
||||
defaultWorld?: string;
|
||||
defaultZoom: number;
|
||||
followMap?: string;
|
||||
followZoom: number;
|
||||
updateInterval: number;
|
||||
showLayerControl: boolean;
|
||||
title: string;
|
||||
loginEnabled: boolean;
|
||||
maxPlayers: number;
|
||||
grayHiddenPlayers: boolean;
|
||||
expandUI: boolean;
|
||||
hash: number;
|
||||
}
|
||||
|
||||
interface DynmapComponentConfig {
|
||||
markers: DynmapMarkersConfig;
|
||||
playerMarkers?: DynmapPlayerMarkersConfig;
|
||||
coordinatesControl?: CoordinatesControlOptions;
|
||||
clockControl ?: ClockControlOptions;
|
||||
linkControl: boolean;
|
||||
logoControls: Array<LogoControlOptions>;
|
||||
chatBox?: DynmapChatBoxConfig;
|
||||
chatSending?: DynmapChatSendingConfig;
|
||||
chatBalloons: boolean;
|
||||
}
|
||||
|
||||
interface DynmapMarkersConfig {
|
||||
showLabels: boolean
|
||||
}
|
||||
|
||||
interface DynmapPlayerMarkersConfig {
|
||||
hideByDefault: boolean;
|
||||
layerName: string;
|
||||
layerPriority: number;
|
||||
showBodies: boolean;
|
||||
showSkinFaces: boolean;
|
||||
showHealth: boolean;
|
||||
smallFaces: boolean;
|
||||
}
|
||||
|
||||
interface DynmapChatBoxConfig {
|
||||
allowUrlName: boolean;
|
||||
showPlayerFaces: boolean;
|
||||
messageLifetime: number;
|
||||
messageHistory: number;
|
||||
}
|
||||
|
||||
interface DynmapChatSendingConfig {
|
||||
loginRequired: boolean;
|
||||
maxLength: number;
|
||||
cooldown: number;
|
||||
}
|
||||
|
||||
interface DynmapConfigurationResponse {
|
||||
config: DynmapServerConfig,
|
||||
messages: LiveAtlasServerMessageConfig,
|
||||
worlds: Array<LiveAtlasWorldDefinition>,
|
||||
components: DynmapComponentConfig,
|
||||
loggedIn: boolean,
|
||||
}
|
||||
|
||||
interface DynmapUpdateResponse {
|
||||
worldState: LiveAtlasWorldState;
|
||||
configHash: number;
|
||||
playerCount: number;
|
||||
players: Set<DynmapPlayer>;
|
||||
updates: DynmapUpdates;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface DynmapPlayer {
|
||||
account: string;
|
||||
armor: number;
|
||||
health: number;
|
||||
name: string;
|
||||
sort: number;
|
||||
hidden: boolean;
|
||||
location: LiveAtlasLocation;
|
||||
}
|
||||
|
||||
interface DynmapMarkerSet {
|
||||
id: string,
|
||||
label: string;
|
||||
hidden: boolean;
|
||||
priority: number;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
showLabels?: boolean;
|
||||
markers: Map<string, DynmapMarker>;
|
||||
areas: Map<string, DynmapArea>;
|
||||
lines: Map<string, DynmapLine>;
|
||||
circles: Map<string, DynmapCircle>;
|
||||
}
|
||||
|
||||
interface DynmapMarker {
|
||||
dimensions: PointTuple;
|
||||
icon: string;
|
||||
label: string;
|
||||
isHTML: boolean;
|
||||
location: Coordinate;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
}
|
||||
|
||||
interface DynmapArea {
|
||||
style: PolylineOptions;
|
||||
label: string;
|
||||
isHTML: boolean;
|
||||
x: Array<number>;
|
||||
y: PointTuple;
|
||||
z: Array<number>;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
}
|
||||
|
||||
interface DynmapLine {
|
||||
x: Array<number>;
|
||||
y: Array<number>;
|
||||
z: Array<number>;
|
||||
style: PolylineOptions;
|
||||
label: string;
|
||||
isHTML: boolean;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
}
|
||||
|
||||
interface DynmapCircle {
|
||||
location: Coordinate;
|
||||
radius: PointTuple;
|
||||
style: PathOptions;
|
||||
label: string;
|
||||
isHTML: boolean;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
}
|
||||
|
||||
interface DynmapUpdates {
|
||||
markerSets: Map<string, DynmapMarkerSetUpdates>,
|
||||
tiles: Array<DynmapTileUpdate>,
|
||||
chat: Array<any> //TODO
|
||||
}
|
||||
|
||||
interface DynmapMarkerSetUpdates {
|
||||
markerUpdates: Array<DynmapMarkerUpdate>
|
||||
areaUpdates: Array<DynmapAreaUpdate>
|
||||
@ -215,32 +56,22 @@ interface DynmapUpdate {
|
||||
}
|
||||
|
||||
interface DynmapMarkerUpdate extends DynmapUpdate {
|
||||
payload?: DynmapMarker
|
||||
payload?: LiveAtlasMarker
|
||||
}
|
||||
|
||||
interface DynmapAreaUpdate extends DynmapUpdate {
|
||||
payload?: DynmapArea
|
||||
payload?: LiveAtlasArea
|
||||
}
|
||||
|
||||
interface DynmapCircleUpdate extends DynmapUpdate {
|
||||
payload?: DynmapCircle
|
||||
payload?: LiveAtlasCircle
|
||||
}
|
||||
|
||||
interface DynmapLineUpdate extends DynmapUpdate {
|
||||
payload?: DynmapLine
|
||||
payload?: LiveAtlasLine
|
||||
}
|
||||
|
||||
interface DynmapTileUpdate {
|
||||
name: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
interface DynmapChat {
|
||||
type: 'chat' | 'playerjoin' | 'playerleave';
|
||||
playerAccount?: string;
|
||||
playerName?: string;
|
||||
channel?: string;
|
||||
message?: string;
|
||||
source?: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
197
src/index.d.ts
vendored
197
src/index.d.ts
vendored
@ -1,6 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {State} from "@/store";
|
||||
import {DynmapPlayer, DynmapUrlConfig} from "@/dynmap";
|
||||
import {DynmapUrlConfig} from "@/dynmap";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import {PathOptions, PointTuple, PolylineOptions} from "leaflet";
|
||||
import {CoordinatesControlOptions} from "@/leaflet/control/CoordinatesControl";
|
||||
import {ClockControlOptions} from "@/leaflet/control/ClockControl";
|
||||
import {LogoControlOptions} from "@/leaflet/control/LogoControl";
|
||||
|
||||
declare module "*.png" {
|
||||
const value: any;
|
||||
@ -40,13 +60,11 @@ interface LiveAtlasGlobalConfig {
|
||||
}
|
||||
|
||||
interface LiveAtlasServerDefinition {
|
||||
id: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
interface LiveAtlasDynmapServerDefinition extends LiveAtlasServerDefinition {
|
||||
type: 'dynmap',
|
||||
dynmap: DynmapUrlConfig,
|
||||
id: string;
|
||||
label?: string;
|
||||
type: 'dynmap' | 'pl3xmap';
|
||||
dynmap?: DynmapUrlConfig;
|
||||
pl3xmap?: string;
|
||||
}
|
||||
|
||||
// Messages defined directly in LiveAtlas and used for all servers
|
||||
@ -108,16 +126,27 @@ export type LiveAtlasUIElement = 'layers' | 'chat' | 'players' | 'maps' | 'setti
|
||||
export type LiveAtlasSidebarSection = 'servers' | 'players' | 'maps';
|
||||
export type LiveAtlasDimension = 'overworld' | 'nether' | 'end';
|
||||
|
||||
interface LiveAtlasSortedPlayers extends Array<DynmapPlayer> {
|
||||
interface LiveAtlasPlayer {
|
||||
name: string;
|
||||
displayName: string;
|
||||
uuid?: string;
|
||||
armor: number;
|
||||
health: number;
|
||||
sort: number;
|
||||
hidden: boolean;
|
||||
location: LiveAtlasLocation;
|
||||
}
|
||||
|
||||
interface LiveAtlasSortedPlayers extends Array<LiveAtlasPlayer> {
|
||||
dirty?: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasWorldDefinition {
|
||||
seaLevel: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
dimension: LiveAtlasDimension;
|
||||
protected: boolean;
|
||||
title: string;
|
||||
height: number;
|
||||
center: Coordinate;
|
||||
maps: Map<string, LiveAtlasMapDefinition>;
|
||||
@ -136,3 +165,151 @@ interface LiveAtlasParsedUrl {
|
||||
zoom?: number;
|
||||
legacy: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasMapProvider {
|
||||
loadServerConfiguration(): Promise<void>;
|
||||
populateWorld(world: LiveAtlasWorldDefinition): Promise<void>;
|
||||
startUpdates(): void;
|
||||
stopUpdates(): void;
|
||||
sendChatMessage(message: string): void;
|
||||
destroy(): void;
|
||||
|
||||
getPlayerHeadUrl(entry: HeadQueueEntry): string;
|
||||
getTilesUrl(): string;
|
||||
getMarkerIconUrl(icon: string): string;
|
||||
}
|
||||
|
||||
interface LiveAtlasMarkerSet {
|
||||
id: string,
|
||||
label: string;
|
||||
hidden: boolean;
|
||||
priority: number;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
showLabels?: boolean;
|
||||
markers: Map<string, LiveAtlasMarker>;
|
||||
areas: Map<string, LiveAtlasArea>;
|
||||
lines: Map<string, LiveAtlasLine>;
|
||||
circles: Map<string, LiveAtlasCircle>;
|
||||
}
|
||||
|
||||
interface LiveAtlasMarker {
|
||||
dimensions: PointTuple;
|
||||
icon: string;
|
||||
label: string;
|
||||
isLabelHTML: boolean;
|
||||
location: Coordinate;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
}
|
||||
|
||||
interface LiveAtlasPath {
|
||||
style: PathOptions;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
popupContent?: string;
|
||||
tooltipContent?: string;
|
||||
isPopupHTML: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasArea extends LiveAtlasPath {
|
||||
style: PolylineOptions;
|
||||
outline: boolean;
|
||||
points: Coordinate[] | Coordinate[][] | Coordinate[][][]
|
||||
}
|
||||
|
||||
interface LiveAtlasLine extends LiveAtlasPath {
|
||||
points: Coordinate[];
|
||||
style: PolylineOptions;
|
||||
}
|
||||
|
||||
interface LiveAtlasCircle extends LiveAtlasPath {
|
||||
location: Coordinate;
|
||||
radius: PointTuple;
|
||||
style: PathOptions;
|
||||
}
|
||||
|
||||
interface HeadQueueEntry {
|
||||
cacheKey: string;
|
||||
name: string;
|
||||
uuid?: string;
|
||||
size: string;
|
||||
image: HTMLImageElement;
|
||||
}
|
||||
|
||||
interface LiveAtlasServerConfig {
|
||||
defaultMap?: string;
|
||||
defaultWorld?: string;
|
||||
defaultZoom: number;
|
||||
followMap?: string;
|
||||
followZoom?: number;
|
||||
title: string;
|
||||
expandUI: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasComponentConfig {
|
||||
markers: {
|
||||
showLabels: boolean;
|
||||
};
|
||||
playerMarkers?: LiveAtlasPlayerMarkerConfig;
|
||||
coordinatesControl?: CoordinatesControlOptions;
|
||||
clockControl?: ClockControlOptions;
|
||||
linkControl: boolean;
|
||||
layerControl: boolean;
|
||||
logoControls: Array<LogoControlOptions>;
|
||||
chatBox?: LiveAtlasChatBoxConfig;
|
||||
chatSending?: LiveAtlasChatSendingConfig;
|
||||
chatBalloons: boolean;
|
||||
login: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasPartialComponentConfig {
|
||||
markers?: {
|
||||
showLabels: boolean;
|
||||
};
|
||||
playerMarkers?: LiveAtlasPlayerMarkerConfig;
|
||||
coordinatesControl?: CoordinatesControlOptions;
|
||||
clockControl?: ClockControlOptions;
|
||||
linkControl?: boolean;
|
||||
layerControl?: boolean;
|
||||
logoControls?: Array<LogoControlOptions>;
|
||||
chatBox?: LiveAtlasChatBoxConfig;
|
||||
chatSending?: LiveAtlasChatSendingConfig;
|
||||
chatBalloons?: boolean;
|
||||
login?: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasPlayerMarkerConfig {
|
||||
grayHiddenPlayers: boolean;
|
||||
hideByDefault: boolean;
|
||||
layerName: string;
|
||||
layerPriority: number;
|
||||
showBodies: boolean;
|
||||
showSkinFaces: boolean;
|
||||
showHealth: boolean;
|
||||
smallFaces: boolean;
|
||||
}
|
||||
|
||||
interface LiveAtlasChatBoxConfig {
|
||||
allowUrlName: boolean;
|
||||
showPlayerFaces: boolean;
|
||||
messageLifetime: number;
|
||||
messageHistory: number;
|
||||
}
|
||||
|
||||
interface LiveAtlasChatSendingConfig {
|
||||
loginRequired: boolean;
|
||||
maxLength: number;
|
||||
cooldown: number;
|
||||
}
|
||||
|
||||
interface LiveAtlasChat {
|
||||
type: 'chat' | 'playerjoin' | 'playerleave';
|
||||
playerAccount?: string;
|
||||
playerName?: string;
|
||||
channel?: string;
|
||||
message?: string;
|
||||
source?: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -47,8 +47,14 @@ export class GenericIcon extends DivIcon {
|
||||
|
||||
// @ts-ignore
|
||||
options: GenericIconOptions;
|
||||
_image?: HTMLImageElement;
|
||||
_label?: HTMLSpanElement;
|
||||
|
||||
private _image?: HTMLImageElement;
|
||||
private _label?: HTMLSpanElement;
|
||||
private _container?: HTMLDivElement;
|
||||
private _labelCreated: boolean = false;
|
||||
private _onHover: EventListener = () => {
|
||||
this.createLabel();
|
||||
};
|
||||
|
||||
constructor(options: GenericIconOptions) {
|
||||
super(Object.assign(GenericIcon.defaultOptions, options));
|
||||
@ -60,18 +66,45 @@ export class GenericIcon extends DivIcon {
|
||||
}
|
||||
|
||||
const div = markerContainer.cloneNode(false) as HTMLDivElement,
|
||||
url = `${useStore().getters.serverConfig.dynmap.markers}_markers_/${this.options.icon}.png`,
|
||||
url = useStore().state.currentMapProvider!.getMarkerIconUrl(this.options.icon),
|
||||
size = point(this.options.iconSize as PointExpression);
|
||||
|
||||
this._image = markerIcon.cloneNode(false) as HTMLImageElement;
|
||||
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
|
||||
|
||||
const sizeClass = [size.x, size.y].join('x');
|
||||
|
||||
this._image.width = size.x;
|
||||
this._image.height = size.y;
|
||||
this._image.src = url;
|
||||
|
||||
// @ts-ignore
|
||||
Icon.prototype._setIconStyles.call(this, div, 'icon');
|
||||
|
||||
div.appendChild(this._image);
|
||||
div.classList.add('marker');
|
||||
|
||||
if(this.options.className) {
|
||||
div.classList.add(this.options.className);
|
||||
}
|
||||
|
||||
//Create label lazily on hover
|
||||
this._image.addEventListener('mouseover', this._onHover);
|
||||
|
||||
this._container = div;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
createLabel() {
|
||||
if(!this._container || this._labelCreated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._image?.removeEventListener('mouseover', this._onHover);
|
||||
|
||||
const size = point(this.options.iconSize as PointExpression),
|
||||
sizeClass = [size.x, size.y].join('x');
|
||||
|
||||
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
|
||||
|
||||
this._label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`);
|
||||
|
||||
if (this.options.isHtml) {
|
||||
@ -80,23 +113,13 @@ export class GenericIcon extends DivIcon {
|
||||
this._label.textContent = this.options.label;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
Icon.prototype._setIconStyles.call(this, div, 'icon');
|
||||
|
||||
div.appendChild(this._image);
|
||||
div.appendChild(this._label);
|
||||
div.classList.add('marker');
|
||||
|
||||
if(this.options.className) {
|
||||
div.classList.add(this.options.className);
|
||||
}
|
||||
|
||||
return div;
|
||||
this._container!.appendChild(this._label);
|
||||
this._labelCreated = true;
|
||||
}
|
||||
|
||||
update(options: GenericIconOptions) {
|
||||
if(this._image && options.icon !== this.options.icon) {
|
||||
this._image!.src = `${useStore().getters.serverConfig.dynmap.markers}_markers_/${options.icon}.png`;
|
||||
this._image!.src = useStore().state.currentMapProvider!.getMarkerIconUrl(this.options.icon);
|
||||
this.options.icon = options.icon;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -18,9 +18,9 @@
|
||||
*/
|
||||
|
||||
import {MarkerOptions, DivIcon, DomUtil} from 'leaflet';
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {getMinecraftHead} from '@/util';
|
||||
import playerImage from '@/assets/images/player_face.png';
|
||||
import {LiveAtlasPlayer} from "@/index";
|
||||
|
||||
const noSkinImage: HTMLImageElement = document.createElement('img');
|
||||
noSkinImage.height = 16;
|
||||
@ -49,7 +49,7 @@ export interface PlayerIconOptions extends MarkerOptions {
|
||||
}
|
||||
|
||||
export class PlayerIcon extends DivIcon {
|
||||
private readonly _player: DynmapPlayer;
|
||||
private readonly _player: LiveAtlasPlayer;
|
||||
private _container?: HTMLDivElement;
|
||||
private _playerImage?: HTMLImageElement;
|
||||
private _playerInfo?: HTMLSpanElement;
|
||||
@ -65,7 +65,7 @@ export class PlayerIcon extends DivIcon {
|
||||
// @ts-ignore
|
||||
options: PlayerIconOptions;
|
||||
|
||||
constructor(player: DynmapPlayer, options: PlayerIconOptions) {
|
||||
constructor(player: LiveAtlasPlayer, options: PlayerIconOptions) {
|
||||
super(options);
|
||||
this._player = player;
|
||||
}
|
||||
@ -87,7 +87,7 @@ export class PlayerIcon extends DivIcon {
|
||||
|
||||
this._playerName = document.createElement('span');
|
||||
this._playerName.className = 'player__name';
|
||||
this._playerName.innerHTML = this._currentName = player.name;
|
||||
this._playerName.innerHTML = this._currentName = player.displayName;
|
||||
|
||||
if (this.options.showSkinFace) {
|
||||
let size;
|
||||
@ -151,8 +151,8 @@ export class PlayerIcon extends DivIcon {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this._player!.name !== this._currentName) {
|
||||
this._playerName!.innerHTML = this._currentName = this._player!.name;
|
||||
if(this._player!.displayName !== this._currentName) {
|
||||
this._playerName!.innerHTML = this._currentName = this._player!.displayName;
|
||||
}
|
||||
|
||||
if(this.options.showHealth) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -25,7 +25,7 @@ export default class LayerManager {
|
||||
private readonly map: Map;
|
||||
|
||||
constructor(map: Map) {
|
||||
const showControl = computed(() => useStore().state.configuration.showLayerControl);
|
||||
const showControl = computed(() => useStore().state.components.layerControl);
|
||||
this.map = map;
|
||||
this.layerControl = new LiveAtlasLayerControl({}, {},{
|
||||
position: 'topleft',
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {Layer, Map as LeafletMap, LayerGroup, LayerOptions, Util, Marker, Path} from "leaflet";
|
||||
import {GenericMarker} from "@/leaflet/marker/GenericMarker";
|
||||
|
||||
export interface LiveAtlasLayerGroupOptions extends LayerOptions {
|
||||
id: string; //Added to the name of layer group panes
|
||||
@ -29,11 +30,11 @@ export interface LiveAtlasLayerGroupOptions extends LayerOptions {
|
||||
export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
// @ts-ignore
|
||||
options: LiveAtlasLayerGroupOptions;
|
||||
_zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom
|
||||
private _zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom
|
||||
_layers: any;
|
||||
_markerPane?: HTMLElement;
|
||||
|
||||
_zoomEndCallback = () => this._updateLayerVisibility();
|
||||
private _zoomEndCallback = () => this._updateLayerVisibility();
|
||||
|
||||
constructor(options: LiveAtlasLayerGroupOptions) {
|
||||
super([], options);
|
||||
@ -80,7 +81,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
layer.options.pane = `vectors`;
|
||||
}
|
||||
|
||||
const zoomLimited = this._isLayerZoomLimited(layer);
|
||||
const zoomLimited = LiveAtlasLayerGroup._isLayerZoomLimited(layer);
|
||||
|
||||
if (zoomLimited) {
|
||||
this._zoomLimitedLayers.add(layer);
|
||||
@ -89,11 +90,11 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
if (this._map) {
|
||||
//If layer is zoom limited, only add to map if it should be visible
|
||||
if (zoomLimited) {
|
||||
if (this._isLayerVisible(layer, this._map.getZoom())) {
|
||||
this._map.addLayer(layer);
|
||||
if (LiveAtlasLayerGroup._isLayerVisible(layer, this._map.getZoom())) {
|
||||
this._addToMap(layer);
|
||||
}
|
||||
} else {
|
||||
this._map.addLayer(layer);
|
||||
this._addToMap(layer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +107,19 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
}
|
||||
|
||||
update(options: LiveAtlasLayerGroupOptions) {
|
||||
if(this.options.showLabels !== options.showLabels) {
|
||||
//Create labels if they are now always visible
|
||||
//TODO: This will be slow when many markers exist. Is it worth doing?
|
||||
if(options.showLabels) {
|
||||
this.eachLayer((layer) => {
|
||||
if(layer instanceof GenericMarker) {
|
||||
(layer as GenericMarker).createLabel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.options.showLabels = options.showLabels;
|
||||
}
|
||||
|
||||
if(this._markerPane) {
|
||||
this._markerPane.classList.toggle('leaflet-pane--show-labels', options.showLabels);
|
||||
@ -128,7 +141,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
}
|
||||
}
|
||||
|
||||
_updateLayerVisibility(onAdd?: boolean) {
|
||||
private _updateLayerVisibility(onAdd?: boolean) {
|
||||
if(!this._map) {
|
||||
return;
|
||||
}
|
||||
@ -142,34 +155,34 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
this.eachLayer((layer) => {
|
||||
//Per marker zoom limits take precedence, if present
|
||||
if(this._zoomLimitedLayers.has(layer)) {
|
||||
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer);
|
||||
LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
|
||||
} else { //Otherwise apply group zoom limit
|
||||
visible ? this._map.addLayer(layer) : this._map.removeLayer(layer);
|
||||
visible ? this._addToMap(layer) : this._removeFromMap(layer);
|
||||
}
|
||||
}, this);
|
||||
//Group isn't zoom limited, but some individual markers are
|
||||
} else if(this._zoomLimitedLayers.size) {
|
||||
this._zoomLimitedLayers.forEach((layer) => {
|
||||
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer);
|
||||
LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
|
||||
});
|
||||
//Nothing is zoom limited, but we've just been added to the map
|
||||
} else if(onAdd) {
|
||||
this.eachLayer(this._map.addLayer, this._map);
|
||||
this.eachLayer((layer: Layer) => this._addToMap(layer), this._map);
|
||||
}
|
||||
}
|
||||
|
||||
//Returns if this layer group has zoom limits defined
|
||||
_isZoomLimited() {
|
||||
private _isZoomLimited() {
|
||||
return this.options.maxZoom !== undefined || this.options.minZoom !== undefined;
|
||||
}
|
||||
|
||||
//Returns if the given layer has its own zoom limits defined
|
||||
_isLayerZoomLimited(layer: Layer) {
|
||||
private static _isLayerZoomLimited(layer: Layer) {
|
||||
return ((layer as any).options && (layer as any).options.minZoom !== undefined)
|
||||
&& ((layer as any).options && (layer as any).options.maxZoom !== undefined);
|
||||
}
|
||||
|
||||
_isLayerVisible(layer: Layer, currentZoom: number) {
|
||||
private static _isLayerVisible(layer: Layer, currentZoom: number) {
|
||||
let minZoom = -Infinity,
|
||||
maxZoom = Infinity;
|
||||
|
||||
@ -183,4 +196,17 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
|
||||
|
||||
return currentZoom >= minZoom && currentZoom <= maxZoom;
|
||||
}
|
||||
|
||||
private _addToMap(layer: Layer) {
|
||||
this._map.addLayer(layer)
|
||||
|
||||
//Create marker label immediately if labels are visible by default
|
||||
if(layer instanceof GenericMarker && this.options.showLabels) {
|
||||
(layer as GenericMarker).createLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private _removeFromMap(layer: Layer) {
|
||||
this._map.removeLayer(layer)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,16 +15,30 @@
|
||||
*/
|
||||
|
||||
import {MarkerOptions, Marker, Util, LatLngExpression, Icon} from 'leaflet';
|
||||
import {LiveAtlasMarker} from "@/index";
|
||||
import {GenericIcon} from "@/leaflet/icon/GenericIcon";
|
||||
|
||||
export interface GenericMarkerOptions extends MarkerOptions {
|
||||
icon: GenericIcon;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
}
|
||||
|
||||
export class GenericMarker extends Marker {
|
||||
constructor(latLng: LatLngExpression, options: GenericMarkerOptions) {
|
||||
super(latLng, options);
|
||||
Util.setOptions(this, options);
|
||||
declare options: GenericMarkerOptions;
|
||||
|
||||
constructor(latLng: LatLngExpression, options: LiveAtlasMarker) {
|
||||
super(latLng, {});
|
||||
|
||||
this.options.icon = new GenericIcon({
|
||||
icon: options.icon,
|
||||
label: options.label,
|
||||
iconSize: options.dimensions,
|
||||
isHtml: options.isLabelHTML,
|
||||
});
|
||||
|
||||
this.options.maxZoom = options.maxZoom;
|
||||
this.options.minZoom = options.maxZoom;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
@ -35,4 +49,8 @@ export class GenericMarker extends Marker {
|
||||
getIcon(): Icon.Default {
|
||||
return this.options.icon as Icon.Default;
|
||||
}
|
||||
|
||||
createLabel(): void {
|
||||
this.options.icon.createLabel();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import {LatLng, MarkerOptions, Marker, Util} from 'leaflet';
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {PlayerIcon} from "@/leaflet/icon/PlayerIcon";
|
||||
import {LiveAtlasPlayer} from "@/index";
|
||||
|
||||
export interface PlayerMarkerOptions extends MarkerOptions {
|
||||
smallFace: boolean,
|
||||
@ -26,9 +26,9 @@ export interface PlayerMarkerOptions extends MarkerOptions {
|
||||
}
|
||||
|
||||
export class PlayerMarker extends Marker {
|
||||
private _player: DynmapPlayer;
|
||||
private _player: LiveAtlasPlayer;
|
||||
|
||||
constructor(player: DynmapPlayer, options: PlayerMarkerOptions) {
|
||||
constructor(player: LiveAtlasPlayer, options: PlayerMarkerOptions) {
|
||||
super(new LatLng(0, 0), options);
|
||||
this._player = player;
|
||||
options.draggable = false;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -17,30 +17,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {TileLayer, Coords, DoneCallback, TileLayerOptions, DomUtil, Util, LatLng} from 'leaflet';
|
||||
import {store} from "@/store";
|
||||
import {Coords, DoneCallback, DomUtil} from 'leaflet';
|
||||
import {useStore} from "@/store";
|
||||
import {Coordinate} from "@/index";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
export interface DynmapTileLayerOptions extends TileLayerOptions {
|
||||
mapSettings: LiveAtlasMapDefinition;
|
||||
errorTileUrl: string;
|
||||
night?: boolean;
|
||||
}
|
||||
|
||||
export interface DynmapTileLayer extends TileLayer {
|
||||
options: DynmapTileLayerOptions;
|
||||
_mapSettings: LiveAtlasMapDefinition;
|
||||
_cachedTileUrls: Map<string, string>;
|
||||
_namedTiles: Map<string, DynmapTileElement>;
|
||||
_tileTemplate: DynmapTileElement;
|
||||
_loadQueue: DynmapTileElement[];
|
||||
_loadingTiles: Set<DynmapTileElement>;
|
||||
|
||||
locationToLatLng(location: Coordinate): LatLng;
|
||||
|
||||
latLngToLocation(latLng: LatLng): Coordinate;
|
||||
}
|
||||
import {LiveAtlasTileLayerOptions, LiveAtlasTileLayer} from "@/leaflet/tileLayer/LiveAtlasTileLayer";
|
||||
import {computed, watch} from "@vue/runtime-core";
|
||||
import {ComputedRef} from "@vue/reactivity";
|
||||
import {WatchStopHandle} from "vue";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
|
||||
export interface DynmapTile {
|
||||
active?: boolean;
|
||||
@ -68,43 +52,59 @@ export interface TileInfo {
|
||||
fmt: string;
|
||||
}
|
||||
|
||||
const store = useStore();
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export class DynmapTileLayer extends TileLayer {
|
||||
constructor(options: DynmapTileLayerOptions) {
|
||||
export class DynmapTileLayer extends LiveAtlasTileLayer {
|
||||
private readonly _cachedTileUrls: Map<any, any> = Object.seal(new Map());
|
||||
private readonly _namedTiles: Map<any, any> = Object.seal(new Map());
|
||||
private readonly _loadQueue: DynmapTileElement[] = [];
|
||||
private readonly _loadingTiles: Set<DynmapTileElement> = Object.seal(new Set());
|
||||
private readonly _tileTemplate: DynmapTileElement;
|
||||
private readonly _baseUrl: string;
|
||||
|
||||
private readonly _night: ComputedRef<boolean>;
|
||||
private readonly _pendingUpdates: ComputedRef<boolean>;
|
||||
private readonly _nightUnwatch: WatchStopHandle;
|
||||
private readonly _updateUnwatch: WatchStopHandle;
|
||||
private _updateFrame: number = 0;
|
||||
|
||||
// @ts-ignore
|
||||
declare options: DynmapTileLayerOptions;
|
||||
|
||||
constructor(options: LiveAtlasTileLayerOptions) {
|
||||
super('', options);
|
||||
|
||||
this._mapSettings = options.mapSettings;
|
||||
options.maxZoom = this._mapSettings.nativeZoomLevels + this._mapSettings.extraZoomLevels;
|
||||
options.maxNativeZoom = this._mapSettings.nativeZoomLevels;
|
||||
options.zoomReverse = true;
|
||||
options.tileSize = 128;
|
||||
options.minZoom = 0;
|
||||
|
||||
Util.setOptions(this, options);
|
||||
|
||||
if (options.mapSettings === null) {
|
||||
throw new TypeError("mapSettings missing");
|
||||
}
|
||||
|
||||
this._cachedTileUrls = Object.seal(new Map());
|
||||
this._namedTiles = Object.seal(new Map());
|
||||
this._loadQueue = [];
|
||||
this._loadingTiles = Object.seal(new Set());
|
||||
|
||||
this._tileTemplate = DomUtil.create('img', 'leaflet-tile') as DynmapTileElement;
|
||||
this._tileTemplate.style.width = this._tileTemplate.style.height = this.options.tileSize + 'px';
|
||||
this._tileTemplate.alt = '';
|
||||
this._tileTemplate.tileName = '';
|
||||
this._tileTemplate.setAttribute('role', 'presentation');
|
||||
this._baseUrl = store.state.currentMapProvider!.getTilesUrl();
|
||||
|
||||
Object.seal(this._tileTemplate);
|
||||
|
||||
if(this.options.crossOrigin || this.options.crossOrigin === '') {
|
||||
this._tileTemplate.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
|
||||
}
|
||||
|
||||
this._pendingUpdates = computed(() => !!store.state.pendingTileUpdates.length);
|
||||
this._updateUnwatch = watch(this._pendingUpdates, (newValue, oldValue) => {
|
||||
if(newValue && !oldValue && !this._updateFrame) {
|
||||
this.handlePendingUpdates();
|
||||
}
|
||||
});
|
||||
|
||||
this._night = computed(() => store.getters.night);
|
||||
this._nightUnwatch = watch(this._night, () => {
|
||||
if(this._mapSettings.nightAndDay) {
|
||||
this.redraw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTileName(coords: Coordinate) {
|
||||
private getTileName(coords: Coordinate) {
|
||||
const info = this.getTileInfo(coords);
|
||||
// Y is inverted for HD-map.
|
||||
info.y = -info.y;
|
||||
@ -116,12 +116,12 @@ export class DynmapTileLayer extends TileLayer {
|
||||
return this.getTileUrlFromName(this.getTileName(coords));
|
||||
}
|
||||
|
||||
getTileUrlFromName(name: string, timestamp?: number) {
|
||||
private getTileUrlFromName(name: string, timestamp?: number) {
|
||||
let url = this._cachedTileUrls.get(name);
|
||||
|
||||
if (!url) {
|
||||
const path = escape(`${this._mapSettings.world.name}/${name}`);
|
||||
url = `${store.getters.serverConfig.dynmap.tiles}${path}`;
|
||||
url = `${this._baseUrl}${path}`;
|
||||
|
||||
if(typeof timestamp !== 'undefined') {
|
||||
url += (url.indexOf('?') === -1 ? `?timestamp=${timestamp}` : `×tamp=${timestamp}`);
|
||||
@ -133,7 +133,7 @@ export class DynmapTileLayer extends TileLayer {
|
||||
return url;
|
||||
}
|
||||
|
||||
updateNamedTile(name: string, timestamp: number) {
|
||||
private updateNamedTile(name: string, timestamp: number) {
|
||||
const tile = this._namedTiles.get(name);
|
||||
this._cachedTileUrls.delete(name);
|
||||
|
||||
@ -240,14 +240,14 @@ export class DynmapTileLayer extends TileLayer {
|
||||
}
|
||||
|
||||
// Some helper functions.
|
||||
zoomprefix(amount: number) {
|
||||
private zoomprefix(amount: number) {
|
||||
// amount == 0 -> ''
|
||||
// amount == 1 -> 'z_'
|
||||
// amount == 2 -> 'zz_'
|
||||
return 'z'.repeat(amount) + (amount === 0 ? '' : '_');
|
||||
}
|
||||
|
||||
getTileInfo(coords: Coordinate): TileInfo {
|
||||
private getTileInfo(coords: Coordinate): TileInfo {
|
||||
// zoom: max zoomed in = this.options.maxZoom, max zoomed out = 0
|
||||
// izoom: max zoomed in = 0, max zoomed out = this.options.maxZoom
|
||||
// zoomoutlevel: izoom < mapzoomin -> 0, else -> izoom - mapzoomin (which ranges from 0 till mapzoomout)
|
||||
@ -259,7 +259,7 @@ export class DynmapTileLayer extends TileLayer {
|
||||
|
||||
return {
|
||||
prefix: this._mapSettings.prefix,
|
||||
nightday: (this._mapSettings.nightAndDay && !this.options.night) ? '_day' : '',
|
||||
nightday: (this._mapSettings.nightAndDay && !this._night.value) ? '_day' : '',
|
||||
scaledx: x >> 5,
|
||||
scaledy: y >> 5,
|
||||
zoom: this.zoomprefix(zoomoutlevel),
|
||||
@ -270,10 +270,34 @@ export class DynmapTileLayer extends TileLayer {
|
||||
};
|
||||
}
|
||||
|
||||
setNight(night: boolean) {
|
||||
if(this.options.night !== night) {
|
||||
this.options.night = night;
|
||||
this.redraw();
|
||||
private async handlePendingUpdates() {
|
||||
const updates = await store.dispatch(ActionTypes.POP_TILE_UPDATES, 10);
|
||||
|
||||
for(const update of updates) {
|
||||
this.updateNamedTile(update.name, update.timestamp);
|
||||
}
|
||||
|
||||
if(this._pendingUpdates.value) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this._updateFrame = requestAnimationFrame(() => this.handlePendingUpdates());
|
||||
} else {
|
||||
this._updateFrame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
super.remove();
|
||||
|
||||
this._nightUnwatch();
|
||||
|
||||
if(this._updateFrame) {
|
||||
cancelAnimationFrame(this._updateFrame);
|
||||
}
|
||||
|
||||
if(this._updateUnwatch) {
|
||||
this._updateUnwatch();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
46
src/leaflet/tileLayer/LiveAtlasTileLayer.ts
Normal file
46
src/leaflet/tileLayer/LiveAtlasTileLayer.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {TileLayer, TileLayerOptions, Util} from 'leaflet';
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
export interface LiveAtlasTileLayerOptions extends TileLayerOptions {
|
||||
mapSettings: LiveAtlasMapDefinition;
|
||||
errorTileUrl: string;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export abstract class LiveAtlasTileLayer extends TileLayer {
|
||||
protected _mapSettings: LiveAtlasMapDefinition;
|
||||
declare options: LiveAtlasTileLayerOptions;
|
||||
|
||||
protected constructor(url: string, options: LiveAtlasTileLayerOptions) {
|
||||
super(url, options);
|
||||
|
||||
this._mapSettings = options.mapSettings;
|
||||
options.maxZoom = this._mapSettings.nativeZoomLevels + this._mapSettings.extraZoomLevels;
|
||||
options.maxNativeZoom = this._mapSettings.nativeZoomLevels;
|
||||
options.zoomReverse = true;
|
||||
options.tileSize = 128;
|
||||
options.minZoom = 0;
|
||||
|
||||
Util.setOptions(this, options);
|
||||
|
||||
if (options.mapSettings === null) {
|
||||
throw new TypeError("mapSettings missing");
|
||||
}
|
||||
}
|
||||
}
|
34
src/leaflet/tileLayer/Pl3xmapTileLayer.ts
Normal file
34
src/leaflet/tileLayer/Pl3xmapTileLayer.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {LiveAtlasTileLayer, LiveAtlasTileLayerOptions} from "@/leaflet/tileLayer/LiveAtlasTileLayer";
|
||||
import {useStore} from "@/store";
|
||||
import {Util} from "leaflet";
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export class Pl3xmapTileLayer extends LiveAtlasTileLayer {
|
||||
constructor(options: LiveAtlasTileLayerOptions) {
|
||||
const worldName = options.mapSettings.world.name,
|
||||
baseUrl = useStore().state.currentMapProvider!.getTilesUrl();
|
||||
|
||||
super(`${baseUrl}${worldName}/{z}/{x}_{y}.png`, options);
|
||||
|
||||
options.tileSize = 512;
|
||||
options.zoomReverse = false;
|
||||
|
||||
Util.setOptions(this, options);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {LatLngExpression, Polygon, PolylineOptions, Util} from "leaflet";
|
||||
|
||||
export interface LiveAtlasPolygonOptions extends PolylineOptions {
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
}
|
||||
|
||||
export default class LiveAtlasPolygon extends Polygon {
|
||||
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: LiveAtlasPolygonOptions) {
|
||||
super(latlngs, options);
|
||||
Util.setOptions(this, options);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {LatLngExpression, Polyline, PolylineOptions, Util} from "leaflet";
|
||||
|
||||
export interface LiveAtlasPolylineOptions extends PolylineOptions {
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
}
|
||||
|
||||
export default class LiveAtlasPolyline extends Polyline {
|
||||
constructor(latlngs: LatLngExpression[] | LatLngExpression[][], options?: LiveAtlasPolylineOptions) {
|
||||
super(latlngs, options);
|
||||
Util.setOptions(this, options);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Coordinate, LiveAtlasWorldDefinition} from "@/index";
|
||||
import {LatLng} from "leaflet";
|
||||
import {LiveAtlasProjection} from "@/model/LiveAtlasProjection";
|
||||
@ -5,8 +21,8 @@ import {LiveAtlasProjection} from "@/model/LiveAtlasProjection";
|
||||
export interface LiveAtlasMapDefinitionOptions {
|
||||
world: LiveAtlasWorldDefinition;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
background?: string;
|
||||
nightAndDay?: boolean;
|
||||
backgroundDay?: string;
|
||||
@ -24,7 +40,7 @@ export default class LiveAtlasMapDefinition {
|
||||
readonly world: LiveAtlasWorldDefinition;
|
||||
readonly name: string;
|
||||
readonly icon?: string;
|
||||
readonly title: string;
|
||||
readonly displayName: string;
|
||||
readonly background: string;
|
||||
readonly nightAndDay: boolean;
|
||||
readonly backgroundDay?: string;
|
||||
@ -35,12 +51,13 @@ export default class LiveAtlasMapDefinition {
|
||||
private readonly projection?: Readonly<LiveAtlasProjection>;
|
||||
readonly nativeZoomLevels: number;
|
||||
readonly extraZoomLevels: number;
|
||||
readonly scale: number;
|
||||
|
||||
constructor(options: LiveAtlasMapDefinitionOptions) {
|
||||
this.world = options.world; //Ignore append_to_world here otherwise things break
|
||||
this.name = options.name;
|
||||
this.icon = options.icon || undefined;
|
||||
this.title = options.title || '';
|
||||
this.displayName = options.displayName || '';
|
||||
|
||||
this.background = options.background || '#000000';
|
||||
this.nightAndDay = options.nightAndDay || false;
|
||||
@ -53,24 +70,25 @@ export default class LiveAtlasMapDefinition {
|
||||
|
||||
this.nativeZoomLevels = options.nativeZoomLevels || 1;
|
||||
this.extraZoomLevels = options.extraZoomLevels || 0;
|
||||
this.scale = (1 / Math.pow(2, this.nativeZoomLevels));
|
||||
|
||||
if(options.mapToWorld || options.worldToMap) {
|
||||
this.projection = Object.freeze(new LiveAtlasProjection({
|
||||
this.projection = new LiveAtlasProjection({
|
||||
mapToWorld: options.mapToWorld || [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
worldToMap: options.worldToMap || [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
nativeZoomLevels: this.nativeZoomLevels,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
locationToLatLng(location: Coordinate): LatLng {
|
||||
return this.projection ? this.projection.locationToLatLng(location)
|
||||
: LiveAtlasMapDefinition.defaultProjection.locationToLatLng(location);
|
||||
: new LatLng(-location.z * this.scale, location.x * this.scale);
|
||||
}
|
||||
|
||||
latLngToLocation(latLng: LatLng, y: number): Coordinate {
|
||||
return this.projection ? this.projection.latLngToLocation(latLng, y)
|
||||
: LiveAtlasMapDefinition.defaultProjection.latLngToLocation(latLng, y);
|
||||
: {x: latLng.lng / this.scale, y: y, z: -latLng.lat / this.scale};
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
@ -97,14 +115,4 @@ export default class LiveAtlasMapDefinition {
|
||||
|
||||
return `block_${worldType}_${mapType}`;
|
||||
}
|
||||
|
||||
static defaultProjection = Object.freeze({
|
||||
locationToLatLng(location: Coordinate): LatLng {
|
||||
return new LatLng(location.x, location.z);
|
||||
},
|
||||
|
||||
latLngToLocation(latLng: LatLng, y: number): Coordinate {
|
||||
return {x: latLng.lat, y: y, z: latLng.lng};
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {LatLng} from 'leaflet';
|
||||
import {Coordinate} from "@/index";
|
||||
|
||||
export interface LiveAtlasProjectionOptions {
|
||||
mapToWorld: [number, number, number, number, number, number, number, number, number],
|
||||
worldToMap: [number, number, number, number, number, number, number, number, number],
|
||||
nativeZoomLevels: number
|
||||
}
|
||||
|
||||
export class LiveAtlasProjection {
|
||||
private readonly options: LiveAtlasProjectionOptions
|
||||
|
||||
constructor(options: LiveAtlasProjectionOptions) {
|
||||
this.options = {
|
||||
mapToWorld: options.mapToWorld || [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
worldToMap: options.worldToMap || [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
nativeZoomLevels: options.nativeZoomLevels || 1
|
||||
}
|
||||
}
|
||||
|
||||
locationToLatLng(location: Coordinate): LatLng {
|
||||
const wtp = this.options.worldToMap,
|
||||
lat = wtp[3] * location.x + wtp[4] * location.y + wtp[5] * location.z,
|
||||
lng = wtp[0] * location.x + wtp[1] * location.y + wtp[2] * location.z;
|
||||
|
||||
return new LatLng(
|
||||
-((128 - lat) / (1 << this.options.nativeZoomLevels)),
|
||||
lng / (1 << this.options.nativeZoomLevels));
|
||||
}
|
||||
|
||||
latLngToLocation(latLng: LatLng, y: number): Coordinate {
|
||||
const ptw = this.options.mapToWorld,
|
||||
lat = latLng.lng * (1 << this.options.nativeZoomLevels),
|
||||
lon = 128 + latLng.lat * (1 << this.options.nativeZoomLevels),
|
||||
x = ptw[0] * lat + ptw[1] * lon + ptw[2] * y,
|
||||
z = ptw[6] * lat + ptw[7] * lon + ptw[8] * y;
|
||||
|
||||
return {x: x, y: y, z: z};
|
||||
}
|
||||
}
|
821
src/providers/DynmapMapProvider.ts
Normal file
821
src/providers/DynmapMapProvider.ts
Normal file
@ -0,0 +1,821 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
HeadQueueEntry,
|
||||
LiveAtlasArea, LiveAtlasChat,
|
||||
LiveAtlasCircle, LiveAtlasComponentConfig,
|
||||
LiveAtlasDimension,
|
||||
LiveAtlasLine,
|
||||
LiveAtlasMarker,
|
||||
LiveAtlasMarkerSet,
|
||||
LiveAtlasPlayer, LiveAtlasServerConfig,
|
||||
LiveAtlasServerDefinition,
|
||||
LiveAtlasServerMessageConfig,
|
||||
LiveAtlasWorldDefinition
|
||||
} from "@/index";
|
||||
import {
|
||||
DynmapMarkerSetUpdates, DynmapTileUpdate, DynmapUpdate
|
||||
} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import ChatError from "@/errors/ChatError";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import MapProvider from "@/providers/MapProvider";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {endWorldNameRegex, netherWorldNameRegex, titleColoursRegex} from "@/util";
|
||||
import {getPoints} from "@/util/areas";
|
||||
import {getLinePoints} from "@/util/lines";
|
||||
|
||||
export default class DynmapMapProvider extends MapProvider {
|
||||
private configurationAbort?: AbortController = undefined;
|
||||
private markersAbort?: AbortController = undefined;
|
||||
private updateAbort?: AbortController = undefined;
|
||||
|
||||
private updatesEnabled = false;
|
||||
private updateTimeout: number = 0;
|
||||
private updateTimestamp: Date = new Date();
|
||||
private updateInterval: number = 3000;
|
||||
|
||||
constructor(config: LiveAtlasServerDefinition) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
private static buildServerConfig(response: any): LiveAtlasServerConfig {
|
||||
return {
|
||||
defaultMap: response.defaultmap || undefined,
|
||||
defaultWorld: response.defaultworld || undefined,
|
||||
defaultZoom: response.defaultzoom || 0,
|
||||
followMap: response.followmap || undefined,
|
||||
followZoom: response.followzoom,
|
||||
title: response.title.replace(titleColoursRegex, '') || 'Dynmap',
|
||||
expandUI: response.sidebaropened && response.sidebaropened !== 'false', //Sent as a string for some reason
|
||||
};
|
||||
}
|
||||
|
||||
private static buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
||||
return {
|
||||
chatPlayerJoin: response.joinmessage || '',
|
||||
chatPlayerQuit: response.quitmessage || '',
|
||||
chatAnonymousJoin: response['msg-hiddennamejoin'] || '',
|
||||
chatAnonymousQuit: response['msg-hiddennamequit'] || '',
|
||||
chatErrorNotAllowed: response['msg-chatnotallowed'] || '',
|
||||
chatErrorRequiresLogin: response['msg-chatrequireslogin'] || '',
|
||||
chatErrorCooldown: response.spammessage || '',
|
||||
worldsHeading: response['msg-maptypes'] || '',
|
||||
playersHeading: response['msg-players'] ? `${response['msg-players']} ({cur}/{max})` : '',
|
||||
}
|
||||
}
|
||||
|
||||
private buildWorlds(response: any): Array<LiveAtlasWorldDefinition> {
|
||||
const worlds: Map<string, LiveAtlasWorldDefinition> = new Map<string, LiveAtlasWorldDefinition>();
|
||||
|
||||
//Get all the worlds first so we can handle append_to_world properly
|
||||
(response.worlds || []).forEach((world: any) => {
|
||||
let worldType: LiveAtlasDimension = 'overworld';
|
||||
|
||||
if (netherWorldNameRegex.test(world.name) || (world.name == 'DIM-1')) {
|
||||
worldType = 'nether';
|
||||
} else if (endWorldNameRegex.test(world.name) || (world.name == 'DIM1')) {
|
||||
worldType = 'end';
|
||||
}
|
||||
|
||||
worlds.set(world.name, {
|
||||
name: world.name,
|
||||
displayName: world.title || '',
|
||||
dimension: worldType,
|
||||
protected: world.protected || false,
|
||||
height: world.height || 256,
|
||||
seaLevel: world.sealevel || 64,
|
||||
center: {
|
||||
x: world.center.x || 0,
|
||||
y: world.center.y || 0,
|
||||
z: world.center.z || 0
|
||||
},
|
||||
maps: new Map(),
|
||||
});
|
||||
});
|
||||
|
||||
(response.worlds || []).forEach((world: any) => {
|
||||
(world.maps || []).forEach((map: any) => {
|
||||
const worldName = map.append_to_world || world.name,
|
||||
w = worlds.get(worldName);
|
||||
|
||||
if(!w) {
|
||||
console.warn(`Ignoring map '${map.name}' associated with non-existent world '${worldName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
w.maps.set(map.name, Object.freeze(new LiveAtlasMapDefinition({
|
||||
world: w, //Ignore append_to_world here otherwise things break
|
||||
background: map.background || '#000000',
|
||||
backgroundDay: map.backgroundday || '#000000',
|
||||
backgroundNight: map.backgroundnight || '#000000',
|
||||
icon: map.icon || undefined,
|
||||
imageFormat: map['image-format'] || 'png',
|
||||
name: map.name || '(Unnamed map)',
|
||||
nightAndDay: map.nightandday || false,
|
||||
prefix: map.prefix || '',
|
||||
protected: map.protected || false,
|
||||
displayName: map.title || '',
|
||||
mapToWorld: map.maptoworld || undefined,
|
||||
worldToMap: map.worldtomap || undefined,
|
||||
nativeZoomLevels: map.mapzoomout || 1,
|
||||
extraZoomLevels: map.mapzoomin || 0
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(worlds.values());
|
||||
}
|
||||
|
||||
private buildComponents(response: any): LiveAtlasComponentConfig {
|
||||
const components: LiveAtlasComponentConfig = {
|
||||
markers: {
|
||||
showLabels: false,
|
||||
},
|
||||
chatBox: undefined,
|
||||
chatBalloons: false,
|
||||
playerMarkers: undefined,
|
||||
coordinatesControl: undefined,
|
||||
layerControl: response.showlayercontrol && response.showlayercontrol !== 'false', //Sent as a string for some reason
|
||||
linkControl: false,
|
||||
clockControl: undefined,
|
||||
logoControls: [],
|
||||
login: response['login-enabled'] || false,
|
||||
};
|
||||
|
||||
(response.components || []).forEach((component: any) => {
|
||||
const type = component.type || "unknown";
|
||||
|
||||
switch (type) {
|
||||
case "markers":
|
||||
components.markers = {
|
||||
showLabels: component.showlabel || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "playermarkers":
|
||||
components.playerMarkers = {
|
||||
grayHiddenPlayers: response.grayplayerswhenhidden || false,
|
||||
hideByDefault: component.hidebydefault || false,
|
||||
layerName: component.label || "Players",
|
||||
layerPriority: component.layerprio || 0,
|
||||
showBodies: component.showplayerbody || false,
|
||||
showSkinFaces: component.showplayerfaces || false,
|
||||
showHealth: component.showplayerhealth || false,
|
||||
smallFaces: component.smallplayerfaces || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "coord":
|
||||
components.coordinatesControl = {
|
||||
showY: !(component.hidey || false),
|
||||
label: component.label || "Location: ",
|
||||
showRegion: component['show-mcr'] || false,
|
||||
showChunk: component['show-chunk'] || false,
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "link":
|
||||
components.linkControl = true;
|
||||
|
||||
break;
|
||||
|
||||
case "digitalclock":
|
||||
components.clockControl = {
|
||||
showDigitalClock: true,
|
||||
showWeather: false,
|
||||
showTimeOfDay: false,
|
||||
}
|
||||
break;
|
||||
|
||||
case "timeofdayclock":
|
||||
components.clockControl = {
|
||||
showTimeOfDay: true,
|
||||
showDigitalClock: component.showdigitalclock || false,
|
||||
showWeather: component.showweather || false,
|
||||
}
|
||||
break;
|
||||
|
||||
case "logo":
|
||||
components.logoControls.push({
|
||||
text: component.text || '',
|
||||
url: component.linkurl || undefined,
|
||||
position: component.position.replace('-', '') || 'topleft',
|
||||
image: component.logourl || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case "chat":
|
||||
if (response.allowwebchat) {
|
||||
components.chatSending = {
|
||||
loginRequired: response['webchat-requires-login'] || false,
|
||||
maxLength: response['chatlengthlimit'] || 256,
|
||||
cooldown: response['webchat-interval'] || 5,
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "chatbox":
|
||||
components.chatBox = {
|
||||
allowUrlName: component.allowurlname || false,
|
||||
showPlayerFaces: component.showplayerfaces || false,
|
||||
messageLifetime: component.messagettl || Infinity,
|
||||
messageHistory: component.scrollback || Infinity,
|
||||
}
|
||||
break;
|
||||
|
||||
case "chatballoon":
|
||||
components.chatBalloons = true;
|
||||
}
|
||||
});
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
private static buildMarkerSet(id: string, data: any): any {
|
||||
return {
|
||||
id,
|
||||
label: data.label || "Unnamed set",
|
||||
hidden: data.hide || false,
|
||||
priority: data.layerprio || 0,
|
||||
showLabels: data.showlabels || undefined,
|
||||
minZoom: typeof data.minzoom !== 'undefined' && data.minzoom > -1 ? data.minzoom : undefined,
|
||||
maxZoom: typeof data.maxzoom !== 'undefined' && data.maxzoom > -1 ? data.maxzoom : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
private static buildMarkers(data: any): Map<string, LiveAtlasMarker> {
|
||||
const markers = Object.freeze(new Map()) as Map<string, LiveAtlasMarker>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
markers.set(key, DynmapMapProvider.buildMarker(data[key]));
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
private static buildMarker(marker: any): LiveAtlasMarker {
|
||||
return {
|
||||
label: marker.label || '',
|
||||
isLabelHTML: marker.markup || false,
|
||||
location: {
|
||||
x: marker.x || 0,
|
||||
y: marker.y || 0,
|
||||
z: marker.z || 0,
|
||||
},
|
||||
dimensions: marker.dim ? marker.dim.split('x') : [16, 16],
|
||||
icon: marker.icon || "default",
|
||||
minZoom: typeof marker.minzoom !== 'undefined' && marker.minzoom > -1 ? marker.minzoom : undefined,
|
||||
maxZoom: typeof marker.maxzoom !== 'undefined' && marker.maxzoom > -1 ? marker.maxzoom : undefined,
|
||||
popupContent: marker.desc || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private static buildAreas(data: any): Map<string, LiveAtlasArea> {
|
||||
const areas = Object.freeze(new Map()) as Map<string, LiveAtlasArea>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
areas.set(key, DynmapMapProvider.buildArea(data[key]));
|
||||
}
|
||||
|
||||
return areas;
|
||||
}
|
||||
|
||||
private static buildArea(area: any): LiveAtlasArea {
|
||||
const opacity = area.fillopacity || 0,
|
||||
x = area.x || [0, 0],
|
||||
y: [number, number] = [area.ybottom || 0, area.ytop || 0],
|
||||
z = area.z || [0, 0];
|
||||
|
||||
return Object.seal({
|
||||
style: {
|
||||
color: area.color || '#ff0000',
|
||||
opacity: area.opacity || 1,
|
||||
weight: area.weight || 1,
|
||||
fillColor: area.fillcolor || '#ff0000',
|
||||
fillOpacity: area.fillopacity || 0,
|
||||
},
|
||||
outline: !opacity,
|
||||
points: getPoints(x, y, z, !opacity),
|
||||
minZoom: typeof area.minzoom !== 'undefined' && area.minzoom > -1 ? area.minzoom : undefined,
|
||||
maxZoom: typeof area.maxzoom !== 'undefined' && area.maxzoom > -1 ? area.maxzoom : undefined,
|
||||
|
||||
isPopupHTML: area.desc ? true : area.markup || false,
|
||||
popupContent: area.desc || area.label || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private static buildLines(data: any): Map<string, LiveAtlasLine> {
|
||||
const lines = Object.freeze(new Map()) as Map<string, LiveAtlasLine>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.set(key, DynmapMapProvider.buildLine(data[key]));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static buildLine(line: any): LiveAtlasLine {
|
||||
return Object.seal({
|
||||
style: {
|
||||
color: line.color || '#ff0000',
|
||||
opacity: line.opacity || 1,
|
||||
weight: line.weight || 1,
|
||||
},
|
||||
points: getLinePoints(line.x || [0, 0], line.y || [0, 0], line.z || [0, 0]),
|
||||
minZoom: typeof line.minzoom !== 'undefined' && line.minzoom > -1 ? line.minzoom : undefined,
|
||||
maxZoom: typeof line.maxzoom !== 'undefined' && line.maxzoom > -1 ? line.maxzoom : undefined,
|
||||
|
||||
isPopupHTML: line.desc ? true : line.markup || false,
|
||||
popupContent: line.desc || line.label || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private static buildCircles(data: any): Map<string, LiveAtlasCircle> {
|
||||
const circles = Object.freeze(new Map()) as Map<string, LiveAtlasCircle>;
|
||||
|
||||
for (const key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
circles.set(key, DynmapMapProvider.buildCircle(data[key]));
|
||||
}
|
||||
|
||||
return circles;
|
||||
}
|
||||
|
||||
private static buildCircle(circle: any): LiveAtlasCircle {
|
||||
return Object.seal({
|
||||
location: {
|
||||
x: circle.x || 0,
|
||||
y: circle.y || 0,
|
||||
z: circle.z || 0,
|
||||
},
|
||||
radius: [circle.xr || 0, circle.zr || 0],
|
||||
style: {
|
||||
fillColor: circle.fillcolor || '#ff0000',
|
||||
fillOpacity: circle.fillopacity || 0,
|
||||
color: circle.color || '#ff0000',
|
||||
opacity: circle.opacity || 1,
|
||||
weight: circle.weight || 1,
|
||||
},
|
||||
minZoom: typeof circle.minzoom !== 'undefined' && circle.minzoom > -1 ? circle.minzoom : undefined,
|
||||
maxZoom: typeof circle.maxzoom !== 'undefined' && circle.maxzoom > -1 ? circle.maxzoom : undefined,
|
||||
|
||||
isPopupHTML: circle.desc ? true : circle.markup || false,
|
||||
popupContent: circle.desc || circle.label || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private buildUpdates(data: Array<any>) {
|
||||
const updates = {
|
||||
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
|
||||
tiles: [] as DynmapTileUpdate[],
|
||||
chat: [] as LiveAtlasChat[],
|
||||
},
|
||||
dropped = {
|
||||
stale: 0,
|
||||
noSet: 0,
|
||||
noId: 0,
|
||||
unknownType: 0,
|
||||
unknownCType: 0,
|
||||
incomplete: 0,
|
||||
notImplemented: 0,
|
||||
},
|
||||
lastUpdate = this.updateTimestamp;
|
||||
|
||||
let accepted = 0;
|
||||
|
||||
for (const entry of data) {
|
||||
switch (entry.type) {
|
||||
case 'component': {
|
||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.id) {
|
||||
dropped.noId++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Set updates don't have a set field, the id is the set
|
||||
const set = entry.msg.startsWith("set") ? entry.id : entry.set;
|
||||
|
||||
if (!set) {
|
||||
dropped.noSet++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.ctype !== 'markers') {
|
||||
dropped.unknownCType++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!updates.markerSets.has(set)) {
|
||||
updates.markerSets.set(set, {
|
||||
areaUpdates: [],
|
||||
markerUpdates: [],
|
||||
lineUpdates: [],
|
||||
circleUpdates: [],
|
||||
removed: false,
|
||||
});
|
||||
}
|
||||
|
||||
const markerSetUpdates = updates.markerSets.get(set),
|
||||
update: DynmapUpdate = {
|
||||
id: entry.id,
|
||||
removed: entry.msg.endsWith('deleted'),
|
||||
};
|
||||
|
||||
if (entry.msg.startsWith("set")) {
|
||||
markerSetUpdates!.removed = update.removed;
|
||||
markerSetUpdates!.payload = update.removed ? undefined : DynmapMapProvider.buildMarkerSet(set, entry);
|
||||
} else if (entry.msg.startsWith("marker")) {
|
||||
update.payload = update.removed ? undefined : DynmapMapProvider.buildMarker(entry);
|
||||
markerSetUpdates!.markerUpdates.push(Object.freeze(update));
|
||||
} else if (entry.msg.startsWith("area")) {
|
||||
update.payload = update.removed ? undefined : DynmapMapProvider.buildArea(entry);
|
||||
markerSetUpdates!.areaUpdates.push(Object.freeze(update));
|
||||
|
||||
} else if (entry.msg.startsWith("circle")) {
|
||||
update.payload = update.removed ? undefined : DynmapMapProvider.buildCircle(entry);
|
||||
markerSetUpdates!.circleUpdates.push(Object.freeze(update));
|
||||
|
||||
} else if (entry.msg.startsWith("line")) {
|
||||
update.payload = update.removed ? undefined : DynmapMapProvider.buildLine(entry);
|
||||
markerSetUpdates!.lineUpdates.push(Object.freeze(update));
|
||||
}
|
||||
|
||||
accepted++;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'chat':
|
||||
if (!entry.message || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.source !== 'player' && entry.source !== 'web') {
|
||||
dropped.notImplemented++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'chat',
|
||||
source: entry.source || undefined,
|
||||
playerAccount: entry.account || undefined,
|
||||
playerName: entry.playerName || undefined,
|
||||
message: entry.message || "",
|
||||
timestamp: entry.timestamp,
|
||||
channel: entry.channel || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'playerjoin':
|
||||
if (!entry.account || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'playerjoin',
|
||||
playerAccount: entry.account,
|
||||
playerName: entry.playerName || "",
|
||||
timestamp: entry.timestamp || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'playerquit':
|
||||
if (!entry.account || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.chat.push({
|
||||
type: 'playerleave',
|
||||
playerAccount: entry.account,
|
||||
playerName: entry.playerName || "",
|
||||
timestamp: entry.timestamp || undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'tile':
|
||||
if (!entry.name || !entry.timestamp) {
|
||||
dropped.incomplete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||
dropped.stale++;
|
||||
continue;
|
||||
}
|
||||
|
||||
updates.tiles.push({
|
||||
name: entry.name,
|
||||
timestamp: entry.timestamp,
|
||||
});
|
||||
|
||||
accepted++;
|
||||
break;
|
||||
|
||||
default:
|
||||
dropped.unknownType++;
|
||||
}
|
||||
}
|
||||
|
||||
//Sort chat by newest first
|
||||
updates.chat = updates.chat.sort((one, two) => {
|
||||
return two.timestamp - one.timestamp;
|
||||
});
|
||||
|
||||
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<Map<string, LiveAtlasMarkerSet>> {
|
||||
const url = `${this.config.dynmap!.markers}_markers_/marker_${world.name}.json`;
|
||||
|
||||
if(this.markersAbort) {
|
||||
this.markersAbort.abort();
|
||||
}
|
||||
|
||||
this.markersAbort = new AbortController();
|
||||
|
||||
const response = await DynmapMapProvider.fetchJSON(url, this.markersAbort.signal);
|
||||
const sets: Map<string, LiveAtlasMarkerSet> = new Map();
|
||||
|
||||
response.sets = response.sets || {};
|
||||
|
||||
for (const key in response.sets) {
|
||||
if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const set = response.sets[key],
|
||||
markers = DynmapMapProvider.buildMarkers(set.markers || {}),
|
||||
circles = DynmapMapProvider.buildCircles(set.circles || {}),
|
||||
areas = DynmapMapProvider.buildAreas(set.areas || {}),
|
||||
lines = DynmapMapProvider.buildLines(set.lines || {});
|
||||
|
||||
sets.set(key, {
|
||||
...DynmapMapProvider.buildMarkerSet(key, set),
|
||||
markers,
|
||||
circles,
|
||||
areas,
|
||||
lines,
|
||||
});
|
||||
}
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
async loadServerConfiguration(): Promise<void> {
|
||||
if(this.configurationAbort) {
|
||||
this.configurationAbort.abort();
|
||||
}
|
||||
|
||||
this.configurationAbort = new AbortController();
|
||||
|
||||
const response = await DynmapMapProvider.fetchJSON(this.config.dynmap!.configuration, this.configurationAbort.signal);
|
||||
|
||||
if (response.error === 'login-required') {
|
||||
throw new Error("Login required");
|
||||
} else if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
const config = DynmapMapProvider.buildServerConfig(response);
|
||||
|
||||
this.updateInterval = response.updaterate || 3000;
|
||||
|
||||
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION, config);
|
||||
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, response.confighash || 0);
|
||||
this.store.commit(MutationTypes.SET_MAX_PLAYERS, response.maxcount || 0);
|
||||
this.store.commit(MutationTypes.SET_SERVER_MESSAGES, DynmapMapProvider.buildMessagesConfig(response));
|
||||
this.store.commit(MutationTypes.SET_WORLDS, this.buildWorlds(response));
|
||||
this.store.commit(MutationTypes.SET_COMPONENTS, this.buildComponents(response));
|
||||
this.store.commit(MutationTypes.SET_LOGGED_IN, response.loggedin || false);
|
||||
}
|
||||
|
||||
async populateWorld(world: LiveAtlasWorldDefinition): Promise<void> {
|
||||
const markerSets = await this.getMarkerSets(world);
|
||||
|
||||
useStore().commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
||||
}
|
||||
|
||||
private async getUpdate(): Promise<void> {
|
||||
let url = this.config.dynmap!.update;
|
||||
url = url.replace('{world}', this.store.state.currentWorld!.name);
|
||||
url = url.replace('{timestamp}', this.updateTimestamp.getTime().toString());
|
||||
|
||||
if(this.updateAbort) {
|
||||
this.updateAbort.abort();
|
||||
}
|
||||
|
||||
this.updateAbort = new AbortController();
|
||||
|
||||
const response = await DynmapMapProvider.fetchJSON(url, this.updateAbort.signal);
|
||||
const players: Set<LiveAtlasPlayer> = new Set(),
|
||||
updates = this.buildUpdates(response.updates || []),
|
||||
worldState = {
|
||||
timeOfDay: response.servertime || 0,
|
||||
thundering: response.isThundering || false,
|
||||
raining: response.hasStorm || false,
|
||||
};
|
||||
|
||||
(response.players || []).forEach((player: any) => {
|
||||
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
|
||||
|
||||
players.add({
|
||||
name: player.account || "",
|
||||
displayName: player.name || "",
|
||||
health: player.health || 0,
|
||||
armor: player.armor || 0,
|
||||
sort: player.sort || 0,
|
||||
hidden: !world,
|
||||
location: {
|
||||
//Add 0.5 to position in the middle of a block
|
||||
x: !isNaN(player.x) ? player.x + 0.5 : 0,
|
||||
y: !isNaN(player.y) ? player.y : 0,
|
||||
z: !isNaN(player.z) ? player.z + 0.5 : 0,
|
||||
world: world,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//Extra fake players for testing
|
||||
// for(let i = 0; i < 450; i++) {
|
||||
// players.add({
|
||||
// account: "VIDEO GAMES " + i,
|
||||
// health: Math.round(Math.random() * 10),
|
||||
// armor: Math.round(Math.random() * 10),
|
||||
// name: "VIDEO GAMES " + i,
|
||||
// sort: Math.round(Math.random() * 10),
|
||||
// hidden: false,
|
||||
// location: {
|
||||
// x: Math.round(Math.random() * 1000) - 500,
|
||||
// y: 64,
|
||||
// z: Math.round(Math.random() * 1000) - 500,
|
||||
// world: "world",
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
this.updateTimestamp = new Date(response.timestamp || 0);
|
||||
|
||||
this.store.commit(MutationTypes.SET_WORLD_STATE, worldState);
|
||||
this.store.commit(MutationTypes.ADD_MARKER_SET_UPDATES, updates.markerSets);
|
||||
this.store.commit(MutationTypes.ADD_TILE_UPDATES, updates.tiles);
|
||||
this.store.commit(MutationTypes.ADD_CHAT, updates.chat);
|
||||
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, response.confighash || 0);
|
||||
|
||||
await this.store.dispatch(ActionTypes.SET_PLAYERS, players);
|
||||
}
|
||||
|
||||
sendChatMessage(message: string) {
|
||||
const store = useStore();
|
||||
|
||||
if (!store.state.components.chatSending) {
|
||||
return Promise.reject(store.state.messages.chatErrorDisabled);
|
||||
}
|
||||
|
||||
return fetch(this.config.dynmap!.sendmessage, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: null,
|
||||
message: message,
|
||||
})
|
||||
}).then((response) => {
|
||||
if (response.status === 403) { //Rate limited
|
||||
throw new ChatError(store.state.messages.chatErrorCooldown
|
||||
.replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network request failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then(response => {
|
||||
if (response.error !== 'none') {
|
||||
throw new ChatError(store.state.messages.chatErrorNotAllowed);
|
||||
}
|
||||
}).catch(e => {
|
||||
if (!(e instanceof ChatError)) {
|
||||
console.error(store.state.messages.chatErrorUnknown);
|
||||
console.trace(e);
|
||||
}
|
||||
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
startUpdates() {
|
||||
this.updatesEnabled = true;
|
||||
this.update();
|
||||
}
|
||||
|
||||
private async update() {
|
||||
try {
|
||||
await this.getUpdate();
|
||||
} finally {
|
||||
if(this.updatesEnabled) {
|
||||
if(this.updateTimeout) {
|
||||
clearTimeout(this.updateTimeout);
|
||||
}
|
||||
|
||||
this.updateTimeout = setTimeout(() => this.update(), this.updateInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopUpdates() {
|
||||
this.updatesEnabled = false;
|
||||
|
||||
if (this.updateTimeout) {
|
||||
clearTimeout(this.updateTimeout);
|
||||
}
|
||||
|
||||
this.updateTimeout = 0;
|
||||
}
|
||||
|
||||
getTilesUrl(): string {
|
||||
return this.config.dynmap!.tiles;
|
||||
}
|
||||
|
||||
getPlayerHeadUrl(head: HeadQueueEntry): string {
|
||||
const icon = (head.size === 'body') ? `faces/body/${head.name}.png` :`faces/${head.size}x${head.size}/${head.name}.png`
|
||||
|
||||
return this.getMarkerIconUrl(icon);
|
||||
}
|
||||
|
||||
getMarkerIconUrl(icon: string): string {
|
||||
return `${this.config.dynmap!.markers}_markers_/${icon}.png`;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
|
||||
if(this.configurationAbort) {
|
||||
this.configurationAbort.abort();
|
||||
}
|
||||
|
||||
if(this.updateAbort) {
|
||||
this.updateAbort.abort();
|
||||
}
|
||||
|
||||
if(this.markersAbort) {
|
||||
this.markersAbort.abort();
|
||||
}
|
||||
}
|
||||
}
|
91
src/providers/MapProvider.ts
Normal file
91
src/providers/MapProvider.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
HeadQueueEntry,
|
||||
LiveAtlasMapProvider,
|
||||
LiveAtlasServerDefinition,
|
||||
LiveAtlasWorldDefinition
|
||||
} from "@/index";
|
||||
import {useStore} from "@/store";
|
||||
import {computed, watch} from "@vue/runtime-core";
|
||||
import {WatchStopHandle} from "vue";
|
||||
|
||||
export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||
protected readonly store = useStore();
|
||||
protected readonly config: LiveAtlasServerDefinition;
|
||||
private readonly currentWorldUnwatch: WatchStopHandle;
|
||||
|
||||
protected constructor(config: LiveAtlasServerDefinition) {
|
||||
this.config = config;
|
||||
const currentWorld = computed(() => this.store.state.currentWorld);
|
||||
|
||||
this.currentWorldUnwatch = watch(currentWorld, (newValue) => {
|
||||
if (newValue) {
|
||||
this.populateWorld(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abstract loadServerConfiguration(): Promise<void>;
|
||||
abstract populateWorld(world: LiveAtlasWorldDefinition): Promise<void>;
|
||||
abstract sendChatMessage(message: string): void;
|
||||
|
||||
abstract startUpdates(): void;
|
||||
abstract stopUpdates(): void;
|
||||
|
||||
abstract getPlayerHeadUrl(head: HeadQueueEntry): string;
|
||||
abstract getTilesUrl(): string;
|
||||
abstract getMarkerIconUrl(icon: string): string;
|
||||
|
||||
destroy() {
|
||||
this.currentWorldUnwatch();
|
||||
}
|
||||
|
||||
protected static async fetchJSON(url: string, signal: AbortSignal) {
|
||||
let response, json;
|
||||
|
||||
try {
|
||||
response = await fetch(url, {signal});
|
||||
} catch(e) {
|
||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.warn(`Request aborted (${url}`);
|
||||
throw e;
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
throw new Error(`Network request failed`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network request failed (${response.statusText || 'Unknown'})`);
|
||||
}
|
||||
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch(e) {
|
||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||
console.warn(`Request aborted (${url}`);
|
||||
throw e;
|
||||
} else {
|
||||
throw new Error('Request returned invalid json');
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
520
src/providers/Pl3xmapMapProvider.ts
Normal file
520
src/providers/Pl3xmapMapProvider.ts
Normal file
@ -0,0 +1,520 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
HeadQueueEntry, LiveAtlasArea, LiveAtlasCircle, LiveAtlasComponentConfig,
|
||||
LiveAtlasDimension, LiveAtlasLine, LiveAtlasMarker,
|
||||
LiveAtlasMarkerSet, LiveAtlasPartialComponentConfig,
|
||||
LiveAtlasPlayer, LiveAtlasServerConfig, LiveAtlasServerDefinition,
|
||||
LiveAtlasServerMessageConfig,
|
||||
LiveAtlasWorldDefinition
|
||||
} from "@/index";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import MapProvider from "@/providers/MapProvider";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {titleColoursRegex} from "@/util";
|
||||
|
||||
export default class Pl3xmapMapProvider extends MapProvider {
|
||||
private configurationAbort?: AbortController = undefined;
|
||||
private markersAbort?: AbortController = undefined;
|
||||
private playersAbort?: AbortController = undefined;
|
||||
|
||||
private updatesEnabled = false;
|
||||
private updateTimeout: number = 0;
|
||||
private updateTimestamp: Date = new Date();
|
||||
private updateInterval: number = 3000;
|
||||
private worldSettings: Map<string, {
|
||||
components: LiveAtlasPartialComponentConfig,
|
||||
}> = new Map();
|
||||
|
||||
constructor(config: LiveAtlasServerDefinition) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
private static buildServerConfig(response: any): LiveAtlasServerConfig {
|
||||
return {
|
||||
title: (response.ui?.title || 'Pl3xmap').replace(titleColoursRegex, ''),
|
||||
expandUI: response.ui?.sidebar?.pinned === 'pinned',
|
||||
|
||||
//Not used by pl3xmap
|
||||
defaultZoom: 1,
|
||||
defaultMap: undefined,
|
||||
defaultWorld: undefined,
|
||||
followMap: undefined,
|
||||
followZoom: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private static buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
||||
return {
|
||||
worldsHeading: response.ui?.sidebar?.world_list_label || '',
|
||||
playersHeading: response.ui?.sidebar?.player_list_label || '',
|
||||
|
||||
//Not used by pl3xmap
|
||||
chatPlayerJoin: '',
|
||||
chatPlayerQuit: '',
|
||||
chatAnonymousJoin: '',
|
||||
chatAnonymousQuit: '',
|
||||
chatErrorNotAllowed: '',
|
||||
chatErrorRequiresLogin: '',
|
||||
chatErrorCooldown: '',
|
||||
}
|
||||
}
|
||||
|
||||
private buildWorlds(serverResponse: any, worldResponses: any[]): Array<LiveAtlasWorldDefinition> {
|
||||
const worlds: Array<LiveAtlasWorldDefinition> = [];
|
||||
|
||||
(serverResponse.worlds || []).filter((w: any) => w && !!w.name).forEach((world: any, index: number) => {
|
||||
const worldResponse = worldResponses[index],
|
||||
worldConfig: {components: LiveAtlasPartialComponentConfig } = {
|
||||
components: {},
|
||||
};
|
||||
|
||||
if(worldResponse.player_tracker?.enabled) {
|
||||
worldConfig.components.playerMarkers = {
|
||||
grayHiddenPlayers: true,
|
||||
hideByDefault: !!worldResponse.player_tracker?.default_hidden,
|
||||
layerName: worldResponse.player_tracker?.label || '',
|
||||
layerPriority: worldResponse.player_tracker?.priority,
|
||||
showBodies: false,
|
||||
showSkinFaces: true,
|
||||
showHealth: !!worldResponse.player_tracker?.nameplates?.show_health,
|
||||
smallFaces: true,
|
||||
}
|
||||
}
|
||||
|
||||
this.worldSettings.set(world.name, worldConfig);
|
||||
|
||||
if(!worldResponse) {
|
||||
console.warn(`World ${world.name} has no matching world config. Ignoring.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let dimension: LiveAtlasDimension = 'overworld';
|
||||
|
||||
if(world.type === 'nether') {
|
||||
dimension = 'nether';
|
||||
} else if(world.type === 'the_end') {
|
||||
dimension = 'nether';
|
||||
}
|
||||
|
||||
const maps: Map<string, LiveAtlasMapDefinition> = new Map();
|
||||
|
||||
maps.set('flat', Object.freeze(new LiveAtlasMapDefinition({
|
||||
world: world,
|
||||
|
||||
background: 'transparent',
|
||||
backgroundDay: 'transparent',
|
||||
backgroundNight: 'transparent',
|
||||
icon: undefined,
|
||||
imageFormat: 'png',
|
||||
name: 'flat',
|
||||
displayName: 'Flat',
|
||||
|
||||
nativeZoomLevels: worldResponse.zoom.max || 1,
|
||||
extraZoomLevels: worldResponse.zoom.extra || 0,
|
||||
})));
|
||||
|
||||
worlds.push({
|
||||
name: world.name || '(Unnamed world)',
|
||||
displayName: world.display_name || world.name,
|
||||
dimension,
|
||||
protected: false,
|
||||
seaLevel: 0,
|
||||
height: 256,
|
||||
center: {x: worldResponse.spawn.x, y: 0, z: worldResponse.spawn.z},
|
||||
maps,
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(worlds.values());
|
||||
}
|
||||
|
||||
private static buildComponents(response: any): LiveAtlasComponentConfig {
|
||||
const components: LiveAtlasComponentConfig = {
|
||||
markers: {
|
||||
showLabels: false,
|
||||
},
|
||||
coordinatesControl: undefined,
|
||||
linkControl: !!response.ui?.link?.enabled,
|
||||
layerControl: !!response.ui?.coordinates?.enabled,
|
||||
|
||||
//Configured per-world
|
||||
playerMarkers: undefined,
|
||||
|
||||
//Not used by pl3xmap
|
||||
chatBox: undefined,
|
||||
chatBalloons: false,
|
||||
clockControl: undefined,
|
||||
logoControls: [],
|
||||
login: false,
|
||||
};
|
||||
|
||||
if(response.ui?.coordinates?.enabled) {
|
||||
//Try to remove {x}/{z} placeholders are we aren't using them
|
||||
const label = (response.ui?.coordinates?.html || "Location: ").replace(/{x}.*{z}/gi, '').trim(),
|
||||
labelPlain = new DOMParser().parseFromString(label, 'text/html').body.textContent || "";
|
||||
|
||||
components.coordinatesControl = {
|
||||
showY: false,
|
||||
label: labelPlain,
|
||||
showRegion: false,
|
||||
showChunk: false,
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<Map<string, LiveAtlasMarkerSet>> {
|
||||
const url = `${this.config.pl3xmap}tiles/${world.name}/markers.json`;
|
||||
|
||||
if(this.markersAbort) {
|
||||
this.markersAbort.abort();
|
||||
}
|
||||
|
||||
this.markersAbort = new AbortController();
|
||||
|
||||
const response = await Pl3xmapMapProvider.fetchJSON(url, this.markersAbort.signal);
|
||||
const sets: Map<string, LiveAtlasMarkerSet> = new Map();
|
||||
|
||||
if(!Array.isArray(response)) {
|
||||
return sets;
|
||||
}
|
||||
|
||||
response.forEach(set => {
|
||||
if(!set || !set.id) {
|
||||
console.warn('Ignoring marker set without id');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = set.id;
|
||||
|
||||
const markers: Map<string, LiveAtlasMarker> = new Map(),
|
||||
circles: Map<string, LiveAtlasCircle> = new Map(),
|
||||
areas: Map<string, LiveAtlasArea> = new Map(),
|
||||
lines: Map<string, LiveAtlasLine> = new Map();
|
||||
|
||||
(set.markers || []).forEach((marker: any) => {
|
||||
switch(marker.type) {
|
||||
case 'icon':
|
||||
markers.set(`marker-${markers.size}`, Pl3xmapMapProvider.buildMarker(marker));
|
||||
break;
|
||||
|
||||
case 'polyline':
|
||||
lines.set(`line-${lines.size}`, Pl3xmapMapProvider.buildLine(marker));
|
||||
break;
|
||||
|
||||
case 'rectangle':
|
||||
areas.set(`area-${areas.size}`, Pl3xmapMapProvider.buildRectangle(marker));
|
||||
break;
|
||||
|
||||
case 'polygon':
|
||||
areas.set(`area-${areas.size}`, Pl3xmapMapProvider.buildArea(marker));
|
||||
break;
|
||||
|
||||
case 'circle':
|
||||
case 'ellipse':
|
||||
circles.set(`circle-${circles.size}`, Pl3xmapMapProvider.buildCircle(marker));
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Marker type ' + marker.type + ' not supported');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const e = {
|
||||
id,
|
||||
label: set.name || "Unnamed set",
|
||||
hidden: set.hide || false,
|
||||
priority: set.order || 0,
|
||||
showLabels: false,
|
||||
markers,
|
||||
circles,
|
||||
areas,
|
||||
lines,
|
||||
};
|
||||
|
||||
sets.set(id, e);
|
||||
});
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
private static buildMarker(marker: any): LiveAtlasMarker {
|
||||
return {
|
||||
location: {
|
||||
x: marker.point?.x || 0,
|
||||
y: 0,
|
||||
z: marker.point?.z || 0,
|
||||
},
|
||||
dimensions: marker.size ? [marker.size.x || 16, marker.size.z || 16] : [16, 16],
|
||||
icon: marker.icon || "default",
|
||||
|
||||
label: (marker.tooltip || '').trim(),
|
||||
isLabelHTML: true
|
||||
};
|
||||
}
|
||||
|
||||
private static buildRectangle(area: any): LiveAtlasArea {
|
||||
return Object.seal({
|
||||
style: {
|
||||
stroke: typeof area.stroke !== 'undefined' ? !!area.stroke : true,
|
||||
color: area.color || '#3388ff',
|
||||
weight: area.weight || 3,
|
||||
opacity: typeof area.opacity !== 'undefined' ? area.opacity : 1,
|
||||
fill: typeof area.stroke !== 'undefined' ? !!area.stroke : true,
|
||||
fillColor: area.fillColor || area.color || '#3388ff',
|
||||
fillOpacity: area.fillOpacity || 0.2,
|
||||
fillRule: area.fillRule,
|
||||
},
|
||||
points: [
|
||||
area.points[0],
|
||||
{x: area.points[0].x, z: area.points[1].z},
|
||||
area.points[1],
|
||||
{x: area.points[1].x, z: area.points[0].z},
|
||||
],
|
||||
outline: false,
|
||||
|
||||
tooltipContent: area.tooltip,
|
||||
popupContent: area.popup,
|
||||
isPopupHTML: true,
|
||||
});
|
||||
}
|
||||
|
||||
private static buildArea(area: any): LiveAtlasArea {
|
||||
return Object.seal({
|
||||
style: {
|
||||
stroke: typeof area.stroke !== 'undefined' ? !!area.stroke : true,
|
||||
color: area.color || '#3388ff',
|
||||
weight: area.weight || 3,
|
||||
opacity: typeof area.opacity !== 'undefined' ? area.opacity : 1,
|
||||
fill: typeof area.fill !== 'undefined' ? !!area.fill : true,
|
||||
fillColor: area.fillColor || area.color || '#3388ff',
|
||||
fillOpacity: area.fillOpacity || 0.2,
|
||||
fillRule: area.fillRule,
|
||||
},
|
||||
points: area.points,
|
||||
outline: false,
|
||||
|
||||
tooltipContent: area.tooltip,
|
||||
popupContent: area.popup,
|
||||
isPopupHTML: true,
|
||||
});
|
||||
}
|
||||
|
||||
private static buildLine(line: any): LiveAtlasLine {
|
||||
return Object.seal({
|
||||
style: {
|
||||
stroke: typeof line.stroke !== 'undefined' ? !!line.stroke : true,
|
||||
color: line.color || '#3388ff',
|
||||
weight: line.weight || 3,
|
||||
opacity: typeof line.opacity !== 'undefined' ? line.opacity : 1,
|
||||
},
|
||||
points: line.points,
|
||||
|
||||
tooltipContent: line.tooltip,
|
||||
popupContent: line.popup,
|
||||
isPopupHTML: true,
|
||||
});
|
||||
}
|
||||
|
||||
private static buildCircle(circle: any): LiveAtlasCircle {
|
||||
return Object.seal({
|
||||
location: {
|
||||
x: circle.center?.x || 0,
|
||||
y: 0,
|
||||
z: circle.center?.z || 0,
|
||||
},
|
||||
radius: [circle.radiusX || circle.radius || 0, circle.radiusZ || circle.radius || 0],
|
||||
style: {
|
||||
stroke: typeof circle.stroke !== 'undefined' ? !!circle.stroke : true,
|
||||
color: circle.color || '#3388ff',
|
||||
weight: circle.weight || 3,
|
||||
opacity: typeof circle.opacity !== 'undefined' ? circle.opacity : 1,
|
||||
fill: typeof circle.stroke !== 'undefined' ? !!circle.stroke : true,
|
||||
fillColor: circle.fillColor || circle.color || '#3388ff',
|
||||
fillOpacity: circle.fillOpacity || 0.2,
|
||||
fillRule: circle.fillRule,
|
||||
},
|
||||
|
||||
tooltipContent: circle.tooltip,
|
||||
popupContent: circle.popup,
|
||||
isPopupHTML: true
|
||||
});
|
||||
}
|
||||
|
||||
async loadServerConfiguration(): Promise<void> {
|
||||
if(this.configurationAbort) {
|
||||
this.configurationAbort.abort();
|
||||
}
|
||||
|
||||
this.configurationAbort = new AbortController();
|
||||
|
||||
const baseUrl = this.config.pl3xmap,
|
||||
response = await Pl3xmapMapProvider.fetchJSON(`${baseUrl}tiles/settings.json`, this.configurationAbort.signal);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
const config = Pl3xmapMapProvider.buildServerConfig(response),
|
||||
worldNames: string[] = (response.worlds || []).filter((world: any) => world && !!world.name)
|
||||
.map((world: any) => world.name);
|
||||
|
||||
const worldResponses = await Promise.all(worldNames.map(name =>
|
||||
Pl3xmapMapProvider.fetchJSON(`${baseUrl}tiles/${name}/settings.json`, this.configurationAbort!.signal)));
|
||||
|
||||
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION, config);
|
||||
this.store.commit(MutationTypes.SET_SERVER_MESSAGES, Pl3xmapMapProvider.buildMessagesConfig(response));
|
||||
this.store.commit(MutationTypes.SET_WORLDS, this.buildWorlds(response, worldResponses));
|
||||
this.store.commit(MutationTypes.SET_COMPONENTS, Pl3xmapMapProvider.buildComponents(response));
|
||||
|
||||
//Pl3xmap has no login functionality
|
||||
this.store.commit(MutationTypes.SET_LOGGED_IN, false);
|
||||
}
|
||||
|
||||
async populateWorld(world: LiveAtlasWorldDefinition) {
|
||||
const markerSets = await this.getMarkerSets(world),
|
||||
worldConfig = this.worldSettings.get(world.name);
|
||||
|
||||
this.store.commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
||||
this.store.commit(MutationTypes.SET_COMPONENTS, worldConfig!.components);
|
||||
}
|
||||
|
||||
private async getPlayers(): Promise<Set<LiveAtlasPlayer>> {
|
||||
const url = `${this.config.pl3xmap}/tiles/players.json`;
|
||||
|
||||
if(this.playersAbort) {
|
||||
this.playersAbort.abort();
|
||||
}
|
||||
|
||||
this.playersAbort = new AbortController();
|
||||
|
||||
const response = await Pl3xmapMapProvider.fetchJSON(url, this.playersAbort.signal),
|
||||
players: Set<LiveAtlasPlayer> = new Set();
|
||||
|
||||
(response.players || []).forEach((player: any) => {
|
||||
console.log(player.uuid);
|
||||
players.add({
|
||||
name: (player.name || '').toLowerCase(),
|
||||
uuid: player.uuid,
|
||||
displayName: player.name || "",
|
||||
health: player.health || 0,
|
||||
armor: player.armor || 0,
|
||||
sort: 0,
|
||||
hidden: false,
|
||||
location: {
|
||||
//Add 0.5 to position in the middle of a block
|
||||
x: !isNaN(player.x) ? player.x + 0.5 : 0,
|
||||
y: 0,
|
||||
z: !isNaN(player.z) ? player.z + 0.5 : 0,
|
||||
world: player.world,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Extra fake players for testing
|
||||
// for(let i = 0; i < 450; i++) {
|
||||
// players.add({
|
||||
// name: "VIDEO GAMES " + i,
|
||||
// displayName: "VIDEO GAMES " + i,
|
||||
// health: Math.round(Math.random() * 10),
|
||||
// armor: Math.round(Math.random() * 10),
|
||||
// sort: Math.round(Math.random() * 10),
|
||||
// hidden: false,
|
||||
// location: {
|
||||
// x: Math.round(Math.random() * 1000) - 500,
|
||||
// y: 0,
|
||||
// z: Math.round(Math.random() * 1000) - 500,
|
||||
// world: "world",
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
this.store.commit(MutationTypes.SET_MAX_PLAYERS, response.max || 0);
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
sendChatMessage(message: string) {
|
||||
throw new Error('Pl3xmap does not support chat');
|
||||
}
|
||||
|
||||
startUpdates() {
|
||||
this.updatesEnabled = true;
|
||||
this.update();
|
||||
}
|
||||
|
||||
private async update() {
|
||||
try {
|
||||
const players = await this.getPlayers();
|
||||
|
||||
this.updateTimestamp = new Date();
|
||||
|
||||
await this.store.dispatch(ActionTypes.SET_PLAYERS, players);
|
||||
} finally {
|
||||
if(this.updatesEnabled) {
|
||||
if(this.updateTimeout) {
|
||||
clearTimeout(this.updateTimeout);
|
||||
}
|
||||
|
||||
this.updateTimeout = setTimeout(() => this.update(), this.updateInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopUpdates() {
|
||||
this.updatesEnabled = false;
|
||||
|
||||
if (this.updateTimeout) {
|
||||
clearTimeout(this.updateTimeout);
|
||||
}
|
||||
|
||||
this.updateTimeout = 0;
|
||||
}
|
||||
|
||||
getTilesUrl(): string {
|
||||
return `${this.config.pl3xmap}tiles/`;
|
||||
}
|
||||
|
||||
getPlayerHeadUrl(head: HeadQueueEntry): string {
|
||||
//TODO: Listen to config
|
||||
return 'https://mc-heads.net/avatar/{uuid}/16'.replace('{uuid}', head.uuid || '');
|
||||
}
|
||||
|
||||
getMarkerIconUrl(icon: string): string {
|
||||
return `${this.config.pl3xmap}images/icon/registered/${icon}.png`;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
|
||||
if(this.configurationAbort) {
|
||||
this.configurationAbort.abort();
|
||||
}
|
||||
|
||||
if(this.playersAbort) {
|
||||
this.playersAbort.abort();
|
||||
}
|
||||
|
||||
if(this.markersAbort) {
|
||||
this.markersAbort.abort();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,22 @@
|
||||
/*!
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@import "leaflet/controls";
|
||||
@import "leaflet/popups";
|
||||
@import "leaflet/tooltips";
|
||||
@import "leaflet/markers";
|
||||
|
||||
.leaflet-container {
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*!
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* Mixin for resetting :focus styles on browsers supporting :focus-visible */
|
||||
@mixin focus-reset() {
|
||||
&:focus:not(:focus-visible) {
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*!
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.vue-notification-group {
|
||||
z-index: 130 !important;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*!
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*******************
|
||||
* players on the map
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
28
src/scss/leaflet/_tooltips.scss
Normal file
28
src/scss/leaflet/_tooltips.scss
Normal file
@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.leaflet-tooltip {
|
||||
background-color: var(--background-base);
|
||||
color: var(--text-base);
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: var(--border-radius);
|
||||
border: none;
|
||||
will-change: transform;
|
||||
|
||||
&:before {
|
||||
content: none;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,8 +16,8 @@
|
||||
|
||||
export enum ActionTypes {
|
||||
LOAD_CONFIGURATION = "loadConfiguration",
|
||||
GET_UPDATE = "getUpdate",
|
||||
GET_MARKER_SETS = "getMarkerSets",
|
||||
START_UPDATES = "startUpdates",
|
||||
STOP_UPDATES = "stopUpdates",
|
||||
SET_PLAYERS = "setPlayers",
|
||||
POP_MARKER_UPDATES = "popMarkerUpdates",
|
||||
POP_AREA_UPDATES = "popAreaUpdates",
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,15 +20,11 @@ import {State} from "@/store/state";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import {Mutations} from "@/store/mutations";
|
||||
import {
|
||||
DynmapAreaUpdate, DynmapCircleUpdate,
|
||||
DynmapConfigurationResponse, DynmapLineUpdate,
|
||||
DynmapMarkerSet,
|
||||
DynmapAreaUpdate, DynmapCircleUpdate, DynmapLineUpdate,
|
||||
DynmapMarkerUpdate,
|
||||
DynmapPlayer, DynmapTileUpdate,
|
||||
DynmapUpdateResponse
|
||||
DynmapTileUpdate,
|
||||
} from "@/dynmap";
|
||||
import {getAPI} from "@/util";
|
||||
import {LiveAtlasWorldDefinition} from "@/index";
|
||||
import {LiveAtlasMarkerSet, LiveAtlasPlayer, LiveAtlasWorldDefinition} from "@/index";
|
||||
|
||||
type AugmentedActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
@ -40,17 +36,17 @@ type AugmentedActionContext = {
|
||||
export interface Actions {
|
||||
[ActionTypes.LOAD_CONFIGURATION](
|
||||
{commit}: AugmentedActionContext,
|
||||
):Promise<DynmapConfigurationResponse>
|
||||
[ActionTypes.GET_UPDATE](
|
||||
):Promise<void>
|
||||
[ActionTypes.START_UPDATES](
|
||||
{commit}: AugmentedActionContext,
|
||||
):Promise<DynmapUpdateResponse>
|
||||
[ActionTypes.GET_MARKER_SETS](
|
||||
):Promise<void>
|
||||
[ActionTypes.STOP_UPDATES](
|
||||
{commit}: AugmentedActionContext,
|
||||
):Promise<Map<string, DynmapMarkerSet>>
|
||||
):Promise<void>
|
||||
[ActionTypes.SET_PLAYERS](
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: Set<DynmapPlayer>
|
||||
):Promise<Map<string, DynmapMarkerSet>>
|
||||
payload: Set<LiveAtlasPlayer>
|
||||
):Promise<Map<string, LiveAtlasMarkerSet>>
|
||||
[ActionTypes.POP_MARKER_UPDATES](
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: {markerSet: string, amount: number}
|
||||
@ -78,21 +74,20 @@ export interface Actions {
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, State> & Actions = {
|
||||
async [ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<DynmapConfigurationResponse> {
|
||||
async [ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<void> {
|
||||
//Clear any existing has to avoid triggering a second config load, after this load changes the hash
|
||||
commit(MutationTypes.CLEAR_SERVER_CONFIGURATION_HASH, undefined);
|
||||
|
||||
const config = await getAPI().getConfiguration();
|
||||
if(!state.currentServer) {
|
||||
console.warn('No current server');
|
||||
return;
|
||||
}
|
||||
|
||||
commit(MutationTypes.SET_SERVER_CONFIGURATION, config.config);
|
||||
commit(MutationTypes.SET_SERVER_MESSAGES, config.messages);
|
||||
commit(MutationTypes.SET_WORLDS, config.worlds);
|
||||
commit(MutationTypes.SET_COMPONENTS, config.components);
|
||||
commit(MutationTypes.SET_LOGGED_IN, config.loggedIn);
|
||||
await state.currentMapProvider!.loadServerConfiguration();
|
||||
|
||||
//Skip default map/ui visibility logic if we already have a map selected (i.e config reload after hash change)
|
||||
if(state.currentMap) {
|
||||
return config;
|
||||
return;
|
||||
}
|
||||
|
||||
//Make UI visible if configured, there's enough space to do so, and this is the first config load
|
||||
@ -104,8 +99,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
let worldName, mapName;
|
||||
|
||||
// Use config default world if it exists
|
||||
if(config.config.defaultWorld && state.worlds.has(config.config.defaultWorld)) {
|
||||
worldName = config.config.defaultWorld;
|
||||
if(state.configuration.defaultWorld && state.worlds.has(state.configuration.defaultWorld)) {
|
||||
worldName = state.configuration.defaultWorld;
|
||||
}
|
||||
|
||||
// Prefer world from parsed url if present and it exists
|
||||
@ -122,8 +117,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
const world = state.worlds.get(worldName) as LiveAtlasWorldDefinition;
|
||||
|
||||
// Use config default map if it exists
|
||||
if(config.config.defaultMap && world.maps.has(config.config.defaultMap)) {
|
||||
mapName = config.config.defaultMap;
|
||||
if(state.configuration.defaultMap && world.maps.has(state.configuration.defaultMap)) {
|
||||
mapName = state.configuration.defaultMap;
|
||||
}
|
||||
|
||||
// Prefer map from parsed url if present and it exists
|
||||
@ -142,40 +137,35 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
worldName, mapName
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
async [ActionTypes.GET_UPDATE]({commit, dispatch, state}) {
|
||||
async [ActionTypes.START_UPDATES]({state}) {
|
||||
if(!state.currentWorld) {
|
||||
return Promise.reject("No current world");
|
||||
}
|
||||
|
||||
const update = await getAPI().getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.valueOf());
|
||||
|
||||
commit(MutationTypes.SET_WORLD_STATE, update.worldState);
|
||||
commit(MutationTypes.SET_UPDATE_TIMESTAMP, new Date(update.timestamp));
|
||||
commit(MutationTypes.INCREMENT_REQUEST_ID, undefined);
|
||||
commit(MutationTypes.ADD_MARKER_SET_UPDATES, update.updates.markerSets);
|
||||
commit(MutationTypes.ADD_TILE_UPDATES, update.updates.tiles);
|
||||
commit(MutationTypes.ADD_CHAT, update.updates.chat);
|
||||
commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, update.configHash);
|
||||
|
||||
await dispatch(ActionTypes.SET_PLAYERS, update.players);
|
||||
return update;
|
||||
state.currentMapProvider!.startUpdates();
|
||||
},
|
||||
|
||||
[ActionTypes.SET_PLAYERS]({commit, state}, players: Set<DynmapPlayer>) {
|
||||
async [ActionTypes.STOP_UPDATES]({state}) {
|
||||
if(!state.currentWorld) {
|
||||
return Promise.reject("No current world");
|
||||
}
|
||||
|
||||
state.currentMapProvider!.stopUpdates();
|
||||
},
|
||||
|
||||
[ActionTypes.SET_PLAYERS]({commit, state}, players: Set<LiveAtlasPlayer>) {
|
||||
const keep: Set<string> = new Set();
|
||||
|
||||
for(const player of players) {
|
||||
keep.add(player.account);
|
||||
keep.add(player.name);
|
||||
}
|
||||
|
||||
//Remove any players that aren't in the set
|
||||
commit(MutationTypes.SYNC_PLAYERS, keep);
|
||||
|
||||
const processQueue = (players: Set<DynmapPlayer>, resolve: Function) => {
|
||||
const processQueue = (players: Set<LiveAtlasPlayer>, resolve: Function) => {
|
||||
commit(MutationTypes.SET_PLAYERS_ASYNC, players);
|
||||
|
||||
if(!players.size) {
|
||||
@ -191,17 +181,6 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
});
|
||||
},
|
||||
|
||||
async [ActionTypes.GET_MARKER_SETS]({commit, state}) {
|
||||
if(!state.currentWorld) {
|
||||
throw new Error("No current world");
|
||||
}
|
||||
|
||||
const markerSets = await getAPI().getMarkerSets(state.currentWorld.name)
|
||||
commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
||||
|
||||
return markerSets;
|
||||
},
|
||||
|
||||
async [ActionTypes.POP_MARKER_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapMarkerUpdate[]> {
|
||||
if(!state.markerSets.has(markerSet)) {
|
||||
console.warn(`POP_MARKER_UPDATES: Marker set ${markerSet} doesn't exist`);
|
||||
@ -263,6 +242,6 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
},
|
||||
|
||||
async [ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
|
||||
await getAPI().sendChatMessage(message);
|
||||
await state.currentMapProvider!.sendChatMessage(message);
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,7 +17,6 @@
|
||||
import {GetterTree} from "vuex";
|
||||
import {State} from "@/store/state";
|
||||
import {getMinecraftTime, getUrlForLocation} from "@/util";
|
||||
import {LiveAtlasDynmapServerDefinition} from "@/index";
|
||||
|
||||
export type Getters = {
|
||||
playerMarkersEnabled(state: State): boolean;
|
||||
@ -26,7 +25,6 @@ export type Getters = {
|
||||
night(state: State): boolean;
|
||||
mapBackground(state: State, getters: GetterTree<State, State> & Getters): string;
|
||||
url(state: State, getters: GetterTree<State, State> & Getters): string;
|
||||
serverConfig(state: State, getters: GetterTree<State, State> & Getters): LiveAtlasDynmapServerDefinition;
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, State> & Getters = {
|
||||
@ -74,12 +72,4 @@ export const getters: GetterTree<State, State> & Getters = {
|
||||
|
||||
return getUrlForLocation(state.currentMap, {x,y,z}, zoom);
|
||||
},
|
||||
|
||||
serverConfig(state: State): LiveAtlasDynmapServerDefinition {
|
||||
if(!state.currentServer) {
|
||||
throw RangeError("No current server");
|
||||
}
|
||||
|
||||
return state.currentServer as LiveAtlasDynmapServerDefinition;
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -28,7 +28,6 @@ export enum MutationTypes {
|
||||
CLEAR_MARKER_SETS = 'clearMarkerSets',
|
||||
ADD_WORLD = 'addWorld',
|
||||
SET_WORLD_STATE = 'setWorldState',
|
||||
SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp',
|
||||
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
|
||||
ADD_TILE_UPDATES = 'addTileUpdates',
|
||||
ADD_CHAT = 'addChat',
|
||||
@ -37,7 +36,7 @@ export enum MutationTypes {
|
||||
POP_CIRCLE_UPDATES = 'popCircleUpdates',
|
||||
POP_LINE_UPDATES = 'popLineUpdates',
|
||||
POP_TILE_UPDATES = 'popTileUpdates',
|
||||
INCREMENT_REQUEST_ID = 'incrementRequestId',
|
||||
SET_MAX_PLAYERS = 'setMaxPlayers',
|
||||
SET_PLAYERS_ASYNC = 'setPlayersAsync',
|
||||
CLEAR_PLAYERS = 'clearPlayers',
|
||||
SYNC_PLAYERS = 'syncPlayers',
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,15 +18,8 @@ import {MutationTree} from "vuex";
|
||||
import {MutationTypes} from "@/store/mutation-types";
|
||||
import {State} from "@/store/state";
|
||||
import {
|
||||
DynmapArea,
|
||||
DynmapCircle,
|
||||
DynmapComponentConfig,
|
||||
DynmapLine, DynmapMarker,
|
||||
DynmapMarkerSet,
|
||||
DynmapMarkerSetUpdates,
|
||||
DynmapPlayer,
|
||||
DynmapServerConfig, DynmapTileUpdate,
|
||||
DynmapChat
|
||||
DynmapTileUpdate
|
||||
} from "@/dynmap";
|
||||
import {
|
||||
Coordinate,
|
||||
@ -38,8 +31,18 @@ import {
|
||||
LiveAtlasParsedUrl,
|
||||
LiveAtlasGlobalConfig,
|
||||
LiveAtlasGlobalMessageConfig,
|
||||
LiveAtlasServerMessageConfig
|
||||
LiveAtlasServerMessageConfig,
|
||||
LiveAtlasPlayer,
|
||||
LiveAtlasCircle,
|
||||
LiveAtlasLine,
|
||||
LiveAtlasArea,
|
||||
LiveAtlasMarker,
|
||||
LiveAtlasMarkerSet,
|
||||
LiveAtlasServerDefinition,
|
||||
LiveAtlasServerConfig, LiveAtlasChat, LiveAtlasPartialComponentConfig, LiveAtlasComponentConfig
|
||||
} from "@/index";
|
||||
import DynmapMapProvider from "@/providers/DynmapMapProvider";
|
||||
import Pl3xmapMapProvider from "@/providers/Pl3xmapMapProvider";
|
||||
|
||||
export type CurrentMapPayload = {
|
||||
worldName: string;
|
||||
@ -48,21 +51,20 @@ export type CurrentMapPayload = {
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.INIT](state: S, config: LiveAtlasGlobalConfig): void
|
||||
[MutationTypes.SET_SERVER_CONFIGURATION](state: S, config: DynmapServerConfig): void
|
||||
[MutationTypes.SET_SERVER_CONFIGURATION](state: S, config: LiveAtlasServerConfig): void
|
||||
[MutationTypes.SET_SERVER_CONFIGURATION_HASH](state: S, hash: number): void
|
||||
[MutationTypes.CLEAR_SERVER_CONFIGURATION_HASH](state: S): void
|
||||
[MutationTypes.SET_SERVER_MESSAGES](state: S, messages: LiveAtlasServerMessageConfig): void
|
||||
[MutationTypes.SET_WORLDS](state: S, worlds: Array<LiveAtlasWorldDefinition>): void
|
||||
[MutationTypes.CLEAR_WORLDS](state: S): void
|
||||
[MutationTypes.SET_COMPONENTS](state: S, worlds: DynmapComponentConfig): void
|
||||
[MutationTypes.SET_MARKER_SETS](state: S, worlds: Map<string, DynmapMarkerSet>): void
|
||||
[MutationTypes.SET_COMPONENTS](state: S, components: LiveAtlasPartialComponentConfig | LiveAtlasComponentConfig): void
|
||||
[MutationTypes.SET_MARKER_SETS](state: S, worlds: Map<string, LiveAtlasMarkerSet>): void
|
||||
[MutationTypes.CLEAR_MARKER_SETS](state: S): void
|
||||
[MutationTypes.ADD_WORLD](state: S, world: LiveAtlasWorldDefinition): void
|
||||
[MutationTypes.SET_WORLD_STATE](state: S, worldState: LiveAtlasWorldState): void
|
||||
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
|
||||
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
|
||||
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
|
||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>): void
|
||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<LiveAtlasChat>): void
|
||||
|
||||
[MutationTypes.POP_MARKER_UPDATES](state: S, payload: {markerSet: string, amount: number}): void
|
||||
[MutationTypes.POP_AREA_UPDATES](state: S, payload: {markerSet: string, amount: number}): void
|
||||
@ -70,8 +72,8 @@ export type Mutations<S = State> = {
|
||||
[MutationTypes.POP_LINE_UPDATES](state: S, payload: {markerSet: string, amount: number}): void
|
||||
[MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void
|
||||
|
||||
[MutationTypes.INCREMENT_REQUEST_ID](state: S): void
|
||||
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer>
|
||||
[MutationTypes.SET_MAX_PLAYERS](state: S, maxPlayers: number): void
|
||||
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<LiveAtlasPlayer>): Set<LiveAtlasPlayer>
|
||||
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
|
||||
[MutationTypes.CLEAR_PLAYERS](state: S): void
|
||||
[MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void
|
||||
@ -81,8 +83,8 @@ export type Mutations<S = State> = {
|
||||
[MutationTypes.SET_PARSED_URL](state: S, payload: LiveAtlasParsedUrl): void
|
||||
[MutationTypes.CLEAR_PARSED_URL](state: S): void
|
||||
[MutationTypes.CLEAR_CURRENT_MAP](state: S): void
|
||||
[MutationTypes.SET_FOLLOW_TARGET](state: S, payload: DynmapPlayer): void
|
||||
[MutationTypes.SET_PAN_TARGET](state: S, payload: DynmapPlayer): void
|
||||
[MutationTypes.SET_FOLLOW_TARGET](state: S, payload: LiveAtlasPlayer): void
|
||||
[MutationTypes.SET_PAN_TARGET](state: S, payload: LiveAtlasPlayer): void
|
||||
[MutationTypes.CLEAR_FOLLOW_TARGET](state: S, a?: void): void
|
||||
[MutationTypes.CLEAR_PAN_TARGET](state: S, a?: void): void
|
||||
|
||||
@ -158,9 +160,8 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
},
|
||||
|
||||
// Sets configuration options from the initial config fetch
|
||||
[MutationTypes.SET_SERVER_CONFIGURATION](state: State, config: DynmapServerConfig) {
|
||||
[MutationTypes.SET_SERVER_CONFIGURATION](state: State, config: LiveAtlasServerConfig) {
|
||||
state.configuration = Object.assign(state.configuration, config);
|
||||
state.configurationHash = config.hash;
|
||||
},
|
||||
|
||||
// Sets configuration hash
|
||||
@ -222,13 +223,15 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
state.currentWorldState.thundering = false;
|
||||
},
|
||||
|
||||
//Sets the state and settings of optional components, from the initial config fetch
|
||||
[MutationTypes.SET_COMPONENTS](state: State, components: DynmapComponentConfig) {
|
||||
//Updates the state of optional components (chat, link button, etc)
|
||||
//Can be called with a LiveAtlasComponentConfig object to replace the whole state
|
||||
//or a LiveAtlasPartialComponentConfig object for partial updates to the existing state
|
||||
[MutationTypes.SET_COMPONENTS](state: State, components: LiveAtlasPartialComponentConfig | LiveAtlasComponentConfig) {
|
||||
state.components = Object.assign(state.components, components);
|
||||
},
|
||||
|
||||
//Sets the existing marker sets from the last marker fetch
|
||||
[MutationTypes.SET_MARKER_SETS](state: State, markerSets: Map<string, DynmapMarkerSet>) {
|
||||
[MutationTypes.SET_MARKER_SETS](state: State, markerSets: Map<string, LiveAtlasMarkerSet>) {
|
||||
state.markerSets.clear();
|
||||
state.pendingSetUpdates.clear();
|
||||
|
||||
@ -257,11 +260,6 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
state.currentWorldState = Object.assign(state.currentWorldState, worldState);
|
||||
},
|
||||
|
||||
//Sets the timestamp of the last update fetch
|
||||
[MutationTypes.SET_UPDATE_TIMESTAMP](state: State, timestamp: Date) {
|
||||
state.updateTimestamp = timestamp;
|
||||
},
|
||||
|
||||
//Adds markerset related updates from an update fetch to the pending updates list
|
||||
[MutationTypes.ADD_MARKER_SET_UPDATES](state: State, updates: Map<string, DynmapMarkerSetUpdates>) {
|
||||
for(const entry of updates) {
|
||||
@ -277,10 +275,10 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
priority: entry[1].payload.priority,
|
||||
label: entry[1].payload.label,
|
||||
hidden: entry[1].payload.hidden,
|
||||
markers: Object.freeze(new Map()) as Map<string, DynmapMarker>,
|
||||
areas: Object.freeze(new Map()) as Map<string, DynmapArea>,
|
||||
circles: Object.freeze(new Map()) as Map<string, DynmapCircle>,
|
||||
lines: Object.freeze(new Map()) as Map<string, DynmapLine>,
|
||||
markers: Object.freeze(new Map()) as Map<string, LiveAtlasMarker>,
|
||||
areas: Object.freeze(new Map()) as Map<string, LiveAtlasArea>,
|
||||
circles: Object.freeze(new Map()) as Map<string, LiveAtlasCircle>,
|
||||
lines: Object.freeze(new Map()) as Map<string, LiveAtlasLine>,
|
||||
});
|
||||
|
||||
state.pendingSetUpdates.set(entry[0], {
|
||||
@ -295,7 +293,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
}
|
||||
}
|
||||
|
||||
const set = state.markerSets.get(entry[0]) as DynmapMarkerSet,
|
||||
const set = state.markerSets.get(entry[0]) as LiveAtlasMarkerSet,
|
||||
setUpdates = state.pendingSetUpdates.get(entry[0]) as DynmapMarkerSetUpdates;
|
||||
|
||||
//Delete the set if it has been deleted
|
||||
@ -320,7 +318,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
if(update.removed) {
|
||||
set.markers.delete(update.id);
|
||||
} else {
|
||||
set.markers.set(update.id, update.payload as DynmapMarker);
|
||||
set.markers.set(update.id, update.payload as LiveAtlasMarker);
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,7 +326,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
if(update.removed) {
|
||||
set.areas.delete(update.id);
|
||||
} else {
|
||||
set.areas.set(update.id, update.payload as DynmapArea);
|
||||
set.areas.set(update.id, update.payload as LiveAtlasArea);
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,7 +334,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
if(update.removed) {
|
||||
set.circles.delete(update.id);
|
||||
} else {
|
||||
set.circles.set(update.id, update.payload as DynmapCircle);
|
||||
set.circles.set(update.id, update.payload as LiveAtlasCircle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,7 +342,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
if(update.removed) {
|
||||
set.lines.delete(update.id);
|
||||
} else {
|
||||
set.lines.set(update.id, update.payload as DynmapLine);
|
||||
set.lines.set(update.id, update.payload as LiveAtlasLine);
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,7 +360,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
},
|
||||
|
||||
//Adds chat messages from an update fetch to the chat history
|
||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>) {
|
||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<LiveAtlasChat>) {
|
||||
state.chat.messages.unshift(...chat);
|
||||
},
|
||||
|
||||
@ -411,37 +409,38 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
state.pendingTileUpdates.splice(0, amount);
|
||||
},
|
||||
|
||||
//Increments the request id for the next update fetch
|
||||
[MutationTypes.INCREMENT_REQUEST_ID](state: State) {
|
||||
state.updateRequestId++;
|
||||
[MutationTypes.SET_MAX_PLAYERS](state: State, maxPlayers: number) {
|
||||
state.maxPlayers = maxPlayers;
|
||||
},
|
||||
|
||||
// Set up to 10 players at once
|
||||
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<DynmapPlayer>): Set<DynmapPlayer> {
|
||||
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<LiveAtlasPlayer>): Set<LiveAtlasPlayer> {
|
||||
let count = 0;
|
||||
|
||||
for(const player of players) {
|
||||
if(state.players.has(player.account)) {
|
||||
const existing = state.players.get(player.account);
|
||||
if(state.players.has(player.name)) {
|
||||
const existing = state.players.get(player.name);
|
||||
|
||||
existing!.health = player.health;
|
||||
existing!.uuid = player.uuid;
|
||||
existing!.armor = player.armor;
|
||||
existing!.location = Object.assign(existing!.location, player.location);
|
||||
existing!.hidden = player.hidden;
|
||||
existing!.name = player.name;
|
||||
existing!.displayName = player.displayName;
|
||||
existing!.sort = player.sort;
|
||||
|
||||
if(existing!.name !== player.name || existing!.sort !== player.sort) {
|
||||
if(existing!.displayName !== player.displayName || existing!.sort !== player.sort) {
|
||||
state.sortedPlayers.dirty = true;
|
||||
}
|
||||
} else {
|
||||
state.sortedPlayers.dirty = true;
|
||||
state.players.set(player.account, {
|
||||
account: player.account,
|
||||
state.players.set(player.name, {
|
||||
name: player.name,
|
||||
uuid: player.uuid,
|
||||
health: player.health,
|
||||
armor: player.armor,
|
||||
location: player.location,
|
||||
name: player.name,
|
||||
displayName: player.displayName,
|
||||
sort: player.sort,
|
||||
hidden: player.hidden,
|
||||
});
|
||||
@ -461,7 +460,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
return a.sort - b.sort;
|
||||
}
|
||||
|
||||
return a.account.toLowerCase().localeCompare(b.account.toLowerCase());
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
}) as LiveAtlasSortedPlayers;
|
||||
}
|
||||
|
||||
@ -471,7 +470,7 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
//Removes all players not found in the provided keep set
|
||||
[MutationTypes.SYNC_PLAYERS](state: State, keep: Set<string>) {
|
||||
for(const [key, player] of state.players) {
|
||||
if(!keep.has(player.account)) {
|
||||
if(!keep.has(player.name)) {
|
||||
state.sortedPlayers.splice(state.sortedPlayers.indexOf(player), 1);
|
||||
state.players.delete(key);
|
||||
}
|
||||
@ -493,6 +492,22 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
}
|
||||
|
||||
state.currentServer = state.servers.get(serverName);
|
||||
|
||||
if(state.currentMapProvider) {
|
||||
state.currentMapProvider.stopUpdates();
|
||||
state.currentMapProvider.destroy();
|
||||
}
|
||||
|
||||
switch(state.currentServer!.type) {
|
||||
case 'pl3xmap':
|
||||
state.currentMapProvider = Object.seal(
|
||||
new Pl3xmapMapProvider(state.servers.get(serverName) as LiveAtlasServerDefinition));
|
||||
break;
|
||||
case 'dynmap':
|
||||
state.currentMapProvider = Object.seal(
|
||||
new DynmapMapProvider(state.servers.get(serverName) as LiveAtlasServerDefinition));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
//Sets the currently active map/world
|
||||
@ -550,12 +565,12 @@ export const mutations: MutationTree<State> & Mutations = {
|
||||
},
|
||||
|
||||
//Set the follow target, which the map will automatically pan to keep in view
|
||||
[MutationTypes.SET_FOLLOW_TARGET](state: State, player: DynmapPlayer) {
|
||||
[MutationTypes.SET_FOLLOW_TARGET](state: State, player: LiveAtlasPlayer) {
|
||||
state.followTarget = player;
|
||||
},
|
||||
|
||||
//Set the pan target, which the map will immediately pan to once
|
||||
[MutationTypes.SET_PAN_TARGET](state: State, player: DynmapPlayer) {
|
||||
[MutationTypes.SET_PAN_TARGET](state: State, player: LiveAtlasPlayer) {
|
||||
state.panTarget = player;
|
||||
},
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,10 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
DynmapComponentConfig, DynmapMarkerSet, DynmapMarkerSetUpdates,
|
||||
DynmapPlayer,
|
||||
DynmapServerConfig, DynmapTileUpdate,
|
||||
DynmapChat
|
||||
DynmapMarkerSetUpdates,
|
||||
DynmapTileUpdate
|
||||
} from "@/dynmap";
|
||||
import {
|
||||
Coordinate,
|
||||
@ -29,37 +27,44 @@ import {
|
||||
LiveAtlasUIElement,
|
||||
LiveAtlasWorldDefinition,
|
||||
LiveAtlasParsedUrl,
|
||||
LiveAtlasMessageConfig
|
||||
LiveAtlasMessageConfig,
|
||||
LiveAtlasMapProvider,
|
||||
LiveAtlasPlayer,
|
||||
LiveAtlasMarkerSet,
|
||||
LiveAtlasComponentConfig,
|
||||
LiveAtlasServerConfig, LiveAtlasChat
|
||||
} from "@/index";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
export type State = {
|
||||
version: string;
|
||||
servers: Map<string, LiveAtlasServerDefinition>;
|
||||
configuration: DynmapServerConfig;
|
||||
configuration: LiveAtlasServerConfig;
|
||||
configurationHash: number | undefined;
|
||||
messages: LiveAtlasMessageConfig;
|
||||
components: DynmapComponentConfig;
|
||||
components: LiveAtlasComponentConfig;
|
||||
|
||||
loggedIn: boolean;
|
||||
|
||||
worlds: Map<string, LiveAtlasWorldDefinition>;
|
||||
maps: Map<string, LiveAtlasMapDefinition>;
|
||||
players: Map<string, DynmapPlayer>;
|
||||
players: Map<string, LiveAtlasPlayer>;
|
||||
sortedPlayers: LiveAtlasSortedPlayers;
|
||||
markerSets: Map<string, DynmapMarkerSet>;
|
||||
maxPlayers: number;
|
||||
markerSets: Map<string, LiveAtlasMarkerSet>;
|
||||
|
||||
chat: {
|
||||
unread: number;
|
||||
messages: DynmapChat[];
|
||||
messages: LiveAtlasChat[];
|
||||
};
|
||||
|
||||
pendingSetUpdates: Map<string, DynmapMarkerSetUpdates>;
|
||||
pendingTileUpdates: Array<DynmapTileUpdate>;
|
||||
|
||||
followTarget?: DynmapPlayer;
|
||||
panTarget?: DynmapPlayer;
|
||||
followTarget?: LiveAtlasPlayer;
|
||||
panTarget?: LiveAtlasPlayer;
|
||||
|
||||
currentMapProvider?: Readonly<LiveAtlasMapProvider>;
|
||||
currentServer?: LiveAtlasServerDefinition;
|
||||
currentWorldState: LiveAtlasWorldState;
|
||||
currentWorld?: LiveAtlasWorldDefinition;
|
||||
@ -67,9 +72,6 @@ export type State = {
|
||||
currentLocation: Coordinate;
|
||||
currentZoom: number;
|
||||
|
||||
updateRequestId: number;
|
||||
updateTimestamp: Date;
|
||||
|
||||
ui: {
|
||||
playersAboveMarkers: boolean;
|
||||
playersSearch: boolean;
|
||||
@ -91,20 +93,13 @@ export const state: State = {
|
||||
servers: new Map(),
|
||||
|
||||
configuration: {
|
||||
version: '',
|
||||
defaultMap: '',
|
||||
defaultWorld: '',
|
||||
defaultZoom: 0,
|
||||
followMap: '',
|
||||
followZoom: 0,
|
||||
updateInterval: 3000,
|
||||
showLayerControl: false,
|
||||
title: '',
|
||||
loginEnabled: false,
|
||||
maxPlayers: 0,
|
||||
grayHiddenPlayers: false,
|
||||
expandUI: false,
|
||||
hash: 0,
|
||||
},
|
||||
configurationHash: undefined,
|
||||
|
||||
@ -157,6 +152,7 @@ export const state: State = {
|
||||
maps: new Map(), //Defined maps from configuration.json
|
||||
players: new Map(), //Online players from world.json
|
||||
sortedPlayers: [] as LiveAtlasSortedPlayers, //Online players from world.json, sorted by their sort property then alphabetically
|
||||
maxPlayers: 0,
|
||||
|
||||
chat: {
|
||||
unread: 0,
|
||||
@ -187,6 +183,9 @@ export const state: State = {
|
||||
//Optional "link" component. Adds button to copy url for current position
|
||||
linkControl: false,
|
||||
|
||||
//Layers control
|
||||
layerControl: false,
|
||||
|
||||
//Optional "logo" controls.
|
||||
logoControls: [],
|
||||
|
||||
@ -197,12 +196,16 @@ export const state: State = {
|
||||
chatBox: undefined,
|
||||
|
||||
//Chat balloons showing messages above player markers
|
||||
chatBalloons: false
|
||||
chatBalloons: false,
|
||||
|
||||
//Login/registering (not currently implemented)
|
||||
login: false,
|
||||
},
|
||||
|
||||
followTarget: undefined,
|
||||
panTarget: undefined,
|
||||
|
||||
currentMapProvider: undefined,
|
||||
currentServer: undefined,
|
||||
currentWorld: undefined,
|
||||
currentMap: undefined,
|
||||
@ -218,9 +221,6 @@ export const state: State = {
|
||||
timeOfDay: 0,
|
||||
},
|
||||
|
||||
updateRequestId: 0,
|
||||
updateTimestamp: new Date(),
|
||||
|
||||
ui: {
|
||||
playersAboveMarkers: true,
|
||||
playersSearch: true,
|
||||
|
61
src/util.ts
61
src/util.ts
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,17 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import API from '@/api';
|
||||
import {DynmapPlayer} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||
|
||||
interface HeadQueueEntry {
|
||||
cacheKey: string;
|
||||
account: string;
|
||||
size: string;
|
||||
image: HTMLImageElement;
|
||||
}
|
||||
import {HeadQueueEntry, LiveAtlasPlayer} from "@/index";
|
||||
|
||||
const headCache = new Map<string, HTMLImageElement>(),
|
||||
headUnresolvedCache = new Map<string, Promise<HTMLImageElement>>(),
|
||||
@ -32,6 +24,10 @@ const headCache = new Map<string, HTMLImageElement>(),
|
||||
|
||||
headQueue: HeadQueueEntry[] = [];
|
||||
|
||||
export const titleColoursRegex = /§[0-9a-f]/ig;
|
||||
export const netherWorldNameRegex = /_?nether(_|$)/i;
|
||||
export const endWorldNameRegex = /(^|_)end(_|$)/i;
|
||||
|
||||
export const getMinecraftTime = (serverTime: number) => {
|
||||
const day = serverTime >= 0 && serverTime < 13700;
|
||||
|
||||
@ -49,8 +45,9 @@ export const getMinecraftTime = (serverTime: number) => {
|
||||
};
|
||||
}
|
||||
|
||||
export const getMinecraftHead = (player: DynmapPlayer | string, size: string): Promise<HTMLImageElement> => {
|
||||
const account = typeof player === 'string' ? player : player.account,
|
||||
export const getMinecraftHead = (player: LiveAtlasPlayer | string, size: string): Promise<HTMLImageElement> => {
|
||||
const account = typeof player === 'string' ? player : player.name,
|
||||
uuid = typeof player === 'string' ? undefined : player.uuid,
|
||||
cacheKey = `${account}-${size}`;
|
||||
|
||||
if(headCache.has(cacheKey)) {
|
||||
@ -79,7 +76,8 @@ export const getMinecraftHead = (player: DynmapPlayer | string, size: string): P
|
||||
};
|
||||
|
||||
headQueue.push({
|
||||
account,
|
||||
name: account,
|
||||
uuid,
|
||||
size,
|
||||
cacheKey,
|
||||
image: faceImage,
|
||||
@ -97,37 +95,14 @@ const tickHeadQueue = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const head = headQueue.pop() as HeadQueueEntry,
|
||||
src = (head.size === 'body') ? `faces/body/${head.account}.png` :`faces/${head.size}x${head.size}/${head.account}.png`;
|
||||
const head = headQueue.pop() as HeadQueueEntry;
|
||||
|
||||
headsLoading.add(head.cacheKey);
|
||||
head.image.src = concatURL(useStore().getters.serverConfig.dynmap.markers, src);
|
||||
head.image.src = useStore().state.currentMapProvider!.getPlayerHeadUrl(head);
|
||||
|
||||
tickHeadQueue();
|
||||
}
|
||||
|
||||
export const concatURL = (base: string, addition: string) => {
|
||||
if(base.indexOf('?') >= 0) {
|
||||
return base + escape(addition);
|
||||
}
|
||||
|
||||
return base + addition;
|
||||
}
|
||||
|
||||
export const getPointConverter = () => {
|
||||
const map = useStore().state.currentMap;
|
||||
|
||||
if(map) {
|
||||
return (x: number, y: number, z: number) => {
|
||||
return map.locationToLatLng({x, y, z});
|
||||
};
|
||||
} else {
|
||||
return (x: number, y: number, z: number) => {
|
||||
return LiveAtlasMapDefinition.defaultProjection.locationToLatLng({x, y, z});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const parseUrl = () => {
|
||||
const query = new URLSearchParams(window.location.search),
|
||||
hash = window.location.hash.replace('#', '');
|
||||
@ -211,16 +186,6 @@ export const parseMapSearchParams = (query: URLSearchParams) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const getAPI = () => {
|
||||
const store = useStore();
|
||||
|
||||
if(!store.state.currentServer) {
|
||||
throw new RangeError("No current server");
|
||||
}
|
||||
|
||||
return API;
|
||||
}
|
||||
|
||||
export const getUrlForLocation = (map: LiveAtlasMapDefinition, location: {
|
||||
x: number,
|
||||
y: number,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -18,39 +18,35 @@
|
||||
*/
|
||||
|
||||
import {LatLngExpression} from "leaflet";
|
||||
import {DynmapArea} from "@/dynmap";
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
|
||||
import {Coordinate, LiveAtlasArea} from "@/index";
|
||||
import {arePointsEqual, createPopup, isStyleEqual, tooltipOptions} from "@/util/paths";
|
||||
|
||||
export const createArea = (options: DynmapArea, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
export const createArea = (options: LiveAtlasArea, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
|
||||
points = getPoints(options, converter, outline),
|
||||
area = outline ? new LiveAtlasPolyline(points, {
|
||||
...options.style,
|
||||
minZoom: options.minZoom,
|
||||
maxZoom: options.maxZoom,
|
||||
}) : new LiveAtlasPolygon(points, {
|
||||
...options.style,
|
||||
minZoom: options.minZoom,
|
||||
maxZoom: options.maxZoom,
|
||||
});
|
||||
points = options.points.map(projectPointsMapCallback, converter) as LatLngExpression[] | LatLngExpression[][],
|
||||
area = outline ? new LiveAtlasPolyline(points, options) : new LiveAtlasPolygon(points, options);
|
||||
|
||||
if (options.label) {
|
||||
area.bindPopup(() => createPopup(options));
|
||||
if (options.popupContent) {
|
||||
area.bindPopup(() => createPopup(options, 'AreaPopup'));
|
||||
}
|
||||
|
||||
if (options.tooltipContent) {
|
||||
area.bindTooltip(() => options.tooltipContent as string, tooltipOptions);
|
||||
}
|
||||
|
||||
return area;
|
||||
};
|
||||
|
||||
export const updateArea = (area: LiveAtlasPolyline | LiveAtlasPolygon | undefined, options: DynmapArea, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
const outline = !options.style || !options.style.fillOpacity || (options.style.fillOpacity <= 0) as boolean,
|
||||
points = getPoints(options, converter, outline);
|
||||
|
||||
export const updateArea = (area: LiveAtlasPolyline | LiveAtlasPolygon | undefined, options: LiveAtlasArea, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
if (!area) {
|
||||
return createArea(options, converter);
|
||||
}
|
||||
|
||||
const oldPoints = area.getLatLngs();
|
||||
const points = options.points.map(projectPointsMapCallback, converter) as LatLngExpression[] | LatLngExpression[][],
|
||||
oldPoints = area.getLatLngs();
|
||||
|
||||
let dirty = false;
|
||||
|
||||
//Avoid pointless setStyle() redrawing by checking if styles have actually changed
|
||||
@ -66,7 +62,7 @@ export const updateArea = (area: LiveAtlasPolyline | LiveAtlasPolygon | undefine
|
||||
|
||||
area.closePopup();
|
||||
area.unbindPopup();
|
||||
area.bindPopup(() => createPopup(options));
|
||||
area.bindPopup(() => createPopup(options, 'AreaPopup'));
|
||||
|
||||
if(dirty) {
|
||||
area.redraw();
|
||||
@ -75,149 +71,129 @@ export const updateArea = (area: LiveAtlasPolyline | LiveAtlasPolygon | undefine
|
||||
return area;
|
||||
};
|
||||
|
||||
const arePointsEqual = (oldPoints: any, newPoints: any) => {
|
||||
return JSON.stringify(oldPoints) === JSON.stringify(newPoints);
|
||||
}
|
||||
|
||||
const isStyleEqual = (oldStyle: any, newStyle: any) => {
|
||||
return oldStyle && newStyle
|
||||
&& (oldStyle.color === newStyle.color)
|
||||
&& (oldStyle.weight === newStyle.weight)
|
||||
&& (oldStyle.opacity === newStyle.opacity)
|
||||
&& (oldStyle.fillColor === newStyle.fillColor)
|
||||
&& (oldStyle.fillOpacity === newStyle.fillOpacity)
|
||||
}
|
||||
|
||||
export const createPopup = (options: DynmapArea): HTMLElement => {
|
||||
const popup = document.createElement('span');
|
||||
|
||||
if (options.popupContent) {
|
||||
popup.classList.add('AreaPopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.popupContent);
|
||||
} else if (options.isHTML) {
|
||||
popup.classList.add('AreaPopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.label);
|
||||
const projectPointsMapCallback = function(this: Function, point: Coordinate | Coordinate[] | Coordinate[][]): LatLngExpression | LatLngExpression[] {
|
||||
if(Array.isArray(point)) {
|
||||
return point.map(projectPointsMapCallback, this) as LatLngExpression[];
|
||||
} else {
|
||||
popup.textContent = options.label;
|
||||
// @ts-ignore
|
||||
return this(point);
|
||||
}
|
||||
|
||||
return popup;
|
||||
};
|
||||
|
||||
export const getPoints = (options: DynmapArea, converter: Function, outline: boolean): LatLngExpression[] | LatLngExpression[][] => {
|
||||
if (options.x.length === 2) { /* Only 2 points */
|
||||
if (options.y[0] === options.y[1]) {
|
||||
return get2DBoxPoints(options, converter, outline);
|
||||
export const getPoints = (x: number[], y: [number, number], z: number[], outline: boolean): Coordinate[] | Coordinate[][] => {
|
||||
if (x.length === 2) { /* Only 2 points */
|
||||
if (y[0] === y[1]) {
|
||||
return get2DBoxPoints(x, y, z, outline);
|
||||
} else {
|
||||
return get3DBoxPoints(options, converter);
|
||||
return get3DBoxPoints(x, y, z);
|
||||
}
|
||||
} else {
|
||||
if (options.y[0] === options.y[1]) {
|
||||
return get2DShapePoints(options, converter, outline);
|
||||
if (y[0] === y[1]) {
|
||||
return get2DShapePoints(x, y, z, outline);
|
||||
} else {
|
||||
return get3DShapePoints(options, converter);
|
||||
return get3DShapePoints(x, y, z);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const get3DBoxPoints = (options: DynmapArea, converter: Function): LatLngExpression[][] => {
|
||||
const maxX = options.x[0],
|
||||
minX = options.x[1],
|
||||
maxY = options.y[0],
|
||||
minY = options.y[1],
|
||||
maxZ = options.z[0],
|
||||
minZ = options.z[1];
|
||||
export const get3DBoxPoints = (x: number[], y: [number, number], z: number[]): Coordinate[][] => {
|
||||
const maxX = x[0],
|
||||
minX = x[1],
|
||||
maxY = y[0],
|
||||
minY = y[1],
|
||||
maxZ = z[0],
|
||||
minZ = z[1];
|
||||
|
||||
return [
|
||||
[
|
||||
converter(minX, minY, minZ),
|
||||
converter(maxX, minY, minZ),
|
||||
converter(maxX, minY, maxZ),
|
||||
converter(minX, minY, maxZ)
|
||||
{x: minX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: maxZ},
|
||||
{x: minX, y: minY, z: maxZ}
|
||||
], [
|
||||
converter(minX, maxY, minZ),
|
||||
converter(maxX, maxY, minZ),
|
||||
converter(maxX, maxY, maxZ),
|
||||
converter(minX, maxY, maxZ)
|
||||
{x: minX, y: maxY, z: minZ},
|
||||
{x: maxX, y: maxY, z: minZ},
|
||||
{x: maxX, y: maxY, z: maxZ},
|
||||
{x: minX, y: maxY, z: maxZ}
|
||||
], [
|
||||
converter(minX, minY, minZ),
|
||||
converter(minX, maxY, minZ),
|
||||
converter(maxX, maxY, minZ),
|
||||
converter(maxX, minY, minZ)
|
||||
{x: minX, y: minY, z: minZ},
|
||||
{x: minX, y: maxY, z: minZ},
|
||||
{x: maxX, y: maxY, z: minZ},
|
||||
{x: maxX, y: minY, z: minZ}
|
||||
], [
|
||||
converter(maxX, minY, minZ),
|
||||
converter(maxX, maxY, minZ),
|
||||
converter(maxX, maxY, maxZ),
|
||||
converter(maxX, minY, maxZ)
|
||||
{x: maxX, y: minY, z: minZ},
|
||||
{x: maxX, y: maxY, z: minZ},
|
||||
{x: maxX, y: maxY, z: maxZ},
|
||||
{x: maxX, y: minY, z: maxZ}
|
||||
], [
|
||||
converter(minX, minY, maxZ),
|
||||
converter(minX, maxY, maxZ),
|
||||
converter(maxX, maxY, maxZ),
|
||||
converter(maxX, minY, maxZ)
|
||||
{x: minX, y: minY, z: maxZ},
|
||||
{x: minX, y: maxY, z: maxZ},
|
||||
{x: maxX, y: maxY, z: maxZ},
|
||||
{x: maxX, y: minY, z: maxZ}
|
||||
], [
|
||||
converter(minX, minY, minZ),
|
||||
converter(minX, maxY, minZ),
|
||||
converter(minX, maxY, maxZ),
|
||||
converter(minX, minY, maxZ)
|
||||
{x: minX, y: minY, z: minZ},
|
||||
{x: minX, y: maxY, z: minZ},
|
||||
{x: minX, y: maxY, z: maxZ},
|
||||
{x: minX, y: minY, z: maxZ}
|
||||
]
|
||||
];
|
||||
};
|
||||
|
||||
export const get2DBoxPoints = (options: DynmapArea, converter: Function, outline: boolean): LatLngExpression[] => {
|
||||
const maxX = options.x[0],
|
||||
minX = options.x[1],
|
||||
minY = options.y[1],
|
||||
maxZ = options.z[0],
|
||||
minZ = options.z[1];
|
||||
export const get2DBoxPoints = (x: number[], y: [number, number], z: number[], outline: boolean): Coordinate[] => {
|
||||
const maxX = x[0],
|
||||
minX = x[1],
|
||||
minY = y[1],
|
||||
maxZ = z[0],
|
||||
minZ = z[1];
|
||||
|
||||
if (outline) {
|
||||
return [
|
||||
converter(minX, minY, minZ),
|
||||
converter(maxX, minY, minZ),
|
||||
converter(maxX, minY, maxZ),
|
||||
converter(minX, minY, maxZ),
|
||||
converter(minX, minY, minZ)
|
||||
{x: minX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: maxZ},
|
||||
{x: minX, y: minY, z: maxZ},
|
||||
{x: minX, y: minY, z: minZ}
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
converter(minX, minY, minZ),
|
||||
converter(maxX, minY, minZ),
|
||||
converter(maxX, minY, maxZ),
|
||||
converter(minX, minY, maxZ)
|
||||
{x: minX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: minZ},
|
||||
{x: maxX, y: minY, z: maxZ},
|
||||
{x: minX, y: minY, z: maxZ}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
export const get3DShapePoints = (options: DynmapArea, converter: Function): LatLngExpression[][] => {
|
||||
export const get3DShapePoints = (x: number[], y: [number, number], z: number[]): Coordinate[][] => {
|
||||
const toplist = [],
|
||||
botlist = [],
|
||||
polylist = [];
|
||||
|
||||
for (let i = 0; i < options.x.length; i++) {
|
||||
toplist[i] = converter(options.x[i], options.y[0], options.z[i]);
|
||||
botlist[i] = converter(options.x[i], options.y[1], options.z[i]);
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
toplist[i] = {x: x[i], y: y[0], z: z[i]};
|
||||
botlist[i] = {x: x[i], y: y[1], z: z[i]};
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.x.length; i++) {
|
||||
const sidelist = [];
|
||||
sidelist[0] = toplist[i];
|
||||
sidelist[1] = botlist[i];
|
||||
sidelist[2] = botlist[(i + 1) % options.x.length];
|
||||
sidelist[3] = toplist[(i + 1) % options.x.length];
|
||||
polylist[i] = sidelist;
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
polylist[i] = [
|
||||
toplist[i],
|
||||
botlist[i],
|
||||
botlist[(i + 1) % x.length],
|
||||
toplist[(i + 1) % x.length],
|
||||
];
|
||||
}
|
||||
|
||||
polylist[options.x.length] = botlist;
|
||||
polylist[options.x.length + 1] = toplist;
|
||||
polylist[x.length] = botlist;
|
||||
polylist[x.length + 1] = toplist;
|
||||
|
||||
return polylist;
|
||||
};
|
||||
|
||||
export const get2DShapePoints = (options: DynmapArea, converter: Function, outline: boolean): LatLngExpression[] => {
|
||||
export const get2DShapePoints = (x: number[], y: [number, number], z: number[], outline: boolean): Coordinate[] => {
|
||||
const points = [];
|
||||
|
||||
for (let i = 0; i < options.x.length; i++) {
|
||||
points[i] = converter(options.x[i], options.y[1], options.z[i]);
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
points[i] = {x: x[i], y: y[1], z: z[i]};
|
||||
}
|
||||
|
||||
if (outline) {
|
||||
@ -225,4 +201,4 @@ export const get2DShapePoints = (options: DynmapArea, converter: Function, outli
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -17,66 +17,46 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {DynmapCircle} from "@/dynmap";
|
||||
import {LatLngExpression} from "leaflet";
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import LiveAtlasPolygon from "@/leaflet/vector/LiveAtlasPolygon";
|
||||
import {LiveAtlasCircle} from "@/index";
|
||||
import {createPopup, tooltipOptions} from "@/util/paths";
|
||||
|
||||
export const createCircle = (options: DynmapCircle, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
export const createCircle = (options: LiveAtlasCircle, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
|
||||
points = getCirclePoints(options, converter, outline),
|
||||
circle = outline ? new LiveAtlasPolyline(points, {
|
||||
...options.style,
|
||||
minZoom: options.minZoom,
|
||||
maxZoom: options.maxZoom,
|
||||
}) : new LiveAtlasPolygon(points, {
|
||||
...options.style,
|
||||
minZoom: options.minZoom,
|
||||
maxZoom: options.maxZoom,
|
||||
});
|
||||
circle = outline ? new LiveAtlasPolyline(points, options) : new LiveAtlasPolygon(points, options);
|
||||
|
||||
if(options.label) {
|
||||
circle.bindPopup(() => createPopup(options));
|
||||
if(options.popupContent) {
|
||||
circle.bindPopup(() => createPopup(options, 'CirclePopup'));
|
||||
}
|
||||
|
||||
if (options.tooltipContent) {
|
||||
circle.bindTooltip(() => options.tooltipContent as string, tooltipOptions);
|
||||
}
|
||||
|
||||
return circle;
|
||||
};
|
||||
|
||||
export const updateCircle = (circle: LiveAtlasPolyline | LiveAtlasPolygon | undefined, options: DynmapCircle, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
const outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean,
|
||||
points = getCirclePoints(options, converter, outline);
|
||||
|
||||
export const updateCircle = (circle: LiveAtlasPolyline | LiveAtlasPolygon | undefined, options: LiveAtlasCircle, converter: Function): LiveAtlasPolyline | LiveAtlasPolygon => {
|
||||
if (!circle) {
|
||||
return createCircle(options, converter);
|
||||
}
|
||||
|
||||
const outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean;
|
||||
|
||||
circle.closePopup();
|
||||
circle.unbindPopup();
|
||||
circle.bindPopup(() => createPopup(options));
|
||||
circle.bindPopup(() => createPopup(options, 'CirclePopup'));
|
||||
circle.setStyle(options.style);
|
||||
circle.setLatLngs(points);
|
||||
circle.setLatLngs(getCirclePoints(options, converter, outline));
|
||||
circle.redraw();
|
||||
|
||||
return circle;
|
||||
}
|
||||
|
||||
export const createPopup = (options: DynmapCircle) => {
|
||||
const popup = document.createElement('span');
|
||||
|
||||
if (options.popupContent) {
|
||||
popup.classList.add('CirclePopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.popupContent);
|
||||
} else if (options.isHTML) {
|
||||
popup.classList.add('CirclePopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.label);
|
||||
} else {
|
||||
popup.textContent = options.label;
|
||||
}
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
export const getCirclePoints = (options: DynmapCircle, converter: Function, outline: boolean): LatLngExpression[] => {
|
||||
export const getCirclePoints = (options: LiveAtlasCircle, converter: Function, outline: boolean): LatLngExpression[] => {
|
||||
const points = [];
|
||||
|
||||
for(let i = 0; i < 360; i++) {
|
||||
@ -84,7 +64,7 @@ export const getCirclePoints = (options: DynmapCircle, converter: Function, outl
|
||||
x = options.radius[0] * Math.sin(rad) + options.location.x,
|
||||
z = options.radius[1] * Math.cos(rad) + options.location.z;
|
||||
|
||||
points.push(converter(x, options.location.y, z));
|
||||
points.push(converter({x, y:options.location.y, z}));
|
||||
}
|
||||
|
||||
if(outline && points.length) {
|
||||
|
@ -1,4 +1,20 @@
|
||||
import {LiveAtlasDynmapServerDefinition, LiveAtlasGlobalConfig, LiveAtlasServerDefinition} from "@/index";
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {LiveAtlasGlobalConfig, LiveAtlasServerDefinition} from "@/index";
|
||||
import ConfigurationError from "@/errors/ConfigurationError";
|
||||
import {DynmapUrlConfig} from "@/dynmap";
|
||||
|
||||
@ -22,10 +38,10 @@ const validateLiveAtlasConfiguration = (config: any): Map<string, LiveAtlasServe
|
||||
}
|
||||
|
||||
serverConfig.id = server;
|
||||
serverConfig.type = serverConfig.type || 'dynmap';
|
||||
|
||||
switch(serverConfig.type) {
|
||||
case 'dynmap':
|
||||
if(typeof serverConfig.pl3xmap !== 'undefined') {
|
||||
serverConfig.type = 'pl3xmap';
|
||||
} else if(typeof serverConfig.dynmap !== 'undefined') {
|
||||
if (!serverConfig.dynmap || serverConfig.dynmap.constructor !== Object) {
|
||||
throw new ConfigurationError(`Server '${server}': Dynmap configuration object missing. ${check}`);
|
||||
}
|
||||
@ -49,13 +65,10 @@ const validateLiveAtlasConfiguration = (config: any): Map<string, LiveAtlasServe
|
||||
if (!serverConfig.dynmap.sendmessage) {
|
||||
throw new ConfigurationError(`Server '${server}': Dynmap sendmessage URL missing. ${check}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'pl3xmap':
|
||||
case 'plexmap':
|
||||
if (!serverConfig.plexmap || serverConfig.plexmap.constructor !== Object) {
|
||||
throw new ConfigurationError(`Server '${server}': Pl3xmap configuration object missing. ${check}`);
|
||||
}
|
||||
serverConfig.type = 'dynmap';
|
||||
} else {
|
||||
throw new ConfigurationError(`Server '${server}': No Dynmap or Pl3xmap configuration defined. ${check}`);
|
||||
}
|
||||
|
||||
result.set(server, serverConfig);
|
||||
@ -64,7 +77,7 @@ const validateLiveAtlasConfiguration = (config: any): Map<string, LiveAtlasServe
|
||||
return result;
|
||||
};
|
||||
|
||||
const validateDynmapConfiguration = (config: DynmapUrlConfig): Map<string, LiveAtlasDynmapServerDefinition> => {
|
||||
const validateDynmapConfiguration = (config: DynmapUrlConfig): Map<string, LiveAtlasServerDefinition> => {
|
||||
const check = '\nCheck your standalone/config.js file exists and is being loaded correctly.';
|
||||
|
||||
if (!config) {
|
||||
@ -91,7 +104,7 @@ const validateDynmapConfiguration = (config: DynmapUrlConfig): Map<string, LiveA
|
||||
throw new ConfigurationError(`Dynmap sendmessage URL is missing. ${check}`);
|
||||
}
|
||||
|
||||
const result = new Map<string, LiveAtlasDynmapServerDefinition>();
|
||||
const result = new Map<string, LiveAtlasServerDefinition>();
|
||||
result.set('dynmap', {
|
||||
id: 'dynmap',
|
||||
label: 'dynmap',
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const navigationKeys = new Set<string>([
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -17,63 +17,55 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {DynmapLine} from "@/dynmap";
|
||||
import {LatLngExpression} from "leaflet";
|
||||
import LiveAtlasPolyline from "@/leaflet/vector/LiveAtlasPolyline";
|
||||
import {Coordinate, LiveAtlasLine} from "@/index";
|
||||
import {LatLngExpression} from "leaflet";
|
||||
import {createPopup, tooltipOptions} from "@/util/paths";
|
||||
|
||||
export const createLine = (options: DynmapLine, converter: Function): LiveAtlasPolyline => {
|
||||
const points = getLinePoints(options, converter),
|
||||
line = new LiveAtlasPolyline(points, {
|
||||
...options.style,
|
||||
minZoom: options.minZoom,
|
||||
maxZoom: options.maxZoom,
|
||||
});
|
||||
export const createLine = (options: LiveAtlasLine, converter: Function): LiveAtlasPolyline => {
|
||||
const points = options.points.map(projectPointsMapCallback, converter),
|
||||
line = new LiveAtlasPolyline(points, options);
|
||||
|
||||
if(options.label) {
|
||||
line.bindPopup(() => createPopup(options));
|
||||
if(options.popupContent) {
|
||||
line.bindPopup(() => createPopup(options, 'LinePopup'));
|
||||
}
|
||||
|
||||
if (options.tooltipContent) {
|
||||
line.bindTooltip(() => options.tooltipContent as string, tooltipOptions);
|
||||
}
|
||||
|
||||
return line;
|
||||
};
|
||||
|
||||
export const updateLine = (line: LiveAtlasPolyline | undefined, options: DynmapLine, converter: Function): LiveAtlasPolyline => {
|
||||
const points = getLinePoints(options, converter);
|
||||
|
||||
export const updateLine = (line: LiveAtlasPolyline | undefined, options: LiveAtlasLine, converter: Function): LiveAtlasPolyline => {
|
||||
if (!line) {
|
||||
return createLine(options, converter);
|
||||
}
|
||||
|
||||
line.closePopup();
|
||||
line.unbindPopup();
|
||||
line.bindPopup(() => createPopup(options));
|
||||
line.bindPopup(() => createPopup(options, 'LinePopup'));
|
||||
line.setStyle(options.style);
|
||||
line.setLatLngs(points);
|
||||
line.setLatLngs(options.points.map(projectPointsMapCallback, converter));
|
||||
line.redraw();
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
export const createPopup = (options: DynmapLine) => {
|
||||
const popup = document.createElement('span');
|
||||
|
||||
if (options.popupContent) {
|
||||
popup.classList.add('LinePopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.popupContent);
|
||||
} else if (options.isHTML) {
|
||||
popup.classList.add('LinePopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.label);
|
||||
const projectPointsMapCallback = function(point: Coordinate): LatLngExpression {
|
||||
if(Array.isArray(point)) {
|
||||
return projectPointsMapCallback(point);
|
||||
} else {
|
||||
popup.textContent = options.label;
|
||||
// @ts-ignore
|
||||
return this(point);
|
||||
}
|
||||
};
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
export const getLinePoints = (options: DynmapLine, converter: Function): LatLngExpression[] => {
|
||||
export const getLinePoints = (x: number[], y: number[], z: number[]): Coordinate[] => {
|
||||
const points = [];
|
||||
|
||||
for(let i = 0; i < options.x.length; i++) {
|
||||
points.push(converter(options.x[i], options.y[i], options.z[i]));
|
||||
for(let i = 0; i < x.length; i++) {
|
||||
points.push({x: x[i], y: y[i], z: z[i]});
|
||||
}
|
||||
|
||||
return points;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 James Lyne
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
|
||||
* These portions are Copyright 2020 Dynmap Contributors.
|
||||
@ -18,21 +18,12 @@
|
||||
*/
|
||||
|
||||
import {LeafletMouseEvent, Marker} from "leaflet";
|
||||
import {DynmapMarker} from "@/dynmap";
|
||||
import {GenericIcon} from "@/leaflet/icon/GenericIcon";
|
||||
import {GenericMarker} from "@/leaflet/marker/GenericMarker";
|
||||
import {LiveAtlasMarker} from "@/index";
|
||||
|
||||
export const createMarker = (options: DynmapMarker, converter: Function): Marker => {
|
||||
const marker = new GenericMarker(converter(options.location.x, options.location.y, options.location.z), {
|
||||
icon: new GenericIcon({
|
||||
icon: options.icon,
|
||||
label: options.label,
|
||||
iconSize: options.dimensions,
|
||||
isHtml: options.isHTML,
|
||||
}),
|
||||
maxZoom: options.maxZoom,
|
||||
minZoom: options.minZoom,
|
||||
});
|
||||
export const createMarker = (options: LiveAtlasMarker, converter: Function): Marker => {
|
||||
const marker = new GenericMarker(converter(options.location), options);
|
||||
|
||||
marker.on('click', (e: LeafletMouseEvent) => {
|
||||
e.target._map.panTo(e.target.getLatLng());
|
||||
@ -45,13 +36,13 @@ export const createMarker = (options: DynmapMarker, converter: Function): Marker
|
||||
return marker;
|
||||
};
|
||||
|
||||
export const updateMarker = (marker: Marker | undefined, options: DynmapMarker, converter: Function): Marker => {
|
||||
export const updateMarker = (marker: Marker | undefined, options: LiveAtlasMarker, converter: Function): Marker => {
|
||||
if (!marker) {
|
||||
return createMarker(options, converter);
|
||||
}
|
||||
|
||||
const oldLocation = marker.getLatLng(),
|
||||
newLocation = converter(options.location.x, options.location.y, options.location.z);
|
||||
newLocation = converter(options.location);
|
||||
|
||||
if(!oldLocation.equals(newLocation)) {
|
||||
marker.setLatLng(newLocation);
|
||||
@ -65,7 +56,7 @@ export const updateMarker = (marker: Marker | undefined, options: DynmapMarker,
|
||||
icon: options.icon,
|
||||
label: options.label,
|
||||
iconSize: options.dimensions,
|
||||
isHtml: options.isHTML,
|
||||
isHtml: options.isLabelHTML,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -80,17 +71,12 @@ export const updateMarker = (marker: Marker | undefined, options: DynmapMarker,
|
||||
return marker;
|
||||
};
|
||||
|
||||
export const createPopup = (options: DynmapMarker) => {
|
||||
const createPopup = (options: LiveAtlasMarker) => {
|
||||
const popup = document.createElement('span');
|
||||
|
||||
if (options.popupContent) {
|
||||
popup.classList.add('MarkerPopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.popupContent);
|
||||
} else if (options.isHTML) {
|
||||
popup.classList.add('MarkerPopup');
|
||||
popup.insertAdjacentHTML('afterbegin', options.label);
|
||||
} else {
|
||||
popup.textContent = options.label;
|
||||
}
|
||||
|
||||
return popup;
|
||||
|
52
src/util/paths.ts
Normal file
52
src/util/paths.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {Direction, LatLngExpression, PathOptions} from "leaflet";
|
||||
import {LiveAtlasPath} from "@/index";
|
||||
|
||||
export const tooltipOptions = {
|
||||
direction: 'top' as Direction,
|
||||
sticky: true,
|
||||
opacity: 1.0,
|
||||
interactive: false,
|
||||
};
|
||||
|
||||
export const arePointsEqual = (oldPoints: LatLngExpression | LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][],
|
||||
newPoints: LatLngExpression | LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][]) => {
|
||||
return JSON.stringify(oldPoints) === JSON.stringify(newPoints);
|
||||
}
|
||||
|
||||
export const isStyleEqual = (oldStyle: PathOptions, newStyle: PathOptions) => {
|
||||
return oldStyle && newStyle
|
||||
&& (oldStyle.color === newStyle.color)
|
||||
&& (oldStyle.weight === newStyle.weight)
|
||||
&& (oldStyle.opacity === newStyle.opacity)
|
||||
&& (oldStyle.fillColor === newStyle.fillColor)
|
||||
&& (oldStyle.fillOpacity === newStyle.fillOpacity)
|
||||
}
|
||||
|
||||
export const createPopup = (options: LiveAtlasPath, className: string): HTMLElement => {
|
||||
const popup = document.createElement('span');
|
||||
|
||||
if(options.isPopupHTML) {
|
||||
popup.classList.add(className);
|
||||
popup.insertAdjacentHTML('afterbegin', options.popupContent as string);
|
||||
} else {
|
||||
popup.textContent = options.popupContent as string;
|
||||
}
|
||||
|
||||
return popup;
|
||||
};
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 James Lyne
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const app = document.getElementById('app'),
|
||||
splash = document.getElementById('splash'),
|
||||
splashSpinner = document.getElementById('splash__spinner'),
|
||||
|
Loading…
Reference in New Issue
Block a user