你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
// ==UserScript==
// @name WME Residencial Navigator
// @namespace http://tampermonkey.net/
// @version 2.3
// @description Herramienta completa para gestionar lugares residenciales en WME con nombre visible
// @author TuNombre
// @match https://www.waze.com/*/editor*
// @match https://beta.waze.com/*/editor*
// @match https://www.waze.com/editor/*
// @grant GM_addStyle
// @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// ==/UserScript==
(function() {
'use strict';
// Configuración
const TAB_NAME = "Residenciales";
const DEBUG_MODE = true;
// Estilos mejorados
GM_addStyle(`
#residentialPanel {
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
width: 100%;
max-height: 80vh;
overflow-y: auto;
margin-top: 10px;
border: 1px solid #ddd;
}
#residentialList {
width: 100%;
margin: 15px 0;
padding: 5px;
max-height: 60vh;
overflow-y: auto;
}
.residential-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.residential-checkbox {
margin-right: 12px;
cursor: pointer;
}
.residential-name {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
}
.go-to-btn {
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
margin-left: 10px;
flex-shrink: 0;
font-size: 13px;
transition: background 0.2s;
}
.go-to-btn:hover {
background: #3e8e41;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
.refresh-btn, .delete-btn {
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
flex-grow: 1;
font-weight: bold;
transition: opacity 0.2s;
}
.refresh-btn {
background: #2196F3;
}
.refresh-btn:hover {
opacity: 0.9;
}
.delete-btn {
background: #f44336;
}
.delete-btn:hover {
opacity: 0.9;
}
.select-all-container {
margin: 10px 0;
font-size: 14px;
display: flex;
align-items: center;
}
.status-message {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
font-size: 13px;
text-align: center;
display: none;
}
.success {
background-color: #dff0d8;
color: #3c763d;
border: 1px solid #d6e9c6;
}
.warning {
background-color: #fcf8e3;
color: #8a6d3b;
border: 1px solid #faebcc;
}
.error {
background-color: #f2dede;
color: #a94442;
border: 1px solid #ebccd1;
}
/* Estilo para la pestaña personalizada */
#user-tabs .residential-tab {
background-color: #f8f9fa;
}
#user-tabs .residential-tab.active {
background-color: #e9ecef;
}
/* Asegurar que el texto de la pestaña sea visible */
#user-tabs .residential-tab a {
color: #333 !important;
font-weight: bold;
padding: 8px 12px;
display: block;
}
/* Botón flotante de respaldo */
#residentialFloatingBtn {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
background: #4CAF50;
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 20px;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
display: none;
}
`);
let residentialPlaces = [];
let panelInitialized = false;
// Función para debug
function debugLog(message) {
if (DEBUG_MODE) {
console.log(`[Residencial Navigator] ${message}`);
}
}
// Función principal para registrar la pestaña
function registerSidebarTab() {
debugLog(`Intentando registrar pestaña: ${TAB_NAME}`);
// Método 1: API oficial de WME
if (typeof W !== 'undefined' && W.userscripts && typeof W.userscripts.registerSidebarTab === 'function') {
try {
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab(TAB_NAME);
if (tabPane) {
debugLog("Pestaña registrada con API oficial");
// Asegurar que el nombre sea visible
tabLabel.textContent = TAB_NAME;
tabLabel.title = TAB_NAME;
tabLabel.style.color = "#333";
tabLabel.style.fontWeight = "bold";
const contentPanel = document.createElement("div");
contentPanel.id = "residential-content";
tabPane.appendChild(contentPanel);
tabLabel.addEventListener("click", function(e) {
e.preventDefault();
toggleResidentialPanel(contentPanel);
});
initResidentialPanel(contentPanel);
return;
}
} catch (e) {
debugLog(`Error con API oficial: ${e}`);
}
}
// Método 2: Alternativo si falla la API
setupAlternativeTab();
}
// Método alternativo para crear pestaña
function setupAlternativeTab() {
debugLog("Usando método alternativo para crear pestaña");
const userTabs = document.querySelector('#user-tabs ul');
if (!userTabs) {
debugLog("No se encontró #user-tabs, creando botón flotante");
createFloatingButton();
return;
}
// Verificar si la pestaña ya existe
if (document.querySelector(`#user-tabs a[title="${TAB_NAME}"]`)) {
debugLog("La pestaña ya existe");
return;
}
// Crear nueva pestaña
const tabItem = document.createElement('li');
tabItem.className = 'residential-tab';
const tabLink = document.createElement('a');
tabLink.href = '#';
tabLink.title = TAB_NAME;
tabLink.textContent = TAB_NAME;
tabItem.appendChild(tabLink);
userTabs.appendChild(tabItem);
// Crear panel de contenido
const userPanel = document.querySelector('#user-panel');
if (!userPanel) {
debugLog("No se encontró #user-panel, creando botón flotante");
createFloatingButton();
return;
}
const contentPanel = document.createElement('div');
contentPanel.id = 'residential-content';
contentPanel.style.display = 'none';
userPanel.appendChild(contentPanel);
// Manejar clics en la pestaña
tabLink.addEventListener('click', function(e) {
e.preventDefault();
toggleResidentialPanel(contentPanel);
// Actualizar estado activo
document.querySelectorAll('#user-tabs li').forEach(tab => {
tab.classList.remove('active');
});
this.parentNode.classList.add('active');
});
initResidentialPanel(contentPanel);
}
// Crear botón flotante como respaldo
function createFloatingButton() {
debugLog("Creando botón flotante de respaldo");
const floatingBtn = document.createElement('button');
floatingBtn.id = 'residentialFloatingBtn';
floatingBtn.textContent = '🏠';
floatingBtn.title = TAB_NAME;
floatingBtn.style.display = 'block';
document.body.appendChild(floatingBtn);
const panel = document.createElement('div');
panel.id = 'residentialPanel';
panel.style.position = 'fixed';
panel.style.right = '20px';
panel.style.bottom = '80px';
panel.style.zIndex = '9998';
panel.style.display = 'none';
panel.style.width = '300px';
document.body.appendChild(panel);
initResidentialPanel(panel);
floatingBtn.addEventListener('click', () => {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
});
}
// Inicializar el panel de contenido
function initResidentialPanel(container) {
if (panelInitialized) return;
panelInitialized = true;
debugLog("Inicializando panel residencial");
const panel = document.createElement('div');
panel.id = 'residentialPanel';
panel.innerHTML = `
<h3 style="margin-top: 0; color: #333;">Lugares Residenciales</h3>
<div class="select-all-container">
<input type="checkbox" id="selectAllResidentials" class="residential-checkbox">
<label for="selectAllResidentials">Seleccionar todos</label>
</div>
<div id="residentialList"></div>
<div class="action-buttons">
<button class="refresh-btn">🔄 Actualizar</button>
<button class="delete-btn">🗑️ Eliminar</button>
</div>
<div id="statusMessage" class="status-message" style="display: none;"></div>
`;
// Configurar eventos
panel.querySelector('#selectAllResidentials').addEventListener('change', function() {
const checkboxes = panel.querySelectorAll('.residential-checkbox');
checkboxes.forEach(checkbox => {
if (checkbox !== this) checkbox.checked = this.checked;
});
});
panel.querySelector('.refresh-btn').addEventListener('click', updateResidentialList);
panel.querySelector('.delete-btn').addEventListener('click', deleteSelectedPlaces);
container.appendChild(panel);
updateResidentialList();
}
// Mostrar/ocultar panel
function toggleResidentialPanel(contentPanel) {
if (!contentPanel) return;
debugLog("Alternando visibilidad del panel");
// Ocultar todos los paneles primero
document.querySelectorAll("#user-panel > div").forEach(panel => {
panel.style.display = "none";
});
// Mostrar/ocultar nuestro panel
contentPanel.style.display = contentPanel.style.display === "block" ? "none" : "block";
// Cargar datos si se muestra
if (contentPanel.style.display === "block" && residentialPlaces.length === 0) {
updateResidentialList();
}
}
// Actualizar lista de lugares residenciales
function updateResidentialList() {
debugLog("Actualizando lista de residenciales");
if (!W || !W.model || !W.model.venues) {
showStatusMessage("WME no está completamente cargado", 'error');
return;
}
residentialPlaces = [];
const venues = W.model.venues.objects;
let count = 0;
for (let id in venues) {
const venue = venues[id];
if (venue.attributes.categories && venue.attributes.categories.includes('RESIDENCE_HOME')) {
const name = venue.attributes.name || 'Sin nombre';
const geom = venue.attributes.geoJSONGeometry;
if (geom && geom.coordinates) {
let lat, lon;
if (geom.type === "Point") {
[lon, lat] = geom.coordinates;
} else if (geom.type === "Polygon") {
[lon, lat] = geom.coordinates[0][0];
} else {
continue;
}
residentialPlaces.push({
id: id,
name: name,
lat: lat,
lon: lon,
venueObject: venue
});
count++;
}
}
}
updateListDisplay();
showStatusMessage(`Encontrados ${count} lugares residenciales`, count > 0 ? 'success' : 'warning');
debugLog(`Encontrados ${count} lugares residenciales`);
}
// Actualizar visualización de la lista
function updateListDisplay() {
const listContainer = document.getElementById('residentialList');
if (!listContainer) return;
listContainer.innerHTML = '';
if (residentialPlaces.length === 0) {
listContainer.innerHTML = '<div style="padding: 10px; text-align: center;">No se encontraron lugares residenciales</div>';
return;
}
residentialPlaces.forEach((place, index) => {
const item = document.createElement('div');
item.className = 'residential-item';
item.innerHTML = `
<input type="checkbox" class="residential-checkbox" data-id="${place.id}">
<span class="residential-name" title="${place.name}">${place.name || `Lugar ${index + 1}`}</span>
<button class="go-to-btn" data-id="${place.id}">Ir</button>
`;
item.querySelector('.go-to-btn').addEventListener('click', () => goToPlace(place));
listContainer.appendChild(item);
});
}
// Navegar a un lugar
function goToPlace(place) {
debugLog(`Navegando a lugar: ${place.name || place.id}`);
if (!W || !W.map) return;
try {
const center = WazeWrap.LonLat.fromObject({lon: place.lon, lat: place.lat});
W.map.setCenter(center, 18);
if (place.venueObject) {
W.model.venues.setSelectedVenue(place.venueObject);
// Habilitar botón de guardar
setTimeout(() => {
const saveBtn = document.querySelector('[title="Save"], [title="Guardar"]');
if (saveBtn) saveBtn.removeAttribute('disabled');
}, 500);
}
} catch (e) {
debugLog(`Error al navegar: ${e}`);
showStatusMessage("Error al navegar al lugar", 'error');
}
}
// Eliminar lugares seleccionados
function deleteSelectedPlaces() {
const checkboxes = document.querySelectorAll('.residential-checkbox:checked');
if (checkboxes.length === 0) {
showStatusMessage('Selecciona al menos un lugar', 'warning');
return;
}
if (!confirm(`¿Eliminar ${checkboxes.length} lugar(es) seleccionado(s)?`)) return;
let deletedCount = 0;
let errors = 0;
checkboxes.forEach(checkbox => {
const id = checkbox.dataset.id;
const place = W.model.venues.getObjectById(id);
if (!place) {
errors++;
return;
}
try {
const DeleteObject = require("Waze/Action/DeleteObject");
const action = new DeleteObject(place);
W.model.actionManager.add(action);
deletedCount++;
debugLog(`Eliminado lugar: ${id}`);
} catch (e) {
debugLog(`Error eliminando lugar ${id}: ${e}`);
errors++;
}
});
if (deletedCount > 0) {
showStatusMessage(`Eliminados ${deletedCount} lugares (${errors} errores)`, 'success');
updateResidentialList();
// Habilitar botón de guardar
setTimeout(() => {
const saveBtn = document.querySelector('[title="Save"], [title="Guardar"]');
if (saveBtn) saveBtn.removeAttribute('disabled');
}, 500);
} else {
showStatusMessage("No se eliminaron lugares", 'warning');
}
}
// Mostrar mensajes de estado
function showStatusMessage(message, type) {
debugLog(`Mensaje de estado [${type}]: ${message}`);
const element = document.getElementById('statusMessage');
if (!element) return;
element.textContent = message;
element.className = `status-message ${type}`;
element.style.display = 'block';
setTimeout(() => {
element.style.display = 'none';
}, 5000);
}
// Inicialización del script
let initAttempts = 0;
const MAX_INIT_ATTEMPTS = 5;
function initScript() {
debugLog("Iniciando WME Residencial Navigator");
initAttempts++;
if (document.querySelector("#user-tabs")) {
registerSidebarTab();
} else if (initAttempts < MAX_INIT_ATTEMPTS) {
setTimeout(initScript, 500);
} else {
debugLog("No se encontró #user-tabs después de múltiples intentos");
createFloatingButton();
}
}
// Esperar a que cargue WME
if (document.readyState === 'complete') {
initScript();
} else {
window.addEventListener('load', initScript);
}
debugLog("Script cargado correctamente");
})();