您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Kreise im WME erstellen - Updated for new Waze API
// ==UserScript== // @name WME Circles // @namespace http://tampermonkey.net/ // @version 2025.07.19 // @description Kreise im WME erstellen - Updated for new Waze API // @namespace https://greasyfork.org/de/users/863740-horst-wittlich // @author Hiwi234 // @match https://*.waze.com/editor* // @match https://*.waze.com/*/editor* // @match https://beta.waze.com/editor* // @match https://beta.waze.com/*/editor* // @icon https://www.google.com/s2/favicons?sz=64&domain=waze.com // @grant none // @license MIT // @run-at document-idle // ==/UserScript== (function() { 'use strict'; let uOpenLayers; let uWaze; let radiusLayer, infoLayer; let polygonControl, freehandControl; function initializeScript() { uWaze = window.W; uOpenLayers = window.OpenLayers; if (!uWaze || !uOpenLayers || !uWaze.map) { console.log('[WME Circles] WME objects not ready, retrying...'); setTimeout(initializeScript, 500); return; } console.log('[WME Circles] Initializing...'); radiusInit(); } function addSidePanel() { try { // Use new W.userscripts API const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-circles"); tabLabel.innerText = 'Circles'; tabLabel.title = 'WME Circles'; setupSidebarContent(tabPane); } catch (error) { console.log('[WME Circles] New API failed, using fallback:', error); addSidePanelFallback(); } } function addSidePanelFallback() { // Fallback to manual DOM manipulation let userTabs = document.getElementById('user-info'); if (!userTabs) { setTimeout(addSidePanelFallback, 1000); return; } let navTabs = userTabs.getElementsByClassName('nav-tabs')[0]; let tabContent = userTabs.getElementsByClassName('tab-content')[0]; if (!navTabs || !tabContent) { setTimeout(addSidePanelFallback, 1000); return; } let tab = document.createElement('li'); tab.innerHTML = '<a href="#sidepanel-wme-circles" data-toggle="tab">Circles</a>'; navTabs.appendChild(tab); let tabPane = document.createElement('section'); tabPane.id = "sidepanel-wme-circles"; tabPane.className = "tab-pane"; tabContent.appendChild(tabPane); setupSidebarContent(tabPane); } function setupSidebarContent(tabPane) { tabPane.innerHTML = ` <div style="padding: 15px;"> <h3>WME Circles v2025</h3> <p id="circles-current-radius"></p> <div style="margin: 15px 0;"> <label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 8px;"> <input type="checkbox" id="wme-circles-edit-mode" style="margin-right: 8px;"> Enable Circle Drawing </label> <label style="display: flex; align-items: center; cursor: pointer;"> <input type="checkbox" id="wme-freehand-edit-mode" style="margin-right: 8px;"> Enable Free Hand Drawing </label> </div> <div style="display: flex; flex-direction: column; gap: 10px; margin-top: 20px;"> <button id="wme-circles-select" style="width: 100%; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;"> Select Streets within </button> <button id="wme-circles-clear" style="width: 100%; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;"> Clear Circles </button> </div> <div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;"> <strong>Display Options:</strong> <div style="margin-top: 12px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;"> <span>Show Radius</span> <label class="toggle-switch"> <input type="checkbox" id="show-radius" checked> <span class="slider"></span> </label> </div> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;"> <span>Show Diameter</span> <label class="toggle-switch"> <input type="checkbox" id="show-diameter"> <span class="slider"></span> </label> </div> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;"> <span>Show Area</span> <label class="toggle-switch"> <input type="checkbox" id="show-area"> <span class="slider"></span> </label> </div> <div style="display: flex; justify-content: space-between; align-items: center;"> <span>Imperial Units (ft/mi)</span> <label class="toggle-switch"> <input type="checkbox" id="use-imperial"> <span class="slider"></span> </label> </div> </div> </div> <div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;"> <strong>Filter Options:</strong> <div style="margin-top: 12px;"> <div style="display: flex; justify-content: space-between; align-items: center;"> <span>Only drivable segments</span> <label class="toggle-switch"> <input type="checkbox" id="filter-drivable-only" checked> <span class="slider"></span> </label> </div> </div> </div> <div style="margin-top: 20px; padding: 10px; background: #e8f5e8; border-radius: 4px;"> <strong>Perimeter Blocking:</strong> <div style="margin-top: 12px;"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div> <div>Block perimeter inbound only</div> <div style="font-size: 11px; color: #666;">(hollow circle - exit allowed)</div> </div> <label class="toggle-switch"> <input type="checkbox" id="block-inbound-only"> <span class="slider"></span> </label> </div> </div> </div> <style> .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 24px; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } input:checked + .slider { background-color: #4CAF50; } input:checked + .slider:before { transform: translateX(20px); } .slider:hover { box-shadow: 0 0 8px rgba(76, 175, 80, 0.3); } </style> </div> `; // Get UI elements let checkbox = tabPane.querySelector('#wme-circles-edit-mode'); let freehandCheckbox = tabPane.querySelector('#wme-freehand-edit-mode'); let clearButton = tabPane.querySelector('#wme-circles-clear'); let selectButton = tabPane.querySelector('#wme-circles-select'); let showRadiusCheckbox = tabPane.querySelector('#show-radius'); let showDiameterCheckbox = tabPane.querySelector('#show-diameter'); let showAreaCheckbox = tabPane.querySelector('#show-area'); let useImperialCheckbox = tabPane.querySelector('#use-imperial'); let filterDrivableCheckbox = tabPane.querySelector('#filter-drivable-only'); let blockInboundCheckbox = tabPane.querySelector('#block-inbound-only'); // Formatting functions function formatLength(meters, useImperial = false) { if (useImperial) { let feet = meters * 3.28084; if (feet >= 5280) { let miles = feet / 5280; return `${Math.round(miles * 100) / 100} mi`; } else { return `${Math.round(feet)} ft`; } } else { if (meters > 1000) { return `${Math.round((meters / 1000) * 100) / 100} km`; } else { return `${Math.round(meters)} m`; } } } function formatArea(squareMeters, useImperial = false) { if (useImperial) { let squareFeet = squareMeters * 10.7639; if (squareFeet >= 27878400) { // 1 square mile let squareMiles = squareFeet / 27878400; return `${Math.round(squareMiles * 100) / 100} mi²`; } else if (squareFeet >= 43560) { // 1 acre let acres = squareFeet / 43560; return `${Math.round(acres * 100) / 100} ac`; } else { return `${Math.round(squareFeet)} ft²`; } } else { if (squareMeters > 1000000) { return `${Math.round((squareMeters / 1000000) * 100) / 100} km²`; } else { return `${Math.round(squareMeters)} m²`; } } } function updateAllAnnotations() { infoLayer.destroyFeatures(); for (let feature of radiusLayer.features) { if (feature.geometry.CLASS_NAME === "OpenLayers.Geometry.Polygon") { let components = feature.geometry.components[0].components; if (components.length > 90) { addCircleAnnotations(feature); } else { addFreehandAnnotations(feature); } } } } function addCircleAnnotations(f) { let minX = f.geometry.bounds.left; let minY = f.geometry.bounds.bottom; let maxX = f.geometry.bounds.right; let maxY = f.geometry.bounds.top; let startX = (minX + maxX) / 2; let startY = (minY + maxY) / 2; let startPoint = new uOpenLayers.Geometry.Point(startX, startY); let endPoint = new uOpenLayers.Geometry.Point(maxX, startY); let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]); let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913")); let area = Math.PI * len * len; let showRadius = showRadiusCheckbox?.checked; let showDiameter = showDiameterCheckbox?.checked; let showArea = showAreaCheckbox?.checked; let useImperial = useImperialCheckbox?.checked; let centerStyle = { strokeColor: "#c40606", strokeWidth: 2, pointRadius: 5, fillOpacity: 0.2 }; let labelText = []; if (showRadius) { labelText.push('R: ' + formatLength(len, useImperial)); } if (showDiameter) { labelText.push('Ø: ' + formatLength(len * 2, useImperial)); } if (showArea) { labelText.push('A: ' + formatArea(area, useImperial)); } let lineStyle = { strokeColor: "#c40606", strokeWidth: 3, label: labelText.join(' | '), labelAlign: "left", labelXOffset: "20", labelYOffset: "10", labelOutlineColor: "white", labelOutlineWidth: 3 }; let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle); if (labelText.length > 0) { let radiusLine = new uOpenLayers.Feature.Vector(radius, { 'length': len }, lineStyle); infoLayer.addFeatures([center, radiusLine]); } else { infoLayer.addFeatures([center]); } } function addFreehandAnnotations(f) { let bounds = f.geometry.getBounds(); let minX = bounds.left; let minY = bounds.bottom; let maxX = bounds.right; let maxY = bounds.top; let centerX = (minX + maxX) / 2; let centerY = (minY + maxY) / 2; let width = maxX - minX; let height = maxY - minY; let diameter = Math.max(width, height); let centerPoint = new uOpenLayers.Geometry.Point(centerX, centerY); let endPoint = new uOpenLayers.Geometry.Point(centerX + diameter/2, centerY); let diameterLine = new uOpenLayers.Geometry.LineString([centerPoint, endPoint]); let len = diameterLine.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913")); let area = f.geometry.getArea(); let showRadius = showRadiusCheckbox?.checked; let showDiameter = showDiameterCheckbox?.checked; let showArea = showAreaCheckbox?.checked; let useImperial = useImperialCheckbox?.checked; let centerStyle = { strokeColor: "#c40606", strokeWidth: 2, pointRadius: 5, fillOpacity: 0.2 }; let labelText = []; if (showRadius) { labelText.push('R: ' + formatLength(len / 2, useImperial)); } if (showDiameter) { labelText.push('Ø: ' + formatLength(len, useImperial)); } if (showArea) { labelText.push('A: ' + formatArea(area, useImperial)); } let lineStyle = { strokeColor: "#c40606", strokeWidth: 3, label: labelText.join(' | '), labelAlign: "left", labelXOffset: "20", labelYOffset: "10", labelOutlineColor: "white", labelOutlineWidth: 3 }; let center = new uOpenLayers.Feature.Vector(centerPoint, {}, centerStyle); if (labelText.length > 0) { let diameterLineFeature = new uOpenLayers.Feature.Vector(diameterLine, { 'diameter': len }, lineStyle); infoLayer.addFeatures([center, diameterLineFeature]); } else { infoLayer.addFeatures([center]); } } // Event handlers showRadiusCheckbox.addEventListener('change', updateAllAnnotations); showDiameterCheckbox.addEventListener('change', updateAllAnnotations); showAreaCheckbox.addEventListener('change', updateAllAnnotations); useImperialCheckbox.addEventListener('change', updateAllAnnotations); checkbox.addEventListener('click', (e) => { if (e.target.checked) { freehandCheckbox.checked = false; freehandControl.deactivate(); polygonControl.activate(); } else { polygonControl.deactivate(); } }); freehandCheckbox.addEventListener('click', (e) => { if (e.target.checked) { checkbox.checked = false; polygonControl.deactivate(); freehandControl.activate(); } else { freehandControl.deactivate(); } }); clearButton.addEventListener('click', (e) => { infoLayer.destroyFeatures(); radiusLayer.destroyFeatures(); checkbox.checked = false; freehandCheckbox.checked = false; polygonControl.deactivate(); freehandControl.deactivate(); }); selectButton.addEventListener('click', (e) => { const toSelect = []; const segments = uWaze.model.segments.getObjectArray(); const blockInboundOnly = blockInboundCheckbox?.checked; if (blockInboundOnly) { // Find perimeter segments only - create hollow circle console.log('[WME Circles] Finding perimeter segments for hollow circle blocking...'); for (const drawnFeature of radiusLayer.features) { const circleGeometry = drawnFeature.geometry; const bounds = circleGeometry.getBounds(); const circleCenter = { x: (bounds.left + bounds.right) / 2, y: (bounds.bottom + bounds.top) / 2 }; // Calculate circle radius for perimeter detection const radius = Math.abs(bounds.right - bounds.left) / 2; const perimeterTolerance = radius * 0.15; // 15% tolerance for perimeter detection for (const segment of segments) { if (!segment.attributes?.roadType) continue; if (filterDrivableCheckbox.checked) { const type = segment.attributes.roadType; if (type < 1 || type > 6) continue; } const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry; if (!segGeom) continue; // Check if segment intersects with circle if (circleGeometry.intersects(segGeom)) { // Calculate segment's distance from circle center const coords = segGeom.components || segGeom.getVertices(); if (!coords || coords.length < 2) continue; // Check multiple points along segment let isPerimeterSegment = false; const checkPoints = Math.min(coords.length, 5); for (let i = 0; i < checkPoints; i++) { const point = coords[Math.floor(i * (coords.length - 1) / (checkPoints - 1))]; const distFromCenter = Math.sqrt( Math.pow(point.x - circleCenter.x, 2) + Math.pow(point.y - circleCenter.y, 2) ); // Check if point is near the perimeter (within tolerance of radius) if (Math.abs(distFromCenter - radius) <= perimeterTolerance) { isPerimeterSegment = true; break; } } if (isPerimeterSegment) { if (shouldBlockSegmentInbound(segment, segGeom, circleCenter)) { toSelect.push(segment); } } } } } } else { // Original logic - all segments within circle for (const segment of segments) { if (!segment.attributes?.roadType) continue; if (filterDrivableCheckbox.checked) { const type = segment.attributes.roadType; if (type < 1 || type > 6) continue; } const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry; if (!segGeom) continue; let circleCenter = null; let isInCircle = false; for (const drawnFeature of radiusLayer.features) { if (drawnFeature.geometry.intersects(segGeom)) { isInCircle = true; const bounds = drawnFeature.geometry.getBounds(); circleCenter = { x: (bounds.left + bounds.right) / 2, y: (bounds.bottom + bounds.top) / 2 }; break; } } if (isInCircle) { toSelect.push(segment); } } } console.log(`[WME Circles] Selected ${toSelect.length} segments${blockInboundOnly ? ' (perimeter only)' : ''}`); if (toSelect.length > 0) { if (blockInboundOnly) { // Apply direction restrictions to perimeter segments applyDirectionRestrictions(toSelect); } else { // Regular selection uWaze.selectionManager.setSelectedModels(toSelect); } } checkbox.checked = false; freehandCheckbox.checked = false; polygonControl.deactivate(); freehandControl.deactivate(); }); function shouldBlockSegmentInbound(segment, segGeom, circleCenter) { const coords = segGeom.components || segGeom.getVertices(); if (!coords || coords.length < 2) { console.log(`[WME Circles] No valid coordinates for segment ${segment.getID()}`); return false; } const startPoint = coords[0]; const endPoint = coords[coords.length - 1]; // Calculate which end is closer to circle center const startDist = Math.sqrt( Math.pow(startPoint.x - circleCenter.x, 2) + Math.pow(startPoint.y - circleCenter.y, 2) ); const endDist = Math.sqrt( Math.pow(endPoint.x - circleCenter.x, 2) + Math.pow(endPoint.y - circleCenter.y, 2) ); segment._circleDirectionInfo = { startPoint: startPoint, endPoint: endPoint, startDist: startDist, endDist: endDist, circleCenter: circleCenter }; console.log(`[WME Circles] Set direction info for segment ${segment.getID()}: startDist=${Math.round(startDist)}, endDist=${Math.round(endDist)}`); return true; } function applyDirectionRestrictions(segments) { console.log(`[WME Circles] Applying direction restrictions to ${segments.length} segments`); // First select the segments uWaze.selectionManager.setSelectedModels(segments); // Add delay to ensure selection is processed setTimeout(() => { let successCount = 0; let modifiedCount = 0; // Apply restrictions to each segment for (const segment of segments) { try { const dirInfo = segment._circleDirectionInfo; if (!dirInfo) { console.log(`[WME Circles] No direction info for segment ${segment.getID()}, skipping`); continue; } const currentFwd = segment.attributes.fwdDirection; const currentRev = segment.attributes.revDirection; // Only modify bidirectional segments if (!currentFwd || !currentRev) { console.log(`[WME Circles] Segment ${segment.getID()}: Already one-way, skipping`); continue; } // FIXED: Simplified direction logic // If start is closer to center: Block REVERSE (B->A), Keep FORWARD (A->B) // If end is closer to center: Block FORWARD (A->B), Keep REVERSE (B->A) let newFwd = currentFwd; let newRev = currentRev; if (dirInfo.startDist < dirInfo.endDist) { // A is closer to center than B // Traffic A->B goes TO center (BLOCK) // Traffic B->A goes AWAY from center (KEEP) newFwd = false; // Block A->B (inbound) newRev = true; // Keep B->A (outbound) console.log(`[WME Circles] Segment ${segment.getID()}: A closer to center - blocking A->B (inbound), keeping B->A (outbound)`); } else { // B is closer to center than A // Traffic A->B goes AWAY from center (KEEP) // Traffic B->A goes TO center (BLOCK) newFwd = true; // Keep A->B (outbound) newRev = false; // Block B->A (inbound) console.log(`[WME Circles] Segment ${segment.getID()}: B closer to center - keeping A->B (outbound), blocking B->A (inbound)`); } // Only apply if there's a change needed if (newFwd !== currentFwd || newRev !== currentRev) { try { // Check if user has edit permissions for this segment if (!segment.arePropertiesEditable || !segment.arePropertiesEditable()) { console.warn(`[WME Circles] No edit permissions for segment ${segment.getID()}`); continue; } // Direct model update using WME's internal methods const modelSegment = W.model.segments.getObjectById(segment.getID()); if (modelSegment) { // Set attributes directly and mark as modified modelSegment.attributes.fwdDirection = newFwd; modelSegment.attributes.revDirection = newRev; // Mark segment as modified for save system modelSegment._hasUnsavedAttributes = true; if (modelSegment.changed) { modelSegment.changed.fwdDirection = true; modelSegment.changed.revDirection = true; } else { modelSegment.changed = { fwdDirection: true, revDirection: true }; } // Trigger UI update modelSegment.trigger('change:fwdDirection'); modelSegment.trigger('change:revDirection'); modelSegment.trigger('change'); // Let WME handle map updates naturally modifiedCount++; console.log(`[WME Circles] ✓ Modified segment ${segment.getID()}: fwd=${newFwd}, rev=${newRev}`); } else { console.warn(`[WME Circles] Model segment not found: ${segment.getID()}`); } } catch (error) { console.warn(`[WME Circles] Failed to modify segment ${segment.getID()}:`, error); } } successCount++; // Clean up temporary data delete segment._circleDirectionInfo; } catch (error) { console.warn('[WME Circles] Error processing segment:', error); } } console.log(`[WME Circles] Processed ${successCount}/${segments.length} segments, modified ${modifiedCount} segments`); // Show user feedback const statusElement = document.getElementById('circles-current-radius'); if (statusElement) { if (modifiedCount > 0) { statusElement.innerHTML = `✓ Applied direction restrictions to ${modifiedCount} segments (outbound traffic preserved)`; statusElement.style.color = '#4CAF50'; } else { statusElement.innerHTML = `⚠ No segments needed modification (${successCount} processed)`; statusElement.style.color = '#ff9800'; } // Clear message after 4 seconds setTimeout(() => { statusElement.innerHTML = ''; statusElement.style.color = ''; }, 4000); } // Force map refresh without causing errors try { // Only trigger minimal updates if (W.model && W.model.segments) { W.model.segments.trigger('change'); } } catch (e) { // Ignore render errors } }, 500); } } function radiusInit() { radiusLayer = new uOpenLayers.Layer.Vector("WME Circle Control Layer", { displayInLayerSwitcher: true, uniqueName: "__CircleControlLayer", visibility: true, style: { "fillColor": "#c40606", "fillOpacity": 0.2, "strokeColor": "#c40606", "strokeOpacity": 1, "strokeWidth": 1, "strokeLinecap": "round", "strokeDashstyle": "solid", "pointRadius": 6, "pointerEvents": "visiblePainted", "labelAlign": "cm", "labelOutlineColor": "white", "labelOutlineWidth": 3 } }); infoLayer = new uOpenLayers.Layer.Vector("WME Circle Visual Layer", { displayInLayerSwitcher: true, uniqueName: "__DrawCircleDisplayLayer", visibility: true }); let polygonHandler = uOpenLayers.Handler.RegularPolygon; polygonControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandler, { handlerOptions: { sides: 100 } }); let polygonHandlerFreehand = uOpenLayers.Handler.Polygon; freehandControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandlerFreehand, { handlerOptions: { freehand: true, freehandToggle: null } }); uWaze.map.addLayer(radiusLayer); uWaze.map.addLayer(infoLayer); uWaze.map.addControl(polygonControl); uWaze.map.addControl(freehandControl); addSidePanel(); polygonControl.handler.callbacks.move = function (e) { let linearRing = new uOpenLayers.Geometry.LinearRing(e.components[0].components); let geometry = new uOpenLayers.Geometry.Polygon([linearRing]); let polygonFeature = new uOpenLayers.Feature.Vector(geometry, null); let polybounds = polygonFeature.geometry.getBounds(); let minX = polybounds.left; let minY = polybounds.bottom; let maxX = polybounds.right; let maxY = polybounds.top; let startX = (minX + maxX) / 2; let startY = (minY + maxY) / 2; let startPoint = new uOpenLayers.Geometry.Point(startX, startY); let endPoint = new uOpenLayers.Geometry.Point(maxX, startY); let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]); let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913")); let unit = 'm'; if (len > 1000) { len = Math.round((len / 1000) * 100) / 100; unit = "km"; } else { len = Math.round(len); } let rad = document.getElementById('circles-current-radius'); if (rad) { rad.innerHTML = 'Current Radius: ' + len + ' ' + unit; } infoLayer.destroyFeatures(); let centerStyle = { strokeColor: "#ff9800", strokeWidth: 2, pointRadius: 3, fillOpacity: 0.3 }; let lineStyle = { strokeColor: "#ff9800", strokeWidth: 2, label: len + ' ' + unit, labelAlign: "left", labelXOffset: "15", labelYOffset: "5", labelOutlineColor: "white", labelOutlineWidth: 2, strokeDashstyle: "dash" }; let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle); let radiusLine = new uOpenLayers.Feature.Vector(radius, {}, lineStyle); infoLayer.addFeatures([center, radiusLine]); } polygonControl.events.on({ 'featureadded': function (e) { let rad = document.getElementById('circles-current-radius'); if (rad) { rad.innerHTML = ''; } setTimeout(() => { // Trigger update from within tabPane scope const event = new CustomEvent('updateAnnotations'); document.dispatchEvent(event); }, 100); } }); freehandControl.events.on({ 'featureadded': function (e) { let rad = document.getElementById('circles-current-radius'); if (rad) { rad.innerHTML = ''; } setTimeout(() => { // Trigger update from within tabPane scope const event = new CustomEvent('updateAnnotations'); document.dispatchEvent(event); }, 100); } }); // Listen for custom update events document.addEventListener('updateAnnotations', () => { // This will call the updateAllAnnotations function from the sidebar scope const updateEvent = new CustomEvent('forceUpdate'); document.dispatchEvent(updateEvent); }); } // Use the new W.userscripts.state API if (W?.userscripts?.state?.isReady) { console.log('[WME Circles] WME already ready, initializing...'); initializeScript(); } else { console.log('[WME Circles] Waiting for WME ready event...'); document.addEventListener("wme-ready", initializeScript, { once: true }); } })();