Implement update handling for areas/lines/circles

This commit is contained in:
James Lyne 2020-12-11 15:28:51 +00:00
parent a809b68bb2
commit e77a777a66
15 changed files with 997 additions and 551 deletions

View File

@ -1,11 +1,18 @@
import axios, {AxiosResponse} from 'axios';
import {
DynmapArea, DynmapCircle,
DynmapArea,
DynmapCircle,
DynmapComponentConfig,
DynmapConfigurationResponse, DynmapLine, DynmapMap, DynmapMarker, DynmapMarkerSet, DynmapMessageConfig,
DynmapConfigurationResponse,
DynmapLine,
DynmapMap,
DynmapMarker,
DynmapMarkerSet,
DynmapMarkerSetUpdates,
DynmapMessageConfig,
DynmapPlayer,
DynmapServerConfig,
DynmapUpdateResponse,
DynmapServerConfig, DynmapUpdate,
DynmapUpdateResponse, DynmapUpdates,
DynmapWorld
} from "@/dynmap";
@ -166,27 +173,29 @@ function buildMarkers(data: any): Map<string, DynmapMarker> {
continue;
}
const marker = data[key];
markers.set(key, {
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: marker.minzoom || undefined,
maxZoom: marker.maxZoom || undefined,
popupContent: marker.desc || undefined,
});
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: marker.minzoom || undefined,
maxZoom: marker.maxZoom || undefined,
popupContent: marker.desc || undefined,
};
}
function buildAreas(data: any): Map<string, DynmapArea> {
const areas = Object.freeze(new Map()) as Map<string, DynmapArea>;
@ -195,30 +204,32 @@ function buildAreas(data: any): Map<string, DynmapArea> {
continue;
}
const area = data[key];
areas.set(key, {
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: area.minzoom || undefined,
maxZoom: area.maxZoom || undefined,
popupContent: area.desc || undefined,
});
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: area.minzoom || undefined,
maxZoom: area.maxZoom || undefined,
popupContent: area.desc || undefined,
};
}
function buildLines(data: any): Map<string, DynmapLine> {
const lines = Object.freeze(new Map()) as Map<string, DynmapLine>;
@ -227,28 +238,30 @@ function buildLines(data: any): Map<string, DynmapLine> {
continue;
}
const line = data[key];
lines.set(key, {
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: line.minzoom || undefined,
maxZoom: line.maxZoom || undefined,
popupContent: line.desc || undefined,
});
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: line.minzoom || undefined,
maxZoom: line.maxZoom || undefined,
popupContent: line.desc || undefined,
};
}
function buildCircles(data: any): Map<string, DynmapCircle> {
const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>;
@ -257,34 +270,117 @@ function buildCircles(data: any): Map<string, DynmapCircle> {
continue;
}
const circle = data[key];
circles.set(key, {
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: circle.minzoom || undefined,
maxZoom: circle.maxZoom || undefined,
popupContent: circle.desc || undefined,
});
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: circle.minzoom || undefined,
maxZoom: circle.maxZoom || undefined,
popupContent: circle.desc || undefined,
};
}
function buildUpdates(data: Array<any>): DynmapUpdates {
const updates = {
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
tiles: new Map(),
chat: [],
}
for(const entry of data) {
switch(entry.type) {
case 'component': {
if(!entry.id) {
console.warn(`Ignoring component update without an ID`);
continue;
}
if(!entry.set) {
console.warn(`Ignoring component update without a marker set`);
continue;
}
if(entry.ctype !== 'markers') {
console.warn(`Ignoring component with unknown ctype ${entry.ctype}`);
continue;
}
if(!updates.markerSets.has(entry.set)) {
updates.markerSets.set(entry.set, {
areaUpdates: [],
markerUpdates: [],
lineUpdates: [],
circleUpdates: [],
});
}
const markerSetUpdates = updates.markerSets.get(entry.set),
update: DynmapUpdate = {
id: entry.id,
removed: entry.msg.endsWith('deleted'),
};
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));
}
break;
}
case 'chat':
//TODO
break;
case 'tile':
if(!entry.name || !entry.timestamp) {
console.warn(`Ignoring tile update without a name or timestamp`);
break;
}
updates.tiles.set(entry.name, entry.timestamp);
break;
default:
console.warn(`Ignoring unknown update type ${entry.type}`);
}
}
return updates;
}
export default {
getConfiguration(): Promise<DynmapConfigurationResponse> {
return axios.get(window.config.url.configuration).then((response): DynmapConfigurationResponse => {
@ -349,6 +445,7 @@ export default {
configHash: data.configHash || 0,
timestamp: data.timestamp || 0,
players,
updates: buildUpdates(data.updates || []),
}
});
},
@ -374,6 +471,7 @@ export default {
lines = buildLines(set.lines || {});
sets.set(key, {
id: key,
label: set.label || "Unnamed set",
hidden: set.hidden || false,
priority: set.layerprio || 0,

View File

@ -1,8 +1,8 @@
<template>
<GenericMarker v-for="[id, marker] in markerSet.markers" :options="marker" :key="id" :layer-group="layerGroup"></GenericMarker>
<Areas :areas="markerSet.areas" :layer-group="layerGroup"></Areas>
<Circles :circles="markerSet.circles" :layer-group="layerGroup"></Circles>
<Lines :lines="markerSet.lines" :layer-group="layerGroup"></Lines>
<Areas :layer-group="layerGroup" :set="markerSet"></Areas>
<Circles :layer-group="layerGroup" :set="markerSet"></Circles>
<Lines :layer-group="layerGroup" :set="markerSet"></Lines>
</template>
<script lang="ts">

View File

@ -1,237 +1,106 @@
<script lang="ts">
import {defineComponent, computed} from "@vue/runtime-core";
import L, {LatLngExpression} from 'leaflet';
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
import {LayerGroup, Polyline, Polygon} from 'leaflet';
import {useStore} from "@/store";
import {DynmapArea} from "@/dynmap";
import {DynmapArea, DynmapMarkerSet} from "@/dynmap";
import {ActionTypes} from "@/store/action-types";
import {createArea, updateArea} from "@/util/areas";
import Util from '@/util';
export default defineComponent({
props: {
areas: {
type: Object as () => Map<string, DynmapArea>,
required: true
set: {
type: Object as () => DynmapMarkerSet,
required: true,
},
layerGroup: {
type: Object as () => L.LayerGroup,
type: Object as () => LayerGroup,
required: true
}
},
setup() {
setup(props) {
let updateFrame = 0;
const store = useStore(),
currentProjection = computed(() => store.state.currentProjection),
layers = Object.freeze(new Map()) as Map<string, L.Path>;
pendingUpdates = computed(() => {
const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id);
return {
layers,
currentProjection,
}
},
return markerSetUpdates && markerSetUpdates.areaUpdates.length;
}),
layers = Object.freeze(new Map()) as Map<string, Polygon | Polyline>,
watch: {
//FIXME: Prevent unnecessary repositioning when changing worlds
currentProjection() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
createAreas = () => {
const converter = Util.getPointConverter();
props.set.areas.forEach((area: DynmapArea, id: string) => {
const layer = createArea(area, converter);
layers.set(id, layer);
props.layerGroup.addLayer(layer);
});
},
deleteArea = (id: string) => {
let area = layers.get(id) as Polyline;
if(!area) {
return;
}
// eslint-disable-next-line no-unused-vars
for (const [id, area] of this.areas) {
this.updateArea(id, area, latLng);
area.remove();
layers.delete(id);
},
handlePendingUpdates = () => {
useStore().dispatch(ActionTypes.POP_AREA_UPDATES, {
markerSet: props.set.id,
amount: 10,
}).then(updates => {
const converter = Util.getPointConverter();
for(const update of updates) {
if(update.removed) {
console.log(`Deleting area ${update.id}`);
deleteArea(update.id);
} else {
console.log(`Updating/creating area ${update.id}`);
layers.set(update.id, updateArea(layers.get(update.id), update.payload as DynmapArea, converter));
}
}
if(pendingUpdates.value) {
console.log('More updates left, scheduling frame');
// eslint-disable-next-line no-unused-vars
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
} else {
updateFrame = 0;
}
});
};
//FIXME: Prevent unnecessary repositioning when changing worlds
watch(currentProjection, () => {
const converter = Util.getPointConverter();
for (const [id, area] of props.set.areas) {
updateArea(layers.get(id), area, converter);
}
}
},
});
mounted() {
this.createAreas();
},
unmounted() {
watch(pendingUpdates, (newValue, oldValue) => {
if(newValue && newValue > 0 && oldValue === 0 && !updateFrame) {
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
}
});
onMounted(() => createAreas());
onUnmounted(() => updateFrame && cancelAnimationFrame(updateFrame));
},
render() {
return null;
},
methods: {
createAreas() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
};
this.areas.forEach((area: DynmapArea, id: string) => {
this.createArea(id, area, latLng);
});
},
createArea(id: string, options: DynmapArea, latLng: Function) {
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
points = this.getPoints(options, latLng, outline),
area: L.Path = outline ? new L.Polyline(points, options.style) : new L.Polygon(points, options.style);
if (options.label) {
area.bindPopup(() => {
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);
} else {
popup.textContent = options.label;
}
return popup;
});
}
this.layers.set(id, area);
this.layerGroup.addLayer(area);
},
updateArea(id: string, options: DynmapArea, latLng: Function) {
let area = this.layers.get(id) as L.Polyline,
outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean,
points = this.getPoints(options, latLng, outline);
if (!area) {
return;
}
area.setLatLngs(points);
area.redraw();
},
getPoints(options: DynmapArea, latLng: Function, outline: boolean): LatLngExpression[] | LatLngExpression[][] {
if (options.x.length === 2) { /* Only 2 points */
if (options.y[0] === options.y[1]) {
return this.get2DBoxPoints(options, latLng, outline);
} else {
return this.get3DBoxPoints(options, latLng);
}
} else {
if (options.y[0] === options.y[1]) {
return this.get2DShapePoints(options, latLng, outline);
} else {
return this.get3DShapePoints(options, latLng);
}
}
},
get3DBoxPoints(options: DynmapArea, latLng: 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];
return [
[
latLng(minX, minY, minZ),
latLng(maxX, minY, minZ),
latLng(maxX, minY, maxZ),
latLng(minX, minY, maxZ)
], [
latLng(minX, maxY, minZ),
latLng(maxX, maxY, minZ),
latLng(maxX, maxY, maxZ),
latLng(minX, maxY, maxZ)
], [
latLng(minX, minY, minZ),
latLng(minX, maxY, minZ),
latLng(maxX, maxY, minZ),
latLng(maxX, minY, minZ)
], [
latLng(maxX, minY, minZ),
latLng(maxX, maxY, minZ),
latLng(maxX, maxY, maxZ),
latLng(maxX, minY, maxZ)
], [
latLng(minX, minY, maxZ),
latLng(minX, maxY, maxZ),
latLng(maxX, maxY, maxZ),
latLng(maxX, minY, maxZ)
], [
latLng(minX, minY, minZ),
latLng(minX, maxY, minZ),
latLng(minX, maxY, maxZ),
latLng(minX, minY, maxZ)
]
];
},
get2DBoxPoints(options: DynmapArea, latLng: 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];
if (outline) {
return [
latLng(minX, minY, minZ),
latLng(maxX, minY, minZ),
latLng(maxX, minY, maxZ),
latLng(minX, minY, maxZ),
latLng(minX, minY, minZ)
];
} else {
return [
latLng(minX, minY, minZ),
latLng(maxX, minY, minZ),
latLng(maxX, minY, maxZ),
latLng(minX, minY, maxZ)
];
}
},
get3DShapePoints(options: DynmapArea, latLng: Function): LatLngExpression[][] {
const toplist = [],
botlist = [],
polylist = [];
for (let i = 0; i < options.x.length; i++) {
toplist[i] = latLng(options.x[i], options.y[0], options.z[i]);
botlist[i] = latLng(options.x[i], options.y[1], options.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;
}
polylist[options.x.length] = botlist;
polylist[options.x.length + 1] = toplist;
return polylist;
},
get2DShapePoints(options: DynmapArea, latLng: Function, outline: boolean): LatLngExpression[] {
const points = [];
for (let i = 0; i < options.x.length; i++) {
points[i] = latLng(options.x[i], options.y[1], options.z[i]);
}
if (outline) {
points.push(points[0]);
}
return points;
}
}
})
});
</script>
<style scoped>
</style>

