WME Level Highlighter

Level highlighter for Waze Map Editor

// ==UserScript==
// @name         WME Level Highlighter
// @namespace    https://gf.qytechs.cn/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);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址