LiveAtlas/src/components/Map.vue

285 lines
8.9 KiB
Vue
Raw Normal View History

2020-12-16 16:54:41 +00:00
<!--
- 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.
-->
2020-12-01 23:20:38 +00:00
<template>
<div class="map" :style="{'background-color': mapBackground }">
<MapLayer v-for="[name, map] in maps" :key="name" :map="map" :name="name" :leaflet="leaflet"></MapLayer>
<PlayersLayer v-if="playerMarkersEnabled" :leaflet="leaflet"></PlayersLayer>
<MarkerSetLayer v-for="[name, markerSet] in markerSets" :key="name" :markerSet="markerSet" :leaflet="leaflet"></MarkerSetLayer>
<LogoControl v-for="(logo, index) in logoControls" :key="index" :options="logo" :leaflet="leaflet"></LogoControl>
<CoordinatesControl v-if="coordinatesControlEnabled" :leaflet="leaflet"></CoordinatesControl>
<LinkControl v-if="linkControlEnabled" :leaflet="leaflet"></LinkControl>
<ClockControl v-if="clockControlEnabled" :leaflet="leaflet"></ClockControl>
2020-12-17 15:25:14 +00:00
<ChatControl v-if="false" :leaflet="leaflet"></ChatControl>
2020-12-01 23:20:38 +00:00
</div>
</template>
<script lang="ts">
import {computed, ref, defineComponent} from "@vue/runtime-core";
import {CRS, LatLng} from 'leaflet';
2020-12-01 23:20:38 +00:00
import {useStore} from '@/store';
import MapLayer from "@/components/map/layer/MapLayer.vue";
import PlayersLayer from "@/components/map/layer/PlayersLayer.vue";
import MarkerSetLayer from "@/components/map/layer/MarkerSetLayer.vue";
import CoordinatesControl from "@/components/map/control/CoordinatesControl.vue";
import ClockControl from "@/components/map/control/ClockControl.vue";
import LinkControl from "@/components/map/control/LinkControl.vue";
2020-12-17 14:50:12 +00:00
import ChatControl from "@/components/map/control/ChatControl.vue";
2020-12-01 23:20:38 +00:00
import LogoControl from "@/components/map/control/LogoControl.vue";
import {MutationTypes} from "@/store/mutation-types";
import {Coordinate, DynmapPlayer} from "@/dynmap";
2020-12-01 23:20:38 +00:00
import {ActionTypes} from "@/store/action-types";
2020-12-11 21:38:50 +00:00
import DynmapMap from "@/leaflet/DynmapMap";
2020-12-15 01:51:19 +00:00
import {LoadingControl} from "@/leaflet/control/LoadingControl";
2020-12-01 23:20:38 +00:00
export default defineComponent({
components: {
MapLayer,
PlayersLayer,
MarkerSetLayer,
CoordinatesControl,
ClockControl,
LinkControl,
2020-12-17 14:50:12 +00:00
ChatControl,
2020-12-01 23:20:38 +00:00
LogoControl
},
setup() {
const store = useStore(),
2020-12-12 22:04:56 +00:00
leaflet = undefined as DynmapMap | undefined,
2020-12-01 23:20:38 +00:00
maps = computed(() => store.state.maps),
markerSets = computed(() => store.state.markerSets),
configuration = computed(() => store.state.configuration),
playerMarkersEnabled = computed(() => store.getters.playerMarkersEnabled),
coordinatesControlEnabled = computed(() => store.getters.coordinatesControlEnabled),
clockControlEnabled = computed(() => store.getters.clockControlEnabled),
linkControlEnabled = computed(() => store.state.components.linkControl),
2020-12-17 14:50:12 +00:00
chatEnabled = computed(() => store.state.components.chat),
2020-12-01 23:20:38 +00:00
logoControls = computed(() => store.state.components.logoControls),
currentWorld = computed(() => store.state.currentWorld),
currentMap = computed(() => store.state.currentMap),
currentProjection = computed(() => store.state.currentProjection),
2020-12-17 00:13:28 +00:00
mapBackground = computed(() => store.getters.mapBackground),
followTarget = computed(() => store.state.followTarget),
panTarget = computed(() => store.state.panTarget),
//Animation frame callbacks for panning after projection change
followFrame = ref(0),
worldChangeFrame = ref(0);
2020-12-01 23:20:38 +00:00
return {
leaflet,
maps,
markerSets,
configuration,
playerMarkersEnabled,
coordinatesControlEnabled,
clockControlEnabled,
linkControlEnabled,
2020-12-17 14:50:12 +00:00
chatEnabled,
2020-12-01 23:20:38 +00:00
logoControls,
2020-12-17 00:13:28 +00:00
followTarget,
panTarget,
followFrame,
worldChangeFrame,
2020-12-01 23:20:38 +00:00
mapBackground,
currentWorld,
currentMap,
currentProjection
}
},
watch: {
2020-12-17 00:13:28 +00:00
followTarget: {
2020-12-01 23:20:38 +00:00
handler(newValue, oldValue) {
if (newValue) {
this.updateFollow(newValue, !oldValue || newValue.account !== oldValue.account);
}
},
deep: true
},
2020-12-17 00:13:28 +00:00
panTarget(newValue) {
if(newValue) {
2020-12-18 14:53:06 +00:00
//Immediately clear if on the correct world, to allow repeated panning
if(this.currentWorld && newValue.location.world === this.currentWorld.name) {
useStore().commit(MutationTypes.CLEAR_PAN_TARGET, undefined);
}
2020-12-17 00:13:28 +00:00
this.updateFollow(newValue, false);
}
},
currentWorld(newValue, oldValue) {
const store = useStore();
//Cancel any pending pan frame
if(this.worldChangeFrame) {
cancelAnimationFrame(this.worldChangeFrame);
this.worldChangeFrame = 0;
}
2020-12-01 23:20:38 +00:00
if(newValue) {
let location: Coordinate;
let zoom: number;
store.dispatch(ActionTypes.GET_MARKER_SETS, undefined);
2020-12-18 14:53:06 +00:00
//Abort if follow target is present, to avoid panning twice
if(store.state.followTarget && store.state.followTarget.location.world === newValue.name) {
return;
//Abort if pan target is present, to avoid panning to the wrong place.
// Also clear it to allow repeated panning.
} else if(store.state.panTarget && store.state.panTarget.location.world === newValue.name) {
store.commit(MutationTypes.CLEAR_PAN_TARGET, undefined);
return;
//Otherwise pan to url location, if present and if we have just loaded
} else if(!oldValue && store.state.parsedUrl.location) {
location = store.state.parsedUrl.location;
2020-12-18 14:53:06 +00:00
//Otherwise pan to world center
} else {
location = newValue.center;
}
if(!oldValue) {
zoom = store.state.parsedUrl.zoom || store.state.configuration.defaultZoom;
}
//Delay the pan by a frame, to allow the projection to be updated by the new world
this.worldChangeFrame = requestAnimationFrame(() => {
this.leaflet!.panTo(this.currentProjection.locationToLatLng(location), {
animate: false,
noMoveStart: true,
});
this.leaflet!.setZoom(zoom, {
animate: false,
});
});
2020-12-01 23:20:38 +00:00
}
},
configuration: {
handler(newValue) {
if(this.leaflet) {
this.leaflet.setZoom(newValue.defaultZoom, {
animate: false,
noMoveStart: true,
});
}
},
deep: true,
},
2020-12-01 23:20:38 +00:00
},
mounted() {
2020-12-11 21:38:50 +00:00
this.leaflet = new DynmapMap(this.$el, Object.freeze({
2020-12-01 23:20:38 +00:00
zoom: this.configuration.defaultZoom,
2020-12-12 22:04:56 +00:00
center: new LatLng(0, 0),
2020-12-01 23:20:38 +00:00
fadeAnimation: false,
zoomAnimation: true,
zoomControl: true,
2020-12-11 21:38:50 +00:00
layerControl: true,
2020-12-01 23:20:38 +00:00
preferCanvas: true,
attributionControl: false,
2020-12-12 22:04:56 +00:00
crs: CRS.Simple,
2020-12-01 23:20:38 +00:00
worldCopyJump: false,
2020-12-10 02:22:29 +00:00
// markerZoomAnimation: false,
2020-12-01 23:20:38 +00:00
}));
2020-12-15 01:51:19 +00:00
this.leaflet.addControl(new LoadingControl({
position: 'topleft',
delayIndicator: 500,
}));
2020-12-01 23:20:38 +00:00
this.leaflet.on('moveend', () => {
useStore().commit(MutationTypes.SET_CURRENT_LOCATION, this.currentProjection.latLngToLocation(this.leaflet!.getCenter(), 64));
});
this.leaflet.on('zoomend', () => {
useStore().commit(MutationTypes.SET_CURRENT_ZOOM, this.leaflet!.getZoom());
});
2020-12-01 23:20:38 +00:00
},
methods: {
updateFollow(player: DynmapPlayer, newFollow: boolean) {
const store = useStore(),
currentWorld = store.state.currentWorld;
//Cancel any pending pan frame
if(this.followFrame) {
cancelAnimationFrame(this.followFrame);
this.followFrame = 0;
}
2020-12-01 23:20:38 +00:00
if(!this.leaflet) {
console.warn(`Cannot follow ${player.name}. Map not yet initialized.`);
return;
2020-12-01 23:20:38 +00:00
}
if(player.hidden) {
console.warn(`Cannot follow ${player.name}. Player is hidden from the map.`);
return;
}
2020-12-01 23:20:38 +00:00
if(!player.location.world) {
console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`);
2020-12-01 23:20:38 +00:00
return;
}
if(!currentWorld || currentWorld.name !== player.location.world) {
const followMapName = store.state.configuration.followMap,
world = store.state.worlds.get(player.location.world);
if(!world) {
console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`);
2020-12-01 23:20:38 +00:00
return;
}
let map = followMapName && world.maps.has(followMapName)
? world.maps.get(followMapName)
: world.maps.entries().next().value[1]
if(map !== store.state.currentMap) {
console.log(`Switching map to match player ${world.name} ${map.name}`);
store.commit(MutationTypes.SET_CURRENT_MAP, {worldName: world.name, mapName: map.name});
}
}
//Delay the pan by a frame, to allow the projection to be updated by the new world
this.followFrame = requestAnimationFrame(() => {
this.leaflet!.panTo(store.state.currentProjection.locationToLatLng(player.location));
2020-12-01 23:20:38 +00:00
if(newFollow) {
console.log(`Setting zoom for new follow ${store.state.configuration.followZoom}`);
this.leaflet!.setZoom(store.state.configuration.followZoom);
}
})
2020-12-01 23:20:38 +00:00
}
}
})
</script>
<style scoped>
.map {
width: 100%;
height: 100%;
background: #000;
z-index: 0;
}
</style>