Migrate LoadingControl to vue

This commit is contained in:
James Lyne 2022-06-25 13:15:11 +01:00
parent 7471bb794f
commit 318ccf6e33
5 changed files with 233 additions and 274 deletions

View File

@ -1,3 +1,3 @@
<component name="DependencyValidationManager">
<scope name="Original" pattern="!file:src/leaflet/control/ClockControl.ts&amp;&amp;!file:src/leaflet/control/CoordinatesControl.ts&amp;&amp;!file:src/leaflet/control/LinkControl.ts&amp;&amp;!file:src/leaflet/control/LogoControl.ts&amp;&amp;!file:src/leaflet/icon/PlayerIcon.ts&amp;&amp;!file:src/leaflet/icon/GenericIcon.ts&amp;&amp;!file:src/leaflet/tileLayer/DynmapTileLayer.ts&amp;&amp;!file:src/util/areas.ts&amp;&amp;!file:src/util/circles.ts&amp;&amp;!file:src/util/lines.ts&amp;&amp;!file:src/util/markers.ts&amp;&amp;!file[LiveAtlas]:standalone/*&amp;&amp;!file:src/model/LiveAtlasProjection.ts&amp;&amp;!file:src/leaflet/control/LiveAtlasLayerControl.ts&amp;&amp;!file[LiveAtlas]:patches/*&amp;&amp;!file[LiveAtlas]:public/*&amp;&amp;!file[LiveAtlas]:.idea/*&amp;&amp;!file[LiveAtlas]:.idea//*&amp;&amp;!file[LiveAtlas]:patches//*&amp;&amp;!file[LiveAtlas]:public//*&amp;&amp;!file[LiveAtlas]:standalone//*&amp;&amp;!file:FUNDING.yml&amp;&amp;!file:README.md&amp;&amp;!file:tsconfig.json&amp;&amp;!file:.gitignore&amp;&amp;!file:.env&amp;&amp;!file:LICENSE.md&amp;&amp;!file:package-lock.json&amp;&amp;!file:package.json&amp;&amp;!file:vite.config.ts&amp;&amp;!file:index.html&amp;&amp;!file:src/leaflet/control/LoadingControl.ts&amp;&amp;!file:src/scss/style.scss&amp;&amp;!file[LiveAtlas]:src/assets/icons//*&amp;&amp;!file:src/providers/OverviewerMapProvider.ts&amp;&amp;!file:src/providers/DynmapMapProvider.ts&amp;&amp;!file:src/leaflet/projection/OverviewerProjection.ts&amp;&amp;!file:src/leaflet/tileLayer/OverviewerTileLayer.ts&amp;&amp;!file:jest.config.ts&amp;&amp;!file:.npmignore&amp;&amp;!file:plugin.yml&amp;&amp;!file[LiveAtlas]:java/*&amp;&amp;!file[LiveAtlas]:java//*" />
<scope name="Original" pattern="!file:src/leaflet/control/ClockControl.ts&amp;&amp;!file:src/leaflet/control/CoordinatesControl.ts&amp;&amp;!file:src/leaflet/control/LinkControl.ts&amp;&amp;!file:src/leaflet/control/LogoControl.ts&amp;&amp;!file:src/leaflet/icon/PlayerIcon.ts&amp;&amp;!file:src/leaflet/icon/GenericIcon.ts&amp;&amp;!file:src/leaflet/tileLayer/DynmapTileLayer.ts&amp;&amp;!file:src/util/areas.ts&amp;&amp;!file:src/util/circles.ts&amp;&amp;!file:src/util/lines.ts&amp;&amp;!file:src/util/markers.ts&amp;&amp;!file[LiveAtlas]:standalone/*&amp;&amp;!file:src/model/LiveAtlasProjection.ts&amp;&amp;!file:src/leaflet/control/LiveAtlasLayerControl.ts&amp;&amp;!file[LiveAtlas]:patches/*&amp;&amp;!file[LiveAtlas]:public/*&amp;&amp;!file[LiveAtlas]:.idea/*&amp;&amp;!file[LiveAtlas]:.idea//*&amp;&amp;!file[LiveAtlas]:patches//*&amp;&amp;!file[LiveAtlas]:public//*&amp;&amp;!file[LiveAtlas]:standalone//*&amp;&amp;!file:FUNDING.yml&amp;&amp;!file:README.md&amp;&amp;!file:tsconfig.json&amp;&amp;!file:.gitignore&amp;&amp;!file:.env&amp;&amp;!file:LICENSE.md&amp;&amp;!file:package-lock.json&amp;&amp;!file:package.json&amp;&amp;!file:vite.config.ts&amp;&amp;!file:index.html&amp;&amp;!file:src/leaflet/control/LoadingControl.ts&amp;&amp;!file:src/scss/style.scss&amp;&amp;!file[LiveAtlas]:src/assets/icons//*&amp;&amp;!file:src/providers/OverviewerMapProvider.ts&amp;&amp;!file:src/providers/DynmapMapProvider.ts&amp;&amp;!file:src/leaflet/projection/OverviewerProjection.ts&amp;&amp;!file:src/leaflet/tileLayer/OverviewerTileLayer.ts&amp;&amp;!file:jest.config.ts&amp;&amp;!file:.npmignore&amp;&amp;!file:plugin.yml&amp;&amp;!file[LiveAtlas]:java/*&amp;&amp;!file[LiveAtlas]:java//*&amp;&amp;!file:src/components/map/control/LoadingControl.vue" />
</component>