View File

@ -1,140 +1,111 @@
<script lang="ts">
import {defineComponent, computed} from "@vue/runtime-core";
import L, {LatLngExpression} from 'leaflet';
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
import {Polyline, LayerGroup, Polygon} from 'leaflet';
import {useStore} from "@/store";
import {DynmapCircle} from "@/dynmap";
import {DynmapCircle, DynmapMarkerSet} from "@/dynmap";
import {ActionTypes} from "@/store/action-types";
import {createCircle, updateCircle} from "@/util/circles";
import Util from '@/util';
export default defineComponent({
props: {
circles: {
type: Object as () => Map<string, DynmapCircle>,
required: true
set: {
type: Object as () => DynmapMarkerSet,
required: true,
},
layerGroup: {
type: Object as () => L.LayerGroup,
type: Object as () => LayerGroup,
required: true
}
},
setup() {
setup(props) {
let updateFrame = 0;
const store = useStore(),
currentProjection = computed(() => store.state.currentProjection),
layers = Object.freeze(new Map()) as Map<string, L.Path>;
pendingUpdates = computed(() => {
const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id);
return {
layers,
currentProjection,
}
},
return markerSetUpdates && markerSetUpdates.circleUpdates.length;
}),
layers = Object.freeze(new Map<string, Polyline | Polygon>()),
watch: {
//FIXME: Prevent unnecessary repositioning when changing worlds
currentProjection() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
createCircles = () => {
const converter = Util.getPointConverter();
props.set.circles.forEach((circle: DynmapCircle, id: string) => {
const layer = createCircle(circle, converter);
layers.set(id, layer);
props.layerGroup.addLayer(layer);
});
},
deleteCircle = (id: string) => {
let circle = layers.get(id) as Polyline;
if (!circle) {
return;
}
// eslint-disable-next-line no-unused-vars
for (const [id, circle] of this.circles) {
this.updateCircle(id, circle, latLng);
circle.remove();
layers.delete(id);
},
handlePendingUpdates = () => {
useStore().dispatch(ActionTypes.POP_CIRCLE_UPDATES, {
markerSet: props.set.id,
amount: 10,
}).then(updates => {
const converter = Util.getPointConverter();
for(const update of updates) {
if(update.removed) {
console.log(`Deleting circle ${update.id}`);
deleteCircle(update.id);
} else {
console.log(`Updating/creating circle ${update.id}`);
const layer = updateCircle(layers.get(update.id), update.payload as DynmapCircle, converter)
if(!layers.has(update.id)) {
layers.set(update.id, layer);
props.layerGroup.addLayer(layer);
}
}
}
if(pendingUpdates.value) {
console.log('More updates left, scheduling frame');
// eslint-disable-next-line no-unused-vars
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
} else {
updateFrame = 0;
}
});
};
//FIXME: Prevent unnecessary repositioning when changing worlds
watch(currentProjection, () => {
const converter = Util.getPointConverter();
for (const [id, circle] of props.set.circles) {
updateCircle(layers.get(id), circle, converter);
}
}
},
});
mounted() {
this.createCircles();
},
unmounted() {
watch(pendingUpdates, (newValue, oldValue) => {
if(newValue && newValue > 0 && oldValue === 0 && !updateFrame) {
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
}
});
onMounted(() => createCircles());
onUnmounted(() => updateFrame && cancelAnimationFrame(updateFrame));
},
render() {
return null;
},
methods: {
createCircles() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
};
this.circles.forEach((circle: DynmapCircle, id: string) => {
this.createCircle(id, circle, latLng);
});
},
createCircle(id: string, options: DynmapCircle, latLng: Function) {
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
points = this.getPoints(options, latLng, outline);
let circle;
if(outline) {
circle = new L.Polyline(points, options.style);
} else {
circle = new L.Polygon(points, options.style);
}
if(options.label) {
circle.bindPopup(() => {
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;
});
}
this.layers.set(id, circle);
this.layerGroup.addLayer(circle);
},
getPoints(options: DynmapCircle, latLng: Function, outline: boolean): LatLngExpression[] {
const points = [];
for(let i = 0; i < 360; i++) {
const rad = i * Math.PI / 180.0,
x = options.radius[0] * Math.sin(rad) + options.location.x,
z = options.radius[1] * Math.cos(rad) + options.location.z;
console.log(x,options.location.y,z, latLng(x, options.location.y, z));
points.push(latLng(x, options.location.y, z));
}
if(outline && points.length) {
points.push(points[0]);
}
return points;
},
updateCircle(id: string, options: DynmapCircle, latLng: Function) {
let circle = this.layers.get(id) as L.Polyline,
outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean,
points = this.getPoints(options, latLng, outline);
if (!circle) {
return;
}
circle.setLatLngs(points);
circle.redraw();
},
}
})
});
</script>
<style scoped>
</style>

