Sebastiankay commited on
Commit
e13e941
·
verified ·
1 Parent(s): c0891cf

Update templates/map.html

Browse files
Files changed (1) hide show
  1. templates/map.html +1037 -88
templates/map.html CHANGED
@@ -1,101 +1,1050 @@
1
  <!DOCTYPE html>
2
- <html lang="en" class="h-full bg-gray-900">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>{{ maps[0].name }} | Map with friends</title>
7
 
8
- <!-- Favicon -->
9
- <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
 
10
 
11
- <!-- Leaflet -->
12
- <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
13
- <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
 
 
 
 
 
14
 
15
- <!-- Tailwind -->
16
- <script src="https://cdn.tailwindcss.com"></script>
17
 
18
- <!-- Custom Styles -->
19
- <link rel="stylesheet" href="/static/style.css" />
20
- </head>
21
 
22
- <body class="h-full flex flex-col text-gray-200">
23
- <!-- Navbar -->
24
- <nav class="bg-gray-800 border-b border-gray-700">
25
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
26
- <div class="flex items-center justify-between h-16">
27
- <div class="flex items-center">
28
- <a href="#" class="text-xl font-bold text-white">Tarkov Map</a>
29
- <div class="ml-10 space-x-4 hidden md:flex">
30
- <a href="/" class="text-gray-300 hover:text-white">Home</a>
31
- <div class="relative group">
32
- <button class="text-gray-300 hover:text-white inline-flex items-center">
33
- Maps <svg class="ml-1 w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M5 7l5 5 5-5H5z" /></svg>
34
- </button>
35
- <div class="absolute hidden group-hover:block mt-2 w-48 bg-gray-800 border border-gray-700 rounded-lg shadow-lg">
36
- <a href="/map/woods" class="block px-4 py-2 hover:bg-gray-700">Woods</a>
37
- <a href="/map/shoreline" class="block px-4 py-2 hover:bg-gray-700">Shoreline</a>
38
- <a href="/map/lighthouse" class="block px-4 py-2 hover:bg-gray-700">Lighthouse</a>
39
- <a href="/map/customs" class="block px-4 py-2 hover:bg-gray-700">Customs</a>
40
- <a href="/map/interchange" class="block px-4 py-2 hover:bg-gray-700">Interchange</a>
41
- <a href="/map/streets-of-tarkov" class="block px-4 py-2 hover:bg-gray-700">Streets of Tarkov</a>
42
- <a href="/map/ground-zero" class="block px-4 py-2 hover:bg-gray-700">Ground Zero</a>
43
- <a href="/map/reserve" class="block px-4 py-2 hover:bg-gray-700">Reserve</a>
44
- <a href="/map/factory" class="block px-4 py-2 hover:bg-gray-700">Factory</a>
45
- </div>
46
- </div>
47
- </div>
48
- </div>
49
- <!-- Settings Button -->
50
- <button onclick="openModal('settings-modal')" class="text-gray-300 hover:text-white">
51
- <i class="fas fa-cog"></i>
52
- </button>
53
- </div>
 
54
  </div>
55
- </nav>
 
56
 
57
- <!-- Map -->
58
- <div id="map" class="flex-1 w-full"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- <!-- Settings Modal -->
61
- <div id="settings-modal" class="fixed inset-0 hidden items-center justify-center bg-black bg-opacity-70 z-50">
62
- <div class="bg-gray-800 rounded-xl shadow-lg max-w-lg w-full p-6 relative">
63
- <button onclick="closeModal('settings-modal')" class="absolute top-3 right-3 text-gray-400 hover:text-white">✕</button>
64
- <h2 class="text-xl font-bold mb-4">Einstellungen</h2>
65
- <p class="text-sm text-gray-400">Hier die Einstellungen basierend auf der config.ini file.</p>
66
- <pre class="mt-4 text-xs bg-gray-900 p-2 rounded text-gray-300">
67
- [mainconfig]
68
- eft_log_older_path = E:/Battlestate Games/Escape from Tarkov/Logs/
69
- eft_screenshots_folder_path = C:/Users/Sebastian/Documents/Escape from Tarkov/Screenshots/
70
- delete_screenshots = True
71
- eft_show_exits_key = o
72
- eft_take_screenshot_key = del
73
- </pre
74
- >
75
  </div>
76
  </div>
77
 
78
- <!-- Scripts -->
79
- <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/js/all.min.js"></script>
80
- <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
81
- <script src="/static/js/leaflet.controlCoordinates.js"></script>
82
- <script>
83
- const mapData = {{ maps[0] | tojson }}
84
- console.info(mapData)
85
- </script>
86
-
87
- <script src="/static/js/map.js"></script>
88
 
89
- <script>
90
- // Simple modal logic
91
- function openModal(id) {
92
- document.getElementById(id).classList.remove("hidden")
93
- document.getElementById(id).classList.add("flex")
94
- }
95
- function closeModal(id) {
96
- document.getElementById(id).classList.add("hidden")
97
- document.getElementById(id).classList.remove("flex")
98
- }
99
- </script>
100
- </body>
101
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
 
 
 
 
3
 
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
 
8
+ <!--title>Tarkov Map with friends</title-->
9
+ <title>{{ maps[0].name }} | Map with friends</title>
10
+
11
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico" />
12
+
13
+ <!-- Leaflet -->
14
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
15
+ <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
16
 
17
+ <!-- Tailwind -->
18
+ <script src="https://cdn.tailwindcss.com"></script>
19
 
20
+ <!-- Bootstrap -->
21
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
 
22
 
23
+ <!-- Custom Styles -->
24
+ <link rel="stylesheet" href="/static/style.css" />
25
+ </head>
26
+
27
+ <body data-bs-theme="dark">
28
+ <nav class="navbar navbar-expand-sm navbar-dark fixed-top">
29
+ <div class="container-fluid">
30
+ <a class="navbar-brand" href="#">Tarkov Map</a>
31
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
32
+ <span class="navbar-toggler-icon"></span>
33
+ </button>
34
+ <div class="collapse navbar-collapse" id="navbarNavDropdown">
35
+ <ul class="navbar-nav">
36
+ <li class="nav-item">
37
+ <a class="nav-link active" aria-current="page" href="#">Home</a>
38
+ </li>
39
+ <li class="nav-item dropdown">
40
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
41
+ Maps
42
+ </a>
43
+ <ul class="dropdown-menu">
44
+ <li><a href="/map/woods" class="dropdown-item">Woods</a></li>
45
+ <li><a href="/map/shoreline" class="dropdown-item">Shoreline</a></li>
46
+ <li><a href="/map/lighthouse" class="dropdown-item">Lighthouse</a></li>
47
+ <li><a href="/map/customs" class="dropdown-item">Customs</a></li>
48
+ <li><a href="/map/interchange" class="dropdown-item">Interchange</a></li>
49
+ <li><a href="/map/streets-of-tarkov" class="dropdown-item">Streets of Tarkov</a></li>
50
+ <li><a href="/map/ground-zero" class="dropdown-item">Ground Zero</a></li>
51
+ <li><a href="/map/reserve" class="dropdown-item">Reserve</a></li>
52
+ <li><a href="/map/factory" class="dropdown-item">Factory</a></li>
53
+ </ul>
54
+ </li>
55
+ </ul>
56
  </div>
