Sebastiankay commited on
Commit
7ad1d24
·
verified ·
1 Parent(s): e762982

Create map.html

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