View File

@ -1,122 +1,111 @@
<script lang="ts">
import {defineComponent, computed} from "@vue/runtime-core";
import L, {LatLngExpression} from 'leaflet';
import {defineComponent, computed, onMounted, onUnmounted, watch} from "@vue/runtime-core";
import {Polyline, LayerGroup, Polygon} from 'leaflet';
import {useStore} from "@/store";
import {DynmapLine} from "@/dynmap";
import {DynmapLine, DynmapMarkerSet} from "@/dynmap";
import {ActionTypes} from "@/store/action-types";
import {createLine, updateLine} from "@/util/lines";
import Util from '@/util';
export default defineComponent({
props: {
lines: {
type: Object as () => Map<string, DynmapLine>,
required: true
set: {
type: Object as () => DynmapMarkerSet,
required: true,
},
layerGroup: {
type: Object as () => L.LayerGroup,
type: Object as () => LayerGroup,
required: true
}
},
setup() {
setup(props) {
let updateFrame = 0;
const store = useStore(),
currentProjection = computed(() => store.state.currentProjection),
layers = Object.freeze(new Map()) as Map<string, L.Path>;
pendingUpdates = computed(() => {
const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id);
return {
layers,
currentProjection,
}
},
return markerSetUpdates && markerSetUpdates.lineUpdates.length;
}),
layers = Object.freeze(new Map<string, Polyline | Polygon>()),
watch: {
//FIXME: Prevent unnecessary repositioning when changing worlds
currentProjection() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
createLines = () => {
const converter = Util.getPointConverter();
props.set.lines.forEach((line: DynmapLine, id: string) => {
const layer = createLine(line, converter);
layers.set(id, layer);
props.layerGroup.addLayer(layer);
});
},
deleteLine = (id: string) => {
let line = layers.get(id) as Polyline;
if (!line) {
return;
}
// eslint-disable-next-line no-unused-vars
for (const [id, line] of this.lines) {
this.updateLine(id, line, latLng);
line.remove();
layers.delete(id);
},
handlePendingUpdates = () => {
useStore().dispatch(ActionTypes.POP_LINE_UPDATES, {
markerSet: props.set.id,
amount: 10,
}).then(updates => {
const converter = Util.getPointConverter();
for(const update of updates) {
if(update.removed) {
console.log(`Deleting line ${update.id}`);
deleteLine(update.id);
} else {
console.log(`Updating/creating line ${update.id}`);
const layer = updateLine(layers.get(update.id), update.payload as DynmapLine, converter)
if(!layers.has(update.id)) {
layers.set(update.id, layer);
props.layerGroup.addLayer(layer);
}
}
}
if(pendingUpdates.value) {
console.log('More updates left, scheduling frame');
// eslint-disable-next-line no-unused-vars
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
} else {
updateFrame = 0;
}
});
};
//FIXME: Prevent unnecessary repositioning when changing worlds
watch(currentProjection, () => {
const converter = Util.getPointConverter();
for (const [id, line] of props.set.lines) {
updateLine(layers.get(id), line, converter);
}
}
},
});
mounted() {
this.createLines();
},
unmounted() {
watch(pendingUpdates, (newValue, oldValue) => {
if(newValue && newValue > 0 && oldValue === 0 && !updateFrame) {
updateFrame = requestAnimationFrame(() => handlePendingUpdates());
}
});
onMounted(() => createLines());
onUnmounted(() => updateFrame && cancelAnimationFrame(updateFrame));
},
render() {
return null;
},
methods: {
createLines() {
const projection = useStore().state.currentProjection,
latLng = (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
};
this.lines.forEach((line: DynmapLine, id: string) => {
this.createLine(id, line, latLng);
});
},
createLine(id: string, options: DynmapLine, latLng: Function) {
const points = this.getPoints(options, latLng),
line= new L.Polyline(points, options.style);
if(options.label) {
line.bindPopup(() => {
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);
} else {
popup.textContent = options.label;
}
return popup;
});
}
this.layers.set(id, line);
this.layerGroup.addLayer(line);
},
getPoints(options: DynmapLine, latLng: Function): LatLngExpression[] {
const points = [];
for(let i = 0; i < options.x.length; i++) {
points.push(latLng(options.x[i], options.y[i], options.z[i]));
}
return points;
},
updateLine(id: string, options: DynmapLine, latLng: Function) {
let line = this.layers.get(id) as L.Polyline,
points = this.getPoints(options, latLng);
if (!line) {
return;
}
line.setLatLngs(points);
line.redraw();
},
}
})
});
</script>
<style scoped>
</style>

