adt-js-components 1.9.4 → 1.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adt-js-components",
3
- "version": "1.9.4",
3
+ "version": "1.9.6",
4
4
  "description": "JavaScript components for Nette framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Map/index.js CHANGED
@@ -7,6 +7,7 @@ import {RouteSetting, defaultRouteSettings} from './route.types.js';
7
7
 
8
8
  let siteKey;
9
9
  let markerImg;
10
+ const svgCache = new Map();
10
11
  const mapInstances = new WeakMap();
11
12
  const selectedMarkers = new WeakMap();
12
13
  const selectionOrder = new WeakMap();
@@ -16,6 +17,8 @@ const routeSettingsMap = new WeakMap();
16
17
  const markersDataMap = new WeakMap();
17
18
  const markerClusters = new WeakMap();
18
19
  const onSelectionChangeMap = new WeakMap();
20
+ const onBeforeRouteCalculationMap = new WeakMap();
21
+ const onAfterRouteCalculationMap = new WeakMap();
19
22
 
20
23
  const DEPOT_TYPE = {
21
24
  START: 'depot-start',
@@ -40,6 +43,8 @@ async function run(options) {
40
43
  customMarkers = {},
41
44
  showSelectionOrder = false,
42
45
  markerInfoCallback = null,
46
+ onBeforeRouteCalculation = null,
47
+ onAfterRouteCalculation = null,
43
48
  } = JSON.parse(el.dataset.adtMap);
44
49
 
45
50
  /** @type {RouteSetting} */
@@ -63,6 +68,8 @@ async function run(options) {
63
68
  routeSettingsMap.set(map, routeSettings);
64
69
  markersDataMap.set(map, markers);
65
70
  onSelectionChangeMap.set(map, onSelectionChange);
71
+ onBeforeRouteCalculationMap.set(map, onBeforeRouteCalculation);
72
+ onAfterRouteCalculationMap.set(map, onAfterRouteCalculation);
66
73
 
67
74
  L.tileLayer("https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=" + siteKey, {
68
75
  minZoom: 0,
@@ -157,9 +164,7 @@ async function run(options) {
157
164
  }
158
165
 
159
166
  if (routeSettings.enabled) {
160
- setTimeout(() => {
161
- calculateRoute(map);
162
- }, 500);
167
+ calculateRoute(map);
163
168
  }
164
169
 
165
170
  if (callback) {
@@ -175,11 +180,10 @@ async function run(options) {
175
180
  if (mutation.type === "childList") {
176
181
  mutation.addedNodes.forEach(node => {
177
182
  if (node.nodeType === 1) {
178
- // Zkontroluj samotný node
179
183
  if (node.hasAttribute("data-adt-map")) {
180
184
  applyEventHandlers(node);
181
185
  }
182
- // Zkontroluj všechny potomky
186
+
183
187
  node.querySelectorAll?.('[data-adt-map]').forEach(el => {
184
188
  applyEventHandlers(el);
185
189
  });
@@ -344,9 +348,7 @@ function addMarkers(map, markers, options, selectedOptions, position, selectable
344
348
  map.addLayer(cluster);
345
349
 
346
350
  if (loadedCount === totalMarkers) {
347
- setTimeout(() => {
348
- checkAndApplyPreselection(map, markers, showSelectionOrder, onSelectionChange, selectable);
349
- }, 200);
351
+ checkAndApplyPreselection(map, markers, showSelectionOrder, onSelectionChange, selectable);
350
352
  }
351
353
  }
352
354
 
@@ -371,7 +373,7 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
371
373
  const markers = markerInstances.get(map);
372
374
  markers.set(marker.id, mapMarker);
373
375
 
374
- mapMarker.on('add', function() {
376
+ mapMarker.on('add', function () {
375
377
  const el = mapMarker.getElement();
376
378
  if (el) {
377
379
  el.style.cursor = 'pointer';
@@ -512,22 +514,29 @@ async function updateMarkerOrderDisplay(map, marker, orderNumber, isSelected, co
512
514
 
513
515
  async function createMarkerIcon(map, iconUrl, orderNumber, color = null) {
514
516
  let inlineStyle = null;
515
- const response = await fetch(iconUrl);
516
- const svg = await response.text();
517
517
  const settings = routeSettingsMap.get(map);
518
518
 
519
519
  if ((settings && settings.enabled) || color) {
520
520
  inlineStyle = `--marker-fill: ${color ?? settings.color};`;
521
521
  }
522
522
 
523
+ let svg;
524
+ if (svgCache.has(iconUrl)) {
525
+ svg = svgCache.get(iconUrl);
526
+ } else {
527
+ const response = await fetch(iconUrl);
528
+ svg = await response.text();
529
+ svgCache.set(iconUrl, svg);
530
+ }
531
+
523
532
  return L.divIcon({
524
533
  className: 'marker-icon-wrapper',
525
534
  html: `
526
- <div class="marker-icon" style="${inlineStyle}">
527
- ${svg}
528
- ${orderNumber ? `<span class="selection-order-label">${orderNumber}</span>` : ''}
529
- </div>
530
- `,
535
+ <div class="marker-icon" style="${inlineStyle}">
536
+ ${svg}
537
+ ${orderNumber ? `<span class="selection-order-label">${orderNumber}</span>` : ''}
538
+ </div>
539
+ `,
531
540
  iconSize: [43, 58],
532
541
  iconAnchor: [21, 58]
533
542
  });
@@ -584,65 +593,58 @@ async function calculateRoute(map) {
584
593
  const hasCustomStart = routeSettings.startPoint !== null && routeSettings.startPoint !== undefined;
585
594
  const hasCustomEnd = routeSettings.endPoint !== null && routeSettings.endPoint !== undefined;
586
595
 
587
- if (hasCustomStart) {
588
- addDepotMarker(map, routeSettings.startPoint, DEPOT_TYPE.START);
589
- }
590
- if (hasCustomEnd) {
591
- addDepotMarker(map, routeSettings.endPoint, DEPOT_TYPE.END);
592
- }
596
+ if (hasCustomStart) addDepotMarker(map, routeSettings.startPoint, DEPOT_TYPE.START);
597
+ if (hasCustomEnd) addDepotMarker(map, routeSettings.endPoint, DEPOT_TYPE.END);
593
598
 
594
- if (selectedSet && selectedSet.size > 0) {
595
- const orderedIds = Array.from(order.entries())
596
- .sort((a, b) => a[1] - b[1])
597
- .map(entry => entry[0]);
598
- const routeMarkers = orderedIds
599
- .map(id => markers.find(m => m.id === id))
600
- .filter(m => m !== undefined);
599
+ if (!selectedSet || selectedSet.size === 0) return;
601
600
 
602
- if (routeMarkers.length < 2) {
603
- return;
604
- }
601
+ const orderedIds = Array.from(order.entries())
602
+ .sort((a, b) => a[1] - b[1])
603
+ .map(entry => entry[0]);
604
+ const routeMarkers = orderedIds
605
+ .map(id => markers.find(m => m.id === id))
606
+ .filter(m => m !== undefined);
605
607
 
606
- let startPoint = routeMarkers[0].position;
607
- if (hasCustomStart) {
608
- startPoint = routeSettings.startPoint;
609
- }
608
+ if (routeMarkers.length < 2) return;
610
609
 
611
- let endPoint = routeMarkers[routeMarkers.length - 1].position;
612
- if (hasCustomEnd) {
613
- endPoint = routeSettings.endPoint;
614
- }
610
+ const beforeCallback = onBeforeRouteCalculationMap.get(map);
611
+ if (beforeCallback && window[beforeCallback]) {
612
+ window[beforeCallback]();
613
+ }
614
+
615
+ let startPoint = hasCustomStart ? routeSettings.startPoint : routeMarkers[0].position;
616
+ let endPoint = hasCustomEnd ? routeSettings.endPoint : routeMarkers[routeMarkers.length - 1].position;
615
617
 
616
- // API mapy.cz povoluje max 15 waypointů, viz https://developer.mapy.com/cs/rest-api/funkce/planovani/
617
- const WAYPOINTS_LIMIT = 15;
618
- const allCoords = [];
618
+ const WAYPOINTS_LIMIT = 15;
619
+ const allCoords = [];
620
+ const allParts = [];
621
+ let totalDuration = 0;
622
+ let totalLength = 0;
619
623
 
624
+ try {
620
625
  if (routeMarkers.length <= WAYPOINTS_LIMIT) {
621
- // Pokud je markerů méně nebo rovno 15, použijeme původní logiku
622
626
  const params = new URLSearchParams({
623
627
  start: `${startPoint.lon},${startPoint.lat}`,
624
628
  end: `${endPoint.lon},${endPoint.lat}`,
625
629
  routeType: routeSettings.routeType,
626
630
  apikey: siteKey
627
631
  });
632
+ routeMarkers.forEach(m => params.append('waypoints', `${m.position.lon},${m.position.lat}`));
628
633
 
629
- routeMarkers.forEach(m => {
630
- params.append('waypoints', `${m.position.lon},${m.position.lat}`);
631
- });
632
-
633
- try {
634
- const response = await fetch(
635
- `https://api.mapy.cz/v1/routing/route?${params.toString()}`
636
- );
637
- const data = await response.json();
634
+ const response = await fetch(`https://api.mapy.cz/v1/routing/route?${params.toString()}`);
635
+ const data = await response.json();
638
636
 
639
- if (data.geometry?.geometry?.coordinates) {
640
- const coords = data.geometry.geometry.coordinates.map(c => [c[1], c[0]]);
641
- allCoords.push(...coords);
642
- }
643
- } catch (error) {
644
- console.error('Error calculating route:', error);
645
- return;
637
+ if (data.geometry?.geometry?.coordinates) {
638
+ allCoords.push(...data.geometry.geometry.coordinates.map(c => [c[1], c[0]]));
639
+ }
640
+ if (data.parts) {
641
+ allParts.push(...data.parts);
642
+ }
643
+ if (data.duration) {
644
+ totalDuration += data.duration;
645
+ }
646
+ if (data.length) {
647
+ totalLength += data.length;
646
648
  }
647
649
  } else {
648
650
  const chunks = [];
@@ -655,18 +657,8 @@ async function calculateRoute(map) {
655
657
  const isFirstChunk = i === 0;
656
658
  const isLastChunk = i === chunks.length - 1;
657
659
 
658
- let chunkStart, chunkEnd;
659
-
660
- if (isFirstChunk) {
661
- chunkStart = startPoint; // Použije custom start nebo první marker
662
- chunkEnd = isLastChunk ? endPoint : chunk[chunk.length - 1].position;
663
- } else if (isLastChunk) {
664
- chunkStart = chunks[i - 1][chunks[i - 1].length - 1].position;
665
- chunkEnd = endPoint; // Použije custom end nebo poslední marker
666
- } else {
667
- chunkStart = chunks[i - 1][chunks[i - 1].length - 1].position;
668
- chunkEnd = chunk[chunk.length - 1].position;
669
- }
660
+ const chunkStart = isFirstChunk ? startPoint : chunks[i - 1][chunks[i - 1].length - 1].position;
661
+ const chunkEnd = isLastChunk ? endPoint : chunk[chunk.length - 1].position;
670
662
 
671
663
  const params = new URLSearchParams({
672
664
  start: `${chunkStart.lon},${chunkStart.lat}`,
@@ -676,38 +668,56 @@ async function calculateRoute(map) {
676
668
  });
677
669
 
678
670
  const markersToAdd = isFirstChunk ? chunk : chunk.slice(1);
671
+ markersToAdd.forEach(m => params.append('waypoints', `${m.position.lon},${m.position.lat}`));
679
672
 
680
- markersToAdd.forEach(m => {
681
- params.append('waypoints', `${m.position.lon},${m.position.lat}`);
682
- });
683
-
684
- try {
685
- const response = await fetch(
686
- `https://api.mapy.cz/v1/routing/route?${params.toString()}`
687
- );
688
- const data = await response.json();
673
+ const response = await fetch(`https://api.mapy.cz/v1/routing/route?${params.toString()}`);
674
+ const data = await response.json();
689
675
 
690
- if (data.geometry?.geometry?.coordinates) {
691
- const coords = data.geometry.geometry.coordinates.map(c => [c[1], c[0]]);
692
- allCoords.push(...coords);
693
- }
694
- } catch (error) {
695
- console.error(`Error calculating route chunk ${i + 1}:`, error);
696
- return;
676
+ if (data.geometry?.geometry?.coordinates) {
677
+ allCoords.push(...data.geometry.geometry.coordinates.map(c => [c[1], c[0]]));
678
+ }
679
+ if (data.parts) {
680
+ allParts.push(...data.parts);
681
+ }
682
+ if (data.duration) {
683
+ totalDuration += data.duration;
684
+ }
685
+ if (data.length) {
686
+ totalLength += data.length;
697
687
  }
698
688
  }
699
689
  }
690
+ } catch (error) {
691
+ console.error('Error calculating route:', error);
692
+ const afterCallback = onAfterRouteCalculationMap.get(map);
693
+ if (afterCallback && window[afterCallback]) window[afterCallback]({}, 0, 0);
700
694
 
701
- if (allCoords.length > 0) {
702
- const polyline = L.polyline(allCoords, {
703
- color: routeSettings.color,
704
- weight: routeSettings.weight,
705
- opacity: routeSettings.opacity
706
- }).addTo(map);
695
+ return;
696
+ }
707
697
 
708
- routePolylines.set(map, polyline);
709
- }
698
+ if (allCoords.length > 0) {
699
+ const polyline = L.polyline(allCoords, {
700
+ color: routeSettings.color,
701
+ weight: routeSettings.weight,
702
+ opacity: routeSettings.opacity
703
+ }).addTo(map);
704
+ routePolylines.set(map, polyline);
710
705
  }
706
+
707
+ // @type {TRouteParts}
708
+ const routeParts = {};
709
+ allParts.forEach((part, index) => {
710
+ if (index < orderedIds.length) {
711
+ const markerId = orderedIds[index];
712
+ routeParts[markerId] = {
713
+ duration: part.duration,
714
+ length: part.length,
715
+ };
716
+ }
717
+ });
718
+
719
+ const afterCallback = onAfterRouteCalculationMap.get(map);
720
+ if (afterCallback && window[afterCallback]) window[afterCallback](routeParts, totalLength, totalDuration);
711
721
  }
712
722
 
713
723
  function addDepotMarker(map, position, depotType) {
@@ -724,7 +734,7 @@ function addDepotMarker(map, position, depotType) {
724
734
  iconAnchor: [img.width / 2, img.height]
725
735
  });
726
736
 
727
- L.marker(position, { icon: icon }).addTo(map);
737
+ L.marker(position, {icon: icon}).addTo(map);
728
738
  };
729
739
  }
730
740
 
@@ -805,10 +815,31 @@ function setOrder(mapElement, markerId, orderNumber) {
805
815
  updateMarkerOrderDisplay(mapElement, marker, orderNumber, true, color);
806
816
  }
807
817
 
818
+ function onBeforeRouteCalculation(mapElement, callbackName) {
819
+ const map = mapInstances.get(mapElement);
820
+ if (!map) return;
821
+ onBeforeRouteCalculationMap.set(map, callbackName);
822
+ }
823
+
824
+ function onAfterRouteCalculation(mapElement, callbackName) {
825
+ const map = mapInstances.get(mapElement);
826
+ if (!map) return;
827
+ onAfterRouteCalculationMap.set(map, callbackName);
828
+ }
829
+
830
+ function recalculateRoute(mapElement) {
831
+ const map = mapInstances.get(mapElement);
832
+ if (!map) return;
833
+ calculateRoute(map);
834
+ }
835
+
808
836
  export default {
809
837
  run,
810
838
  getSelectedMarkers,
811
839
  clearSelection,
812
840
  toggleMarker,
813
841
  setOrder,
842
+ onBeforeRouteCalculation,
843
+ onAfterRouteCalculation,
844
+ recalculateRoute,
814
845
  }
@@ -4,7 +4,11 @@ async function run(options) {
4
4
  if ($el.is('button, input[type="button"], input[type="submit"]')) {
5
5
  $el.on('click', sendNetteAjax);
6
6
  } else if ($el.is('input:not([type="button"]):not([type="submit"]), select, textarea')) {
7
- $el.on('input', sendNetteAjax);
7
+ let debounceTimer;
8
+ $el.on('input', function(e) {
9
+ clearTimeout(debounceTimer);
10
+ debounceTimer = setTimeout(() => sendNetteAjax(e), 500);
11
+ });
8
12
  }
9
13
  }
10
14
 
@@ -110,6 +110,14 @@ function run(options) {
110
110
 
111
111
  $.nette.ext("submitForm", {
112
112
  before: function (xhr, settings) {
113
+ if (settings.nette && settings.nette.el) {
114
+ const pendingXhr = settings.nette.el.data('pendingXhr');
115
+ if (pendingXhr) {
116
+ pendingXhr.abort();
117
+ }
118
+ settings.nette.el.data('pendingXhr', xhr);
119
+ }
120
+
113
121
  if (settings.nette && settings.nette.form && settings.nette.form.attr('data-adt-submit-form') !== undefined) {
114
122
  let beforeCallback = settings.nette.el.attr('data-adt-submit-form-before-callback');
115
123
  if (beforeCallback) {
@@ -156,6 +164,10 @@ function run(options) {
156
164
  }
157
165
  },
158
166
  complete: function (xhr, status, settings) {
167
+ if (settings.nette && settings.nette.el) {
168
+ settings.nette.el.data('pendingXhr', null);
169
+ }
170
+
159
171
  // if there are errors we will scroll to first of them
160
172
  if (settings.nette && settings.nette.form && settings.nette.form.attr('data-adt-submit-form') !== undefined && settings.nette.form.find('.alert-danger, .is-invalid').length > 0) {
161
173
  scrollToFirstError(settings.nette.form);