// ==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);
})();