38
src/dynmap.d.ts vendored
View File

@ -139,8 +139,8 @@ interface DynmapUpdateResponse {
configHash: number;
playerCount: number;
players: Set<DynmapPlayer>;
updates: DynmapUpdates;
timestamp: number;
//TODO: Tiles etc
}
interface DynmapPlayer {
@ -153,6 +153,7 @@ interface DynmapPlayer {
}
interface DynmapMarkerSet {
id: string,
label: string;
hidden: boolean;
priority: number;
@ -210,3 +211,38 @@ interface DynmapCircle {
maxZoom?: number;
popupContent?: string;
}
interface DynmapUpdates {
markerSets: Map<string, DynmapMarkerSetUpdates>,
tiles: Map<string, number>,
chat: Array<any> //TODO
}
interface DynmapMarkerSetUpdates {
markerUpdates: Array<DynmapMarkerUpdate>
areaUpdates: Array<DynmapAreaUpdate>
circleUpdates: Array<DynmapCircleUpdate>
lineUpdates: Array<DynmapLineUpdate>
}
interface DynmapUpdate {
id: string,
removed: boolean,
payload?: any,
}
interface DynmapMarkerUpdate extends DynmapUpdate {
payload?: DynmapMarker
}
interface DynmapAreaUpdate extends DynmapUpdate {
payload?: DynmapArea
}
interface DynmapCircleUpdate extends DynmapUpdate {
payload?: DynmapCircle
}
interface DynmapLineUpdate extends DynmapUpdate {
payload?: DynmapLine
}

View File

@ -3,4 +3,8 @@ export enum ActionTypes {
GET_UPDATE = "getUpdate",
GET_MARKER_SETS = "getMarkerSets",
SET_PLAYERS = "setPlayers",
POP_MARKER_UPDATES = "popMarkerUpdates",
POP_AREA_UPDATES = "popAreaUpdates",
POP_CIRCLE_UPDATES = "popCircleUpdates",
POP_LINE_UPDATES = "popLineUpdates",
}

View File

@ -4,7 +4,14 @@ import {State} from "@/store/state";
import {ActionTypes} from "@/store/action-types";
import API from '@/api';
import {Mutations} from "@/store/mutations";
import {DynmapConfigurationResponse, DynmapMarkerSet, DynmapPlayer, DynmapUpdateResponse} from "@/dynmap";
import {
DynmapAreaUpdate, DynmapCircleUpdate,
DynmapConfigurationResponse, DynmapLineUpdate,
DynmapMarkerSet,
DynmapMarkerUpdate,
DynmapPlayer,
DynmapUpdateResponse
} from "@/dynmap";
type AugmentedActionContext = {
commit<K extends keyof Mutations>(
@ -27,6 +34,22 @@ export interface Actions {
{commit}: AugmentedActionContext,
payload: Set<DynmapPlayer>
):Promise<Map<string, DynmapMarkerSet>>
[ActionTypes.POP_MARKER_UPDATES](
{commit}: AugmentedActionContext,
payload: {markerSet: string, amount: number}
): Promise<DynmapMarkerUpdate[]>
[ActionTypes.POP_AREA_UPDATES](
{commit}: AugmentedActionContext,
payload: {markerSet: string, amount: number}
): Promise<DynmapAreaUpdate[]>
[ActionTypes.POP_CIRCLE_UPDATES](
{commit}: AugmentedActionContext,
payload: {markerSet: string, amount: number}
): Promise<DynmapCircleUpdate[]>
[ActionTypes.POP_LINE_UPDATES](
{commit}: AugmentedActionContext,
payload: {markerSet: string, amount: number}
): Promise<DynmapLineUpdate[]>
}
export const actions: ActionTree<State, State> & Actions = {
@ -52,10 +75,11 @@ export const actions: ActionTree<State, State> & Actions = {
return Promise.reject("No current world");
}
return API.getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.getUTCMilliseconds()).then(update => {
return API.getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.valueOf()).then(update => {
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);
return dispatch(ActionTypes.SET_PLAYERS, update.players).then(() => {
return update;
@ -97,5 +121,58 @@ export const actions: ActionTree<State, State> & Actions = {
return markerSets;
});
}
},
[ActionTypes.POP_MARKER_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapMarkerUpdate[]> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return Promise.resolve([]);
}
const updates = state.pendingSetUpdates.get(markerSet)!.markerUpdates.slice(0, amount);
commit(MutationTypes.POP_MARKER_UPDATES, {markerSet, amount});
return Promise.resolve(updates);
},
[ActionTypes.POP_AREA_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapAreaUpdate[]> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return Promise.resolve([]);
}
const updates = state.pendingSetUpdates.get(markerSet)!.areaUpdates.slice(0, amount);
commit(MutationTypes.POP_AREA_UPDATES, {markerSet, amount});
return Promise.resolve(updates);
},
[ActionTypes.POP_CIRCLE_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapCircleUpdate[]> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return Promise.resolve([]);
}
const updates = state.pendingSetUpdates.get(markerSet)!.circleUpdates.slice(0, amount);
commit(MutationTypes.POP_CIRCLE_UPDATES, {markerSet, amount});
return Promise.resolve(updates);
},
[ActionTypes.POP_LINE_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapLineUpdate[]> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return Promise.resolve([]);
}
const updates = state.pendingSetUpdates.get(markerSet)!.lineUpdates.slice(0, amount);
commit(MutationTypes.POP_LINE_UPDATES, {markerSet, amount});
return Promise.resolve(updates);
},
}

