WME Permalink to several Maps DACH

WME PTSM für Deutschland Österreich Schweiz

  1. // ==UserScript==
  2. // @name WME Permalink to several Maps DACH
  3. // @description WME PTSM für Deutschland Österreich Schweiz
  4. // @namespace https://greasyfork.org/de/users/863740-horst-wittlich
  5. // @version 2025.06.12
  6. // @match https://*.waze.com/editor*
  7. // @match https://*.waze.com/*/editor*
  8. // @match https://beta.waze.com/editor*
  9. // @match https://beta.waze.com/*/editor*
  10. // @icon https://i.ibb.co/ckSvk59/waze-icon.png
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. /* global OpenLayers, W, proj4 */
  16.  
  17. var ptsmVersion = '2025.06.12';
  18. var ptsmInitialized = false;
  19. var ptsmUpdateKey = 'wme-ptsm-update-shown-' + ptsmVersion;
  20.  
  21. // Einstellungen laden/speichern - MUSS VOR anderen Funktionen definiert werden
  22. function loadSettings() {
  23. try {
  24. const saved = localStorage.getItem('wme-ptsm-settings');
  25. return saved ? JSON.parse(saved) : { freePosition: false };
  26. } catch (e) {
  27. return { freePosition: false };
  28. }
  29. }
  30.  
  31. function saveSettings(settings) {
  32. try {
  33. localStorage.setItem('wme-ptsm-settings', JSON.stringify(settings));
  34. } catch (e) {
  35. console.log('PTSM: localStorage not available');
  36. }
  37. }
  38.  
  39. function saveDropdownStates() {
  40. const states = {};
  41. document.querySelectorAll('.ptsm-category').forEach(category => {
  42. const className = category.className.match(/ptsm-category-(\w+)/);
  43. if (className) {
  44. states[className[1]] = category.classList.contains('open');
  45. }
  46. });
  47. try {
  48. localStorage.setItem('wme-ptsm-dropdown-states', JSON.stringify(states));
  49. } catch (e) {
  50. console.log('PTSM: localStorage not available');
  51. }
  52. }
  53.  
  54. function loadDropdownStates() {
  55. try {
  56. const saved = localStorage.getItem('wme-ptsm-dropdown-states');
  57. return saved ? JSON.parse(saved) : {};
  58. } catch (e) {
  59. return {};
  60. }
  61. }
  62.  
  63. function saveCompactMode(isCompact) {
  64. try {
  65. localStorage.setItem('wme-ptsm-compact-mode', isCompact ? 'true' : 'false');
  66. } catch (e) {
  67. console.log('PTSM: localStorage not available');
  68. }
  69. }
  70.  
  71. function loadCompactMode() {
  72. try {
  73. const saved = localStorage.getItem('wme-ptsm-compact-mode');
  74. return saved === 'true';
  75. } catch (e) {
  76. return false;
  77. }
  78. }
  79.  
  80. // HYBRIDE Drag & Drop Funktionalität - Desktop-weit ODER Container-intern
  81. function enableDragAndDrop() {
  82. const container = document.querySelector('.ptsm-container');
  83. const divs = ['.ptsm-category-allgem', '.ptsm-category-baustell', '.ptsm-category-blitzer', '.ptsm-category-bilder', '.ptsm-category-geoportal', '.ptsm-category-misc', '.ptsm-category-settings'];
  84.  
  85. // Gespeicherte Desktop-Positionen laden
  86. function loadPositions() {
  87. try {
  88. const saved = localStorage.getItem('wme-ptsm-drag-positions');
  89. return saved ? JSON.parse(saved) : {};
  90. } catch (e) {
  91. return {};
  92. }
  93. }
  94.  
  95. // Desktop-Positionen speichern
  96. function savePositions() {
  97. const positions = {};
  98. divs.forEach(selector => {
  99. const element = document.querySelector(selector);
  100. if (element && element.style.position === 'fixed') {
  101. positions[selector] = {
  102. left: element.style.left,
  103. top: element.style.top,
  104. zIndex: element.style.zIndex
  105. };
  106. }
  107. });
  108. try {
  109. localStorage.setItem('wme-ptsm-drag-positions', JSON.stringify(positions));
  110. } catch (e) {
  111. console.log('PTSM: localStorage not available');
  112. }
  113. }
  114.  
  115. // Container-Reihenfolge speichern
  116. function saveCategoryOrder() {
  117. if (!container) return;
  118.  
  119. const order = Array.from(container.querySelectorAll('.ptsm-category')).map(cat => {
  120. const className = cat.className.match(/ptsm-category-(\w+)/);
  121. return className ? className[1] : null;
  122. }).filter(Boolean);
  123.  
  124. try {
  125. localStorage.setItem('wme-ptsm-category-order', JSON.stringify(order));
  126. } catch (e) {
  127. console.log('PTSM: localStorage not available');
  128. }
  129. }
  130.  
  131. // Gespeicherte Desktop-Positionen wiederherstellen
  132. function restorePositions() {
  133. const positions = loadPositions();
  134. Object.keys(positions).forEach(selector => {
  135. const element = document.querySelector(selector);
  136. if (element) {
  137. const pos = positions[selector];
  138. element.style.position = 'fixed';
  139. element.style.left = pos.left;
  140. element.style.top = pos.top;
  141. element.style.zIndex = pos.zIndex || '10000';
  142. element.style.width = '280px';
  143. element.style.maxWidth = '280px';
  144.  
  145. // Element an document.body anhängen für Tab-Unabhängigkeit
  146. if (element.parentElement !== document.body) {
  147. element.setAttribute('data-original-parent', 'container');
  148. document.body.appendChild(element);
  149. }
  150. }
  151. });
  152. }
  153.  
  154. divs.forEach(selector => {
  155. const element = document.querySelector(selector);
  156. if (!element) return;
  157.  
  158. const header = element.querySelector('.ptsm-category-header');
  159. if (!header || header.hasAttribute('data-drag-enabled')) return;
  160.  
  161. header.setAttribute('data-drag-enabled', 'true');
  162.  
  163. let isDragging = false;
  164. let dragStarted = false;
  165. let startX, startY, startTime;
  166. let originalPosition = { left: '', top: '', position: '', width: '', zIndex: '' };
  167. let placeholder = null;
  168. let clickBlocked = false; // Neue Variable für Click-Blockierung
  169.  
  170. // Ursprüngliche Position merken
  171. function saveOriginalPosition() {
  172. originalPosition = {
  173. left: element.style.left,
  174. top: element.style.top,
  175. position: element.style.position,
  176. width: element.style.width,
  177. zIndex: element.style.zIndex
  178. };
  179. }
  180.  
  181. header.addEventListener('mousedown', function(e) {
  182. // Nur Linksklick
  183. if (e.button !== 0) return;
  184.  
  185. isDragging = true;
  186. dragStarted = false;
  187. clickBlocked = false;
  188. startX = e.clientX;
  189. startY = e.clientY;
  190. startTime = Date.now();
  191.  
  192. saveOriginalPosition();
  193.  
  194. // WICHTIG: preventDefault() IMMER bei mousedown um Click-Konflikte zu vermeiden
  195. e.preventDefault();
  196. e.stopPropagation();
  197. });
  198.  
  199. document.addEventListener('mousemove', function(e) {
  200. if (!isDragging) return;
  201.  
  202. const deltaX = Math.abs(e.clientX - startX);
  203. const deltaY = Math.abs(e.clientY - startY);
  204. const deltaTime = Date.now() - startTime;
  205.  
  206. // Drag erst nach Mindestbewegung oder Zeit starten
  207. if (!dragStarted && (deltaX > 5 || deltaY > 5 || deltaTime > 200)) {
  208. dragStarted = true;
  209. clickBlocked = true; // Click blockieren sobald Drag gestartet wird
  210.  
  211. const settings = loadSettings();
  212.  
  213. if (settings.freePosition) {
  214. // DESKTOP-WEITES DRAG
  215. const rect = element.getBoundingClientRect();
  216. element.style.position = 'fixed';
  217. element.style.left = rect.left + 'px';
  218. element.style.top = rect.top + 'px';
  219. element.style.zIndex = '10000';
  220. element.style.width = '280px';
  221. element.style.maxWidth = '280px';
  222. element.style.opacity = '0.8';
  223. element.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)';
  224. element.style.transform = 'rotate(1deg)';
  225.  
  226. // Element an document.body anhängen für Tab-Unabhängigkeit
  227. if (element.parentElement !== document.body) {
  228. element.setAttribute('data-original-parent', 'container');
  229. document.body.appendChild(element);
  230. }
  231. } else {
  232. // CONTAINER-INTERNES DRAG
  233. if (!container) return;
  234.  
  235. // Element aus normalem Flow nehmen
  236. const rect = element.getBoundingClientRect();
  237.  
  238. // Placeholder erstellen
  239. placeholder = document.createElement('div');
  240. placeholder.className = 'ptsm-drag-placeholder';
  241. placeholder.style.cssText = `
  242. height: ${element.offsetHeight}px;
  243. background: #f0f0f0;
  244. border: 2px dashed #ccc;
  245. border-radius: 6px;
  246. margin-bottom: 8px;
  247. opacity: 0.5;
  248. transition: all 0.2s ease;
  249. `;
  250.  
  251. // Container-Drag-Styling
  252. element.style.position = 'fixed';
  253. element.style.left = rect.left + 'px';
  254. element.style.top = rect.top + 'px';
  255. element.style.width = '280px';
  256. element.style.maxWidth = '280px';
  257. element.style.opacity = '0.8';
  258. element.style.transform = 'rotate(1deg)';
  259. element.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)';
  260. element.style.zIndex = '10000';
  261. element.style.pointerEvents = 'none';
  262.  
  263. // Placeholder an der ursprünglichen Position einfügen
  264. element.parentNode.insertBefore(placeholder, element);
  265. }
  266.  
  267. // Header für Drag-Zeit blockieren
  268. header.style.pointerEvents = 'none';
  269. }
  270.  
  271. if (dragStarted) {
  272. const settings = loadSettings();
  273.  
  274. if (settings.freePosition) {
  275. // DESKTOP-WEITE BEWEGUNG
  276. const newLeft = Math.max(0, Math.min(e.clientX - startX + parseInt(element.style.left), window.innerWidth - 300));
  277. const newTop = Math.max(0, Math.min(e.clientY - startY + parseInt(element.style.top), window.innerHeight - 100));
  278.  
  279. element.style.left = newLeft + 'px';
  280. element.style.top = newTop + 'px';
  281.  
  282. // Update startX/Y für smooth dragging
  283. startX = e.clientX;
  284. startY = e.clientY;
  285. } else {
  286. // CONTAINER-INTERNE REORDERING
  287. if (!container || !placeholder) return;
  288.  
  289. // Vertikale Mausbewegung für bessere Sortierung
  290. const containerRect = container.getBoundingClientRect();
  291. const mouseY = e.clientY;
  292.  
  293. // Alle Kategorien außer dem gezogenen Element
  294. const allCategories = Array.from(container.querySelectorAll('.ptsm-category')).filter(cat => cat !== element);
  295.  
  296. let insertBefore = null;
  297. let minDistance = Infinity;
  298.  
  299. // Finde das Element mit der geringsten Y-Distanz oberhalb der Maus
  300. for (let otherCategory of allCategories) {
  301. if (otherCategory === placeholder) continue;
  302.  
  303. const otherRect = otherCategory.getBoundingClientRect();
  304. const otherCenterY = otherRect.top + otherRect.height / 2;
  305.  
  306. if (mouseY < otherCenterY) {
  307. const distance = otherCenterY - mouseY;
  308. if (distance < minDistance) {
  309. minDistance = distance;
  310. insertBefore = otherCategory;
  311. }
  312. }
  313. }
  314.  
  315. // Placeholder neu positionieren
  316. if (insertBefore) {
  317. container.insertBefore(placeholder, insertBefore);
  318. } else {
  319. // Ans Ende setzen wenn keine Kategorie oberhalb gefunden
  320. container.appendChild(placeholder);
  321. }
  322.  
  323. // Element visuell an Mausposition folgen lassen (nur Y-Achse im Container)
  324. const constrainedY = Math.max(containerRect.top, Math.min(mouseY - 20, containerRect.bottom - 50));
  325. element.style.top = constrainedY + 'px';
  326. }
  327. }
  328. });
  329.  
  330. document.addEventListener('mouseup', function(e) {
  331. if (!isDragging) return;
  332.  
  333. isDragging = false;
  334.  
  335. if (dragStarted) {
  336. dragStarted = false;
  337.  
  338. const settings = loadSettings();
  339.  
  340. if (settings.freePosition) {
  341. // DESKTOP-DRAG beenden
  342. element.style.opacity = '1';
  343. element.style.boxShadow = '0 1px 4px rgba(0,0,0,0.08)';
  344. element.style.transform = 'none';
  345. element.style.zIndex = '10000'; // Höher für Desktop-Modus
  346.  
  347. // Element dauerhaft an document.body lassen für Tab-Unabhängigkeit
  348. if (element.parentElement !== document.body) {
  349. element.setAttribute('data-original-parent', 'container');
  350. document.body.appendChild(element);
  351. }
  352.  
  353. // Desktop-Position speichern
  354. savePositions();
  355. } else {
  356. // CONTAINER-DRAG beenden
  357. if (placeholder && placeholder.parentNode && container) {
  358. // Element an Placeholder-Position einfügen
  359. placeholder.parentNode.insertBefore(element, placeholder);
  360. placeholder.remove();
  361.  
  362. // Container-Styling KOMPLETT zurücksetzen
  363. element.style.position = '';
  364. element.style.left = '';
  365. element.style.top = '';
  366. element.style.width = '';
  367. element.style.maxWidth = '';
  368. element.style.opacity = '';
  369. element.style.transform = '';
  370. element.style.boxShadow = '';
  371. element.style.zIndex = '';
  372. element.style.pointerEvents = '';
  373.  
  374. // Container-Reihenfolge speichern
  375. saveCategoryOrder();
  376. } else {
  377. // Fallback: Element zurück zur ursprünglichen Position
  378. element.style.position = originalPosition.position;
  379. element.style.left = originalPosition.left;
  380. element.style.top = originalPosition.top;
  381. element.style.width = originalPosition.width;
  382. element.style.zIndex = originalPosition.zIndex;
  383. element.style.opacity = '';
  384. element.style.transform = '';
  385. element.style.boxShadow = '';
  386. element.style.pointerEvents = '';
  387.  
  388. if (placeholder && placeholder.parentNode) {
  389. placeholder.remove();
  390. }
  391. }
  392. }
  393.  
  394. placeholder = null;
  395.  
  396. // Header wieder aktivieren (mit Verzögerung um Click zu blockieren)
  397. setTimeout(() => {
  398. header.style.pointerEvents = '';
  399. // Click-Blockierung nach kurzer Zeit aufheben
  400. setTimeout(() => {
  401. clickBlocked = false;
  402. }, 100);
  403. }, 50);
  404.  
  405. // Event verhindern
  406. e.preventDefault();
  407. e.stopPropagation();
  408. } else {
  409. // Kein Drag - ursprüngliche Position wiederherstellen + Cleanup
  410. element.style.left = originalPosition.left;
  411. element.style.top = originalPosition.top;
  412. element.style.position = originalPosition.position;
  413. element.style.width = originalPosition.width;
  414. element.style.zIndex = originalPosition.zIndex;
  415. element.style.opacity = '';
  416. element.style.transform = '';
  417. element.style.boxShadow = '';
  418. element.style.pointerEvents = '';
  419.  
  420. // Placeholder entfernen falls vorhanden
  421. if (placeholder && placeholder.parentNode) {
  422. placeholder.remove();
  423. placeholder = null;
  424. }
  425.  
  426. // Header wieder aktivieren
  427. header.style.pointerEvents = '';
  428.  
  429. // Kurze Verzögerung bevor Click wieder erlaubt wird
  430. setTimeout(() => {
  431. clickBlocked = false;
  432. }, 10);
  433. }
  434. });
  435.  
  436. // NEUER Click Event Handler mit Blockierung-Check
  437. header.addEventListener('click', function(e) {
  438. // Click blockieren wenn gerade gedraggt wurde oder wird
  439. if (clickBlocked || isDragging || dragStarted) {
  440. e.preventDefault();
  441. e.stopPropagation();
  442. e.stopImmediatePropagation();
  443. return false;
  444. }
  445.  
  446. // Normaler Toggle nur wenn kein Drag
  447. const category = element;
  448. category.classList.toggle('open');
  449. setTimeout(saveDropdownStates, 100);
  450. }, true); // useCapture = true für höhere Priorität
  451.  
  452. // Doppelklick zum Zurücksetzen (nur bei Desktop-Modus)
  453. header.addEventListener('dblclick', function(e) {
  454. // Click blockieren
  455. e.preventDefault();
  456. e.stopPropagation();
  457.  
  458. const settings = loadSettings();
  459.  
  460. if (settings.freePosition) {
  461. // Desktop-Position zurücksetzen - KORREKTUR
  462. element.style.position = '';
  463. element.style.left = '';
  464. element.style.top = '';
  465. element.style.zIndex = '';
  466. element.style.width = '';
  467. element.style.maxWidth = '';
  468. element.style.transform = '';
  469. element.style.opacity = '';
  470. element.style.boxShadow = '';
  471.  
  472. // WICHTIG: Element zurück in den ursprünglichen Container bringen
  473. const container = document.querySelector('.ptsm-container');
  474. if (container && element.parentElement !== container) {
  475. // Element wieder in Container einfügen
  476. container.appendChild(element);
  477.  
  478. // Falls es eine gespeicherte Reihenfolge gibt, diese wiederherstellen
  479. setTimeout(() => {
  480. applyCategoryOrder();
  481. }, 50);
  482. }
  483.  
  484. // Position aus localStorage entfernen
  485. const positions = loadPositions();
  486. delete positions[selector];
  487. try {
  488. localStorage.setItem('wme-ptsm-drag-positions', JSON.stringify(positions));
  489. } catch (e) {
  490. console.log('PTSM: localStorage not available');
  491. }
  492. }
  493. });
  494.  
  495. // Cursor-Stil setzen
  496. function updateCursor() {
  497. const settings = loadSettings();
  498. if (settings.freePosition) {
  499. header.style.cursor = 'move';
  500. header.title = 'Ziehen zum Verschieben (Doppelklick zum Zurücksetzen)';
  501. } else {
  502. header.style.cursor = 'move';
  503. header.title = 'Ziehen zum Umsortieren';
  504. }
  505. }
  506.  
  507. updateCursor();
  508.  
  509. // Event Listener für Settings-Änderungen
  510. document.addEventListener('ptsm-settings-changed', updateCursor);
  511. });
  512.  
  513. // Gespeicherte Desktop-Positionen beim Start wiederherstellen (nur wenn freie Position aktiv)
  514. const settings = loadSettings();
  515. if (settings.freePosition) {
  516. setTimeout(restorePositions, 100);
  517. }
  518. }
  519.  
  520. // Kategorie-Reihenfolge speichern und laden
  521. function saveCategoryOrder() {
  522. const container = document.querySelector('.ptsm-container');
  523. if (!container) return;
  524.  
  525. const order = Array.from(container.querySelectorAll('.ptsm-category')).map(cat => {
  526. const className = cat.className.match(/ptsm-category-(\w+)/);
  527. return className ? className[1] : null;
  528. }).filter(Boolean);
  529.  
  530. try {
  531. localStorage.setItem('wme-ptsm-category-order', JSON.stringify(order));
  532. } catch (e) {
  533. console.log('PTSM: localStorage not available');
  534. }
  535. }
  536.  
  537. function loadCategoryOrder() {
  538. try {
  539. const saved = localStorage.getItem('wme-ptsm-category-order');
  540. return saved ? JSON.parse(saved) : null;
  541. } catch (e) {
  542. return null;
  543. }
  544. }
  545.  
  546. function applyCategoryOrder() {
  547. const savedOrder = loadCategoryOrder();
  548. if (!savedOrder) return;
  549.  
  550. const container = document.querySelector('.ptsm-container');
  551. if (!container) return;
  552.  
  553. // Kategorien nach gespeicherter Reihenfolge sortieren
  554. savedOrder.forEach(categoryKey => {
  555. const category = container.querySelector('.ptsm-category-' + categoryKey);
  556. if (category) {
  557. container.appendChild(category);
  558. }
  559. });
  560. }
  561.  
  562. function showUpdatePopup() {
  563. try {
  564. // Prüfen ob Update-Info bereits angezeigt wurde
  565. if (localStorage.getItem(ptsmUpdateKey) === 'true') {
  566. return;
  567. }
  568. } catch (e) {
  569. // Falls localStorage nicht verfügbar, trotzdem anzeigen
  570. }
  571.  
  572. // Popup erstellen
  573. var overlay = document.createElement('div');
  574. overlay.style.cssText = `
  575. position: fixed;
  576. top: 0;
  577. left: 0;
  578. width: 100%;
  579. height: 100%;
  580. background: rgba(0, 0, 0, 0.5);
  581. z-index: 999999;
  582. display: flex;
  583. align-items: center;
  584. justify-content: center;
  585. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  586. `;
  587.  
  588. var popup = document.createElement('div');
  589. popup.style.cssText = `
  590. background: white;
  591. border-radius: 12px;
  592. padding: 24px;
  593. max-width: 400px;
  594. margin: 20px;
  595. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  596. text-align: center;
  597. position: relative;
  598. `;
  599.  
  600. popup.innerHTML = `
  601. <div style="font-size: 24px; margin-bottom: 16px;">🎉 PTSM DACH Update! 🗺️</div>
  602. <div style="font-size: 18px; font-weight: bold; color: #007bff; margin-bottom: 16px;">
  603. Version ${ptsmVersion}
  604. </div>
  605. <div style="text-align: left; margin-bottom: 20px; line-height: 1.6;">
  606. <div style="font-size: 16px; font-weight: bold; margin-bottom: 12px; color: #333;">
  607. Was ist neu:
  608. </div>
  609. <div style="margin-bottom: 8px;">
  610. 🔧 <strong>Neu Drag & Drop Funktionalität</strong><br>Sortiere wie es dir gefällt: Doppelklick auf die Überschrift = zurück zur ursprünglichen Position.<br>Neuer Modus: <b>Major Tom</b> meldet <u>Völlig losgelöst.</u> Tabs haben bei bedarf nun einen Fliegenden Modus. Alle Einstellungen werden lokal gespeichert und können bei bedarf wieder Rückgängig gemacht werden.
  611. </div>
  612. </div>
  613. <button id="ptsm-close-popup" style="
  614. background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
  615. color: white;
  616. border: none;
  617. border-radius: 8px;
  618. padding: 12px 24px;
  619. font-size: 14px;
  620. font-weight: 600;
  621. cursor: pointer;
  622. transition: all 0.2s ease;
  623. ">
  624. 🚀 Los geht's!
  625. </button>
  626. `;
  627.  
  628. overlay.appendChild(popup);
  629. document.body.appendChild(overlay);
  630.  
  631. // Close button event
  632. var closeBtn = popup.querySelector('#ptsm-close-popup');
  633. closeBtn.addEventListener('click', function() {
  634. overlay.remove();
  635. try {
  636. localStorage.setItem(ptsmUpdateKey, 'true');
  637. } catch (e) {
  638. // localStorage not available
  639. }
  640. });
  641.  
  642. closeBtn.addEventListener('mouseover', function() {
  643. this.style.transform = 'translateY(-2px)';
  644. this.style.boxShadow = '0 4px 12px rgba(0, 123, 255, 0.3)';
  645. });
  646.  
  647. closeBtn.addEventListener('mouseout', function() {
  648. this.style.transform = 'translateY(0)';
  649. this.style.boxShadow = 'none';
  650. });
  651.  
  652. // Schließen bei Klick auf Overlay
  653. overlay.addEventListener('click', function(e) {
  654. if (e.target === overlay) {
  655. overlay.remove();
  656. try {
  657. localStorage.setItem(ptsmUpdateKey, 'true');
  658. } catch (e) {
  659. // localStorage not available
  660. }
  661. }
  662. });
  663. }
  664.  
  665. function getCenterZoom() {
  666. var map = W.map.getOLMap();
  667. var zoom = map.getZoom();
  668. var center = map.getCenter().transform(new OpenLayers.Projection('EPSG:900913'), new OpenLayers.Projection('EPSG:4326'));
  669. center.zoom = zoom;
  670. return center;
  671. }
  672.  
  673. function createCategory(title, className, defaultOpen = false) {
  674. const savedStates = loadDropdownStates();
  675. const categoryKey = className.replace('ptsm-category-', '');
  676. const isOpen = savedStates.hasOwnProperty(categoryKey) ? savedStates[categoryKey] : defaultOpen;
  677.  
  678. var category = document.createElement('div');
  679. category.className = 'ptsm-category ' + className + (isOpen ? ' open' : '');
  680.  
  681. var header = document.createElement('button');
  682. header.className = 'ptsm-category-header';
  683. header.innerHTML = title + '<div class="ptsm-dropdown-arrow"></div>';
  684.  
  685. var content = document.createElement('div');
  686. content.className = 'ptsm-category-content';
  687.  
  688. // ENTFERNT: Der alte click handler wird durch den neuen in enableDragAndDrop ersetzt
  689.  
  690. category.appendChild(header);
  691. category.appendChild(content);
  692.  
  693. return { category, content };
  694. }
  695.  
  696. function createMapButton(text, className, clickHandler) {
  697. var btn = document.createElement('button');
  698. btn.textContent = text;
  699. btn.className = 'ptsm-map-btn ' + className;
  700. btn.addEventListener('click', clickHandler);
  701. return btn;
  702. }
  703.  
  704. function createCompactButton() {
  705. var btn = document.createElement('button');
  706. btn.className = 'ptsm-compact-btn';
  707. btn.innerHTML = '🔧 Kompakt-Modus';
  708.  
  709. const isCompact = loadCompactMode();
  710. if (isCompact) {
  711. btn.innerHTML = '🔧 Normal-Modus';
  712. btn.classList.add('active');
  713. }
  714.  
  715. btn.addEventListener('click', function() {
  716. const container = document.querySelector('.ptsm-container');
  717. const isCurrentlyCompact = container.classList.contains('compact');
  718.  
  719. if (isCurrentlyCompact) {
  720. container.classList.remove('compact');
  721. btn.innerHTML = '🔧 Kompakt-Modus';
  722. btn.classList.remove('active');
  723. saveCompactMode(false);
  724. } else {
  725. container.classList.add('compact');
  726. btn.innerHTML = '🔧 Normal-Modus';
  727. btn.classList.add('active');
  728. saveCompactMode(true);
  729. }
  730. });
  731.  
  732. return btn;
  733. }
  734.  
  735. function addButtons() {
  736. if (ptsmInitialized) {
  737. return;
  738. }
  739.  
  740. if (!document.getElementById('user-info')) {
  741. setTimeout(addButtons, 500);
  742. return;
  743. }
  744.  
  745. if (!W.loginManager.user) {
  746. if (!ptsmInitialized) {
  747. W.loginManager.events.register('login', null, function() {
  748. if (!ptsmInitialized) addButtons();
  749. });
  750. W.loginManager.events.register('loginStatus', null, function() {
  751. if (!ptsmInitialized) addButtons();
  752. });
  753. }
  754. if (!W.loginManager.user) {
  755. return;
  756. }
  757. }
  758.  
  759. ptsmInitialized = true;
  760.  
  761. if (typeof proj4 === "undefined") {
  762. var script = document.createElement('script');
  763. script.src = 'https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4.js';
  764. document.head.appendChild(script);
  765. }
  766.  
  767. // Add CSS
  768. var style = document.createElement('style');
  769. style.textContent = `
  770. .ptsm-container {
  771. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  772. padding: 8px;
  773. background: #fff;
  774. font-size: 12px;
  775. position: relative;
  776. overflow: hidden;
  777. }
  778.  
  779. .ptsm-category {
  780. margin-bottom: 8px;
  781. border-radius: 6px;
  782. overflow: hidden;
  783. background: #fff;
  784. box-shadow: 0 1px 4px rgba(0,0,0,0.08);
  785. border: 1px solid #e1e5e9;
  786. position: relative;
  787. }
  788.  
  789. .ptsm-category-header {
  790. width: 100%;
  791. padding: 8px 12px;
  792. background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
  793. color: white;
  794. border: none;
  795. cursor: pointer;
  796. font-weight: 600;
  797. font-size: 11px;
  798. text-transform: uppercase;
  799. letter-spacing: 0.5px;
  800. display: flex;
  801. justify-content: space-between;
  802. align-items: center;
  803. transition: all 0.2s ease;
  804. user-select: none;
  805. }
  806.  
  807. .ptsm-category-header:hover {
  808. background: linear-gradient(135deg, #5a6268 0%, #343a40 100%);
  809. }
  810.  
  811. .ptsm-dropdown-arrow {
  812. width: 0;
  813. height: 0;
  814. border-left: 4px solid transparent;
  815. border-right: 4px solid transparent;
  816. border-top: 6px solid white;
  817. transition: transform 0.2s ease;
  818. }
  819.  
  820. .ptsm-category.open .ptsm-dropdown-arrow {
  821. transform: rotate(180deg);
  822. }
  823.  
  824. .ptsm-category-content {
  825. max-height: 0;
  826. overflow: hidden;
  827. padding: 0 8px;
  828. transition: max-height 0.3s ease, padding 0.2s ease;
  829. }
  830.  
  831. .ptsm-category.open .ptsm-category-content {
  832. max-height: 500px;
  833. padding: 8px;
  834. }
  835.  
  836. .ptsm-map-btn {
  837. width: calc(33.333% - 4px);
  838. height: 28px;
  839. margin: 2px;
  840. padding: 4px 6px 4px 24px;
  841. background: #f8f9fa;
  842. border: 1px solid #dee2e6;
  843. border-radius: 4px;
  844. font-size: 10px;
  845. font-weight: 500;
  846. color: #495057;
  847. cursor: pointer;
  848. position: relative;
  849. display: inline-flex;
  850. align-items: center;
  851. justify-content: flex-start;
  852. transition: all 0.15s ease;
  853. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  854. text-overflow: ellipsis;
  855. overflow: hidden;
  856. white-space: nowrap;
  857. }
  858.  
  859. .ptsm-map-btn::before {
  860. content: '';
  861. position: absolute;
  862. left: 4px;
  863. top: 50%;
  864. transform: translateY(-50%);
  865. width: 12px;
  866. height: 12px;
  867. background-size: contain;
  868. background-repeat: no-repeat;
  869. background-position: center;
  870. flex-shrink: 0;
  871. }
  872.  
  873. .ptsm-map-btn:hover {
  874. transform: translateY(-1px);
  875. background: linear-gradient(145deg, #ffffff 0%, #f1f3f4 100%);
  876. border-color: #007bff;
  877. color: #0056b3;
  878. box-shadow: 0 2px 8px rgba(0, 123, 255, 0.12);
  879. }
  880.  
  881. .ptsm-compact-btn {
  882. width: calc(100% - 4px);
  883. height: 32px;
  884. margin: 2px;
  885. padding: 8px 12px;
  886. background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
  887. border: none;
  888. border-radius: 4px;
  889. font-size: 11px;
  890. font-weight: 600;
  891. color: white;
  892. cursor: pointer;
  893. display: flex;
  894. align-items: center;
  895. justify-content: center;
  896. transition: all 0.15s ease;
  897. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  898. text-transform: uppercase;
  899. letter-spacing: 0.5px;
  900. }
  901.  
  902. .ptsm-compact-btn:hover {
  903. transform: translateY(-1px);
  904. background: linear-gradient(135deg, #1e7e34 0%, #155724 100%);
  905. box-shadow: 0 2px 8px rgba(40, 167, 69, 0.25);
  906. }
  907.  
  908. .ptsm-compact-btn.active {
  909. background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
  910. }
  911.  
  912. .ptsm-compact-btn.active:hover {
  913. background: linear-gradient(135deg, #c82333 0%, #a02834 100%);
  914. box-shadow: 0 2px 8px rgba(220, 53, 69, 0.25);
  915. }
  916.  
  917. /* Kompakt-Modus Styles */
  918. .ptsm-container.compact .ptsm-category {
  919. margin-bottom: 4px;
  920. }
  921.  
  922. .ptsm-container.compact .ptsm-category-header {
  923. padding: 6px 10px;
  924. font-size: 10px;
  925. }
  926.  
  927. .ptsm-container.compact .ptsm-category.open .ptsm-category-content {
  928. padding: 4px;
  929. }
  930.  
  931. .ptsm-container.compact .ptsm-map-btn {
  932. width: calc(33.333% - 3px);
  933. height: 24px;
  934. margin: 1.5px;
  935. padding: 3px 5px 3px 20px;
  936. font-size: 9px;
  937. }
  938.  
  939. .ptsm-container.compact .ptsm-map-btn::before {
  940. width: 10px;
  941. height: 10px;
  942. left: 3px;
  943. }
  944.  
  945. .ptsm-container.compact .ptsm-compact-btn {
  946. height: 28px;
  947. font-size: 10px;
  948. padding: 6px 10px;
  949. }
  950.  
  951. /* Drag Placeholder */
  952. .ptsm-drag-placeholder {
  953. background: #f0f0f0;
  954. border: 2px dashed #ccc;
  955. border-radius: 6px;
  956. margin-bottom: 8px;
  957. opacity: 0.5;
  958. }
  959.  
  960. /* Category colors */
  961. .ptsm-category-allgem .ptsm-category-header { background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); }
  962. .ptsm-category-allgem .ptsm-category-header:hover { background: linear-gradient(135deg, #0056b3 0%, #004085 100%); }
  963. .ptsm-category-baustell .ptsm-category-header { background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%); }
  964. .ptsm-category-baustell .ptsm-category-header:hover { background: linear-gradient(135deg, #1e7e34 0%, #155724 100%); }
  965. .ptsm-category-blitzer .ptsm-category-header { background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); }
  966. .ptsm-category-blitzer .ptsm-category-header:hover { background: linear-gradient(135deg, #c82333 0%, #a02834 100%); }
  967. .ptsm-category-bilder .ptsm-category-header { background: linear-gradient(135deg, #17a2b8 0%, #138496 100%); }
  968. .ptsm-category-bilder .ptsm-category-header:hover { background: linear-gradient(135deg, #138496 0%, #0f6674 100%); }
  969. .ptsm-category-geoportal .ptsm-category-header { background: linear-gradient(135deg, #6f42c1 0%, #59359a 100%); }
  970. .ptsm-category-geoportal .ptsm-category-header:hover { background: linear-gradient(135deg, #59359a 0%, #4c2c85 100%); }
  971. .ptsm-category-misc .ptsm-category-header { background: linear-gradient(135deg, #fd7e14 0%, #e65100 100%); }
  972. .ptsm-category-misc .ptsm-category-header:hover { background: linear-gradient(135deg, #e65100 0%, #bf360c 100%); }
  973. .ptsm-category-settings .ptsm-category-header { background: linear-gradient(135deg, #6c757d 0%, #495057 100%); }
  974. .ptsm-category-settings .ptsm-category-header:hover { background: linear-gradient(135deg, #5a6268 0%, #343a40 100%); }
  975.  
  976. /* Icons */
  977. .ptsm-google::before { background-image: url(https://i.ibb.co/d0zx6Pdt/google-maps.png); }
  978. .ptsm-f4map::before { background-image: url(https://i.ibb.co/5WxjKkLp/F-Logo.png); }
  979. .ptsm-apple::before { background-image: url(https://i.ibb.co/WsH15zC/Apple-Jetzt.png); }
  980. .ptsm-bing::before { background-image: url(https://i.ibb.co/0LF74p6/bing.png); }
  981. .ptsm-nrw::before { background-image: url(https://i.ibb.co/37q7H37/nrw.png); }
  982. .ptsm-osm::before { background-image: url(https://i.ibb.co/20wtGrsL/osm.png); }
  983. .ptsm-autobahn::before { background-image: url(https://i.ibb.co/2Y3pT8v2/Autobahn-Logo.png); }
  984. .ptsm-poi-karte::before { background-image: url(https://i.ibb.co/nMRSSKKp/POI-Karte.jpg); }
  985. .ptsm-poi-base::before { background-image: url(https://i.ibb.co/xS7vJQr8/POI-Base.jpg); }
  986. .ptsm-viamichelin::before { background-image: url(https://i.ibb.co/RTzJP87C/viamichelin.png); }
  987. .ptsm-here::before { background-image: url(https://i.ibb.co/MC9JF7T/h-logo.png); }
  988. .ptsm-mapillary::before { background-image: url(https://i.ibb.co/JWkZnh0X/mapillary.png); }
  989. .ptsm-osbrowser::before { background-image: url(https://i.ibb.co/RdQSgsY/osb.png); }
  990. .ptsm-mappy::before { background-image: url(https://i.ibb.co/wrhH7H95/mappy.png); }
  991. .ptsm-blitzer::before { background-image: url(https://i.ibb.co/gVKMwKS/blitzer.png); }
  992. .ptsm-bayernatlas::before { background-image: url(https://i.ibb.co/KxnBpv7J/bayernatlas.png); }
  993. .ptsm-tomtom::before { background-image: url(https://i.ibb.co/hDq5bys/tomtom-icon2.png); }
  994. .ptsm-basemap-de::before { background-image: url(https://i.ibb.co/V3jJJrb/de-map.png); }
  995. .ptsm-reporting::before { background-image: url(https://i.ibb.co/rZb76j2/pin.png); }
  996. .ptsm-kartaview::before { background-image: url(https://i.ibb.co/xgnTMFf/kartaview.png); }
  997. .ptsm-bayerninfo::before { background-image: url(https://i.ibb.co/R0K3SSs/bayerninfo.png); }
  998. .ptsm-timonline::before { background-image: url(https://i.ibb.co/bPJ4qRy/das-da2.png); }
  999. .ptsm-geoadmin::before { background-image: url(https://i.ibb.co/Np5chv4/CH-Icon-20.png); }
  1000. .ptsm-basemap-at::before { background-image: url(https://i.ibb.co/MCKhDSH/AT-Icon.png); }
  1001. .ptsm-adac::before { background-image: url(https://i.ibb.co/6YsGCFy/adac.png); }
  1002. .ptsm-here-edit::before { background-image: url(https://i.ibb.co/VghMgy8/here.png); }
  1003. .ptsm-umsehen::before { background-image: url(https://i.ibb.co/XYqjkYX/umsehen-icon.png); }
  1004. .ptsm-hackintosh::before { background-image: url(https://i.ibb.co/8xP5RyC/Hackintosh.png); }
  1005. .ptsm-archive::before { background-image: url(https://i.ibb.co/QHpvd85/Das-Logo.png); }
  1006.  
  1007. .ptsm-state-info {
  1008. font-size: 9px;
  1009. color: #6c757d;
  1010. font-style: italic;
  1011. text-align: center;
  1012. margin-top: 6px;
  1013. margin-bottom: 4px;
  1014. }
  1015.  
  1016. .ptsm-warning {
  1017. background: #fff8e1;
  1018. border: 1px solid #ffcc02;
  1019. border-radius: 4px;
  1020. padding: 8px;
  1021. margin-top: 8px;
  1022. margin-bottom: 8px;
  1023. display: flex;
  1024. align-items: flex-start;
  1025. }
  1026.  
  1027. .ptsm-warning .w-icon {
  1028. font-size: 14px;
  1029. color: #f57c00;
  1030. margin-right: 6px;
  1031. flex-shrink: 0;
  1032. }
  1033.  
  1034. .ptsm-warning-text {
  1035. font-size: 9px;
  1036. color: #e65100;
  1037. line-height: 1.3;
  1038. }
  1039.  
  1040. @media (max-width: 768px) {
  1041. .ptsm-map-btn { width: calc(50% - 4px); }
  1042. }
  1043. @media (max-width: 480px) {
  1044. .ptsm-map-btn { width: calc(100% - 4px); }
  1045. }
  1046. `;
  1047. document.head.appendChild(style);
  1048.  
  1049. // Create all map buttons
  1050. var btn1 = createMapButton('Google', 'ptsm-google', () => {
  1051. var cz = getCenterZoom();
  1052. // Bessere Google Maps Zoom-Level Umrechnung
  1053. var googleZoom;
  1054. if (cz.zoom >= 20) googleZoom = 19; // Sehr nah
  1055. else if (cz.zoom >= 18) googleZoom = 17; // Nah
  1056. else if (cz.zoom >= 16) googleZoom = 15; // Mittel-nah
  1057. else if (cz.zoom >= 14) googleZoom = 13; // Mittel
  1058. else if (cz.zoom >= 12) googleZoom = 11; // Mittel-weit
  1059. else if (cz.zoom >= 10) googleZoom = 9; // Weit
  1060. else googleZoom = Math.max(1, cz.zoom - 2); // Sehr weit
  1061.  
  1062. window.open('https://www.google.com/maps/@' + cz.lat + ',' + cz.lon + ',' + googleZoom + 'z/data=!5m1!1e1', '_blank');
  1063. });
  1064.  
  1065. var btn2 = createMapButton('Bing', 'ptsm-bing', () => {
  1066. var cz = getCenterZoom();
  1067. cz.zoom -= 1;
  1068. window.open('https://www.bing.com/maps/traffic?cp=' + cz.lat + '~' + cz.lon + '&lvl=' + cz.zoom, '_blank');
  1069. });
  1070.  
  1071. var btn3 = createMapButton('Ver. NRW', 'ptsm-nrw', () => {
  1072. var cz = getCenterZoom();
  1073. window.open('https://www.verkehr.nrw/?center=' + cz.lat + ',' + cz.lon + '&zoom=' + cz.zoom + '&layer=Verkehrslage,Baustellen,Haltestellen,Parken,Webcams,Verkehrsmeldungen,ELadesaeulen,Tankstellen&highlightRoute=false', '_blank');
  1074. });
  1075.  
  1076. var btn3a = createMapButton('OSM', 'ptsm-osm', () => {
  1077. var cz = getCenterZoom();
  1078. window.open('https://www.openstreetmap.org/#map=' + cz.zoom + '/' + cz.lat + '/' + cz.lon, '_blank');
  1079. });
  1080.  
  1081. var btn4 = createMapButton('F4 3D Map', 'ptsm-f4map', () => {
  1082. var cz = getCenterZoom();
  1083. window.open('https://demo.f4map.com/#lat=' + cz.lat + '&lon=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
  1084. });
  1085.  
  1086. var btn5 = createMapButton('Apple', 'ptsm-apple', () => {
  1087. var cz = getCenterZoom();
  1088. window.open('https://maps.apple.com/look-around?coordinate=' + cz.lat + '%2C' + cz.lon, '_blank');
  1089. });
  1090.  
  1091. var btn6 = createMapButton('Autobahn', 'ptsm-autobahn', () => {
  1092. var cz = getCenterZoom();
  1093. window.open('https://verkehr.vz-deutschland.de/?layer=raststellen,baustellen,stau,verkehrsmeldungen&zoom=' + cz.zoom + '&lat=' + cz.lat + '&lon=' + cz.lon, '_blank');
  1094. });
  1095.  
  1096. var btn7 = createMapButton('POI Karte', 'ptsm-poi-karte', () => {
  1097. var cz = getCenterZoom();
  1098. // Bessere Zoom-Level-Übertragung: Je höher das Waze-Zoom, desto kleiner der Radius
  1099. var radius;
  1100. if (cz.zoom >= 18) radius = 500; // Sehr nah - 500m Radius
  1101. else if (cz.zoom >= 16) radius = 1000; // Nah - 1km Radius
  1102. else if (cz.zoom >= 14) radius = 2500; // Mittel - 2.5km Radius
  1103. else if (cz.zoom >= 12) radius = 5000; // Weit - 5km Radius
  1104. else if (cz.zoom >= 10) radius = 10000; // Sehr weit - 10km Radius
  1105. else radius = 25000; // Maximum - 25km Radius
  1106.  
  1107. window.open('https://www.flosm.org/de/POI-Karte.html?lat=' + cz.lat + '&lon=' + cz.lon + '&r=' + radius + '&st=0&sw=speedcamera', '_blank');
  1108. });
  1109.  
  1110. var btn8 = createMapButton('POI Base', 'ptsm-poi-base', () => {
  1111. var cz = getCenterZoom();
  1112. window.open('https://www.poibase.com/de/karte/#/map/coords-' + cz.lon + ',' + cz.lat + '/zoom-' + cz.zoom, '_blank');
  1113. });
  1114.  
  1115. var btn9 = createMapButton('ViaM', 'ptsm-viamichelin', () => {
  1116. var cz = getCenterZoom();
  1117. // ViaMichelin moderne URL-Struktur mit bounds und center
  1118. // Zoom-abhängige Bounds-Berechnung für bessere Darstellung
  1119. var zoomFactor;
  1120. if (cz.zoom >= 18) zoomFactor = 0.001; // Sehr nah
  1121. else if (cz.zoom >= 16) zoomFactor = 0.002; // Nah
  1122. else if (cz.zoom >= 14) zoomFactor = 0.005; // Mittel-nah
  1123. else if (cz.zoom >= 12) zoomFactor = 0.01; // Mittel
  1124. else if (cz.zoom >= 10) zoomFactor = 0.02; // Mittel-weit
  1125. else if (cz.zoom >= 8) zoomFactor = 0.05; // Weit
  1126. else zoomFactor = 0.1; // Sehr weit
  1127.  
  1128. // Bounds berechnen (Rechteck um den Mittelpunkt)
  1129. var latMin = (cz.lat - zoomFactor).toFixed(6);
  1130. var latMax = (cz.lat + zoomFactor).toFixed(6);
  1131. var lonMin = (cz.lon - zoomFactor).toFixed(6);
  1132. var lonMax = (cz.lon + zoomFactor).toFixed(6);
  1133.  
  1134. var viaMichelinUrl = 'https://www.viamichelin.de/karten-stadtplan/verkehr?bounds=' +
  1135. lonMin + '%7E' + latMin + '%7E' + lonMax + '%7E' + latMax +
  1136. '&center=' + cz.lon.toFixed(6) + '%7E' + cz.lat.toFixed(6) +
  1137. '&page=1&poiCategories=0&showPolandModal=false';
  1138.  
  1139. window.open(viaMichelinUrl, '_blank');
  1140. });
  1141.  
  1142. var btn10 = createMapButton('Here', 'ptsm-here', () => {
  1143. var cz = getCenterZoom();
  1144. window.open('https://wego.here.com/?map=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + ',normal', '_blank');
  1145. });
  1146.  
  1147. var btn11 = createMapButton('Mapillary', 'ptsm-mapillary', () => {
  1148. var cz = getCenterZoom();
  1149. cz.zoom -= 1;
  1150. window.open('https://www.mapillary.com/app/?lat=' + cz.lat + '&lng=' + cz.lon + '&z=' + cz.zoom, '_blank');
  1151. });
  1152.  
  1153. var btn12 = createMapButton('OSBrowser', 'ptsm-osbrowser', () => {
  1154. var cz = getCenterZoom();
  1155. window.open('https://www.openstreetbrowser.org/#map=' + cz.zoom + '/' + cz.lat + '/' + cz.lon + '&categories=car_maxspeed', '_blank');
  1156. });
  1157.  
  1158. var btn13 = createMapButton('Mappy', 'ptsm-mappy', () => {
  1159. var cz = getCenterZoom();
  1160. window.open('https://en.mappy.com/plan#/' + cz.lat + ',' + cz.lon, '_blank');
  1161. });
  1162.  
  1163. var btn14 = createMapButton('Blitzer.de', 'ptsm-blitzer', () => {
  1164. var cz = getCenterZoom();
  1165. window.open('https://map.atudo.com/v5/?lat=' + cz.lat + '&lng=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
  1166. });
  1167.  
  1168. var btn16 = createMapButton('BY Atlas', 'ptsm-bayernatlas', () => {
  1169. var cz = getCenterZoom();
  1170. cz.zoom -= 5;
  1171. if (typeof proj4 === "undefined") {
  1172. alert('proj4 library not loaded');
  1173. return;
  1174. }
  1175. var firstProj = '+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs';
  1176. var utm = proj4(firstProj, [cz.lon, cz.lat]);
  1177. window.open('https://geoportal.bayern.de/bayernatlas/index.html?zoom=' + cz.zoom + '&E=' + utm[0] + '&N=' + utm[1], '_blank');
  1178. });
  1179.  
  1180. var btn18 = createMapButton('TomTom', 'ptsm-tomtom', () => {
  1181. var cz = getCenterZoom();
  1182. cz.zoom -= 1;
  1183. window.open('https://plan.tomtom.com/de?p=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + 'z', '_blank');
  1184. });
  1185.  
  1186. var btn19 = createMapButton('Basemap.de', 'ptsm-basemap-de', () => {
  1187. var cz = getCenterZoom();
  1188. cz.zoom = 0.985 * cz.zoom - 1.05;
  1189. window.open('https://basemap.de/viewer?config=' + btoa('{"lat":' + cz.lat + ',"lon":' + cz.lon + ',"zoom":' + cz.zoom + ',"styleID":0,"pitch":0,"bearing":0,"saturation":0,"brightness":0,"hiddenControls":[],"hiddenLayers":[],"changedLayers":[],"hiddenSubGroups":[],"changedSubGroups":[],"externalStyleURL":""}'), '_blank');
  1190. });
  1191.  
  1192. var btn20 = createMapButton('Reporting', 'ptsm-reporting', () => {
  1193. var cz = getCenterZoom();
  1194. cz.zoom -= 1;
  1195. window.open('https://www.waze.com/partnerhub/map-tool?lat=' + cz.lat + '&lon=' + cz.lon + '&zoom=' + cz.zoom, '_blank');
  1196. });
  1197.  
  1198. var btn21 = createMapButton('KartaView', 'ptsm-kartaview', () => {
  1199. var cz = getCenterZoom();
  1200. cz.zoom -= 1;
  1201. window.open('https://kartaview.org/map/@' + cz.lat + ',' + cz.lon + ',' + cz.zoom + 'z', '_blank');
  1202. });
  1203.  
  1204. var btn22 = createMapButton('Bayerninfo', 'ptsm-bayerninfo', () => {
  1205. var cz = getCenterZoom();
  1206. cz.zoom -= 2;
  1207. var now = new Date();
  1208. var then = new Date();
  1209. then.setDate(then.getDate() + 60);
  1210. var mapsUrl = 'https://www.bayerninfo.de/de/baustellenkalender?geo=' + cz.lat + ',' + cz.lon + '&zoom=' + cz.zoom;
  1211. mapsUrl += '&datetimeFrom=' + now.toISOString() + '&datetimeTo=' + then.toISOString();
  1212. window.open(mapsUrl, '_blank');
  1213. });
  1214.  
  1215. var btn30 = createMapButton('TimOnline', 'ptsm-timonline', () => {
  1216. var cz = getCenterZoom();
  1217. cz.zoom = 454959671.96858*Math.exp(-0.693*cz.zoom);
  1218. window.open('https://www.tim-online.nrw.de/tim-online2/?center=' + cz.lat + ',' + cz.lon + '&scale=' + cz.zoom, '_blank');
  1219. });
  1220.  
  1221. var btn31 = createMapButton('GeoAdmin', 'ptsm-geoadmin', () => {
  1222. var cz = getCenterZoom();
  1223. cz.zoom -= 7.2;
  1224. var phi1 = ((cz.lat * 3600) - 169028.66) / 10000;
  1225. var lmd1 = ((cz.lon * 3600) - 26782.5) / 10000;
  1226. var x = 200147.07 + 308807.95 * phi1 + 3745.25 * lmd1 * lmd1 + 76.63 * phi1 * phi1 + 119.79 * phi1 * phi1 * phi1 - 194.56 * lmd1 * lmd1 * phi1;
  1227. var y = 600072.37 + 211455.93 * lmd1 - 10938.51 * lmd1 * phi1 - 0.36 * lmd1 * phi1 * phi1 - 44.54 * lmd1 * lmd1 * lmd1;
  1228. window.open('https://map.geo.admin.ch/?Y=' + y.toFixed(0) + '&X=' + x.toFixed(0) + '&zoom=' + cz.zoom + '&topic=ech&lang=de&bgLayer=ch.swisstopo.pixelkarte-farbe&layers=ch.bfs.gebaeude_wohnungs_register,ch.kantone.cadastralwebmap-farbe,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet,ch.swisstopo.swissimage-product,ch.bfe.ladestellen-elektromobilitaet&catalogNodes=457,532,687,458,477,485,491,510,527,1743&layers_visibility=false,false,true,true&layers_timestamp=,,current', '_blank');
  1229. });
  1230.  
  1231. var btn32 = createMapButton('Basemap', 'ptsm-basemap-at', () => {
  1232. var cz = getCenterZoom();
  1233. var x = cz.lon / 180 * 20037508.34;
  1234. var y = Math.log(Math.tan((90 + cz.lat * 1) * Math.PI / 360)) / Math.PI;
  1235. y *= 20037508.34;
  1236. window.open('https://basemap.at/bmapp/index.html#{"center":[' + x.toFixed(10) + ',' + y.toFixed(10) + '],"zoom":' + cz.zoom + ',"rotation":0,"layers":"1000000000"}', '_blank');
  1237. });
  1238.  
  1239. var btn34 = createMapButton('ADAC', 'ptsm-adac', () => {
  1240. var cz = getCenterZoom();
  1241. // ADAC Maps präzise Bounds-Berechnung basierend auf Bildvergleich
  1242. var zoomFactor;
  1243. if (cz.zoom >= 19) zoomFactor = 0.002; // Extrem nah - größerer Bereich
  1244. else if (cz.zoom >= 18) zoomFactor = 0.003; // Sehr nah
  1245. else if (cz.zoom >= 17) zoomFactor = 0.004; // Nah+
  1246. else if (cz.zoom >= 16) zoomFactor = 0.006; // Nah
  1247. else if (cz.zoom >= 15) zoomFactor = 0.008; // Mittel-nah+
  1248. else if (cz.zoom >= 14) zoomFactor = 0.012; // Mittel-nah
  1249. else if (cz.zoom >= 13) zoomFactor = 0.016; // Mittel+
  1250. else if (cz.zoom >= 12) zoomFactor = 0.022; // Mittel
  1251. else if (cz.zoom >= 11) zoomFactor = 0.030; // Mittel-weit+
  1252. else if (cz.zoom >= 10) zoomFactor = 0.040; // Mittel-weit
  1253. else if (cz.zoom >= 9) zoomFactor = 0.060; // Weit+
  1254. else if (cz.zoom >= 8) zoomFactor = 0.080; // Weit
  1255. else zoomFactor = 0.120; // Sehr weit
  1256.  
  1257. // Korrektur der Verschiebung: ADAC zeigt zu weit östlich und leicht nördlich
  1258. var correctedLat = cz.lat - (zoomFactor * 0.05); // Leichte Südverschiebung
  1259. var correctedLon = cz.lon - (zoomFactor * 0.15); // Leichte Westverschiebung
  1260.  
  1261. var latMin = (correctedLat - zoomFactor).toFixed(6);
  1262. var latMax = (correctedLat + zoomFactor).toFixed(6);
  1263. var lonMin = (correctedLon - zoomFactor).toFixed(6);
  1264. var lonMax = (correctedLon + zoomFactor).toFixed(6);
  1265.  
  1266. var adacUrl = 'https://maps.adac.de/?bounds=' +
  1267. latMin + ',' + lonMin + '-' + latMax + ',' + lonMax +
  1268. '&traffic=construction,announcements,flow';
  1269.  
  1270. window.open(adacUrl, '_blank');
  1271. });
  1272.  
  1273. var btn36 = createMapButton('Here Edit', 'ptsm-here-edit', () => {
  1274. var cz = getCenterZoom();
  1275. window.open('https://mapcreator.here.com/?l=' + cz.lat + ',' + cz.lon + ',' + cz.zoom + ',autoselect', '_blank');
  1276. });
  1277.  
  1278. var btn37 = createMapButton('Umsehen', 'ptsm-umsehen', () => {
  1279. var cz = getCenterZoom();
  1280. cz.zoom += 1.0;
  1281. window.open('https://maps.apple.com/?ll=' + cz.lat + ',' + cz.lon + '&z=' + cz.zoom + '&t=m', '_blank');
  1282. });
  1283.  
  1284. var btn39 = createMapButton('Hackintosh', 'ptsm-hackintosh', () => {
  1285. var cz = getCenterZoom();
  1286. window.open('https://lookmap.eu.pythonanywhere.com/#c=' + cz.zoom + '/' + cz.lat + '/' + cz.lon + '/', '_blank');
  1287. });
  1288.  
  1289. var btn40 = createMapButton('Archive', 'ptsm-archive', () => {
  1290. window.open('https://archive.is/', '_blank');
  1291. });
  1292.  
  1293. // Create container
  1294. var container = document.createElement('div');
  1295. container.className = 'ptsm-container';
  1296.  
  1297. // Kompakt-Modus beim Laden anwenden falls gespeichert
  1298. if (loadCompactMode()) {
  1299. container.classList.add('compact');
  1300. }
  1301.  
  1302. // Create categories
  1303. var allgemCategory = createCategory('Allgemeine Karten', 'ptsm-category-allgem', true);
  1304. var baustellCategory = createCategory('Baustellen & Verkehr', 'ptsm-category-baustell', false);
  1305. var blitzerCategory = createCategory('Blitzer & Geschwindigkeit', 'ptsm-category-blitzer', false);
  1306. var bilderCategory = createCategory('Straßenbilder', 'ptsm-category-bilder', false);
  1307. var geoportalCategory = createCategory('Geoportale', 'ptsm-category-geoportal', false);
  1308. var miscCategory = createCategory('Sonstiges', 'ptsm-category-misc', false);
  1309.  
  1310. // Add buttons to categories
  1311. // Allgemeine Karten
  1312. allgemCategory.content.appendChild(btn1); // Google
  1313. allgemCategory.content.appendChild(btn2); // Bing
  1314. allgemCategory.content.appendChild(btn3a); // OSM
  1315. allgemCategory.content.appendChild(btn4); // F4 3D
  1316. allgemCategory.content.appendChild(btn5); // Apple
  1317. allgemCategory.content.appendChild(btn10); // Here
  1318. allgemCategory.content.appendChild(btn13); // Mappy
  1319. allgemCategory.content.appendChild(btn18); // TomTom
  1320. allgemCategory.content.appendChild(btn9); // ViaM
  1321.  
  1322. // Baustellen & Verkehr
  1323. baustellCategory.content.appendChild(btn3); // Ver. NRW
  1324. baustellCategory.content.appendChild(btn6); // Autobahn
  1325. baustellCategory.content.appendChild(btn34); // ADAC
  1326.  
  1327. // Blitzer & Geschwindigkeit
  1328. blitzerCategory.content.appendChild(btn14); // Blitzer.de
  1329. blitzerCategory.content.appendChild(btn7); // POI Karte
  1330. blitzerCategory.content.appendChild(btn8); // POI Base
  1331.  
  1332. // Straßenbilder
  1333. bilderCategory.content.appendChild(btn11); // Mapillary
  1334. bilderCategory.content.appendChild(btn21); // KartaView
  1335. bilderCategory.content.appendChild(btn12); // OSBrowser
  1336.  
  1337. // Geoportale
  1338. geoportalCategory.content.appendChild(btn19); // Basemap DE
  1339. geoportalCategory.content.appendChild(btn31); // GeoAdmin
  1340. geoportalCategory.content.appendChild(btn32); // Basemap AT
  1341. geoportalCategory.content.appendChild(btn30); // TimOnline
  1342. geoportalCategory.content.appendChild(btn16); // BY Atlas
  1343. geoportalCategory.content.appendChild(btn22); // Bayerninfo
  1344.  
  1345. // Einstellungen-Kategorie erstellen
  1346. var settingsCategory = createCategory('Einstellungen', 'ptsm-category-settings', false);
  1347.  
  1348. // Settings-Inhalte erstellen
  1349. function createSettingsContent() {
  1350. const settings = loadSettings();
  1351.  
  1352. var settingsContent = document.createElement('div');
  1353. settingsContent.className = 'ptsm-settings-content';
  1354. settingsContent.style.cssText = 'padding: 8px;';
  1355.  
  1356. // Freie Position Checkbox
  1357. var freePositionContainer = document.createElement('div');
  1358. freePositionContainer.style.cssText = `
  1359. margin-bottom: 12px;
  1360. padding: 8px;
  1361. background: #f8f9fa;
  1362. border-radius: 4px;
  1363. display: flex;
  1364. align-items: center;
  1365. border: 1px solid #e9ecef;
  1366. `;
  1367.  
  1368. var freePositionCheckbox = document.createElement('input');
  1369. freePositionCheckbox.type = 'checkbox';
  1370. freePositionCheckbox.id = 'ptsm-free-position';
  1371. freePositionCheckbox.checked = settings.freePosition;
  1372. freePositionCheckbox.style.cssText = 'margin-right: 10px; transform: scale(1.2);';
  1373.  
  1374. var freePositionLabel = document.createElement('label');
  1375. freePositionLabel.htmlFor = 'ptsm-free-position';
  1376. freePositionLabel.innerHTML = '📌 Freie Position (Desktop-weit)';
  1377. freePositionLabel.style.cssText = `
  1378. font-size: 12px;
  1379. font-weight: 500;
  1380. cursor: pointer;
  1381. flex: 1;
  1382. user-select: none;
  1383. `;
  1384.  
  1385. freePositionContainer.appendChild(freePositionCheckbox);
  1386. freePositionContainer.appendChild(freePositionLabel);
  1387.  
  1388. // Kompakt-Modus Checkbox
  1389. var compactContainer = document.createElement('div');
  1390. compactContainer.style.cssText = `
  1391. margin-bottom: 12px;
  1392. padding: 8px;
  1393. background: #f8f9fa;
  1394. border-radius: 4px;
  1395. display: flex;
  1396. align-items: center;
  1397. border: 1px solid #e9ecef;
  1398. `;
  1399.  
  1400. var compactCheckbox = document.createElement('input');
  1401. compactCheckbox.type = 'checkbox';
  1402. compactCheckbox.id = 'ptsm-compact-mode';
  1403. compactCheckbox.checked = loadCompactMode();
  1404. compactCheckbox.style.cssText = 'margin-right: 10px; transform: scale(1.2);';
  1405.  
  1406. var compactLabel = document.createElement('label');
  1407. compactLabel.htmlFor = 'ptsm-compact-mode';
  1408. compactLabel.innerHTML = '🔧 Kompakt-Modus';
  1409. compactLabel.style.cssText = `
  1410. font-size: 12px;
  1411. font-weight: 500;
  1412. cursor: pointer;
  1413. flex: 1;
  1414. user-select: none;
  1415. `;
  1416.  
  1417. compactContainer.appendChild(compactCheckbox);
  1418. compactContainer.appendChild(compactLabel);
  1419.  
  1420. // Reset Button
  1421. var resetButton = document.createElement('button');
  1422. resetButton.className = 'ptsm-reset-settings-btn';
  1423. resetButton.innerHTML = '🔄 Einstellungen zurücksetzen';
  1424. resetButton.style.cssText = `
  1425. width: 100%;
  1426. height: 36px;
  1427. padding: 8px 12px;
  1428. background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
  1429. border: none;
  1430. border-radius: 4px;
  1431. font-size: 12px;
  1432. font-weight: 600;
  1433. color: white;
  1434. cursor: pointer;
  1435. display: flex;
  1436. align-items: center;
  1437. justify-content: center;
  1438. transition: all 0.15s ease;
  1439. text-transform: uppercase;
  1440. letter-spacing: 0.5px;
  1441. margin-top: 4px;
  1442. `;
  1443.  
  1444. // Event Listeners
  1445. freePositionCheckbox.addEventListener('change', function() {
  1446. toggleFreePosition(this.checked);
  1447. // Event für Cursor-Update senden
  1448. document.dispatchEvent(new Event('ptsm-settings-changed'));
  1449. });
  1450.  
  1451. compactCheckbox.addEventListener('change', function() {
  1452. const container = document.querySelector('.ptsm-container');
  1453. if (this.checked) {
  1454. container.classList.add('compact');
  1455. saveCompactMode(true);
  1456. } else {
  1457. container.classList.remove('compact');
  1458. saveCompactMode(false);
  1459. }
  1460. });
  1461.  
  1462. resetButton.addEventListener('click', resetAllSettings);
  1463.  
  1464. resetButton.addEventListener('mouseover', function() {
  1465. this.style.transform = 'translateY(-1px)';
  1466. this.style.background = 'linear-gradient(135deg, #c82333 0%, #a02834 100%)';
  1467. this.style.boxShadow = '0 2px 8px rgba(220, 53, 69, 0.25)';
  1468. });
  1469.  
  1470. resetButton.addEventListener('mouseout', function() {
  1471. this.style.transform = 'translateY(0)';
  1472. this.style.background = 'linear-gradient(135deg, #dc3545 0%, #c82333 100%)';
  1473. this.style.boxShadow = 'none';
  1474. });
  1475.  
  1476. settingsContent.appendChild(freePositionContainer);
  1477. settingsContent.appendChild(compactContainer);
  1478. settingsContent.appendChild(resetButton);
  1479.  
  1480. return settingsContent;
  1481. }
  1482.  
  1483. // Settings-Inhalt zur Kategorie hinzufügen
  1484. var settingsContent = createSettingsContent();
  1485. settingsCategory.content.appendChild(settingsContent);
  1486.  
  1487. // Sonstiges
  1488. miscCategory.content.appendChild(btn20); // Reporting
  1489. miscCategory.content.appendChild(btn36); // Here Edit
  1490. miscCategory.content.appendChild(btn37); // Umsehen
  1491. miscCategory.content.appendChild(btn39); // Hackintosh
  1492. miscCategory.content.appendChild(btn40); // Archive
  1493.  
  1494. // Add categories to container
  1495. container.appendChild(allgemCategory.category);
  1496. container.appendChild(baustellCategory.category);
  1497. container.appendChild(blitzerCategory.category);
  1498. container.appendChild(bilderCategory.category);
  1499. container.appendChild(geoportalCategory.category);
  1500. container.appendChild(miscCategory.category);
  1501. container.appendChild(settingsCategory.category);
  1502.  
  1503. // Add version info with update link
  1504. var versionInfo = document.createElement('div');
  1505. versionInfo.className = 'ptsm-state-info';
  1506.  
  1507. var updateLink = document.createElement('a');
  1508. updateLink.href = 'https://greasyfork.org/de/scripts/448378-wme-permalink-to-several-maps-dach';
  1509. updateLink.target = '_blank';
  1510. updateLink.textContent = 'Auf Updates überprüfen / V' + ptsmVersion;
  1511. updateLink.style.cssText = 'color: #007bff; text-decoration: none; font-size: 9px;';
  1512. updateLink.addEventListener('mouseover', function() {
  1513. this.style.textDecoration = 'underline';
  1514. });
  1515. updateLink.addEventListener('mouseout', function() {
  1516. this.style.textDecoration = 'none';
  1517. });
  1518.  
  1519. versionInfo.appendChild(updateLink);
  1520. container.appendChild(versionInfo);
  1521.  
  1522. // Add warning about external services
  1523. var warning = document.createElement('div');
  1524. warning.className = 'ptsm-warning';
  1525. warning.innerHTML = '<div class="w-icon">⚠</div><div class="ptsm-warning-text">Hinweis: Einige der externen Karten sind als Informationsquelle zur Kartenbearbeitung nicht zulässig!</div>';
  1526. container.appendChild(warning);
  1527.  
  1528. // Try to use WME Userscript API first, fallback to manual insertion
  1529. try {
  1530. if (W.userscripts && W.userscripts.registerSidebarTab) {
  1531. const result = W.userscripts.registerSidebarTab('wme-ptsm-dach');
  1532. var tabLabel = result.tabLabel;
  1533. var tabPane = result.tabPane;
  1534.  
  1535. tabLabel.textContent = 'PTSM';
  1536. tabLabel.title = 'WME Permalink to several Maps DACH';
  1537.  
  1538. tabPane.appendChild(container);
  1539. } else {
  1540. // Fallback: Try to add to existing sidebar
  1541. var sidebar = document.querySelector('#sidebar') || document.querySelector('.sidebar');
  1542. if (sidebar) {
  1543. // Create a collapsible section
  1544. var section = document.createElement('div');
  1545. section.style.cssText = 'border: 1px solid #ccc; margin: 10px 0; border-radius: 5px;';
  1546.  
  1547. var header = document.createElement('div');
  1548. header.style.cssText = 'background: #f5f5f5; padding: 10px; cursor: pointer; font-weight: bold; user-select: none;';
  1549. header.textContent = 'PTSM DACH';
  1550.  
  1551. var content = document.createElement('div');
  1552. content.style.display = 'none';
  1553. content.appendChild(container);
  1554.  
  1555. header.addEventListener('click', function() {
  1556. content.style.display = content.style.display === 'none' ? 'block' : 'none';
  1557. });
  1558.  
  1559. section.appendChild(header);
  1560. section.appendChild(content);
  1561. sidebar.appendChild(section);
  1562. } else {
  1563. // Last resort: Create floating panel
  1564. var floatingPanel = document.createElement('div');
  1565. floatingPanel.style.cssText = `
  1566. position: fixed;
  1567. top: 100px;
  1568. right: 10px;
  1569. width: 300px;
  1570. max-height: 70vh;
  1571. overflow-y: auto;
  1572. background: white;
  1573. border: 1px solid #ccc;
  1574. border-radius: 8px;
  1575. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  1576. z-index: 10000;
  1577. resize: both;
  1578. `;
  1579.  
  1580. var panelHeader = document.createElement('div');
  1581. panelHeader.style.cssText = `
  1582. background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
  1583. color: white;
  1584. padding: 10px;
  1585. font-weight: bold;
  1586. cursor: move;
  1587. user-select: none;
  1588. display: flex;
  1589. justify-content: space-between;
  1590. align-items: center;
  1591. `;
  1592. panelHeader.innerHTML = 'PTSM DACH <span style="cursor: pointer; font-size: 18px;">&times;</span>';
  1593.  
  1594. var closeBtn = panelHeader.querySelector('span');
  1595. closeBtn.addEventListener('click', function() {
  1596. floatingPanel.style.display = 'none';
  1597. });
  1598.  
  1599. // Make panel draggable
  1600. var isDragging = false;
  1601. var startX, startY, startLeft, startTop;
  1602.  
  1603. panelHeader.addEventListener('mousedown', function(e) {
  1604. if (e.target === closeBtn) return;
  1605. isDragging = true;
  1606. startX = e.clientX;
  1607. startY = e.clientY;
  1608. startLeft = parseInt(window.getComputedStyle(floatingPanel).left, 10);
  1609. startTop = parseInt(window.getComputedStyle(floatingPanel).top, 10);
  1610. document.addEventListener('mousemove', drag);
  1611. document.addEventListener('mouseup', stopDrag);
  1612. });
  1613.  
  1614. function drag(e) {
  1615. if (!isDragging) return;
  1616. var newLeft = startLeft + e.clientX - startX;
  1617. var newTop = startTop + e.clientY - startY;
  1618. floatingPanel.style.left = newLeft + 'px';
  1619. floatingPanel.style.top = newTop + 'px';
  1620. }
  1621.  
  1622. function stopDrag() {
  1623. isDragging = false;
  1624. document.removeEventListener('mousemove', drag);
  1625. document.removeEventListener('mouseup', stopDrag);
  1626. }
  1627.  
  1628. floatingPanel.appendChild(panelHeader);
  1629. floatingPanel.appendChild(container);
  1630. document.body.appendChild(floatingPanel);
  1631. }
  1632. }
  1633. } catch (e) {
  1634. console.error('PTSM: Error adding to sidebar:', e);
  1635. }
  1636.  
  1637. // Alle PTSM-Daten zurücksetzen
  1638. function resetAllSettings() {
  1639. if (confirm('Alle PTSM-Einstellungen zurücksetzen?\n\n- Menü-Positionen\n- Dropdown-Zustände\n- Kompakt-Modus\n- Freie Position\n\nSeite wird neu geladen.')) {
  1640. try {
  1641. // Alle PTSM localStorage Keys entfernen
  1642. const keysToRemove = [];
  1643. for (let i = 0; i < localStorage.length; i++) {
  1644. const key = localStorage.key(i);
  1645. if (key && key.startsWith('wme-ptsm-')) {
  1646. keysToRemove.push(key);
  1647. }
  1648. }
  1649. keysToRemove.forEach(key => localStorage.removeItem(key));
  1650.  
  1651. // Seite neu laden
  1652. location.reload();
  1653. } catch (e) {
  1654. alert('Fehler beim Zurücksetzen der Einstellungen');
  1655. }
  1656. }
  1657. }
  1658.  
  1659. // Freie Position ein/ausschalten
  1660. function toggleFreePosition(enabled) {
  1661. const settings = loadSettings();
  1662. settings.freePosition = enabled;
  1663. saveSettings(settings);
  1664.  
  1665. const container = document.querySelector('.ptsm-container');
  1666. if (!container) return;
  1667.  
  1668. const categoryOrder = [
  1669. '.ptsm-category-allgem',
  1670. '.ptsm-category-baustell',
  1671. '.ptsm-category-blitzer',
  1672. '.ptsm-category-bilder',
  1673. '.ptsm-category-geoportal',
  1674. '.ptsm-category-misc',
  1675. '.ptsm-category-settings'
  1676. ];
  1677.  
  1678. if (enabled) {
  1679. // Freier Modus: Menüs können Desktop-weit gezogen werden
  1680. categoryOrder.forEach(selector => {
  1681. const category = document.querySelector(selector);
  1682. if (category) {
  1683. // Menüs für Desktop-weites Drag vorbereiten
  1684. category.style.position = 'relative';
  1685. category.style.zIndex = '1';
  1686. }
  1687. });
  1688. } else {
  1689. // Normaler Modus: Nur Desktop-Positionierung zurücksetzen
  1690. categoryOrder.forEach(selector => {
  1691. const category = document.querySelector(selector);
  1692. if (category) {
  1693. // Nur Desktop-Drag-Eigenschaften entfernen (wenn fixed positioniert)
  1694. if (category.style.position === 'fixed') {
  1695. category.style.position = '';
  1696. category.style.left = '';
  1697. category.style.top = '';
  1698. category.style.zIndex = '';
  1699. category.style.width = '';
  1700. category.style.maxWidth = '';
  1701. category.style.transform = '';
  1702. category.style.opacity = '';
  1703. category.style.boxShadow = '';
  1704.  
  1705. // Nur zurück in Container wenn es außerhalb ist
  1706. if (category.parentElement !== container) {
  1707. container.appendChild(category);
  1708. }
  1709. }
  1710. }
  1711. });
  1712.  
  1713. // Nur Version-Info und Warnung korrigieren falls sie verschoben wurden
  1714. const versionInfo = container.querySelector('.ptsm-state-info');
  1715. const warning = container.querySelector('.ptsm-warning');
  1716.  
  1717. // Prüfen ob Version-Info nicht am Ende ist
  1718. if (versionInfo && versionInfo.parentElement === container) {
  1719. const allElements = Array.from(container.children);
  1720. const versionIndex = allElements.indexOf(versionInfo);
  1721. const warningIndex = warning ? allElements.indexOf(warning) : -1;
  1722.  
  1723. // Nur neu positionieren wenn Version-Info nicht an vorletzter Stelle ist
  1724. // (oder letzter Stelle wenn keine Warnung da ist)
  1725. const shouldBeAtIndex = warning ? allElements.length - 2 : allElements.length - 1;
  1726.  
  1727. if (versionIndex !== shouldBeAtIndex) {
  1728. versionInfo.parentElement.removeChild(versionInfo);
  1729. if (warning && warning.parentElement === container) {
  1730. container.insertBefore(versionInfo, warning);
  1731. } else {
  1732. container.appendChild(versionInfo);
  1733. }
  1734. }
  1735. }
  1736.  
  1737. // Prüfen ob Warnung nicht am Ende ist
  1738. if (warning && warning.parentElement === container) {
  1739. const allElements = Array.from(container.children);
  1740. const warningIndex = allElements.indexOf(warning);
  1741.  
  1742. // Warnung sollte immer ganz am Ende sein
  1743. if (warningIndex !== allElements.length - 1) {
  1744. warning.parentElement.removeChild(warning);
  1745. container.appendChild(warning);
  1746. }
  1747. }
  1748.  
  1749. // Gespeicherte Desktop-Positionen löschen
  1750. try {
  1751. localStorage.removeItem('wme-ptsm-drag-positions');
  1752. } catch (e) {
  1753. console.log('PTSM: localStorage not available');
  1754. }
  1755. }
  1756.  
  1757. // Drag & Drop Funktionalität neu initialisieren
  1758. setTimeout(() => {
  1759. enableDragAndDrop();
  1760. // Event für Cursor-Update senden
  1761. document.dispatchEvent(new Event('ptsm-settings-changed'));
  1762. }, 100);
  1763. }
  1764.  
  1765. console.log('PTSM DACH v' + ptsmVersion + ' loaded successfully');
  1766.  
  1767. // Update-Popup anzeigen (einmalig pro Version)
  1768. setTimeout(showUpdatePopup, 1000);
  1769.  
  1770. // Gespeicherte Kategorie-Reihenfolge wiederherstellen
  1771. setTimeout(applyCategoryOrder, 100);
  1772.  
  1773. // Drag & Drop aktivieren
  1774. setTimeout(enableDragAndDrop, 500);
  1775.  
  1776. // Initiale Einstellungen anwenden
  1777. setTimeout(() => {
  1778. const settings = loadSettings();
  1779. if (settings.freePosition) {
  1780. toggleFreePosition(true);
  1781. }
  1782. }, 1000);
  1783. }
  1784.  
  1785. // Initialize when page loads
  1786. if (document.readyState === 'loading') {
  1787. document.addEventListener('DOMContentLoaded', addButtons);
  1788. } else {
  1789. setTimeout(addButtons, 1000);
  1790. }
  1791.  
  1792. // Also try to initialize when Waze editor loads
  1793. if (typeof W !== 'undefined') {
  1794. if (W.loginManager && W.loginManager.events) {
  1795. W.loginManager.events.register('loginStateChanged', null, addButtons);
  1796. }
  1797. setTimeout(addButtons, 2000);
  1798. } else {
  1799. // Wait for Waze object to be available
  1800. var checkForWaze = setInterval(function() {
  1801. if (typeof W !== 'undefined' && W.loginManager) {
  1802. clearInterval(checkForWaze);
  1803. setTimeout(addButtons, 1000);
  1804. }
  1805. }, 500);
  1806. }