Migrate LayerControl to vue
This commit is contained in:
parent
4887acb917
commit
14674b774e
@ -39,6 +39,7 @@ import {MutationTypes} from "@/store/mutation-types";
|
|||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||||
import {LiveAtlasLocation, LiveAtlasPlayer, LiveAtlasMapViewTarget} from "@/index";
|
import {LiveAtlasLocation, LiveAtlasPlayer, LiveAtlasMapViewTarget} from "@/index";
|
||||||
import TileLayerOverlay from "@/components/map/layer/TileLayerOverlay.vue";
|
import TileLayerOverlay from "@/components/map/layer/TileLayerOverlay.vue";
|
||||||
|
import {ActionTypes} from "@/store/action-types";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -69,6 +70,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
//Location and zoom to pan to upon next projection change
|
//Location and zoom to pan to upon next projection change
|
||||||
scheduledView = ref<LiveAtlasMapViewTarget|null>(null),
|
scheduledView = ref<LiveAtlasMapViewTarget|null>(null),
|
||||||
|
pendingLayerUpdates = computed(() => !!store.state.pendingLayerUpdates.size),
|
||||||
|
|
||||||
mapTitle = computed(() => store.state.messages.mapTitle);
|
mapTitle = computed(() => store.state.messages.mapTitle);
|
||||||
|
|
||||||
@ -90,6 +92,7 @@ export default defineComponent({
|
|||||||
currentMap,
|
currentMap,
|
||||||
|
|
||||||
scheduledView,
|
scheduledView,
|
||||||
|
pendingLayerUpdates,
|
||||||
|
|
||||||
mapTitle
|
mapTitle
|
||||||
}
|
}
|
||||||
@ -190,6 +193,21 @@ export default defineComponent({
|
|||||||
this.scheduledView = viewTarget;
|
this.scheduledView = viewTarget;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async pendingLayerUpdates(size) {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
if(size) {
|
||||||
|
const updates = await store.dispatch(ActionTypes.POP_LAYER_UPDATES, undefined);
|
||||||
|
|
||||||
|
for (const update of updates) {
|
||||||
|
if(update[1]) {
|
||||||
|
this.leaflet.addLayer(update[0]);
|
||||||
|
} else {
|
||||||
|
this.leaflet.removeLayer(update[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
parsedUrl: {
|
parsedUrl: {
|
||||||
handler(newValue) {
|
handler(newValue) {
|
||||||
if(!newValue || !this.currentMap || !this.leaflet) {
|
if(!newValue || !this.currentMap || !this.leaflet) {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<div class="ui__toolbar toolbar--vertical">
|
<div class="ui__toolbar toolbar--vertical">
|
||||||
<LogoControl v-for="logo in logoControls" :key="JSON.stringify(logo)" :options="logo"></LogoControl>
|
<LogoControl v-for="logo in logoControls" :key="JSON.stringify(logo)" :options="logo"></LogoControl>
|
||||||
<ZoomControl :leaflet="leaflet"></ZoomControl>
|
<ZoomControl :leaflet="leaflet"></ZoomControl>
|
||||||
|
<LayerControl></LayerControl>
|
||||||
<LoadingControl :leaflet="leaflet" :delay="500"></LoadingControl>
|
<LoadingControl :leaflet="leaflet" :delay="500"></LoadingControl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -61,6 +62,7 @@ import LoginControl from "@/components/map/control/LoginControl.vue";
|
|||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||||
import LoadingControl from "@/components/map/control/LoadingControl.vue";
|
import LoadingControl from "@/components/map/control/LoadingControl.vue";
|
||||||
import ZoomControl from "@/components/map/control/ZoomControl.vue";
|
import ZoomControl from "@/components/map/control/ZoomControl.vue";
|
||||||
|
import LayerControl from "@/components/map/control/LayerControl.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -71,6 +73,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
LayerControl,
|
||||||
ZoomControl,
|
ZoomControl,
|
||||||
LoadingControl,
|
LoadingControl,
|
||||||
LogoControl,
|
LogoControl,
|
||||||
|
@ -31,6 +31,7 @@ import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
|||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {Coordinate, CoordinatesControlOptions} from "@/index";
|
import {Coordinate, CoordinatesControlOptions} from "@/index";
|
||||||
import {LeafletMouseEvent} from "leaflet";
|
import {LeafletMouseEvent} from "leaflet";
|
||||||
|
import {CoordinatesControlOptions} from "@/leaflet/control/CoordinatesControl";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -42,7 +43,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore(),
|
const store = useStore(),
|
||||||
componentSettings = computed(() => store.state.components.coordinatesControl),
|
componentSettings = computed(() => store.state.components.coordinatesControl as CoordinatesControlOptions),
|
||||||
currentMap = computed(() => store.state.currentMap),
|
currentMap = computed(() => store.state.currentMap),
|
||||||
|
|
||||||
chunkLabel = computed(() => store.state.messages.locationChunk),
|
chunkLabel = computed(() => store.state.messages.locationChunk),
|
||||||
|
179
src/components/map/control/LayerControl.vue
Normal file
179
src/components/map/control/LayerControl.vue
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<!--
|
||||||
|
- Copyright 2022 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>
|
||||||
|
<div class="layers">
|
||||||
|
<button ref="button" type="button" class="ui__element ui__button" title="Layers" :aria-expanded="listVisible"
|
||||||
|
@click.prevent="toggleList"
|
||||||
|
@keydown.right.prevent.stop="toggleList">
|
||||||
|
<SvgIcon name="layers"></SvgIcon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<section ref="list" :hidden="!listVisible" class="ui__element ui__panel layers__list"
|
||||||
|
:style="listStyle" @keydown="handleListKeydown">
|
||||||
|
<div class="layers__base"></div>
|
||||||
|
<div class="layers__overlays">
|
||||||
|
<label v-for="layer in overlayLayers" :key="stamp(layer.layer)" class="layer checkbox">
|
||||||
|
<input type="checkbox" :checked="layer.enabled" @keydown.space.prevent="toggleLayer(layer.layer)"
|
||||||
|
@input.prevent="toggleLayer(layer.layer)">
|
||||||
|
<SvgIcon name="checkbox"></SvgIcon>
|
||||||
|
<span>{{ layer.name }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import SvgIcon from "@/components/SvgIcon.vue";
|
||||||
|
import {computed, defineComponent, onUnmounted, watch} from "@vue/runtime-core";
|
||||||
|
import {useStore} from "@/store";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
|
||||||
|
import '@/assets/icons/layers.svg';
|
||||||
|
import '@/assets/icons/checkbox.svg';
|
||||||
|
import {stamp} from "leaflet";
|
||||||
|
import {toggleLayer} from "@/util/layers";
|
||||||
|
import {nextTick, onMounted, ref} from "vue";
|
||||||
|
import {handleKeyboardEvent} from "@/util/events";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {SvgIcon},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore(),
|
||||||
|
overlayLayers = computed(() => store.state.sortedLayers.filter(layer => layer.overlay)),
|
||||||
|
baseLayers = computed(() => store.state.sortedLayers.filter(layer => !layer.overlay)),
|
||||||
|
listVisible = computed(() => store.state.ui.visibleElements.has('layers')),
|
||||||
|
listStyle = ref({'max-height': 'auto'}),
|
||||||
|
|
||||||
|
button = ref<HTMLButtonElement|null>(null),
|
||||||
|
list = ref<HTMLElement|null>(null);
|
||||||
|
|
||||||
|
const toggleList = () => listVisible.value ? closeList() : openList();
|
||||||
|
|
||||||
|
const openList = () =>
|
||||||
|
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'layers', state: true});
|
||||||
|
|
||||||
|
const closeList = () =>
|
||||||
|
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'layers', state: false});
|
||||||
|
|
||||||
|
const handleListKeydown = (event: KeyboardEvent) => {
|
||||||
|
if(event.key === 'ArrowLeft') {
|
||||||
|
closeList();
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = Array.from((list.value as HTMLElement).querySelectorAll('input')) as HTMLElement[];
|
||||||
|
handleKeyboardEvent(event as KeyboardEvent, elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const y = button.value!.getBoundingClientRect().y;
|
||||||
|
|
||||||
|
//Limit height to remaining vertical space
|
||||||
|
//Avoid covering bottom bar
|
||||||
|
listStyle.value['max-height'] = `calc(100vh - ${(y + 10 + 60)}px)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(listVisible, visible => {
|
||||||
|
if(visible) {
|
||||||
|
const firstCheckbox = (list.value as HTMLElement).querySelector('.checkbox');
|
||||||
|
|
||||||
|
if(firstCheckbox instanceof HTMLElement) {
|
||||||
|
nextTick(() => firstCheckbox.focus());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextTick(() => (button.value as HTMLButtonElement).focus());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
handleResize();
|
||||||
|
});
|
||||||
|
onUnmounted(() => window.addEventListener('resize', handleResize));
|
||||||
|
|
||||||
|
return {
|
||||||
|
overlayLayers,
|
||||||
|
baseLayers,
|
||||||
|
listVisible,
|
||||||
|
listStyle,
|
||||||
|
|
||||||
|
button,
|
||||||
|
list,
|
||||||
|
|
||||||
|
toggleList,
|
||||||
|
openList,
|
||||||
|
closeList,
|
||||||
|
handleListKeydown,
|
||||||
|
toggleLayer,
|
||||||
|
stamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../scss/placeholders';
|
||||||
|
|
||||||
|
.layers {
|
||||||
|
width: auto;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-base);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.layers__list[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers__list {
|
||||||
|
@extend %panel;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(var(--ui-element-spacing) + var(--ui-button-size));
|
||||||
|
overflow: auto;
|
||||||
|
max-width: calc(100vw - 14rem);
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
max-width: calc(100vw - 13rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layers__overlays {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.8rem 0 0.7rem;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: -0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: -0.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -21,11 +21,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, computed, onMounted, onUnmounted} from "@vue/runtime-core";
|
import {defineComponent, computed, onMounted, onUnmounted} from "@vue/runtime-core";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
|
||||||
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
import LiveAtlasLayerGroup from "@/leaflet/layer/LiveAtlasLayerGroup";
|
||||||
import {LiveAtlasMarkerSet} from "@/index";
|
import {LiveAtlasMarkerSet} from "@/index";
|
||||||
import {watch} from "vue";
|
import {markRaw, watch} from "vue";
|
||||||
import Markers from "@/components/map/marker/Markers.vue";
|
import Markers from "@/components/map/marker/Markers.vue";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -33,11 +33,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
leaflet: {
|
|
||||||
type: Object as () => LiveAtlasLeafletMap,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
markerSet: {
|
markerSet: {
|
||||||
type: Object as () => LiveAtlasMarkerSet,
|
type: Object as () => LiveAtlasMarkerSet,
|
||||||
required: true,
|
required: true,
|
||||||
@ -65,27 +60,25 @@ export default defineComponent({
|
|||||||
priority: props.markerSet.priority,
|
priority: props.markerSet.priority,
|
||||||
});
|
});
|
||||||
|
|
||||||
if(newValue.hidden) {
|
// store.commit(MutationTypes.UPDATE_LAYER, {
|
||||||
props.leaflet.getLayerManager()
|
// layer: layerGroup,
|
||||||
.addHiddenLayer(layerGroup, newValue.label, props.markerSet.priority);
|
// options: {enabled: newValue.hidden}
|
||||||
} else {
|
// });
|
||||||
props.leaflet.getLayerManager()
|
|
||||||
.addLayer(layerGroup, true, newValue.label, props.markerSet.priority);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, {deep: true});
|
}, {deep: true});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if(props.markerSet.hidden) {
|
store.commit(MutationTypes.ADD_LAYER, {
|
||||||
props.leaflet.getLayerManager()
|
layer: markRaw(layerGroup),
|
||||||
.addHiddenLayer(layerGroup, props.markerSet.label, props.markerSet.priority);
|
name: props.markerSet.label,
|
||||||
} else {
|
overlay: true,
|
||||||
props.leaflet.getLayerManager()
|
position: props.markerSet.priority || 0,
|
||||||
.addLayer(layerGroup, true, props.markerSet.label, props.markerSet.priority);
|
enabled: !props.markerSet.hidden,
|
||||||
}
|
showInControl: true
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => props.leaflet.getLayerManager().removeLayer(layerGroup));
|
onUnmounted(() => store.commit(MutationTypes.REMOVE_LAYER, layerGroup));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
markerSettings,
|
markerSettings,
|
||||||
|
@ -24,6 +24,8 @@ import {defineComponent, computed, watch, onMounted, onUnmounted} from "@vue/run
|
|||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import {LayerGroup} from 'leaflet';
|
import {LayerGroup} from 'leaflet';
|
||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
import {markRaw} from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -51,21 +53,17 @@ export default defineComponent({
|
|||||||
watch(playerCount, (newValue) => playerPane.classList.toggle('no-animations', newValue > 150));
|
watch(playerCount, (newValue) => playerPane.classList.toggle('no-animations', newValue > 150));
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if(!componentSettings.value!.hideByDefault) {
|
store.commit(MutationTypes.ADD_LAYER, {
|
||||||
props.leaflet.getLayerManager().addLayer(
|
layer: markRaw(layerGroup),
|
||||||
layerGroup,
|
name: store.state.components.players.markers!.layerName,
|
||||||
true,
|
overlay: true,
|
||||||
store.state.components.players.markers!.layerName,
|
position: componentSettings.value!.layerPriority,
|
||||||
componentSettings.value!.layerPriority);
|
enabled: !componentSettings.value!.hideByDefault,
|
||||||
} else {
|
showInControl: true
|
||||||
props.leaflet.getLayerManager().addHiddenLayer(
|
});
|
||||||
layerGroup,
|
|
||||||
store.state.components.players.markers!.layerName,
|
|
||||||
componentSettings.value!.layerPriority);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => props.leaflet.getLayerManager().removeLayer(layerGroup));
|
onUnmounted(() => store.commit(MutationTypes.REMOVE_LAYER, layerGroup));
|
||||||
|
|
||||||
if(playersAboveMarkers.value) {
|
if(playersAboveMarkers.value) {
|
||||||
playerPane.style.zIndex = '600';
|
playerPane.style.zIndex = '600';
|
||||||
|
@ -19,6 +19,8 @@ import {defineComponent, onUnmounted} from "@vue/runtime-core";
|
|||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||||
import {LiveAtlasTileLayerOverlay} from "@/index";
|
import {LiveAtlasTileLayerOverlay} from "@/index";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
import {markRaw} from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -37,10 +39,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
let layer = store.state.currentMapProvider!.createTileLayer(Object.freeze(JSON.parse(JSON.stringify(props.options.tileLayerOptions))));
|
let layer = store.state.currentMapProvider!.createTileLayer(Object.freeze(JSON.parse(JSON.stringify(props.options.tileLayerOptions))));
|
||||||
|
|
||||||
props.leaflet.getLayerManager().addHiddenLayer(layer, props.options.label, props.options.priority);
|
store.commit(MutationTypes.ADD_LAYER, {
|
||||||
|
layer: markRaw(layer),
|
||||||
|
name: props.options.label,
|
||||||
|
overlay: true,
|
||||||
|
position: props.options.priority || 0,
|
||||||
|
enabled: false,
|
||||||
|
showInControl: true
|
||||||
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
props.leaflet.getLayerManager().removeLayer(layer);
|
store.commit(MutationTypes.REMOVE_LAYER, layer)
|
||||||
layer.remove();
|
layer.remove();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
19
src/index.d.ts
vendored
19
src/index.d.ts
vendored
@ -21,7 +21,7 @@ import {
|
|||||||
ControlOptions,
|
ControlOptions,
|
||||||
Coords,
|
Coords,
|
||||||
DoneCallback, FitBoundsOptions,
|
DoneCallback, FitBoundsOptions,
|
||||||
InternalTiles, LatLng,
|
InternalTiles, LatLng, Layer,
|
||||||
PathOptions,
|
PathOptions,
|
||||||
PointTuple,
|
PointTuple,
|
||||||
PolylineOptions
|
PolylineOptions
|
||||||
@ -171,6 +171,23 @@ interface LiveAtlasWorldState {
|
|||||||
timeOfDay: number;
|
timeOfDay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LiveAtlasLayerDefinition {
|
||||||
|
layer: Layer;
|
||||||
|
overlay: boolean;
|
||||||
|
name: string;
|
||||||
|
position: number;
|
||||||
|
enabled: boolean;
|
||||||
|
showInControl: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiveAtlasPartialLayerDefinition {
|
||||||
|
overlay?: boolean;
|
||||||
|
name?: string;
|
||||||
|
position?: number;
|
||||||
|
enabled?: boolean;
|
||||||
|
showInControl?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface LiveAtlasParsedUrl {
|
interface LiveAtlasParsedUrl {
|
||||||
world?: string;
|
world?: string;
|
||||||
map?: string;
|
map?: string;
|
||||||
|
@ -15,23 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Map, DomUtil, MapOptions} from 'leaflet';
|
import {Map, DomUtil, MapOptions} from 'leaflet';
|
||||||
import LayerManager from "@/leaflet/layer/LayerManager";
|
|
||||||
|
|
||||||
export default class LiveAtlasLeafletMap extends Map {
|
export default class LiveAtlasLeafletMap extends Map {
|
||||||
declare _controlCorners: any;
|
declare _controlCorners: any;
|
||||||
declare _controlContainer?: HTMLElement;
|
declare _controlContainer?: HTMLElement;
|
||||||
declare _container?: HTMLElement;
|
declare _container?: HTMLElement;
|
||||||
|
|
||||||
private readonly _layerManager: LayerManager;
|
|
||||||
|
|
||||||
constructor(element: string | HTMLElement, options?: MapOptions) {
|
constructor(element: string | HTMLElement, options?: MapOptions) {
|
||||||
super(element, options);
|
super(element, options);
|
||||||
|
|
||||||
this._layerManager = Object.seal(new LayerManager(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
getLayerManager(): LayerManager {
|
|
||||||
return this._layerManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 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 {Control, DomEvent, DomUtil, Layer, LeafletEvent, Map as LeafletMap, Util} from 'leaflet';
|
|
||||||
|
|
||||||
import '@/assets/icons/layers.svg';
|
|
||||||
import '@/assets/icons/checkbox.svg';
|
|
||||||
import {useStore} from "@/store";
|
|
||||||
import {MutationTypes} from "@/store/mutation-types";
|
|
||||||
import {nextTick, watch} from "vue";
|
|
||||||
import {handleKeyboardEvent} from "@/util/events";
|
|
||||||
import LayersObject = Control.LayersObject;
|
|
||||||
import LayersOptions = Control.LayersOptions;
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension of leaflet's standard {@link Control.Layers}
|
|
||||||
* Sorts layers by position, adds additional keyboard navigation, adjusts to viewport size and tracks expanded state in vuex
|
|
||||||
*/
|
|
||||||
export class LiveAtlasLayerControl extends Control.Layers {
|
|
||||||
declare _map ?: LeafletMap;
|
|
||||||
declare _overlaysList?: HTMLElement;
|
|
||||||
declare _baseLayersList?: HTMLElement;
|
|
||||||
declare _layerControlInputs?: HTMLElement[];
|
|
||||||
declare _container?: HTMLElement;
|
|
||||||
declare _section?: HTMLElement;
|
|
||||||
declare _separator?: HTMLElement;
|
|
||||||
|
|
||||||
private _layersButton?: HTMLElement;
|
|
||||||
private _layerPositions: Map<Layer, number>;
|
|
||||||
private visible: boolean = false;
|
|
||||||
|
|
||||||
constructor(baseLayers?: LayersObject, overlays?: LayersObject, options?: LayersOptions) {
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
super(baseLayers, overlays, Object.assign(options || {}, {
|
|
||||||
sortLayers: true,
|
|
||||||
sortFunction: (layer1: Layer, layer2: Layer, name1: string, name2: string) => {
|
|
||||||
const priority1 = this._layerPositions.get(layer1) || 0,
|
|
||||||
priority2 = this._layerPositions.get(layer2) || 0;
|
|
||||||
|
|
||||||
if(priority1 !== priority2) {
|
|
||||||
return priority1 - priority2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((name1 < name2) ? -1 : ((name1 > name2) ? 1 : 0));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
this._layerPositions = new Map<Layer, number>();
|
|
||||||
}
|
|
||||||
|
|
||||||
hasLayer(layer: Layer): boolean {
|
|
||||||
// @ts-ignore
|
|
||||||
return !!super._getLayer(Util.stamp(layer));
|
|
||||||
}
|
|
||||||
|
|
||||||
expand() {
|
|
||||||
this._layersButton!.setAttribute('aria-expanded', 'true');
|
|
||||||
this._section!.style.display = '';
|
|
||||||
this.handleResize();
|
|
||||||
|
|
||||||
const firstCheckbox = this._container!.querySelector('input');
|
|
||||||
|
|
||||||
if(firstCheckbox) {
|
|
||||||
(firstCheckbox as HTMLElement).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
super._checkDisabledLayers();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
collapse() {
|
|
||||||
this._layersButton!.setAttribute('aria-expanded', 'false');
|
|
||||||
this._section!.style.display = 'none';
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
_initLayout() {
|
|
||||||
const className = 'leaflet-control-layers',
|
|
||||||
container = this._container = DomUtil.create('div', className),
|
|
||||||
section = this._section = DomUtil.create('section', className + '-list'),
|
|
||||||
button = this._layersButton = DomUtil.create('button', className + '-toggle', container);
|
|
||||||
|
|
||||||
DomEvent.disableClickPropagation(container);
|
|
||||||
DomEvent.disableScrollPropagation(container);
|
|
||||||
|
|
||||||
//Open layer list on ArrowRight from button
|
|
||||||
DomEvent.on(button,'keydown', (e: Event) => {
|
|
||||||
if((e as KeyboardEvent).key === 'ArrowRight') {
|
|
||||||
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'layers', state: true});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DomEvent.on(container, 'keydown', (e: Event) => {
|
|
||||||
//Close layer list on ArrowLeft from within list
|
|
||||||
if((e as KeyboardEvent).key === 'ArrowLeft') {
|
|
||||||
e.preventDefault();
|
|
||||||
store.commit(MutationTypes.SET_UI_ELEMENT_VISIBILITY, {element: 'layers', state: false});
|
|
||||||
nextTick(() => button.focus());
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = Array.from(container.querySelectorAll('input')) as HTMLElement[];
|
|
||||||
handleKeyboardEvent(e as KeyboardEvent, elements);
|
|
||||||
});
|
|
||||||
DomEvent.on(button,'click', () => store.commit(MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY, 'layers'));
|
|
||||||
|
|
||||||
section.style.display = 'none';
|
|
||||||
|
|
||||||
button.title = store.state.messages.layersTitle;
|
|
||||||
button.setAttribute('aria-expanded', 'false');
|
|
||||||
button.innerHTML = `
|
|
||||||
<svg class="svg-icon" aria-hidden="true">
|
|
||||||
<use xlink:href="#icon--layers" />
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
|
|
||||||
//Use vuex track expanded state
|
|
||||||
watch(store.state.ui.visibleElements, (newValue) => {
|
|
||||||
if(newValue.has('layers') && !this.visible) {
|
|
||||||
this.expand();
|
|
||||||
} else if(this.visible && !newValue.has('layers')) {
|
|
||||||
this.collapse();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.visible = store.state.ui.visibleElements.has('layers');
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(store.state.messages, (newValue) => (button.title = newValue.layersTitle));//
|
|
||||||
|
|
||||||
this.visible = store.state.ui.visibleElements.has('layers');
|
|
||||||
|
|
||||||
if (this.visible) {
|
|
||||||
this.expand();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._baseLayersList = DomUtil.create('div', className + '-base', section);
|
|
||||||
this._separator = DomUtil.create('div', className + '-separator', section);
|
|
||||||
this._overlaysList = DomUtil.create('div', className + '-overlays', section);
|
|
||||||
|
|
||||||
container.appendChild(section);
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => this.handleResize());
|
|
||||||
this.handleResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize() {
|
|
||||||
const y = this._layersButton!.getBoundingClientRect().y;
|
|
||||||
|
|
||||||
//Limit height to remaining vertical space
|
|
||||||
// Including 30px element padding, 10px padding from edge of viewport, and 55px padding to avoid covering bottom bar
|
|
||||||
this._section!.style.maxHeight = `calc(100vh - ${(y + 30 + 10 + 55)}px)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
addOverlayAtPosition(layer: Layer, name: string, position: number): this {
|
|
||||||
this._layerPositions.set(layer, position);
|
|
||||||
return super.addOverlay(layer, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
addOverlay(layer: Layer, name: string): this {
|
|
||||||
this._layerPositions.set(layer, 0);
|
|
||||||
return super.addOverlay(layer, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLayer(layer: Layer): this {
|
|
||||||
this._layerPositions.delete(layer);
|
|
||||||
return super.removeLayer(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
_addItem(obj: any) {
|
|
||||||
const container = obj.overlay ? this._overlaysList : this._baseLayersList,
|
|
||||||
item = document.createElement('label'),
|
|
||||||
label = document.createElement('span'),
|
|
||||||
checked = this._map!.hasLayer(obj.layer);
|
|
||||||
|
|
||||||
let input;
|
|
||||||
|
|
||||||
item.className = 'layer checkbox';
|
|
||||||
|
|
||||||
if (obj.overlay) {
|
|
||||||
input = document.createElement('input');
|
|
||||||
input.type = 'checkbox';
|
|
||||||
input.className = 'leaflet-control-layers-selector';
|
|
||||||
input.defaultChecked = checked;
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
input = super._createRadioElement('leaflet-base-layers_' + Util.stamp(this), checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.layerId = Util.stamp(obj.layer);
|
|
||||||
this._layerControlInputs!.push(input);
|
|
||||||
label.textContent = obj.name;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
DomEvent.on(input, 'click', (e: LeafletEvent) => super._onInputClick(e), this);
|
|
||||||
|
|
||||||
item.appendChild(input);
|
|
||||||
item.insertAdjacentHTML('beforeend', `
|
|
||||||
<svg class="svg-icon" aria-hidden="true">
|
|
||||||
<use xlink:href="#icon--checkbox" />
|
|
||||||
</svg>`);
|
|
||||||
item.appendChild(label);
|
|
||||||
|
|
||||||
container!.appendChild(item);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
super._checkDisabledLayers();
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
onRemove(map: LeafletMap) {
|
|
||||||
this._layerControlInputs = [];
|
|
||||||
|
|
||||||
(super.onRemove as Function)(map);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 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 {Map, Layer} from 'leaflet';
|
|
||||||
import {LiveAtlasLayerControl} from "@/leaflet/control/LiveAtlasLayerControl";
|
|
||||||
import {watch} from "vue";
|
|
||||||
import {useStore} from "@/store";
|
|
||||||
import {computed} from "@vue/runtime-core";
|
|
||||||
|
|
||||||
export default class LayerManager {
|
|
||||||
private readonly layerControl: LiveAtlasLayerControl;
|
|
||||||
private readonly map: Map;
|
|
||||||
|
|
||||||
constructor(map: Map) {
|
|
||||||
const showControl = computed(() => useStore().state.components.layerControl);
|
|
||||||
this.map = map;
|
|
||||||
this.layerControl = new LiveAtlasLayerControl({}, {},{
|
|
||||||
position: 'topleft',
|
|
||||||
});
|
|
||||||
|
|
||||||
if(showControl.value) {
|
|
||||||
this.map.addControl(this.layerControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(showControl, (show) => {
|
|
||||||
if(show) {
|
|
||||||
this.map.addControl(this.layerControl);
|
|
||||||
} else {
|
|
||||||
this.map.removeControl(this.layerControl);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
addLayer(layer: Layer, showInControl: boolean, name: string, position: number) {
|
|
||||||
this.map.addLayer(layer);
|
|
||||||
|
|
||||||
if(showInControl) {
|
|
||||||
if(this.layerControl.hasLayer(layer)) {
|
|
||||||
this.layerControl.removeLayer(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof position !== 'undefined') {
|
|
||||||
this.layerControl.addOverlayAtPosition(layer, name, position);
|
|
||||||
} else {
|
|
||||||
this.layerControl.addOverlay(layer, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addHiddenLayer(layer: Layer, name: string, position: number) {
|
|
||||||
if(this.layerControl.hasLayer(layer)) {
|
|
||||||
this.layerControl.removeLayer(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof position !== 'undefined') {
|
|
||||||
this.layerControl.addOverlayAtPosition(layer, name, position);
|
|
||||||
} else {
|
|
||||||
this.layerControl.addOverlay(layer, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLayer(layer: Layer) {
|
|
||||||
this.map.removeLayer(layer);
|
|
||||||
this.layerControl.removeLayer(layer);
|
|
||||||
}
|
|
||||||
}
|
|
@ -99,45 +99,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-layers {
|
|
||||||
width: auto;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-base);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.leaflet-control-layers-list {
|
|
||||||
@extend %panel;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: calc(var(--ui-element-spacing) + var(--ui-button-size));
|
|
||||||
overflow: auto;
|
|
||||||
max-width: calc(100vw - 14rem);
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
@media screen and (max-width: 400px) {
|
|
||||||
max-width: calc(100vw - 13rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-control-layers-overlays {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.8rem 0 0.7rem;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: -0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: -0.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-top, .leaflet-bottom,
|
.leaflet-top, .leaflet-bottom,
|
||||||
.leaflet-left, .leaflet-right {
|
.leaflet-left, .leaflet-right {
|
||||||
|
@ -20,6 +20,7 @@ export enum ActionTypes {
|
|||||||
START_UPDATES = "startUpdates",
|
START_UPDATES = "startUpdates",
|
||||||
STOP_UPDATES = "stopUpdates",
|
STOP_UPDATES = "stopUpdates",
|
||||||
SET_PLAYERS = "setPlayers",
|
SET_PLAYERS = "setPlayers",
|
||||||
|
POP_LAYER_UPDATES = "popLayerUpdates",
|
||||||
POP_MARKER_UPDATES = "popMarkerUpdates",
|
POP_MARKER_UPDATES = "popMarkerUpdates",
|
||||||
POP_TILE_UPDATES = "popTileUpdates",
|
POP_TILE_UPDATES = "popTileUpdates",
|
||||||
SEND_CHAT_MESSAGE = "sendChatMessage",
|
SEND_CHAT_MESSAGE = "sendChatMessage",
|
||||||
|
@ -23,6 +23,7 @@ import {DynmapMarkerUpdate, DynmapTileUpdate} from "@/dynmap";
|
|||||||
import {LiveAtlasGlobalConfig, LiveAtlasMarkerSet, LiveAtlasPlayer, LiveAtlasWorldDefinition} from "@/index";
|
import {LiveAtlasGlobalConfig, LiveAtlasMarkerSet, LiveAtlasPlayer, LiveAtlasWorldDefinition} from "@/index";
|
||||||
import {nextTick} from "vue";
|
import {nextTick} from "vue";
|
||||||
import {startUpdateHandling, stopUpdateHandling} from "@/util/markers";
|
import {startUpdateHandling, stopUpdateHandling} from "@/util/markers";
|
||||||
|
import {Layer} from "leaflet";
|
||||||
|
|
||||||
type AugmentedActionContext = {
|
type AugmentedActionContext = {
|
||||||
commit<K extends keyof Mutations>(
|
commit<K extends keyof Mutations>(
|
||||||
@ -49,6 +50,9 @@ export interface Actions {
|
|||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
payload: Set<LiveAtlasPlayer>
|
payload: Set<LiveAtlasPlayer>
|
||||||
):Promise<Map<string, LiveAtlasMarkerSet>>
|
):Promise<Map<string, LiveAtlasMarkerSet>>
|
||||||
|
[ActionTypes.POP_LAYER_UPDATES](
|
||||||
|
{commit}: AugmentedActionContext,
|
||||||
|
):Promise<[Layer, boolean][]>
|
||||||
[ActionTypes.POP_MARKER_UPDATES](
|
[ActionTypes.POP_MARKER_UPDATES](
|
||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
amount: number
|
amount: number
|
||||||
@ -191,6 +195,14 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async [ActionTypes.POP_LAYER_UPDATES]({commit, state}): Promise<[Layer, boolean][]> {
|
||||||
|
const updates = Array.from(state.pendingLayerUpdates.entries());
|
||||||
|
|
||||||
|
commit(MutationTypes.POP_LAYER_UPDATES, undefined);
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
},
|
||||||
|
|
||||||
async [ActionTypes.POP_MARKER_UPDATES]({commit, state}, amount: number): Promise<DynmapMarkerUpdate[]> {
|
async [ActionTypes.POP_MARKER_UPDATES]({commit, state}, amount: number): Promise<DynmapMarkerUpdate[]> {
|
||||||
const updates = state.pendingMarkerUpdates.slice(0, amount);
|
const updates = state.pendingMarkerUpdates.slice(0, amount);
|
||||||
|
|
||||||
|
@ -30,11 +30,15 @@ export enum MutationTypes {
|
|||||||
ADD_MARKER_UPDATES = 'addMarkerUpdates',
|
ADD_MARKER_UPDATES = 'addMarkerUpdates',
|
||||||
ADD_TILE_UPDATES = 'addTileUpdates',
|
ADD_TILE_UPDATES = 'addTileUpdates',
|
||||||
ADD_CHAT = 'addChat',
|
ADD_CHAT = 'addChat',
|
||||||
|
POP_LAYER_UPDATES = 'popLayerUpdates',
|
||||||
POP_MARKER_UPDATES = 'popMarkerUpdates',
|
POP_MARKER_UPDATES = 'popMarkerUpdates',
|
||||||
POP_TILE_UPDATES = 'popTileUpdates',
|
POP_TILE_UPDATES = 'popTileUpdates',
|
||||||
SET_MAX_PLAYERS = 'setMaxPlayers',
|
SET_MAX_PLAYERS = 'setMaxPlayers',
|
||||||
SET_PLAYERS_ASYNC = 'setPlayersAsync',
|
SET_PLAYERS_ASYNC = 'setPlayersAsync',
|
||||||
SYNC_PLAYERS = 'syncPlayers',
|
SYNC_PLAYERS = 'syncPlayers',
|
||||||
|
ADD_LAYER = 'addLayer',
|
||||||
|
UPDATE_LAYER = 'updateLayer',
|
||||||
|
REMOVE_LAYER = 'removeLayer',
|
||||||
SET_LOADED = 'setLoaded',
|
SET_LOADED = 'setLoaded',
|
||||||
|
|
||||||
SET_CURRENT_SERVER = 'setCurrentServer',
|
SET_CURRENT_SERVER = 'setCurrentServer',
|
||||||
|
@ -41,10 +41,12 @@ import {
|
|||||||
LiveAtlasMarker,
|
LiveAtlasMarker,
|
||||||
LiveAtlasMapViewTarget,
|
LiveAtlasMapViewTarget,
|
||||||
LiveAtlasGlobalMessageConfig,
|
LiveAtlasGlobalMessageConfig,
|
||||||
LiveAtlasUIConfig, LiveAtlasServerDefinition
|
LiveAtlasUIConfig, LiveAtlasServerDefinition, LiveAtlasLayerDefinition, LiveAtlasPartialLayerDefinition
|
||||||
} from "@/index";
|
} from "@/index";
|
||||||
import {getServerMapProvider} from "@/util/config";
|
import {getServerMapProvider} from "@/util/config";
|
||||||
import {getDefaultPlayerImage} from "@/util/images";
|
import {getDefaultPlayerImage} from "@/util/images";
|
||||||
|
import {Layer} from "leaflet";
|
||||||
|
import {sortLayers} from "@/util/layers";
|
||||||
|
|
||||||
export type CurrentMapPayload = {
|
export type CurrentMapPayload = {
|
||||||
worldName: string;
|
worldName: string;
|
||||||
@ -67,12 +69,16 @@ export type Mutations<S = State> = {
|
|||||||
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
|
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
|
||||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<LiveAtlasChat>): void
|
[MutationTypes.ADD_CHAT](state: State, chat: Array<LiveAtlasChat>): void
|
||||||
|
|
||||||
|
[MutationTypes.POP_LAYER_UPDATES](state: State): void
|
||||||
[MutationTypes.POP_MARKER_UPDATES](state: S, amount: number): void
|
[MutationTypes.POP_MARKER_UPDATES](state: S, amount: number): void
|
||||||
[MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void
|
[MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void
|
||||||
|
|
||||||
[MutationTypes.SET_MAX_PLAYERS](state: S, maxPlayers: number): void
|
[MutationTypes.SET_MAX_PLAYERS](state: S, maxPlayers: number): void
|
||||||
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<LiveAtlasPlayer>): Set<LiveAtlasPlayer>
|
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<LiveAtlasPlayer>): Set<LiveAtlasPlayer>
|
||||||
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
|
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
|
||||||
|
[MutationTypes.ADD_LAYER](state: State, layer: LiveAtlasLayerDefinition): void
|
||||||
|
[MutationTypes.UPDATE_LAYER](state: State, payload: {layer: Layer, options: LiveAtlasPartialLayerDefinition}): void
|
||||||
|
[MutationTypes.REMOVE_LAYER](state: State, layer: Layer): void
|
||||||
[MutationTypes.SET_LOADED](state: S, a?: void): void
|
[MutationTypes.SET_LOADED](state: S, a?: void): void
|
||||||
[MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void
|
[MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void
|
||||||
[MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void
|
[MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void
|
||||||
@ -173,6 +179,14 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
state.worlds.clear();
|
state.worlds.clear();
|
||||||
state.maps.clear();
|
state.maps.clear();
|
||||||
|
|
||||||
|
//Mark all layers for removal
|
||||||
|
for (const layer of state.layers.keys()) {
|
||||||
|
state.pendingLayerUpdates.set(layer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.layers.clear();
|
||||||
|
state.sortedLayers.splice(0);
|
||||||
|
|
||||||
state.followTarget = undefined;
|
state.followTarget = undefined;
|
||||||
state.viewTarget = undefined;
|
state.viewTarget = undefined;
|
||||||
|
|
||||||
@ -299,6 +313,11 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
state.chat.messages.unshift(...chat);
|
state.chat.messages.unshift(...chat);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//Pops the specified number of marker updates from the pending updates list
|
||||||
|
[MutationTypes.POP_LAYER_UPDATES](state: State) {
|
||||||
|
state.pendingLayerUpdates.clear();
|
||||||
|
},
|
||||||
|
|
||||||
//Pops the specified number of marker updates from the pending updates list
|
//Pops the specified number of marker updates from the pending updates list
|
||||||
[MutationTypes.POP_MARKER_UPDATES](state: State, amount: number) {
|
[MutationTypes.POP_MARKER_UPDATES](state: State, amount: number) {
|
||||||
state.pendingMarkerUpdates.splice(0, amount);
|
state.pendingMarkerUpdates.splice(0, amount);
|
||||||
@ -379,6 +398,38 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[MutationTypes.ADD_LAYER](state: State, layer: LiveAtlasLayerDefinition) {
|
||||||
|
state.layers.set(layer.layer, layer);
|
||||||
|
state.sortedLayers = sortLayers(state.layers);
|
||||||
|
state.pendingLayerUpdates.set(layer.layer, layer.enabled);
|
||||||
|
},
|
||||||
|
|
||||||
|
[MutationTypes.UPDATE_LAYER](state: State, {layer, options}) {
|
||||||
|
if(state.layers.has(layer)) {
|
||||||
|
const existing = state.layers.get(layer) as LiveAtlasLayerDefinition,
|
||||||
|
existingEnabled = existing.enabled;
|
||||||
|
|
||||||
|
state.layers.set(layer, Object.assign(existing, options));
|
||||||
|
state.sortedLayers = sortLayers(state.layers);
|
||||||
|
|
||||||
|
// Sort layers if position has changed
|
||||||
|
if((typeof options.enabled === 'boolean' && existingEnabled !== options.enabled)) {
|
||||||
|
state.pendingLayerUpdates.set(layer, options.enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[MutationTypes.REMOVE_LAYER](state: State, layer: Layer) {
|
||||||
|
const existing = state.layers.get(layer);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
console.log('removing???');
|
||||||
|
state.layers.delete(layer);
|
||||||
|
state.pendingLayerUpdates.set(layer, false); // Remove from map
|
||||||
|
state.sortedLayers.splice(state.sortedLayers.indexOf(existing, 1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
//Sets flag indicating LiveAtlas has fully loaded
|
//Sets flag indicating LiveAtlas has fully loaded
|
||||||
[MutationTypes.SET_LOADED](state: State) {
|
[MutationTypes.SET_LOADED](state: State) {
|
||||||
state.firstLoad = false;
|
state.firstLoad = false;
|
||||||
@ -553,6 +604,15 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
|
|
||||||
state.worlds.clear();
|
state.worlds.clear();
|
||||||
state.maps.clear();
|
state.maps.clear();
|
||||||
|
|
||||||
|
//Mark all layers for removal
|
||||||
|
for (const layer of state.layers.keys()) {
|
||||||
|
state.pendingLayerUpdates.set(layer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.layers.clear();
|
||||||
|
state.sortedLayers.splice(0);
|
||||||
|
|
||||||
state.currentZoom = 0;
|
state.currentZoom = 0;
|
||||||
state.currentLocation = {x: 0, y: 0, z: 0};
|
state.currentLocation = {x: 0, y: 0, z: 0};
|
||||||
|
|
||||||
|
@ -36,11 +36,12 @@ import {
|
|||||||
LiveAtlasChat,
|
LiveAtlasChat,
|
||||||
LiveAtlasUIModal,
|
LiveAtlasUIModal,
|
||||||
LiveAtlasSidebarSectionState,
|
LiveAtlasSidebarSectionState,
|
||||||
LiveAtlasMarker, LiveAtlasMapViewTarget
|
LiveAtlasMarker, LiveAtlasMapViewTarget, LiveAtlasLayerDefinition
|
||||||
} from "@/index";
|
} from "@/index";
|
||||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||||
import {getMessages} from "@/util";
|
import {getMessages} from "@/util";
|
||||||
import {getDefaultPlayerImage} from "@/util/images";
|
import {getDefaultPlayerImage} from "@/util/images";
|
||||||
|
import {Layer} from "leaflet";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
version: string;
|
version: string;
|
||||||
@ -58,6 +59,10 @@ export type State = {
|
|||||||
|
|
||||||
worlds: Map<string, LiveAtlasWorldDefinition>;
|
worlds: Map<string, LiveAtlasWorldDefinition>;
|
||||||
maps: Map<string, LiveAtlasMapDefinition>;
|
maps: Map<string, LiveAtlasMapDefinition>;
|
||||||
|
|
||||||
|
layers: Map<Layer, LiveAtlasLayerDefinition>;
|
||||||
|
sortedLayers: LiveAtlasLayerDefinition[];
|
||||||
|
|
||||||
players: Map<string, LiveAtlasPlayer>;
|
players: Map<string, LiveAtlasPlayer>;
|
||||||
sortedPlayers: LiveAtlasSortedPlayers;
|
sortedPlayers: LiveAtlasSortedPlayers;
|
||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
@ -68,6 +73,7 @@ export type State = {
|
|||||||
messages: LiveAtlasChat[];
|
messages: LiveAtlasChat[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pendingLayerUpdates: Map<Layer, boolean>; //Pending changes to map layer visibility
|
||||||
pendingMarkerUpdates: DynmapMarkerUpdate[];
|
pendingMarkerUpdates: DynmapMarkerUpdate[];
|
||||||
pendingTileUpdates: Array<DynmapTileUpdate>;
|
pendingTileUpdates: Array<DynmapTileUpdate>;
|
||||||
|
|
||||||
@ -130,6 +136,10 @@ export const state: State = {
|
|||||||
|
|
||||||
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
|
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
|
||||||
maps: new Map(), //Defined maps from configuration.json
|
maps: new Map(), //Defined maps from configuration.json
|
||||||
|
|
||||||
|
layers: new Map(), //Leaflet map layers
|
||||||
|
sortedLayers: [], //Layers sorted by position for layer control
|
||||||
|
|
||||||
players: new Map(), //Online players from world.json
|
players: new Map(), //Online players from world.json
|
||||||
sortedPlayers: [] as LiveAtlasSortedPlayers, //Online players from world.json, sorted by their sort property then alphabetically
|
sortedPlayers: [] as LiveAtlasSortedPlayers, //Online players from world.json, sorted by their sort property then alphabetically
|
||||||
maxPlayers: 0,
|
maxPlayers: 0,
|
||||||
@ -141,7 +151,8 @@ export const state: State = {
|
|||||||
|
|
||||||
markerSets: new Map(), //Marker sets from world_markers.json, doesn't include the markers themselves for performance reasons
|
markerSets: new Map(), //Marker sets from world_markers.json, doesn't include the markers themselves for performance reasons
|
||||||
|
|
||||||
pendingMarkerUpdates: [], //Pending updates to markers/areas/etc
|
pendingLayerUpdates: new Map(), //Pending updates to map layer visibility
|
||||||
|
pendingMarkerUpdates: [], //Pending updates to markers/areas/etc
|
||||||
pendingTileUpdates: [], //Pending updates to map tiles
|
pendingTileUpdates: [], //Pending updates to map tiles
|
||||||
|
|
||||||
// Map plugin provided settings for various parts of LiveAtlas
|
// Map plugin provided settings for various parts of LiveAtlas
|
||||||
|
44
src/util/layers.ts
Normal file
44
src/util/layers.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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 {Layer} from "leaflet";
|
||||||
|
import {LiveAtlasLayerDefinition} from "@/index";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
import {useStore} from "@/store";
|
||||||
|
|
||||||
|
export const sortLayers = (layers: Map<Layer, LiveAtlasLayerDefinition>) => {
|
||||||
|
return Array.from(layers.values()).sort((entry1, entry2) => {
|
||||||
|
if (entry1.position != entry2.position) {
|
||||||
|
return entry1.position - entry2.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((entry1.name < entry2.name) ? -1 : ((entry1.name > entry2.name) ? 1 : 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleLayer = (layer: Layer) => {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
if(!store.state.layers.has(layer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const enabled = !store.state.layers.get(layer)!.enabled;
|
||||||
|
|
||||||
|
store.commit(MutationTypes.UPDATE_LAYER, {
|
||||||
|
layer: layer,
|
||||||
|
options: {enabled}
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user