View File

@ -7,6 +7,11 @@ export enum MutationTypes {
ADD_WORLD = 'addWorld',
SET_WORLD_STATE = 'setWorldState',
SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp',
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
POP_MARKER_UPDATES = 'popMarkerUpdates',
POP_AREA_UPDATES = 'popAreaUpdates',
POP_CIRCLE_UPDATES = 'popCircleUpdates',
POP_LINE_UPDATES = 'popLineUpdates',
INCREMENT_REQUEST_ID = 'incrementRequestId',
SET_PLAYERS = 'setPlayers',
SET_PLAYERS_ASYNC = 'setPlayersAsync',

View File

@ -2,7 +2,17 @@ import {MutationTree} from "vuex";
import {MutationTypes} from "@/store/mutation-types";
import {State} from "@/store/state";
import {
DynmapComponentConfig, DynmapMarkerSet,
DynmapArea,
DynmapAreaUpdate,
DynmapCircle,
DynmapCircleUpdate,
DynmapComponentConfig,
DynmapLine,
DynmapLineUpdate,
DynmapMarker,
DynmapMarkerSet,
DynmapMarkerSetUpdates,
DynmapMarkerUpdate,
DynmapMessageConfig,
DynmapPlayer,
DynmapServerConfig,
@ -25,9 +35,15 @@ export type Mutations<S = State> = {
[MutationTypes.ADD_WORLD](state: S, world: DynmapWorld): void
[MutationTypes.SET_WORLD_STATE](state: S, worldState: DynmapWorldState): void
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
[MutationTypes.POP_MARKER_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapMarkerUpdate>
[MutationTypes.POP_AREA_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapAreaUpdate>
[MutationTypes.POP_CIRCLE_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapCircleUpdate>
[MutationTypes.POP_LINE_UPDATES](state: S, payload: {markerSet: string, amount: number}): Array<DynmapLineUpdate>
[MutationTypes.INCREMENT_REQUEST_ID](state: S): void
// [MutationTypes.SET_PLAYERS](state: S, players: Array<DynmapPlayer>): void
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): void
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer>
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
[MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void
[MutationTypes.SET_CURRENT_PROJECTION](state: S, payload: DynmapProjection): void
@ -73,6 +89,16 @@ export const mutations: MutationTree<State> & Mutations = {
//Sets the existing marker sets from the last marker fetch
[MutationTypes.SET_MARKER_SETS](state: State, markerSets: Map<string, DynmapMarkerSet>) {
state.markerSets = markerSets;
state.pendingSetUpdates.clear();
for(const entry of markerSets) {
state.pendingSetUpdates.set(entry[0], {
markerUpdates: [],
areaUpdates: [],
circleUpdates: [],
lineUpdates: [],
});
}
},
[MutationTypes.ADD_WORLD](state: State, world: DynmapWorld) {
@ -89,46 +115,101 @@ export const mutations: MutationTree<State> & Mutations = {
state.updateTimestamp = timestamp;
},
//Sets the timestamp of the last update fetch
[MutationTypes.ADD_MARKER_SET_UPDATES](state: State, updates: Map<string, DynmapMarkerSetUpdates>) {
for(const entry of updates) {
if(!state.markerSets.has(entry[0])) {
console.log(`Marker set ${entry[0]} doesn't exist`);
continue;
}
const set = state.markerSets.get(entry[0]) as DynmapMarkerSet,
setUpdates = state.pendingSetUpdates.get(entry[0]) as DynmapMarkerSetUpdates;
//Update non-reactive lists
for(const update of entry[1].markerUpdates) {
if(update.removed) {
set.markers.delete(update.id);
} else {
set.markers.set(update.id, update.payload as DynmapMarker);
}
}
for(const update of entry[1].areaUpdates) {
if(update.removed) {
set.areas.delete(update.id);
} else {
set.areas.set(update.id, update.payload as DynmapArea);
}
}
for(const update of entry[1].circleUpdates) {
if(update.removed) {
set.circles.delete(update.id);
} else {
set.circles.set(update.id, update.payload as DynmapCircle);
}
}
for(const update of entry[1].lineUpdates) {
if(update.removed) {
set.lines.delete(update.id);
} else {
set.lines.set(update.id, update.payload as DynmapLine);
}
}
//Add to reactive pending updates lists
setUpdates.markerUpdates = setUpdates.markerUpdates.concat(entry[1].markerUpdates);
setUpdates.areaUpdates = setUpdates.areaUpdates.concat(entry[1].areaUpdates);
setUpdates.circleUpdates = setUpdates.circleUpdates.concat(entry[1].circleUpdates);
setUpdates.lineUpdates = setUpdates.lineUpdates.concat(entry[1].lineUpdates);
}
},
[MutationTypes.POP_MARKER_UPDATES](state: State, {markerSet, amount}): Array<DynmapMarkerUpdate> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return [];
}
return state.pendingSetUpdates.get(markerSet)!.markerUpdates.splice(0, amount);
},
[MutationTypes.POP_AREA_UPDATES](state: State, {markerSet, amount}): Array<DynmapAreaUpdate> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return [];
}
return state.pendingSetUpdates.get(markerSet)!.areaUpdates.splice(0, amount);
},
[MutationTypes.POP_CIRCLE_UPDATES](state: State, {markerSet, amount}): Array<DynmapCircleUpdate> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return [];
}
return state.pendingSetUpdates.get(markerSet)!.circleUpdates.splice(0, amount);
},
[MutationTypes.POP_LINE_UPDATES](state: State, {markerSet, amount}): Array<DynmapLineUpdate> {
if(!state.markerSets.has(markerSet)) {
console.log(`Marker set ${markerSet} doesn't exist`);
return [];
}
return state.pendingSetUpdates.get(markerSet)!.lineUpdates.splice(0, amount);
},
//Increments the request id for the next update fetch
[MutationTypes.INCREMENT_REQUEST_ID](state: State) {
state.updateRequestId++;
},
// [MutationTypes.SET_PLAYERS](state: State, players: Array<DynmapPlayer>) {
// const existingPlayers: Set<string> = new Set();
//
// players.forEach(player => {
// existingPlayers.add(player.account);
//
// if (state.players.has(player.account)) {
// const existing = state.players.get(player.account);
//
// existing!.health = player.health;
// existing!.armor = player.armor;
// existing!.location = Object.assign(existing!.location, player.location);
// existing!.name = player.name;
// existing!.sort = player.sort;
// } else {
// state.players.set(player.account, {
// account: player.account,
// health: player.health,
// armor: player.armor,
// location: player.location,
// name: player.name,
// sort: player.sort,
// });
// }
// });
//
// for (const key of state.players.keys()) {
// if (!existingPlayers.has(key)) {
// state.players.delete(key);
// }
// }
// },
//Set up to 10 players at once, returning the rest for future setting
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<DynmapPlayer>) {
// Set up to 10 players at once
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<DynmapPlayer>): Set<DynmapPlayer> {
let count = 0;
for(const player of players) {
@ -154,9 +235,11 @@ export const mutations: MutationTree<State> & Mutations = {
players.delete(player);
if(++count >= 10) {
return players;
break;
}
}
return players;
},
//Removes all players not found in the provided keep set
@ -184,6 +267,8 @@ export const mutations: MutationTree<State> & Mutations = {
if(state.currentWorld !== newWorld) {
state.currentWorld = state.worlds.get(worldName);
state.markerSets.clear();
state.pendingSetUpdates.clear();
state.pendingTileUpdates = [];
}
state.currentMap = state.maps.get(mapName);

View File

@ -1,6 +1,6 @@
import {
DynmapComponentConfig,
DynmapMap, DynmapMarker, DynmapMarkerSet,
DynmapMap, DynmapMarkerSet, DynmapMarkerSetUpdates,
DynmapMessageConfig,
DynmapPlayer,
DynmapServerConfig,
@ -18,9 +18,11 @@ export type State = {
players: Map<string, DynmapPlayer>;
markerSets: Map<string, DynmapMarkerSet>;
pendingSetUpdates: Map<string, DynmapMarkerSetUpdates>;
pendingTileUpdates: Array<string>;
following?: DynmapPlayer;
// currentServer?: string;
currentWorldState: DynmapWorldState;
currentWorld?: DynmapWorld;
currentMap?: DynmapMap;
@ -65,7 +67,10 @@ export const state: State = {
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
maps: new Map(), //Defined maps from configuration.json
players: new Map(), //Online players from world.json
markerSets: new Map(), //Markers from world_markers.json. Contents of each set isn't reactive for performance reasons.
pendingSetUpdates: new Map(), //Pending updates to markers/areas/etc for each marker set
pendingTileUpdates: [], //Pending updates to map tiles
//Dynmap optional components
components: {
@ -73,6 +78,7 @@ export const state: State = {
markers: {
showLabels: false,
},
// Optional "playermarkers" component. Settings for online player markers.
// If not present, player markers will be disabled
playerMarkers: undefined,
@ -83,22 +89,23 @@ export const state: State = {
//Optional clock component. Used for both "digitalclock" and "timeofdayclock". Shows world time/weather.
clockControl: undefined,
//Optional "link" component. Adds button to get url for current position
//Optional "link" component. Adds button to copy url for current position
linkControl: false,
//Optional "logo" controls.
logoControls: [],
},
following: undefined,
currentWorld: undefined,
currentMap: undefined,
currentProjection: new DynmapProjection(), //Projection for converting location <-> latlg. Object itself isn't reactive for performance reasons
currentWorldState: {
raining: false,
thundering: false,
timeOfDay: 0,
},
currentMap: undefined,
currentProjection: new DynmapProjection(), //Projection for converting location <-> latlg. Object itself isn't reactive for performance reasons
updateRequestId: 0,
updateTimestamp: new Date(),

View File

@ -1,4 +1,5 @@
import {DynmapPlayer} from "@/dynmap";
import {useStore} from "@/store";
const headCache = new Map<DynmapPlayer, HTMLImageElement>();
@ -49,5 +50,13 @@ export default {
}
return base + addition;
},
getPointConverter() {
const projection = useStore().state.currentProjection;
return (x: number, y: number, z: number) => {
return projection.locationToLatLng({x, y, z});
};
}
}

170
src/util/areas.ts Normal file
View File

@ -0,0 +1,170 @@
import {LatLngExpression, Polygon, Polyline} from "leaflet";
import {DynmapArea} from "@/dynmap";
export const createArea = (options: DynmapArea, converter: Function): Polyline | Polygon => {
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
points = getPoints(options, converter, outline),
area = outline ? new Polyline(points, options.style) : new Polygon(points, options.style);
if (options.label) {
area.bindPopup(() => createPopup(options));
}
return area;
};
export const updateArea = (area: Polyline | Polygon | undefined, options: DynmapArea, converter: Function): Polyline | Polygon => {
const outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean,
points = getPoints(options, converter, outline);
if (!area) {
return createArea(options, converter);
}
area.unbindPopup();
area.bindPopup(() => createPopup(options));
area.setStyle(options.style);
area.setLatLngs(points);
area.redraw();
return area;
};
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);
} else {
popup.textContent = options.label;
}
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);
} else {
return get3DBoxPoints(options, converter);
}
} else {
if (options.y[0] === options.y[1]) {
return get2DShapePoints(options, converter, outline);
} else {
return get3DShapePoints(options, converter);
}
}
};
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];
return [
[
converter(minX, minY, minZ),
converter(maxX, minY, minZ),
converter(maxX, minY, maxZ),
converter(minX, minY, maxZ)
], [
converter(minX, maxY, minZ),
converter(maxX, maxY, minZ),
converter(maxX, maxY, maxZ),
converter(minX, maxY, maxZ)
], [
converter(minX, minY, minZ),
converter(minX, maxY, minZ),
converter(maxX, maxY, minZ),
converter(maxX, minY, minZ)
], [
converter(maxX, minY, minZ),
converter(maxX, maxY, minZ),
converter(maxX, maxY, maxZ),
converter(maxX, minY, maxZ)
], [
converter(minX, minY, maxZ),
converter(minX, maxY, maxZ),
converter(maxX, maxY, maxZ),
converter(maxX, minY, maxZ)
], [
converter(minX, minY, minZ),
converter(minX, maxY, minZ),
converter(minX, maxY, maxZ),
converter(minX, minY, 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];
if (outline) {
return [
converter(minX, minY, minZ),
converter(maxX, minY, minZ),
converter(maxX, minY, maxZ),
converter(minX, minY, maxZ),
converter(minX, minY, minZ)
];
} else {
return [
converter(minX, minY, minZ),
converter(maxX, minY, minZ),
converter(maxX, minY, maxZ),
converter(minX, minY, maxZ)
];
}
};
export const get3DShapePoints = (options: DynmapArea, converter: Function): LatLngExpression[][] => {
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 < 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;
}
polylist[options.x.length] = botlist;
polylist[options.x.length + 1] = toplist;
return polylist;
};
export const get2DShapePoints = (options: DynmapArea, converter: Function, outline: boolean): LatLngExpression[] => {
const points = [];
for (let i = 0; i < options.x.length; i++) {
points[i] = converter(options.x[i], options.y[1], options.z[i]);
}
if (outline) {
points.push(points[0]);
}
return points;
}

