WME Level Highlighter

Level highlighter for Waze Map Editor

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WME Level Highlighter
// @namespace    https://greasyfork.org/de/users/863740-horst-wittlich
// @version      2025.07.22
// @description  Level highlighter for Waze Map Editor
// @icon         https://i.ibb.co/ckSvk59/waze-icon.png
// @author       Assistant
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @include      https://beta.waze.com/*
// @exclude      https://www.waze.com/user/editor*
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';
    
    console.log("=== WME Level Highlighter LOADING ===");
    
    var SCRIPT_VERSION = "2025.07.22";
    var highlightedSegmentIds = [];
    var foundSegmentsList = [];
    var autoRefreshInterval = null;
    var autoRefreshEnabled = false;
    
    var HIGHLIGHT_ORANGE = "#ff5000";
    var HIGHLIGHT_MAGENTA = "#ff00dd";
    var HIGHLIGHT_OPACITY = 0.7;
    var levelIcon = '🔎';

    // Local storage keys
    var STORAGE_KEYS = {
        selectedLevel: 'wme_level_highlighter_level',
        daysOld: 'wme_level_highlighter_days',
        colorMode: 'wme_level_highlighter_color_mode',
        customColor: 'wme_level_highlighter_custom_color',
        autoRefresh: 'wme_level_highlighter_auto_refresh'
    }

    function toggleAutoRefresh() {
        var autoRefreshCheckbox = getId('_cbAutoRefresh');
        if (!autoRefreshCheckbox) return;

        autoRefreshEnabled = autoRefreshCheckbox.checked;
        saveToLocalStorage(STORAGE_KEYS.autoRefresh, autoRefreshEnabled.toString());

        if (autoRefreshEnabled) {
            console.log("Auto-refresh enabled (5 seconds)");
            autoRefreshInterval = setInterval(function() {
                console.log("Auto-refresh: Running highlighter...");
                runHighlighter();
            }, 5000); // 5 seconds
            
            // Update button text
            var runButton = getId('_btnRunHighlighter');
            if (runButton) {
                runButton.style.background = "#FF9800";
                runButton.textContent = "AUTO REFRESH ON";
            }
        } else {
            console.log("Auto-refresh disabled");
            if (autoRefreshInterval) {
                clearInterval(autoRefreshInterval);
                autoRefreshInterval = null;
            }
            
            // Reset button text
            var runButton = getId('_btnRunHighlighter');
            if (runButton) {
                runButton.style.background = "#4CAF50";
                runButton.textContent = "RUN HIGHLIGHTER";
            }
        }
    };

    // Local storage functions
    function saveToLocalStorage(key, value) {
        try {
            localStorage.setItem(key, value);
            console.log("Saved:", key, "=", value);
        } catch (e) {
            console.log("Could not save to localStorage:", e);
        }
    }

    function loadFromLocalStorage(key, defaultValue) {
        try {
            var value = localStorage.getItem(key);
            console.log("Loaded:", key, "=", value || defaultValue);
            return value !== null ? value : defaultValue;
        } catch (e) {
            console.log("Could not load from localStorage:", e);
            return defaultValue;
        }
    }

    function getId(node) {
        return document.getElementById(node);
    }

    function getColorMode() {
        var orange = getId('_rbHilightOrange');
        var magenta = getId('_rbHilightMagenta');
        var custom = getId('_rbHilightCustom');
        
        if (orange && orange.checked) return 'orange';
        if (magenta && magenta.checked) return 'magenta';
        if (custom && custom.checked) return 'custom';
        return 'orange';
    }

    function getHighlightColor(mode, customColor) {
        if (mode === 'orange') return HIGHLIGHT_ORANGE;
        if (mode === 'magenta') return HIGHLIGHT_MAGENTA;
        if (mode === 'custom') return customColor;
        return HIGHLIGHT_ORANGE;
    }

    function updateCustomColorVisibility() {
        var customRadio = getId('_rbHilightCustom');
        var colorPicker = getId('_customColorPicker');
        if (customRadio && colorPicker) {
            colorPicker.style.display = customRadio.checked ? 'inline-block' : 'none';
        }
    }

    function resetAllHighlights() {
        console.log("=== RESETTING ALL HIGHLIGHTS ===");
        
        if (!window.W || !window.W.model || !window.W.model.segments) return;

        var resetCount = 0;
        for (var seg in W.model.segments.objects) {
            var segment = W.model.segments.getObjectById(seg);
            if (!segment || !segment.attributes) continue;
            
            try {
                var line = W.userscripts.getFeatureElementByDataModel(segment);
                if (line) {
                    line.removeAttribute("stroke");
                    line.removeAttribute("stroke-opacity");
                    line.removeAttribute("stroke-width");
                    line.removeAttribute("stroke-dasharray");
                    resetCount++;
                }
            } catch (e) {}
        }
        
        console.log("Reset highlights on", resetCount, "segments");
        highlightedSegmentIds = [];
        foundSegmentsList = [];
        updateEditorsDisplay();
    }

    function selectSegmentById(segmentId) {
        if (!window.W || !window.W.model || !window.W.model.segments || !window.W.selectionManager) return;

        try {
            var segment = W.model.segments.getObjectById(segmentId);
            if (segment) {
                W.selectionManager.unselectAll();
                W.selectionManager.setSelectedModels([segment]);
                
                // Try to zoom to segment using modern APIs
                setTimeout(function() {
                    try {
                        // Method 1: Try WME's built-in zoom to selection (modern API)
                        if (W.selectionManager.getSelectedWMEFeatures && W.selectionManager.getSelectedWMEFeatures().length > 0) {
                            if (W.map && W.map.zoomToSelection) {
                                W.map.zoomToSelection();
                                return;
                            }
                        }
                        
                        // Method 2: Try using modern geometry API
                        if (segment.getOLGeometry) {
                            var olGeometry = segment.getOLGeometry();
                            if (olGeometry && olGeometry.getExtent) {
                                var extent = olGeometry.getExtent();
                                var olMap = W.map.getOLMap();
                                if (olMap && olMap.getView) {
                                    var view = olMap.getView();
                                    if (view.fit) {
                                        view.fit(extent, {
                                            padding: [100, 100, 100, 100],
                                            maxZoom: 6,
                                            duration: 500
                                        });
                                        return;
                                    }
                                }
                            }
                        }
                        
                        // Method 3: Try using OpenLayers center
                        if (segment.getOLGeometry) {
                            var olGeometry = segment.getOLGeometry();
                            if (olGeometry && olGeometry.getCoordinates) {
                                var coords = olGeometry.getCoordinates();
                                if (coords && coords.length > 0) {
                                    var centerCoord = Array.isArray(coords[0]) ? coords[Math.floor(coords.length/2)] : coords;
                                    if (centerCoord && centerCoord.length >= 2) {
                                        var olMap = W.map.getOLMap();
                                        if (olMap && olMap.getView) {
                                            var view = olMap.getView();
                                            view.setCenter([centerCoord[0], centerCoord[1]]);
                                            view.setZoom(Math.max(view.getZoom(), 5));
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                        
                        // Method 4: Fallback - try legacy geometry (only if modern fails)
                        if (segment.geometry && segment.geometry.getCentroid) {
                            var centroid = segment.geometry.getCentroid();
                            if (centroid && centroid.x && centroid.y && W.map.setCenter) {
                                W.map.setCenter(new OpenLayers.LonLat(centroid.x, centroid.y));
                                if (W.map.getZoom() < 5) {
                                    W.map.zoomTo(5);
                                }
                            }
                        }
                        
                        console.log("Successfully zoomed to segment:", segmentId);
                        
                    } catch (zoomError) {
                        console.log("Zoom failed for segment", segmentId, ":", zoomError);
                    }
                }, 100);
            }
        } catch (error) {
            console.error("Error selecting segment:", error);
        }
    }

    function selectSegmentsByEditor(editorName) {
        if (!window.W || !window.W.model || !window.W.model.segments || !window.W.selectionManager) return;

        try {
            var editorSegments = [];
            for (var i = 0; i < foundSegmentsList.length; i++) {
                if (foundSegmentsList[i].editorName === editorName) {
                    editorSegments.push(foundSegmentsList[i]);
                }
            }
            
            if (editorSegments.length === 0) return;
            
            W.selectionManager.unselectAll();
            
            var segmentsToSelect = [];
            for (var j = 0; j < editorSegments.length; j++) {
                var segment = W.model.segments.getObjectById(editorSegments[j].segmentId);
                if (segment) {
                    segmentsToSelect.push(segment);
                }
            }
            
            if (segmentsToSelect.length > 0) {
                W.selectionManager.setSelectedModels(segmentsToSelect);
            }
            
        } catch (error) {
            console.error("Error selecting segments by editor:", error);
        }
    }

    function updateEditorsDisplay() {
        var editorsElement = getId('_editorsDisplay');
        if (!editorsElement) return;

        while (editorsElement.firstChild) {
            editorsElement.removeChild(editorsElement.firstChild);
        }

        if (foundSegmentsList.length === 0) {
            return;
        }

        var editorGroups = {};
        for (var i = 0; i < foundSegmentsList.length; i++) {
            var segInfo = foundSegmentsList[i];
            if (!editorGroups[segInfo.editorName]) {
                editorGroups[segInfo.editorName] = [];
            }
            editorGroups[segInfo.editorName].push(segInfo);
        }

        var containerDiv = document.createElement('div');
        containerDiv.style.maxHeight = '200px';
        containerDiv.style.overflowY = 'auto';
        containerDiv.style.border = '1px solid #ddd';
        containerDiv.style.padding = '10px';
        containerDiv.style.background = '#f9f9f9';
        containerDiv.style.borderRadius = '4px';
        containerDiv.style.marginBottom = '15px';
        
        var editorNames = Object.keys(editorGroups);
        for (var k = 0; k < editorNames.length; k++) {
            var editorName = editorNames[k];
            var segments = editorGroups[editorName];
            
            var editorDiv = document.createElement('div');
            editorDiv.style.marginBottom = '15px';
            
            var editorNameSpan = document.createElement('span');
            editorNameSpan.style.color = '#2196F3';
            editorNameSpan.style.cursor = 'pointer';
            editorNameSpan.style.textDecoration = 'underline';
            editorNameSpan.style.fontWeight = 'bold';
            editorNameSpan.style.fontSize = '14px';
            editorNameSpan.textContent = editorName;
            editorNameSpan.title = 'Click to select all segments by ' + editorName;
            
            (function(name) {
                editorNameSpan.addEventListener('click', function() {
                    selectSegmentsByEditor(name);
                });
            })(editorName);
            
            var segmentCountSpan = document.createElement('span');
            segmentCountSpan.style.color = '#666';
            segmentCountSpan.style.marginLeft = '8px';
            segmentCountSpan.textContent = '(' + segments.length + ' segments)';
            
            editorDiv.appendChild(editorNameSpan);
            editorDiv.appendChild(segmentCountSpan);
            editorDiv.appendChild(document.createElement('br'));
            
            var maxShow = 4;
            for (var m = 0; m < Math.min(maxShow, segments.length); m++) {
                var seg = segments[m];
                var editDate = new Date(seg.editDate);
                var daysAgo = Math.floor((new Date() - editDate) / 86400000);
                
                var segmentDiv = document.createElement('div');
                segmentDiv.style.color = '#888';
                segmentDiv.style.marginLeft = '15px';
                segmentDiv.style.marginTop = '3px';
                segmentDiv.style.fontSize = '12px';
                
                var idLabel = document.createTextNode('ID: ');
                segmentDiv.appendChild(idLabel);
                
                var segmentIdSpan = document.createElement('span');
                segmentIdSpan.style.color = '#2196F3';
                segmentIdSpan.style.cursor = 'pointer';
                segmentIdSpan.style.textDecoration = 'underline';
                segmentIdSpan.style.fontWeight = 'bold';
                segmentIdSpan.textContent = seg.segmentId;
                segmentIdSpan.title = 'Click to select segment ' + seg.segmentId;
                
                (function(segId) {
                    segmentIdSpan.addEventListener('click', function() {
                        selectSegmentById(segId);
                    });
                })(seg.segmentId);
                
                var daysLabel = document.createTextNode(' - ' + daysAgo + ' days ago');
                
                segmentDiv.appendChild(segmentIdSpan);
                segmentDiv.appendChild(daysLabel);
                editorDiv.appendChild(segmentDiv);
            }
            
            if (segments.length > maxShow) {
                var moreDiv = document.createElement('div');
                moreDiv.style.color = '#888';
                moreDiv.style.marginLeft = '15px';
                moreDiv.style.fontStyle = 'italic';
                moreDiv.style.fontSize = '12px';
                moreDiv.style.marginTop = '3px';
                moreDiv.textContent = '... and ' + (segments.length - maxShow) + ' more segments';
                editorDiv.appendChild(moreDiv);
            }
            
            containerDiv.appendChild(editorDiv);
        }
        
        editorsElement.appendChild(containerDiv);
    }

    function selectHighlightedSegments() {
        if (!window.W || !window.W.model || !window.W.model.segments || !window.W.selectionManager) return;

        if (highlightedSegmentIds.length === 0) {
            return;
        }

        try {
            W.selectionManager.unselectAll();
            
            var segmentsToSelect = [];
            for (var i = 0; i < highlightedSegmentIds.length; i++) {
                var segment = W.model.segments.getObjectById(highlightedSegmentIds[i]);
                if (segment) {
                    segmentsToSelect.push(segment);
                }
            }
            
            if (segmentsToSelect.length > 0) {
                W.selectionManager.setSelectedModels(segmentsToSelect);
            }
            
        } catch (error) {
            console.error("Error selecting segments:", error);
        }
    }

    function runHighlighter() {
        console.log("=== RUNNING HIGHLIGHTER ===");
        
        if (!window.W || !window.W.model || !window.W.model.segments) {
            console.log("W.model not ready, retrying...");
            setTimeout(runHighlighter, 2000);
            return;
        }

        resetAllHighlights();

        var selectedLevel = getId('_selectEditorLevel');
        var daysInput = getId('_txtDaysOld');
        var customColorPicker = getId('_customColorPicker');
        var opacitySlider = getId('_opacitySlider');

        var selectedDisplayLevel = selectedLevel ? parseInt(selectedLevel.value) || 1 : 1;
        var selectedInternalLevel = selectedDisplayLevel - 1;
        var daysOld = daysInput ? parseInt(daysInput.value) || 30 : 30;
        var colorMode = getColorMode();
        var customColor = customColorPicker ? customColorPicker.value : HIGHLIGHT_ORANGE;
        var opacity = opacitySlider ? parseFloat(opacitySlider.value) || 0.7 : 0.7;

        // Save current settings to localStorage
        if (selectedLevel) {
            saveToLocalStorage(STORAGE_KEYS.selectedLevel, selectedLevel.value);
        }
        if (daysInput) {
            saveToLocalStorage(STORAGE_KEYS.daysOld, daysInput.value);
        }
        saveToLocalStorage(STORAGE_KEYS.colorMode, colorMode);
        if (customColorPicker) {
            saveToLocalStorage(STORAGE_KEYS.customColor, customColorPicker.value);
        }
        if (opacitySlider) {
            saveToLocalStorage(STORAGE_KEYS.opacity, opacitySlider.value);
        }
        
        var autoRefreshCheckbox = getId('_cbAutoRefresh');
        if (autoRefreshCheckbox) {
            saveToLocalStorage(STORAGE_KEYS.autoRefresh, autoRefreshCheckbox.checked.toString());
        }

        console.log("Level:", selectedDisplayLevel, "Days:", daysOld);

        var totalSegments = 0;
        var matchingSegments = 0;
        var highlightedSegments = 0;
        var today = new Date();

        for (var seg in W.model.segments.objects) {
            var segment = W.model.segments.getObjectById(seg);
            if (!segment || !segment.attributes) continue;
            
            totalSegments++;
            var attributes = segment.attributes;
            var updatedBy = attributes.updatedBy || attributes.createdBy;
            
            if (!updatedBy) continue;
            
            var editDate = attributes.updatedOn || attributes.createdOn;
            var editDays = editDate ? (today.getTime() - editDate) / 86400000 : 9999;
            
            if (editDays > daysOld) continue;
            
            try {
                var user = W.model.users.getObjectById(parseInt(updatedBy));
                if (!user || !user.attributes || !user.attributes.userName || user.attributes.userName === 'Inactive User') continue;
                
                var userInternalLevel = user.attributes.rank;
                if (userInternalLevel !== selectedInternalLevel) continue;
                
                matchingSegments++;
                
                var segmentInfo = {
                    segmentId: seg,
                    editorName: user.attributes.userName,
                    editorLevel: userInternalLevel + 1,
                    editDate: editDate
                };
                foundSegmentsList.push(segmentInfo);
                
                try {
                    var line = W.userscripts.getFeatureElementByDataModel(segment);
                    if (line) {
                        var highlightColor = getHighlightColor(colorMode, customColor);
                        line.setAttribute("stroke", highlightColor);
                        line.setAttribute("stroke-opacity", HIGHLIGHT_OPACITY);
                        line.setAttribute("stroke-width", 8);
                        line.setAttribute("stroke-dasharray", "none");
                        highlightedSegments++;
                        highlightedSegmentIds.push(seg);
                    }
                } catch (e) {}
                
            } catch (userError) {
                console.warn("Error accessing user", updatedBy, userError);
            }
        }

        var counterElement = getId('_highlightCounter');
        if (counterElement) {
            counterElement.innerHTML = 
                '<strong>Found ' + matchingSegments + ' segments by Level ' + selectedDisplayLevel + ' editors</strong><br>' +
                '<strong>Highlighted ' + highlightedSegments + ' segments</strong><br>' +
                '<span style="color: #888;">(' + totalSegments + ' total segments checked)</span>';
        }

        updateEditorsDisplay();

        console.log("Found:", matchingSegments, "Highlighted:", highlightedSegments);
    }

    function createUI() {
        console.log("=== CREATING UI ===");
        
        var userTabs = getId('user-info');
        if (!userTabs) {
            setTimeout(createUI, 1000);
            return;
        }
        
        var navTabs = userTabs.querySelector('.nav-tabs');
        var tabContentContainer = userTabs.querySelector('.tab-content');
        
        if (!navTabs || !tabContentContainer) {
            setTimeout(createUI, 1000);
            return;
        }
        
        var newtab = document.createElement('li');
        var tabLink = document.createElement('a');
        tabLink.title = "Level Highlighter";
        tabLink.href = "#levelPanel";
        tabLink.setAttribute('data-toggle', 'tab');
        tabLink.textContent = "LH " + levelIcon;
        newtab.appendChild(tabLink);
        navTabs.appendChild(newtab);
        
        var tabPane = document.createElement('div');
        tabPane.id = "levelPanel";
        tabPane.className = "tab-pane";
        tabContentContainer.appendChild(tabPane);

        var section = document.createElement('div');
        section.style.padding = "15px";
        section.style.fontFamily = "Arial, sans-serif";
        
        // Title
        var titleDiv = document.createElement('div');
        titleDiv.style.marginBottom = "20px";
        titleDiv.innerHTML = '<h3 style="margin: 0; color: #333;">' + levelIcon + ' Level Highlighter</h3>';
        section.appendChild(titleDiv);
        
        // Controls
        var controlsDiv = document.createElement('div');
        controlsDiv.style.marginBottom = "20px";
        
        var controlsLabel = document.createElement('div');
        controlsLabel.style.fontSize = "14px";
        controlsLabel.style.marginBottom = "8px";
        controlsLabel.innerHTML = '<strong>Find segments edited by Level:</strong>';
        controlsDiv.appendChild(controlsLabel);
        
        var inputGroup = document.createElement('div');
        inputGroup.style.display = "flex";
        inputGroup.style.alignItems = "center";
        inputGroup.style.gap = "8px";
        
        var selectElement = document.createElement('select');
        selectElement.id = "_selectEditorLevel";
        selectElement.style.padding = "5px";
        selectElement.style.border = "1px solid #ccc";
        selectElement.style.borderRadius = "3px";
        selectElement.style.fontSize = "14px";
        
        // Load saved values or use defaults
        var savedLevel = loadFromLocalStorage(STORAGE_KEYS.selectedLevel, '1');
        var savedDays = loadFromLocalStorage(STORAGE_KEYS.daysOld, '30');
        var savedColorMode = loadFromLocalStorage(STORAGE_KEYS.colorMode, 'orange');
        var savedCustomColor = loadFromLocalStorage(STORAGE_KEYS.customColor, HIGHLIGHT_ORANGE);
        var savedAutoRefresh = loadFromLocalStorage(STORAGE_KEYS.autoRefresh, 'false') === 'true';
        var savedOpacity = loadFromLocalStorage(STORAGE_KEYS.opacity, '0.7');
        
        for (var i = 1; i <= 6; i++) {
            var option = document.createElement('option');
            option.value = i;
            option.text = i;
            if (i.toString() === savedLevel) option.selected = true;
            selectElement.appendChild(option);
        }
        
        inputGroup.appendChild(selectElement);
        
        var withinLabel = document.createElement('span');
        withinLabel.textContent = 'within the last';
        withinLabel.style.fontSize = "14px";
        inputGroup.appendChild(withinLabel);
        
        var daysInput = document.createElement('input');
        daysInput.type = "number";
        daysInput.min = "0";
        daysInput.max = "9999";
        daysInput.step = "1";
        daysInput.style.width = "80px";
        daysInput.style.padding = "5px";
        daysInput.style.border = "1px solid #ccc";
        daysInput.style.borderRadius = "3px";
        daysInput.style.fontSize = "14px";
        daysInput.id = "_txtDaysOld";
        daysInput.value = savedDays;
        
        inputGroup.appendChild(daysInput);
        
        var daysLabel = document.createElement('span');
        daysLabel.textContent = 'days';
        daysLabel.style.fontSize = "14px";
        inputGroup.appendChild(daysLabel);
        
        controlsDiv.appendChild(inputGroup);
        section.appendChild(controlsDiv);
        
        // Status
        var statusDiv = document.createElement('div');
        statusDiv.id = "_highlightCounter";
        statusDiv.style.padding = "12px";
        statusDiv.style.background = "#f5f5f5";
        statusDiv.style.border = "1px solid #ddd";
        statusDiv.style.borderRadius = "4px";
        statusDiv.style.marginBottom = "15px";
        statusDiv.style.fontSize = "14px";
        statusDiv.innerHTML = 'Ready to search for segments';
        section.appendChild(statusDiv);
        
        // Results
        var resultsDiv = document.createElement('div');
        resultsDiv.id = "_editorsDisplay";
        section.appendChild(resultsDiv);
        
        // Color section
        var colorDiv = document.createElement('div');
        colorDiv.style.marginBottom = "20px";
        
        var colorLabel = document.createElement('div');
        colorLabel.style.fontSize = "14px";
        colorLabel.style.marginBottom = "8px";
        colorLabel.innerHTML = '<strong>Color:</strong>';
        colorDiv.appendChild(colorLabel);
        
        var radioGroup = document.createElement('div');
        radioGroup.style.display = "flex";
        radioGroup.style.flexDirection = "column";
        radioGroup.style.gap = "5px";
        
        // Orange
        var orangeDiv = document.createElement('div');
        var orangeRadio = document.createElement('input');
        orangeRadio.type = "radio";
        orangeRadio.name = "colour";
        orangeRadio.checked = (savedColorMode === 'orange');
        orangeRadio.id = "_rbHilightOrange";
        var orangeLabel = document.createElement('label');
        orangeLabel.style.marginLeft = "8px";
        orangeLabel.style.fontSize = "14px";
        orangeLabel.textContent = 'Orange';
        orangeDiv.appendChild(orangeRadio);
        orangeDiv.appendChild(orangeLabel);
        radioGroup.appendChild(orangeDiv);
        
        // Magenta
        var magentaDiv = document.createElement('div');
        var magentaRadio = document.createElement('input');
        magentaRadio.type = "radio";
        magentaRadio.name = "colour";
        magentaRadio.checked = (savedColorMode === 'magenta');
        magentaRadio.id = "_rbHilightMagenta";
        var magentaLabel = document.createElement('label');
        magentaLabel.style.marginLeft = "8px";
        magentaLabel.style.fontSize = "14px";
        magentaLabel.textContent = 'Magenta';
        magentaDiv.appendChild(magentaRadio);
        magentaDiv.appendChild(magentaLabel);
        radioGroup.appendChild(magentaDiv);
        
        // Custom
        var customDiv = document.createElement('div');
        customDiv.style.display = "flex";
        customDiv.style.alignItems = "center";
        var customRadio = document.createElement('input');
        customRadio.type = "radio";
        customRadio.name = "colour";
        customRadio.checked = (savedColorMode === 'custom');
        customRadio.id = "_rbHilightCustom";
        var customLabel = document.createElement('label');
        customLabel.style.marginLeft = "8px";
        customLabel.style.fontSize = "14px";
        customLabel.textContent = 'Custom:';
        var colorPicker = document.createElement('input');
        colorPicker.type = "color";
        colorPicker.id = "_customColorPicker";
        colorPicker.value = savedCustomColor;
        colorPicker.style.marginLeft = "8px";
        colorPicker.style.width = "40px";
        colorPicker.style.height = "25px";
        colorPicker.style.display = (savedColorMode === 'custom') ? 'inline-block' : 'none';
        customDiv.appendChild(customRadio);
        customDiv.appendChild(customLabel);
        customDiv.appendChild(colorPicker);
        radioGroup.appendChild(customDiv);
        
        colorDiv.appendChild(radioGroup);
        section.appendChild(colorDiv);
        
        // Auto-refresh option (moved here)
        var autoRefreshDiv = document.createElement('div');
        autoRefreshDiv.style.marginBottom = "20px";
        
        var autoRefreshCheckbox = document.createElement('input');
        autoRefreshCheckbox.type = "checkbox";
        autoRefreshCheckbox.id = "_cbAutoRefresh";
        autoRefreshCheckbox.checked = savedAutoRefresh;
        autoRefreshCheckbox.style.marginRight = "8px";
        
        var autoRefreshLabel = document.createElement('label');
        autoRefreshLabel.style.fontSize = "14px";
        autoRefreshLabel.style.cursor = "pointer";
        autoRefreshLabel.appendChild(autoRefreshCheckbox);
        autoRefreshLabel.appendChild(document.createTextNode('Auto-refresh every 5 seconds'));
        
        autoRefreshDiv.appendChild(autoRefreshLabel);
        section.appendChild(autoRefreshDiv);
        
        // Opacity section
        var opacityDiv = document.createElement('div');
        opacityDiv.style.marginBottom = "20px";
        
        var opacityLabel = document.createElement('div');
        opacityLabel.style.fontSize = "14px";
        opacityLabel.style.marginBottom = "8px";
        opacityLabel.innerHTML = '<strong>Opacity:</strong>';
        opacityDiv.appendChild(opacityLabel);
        
        var opacityGroup = document.createElement('div');
        opacityGroup.style.display = "flex";
        opacityGroup.style.alignItems = "center";
        opacityGroup.style.gap = "10px";
        
        var opacitySlider = document.createElement('input');
        opacitySlider.type = "range";
        opacitySlider.id = "_opacitySlider";
        opacitySlider.min = "0.1";
        opacitySlider.max = "1.0";
        opacitySlider.step = "0.01";
        opacitySlider.value = savedOpacity;
        opacitySlider.style.flex = "1";
        opacitySlider.style.height = "20px";
        
        var opacityValue = document.createElement('span');
        opacityValue.id = "_opacityValue";
        opacityValue.style.fontSize = "14px";
        opacityValue.style.minWidth = "40px";
        opacityValue.style.textAlign = "center";
        opacityValue.textContent = Math.round(parseFloat(savedOpacity) * 100) + '%';
        
        opacityGroup.appendChild(opacitySlider);
        opacityGroup.appendChild(opacityValue);
        opacityDiv.appendChild(opacityGroup);
        section.appendChild(opacityDiv);
        
        // Buttons
        var buttonGroup = document.createElement('div');
        buttonGroup.style.display = "flex";
        buttonGroup.style.gap = "10px";
        buttonGroup.style.marginBottom = "20px";
        
        var runButton = document.createElement('button');
        runButton.id = "_btnRunHighlighter";
        runButton.style.padding = "10px 20px";
        runButton.style.background = savedAutoRefresh ? "#FF9800" : "#4CAF50";
        runButton.style.color = "white";
        runButton.style.border = "none";
        runButton.style.borderRadius = "4px";
        runButton.style.fontSize = "14px";
        runButton.style.fontWeight = "bold";
        runButton.style.cursor = "pointer";
        runButton.textContent = savedAutoRefresh ? "AUTO REFRESH ON" : "RUN HIGHLIGHTER";
        buttonGroup.appendChild(runButton);
        
        var resetButton = document.createElement('button');
        resetButton.style.padding = "10px 20px";
        resetButton.style.background = "#f44336";
        resetButton.style.color = "white";
        resetButton.style.border = "none";
        resetButton.style.borderRadius = "4px";
        resetButton.style.fontSize = "14px";
        resetButton.style.cursor = "pointer";
        resetButton.textContent = "RESET";
        buttonGroup.appendChild(resetButton);
        
        section.appendChild(buttonGroup);
        
        var selectButton = document.createElement('button');
        selectButton.style.padding = "8px 16px";
        selectButton.style.background = "#2196F3";
        selectButton.style.color = "white";
        selectButton.style.border = "none";
        selectButton.style.borderRadius = "4px";
        selectButton.style.fontSize = "14px";
        selectButton.style.cursor = "pointer";
        selectButton.style.marginBottom = "20px";
        selectButton.textContent = "SELECT HIGHLIGHTED";
        section.appendChild(selectButton);
        
        // Version
        var versionDiv = document.createElement('div');
        versionDiv.style.fontSize = "12px";
        versionDiv.style.color = "#999";
        versionDiv.style.textAlign = "center";
        versionDiv.textContent = 'Level Highlighter v' + SCRIPT_VERSION;
        section.appendChild(versionDiv);

        tabPane.appendChild(section);
        
        // Event handlers
        runButton.addEventListener('click', runHighlighter);
        
        resetButton.addEventListener('click', function() {
            resetAllHighlights();
            var counterElement = getId('_highlightCounter');
            if (counterElement) {
                counterElement.innerHTML = "All highlights cleared";
            }
        });
        
        selectButton.addEventListener('click', selectHighlightedSegments);
        
        // Save settings when changed
        selectElement.addEventListener('change', function() {
            saveToLocalStorage(STORAGE_KEYS.selectedLevel, this.value);
        });
        
        daysInput.addEventListener('change', function() {
            saveToLocalStorage(STORAGE_KEYS.daysOld, this.value);
        });
        
        orangeRadio.addEventListener('change', function() {
            if (this.checked) {
                saveToLocalStorage(STORAGE_KEYS.colorMode, 'orange');
                updateCustomColorVisibility();
            }
        });
        
        magentaRadio.addEventListener('change', function() {
            if (this.checked) {
                saveToLocalStorage(STORAGE_KEYS.colorMode, 'magenta');
                updateCustomColorVisibility();
            }
        });
        
        customRadio.addEventListener('change', function() {
            if (this.checked) {
                saveToLocalStorage(STORAGE_KEYS.colorMode, 'custom');
                updateCustomColorVisibility();
            }
        });
        
        colorPicker.addEventListener('change', function() {
            saveToLocalStorage(STORAGE_KEYS.customColor, this.value);
        });
        
        // Auto-refresh checkbox
        autoRefreshCheckbox.addEventListener('change', toggleAutoRefresh);
        
        // Opacity slider
        opacitySlider.addEventListener('input', function() {
            var value = parseFloat(this.value);
            var percentage = Math.round(value * 100);
            var opacityValueElement = getId('_opacityValue');
            if (opacityValueElement) {
                opacityValueElement.textContent = percentage + '%';
            }
            saveToLocalStorage(STORAGE_KEYS.opacity, this.value);
            
            // Update existing highlights with new opacity
            updateHighlightOpacity(value);
        });
        
        // Update highlight opacity function
        function updateHighlightOpacity(newOpacity) {
            if (!window.W || !window.W.model || !window.W.model.segments) return;
            
            for (var i = 0; i < highlightedSegmentIds.length; i++) {
                var segId = highlightedSegmentIds[i];
                var segment = W.model.segments.getObjectById(segId);
                if (!segment) continue;
                
                try {
                    var line = W.userscripts.getFeatureElementByDataModel(segment);
                    if (line && line.getAttribute("stroke")) {
                        line.setAttribute("stroke-opacity", newOpacity);
                    }
                } catch (e) {
                    // Ignore errors
                }
            }
            console.log("Updated opacity for", highlightedSegmentIds.length, "highlights to", Math.round(newOpacity * 100) + "%");
        }

        // Global functions for clicking
        window.selectSegmentById = selectSegmentById;
        window.selectSegmentsByEditor = selectSegmentsByEditor;

        console.log("=== UI CREATED ===");
        
        // Apply saved custom color visibility
        updateCustomColorVisibility();
        
        // Initialize auto-refresh if enabled
        if (savedAutoRefresh) {
            autoRefreshEnabled = true;
            autoRefreshInterval = setInterval(function() {
                console.log("Auto-refresh: Running highlighter...");
                runHighlighter();
            }, 5000);
            console.log("Auto-refresh initialized from saved settings");
        }
        
        setTimeout(runHighlighter, 2000);
    }

    function initialize() {
        if (!window.W) {
            setTimeout(initialize, 1000);
            return;
        }
        
        setTimeout(createUI, 2000);
    }

    setTimeout(initialize, 1000);

})();