57
+ </div>
58
+ </nav>
59
 
60
+ <div id="map"></div>
61
+
62
+
63
+ <div id="settings-modal" class="modal">
64
+ <div class="modal-background"></div>
65
+
66
+ <div class="modal-content container">
67
+ <div class="box">
68
+ <section class="hero">
69
+ <div class="hero-body">
70
+ <p class="title has-text-weight-bold">EINSTELLUNGEN</p>
71
+ <hr class="title-line">
72
+ <p class="subtitle">Subtitle</p>
73
+ </div>
74
+ </section>
75
+ <!-- Your content -->
76
+ <section class="section">
77
+ <div class="content">
78
+ <p>
79
+ Hier die Einstellungen basierend auf der config.ini file.
80
 
81
+ {#
82
+ [mainconfig]
83
+ eft_log_older_path = E:/Battlestate Games/Escape from Tarkov/Logs/
84
+ eft_screenshots_folder_path = C:/Users/Sebastian/Documents/Escape from Tarkov/Screenshots/
85
+ delete_screenshots = True
86
+ eft_show_exits_key = o
87
+ eft_take_screenshot_key = del #}
88
+ </p>
89
+ </div>
90
+ </section>
 
 
 
 
 
91
  </div>
92
  </div>
93
 
94
+ <button class="modal-close is-large" aria-label="close"></button>
95
+ </div>
 
 
 
 
 
 
 
 
96
 
97
+ <!-- Scripts -->
98
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script>
99
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/js/all.min.js"></script>
100
+ <script src="/static/js/leaflet.controlGroupedlayer.js"></script>
101
+ <script src="/static/js/leaflet.controlCoordinates.js"></script>
102
+ <script>
103
+ // GLOBAL VARIABLE TO HOLD THE CURRENT MARKER
104
+ const playerMarkers = {}
105
+ let currentMarker = null
106
+ const client_id = Date.now()
107
+ const mapData = {{ maps[0] | tojson }}
108
+ console.info(mapData)
109
+ //const transform = [0.1855, 113.1, 0.1855, 167.8]
110
+ const transform = mapData.transform
111
+ const bounds = mapData.bounds
112
+ const svgBounds = mapData.svgBounds ? mapData.svgBounds : bounds
113
+ const coordinateRotation = mapData.coordinateRotation ? mapData.coordinateRotation : 0
114
+ const svgPath = mapData.svgPath
115
+ const imageUrl = mapData.svgPath
116
+ const minZoom = mapData.minZoom ? mapData.minZoom : 1
117
+ const maxZoom = mapData.maxZoom ? mapData.maxZoom : 6
118
+ const showElevation = false;
119
+ const showStaticMarkers = false
120
+ const wsgroup = "1234"
121
+ let currentZoom = 3;
122
+ //MARK: INIT WEBSOCKET
123
+ // Initialize WebSocket connection
124
+ const ws = new WebSocket(`wss://sebastiankay-eft-group-map-websocket.hf.space/ws`);
125
+ // WebSocket event handlers
126
+ ws.onmessage = function (event) {
127
+ console.log(event.data);
128
+ const data = JSON.parse(event.data);
129
+ switch (data.type) {
130
+ case "coordinates":
131
+ const parsedData = data.data;
132
+ localStorage.setItem("last_marker", JSON.stringify(parsedData));
133
+ loadLocalData();
134
+ break;
135
+ case "location_map":
136
+ const map_name = data.data.map.toLowerCase().replaceAll(" ", "-");
137
+ localStorage.setItem("last_map_name", map_name);
138
+ localStorage.removeItem("last_marker");
139
+ removeAllMarkers(); // Clear all markers when the map changes
140
+ if (!location.pathname.includes(map_name)) {
141
+ location.pathname = `/map/${map_name}`;
142
+ } else {
143
+ location.reload();
144
+ }
145
+ break;
146
+ case "new_rade_data":
147
+ console.log(data.data);
148
+ const radeData = JSON.parse(data.data);
149
+ localStorage.setItem("rade_data", JSON.stringify(radeData));
150
+ loadLocalData();
151
+ break;
152
+ }
153
+ };
154
+ ws.onopen = function (event) {
155
+ ws.send(JSON.stringify({ type: "join", group: wsgroup }));
156
+ };
157
+ // Function to remove all markers
158
+ function removeAllMarkers() {
159
+ for (const playername in playerMarkers) {
160
+ if (playerMarkers.hasOwnProperty(playername)) {
161
+ map.removeLayer(playerMarkers[playername]);
162
+ }
163
+ }
164
+ Object.keys(playerMarkers).forEach(key => delete playerMarkers[key]);
165
+ console.log("Alle Marker wurden entfernt.");
166
+ }
167
+ // Function to load and display local marker data
168
+ function loadLocalData() {
169
+ const map_name = localStorage.getItem("last_map_name");
170
+ if (map_name) {
171
+ if (!location.pathname.includes(map_name)) {
172
+ localStorage.removeItem("last_marker");
173
+ }
174
+ } else {
175
+ localStorage.removeItem("last_marker");
176
+ }
177
+ const markerData = localStorage.getItem("last_marker");
178
+ if (markerData) {
179
+ const parsedData = JSON.parse(markerData);
180
+ // Use the new addMarker function with all required parameters
181
+ addMarker(
182
+ parsedData.x,
183
+ parsedData.y,
184
+ parsedData.z,
185
+ parsedData.timestamp,
186
+ parsedData.preview || false,
187
+ parsedData.actualmap || "Unbekannte Map",
188
+ parsedData.playername || "Unnamed Player",
189
+ parsedData.markercolor || false
190
+ );
191
+ }
192
+ }
193
+ const images = {
194
+ 'container_bank-cash-register': 'container_cash-register',
195
+ 'container_bank-safe': 'container_safe',
196
+ 'container_buried-barrel-cache': 'container_buried-barrel-cache',
197
+ 'container_cash-register': 'container_cash-register',
198
+ 'container_cash-register-tar2-2': 'container_cash-register',
199
+ 'container_dead-civilian': 'container_dead-scav',
200
+ 'container_dead-scav': 'container_dead-scav',
201
+ 'container_festive-airdrop-supply-crate': 'container_festive-airdrop-supply-crate',
202
+ 'container_pmc-body': 'container_dead-scav',
203
+ 'container_civilian-body': 'container_dead-scav',
204
+ 'container_drawer': 'container_drawer',
205
+ 'container_duffle-bag': 'container_duffle-bag',
206
+ 'container_grenade-box': 'container_grenade-box',
207
+ 'container_ground-cache': 'container_ground-cache',
208
+ 'container_jacket': 'container_jacket',
209
+ 'container_lab-technician-body': 'container_dead-scav',
210
+ 'container_medbag-smu06': 'container_medbag-smu06',
211
+ 'container_medcase': 'container_medcase',
212
+ 'container_medical-supply-crate': 'container_crate',
213
+ 'container_pc-block': 'container_pc-block',
214
+ 'container_plastic-suitcase': 'container_plastic-suitcase',
215
+ 'container_ration-supply-crate': 'container_crate',
216
+ 'container_safe': 'container_safe',
217
+ 'container_scav-body': 'container_dead-scav',
218
+ 'container_shturmans-stash': 'container_weapon-box',
219
+ 'container_technical-supply-crate': 'container_crate',
220
+ 'container_toolbox': 'container_toolbox',
221
+ 'container_weapon-box': 'container_weapon-box',
222
+ 'container_wooden-ammo-box': 'container_wooden-ammo-box',
223
+ 'container_wooden-crate': 'container_wooden-crate',
224
+ 'extract_pmc': 'extract_pmc',
225
+ 'extract_scav': 'extract_scav',
226
+ 'extract_shared': 'extract_shared',
227
+ 'extract_transit': 'extract_transit',
228
+ 'hazard': 'hazard',
229
+ 'hazard_mortar': 'hazard_mortar',
230
+ 'hazard_minefield': 'hazard',
231
+ 'hazard_sniper': 'hazard',
232
+ 'key': 'key',
233
+ 'lock': 'lock',
234
+ 'loose_loot': 'loose_loot',
235
+ 'quest_item': 'quest_item',
236
+ 'quest_objective': 'quest_objective',
237
+ 'spawn_sniper_scav': 'spawn_sniper_scav',
238
+ 'spawn_bloodhound': 'spawn_bloodhound',
239
+ 'spawn_boss': 'spawn_boss',
240
+ 'spawn_cultist-priest': 'spawn_cultist-priest',
241
+ 'spawn_pmc': 'spawn_pmc',
242
+ 'spawn_rogue': 'spawn_rogue',
243
+ 'spawn_scav': 'spawn_scav',
244
+ 'stationarygun': 'stationarygun',
245
+ 'switch': 'switch',
246
+ };
247
+ const categories = {
248
+ 'extract_pmc': 'PMC',
249
+ 'extract_shared': 'Shared',
250
+ 'extract_scav': 'Scav',
251
+ 'extract_transit': 'Transit',
252
+ 'spawn_sniper_scav': 'Sniper Scav',
253
+ 'spawn_pmc': 'PMC',
254
+ 'spawn_scav': 'Scav',
255
+ 'spawn_boss': 'Boss',
256
+ 'quest_item': 'Item',
257
+ 'quest_objective': 'Objective',
258
+ 'lock': 'Locks',
259
+ 'lever': 'Lever',
260
+ 'stationarygun': 'Stationary Gun',
261
+ 'switch': 'Switch',
262
+ 'place-names': 'Place Names',
263
+ };
264
+ function getCRS(transform) {
265
+ let scaleX = 1;
266
+ let scaleY = 1;
267
+ let marginX = 0;
268
+ let marginY = 0;
269
+ if (transform) {
270
+ scaleX = transform[0];
271
+ scaleY = transform[2] * -1;
272
+ marginX = transform[1];
273
+ marginY = transform[3];
274
+ }
275
+ return L.extend({}, L.CRS.Simple, {
276
+ transformation: new L.Transformation(scaleX, marginX, scaleY, marginY),
277
+ projection: L.extend({}, L.Projection.LonLat, {
278
+ project: latLng => {
279
+ return L.Projection.LonLat.project(applyRotation(latLng, coordinateRotation));
280
+ },
281
+ unproject: point => {
282
+ return applyRotation(L.Projection.LonLat.unproject(point), coordinateRotation * -1);
283
+ },
284
+ }),
285
+ });
286
+ }
287
+ function applyRotation(latLng, rotation) {
288
+ if (!latLng.lng && !latLng.lat) {
289
+ return L.latLng(0, 0);
290
+ }
291
+ if (!rotation) {
292
+ return latLng;
293
+ }
294
+ const angleInRadians = (rotation * Math.PI) / 180;
295
+ const cosAngle = Math.cos(angleInRadians);
296
+ const sinAngle = Math.sin(angleInRadians);
297
+ const { lng: x, lat: y } = latLng;
298
+ const rotatedX = x * cosAngle - y * sinAngle;
299
+ const rotatedY = x * sinAngle + y * cosAngle;
300
+ return L.latLng(rotatedY, rotatedX);
301
+ }
302
+ function pos(position) {
303
+ return [position.z, position.x];
304
+ }
305
+ function addElevation(item, popup) {
306
+ if (!showElevation) {
307
+ return;
308
+ }
309
+ const elevationContent = L.DomUtil.create('div', undefined, popup);
310
+ elevationContent.textContent = `Elevation: ${item.position.y.toFixed(2)}`;
311
+ if (item.top && item.bottom && item.top !== item.position.y && item.bottom !== item.position.y) {
312
+ const heightContent = L.DomUtil.create('div', undefined, popup);
313
+ heightContent.textContent = `Top ${item.top.toFixed(2)}, bottom: ${item.bottom.toFixed(2)}`;
314
+ }
315
+ }
316
+ function markerIsOnLayer(marker, layer) {
317
+ if (!layer) {
318
+ return true;
319
+ }
320
+ var top = marker.options.top || marker.options.position.y;
321
+ var bottom = marker.options.bottom || marker.options.position.y;
322
+ for (const extent of layer.options.extents) {
323
+ if (top >= extent.height[0] && bottom < extent.height[1]) {
324
+ let containedType = 'partial';
325
+ if (bottom >= extent.height[0] && top <= extent.height[1]) {
326
+ containedType = 'full';
327
+ }
328
+ if (extent.bounds) {
329
+ for (const boundsArray of extent.bounds) {
330
+ const bounds = getBounds(boundsArray);
331
+ if (bounds.contains(pos(marker.options.position))) {
332
+ return containedType;
333
+ }
334
+ }
335
+ } else {
336
+ return containedType;
337
+ }
338
+ }
339
+ }
340
+ return false;
341
+ }
342
+ function markerIsOnActiveLayer(marker) {
343
+ if (!marker.options.position) {
344
+ return true;
345
+ }
346
+ const map = marker._map;
347
+ // check if marker is completely contained by inactive layer
348
+ const overlays = map.layerControl._layers.map(l => l.layer).filter(l => Boolean(l.options.extents) && l.options.overlay);
349
+ for (const layer of overlays) {
350
+ for (const extent of layer.options.extents) {
351
+ if (markerIsOnLayer(marker, layer) === 'full' && !map.hasLayer(layer) && extent.bounds) {
352
+ return false;
353
+ }
354
+ }
355
+ }
356
+ // check if marker is on active overlay
357
+ const activeOverlay = Object.values(map._layers).find(l => l.options?.extents && l.options?.overlay);
358
+ if (activeOverlay && markerIsOnLayer(marker, activeOverlay)) {
359
+ return true;
360
+ }
361
+ // check if marker is on base layer
362
+ const baseLayer = Object.values(map._layers).find(l => l.options?.extents && !L.options?.overlay);
363
+ if (!activeOverlay && markerIsOnLayer(marker, baseLayer)) {
364
+ return true;
365
+ }
366
+ return false;
367
+ }
368
+ function checkMarkerForActiveLayers(event) {
369
+ const marker = event.target || event;
370
+ const outline = marker.options.outline;
371
+ const onLevel = markerIsOnActiveLayer(marker);
372
+ if (onLevel) {
373
+ marker._icon?.classList.remove('off-level');
374
+ if (outline) {
375
+ outline._path?.classList.remove('off-level');
376
+ }
377
+ } else {
378
+ marker._icon?.classList.add('off-level');
379
+ if (outline) {
380
+ outline._path?.classList.add('off-level');
381
+ }
382
+ }
383
+ }
384
+ function activateMarkerLayer(event) {
385
+ const marker = event.target || event;
386
+ if (markerIsOnActiveLayer(marker)) {
387
+ return;
388
+ }
389
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
390
+ for (const layer of activeLayers) {
391
+ layer.removeFrom(marker._map);
392
+ }
393
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
394
+ for (const layer of heightLayers) {
395
+ if (markerIsOnLayer(marker, layer)) {
396
+ layer.addTo(marker._map);
397
+ break;
398
+ }
399
+ }
400
+ }
401
+ const getALink = (path, contents) => {
402
+ const a = L.DomUtil.create('a');
403
+ a.setAttribute('href', path);
404
+ a.setAttribute('target', '_blank');
405
+ a.append(contents);
406
+ // a.addEventListener('click', (event) => {
407
+ // navigate(path);
408
+ // event.preventDefault();
409
+ // });
410
+ return a;
411
+ };
412
+ function getScaledBounds(bounds, scaleFactor) {
413
+ // Calculate the center point of the bounds
414
+ const centerX = (bounds[0][0] + bounds[1][0]) / 2;
415
+ const centerY = (bounds[0][1] + bounds[1][1]) / 2;
416
+ // Calculate the new width and height
417
+ const width = bounds[1][0] - bounds[0][0];
418
+ const height = bounds[1][1] - bounds[0][1];
419
+ const newWidth = width * scaleFactor;
420
+ const newHeight = height * scaleFactor;
421
+ // Update the coordinates of the two points defining the bounds
422
+ const newBounds = [
423
+ [centerY - newHeight / 2, centerX - newWidth / 2],
424
+ [centerY + newHeight / 2, centerX + newWidth / 2]
425
+ ];
426
+ // console.log("Initial Rectangle:", bounds);
427
+ // console.log("Scaled Rectangle:", newBounds);
428
+ // console.log("Center:", L.bounds(bounds).getCenter(true));
429
+ return newBounds;
430
+ }
431
+ // Erstelle die Karte
432
+ //const map = L.map('map').setView([0, 0], 2);
433
+ const map = L.map('map', {
434
+ maxBounds: getScaledBounds(svgBounds, 1.5),
435
+ //maxBounds: bounds,
436
+ center: [0, 0],
437
+ zoom: 2,
438
+ minZoom: minZoom,
439
+ maxZoom: maxZoom,
440
+ zoomSnap: 0.1,
441
+ scrollWheelZoom: true,
442
+ wheelPxPerZoomLevel: 120,
443
+ crs: getCRS(transform),
444
+ attributionControl: false,
445
+ id: "wwoodsMap",
446
+ });
447
+ const layerControl = L.control.groupedLayers(null, null, {
448
+ position: 'topleft',
449
+ collapsed: true,
450
+ groupCheckboxes: true,
451
+ groupsCollapsable: true,
452
+ exclusiveOptionalGroups: ['Levels'],
453
+ }).addTo(map);
454
+ layerControl.on('overlayToggle', (e) => {
455
+ const layerState = e.detail;
456
+ if (layerState.checked) {
457
+ mapViewRef.current.layer = layerState.key;
458
+ } else {
459
+ mapViewRef.current.layer = undefined;
460
+ }
461
+ });
462
+ layerControl.on('layerToggle', (e) => {
463
+ const layerState = e.detail;
464
+ if (!layerState.checked) {
465
+ mapSettingsRef.current.hiddenLayers.push(layerState.key);
466
+ } else {
467
+ mapViewRef.current.layer = layerState.key;
468
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== layerState.key);
469
+ }
470
+ updateSavedMapSettings();
471
+ });
472
+ layerControl.on('groupToggle', (e) => {
473
+ const groupState = e.detail;
474
+ for (const groupLayer of layerControl._layers) {
475
+ if (groupLayer.group?.key !== groupState.key) {
476
+ continue;
477
+ }
478
+ if (!groupState.checked) {
479
+ mapSettingsRef.current.hiddenLayers.push(groupLayer.key);
480
+ } else {
481
+ mapSettingsRef.current.hiddenLayers = mapSettingsRef.current.hiddenLayers.filter(key => key !== groupLayer.key);
482
+ }
483
+ }
484
+ if (!groupState.checked) {
485
+ mapSettingsRef.current.hiddenGroups.push(groupState.key);
486
+ } else {
487
+ mapSettingsRef.current.hiddenGroups = mapSettingsRef.current.hiddenGroups.filter(key => key !== groupState.key);
488
+ }
489
+ updateSavedMapSettings();
490
+ });
491
+ layerControl.on('groupCollapseToggle', (e) => {
492
+ const groupState = e.detail;
493
+ if (groupState.collapsed) {
494
+ mapSettingsRef.current.collapsedGroups.push(groupState.key);
495
+ } else {
496
+ mapSettingsRef.current.collapsedGroups = mapSettingsRef.current.collapsedGroups.filter(key => key !== groupState.key);
497
+ }
498
+ updateSavedMapSettings();
499
+ });
500
+ const getLayerOptions = (layerKey, groupKey, layerName) => {
501
+ return {
502
+ groupKey,
503
+ layerKey,
504
+ groupName: groupKey,
505
+ layerName: layerName || categories[layerKey] || layerKey,
506
+ //groupHidden: Boolean(mapSettingsRef.current.hiddenGroups?.includes(groupKey)),
507
+ //layerHidden: Boolean(mapSettingsRef.current.hiddenLayers?.includes(layerKey)),
508
+ image: images[layerKey] ? "/static/maps/interactive/${images[layerKey]}.png" : undefined,
509
+ //groupCollapsed: Boolean(mapSettingsRef.current.collapsedGroups?.includes(groupKey)),
510
+ };
511
+ };
512
+ const addLayer = (layer, layerKey, groupKey, layerName) => {
513
+ layer.key = layerKey;
514
+ const layerOptions = getLayerOptions(layerKey, groupKey, layerName);
515
+ if (!layerOptions.layerHidden) {
516
+ layer.addTo(map);
517
+ }
518
+ layerControl.addOverlay(layer, layerOptions.layerName, layerOptions);
519
+ };
520
+ map.layerControl = layerControl;
521
+ // Hinzufügen des Image-Overlays
522
+ const overlay = L.imageOverlay(imageUrl, getBounds(svgBounds));
523
+ overlay.addTo(map);
524
+ function checkMarkerBounds(position, markerBounds) {
525
+ if (position.x < markerBounds.TL.x) markerBounds.TL.x = position.x;
526
+ if (position.z > markerBounds.TL.z) markerBounds.TL.z = position.z;
527
+ if (position.x > markerBounds.BR.x) markerBounds.BR.x = position.x;
528
+ if (position.z < markerBounds.BR.z) markerBounds.BR.z = position.z;
529
+ }
530
+ function getBounds(bounds) {
531
+ if (!bounds) {
532
+ return undefined;
533
+ }
534
+ return L.latLngBounds([bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]);
535
+ //return [[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]];
536
+ }
537
+ function mouseHoverOutline(event) {
538
+ const outline = event.target.options.outline;
539
+ if (event.originalEvent.type === 'mouseover') {
540
+ outline._path.classList.remove('not-shown');
541
+ } else if (!outline._path.classList.contains('force-show')) {
542
+ outline._path.classList.add('not-shown');
543
+ }
544
+ }
545
+ function toggleForceOutline(event) {
546
+ const outline = event.target.options.outline;
547
+ outline._path.classList.toggle('force-show');
548
+ if (outline._path.classList.contains('force-show')) {
549
+ outline._path.classList.remove('not-shown');
550
+ }
551
+ activateMarkerLayer(event);
552
+ }
553
+ function activateMarkerLayer(event) {
554
+ const marker = event.target || event;
555
+ if (markerIsOnActiveLayer(marker)) {
556
+ return;
557
+ }
558
+ const activeLayers = Object.values(marker._map._layers).filter(l => l.options?.extents && l.options?.overlay);
559
+ for (const layer of activeLayers) {
560
+ layer.removeFrom(marker._map);
561
+ }
562
+ const heightLayers = marker._map.layerControl._layers.filter(l => l.layer.options.extents && l.layer.options.overlay).map(l => l.layer);
563
+ for (const layer of heightLayers) {
564
+ if (markerIsOnLayer(marker, layer)) {
565
+ layer.addTo(marker._map);
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ function outlineToPoly(outline) {
571
+ if (!outline) return [];
572
+ return outline.map(vector => [vector.z, vector.x]);
573
+ }
574
+ const layerOptions = {
575
+ maxZoom: maxZoom,
576
+ maxNativeZoom: maxZoom,
577
+ extents: [
578
+ {
579
+ height: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
580
+ bounds: [bounds],
581
+ }
582
+ ],
583
+ type: 'map-layer',
584
+ };
585
+ let tileLayer = false;
586
+ const baseLayers = [];
587
+ const tileSize = 256;
588
+ let svgLayer = false;
589
+ if (svgPath) {
590
+ const svgBounds2 = svgBounds ? getBounds(svgBounds) : bounds;
591
+ svgLayer = L.imageOverlay(svgPath, svgBounds2, layerOptions);
592
+ baseLayers.push(svgLayer);
593
+ }
594
+ const positionIsInBounds = (position) => {
595
+ return getBounds(bounds).contains(pos(position));
596
+ };
597
+ let markerBounds = {
598
+ 'TL': { x: Number.MAX_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER },
599
+ 'BR': { x: Number.MIN_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER }
600
+ }
601
+ if (mapData.labels?.length > 0) {
602
+ const labelsGroup = L.layerGroup();
603
+ const defaultHeight = ((layerOptions.extents[0].height[1] - layerOptions.extents[0].height[0]) / 2) + layerOptions.extents[0].height[0];
604
+ for (const label of mapData.labels) {
605
+ const fontSize = label.size ? label.size : 100;
606
+ const height = label.position.length < 3 ? defaultHeight : label.position[2];
607
+ const rotation = label.rotation ? label.rotation : 0;
608
+ L.marker(pos({ x: label.position[0], z: label.position[1] }), {
609
+ icon: L.divIcon({ html: `<div class="label" style="font-size: ${fontSize}%; transform: translate3d(-50%, -50%, 0) rotate(${rotation}deg)">${label.text}</div>`, className: 'map-area-label', layers: baseLayers, }),
610
+ interactive: false,
611
+ zIndexOffset: -100000,
612
+ position: {
613
+ x: label.position[0],
614
+ y: height,
615
+ z: label.position[1],
616
+ },
617
+ top: typeof label.top !== 'undefined' ? label.top : 1000,
618
+ bottom: typeof label.bottom !== 'undefined' ? label.bottom : -1000,
619
+ }).addTo(labelsGroup);
620
+ }
621
+ addLayer(labelsGroup, 'place-names', 'Landmarks');
622
+ //labelsGroup.addTo(map);
623
+ }
624
+ // Add spawns
625
+ if (mapData.spawns.length > 0) {
626
+ const spawnLayers = {
627
+ 'pmc': L.layerGroup(),
628
+ 'scav': L.layerGroup(),
629
+ 'sniper_scav': L.layerGroup(),
630
+ 'boss': L.layerGroup(),
631
+ 'cultist-priest': L.layerGroup(),
632
+ 'rogue': L.layerGroup(),
633
+ 'bloodhound': L.layerGroup(),
634
+ }
635
+ for (const spawn of mapData.spawns) {
636
+ if (!positionIsInBounds(spawn.position)) {
637
+ continue;
638
+ }
639
+ let spawnType = '';
640
+ let bosses = [];
641
+ if (spawn.categories.includes('boss')) {
642
+ bosses = mapData.bosses.filter(boss => boss.spawnLocations.some(sl => sl.spawnKey === spawn.zoneName));
643
+ if (bosses.length === 0) {
644
+ if (spawn.categories.includes('bot') && spawn.sides.includes('scav')) {
645
+ spawnType = 'scav';
646
+ }
647
+ else {
648
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
649
+ continue;
650
+ }
651
+ }
652
+ else if (bosses.length === 1 && (bosses[0].normalizedName === 'bloodhound' || bosses[0].normalizedName === 'cultist-priest' || bosses[0].normalizedName === 'rogue')) {
653
+ spawnType = bosses[0].normalizedName;
654
+ }
655
+ else {
656
+ spawnType = 'boss';
657
+ }
658
+ } else if (spawn.categories.includes('sniper')) {
659
+ spawnType = 'sniper_scav';
660
+ } else if (spawn.sides.includes('scav')) {
661
+ if (spawn.categories.includes('bot') || spawn.categories.includes('all')) {
662
+ spawnType = 'scav';
663
+ }
664
+ else {
665
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
666
+ continue;
667
+ }
668
+ }
669
+ else if (spawn.categories.includes('player')) {
670
+ if (spawn.sides.includes('pmc') || spawn.sides.includes('all')) {
671
+ spawnType = 'pmc'
672
+ }
673
+ else {
674
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
675
+ continue;
676
+ }
677
+ }
678
+ else {
679
+ console.error(`Unusual spawn: ${spawn.sides}, ${spawn.categories}`);
680
+ continue;
681
+ }
682
+ const spawnIcon = L.icon({
683
+ iconUrl: `/static/maps/interactive/spawn_${spawnType}.png`,
684
+ iconSize: [24, 24],
685
+ popupAnchor: [0, -12],
686
+ });
687
+ if (spawnType === 'pmc') {
688
+ spawnIcon.iconAnchor = [12, 24];
689
+ spawnIcon.popupAnchor = [0, -24];
690
+ }
691
+ const popupContent = L.DomUtil.create('div')
692
+ if (spawn.categories.includes('boss') && bosses.length > 0) {
693
+ bosses = bosses.reduce((unique, current) => {
694
+ if (!unique.some(b => b.normalizedName === current.normalizedName)) {
695
+ unique.push(current)
696
+ if (!categories[`spawn_${current.normalizedName}`]) {
697
+ categories[`spawn_${current.normalizedName}`] = current.name
698
+ }
699
+ }
700
+ return unique;
701
+ }, []);
702
+ const bossList = L.DomUtil.create('div', undefined, popupContent)
703
+ for (const boss of bosses) {
704
+ if (bossList.childNodes.length > 0) {
705
+ const comma = L.DomUtil.create('span', undefined, bossList)
706
+ comma.textContent = ', '
707
+ }
708
+ bossList.append(getALink(`https://escapefromtarkov.fandom.com/wiki/Special:Search?scope=internal&query=${boss.name}`, `${boss.name} (${Math.round(boss.spawnChance * 100)}%)`))
709
+ }
710
+ }
711
+ else {
712
+ const spawnDiv = L.DomUtil.create('div', undefined, popupContent)
713
+ spawnDiv.textContent = categories[`spawn_${spawnType}`]
714
+ }
715
+ addElevation(spawn, popupContent)
716
+ const marker = L.marker(pos(spawn.position), {
717
+ icon: spawnIcon,
718
+ position: spawn.position,
719
+ });
720
+ if (popupContent.childNodes.length > 0) {
721
+ marker.bindPopup(L.popup().setContent(popupContent))
722
+ }
723
+ marker.position = spawn.position
724
+ marker.on('add', checkMarkerForActiveLayers)
725
+ marker.on('click', activateMarkerLayer)
726
+ marker.addTo(spawnLayers[spawnType])
727
+ checkMarkerBounds(spawn.position, markerBounds)
728
+ }
729
+ for (const key in spawnLayers) {
730
+ if (Object.keys(spawnLayers[key]._layers).length > 0) {
731
+ addLayer(spawnLayers[key], `spawn_${key}`, 'Spawns')
732
+ }
733
+ }
734
+ }
735
+ //add extracts
736
+ if (mapData.extracts.length > 0) {
737
+ const extractLayers = {
738
+ pmc: L.layerGroup(),
739
+ scav: L.layerGroup(),
740
+ shared: L.layerGroup(),
741
+ }
742
+ const zIndexOffsets = {
743
+ pmc: 150,
744
+ shared: 125,
745
+ scav: 100,
746
+ };
747
+ for (const extract of mapData.extracts) {
748
+ const faction = extract.faction ?? 'shared';
749
+ if (!positionIsInBounds(extract.position)) {
750
+ //continue;
751
+ }
752
+ const colorMap = {
753
+ scav: '#ff7800',
754
+ pmc: '#00e599',
755
+ shared: '#00e4e5',
756
+ }
757
+ const rect = L.polygon(outlineToPoly(extract.outline), { color: colorMap[faction], weight: 1, className: 'not-shown' });
758
+ const extractIcon = L.divIcon({
759
+ className: 'extract-icon',
760
+ html: `<img src="/static/maps/interactive/extract_${faction}.png"/><span class="extract-name ${faction}">${extract.name}</span>`,
761
+ iconAnchor: [12, 12]
762
+ });
763
+ const extractMarker = L.marker(pos(extract.position), {
764
+ icon: extractIcon,
765
+ title: extract.name,
766
+ zIndexOffset: zIndexOffsets[faction],
767
+ position: extract.position,
768
+ top: extract.top,
769
+ bottom: extract.bottom,
770
+ outline: rect,
771
+ id: extract.id,
772
+ });
773
+ extractMarker.on('mouseover', mouseHoverOutline);
774
+ extractMarker.on('mouseout', mouseHoverOutline);
775
+ extractMarker.on('click', toggleForceOutline);
776
+ if (extract.switches?.length > 0) {
777
+ const popup = L.DomUtil.create('div');
778
+ const textElement = L.DomUtil.create('div');
779
+ textElement.textContent = `${tMaps('Activated by')}:`;
780
+ popup.appendChild(textElement);
781
+ for (const sw of extract.switches) {
782
+ const linkElement = getPoiLinkElement(sw.id, 'switch');
783
+ const nameElement = L.DomUtil.create('span');
784
+ nameElement.innerHTML = `<strong>${sw.name}</strong>`;
785
+ linkElement.append(nameElement);
786
+ popup.appendChild(linkElement);
787
+ }
788
+ addElevation(extract, popup);
789
+ extractMarker.bindPopup(L.popup().setContent(popup));
790
+ } else if (showElevation) {
791
+ const popup = L.DomUtil.create('div');
792
+ addElevation(extract, popup);
793
+ extractMarker.bindPopup(L.popup().setContent(popup));
794
+ }
795
+ extractMarker.on('add', checkMarkerForActiveLayers);
796
+ L.layerGroup([rect, extractMarker]).addTo(extractLayers[faction]);
797
+ checkMarkerBounds(extract.position, markerBounds);
798
+ }
799
+ if (mapData.transits.length > 0) {
800
+ extractLayers.transit = L.layerGroup();
801
+ for (const transit of mapData.transits) {
802
+ if (!positionIsInBounds(transit.position)) {
803
+ //continue;
804
+ }
805
+ const rect = L.polygon(outlineToPoly(transit.outline), { color: '#e53500', weight: 1, className: 'not-shown' });
806
+ const transitIcon = L.divIcon({
807
+ className: 'extract-icon',
808
+ html: `<img src="/static/maps/interactive/extract_transit.png"/><span class="extract-name transit">${transit.description}</span>`,
809
+ iconAnchor: [12, 12]
810
+ });
811
+ const transitMarker = L.marker(pos(transit.position), {
812
+ icon: transitIcon,
813
+ title: transit.description,
814
+ zIndexOffset: zIndexOffsets.pmc,
815
+ position: transit.position,
816
+ top: transit.top,
817
+ bottom: transit.bottom,
818
+ outline: rect,
819
+ id: transit.id,
820
+ });
821
+ transitMarker.on('mouseover', mouseHoverOutline);
822
+ transitMarker.on('mouseout', mouseHoverOutline);
823
+ transitMarker.on('click', toggleForceOutline);
824
+ if (showElevation) {
825
+ const popup = L.DomUtil.create('div');
826
+ addElevation(transit, popup);
827
+ transitMarker.bindPopup(L.popup().setContent(popup));
828
+ }
829
+ transitMarker.on('add', checkMarkerForActiveLayers);
830
+ L.layerGroup([rect, transitMarker]).addTo(extractLayers.transit);
831
+ checkMarkerBounds(transit.position, markerBounds);
832
+ }
833
+ }
834
+ for (const key in extractLayers) {
835
+ if (Object.keys(extractLayers[key]._layers).length > 0) {
836
+ addLayer(extractLayers[key], `extract_${key}`, 'Extracts');
837
+ }
838
+ }
839
+ }
840
+ // Add static items
841
+ if (showStaticMarkers) {
842
+ for (const category in mapData) {
843
+ const markerLayer = L.layerGroup();
844
+ const items = mapData[category];
845
+ for (const item of items) {
846
+ const itemIcon = L.icon({
847
+ iconUrl: `/static/maps/interactive/${category}.png`,
848
+ iconSize: [24, 24],
849
+ popupAnchor: [0, -12],
850
+ //className: layerIncludesMarker(heightLayer, item) ? '' : 'off-level',
851
+ });
852
+ L.marker(pos(item.position), { icon: itemIcon, position: item.position })
853
+ .bindPopup(L.popup().setContent(`${item.name}<br>Elevation: ${item.position.y}`))
854
+ .addTo(markerLayer);
855
+ checkMarkerBounds(item.position, markerBounds);
856
+ }
857
+ if (items.length > 0) {
858
+ var section;
859
+ if (category.startsWith('extract')) {
860
+ section = 'Extracts';
861
+ }
862
+ else if (category.startsWith('spawn')) {
863
+ section = 'Spawns';
864
+ }
865
+ else {
866
+ section = 'Lootable Items';
867
+ }
868
+ markerLayer.addTo(map);
869
+ addLayer(markerLayer, category, section);
870
+ // layerControl.addOverlay(markerLayer, `<img src='${process.env.PUBLIC_URL}/maps/interactive/${category}.png' class='control-item-image' /> ${categories[category] || category}`, section);
871
+ }
872
+ }
873
+ }
874
+ // Erstelle ein neues Control-Element
875
+ const customControl = L.Control.extend({
876
+ onAdd: function (map) {
877
+ this.container = L.DomUtil.create('div', 'custom-control leaflet-control-layers leaflet-control-layers-expanded')
878
+ this.container.innerHTML = '<h2>Add Marker to Pos: </h2>'
879
+ return this.container
880
+ },
881
+ updateText: function (html) {
882
+ if (this.container) {
883
+ this.container.innerHTML = html
884
+ }
885
+ },
886
+ clearText: function () {
887
+ if (this.container) {
888
+ this.container.innerHTML = ''
889
+ }
890
+ },
891
+ onRemove: function (map) {
892
+ // Nichts zu tun hier
893
+ }
894
+ });
895
+ const myControl = new customControl({ position: 'topright' })
896
+ map.addControl(myControl)
897
+ function startCountdown(elementId) {
898
+ const element = document.getElementById(elementId);
899
+ if (!element) {
900
+ console.error(`Element with id ${elementId} not found.`);
901
+ return;
902
+ }
903
+ //let secondsStart = Math.floor(new Date().getTime() / 1000.0) - parseInt(element.getAttribute('data-rade-end')) - 60
904
+ let secondsStart = parseInt(element.getAttribute('data-seconds')) - 60
905
+ let seconds = secondsStart
906
+ if (isNaN(secondsStart)) {
907
+ console.error('Invalid data-seconds attribute.');
908
+ return;
909
+ }
910
+ const intervalId = setInterval(() => {
911
+ if (!document.hidden) {
912
+ //seconds = (secondsStart - parseInt(Math.floor(new Date().getTime() / 1000.0)))
913
+ //localStorage.setItem('rade_data', JSON.stringify({ remaining_time: seconds }));
914
+ seconds--
915
+ if (seconds <= 0) {
916
+ clearInterval(intervalId);
917
+ element.textContent = '0:00:00';
918
+ myControl.updateText("")
919
+ document.querySelector("head > title").textContent = `${mapData.name} | Map with friends`
920
+ return;
921
+ }
922
+ //seconds--;
923
+ const hours = Math.floor(seconds / 3600);
924
+ const minutes = Math.floor((seconds % 3600) / 60);
925
+ const remainingSeconds = seconds % 60;
926
+ const timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
927
+ element.textContent = timeString;
928
+ document.querySelector("head > title").textContent = `${mapData.name} | ${timeString}`
929
+ }
930
+ }, 1000);
931
+ }
932
+ function addNewRadeData(new_html) {
933
+ myControl.updateText(new_html)
934
+ startCountdown("rade_time_remain")
935
+ };
936
+ //MARK: ADDMARKER
937
+ // Funktion zum Hinzufügen eines Markers
938
+ function addMarker(x, y, z, timestamp, preview, actualmap, playername, markercolor) {
939
+ // Validate position
940
+ const position = {
941
+ x: parseFloat(x),
942
+ y: parseFloat(y),
943
+ z: parseFloat(z)
944
+ };
945
+ if (!positionIsInBounds(position)) {
946
+ console.error("Position außerhalb der Karte:", position);
947
+ return;
948
+ }
949
+ // SVG-Icon als String mit dynamischer Farbe
950
+ let markerColor;
951
+ if (markercolor) {
952
+ markerColor = '#' + markercolor;
953
+ } else {
954
+ markerColor = 'currentColor';
955
+ }
956
+ const svgString = `
957
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76" class="marker-svg">
958
+ <path d="M60.8 50.5 38 72.9 15.2 50.5 8.1 3.9 38 13.3l29.8-9.4-7 46.6z" style="stroke-linecap:round;stroke-linejoin:round;fill:#fff;stroke:#fff;stroke-width:4.4px"/><path d="M58.8 49.4 38 69.9 17.1 49.4 10.7 6.9 38 15.5l27.2-8.6-6.4 42.5z" style="fill:${markerColor};stroke:#000;stroke-width:4px;stroke-linecap:round;stroke-linejoin:round"/>
959
+ </svg>
960
+ `;
961
+ const svgString2 = `
962
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512' class="marker-svg">
963
+ <path fill='${markerColor}' d='M172.3 501.7C27 291 0 269.4 0 192 0 86 86 0 192 0s192 86 192 192c0 77.4-27 99-172.3 309.7-9.5 13.8-29.9 13.8-39.5 0zM192 272c44.2 0 80-35.8 80-80s-35.8-80-80-80-80 35.8-80 80 35.8 80 80 80z'/>
964
+ </svg>
965
+ `;
966
+ // DivIcon mit dynamischem SVG erstellen
967
+ const markerIcon = L.divIcon({
968
+ html: svgString,
969
+ className: 'custom-marker',
970
+ iconSize: [32, 32],
971
+ iconAnchor: [16, 32],
972
+ popupAnchor: [0, -32]
973
+ });
974
+ const playerMarkerName = playername.toLowerCase().replaceAll(" ", "-")
975
+ // Remove old marker for this player, if it exists
976
+ if (playerMarkers[playerMarkerName]) {
977
+ map.removeLayer(playerMarkers[playerMarkerName]);
978
+ }
979
+ // Create new marker
980
+ playerMarkers[playerMarkerName] = L.marker(pos(position), {
981
+ icon: markerIcon,
982
+ position: position,
983
+ title: `Koordinaten: ${x}, ${y}, ${z}`,
984
+ riseOnHover: true,
985
+ zIndexOffset: 400
986
+ });
987
+ // Popup mit Informationen erstellen
988
+ const popupContent = `
989
+ <div class="marker-popup">
990
+ ${playername ? `<strong>Player: ${playername}</strong>` : ''}
991
+ <strong>Koordinaten:</strong>
992
+ <span>X: ${x} Y: ${y} Z: ${z}</span>
993
+ ${preview ? `<img class="preview-image" src="${preview}"><br>` : ''}
994
+ <small>${timestamp}</small>
995
+ </div>
996
+ `;
997
+ playerMarkers[playerMarkerName].bindPopup(popupContent, {
998
+ maxWidth: 250,
999
+ minWidth: 150,
1000
+ autoClose: true,
1001
+ closeOnClick: true
1002
+ });
1003
+ // Marker zur Karte hinzufügen
1004
+ playerMarkers[playerMarkerName].addTo(map);
1005
+ // Karte auf Marker zentrieren
1006
+ map.setView(pos(position), map.getZoom(), {
1007
+ animate: true,
1008
+ duration: 0.5
1009
+ });
1010
+ console.log("Neuer Marker gesetzt für " + playername + ": " + position);
1011
+ }
1012
+ document.onload = loadLocalData()
1013
+ // MODAL Trigger
1014
+ document.addEventListener('DOMContentLoaded', () => {
1015
+ // Functions to open and close a modal
1016
+ function openModal($el) {
1017
+ $el.classList.add('is-active');
1018
+ }
1019
+ function closeModal($el) {
1020
+ $el.classList.remove('is-active');
1021
+ }
1022
+ function closeAllModals() {
1023
+ (document.querySelectorAll('.modal') || []).forEach(($modal) => {
1024
+ closeModal($modal);
1025
+ });
1026
+ }
1027
+ // Add a click event on buttons to open a specific modal
1028
+ (document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
1029
+ const modal = $trigger.dataset.target;
1030
+ const $target = document.getElementById(modal);
1031
+ $trigger.addEventListener('click', () => {
1032
+ openModal($target);
1033
+ });
1034
+ });
1035
+ // Add a click event on various child elements to close the parent modal
1036
+ (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
1037
+ const $target = $close.closest('.modal');
1038
+ $close.addEventListener('click', () => {
1039
+ closeModal($target);
1040
+ });
1041
+ });
1042
+ // Add a keyboard event to close all modals
1043
+ document.addEventListener('keydown', (event) => {
1044
+ if (event.key === "Escape") {
1045
+ closeAllModals();
1046
+ }
1047
+ });
1048
+ });
1049
+ </script>
1050
+ </body>