PageSourceSearch

https://www.eifelsteig.de/_assets/1798f66a72c0609b2ee5b404d10475ff…/Structures/MapList.8f3f9490e71ea5c2f109.js

js eifelsteig.de collected 2026-10-02 10:07:24 UTC 17,942 bytes, 519 lines download raw bytes

1/******/ (() => { // webpackBootstrap
2/******/ 	"use strict";
3// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules.
4(() => {
5// extracted by mini-css-extract-plugin
6
7})();
8
9// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules.
10(() => {
11
12;// ./packages/portal/Resources/Private/Components/Atoms/Map/Scripts/base/util/spiderifier.ts
13const TWO_PI = Math.PI * 2;
14const CLASSNAME_CONTAINER = 'map__leg';
15const CLASSNAME_LINE = 'map__leg__line';
16const CLASSNAME_PIN = 'map__leg__pin';
17class Spiderifier {
18  map;
19  options = {
20    circleSpiralSwitchover: 9,
21    circleFootSeparation: 25,
22    spiralFootSeparation: 28,
23    spiralLengthStart: 15,
24    spiralLengthFactor: 4
25  };
26  previousSpiderLegs = [];
27  constructor(map, options = {}) {
28    this.map = map;
29    this.options = {
30      ...this.options,
31      ...options
32    };
33    map.on('zoomstart', () => {
34      this.unspiderfy();
35    });
36    map.on('click', event => {
37      if (!event.originalEvent.target.closest(CLASSNAME_CONTAINER)) {
38        this.unspiderfy();
39      }
40    });
41  }
42  spiderfy(latLng, data) {
43    this.unspiderfy();
44    const spiderLegParams = this.generateSpiderLegParams(data.length);
45    const spiderLegs = data.map((item, index) => {
46      const params = spiderLegParams[index];
47      const elements = this.createMarkerElements(params, item);
48      return {
49        data: item,
50        elements: elements,
51        marker: new maplibregl.Marker({
52          element: elements.container
53        }).setLngLat(latLng),
54        param: params
55      };
56    });
57    spiderLegs.forEach(leg => leg.marker.addTo(this.map));
58    this.previousSpiderLegs = spiderLegs;
59    if (this.options.spiderfyCallback) {
60      this.options.spiderfyCallback();
61    }
62  }
63  unspiderfy() {
64    this.previousSpiderLegs.forEach(leg => leg.marker.remove());
65    this.previousSpiderLegs = [];
66    if (this.options.unspiderfyCallback) {
67      this.options.unspiderfyCallback();
68    }
69  }
70  generateSpiderLegParams(count) {
71    if (count >= this.options.circleSpiralSwitchover) {
72      return this.generateSpiralParams(count);
73    } else {
74      return this.generateCircleParams(count);
75    }
76  }
77  generateSpiralParams(count) {
78    let legLength = this.options.spiralLengthStart;
79    let angle = 0;
80    const output = [];
81    for (let index = 0; index < count; index++) {
82      angle += this.options.spiralFootSeparation / legLength + index * 0.0005;
83      output.push({
84        x: legLength * Math.cos(angle),
85        y: legLength * Math.sin(angle),
86        angle: angle,
87        legLength: legLength,
88        index: index
89      });
90      legLength += TWO_PI * this.options.spiralLengthFactor / angle;
91    }
92    return output;
93  }
94  generateCircleParams(count) {
95    const circumference = this.options.circleFootSeparation * (2 + count);
96    const legLength = circumference / TWO_PI;
97    const angleStep = TWO_PI / count;
98    const output = [];
99    for (let index = 0; index < count; index++) {
100      const angle = index * angleStep;
101      output.push({
102        x: legLength * Math.cos(angle),
103        y: legLength * Math.sin(angle),
104        angle: angle,
105        legLength: legLength,
106        index: index
107      });
108    }
109    return output;
110  }
111  createMarkerElements(params, data) {
112    const container = document.createElement('div');
113    const line = document.createElement('div');
114    const pin = document.createElement('div');
115    container.classList.add(CLASSNAME_CONTAINER);
116    container.style.marginLeft = params.x + 'px';
117    container.style.marginTop = params.y + 'px';
118    line.classList.add(CLASSNAME_LINE);
119    line.style.height = params.legLength + 'px';
120    line.style.transform = 'rotate(' + (params.angle - Math.PI / 2) + 'rad)';
121    pin.classList.add(CLASSNAME_PIN);
122    if (this.options.markerBuilderCallback) {
123      pin.appendChild(this.options.markerBuilderCallback(data).getElement());
124    }
125    container.appendChild(line);
126    container.appendChild(pin);
127    return {
128      container: container,
129      line: line,
130      pin: pin
131    };
132  }
133}
134;
134// ./packages/portal/Resources/Private/Components/Atoms/Map/Scripts/base/marker-painter/cluster-marker-painter.ts
135
136const SOURCE = 'imx-clustersource';
137const LAYER = 'imx-clusterlayer';
138const MAXZOOM = 16;
139class ClusterMarkerPainter {
140  maptoolkit;
141  util;
142  maplibre;
143  spiderifier;
144  builtMarkers = {};
145  shownMarkers = {};
146  markerCallback = (data, marker) => marker;
147  constructor(maptoolkit, util) {
148    this.maptoolkit = maptoolkit;
149    this.util = util;
150    this.maplibre = maptoolkit.gl;
151    this.maplibre.addSource(SOURCE, {
152      type: 'geojson',
153      cluster: true,
154      clusterMaxZoom: 18,
155      // max zoom level to not resolve clusters into individual markers
156      data: {
157        type: 'FeatureCollection',
158        features: []
159      }
160    });
161    this.maplibre.addLayer({
162      id: LAYER,
163      type: 'symbol',
164      source: SOURCE
165    });
166    this.maplibre.on('render', () => {
167      if (!this.maplibre.isSourceLoaded(SOURCE)) return;
168      const features = this.maplibre.querySourceFeatures(SOURCE);
169      const newMarkers = {};
170      for (const feature of features) {
171        const props = feature.properties;
172        const id = props.cluster_id ?? props.ident;
173        if (!this.builtMarkers[id]) {
174          if (props.cluster) {
175            this.builtMarkers[id] = this.builtMarkers[id] ?? this.buildClusterMarker(feature);
176          } else {
177            const data = JSON.parse(props.marker);
178            const marker = data.icon ? this.util.createIconMarker(data) : this.util.createDefaultMarker(data);
179            this.builtMarkers[id] = this.markerCallback(data, marker);
180          }
181        }
182        newMarkers[id] = this.builtMarkers[id];
183        if (!this.shownMarkers[id]) {
184          this.builtMarkers[id].addTo(this.maplibre);
185        }
186      }
187      for (const id in this.shownMarkers) {
188        if (!newMarkers[id]) {
189          this.shownMarkers[id].remove();
190        }
191      }
192      this.shownMarkers = newMarkers;
193    });
194    this.spiderifier = new Spiderifier(this.maplibre, {
195      spiralLengthStart: 45,
196      circleFootSeparation: 75,
197      spiderfyCallback: () => Object.values(this.builtMarkers).forEach(marker => this.util.backgroundMarker(marker)),
198      unspiderfyCallback: () => Object.values(this.builtMarkers).forEach(marker => this.util.foregroundMarker(marker)),
199      markerBuilderCallback: data => {
200        return this.markerCallback(data, data.icon ? this.util.createIconMarker(data) : this.util.createDefaultMarker(data));
201      }
202    });
203  }
204  paint(data, callback) {
205    this.markerCallback = callback ?? ((data, marker) => marker);
206    Object.keys(this.builtMarkers).forEach(id => this.builtMarkers[id].remove());
207    Object.keys(this.shownMarkers).forEach(id => this.shownMarkers[id].remove());
208    this.builtMarkers = {};
209    this.shownMarkers = {};
210    this.maplibre.getSource(SOURCE).setData({
211      type: 'FeatureCollection',
212      features: data.map(marker => {
213        return {
214          type: 'Feature',
215          geometry: {
216            type: 'Point',
217            coordinates: marker.lnglat
218          },
219          properties: {
220            ident: marker.ident,
221            marker: marker
222          }
223        };
224      })
225    });
226  }
227  buildClusterMarker(feature) {
228    const marker = this.util.createClusterMarker(feature.properties.point_count, feature.geometry.coordinates);
229    marker.getElement().addEventListener('click', () => {
230      if (this.maplibre.getZoom() < MAXZOOM) {
231        this.zoomToCluster(feature);
232      } else {
233        this.spiderify(feature);
234      }
235    });
236    return marker;
237  }
238  zoomToCluster(feature) {
239    this.maplibre.getSource(SOURCE).getClusterExpansionZoom(feature.properties.cluster_id, (error, zoom) => {
240      if (error) return;
241      if (zoom > MAXZOOM) {
242        this.spiderify(feature);
243      }
244      this.maplibre.easeTo({
245        center: feature.geometry.coordinates,
246        zoom: Math.max(zoom, this.maplibre.getZoom() + 2)
247      });
248    });
249  }
250  spiderify(feature) {
251    this.maplibre.getSource(SOURCE).getClusterLeaves(feature.properties.cluster_id, 100, 0, (error, leaves) => {
252      if (error) {
253        return console.error('error while getting leaves of a cluster', error);
254      }
255      this.spiderifier.spiderfy(feature.geometry.coordinates, leaves.map(leaf => leaf.properties.marker));
256    });
257  }
258}
259;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Classes/json-request.ts
260window.imx = window.imx || {};
261window.imx.jsonrequest = window.imx.jsonrequest || {};
262class JsonRequest {
263  request(url) {
264    if (!window.imx.jsonrequest[url]) {
265      window.imx.jsonrequest[url] = fetch(url).then(response => {
266        return response.json();
267      });
268    }
269    return window.imx.jsonrequest[url];
270  }
271}
272;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Util/dispatch-custom-event.ts
273/**
274 * Creates and dispatches a custom event, that can be used to loosely couple things together.
275 */
276function dispatchCustomEvent(target, name, data) {
277  const event = new CustomEvent(name, {
278    detail: data || {}
279  });
280  target.dispatchEvent(event);
281}
282;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Util/load-style.ts
283window.imx = window.imx || {};
284window.imx.styleloader = window.imx.styleloader || {};
285function loadStyle(file) {
286  window.imx.styleloader[file] = window.imx.styleloader[file] || new Promise((resolve, reject) => {
287    if (document.querySelector(`link[href*="${file}"]`)) {
288      resolve(file);
289      return;
290    }
291    const link = document.createElement('link');
292    link.href = file;
293    link.rel = 'stylesheet';
294    link.onload = () => resolve(file);
295    link.onerror = () => reject(new Error(`Style load error for ${file}`));
296    document.head.appendChild(link);
297  });
298  return window.imx.styleloader[file];
299}
300;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Util/load-script.ts
301window.imx = window.imx || {};
302window.imx.scriptloader = window.imx.scriptloader || {};
303function loadScript(file) {
304  window.imx.scriptloader[file] = window.imx.scriptloader[file] || new Promise((resolve, reject) => {
305    if (document.querySelector(`script[src*="${file}"]`)) {
306      resolve(file);
307      return;
308    }
309    const script = document.createElement('script');
310    script.setAttribute('src', file);
311    script.onload = () => resolve(file);
312    script.onerror = () => reject(new Error(`Script load error for ${file}`));
313    document.body.appendChild(script);
314  });
315  return window.imx.scriptloader[file];
316}
317;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Classes/content-loader.ts
318/**
319 * LICENSE
320 *
321 * This software and its source code is protected by copyright law (Sec. 69a ff. UrhG).
322 * It is not allowed to make any kinds of modifications, nor must it be copied,
323 * or published without explicit permission. Misuse will lead to persecution.
324 *
325 * @copyright  2020 infomax websolutions GmbH
326 * @link       https://www.infomax-online.de
327 * @author     Benjamin Hofmann <[email protected]>
328 * @since      2020-02-17
329 */
330
331
332
333
334/**
335 *
336 *
337 * @copyright  2020 infomax websolutions GmbH
338 * @link       https://www.infomax-online.de
339 */
340class ContentLoader {
341  constructor() {
342    this.request = new JsonRequest();
343  }
344  load(url) {
345    const promise = this.request.request(url).then(json => {
346      return Promise.all([new Promise(resolve => resolve(json.content)), this.injectCss(json.assets.css), this.injectJavaScript(json.assets.js)]);
347    }).then(data => {
348      return data[0];
349    });
350    Promise.all([promise]).finally(() => {
351      dispatchCustomEvent(document, 'ContentLoader.load');
352    });
353    return promise;
354  }
355  injectCss(files) {
356    const promises = [];
357    files.filter(file => {
358      return !document.querySelector(`link[href*="${file}"]`);
359    }
359).forEach(file => {
360      promises.push(loadStyle(file));
361    });
362    return Promise.all(promises);
363  }
364  injectJavaScript(files) {
365    const promises = [];
366    files.filter(file => {
367      return !document.querySelector(`script[src*="${file}"]`);
368    }).forEach(file => {
369      promises.push(loadScript(file));
370    });
371    return Promise.all(promises);
372  }
373}
374;// ./packages/portal/Resources/Private/Components/Assistants/Shared/Scripts/Classes/base-url.ts
375class BaseUrl {
376  getBaseUrl() {
377    const pathSegments = window.location.pathname.split('/').filter(Boolean);
378    const possibleLangCode = pathSegments[0];
379    const isLangCode = possibleLangCode && /^[a-z]{2,3}$/.test(possibleLangCode);
380    const basePath = isLangCode ? `/${possibleLangCode}` : '/';
381    return new URL(`${basePath}`, window.location.origin).toString();
382  }
383}
384;// ./packages/portal/Resources/Private/Components/Structures/MapList/Scripts/map/_init.ts
385
386
387
388const loader = new ContentLoader();
389const baseUrl = new BaseUrl();
390document.querySelectorAll('.mapList').forEach(mapList => {
391  const mapWrapper = mapList.querySelector('.mapList__wrapper__map');
392  const listWrapper = mapList.querySelector('.mapList__wrapper__list');
393  const listItems = listWrapper.querySelectorAll('.mapList__listItem .cardTeaser');
394  const infoboard = mapList.querySelector('.mapList__infoboard');
395  const infoboardCloser = infoboard.querySelector('.mapList__infoboard__closer');
396  const infoboardContent = infoboard.querySelector('.mapList__infoboard__content');
397  const markerMap = new Map();
398  const mql = window.matchMedia('(max-width: 1000px)');
399  let activeMarker;
400  let activePolyline;
401  let listVisible = false;
402  const markerData = JSON.parse(mapWrapper.dataset.markers);
403  const allCoordinates = markerData.map(data => data.lnglat);
404  // Calculate dynamic map padding based on screen size and visibility
405  const getPaddingOptions = () => ({
406    top: !listVisible && mql.matches ? 150 : 70,
407    right: !listVisible && mql.matches ? 40 : listWrapper.offsetWidth + 40,
408    bottom: 40,
409    left: 40
410  });
411  const showInfoboard = (ident, map, util) => {
412    infoboard.removeAttribute('hidden');
413    infoboard.setAttribute('data-loading', '1');
414    // Remove previous highlighted marker
415    if (activeMarker) {
416      util.lowlightMarker(activeMarker);
417      activeMarker = undefined;
418    }
419    // Remove previous polyline
420    if (activePolyline) {
421      activePolyline.remove();
422      activePolyline = undefined;
423    }
424    const url = new URL(`${baseUrl.getBaseUrl()}/action/teaser`, window.location.origin);
425    url.searchParams.set('ident', ident);
426    url.searchParams.set('parent', mapWrapper.dataset.parent);
427    return loader.load(url.toString()).then(response => {
428      infoboard.setAttribute('data-loading', '0');
429      infoboardContent.innerHTML = response.html;
430      if (response.polyline) {
431        activePolyline = util.createPolyline(response.polyline).addTo(map);
432        util.fitBounds(response.polyline);
433      }
434    });
435  };
436  const hideInfoboard = (util, map) => {
437    infoboard.setAttribute('hidden', 'hidden');
438    infoboard.setAttribute('data-loading', '0');
439    infoboardContent.innerHTML = '';
440    if (activeMarker) {
441      util.lowlightMarker(activeMarker);
442      activeMarker = undefined;
443    }
444    // Remove previous polyline
445    if (activePolyline) {
446      activePolyline.remove();
447      activePolyline = undefined;
448    }
449    util.fitBounds(allCoordinates);
450  };
451  document.addEventListener(`map.${mapWrapper.id.replace('--map', '')}.ready`, event => {
452    const {
453      map,
454      util
455    } = event.detail;
456    // Ensure map resizes when wrapper is in view
457    new IntersectionObserver(() => map.gl.resize()).observe(mapWrapper);
458    // Adjust padding when list visibility changes (e.g., on scroll or resize)
459    new IntersectionObserver(([entry]) => {
460      listVisible = entry.isIntersecting;
461      map.gl.setPadding(getPaddingOptions());
462      util.fitBounds(allCoordinates);
463    }).observe(listWrapper);
464    // Adjust padding when screen width crosses threshold
465    mql.addEventListener('change', () => {
466      map.gl.setPadding(getPaddingOptions());
467      util.fitBounds(allCoordinates);
468    });
469    // Close infoboard when close button is clicked
470    infoboardCloser.addEventListener('click', () => hideInfoboard(util, map));
471    new ClusterMarkerPainter(map, util).paint(markerData, (data, marker) => {
472      markerMap.set(data.ident, marker);
473      // Click on marker opens infoboard and polyline
474      marker.getElement().addEventListener('click', () => {
475        showInfoboard(data.ident, map, util).then(() => {
476          util.highlightMarker(marker);
477          activeMarker = marker;
478        });
479      });
480      return marker;
481    });
482    // Highlight markers on hover in the list
483    listItems.forEach(item => {
484      const ident = item.getAttribute('data-ident');
485      item.addEventListener('mouseenter', () => {
486        const marker = markerMap.get(ident);
487        if (marker) util.highlightMarker(marker);
488      });
489      item.addEventListener('mouseleave', () => {
490        const marker = markerMap.get(ident);
491        if (marker && marker !== activeMarker) util.lowlightMarker(marker);
492      });
493    });
494  });
495});
496})();
497
498// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules.
499(() => {
500
501
502const mql = window.matchMedia('(max-width: 1000px)');
503const mapWrappers = document.querySelectorAll('.mapList__wrapper__map');
504const listWrappers = document.querySelectorAll('.mapList__wrapper__list');
505function updateListView(mql) {
506  if (mql.matches) {
507    mapWrappers.forEach(wrapper => wrapper.setAttribute('hidden', 'hidden'));
508    listWrappers.forEach(wrapper => wrapper.removeAttribute('hidden'));
509  } else {
510    mapWrappers.forEach(wrapper => wrapper.removeAttribute('hidden'));
511    listWrappers.forEach(wrapper => wrapper.removeAttribute('hidden'));
512  }
513}
514mql.addEventListener('change', event => updateListView(event));
515updateListView(mql);
516})();
517
518/******/ })()
519;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.