71
src/util/circles.ts Normal file
View File

@ -0,0 +1,71 @@
import {DynmapCircle} from "@/dynmap";
import {Polyline, Polygon, LatLngExpression} from "leaflet";
export const createCircle = (options: DynmapCircle, converter: Function): Polyline | Polygon => {
const outline = !options.style.fillOpacity || (options.style.fillOpacity <= 0),
points = getCirclePoints(options, converter, outline);
let circle;
if(outline) {
circle = new Polyline(points, options.style);
} else {
circle = new Polygon(points, options.style);
}
if(options.label) {
circle.bindPopup(() => createPopup(options));
}
return circle;
};
export const updateCircle = (circle: Polyline | Polygon | undefined, options: DynmapCircle, converter: Function): Polyline | Polygon => {
const outline = (options.style && options.style.fillOpacity && (options.style.fillOpacity <= 0)) as boolean,
points = getCirclePoints(options, converter, outline);
if (!circle) {
return createCircle(options, converter);
}
circle.unbindPopup();
circle.bindPopup(() => createPopup(options));
circle.setStyle(options.style);
circle.setLatLngs(points);
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[] => {
const points = [];
for(let i = 0; i < 360; i++) {
const rad = i * Math.PI / 180.0,
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));
}
if(outline && points.length) {
points.push(points[0]);
}
return points;
};

55
src/util/lines.ts Normal file
View File

@ -0,0 +1,55 @@
import {DynmapLine} from "@/dynmap";
import {Polyline, Polygon, LatLngExpression} from "leaflet";
export const createLine = (options: DynmapLine, converter: Function): Polyline | Polygon => {
const points = getLinePoints(options, converter),
line = new Polyline(points, options.style);
if(options.label) {
line.bindPopup(() => createPopup(options));
}
return line;
};
export const updateLine = (line: Polyline | Polygon | undefined, options: DynmapLine, converter: Function): Polyline | Polygon => {
const points = getLinePoints(options, converter);
if (!line) {
return createLine(options, converter);
}
line.unbindPopup();
line.bindPopup(() => createPopup(options));
line.setStyle(options.style);
line.setLatLngs(points);
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);
} else {
popup.textContent = options.label;
}
return popup;
}
export const getLinePoints = (options: DynmapLine, converter: Function): LatLngExpression[] => {
const points = [];
for(let i = 0; i < options.x.length; i++) {
points.push(converter(options.x[i], options.y[i], options.z[i]));
}
return points;
};