View File

@ -21,7 +21,9 @@
</div>
<div id="ui__top-left" class="ui__section section--vertical">
<div class="ui__toolbar toolbar--vertical"></div>
<div class="ui__toolbar toolbar--vertical">
<LoadingControl :leaflet="leaflet" :delay="500"></LoadingControl>
</div>
</div>
<div id="ui__bottom-left" class="ui__section">
@ -52,11 +54,10 @@ import ClockControl from "@/components/map/control/ClockControl.vue";
import LinkControl from "@/components/map/control/LinkControl.vue";
import ChatControl from "@/components/map/control/ChatControl.vue";
import LogoControl from "@/components/map/control/LogoControl.vue";
import {LoadingControl} from "@/leaflet/control/LoadingControl";
import MapContextMenu from "@/components/map/MapContextMenu.vue";
import LoginControl from "@/components/map/control/LoginControl.vue";
import {onMounted} from "vue";
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
import LoadingControl from "@/components/map/control/LoadingControl.vue";
export default defineComponent({
props: {
@ -67,6 +68,7 @@ export default defineComponent({
},
components: {
LoadingControl,
LogoControl,
CoordinatesControl,
LinkControl,
@ -87,13 +89,6 @@ export default defineComponent({
logoControls = computed(() => store.state.components.logoControls);
onMounted(() => {
props.leaflet.addControl(new LoadingControl({
position: 'topleft',
delayIndicator: 500,
}))
});
return {
contextMenuEnabled,
coordinatesControlEnabled,

View File

@ -0,0 +1,226 @@
<!--
- 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.
-
- Portions of this file are taken from Leaflet.loading:
-
- Copyright (c) 2013 Eric Brelsford
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
-->
<template>
<div class="ui__element ui__button loading" :title="loadingTitle" :hidden="!showIndicator">
<SvgIcon name="loading"></SvgIcon>
</div>
</template>
<script lang="ts">
import {computed, defineComponent, onUnmounted, watch} from "@vue/runtime-core";
import SvgIcon from "@/components/SvgIcon.vue";
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
import {onMounted, ref} from "vue";
import {Layer, LayerEvent, LeafletEvent, TileLayer} from "leaflet";
import {useStore} from "@/store";
import '@/assets/icons/loading.svg';
export default defineComponent({
components: {SvgIcon},
props: {
leaflet: {
type: Object as () => LiveAtlasLeafletMap,
required: true,
},
delay: {
type: Number,
default: 0
}
},
setup(props) {
const store = useStore(),
loadingTitle = computed(() => store.state.messages.loadingTitle),
dataLoaders = ref<Set<number>>(new Set()),
showIndicator = ref<boolean>(false);
let delayIndicatorTimeout: ReturnType<typeof setTimeout> | null = null;
const addLayerListeners = () => {
// Add listeners for begin and end of load to any layers already
// on the map
props.leaflet.eachLayer((layer: Layer) => {
if(!(layer instanceof TileLayer)) {
return;
}
if(layer.isLoading()) {
dataLoaders.value.add((layer as any)._leaflet_id);
}
layer.on('loading', handleLoading);
layer.on('load', handleLoad);
});
// When a layer is added to the map, add listeners for begin and
// end of load
props.leaflet.on('layeradd', layerAdd);
props.leaflet.on('layerremove', layerRemove);
};
const removeLayerListeners = () => {
// Remove listeners for begin and end of load from all layers
props.leaflet.eachLayer((layer: Layer) => {
if(!(layer instanceof TileLayer)) {
return;
}
dataLoaders.value.delete((layer as any)._leaflet_id);
layer.off('loading', handleLoading);
layer.off('load', handleLoad);
});
// Remove layeradd/layerremove listener from map
props.leaflet.off('layeradd', layerAdd);
props.leaflet.off('layerremove', layerRemove);
};
const layerAdd = (e: LayerEvent) => {
if(!(e.layer instanceof TileLayer)) {
return;
}
try {
if(e.layer.isLoading()) {
handleLoading(e);
}
e.layer.on('loading', handleLoading);
e.layer.on('load', handleLoad);
} catch (exception) {
console.warn('L.Control.Loading: Tried and failed to add ' +
' event handlers to layer', e.layer);
console.warn('L.Control.Loading: Full details', exception);
}
};
const layerRemove = (e: LayerEvent) => {
if(!(e.layer instanceof TileLayer)) {
return;
}
handleLoad(e);
try {
e.layer.off('loading', handleLoading);
e.layer.off('load', handleLoad);
} catch (exception) {
console.warn('L.Control.Loading: Tried and failed to remove ' +
'event handlers from layer', e.layer);
console.warn('L.Control.Loading: Full details', exception);
}
};
const handleLoading = (e: LeafletEvent) => dataLoaders.value.add(getEventId(e))
const handleLoad = (e: LeafletEvent) => dataLoaders.value.delete(getEventId(e));
const getEventId = (e: any) => {
if (e.id) {
return e.id;
} else if (e.layer) {
return e.layer._leaflet_id;
}
return e.target._leaflet_id;
};
watch(dataLoaders, (newValue) => {
if(props.delay) { // If we are delaying showing the indicator
if(newValue.size > 0) {
// If we're not already waiting for that delay, set up a timeout.
if(!delayIndicatorTimeout) {
setTimeout(() => showIndicator.value = true)
}
} else {
// If removing this loader means we're in no danger of loading,
// clear the timeout. This prevents old delays from instantly
// triggering the indicator.
showIndicator.value = false;
clearTimeout(Number(delayIndicatorTimeout));
delayIndicatorTimeout = null;
}
return;
} else {
// Otherwise update the indicator immediately
showIndicator.value = !!newValue.size;
}
}, {deep: true});
onMounted(() => {
// Add listeners to the map for (custom) dataloading and dataload
// events, eg, for AJAX calls that affect the map but will not be
// reflected in the above layer events.
props.leaflet.on('dataloading', handleLoading);
props.leaflet.on('dataload', handleLoad);
addLayerListeners();
});
onUnmounted(() => {
props.leaflet.off('dataloading', handleLoading);
props.leaflet.off('dataload', handleLoad);
removeLayerListeners();
});
return {
loadingTitle,
dataLoaders,
showIndicator
}
}
});
</script>
<style lang="scss" scoped>
.loading {
cursor: wait;
animation: fade 0.3s linear;
animation-fill-mode: forwards;
&:hover, &:active, &:focus {
background-color: var(--background-base);
}
&[hidden] {
display: none;
}
}
</style>

View File

@ -1,248 +0,0 @@
/*
Portions of this file are taken from Leaflet.loading:
Copyright (c) 2013 Eric Brelsford
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import {
Control,
ControlOptions,
DomUtil,
Layer,
LeafletEvent,
Map, TileLayer,
} from 'leaflet';
import '@/assets/icons/loading.svg';
import {useStore} from "@/store";
export interface LoadingControlOptions extends ControlOptions {
delayIndicator?: number;
}
/**
* Leaflet map control which displays a loading spinner when any tiles are loading
*/
export class LoadingControl extends Control {
declare options: LoadingControlOptions;
private _dataLoaders: Set<number> = new Set();
private readonly _loadingIndicator: HTMLDivElement;
private _delayIndicatorTimeout: null | ReturnType<typeof setTimeout> = null;
constructor(options: LoadingControlOptions) {
super(options);
this._loadingIndicator = DomUtil.create('div',
'leaflet-control-button leaflet-control-loading') as HTMLDivElement;
}
onAdd(map: Map) {
this._loadingIndicator.title = useStore().state.messages.loadingTitle;
this._loadingIndicator.hidden = true;
this._loadingIndicator.innerHTML = `
<svg class="svg-icon">
<use xlink:href="#icon--loading" />
</svg>`;
this._addLayerListeners(map);
this._addMapListeners(map);
return this._loadingIndicator;
}
onRemove(map: Map) {
this._removeLayerListeners(map);
this._removeMapListeners(map);
}
addLoader(id: number) {
this._dataLoaders.add(id);
if (this.options.delayIndicator && !this._delayIndicatorTimeout) {
// If we are delaying showing the indicator and we're not
// already waiting for that delay, set up a timeout.
this._delayIndicatorTimeout = setTimeout(() => {
this.updateIndicator();
this._delayIndicatorTimeout = null;
}, this.options.delayIndicator);
} else {
// Otherwise show the indicator immediately
this.updateIndicator();
}
}
removeLoader(id: number) {
this._dataLoaders.delete(id);
this.updateIndicator();
// If removing this loader means we're in no danger of loading,
// clear the timeout. This prevents old delays from instantly
// triggering the indicator.
if (this.options.delayIndicator && this._delayIndicatorTimeout && !this.isLoading()) {
clearTimeout(this._delayIndicatorTimeout);
this._delayIndicatorTimeout = null;
}
}
updateIndicator() {
if (this.isLoading()) {
this._showIndicator();
}
else {
this._hideIndicator();
}
}
isLoading() {
return this._countLoaders() > 0;
}
_countLoaders() {
return this._dataLoaders.size;
}
_showIndicator() {
this._loadingIndicator.hidden = false;
}
_hideIndicator() {
this._loadingIndicator.hidden = true;
}
_handleLoading(e: LeafletEvent) {
this.addLoader(this.getEventId(e));
}
_handleBaseLayerChange (e: LeafletEvent) {
// Check for a target 'layer' that contains multiple layers, such as
// L.LayerGroup. This will happen if you have an L.LayerGroup in an
// L.Control.Layers.
if (e.layer && e.layer.eachLayer && typeof e.layer.eachLayer === 'function') {
e.layer.eachLayer((layer: Layer) => {
this._handleBaseLayerChange({ layer: layer } as LeafletEvent);
});
}
}
_handleLoad(e: LeafletEvent) {
this.removeLoader(this.getEventId(e));
}
getEventId(e: any) {
if (e.id) {
return e.id;
} else if (e.layer) {
return e.layer._leaflet_id;
}
return e.target._leaflet_id;
}
_layerAdd(e: LeafletEvent) {
if(!(e.layer instanceof TileLayer)) {
return;
}
try {
if(e.layer.isLoading()) {
this.addLoader((e.layer as any)._leaflet_id);
}
e.layer.on('loading', this._handleLoading, this);
e.layer.on('load', this._handleLoad, this);
} catch (exception) {
console.warn('L.Control.Loading: Tried and failed to add ' +
' event handlers to layer', e.layer);
console.warn('L.Control.Loading: Full details', exception);
}
}
_layerRemove(e: LeafletEvent) {
if(!(e.layer instanceof TileLayer)) {
return;
}
try {
e.layer.off('loading', this._handleLoading, this);
e.layer.off('load', this._handleLoad, this);
} catch (exception) {
console.warn('L.Control.Loading: Tried and failed to remove ' +
'event handlers from layer', e.layer);
console.warn('L.Control.Loading: Full details', exception);
}
}
_addLayerListeners(map: Map) {
// Add listeners for begin and end of load to any layers already
// on the map
map.eachLayer((layer: Layer) => {
if(!(layer instanceof TileLayer)) {
return;
}
if(layer.isLoading()) {
this.addLoader((layer as any)._leaflet_id);
}
layer.on('loading', this._handleLoading, this);
layer.on('load', this._handleLoad, this);
});
// When a layer is added to the map, add listeners for begin and
// end of load
map.on('layeradd', this._layerAdd, this);
map.on('layerremove', this._layerRemove, this);
}
_removeLayerListeners(map: Map) {
// Remove listeners for begin and end of load from all layers
map.eachLayer((layer: Layer) => {
if(!(layer instanceof TileLayer)) {
return;
}
this.removeLoader((layer as any)._leaflet_id);
layer.off('loading', this._handleLoading, this);
layer.off('load', this._handleLoad, this);
});
// Remove layeradd/layerremove listener from map
map.off('layeradd', this._layerAdd, this);
map.off('layerremove', this._layerRemove, this);
}
_addMapListeners(map: Map) {
// Add listeners to the map for (custom) dataloading and dataload
// events, eg, for AJAX calls that affect the map but will not be
// reflected in the above layer events.
map.on('baselayerchange', this._handleBaseLayerChange, this);
map.on('dataloading', this._handleLoading, this);
map.on('dataload', this._handleLoad, this);
map.on('layerremove', this._handleLoad, this);
}
_removeMapListeners(map: Map) {
map.off('baselayerchange', this._handleBaseLayerChange, this);
map.off('dataloading', this._handleLoading, this);
map.off('dataload', this._handleLoad, this);
map.off('layerremove', this._handleLoad, this);
}
}

View File

@ -261,17 +261,3 @@
margin: 0;
}
}
.leaflet-control-loading {
cursor: wait;
animation: fade 0.3s linear;
animation-fill-mode: forwards;
&:hover, &:active, &:focus {
background-color: var(--background-base);
}
&[hidden] {
display: none;
}
}