Files
librenms-librenms/resources/views/map/custom-background-modal.blade.php
Tony Murray 0d246a6ffc Custom Maps: geo map and color backgrounds (#16020)
* Custom Maps: geo map and color background
tabs blade component
geo-map blade component and related script enhancements

* Update css/js

* style fixes

* update db_schema.yaml

* fix db_schema hand edit

* ignore phpstan being wrong

* Handle null

* another possible null spot

* Use standard file cache for custom map background images

* Create map->image as jpeg so we can compress it

* whitespace fix

* Fix background cancel button when other type is selected than the saved type

* Save and restore layer

* Map must exist before creating static image

* Don't show set as image button for Google and Bing.
Bing gives an odd error, but Google won't work.
2024-05-13 08:12:59 -05:00

200 lines
9.5 KiB
PHP

<div class="modal fade" id="bgModal" tabindex="-1" role="dialog" aria-labelledby="bgModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" x-data="backgroundModalData()" x-init="resetBackground">
<div class="modal-header">
<h5 class="modal-title" id="bgModalLabel">{{ __('map.custom.edit.bg.title') }}</h5>
</div>
<div class="modal-body tw-p-10">
<x-tabs class="tw-text-2xl" @tab-change="type=$event.detail" x-effect="activeTab = type">
<x-tab value="image" name="{{ __('map.custom.edit.bg.image') }}" class="tw-mt-10">
<x-input id="bgimage"
x-ref="bgimage"
type="file"
label="{{ __('map.custom.edit.bg.background') }}"
accept="image/png,image/jpeg,image/svg+xml,image/gif"
x-show="!image"
x-on:change="setImage($event)"></x-input>
<div x-show="image">
<span x-text="image"></span>
<button type="button" class="btn btn-danger" @click="clearImage">{{ __('map.custom.edit.bg.clear_background') }}</button>
</div>
</x-tab>
<x-tab value="color" name="{{ __('map.custom.edit.bg.color') }}" class="tw-mt-10">
<x-input id="bg-color" type="color" x-model="color"
class="tw-cursor-pointer tw-h-24 tw-w-48"
></x-input>
</x-tab>
<x-tab value="map" name="{{ __('map.custom.edit.bg.map') }}" class="tw-mt-5">
<x-input id="bg-lat" label="{{ __('map.custom.edit.bg.lat') }}" x-model="lat"></x-input>
<x-input id="bg-lng" label="{{ __('map.custom.edit.bg.lng') }}" x-model="lng"></x-input>
<x-input id="bg-zoom" label="{{ __('map.custom.edit.bg.zoom') }}" x-model="zoom"></x-input>
<button type="button" class="btn btn-primary tw-mt-2" @click="adjustMap">{{ __('map.custom.edit.bg.adjust_map') }}</button>
<button type="button" class="btn btn-primary tw-mt-2" @click="setMapAsImage" title="{{ __('map.custom.edit.bg.as_image_hint') }}" :disabled="saving_map_as_image" x-show="show_image_export">
<i class="fa-solid fa-circle-notch fa-spin" x-show="saving_map_as_image"></i>
{{ __('map.custom.edit.bg.as_image') }}
</button>
</x-tab>
<x-tab value="none" name="{{ __('map.custom.edit.bg.none') }}"></x-tab>
</x-tabs>
<div x-show="error">
<div class="tw-text-red-600" x-text="error"></div>
</div>
</div>
<div class="modal-footer">
<button type=button class="btn btn-primary" @click="saveBackground">{{ __('Save') }}</button>
<button type=button class="btn btn-default" @click="closeBackgroundModal">{{ __('Cancel') }}</button>
</div>
</div>
</div>
</div>
<script>
function backgroundModalData() {
return {
initial_data: {{ Js::from($background_config) }},
initial_type: {{ Js::from($background_type) }},
type: 'none',
color: null,
lat: null,
lng: null,
zoom: null,
layer: null,
image: null,
show_image_export: true,
image_content: null,
saving_map_as_image: false,
error: '',
resetBackground() {
this.type = this.initial_type;
this.color = 'color' in this.initial_data ? this.initial_data.color : '#badaee';
this.lat = 'lat' in this.initial_data ? this.initial_data.lat : 40;
this.lng = 'lng' in this.initial_data ? this.initial_data.lng : -20;
this.zoom = 'zoom' in this.initial_data ? this.initial_data.zoom : 3;
this.layer = 'layer' in this.initial_data ? this.initial_data.layer : null;
this.image = this.initial_data['original_filename'];
this.image_content = null;
this.show_image_export = (! 'engine' in this.initial_data) || ! ['google', 'bing'].includes(this.initial_data['engine']);
this.error = '';
setCustomMapBackground('custom-map', this.type, this.initial_data);
// stop map interaction
document.getElementById('custom-map-bg-geo-map').style.zIndex = '1';
const leaflet = get_map('custom-map-bg-geo-map');
if (leaflet) {
disable_map_interaction(leaflet)
leaflet.off('zoomend');
leaflet.off('moveend');
leaflet.off('baselayerchange');
leaflet.setView(L.latLng(this.lat, this.lng), this.zoom);
}
},
setImage(event) {
this.image_content = event.target.files[0];
},
clearImage() {
this.image = null;
this.image_content = null;
},
setMapAsImage() {
setCustomMapBackground('custom-map', this.type, this.initial_data);
this.saving_map_as_image = true;
leafletImage(get_map('custom-map-bg-geo-map'), (err, canvas) => {
if (! canvas) {
this.error = err;
return;
}
this.type = 'image';
this.image = 'geo-map.jpg';
canvas.toBlob((blob) => this.image_content = blob, 'image/jpeg', 0.5);
this.saving_map_as_image = false;
});
},
saveBackground() {
if (this.type === 'image' && ! this.image_content) {
// change to none type when saving bg image with no file
// helps with mental work flow of clicking clear image -> save.
this.type = 'none';
}
let fd = new FormData();
fd.append('type', this.type);
if (this.type === 'color') {
fd.append('color', this.color);
} else if (this.type === 'image') {
fd.append('image', this.image_content, this.image);
}
if (this.type === 'map' || this.image === 'geo-map.png') {
// include map data when we converted a map to a static image
fd.append('lat', this.lat);
fd.append('lng', this.lng);
fd.append('zoom', this.zoom);
fd.append('layer', this.layer);
}
fetch({{ Js::from(route('maps.custom.background.save', ['map' => $map_id])) }}, {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.head.querySelector('meta[name=\'csrf-token\']').content
},
body: fd
}).then((response) => {
if (response.status === 413) {
this.error = response.statusText;
return;
}
response.json().then(data => {
if (data.message) {
this.error = data.message;
} else {
setCustomMapBackground('custom-map', data.bgtype, data.bgdata);
this.initial_type = data.bgtype;
this.initial_data = data.bgdata;
// update jquery code
if (bgtype) {
bgtype = data.bgtype;
}
if (bgdata) {
bgdata = data.bgdata;
}
this.closeBackgroundModal();
}
})
})
.catch(() => {
this.error = 'Ooops! Something went wrong!'
});
},
adjustMap() {
let leaflet = init_map('custom-map-bg-geo-map', this.initial_data);
let adjustValues = () => {
const center = leaflet.getCenter();
this.lat = center.lat;
this.lng = center.lng;
this.zoom = leaflet.getZoom();
}
let layerChange = (event) => {this.layer = event.name};
leaflet._container.style.zIndex = '3';
enable_map_interaction(leaflet);
leaflet.on({
zoomend: adjustValues,
moveend: adjustValues,
baselayerchange: layerChange,
});
startBackgroundMapAdjust();
$('#bgModal').modal('hide');
},
closeBackgroundModal() {
$('#bgModal').modal('hide');
this.resetBackground();
}
}
}
</script>