xref: /webtrees/resources/views/modules/places/tab.phtml (revision 53a83c54b8b942be5ba4e73683d0401e77cc44e4)
1<?php
2
3use Fisharebest\Webtrees\I18N;
4
5/**
6 * @var array<mixed> $data
7 * @var object       $leaflet_config
8 */
9
10?>
11
12<div class="py-4">
13    <div class="row gchart osm-wrapper">
14        <div id="osm-map" class="col-sm-9 wt-ajax-load osm-user-map" dir="ltr"></div>
15        <ul class="col-sm-3 osm-sidebar wt-page-options-value list-unstyled px-md-1"></ul>
16    </div>
17</div>
18
19<style>
20    .osm-wrapper, .osm-user-map {
21        height: 70vh
22    }
23
24    .osm-sidebar {
25        height: 100%;
26        overflow-y: auto;
27        font-size: small;
28    }
29</style>
30
31<script>
32    "use strict";
33
34    window.WT_OSM = (function() {
35        const config = <?= json_encode($leaflet_config) ?>;
36
37        let map       = null;
38        let zoom      = null;
39        let sidebar   = $('.osm-sidebar');
40
41        // Map components
42        let markers = L.markerClusterGroup({
43            showCoverageOnHover: false,
44        });
45
46        let resetControl = L.Control.extend({
47            options: {
48                position: "topleft",
49            },
50            onAdd: function(map) {
51                let container = L.DomUtil.create("div", "leaflet-bar leaflet-control leaflet-control-custom");
52                container.onclick = function() {
53                    if (zoom) {
54                        map.flyTo(markers.getBounds().getCenter(), zoom);
55                    } else {
56                        map.flyToBounds(markers.getBounds().pad(0.2));
57                    }
58                    sidebar.scrollTo(sidebar.children(":first"));
59
60                    return false;
61                };
62                let reset    = config.i18n.reset;
63                let anchor   = L.DomUtil.create('a', 'leaflet-control-reset', container);
64                anchor.setAttribute('aria-label', reset);
65                anchor.href  = '#';
66                anchor.title = reset;
67                anchor.role  = 'button';
68                let image    = L.DomUtil.create('i', 'fas fa-redo', anchor);
69                image.alt    = reset;
70
71                return container;
72            },
73        });
74
75        /**
76         *
77         * @private
78         */
79        let _drawMap = function() {
80            map = webtrees.buildLeafletJsMap('osm-map', config)
81                .addControl(new resetControl());
82        };
83
84        /**
85         *
86         * @private
87         */
88        let _buildMapData = function() {
89            let sidebar_content = "";
90            let data            = <?= json_encode($data) ?>;
91
92            if (data.features.length === 0) {
93                map.fitWorld();
94                sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show')) ?> + '</div>';
95            } else {
96                if (data.features.length === 1) {
97                    //fudge factor - maps zooms to maximum permitted otherwise
98                    zoom = data.features[0].properties.zoom;
99                }
100                let geoJsonLayer = L.geoJson(data, {
101                    pointToLayer: function(feature, latlng) {
102                        return new L.Marker(latlng, {
103                            icon: L.BeautifyIcon.icon({
104                                icon           : feature.properties.icon["name"],
105                                borderColor    : "transparent",
106                                backgroundColor: feature.properties.icon["color"],
107                                iconShape      : "marker",
108                                textColor      : "white",
109                            }),
110                            title: feature.properties.tooltip,
111                            alt  : feature.properties.tooltip,
112                            id   : feature.id,
113                        })
114                        .on("popupopen", function(e) {
115                            let item = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]");
116                            item.addClass("messagebox");
117                            sidebar.scrollTo(item);
118                        })
119                        .on("popupclose", function() {
120                            sidebar.children(".gchart")
121                                .removeClass("messagebox");
122                        });
123                    },
124                    onEachFeature: function(feature, layer) {
125                        layer.bindPopup(feature.properties.summary);
126                        sidebar_content += `<li class="gchart px-md-2" data-id=${feature.id}>${feature.properties.summary}</li>`;
127                    },
128                });
129                markers.addLayer(geoJsonLayer);
130                map.addLayer(markers);
131                if (zoom) {
132                    map.setView(markers.getBounds().getCenter(), zoom);
133                } else {
134                    map.fitBounds(markers.getBounds(), {padding: [50, 30]});
135                }
136            }
137            sidebar.append(sidebar_content);
138        };
139
140        /**
141         * @param   elem
142         * @returns {$}
143         */
144        $.fn.scrollTo = function(elem) {
145            let _this = $(this);
146            _this.animate({
147                scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
148            });
149            return this;
150        };
151
152        // Activate marker popup when sidebar entry clicked
153        $(function() {
154            sidebar
155            // open marker popup if sidebar event is clicked
156            .on('click', '.gchart', function(e) {
157                // first close any existing
158                map.closePopup();
159                let eventId = $(this).data('id');
160                //find the marker corresponding to the clicked event
161                let mkrLayer = markers.getLayers().filter(function(v) {
162                    return typeof(v.feature) !== 'undefined' && v.feature.id === eventId;
163                });
164                let mkr = mkrLayer.pop();
165                // Unfortunately zoomToShowLayer zooms to maxZoom
166                // when all marker in a cluster have exactly the
167                // same co-ordinates
168                markers.zoomToShowLayer(mkr, function(e) {
169                    mkr.openPopup();
170                });
171
172                return false;
173            })
174            .on('click', 'a', function(e) { // stop click on a person also opening the popup
175                e.stopPropagation();
176            });
177        });
178
179        _drawMap();
180        _buildMapData();
181
182        return "Leaflet map interface for webtrees-2";
183    })();
184</script>
185