Greasy Fork is available in English.
Permite controlar a exibição de interdições ativas, programadas e finalizadas na interface do WME.
// ==UserScript==
// @name WME Closures Control
// @description Permite controlar a exibição de interdições ativas, programadas e finalizadas na interface do WME.
// @namespace https://greasyfork.org/pt-BR/scripts/564733-wme-closures-control
// @version 1.2.0
// @icon data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><circle cx='32' cy='32' r='31' fill='%23000000'/><circle cx='32' cy='32' r='30' fill='%23ffffff'/><circle cx='32' cy='32' r='24' fill='%23d32f2f'/><rect x='14' y='28' width='36' height='8' rx='4' fill='%23ffffff'/></svg>
// @author T0NINI
// @match https://beta.waze.com/*editor*
// @match https://www.waze.com/*editor*
// @exclude https://www.waze.com/*user/*editor/*
// @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
const SCRIPT_NAME = 'WME_Closures_Control';
const DEFAULT_SETTINGS = {
showActive: true,
showFuture: true,
showInactive: false
};
let settings = { ...DEFAULT_SETTINGS };
const UI_CONFIG = {
itemHeight: '24px',
fontSize: '14px',
iconSize: '17px',
dotSize: '9px',
indentation: '2px',
colorBlue: 'var(--primary_variant, #0075e3)',
colorGray: 'var(--hairline_strong, #90959c)',
colorText: 'var(--content_default, #202124)',
colorBg: 'var(--background_default, #ffffff)',
fontFamily: 'var(--wz-font-family, "Rubik", "Boing", sans-serif)'
};
const TRANSLATIONS = {
'pt-BR': { active: 'Ativas', scheduled: 'Programadas', finished: 'Finalizadas' },
'en': { active: 'Active', scheduled: 'Scheduled', finished: 'Finished' },
'es': { active: 'Activos', scheduled: 'Programados', finished: 'Finalizados' },
'es-419': { active: 'Activos', scheduled: 'Programados', finished: 'Finalizados' },
'fr': { active: 'Actifs', scheduled: 'Programmés', finished: 'Terminés' },
'it': { active: 'Attivi', scheduled: 'Programmati', finished: 'Terminati' },
'de': { active: 'Aktiv', scheduled: 'Geplant', finished: 'Beendet' },
'nl': { active: 'Actief', scheduled: 'Gepland', finished: 'Voltooid' },
'pl': { active: 'Aktywne', scheduled: 'Planowane', finished: 'Zakończone' },
'ru': { active: 'Активные', scheduled: 'Плановые', finished: 'Завершенные' },
'uk': { active: 'Активні', scheduled: 'Заплановані', finished: 'Завершені' },
'cs': { active: 'Aktivní', scheduled: 'Plánované', finished: 'Ukončené' },
'he': { active: 'פעיל', scheduled: 'מתוכנן', finished: 'הסתיים' },
'ar': { active: 'نشط', scheduled: 'مجدول', finished: 'منتهي' }
};
function loadSettings() {
try {
const saved = localStorage.getItem(SCRIPT_NAME);
if (saved) settings = { ...DEFAULT_SETTINGS, ...JSON.parse(saved) };
} catch (e) {}
applyBodyClasses();
}
function saveSettings() {
localStorage.setItem(SCRIPT_NAME, JSON.stringify(settings));
applyBodyClasses();
}
function applyBodyClasses() {
document.body.classList.toggle('wme-hide-active', !settings.showActive);
document.body.classList.toggle('wme-hide-future', !settings.showFuture);
document.body.classList.toggle('wme-hide-inactive', !settings.showInactive);
requestAnimationFrame(processMarkers);
}
function getClosureState(marker, element) {
try {
let realClosureId = null;
if (marker.model?.attributes?.id) realClosureId = marker.model.attributes.id;
else if (marker.feature?.attributes?.id) realClosureId = marker.feature.attributes.id;
else if (marker.feature?.id) realClosureId = marker.feature.id;
else if (element.dataset?.id) realClosureId = element.dataset.id;
if (realClosureId && W?.model?.roadClosures) {
if (realClosureId.includes('.')) realClosureId = realClosureId.split('.').pop();
const closure = W.model.roadClosures.getObjectById(realClosureId);
if (closure) {
const now = Date.now();
const startTime = new Date(closure.startTime).getTime();
const endTime = new Date(closure.endTime).getTime();
if (endTime < now) return 'inactive';
if (startTime > now) return 'future';
return 'active';
}
}
const classNameStr = (element.className || "").toString();
if (classNameStr.length < 5 || classNameStr === 'map-marker') return 'loading';
if (classNameStr.includes('status-finished')) return 'inactive';
if (
classNameStr.includes('status-planned') ||
classNameStr.includes('status-scheduled') ||
classNameStr.includes('status-not-started')
) return 'future';
if (element.style.opacity && parseFloat(element.style.opacity) < 0.9) return 'future';
} catch (e) {
return 'active';
}
return 'active';
}
function processMarkers() {
if (!W?.map?.layers) return;
const layer = W.map.layers.find(l => l.name === 'closures') || W.map.getLayersBy('name', 'Closures')[0];
if (!layer || !layer.markers) return;
for (const marker of layer.markers) {
if (!marker) continue;
const el = marker.element || marker.icon?.imageDiv;
if (!el) continue;
const state = getClosureState(marker, el);
if (state === 'loading') continue;
if (!el.classList.contains('wme-closure-marker')) el.classList.add('wme-closure-marker');
if (el.dataset.wmeStatus !== state) el.dataset.wmeStatus = state;
}
}
function injectStyles() {
if (document.getElementById('wme-ui-fix-styles')) return;
const css = `
body.wme-hide-active .wme-closure-marker[data-wme-status="active"] { display:none!important; }
body.wme-hide-future .wme-closure-marker[data-wme-status="future"] { display:none!important; }
body.wme-hide-inactive .wme-closure-marker[data-wme-status="inactive"] { display:none!important; }
body.wme-hide-inactive .map-marker.status-finished { display:none!important; }
body.wme-hide-future .map-marker.status-planned,
body.wme-hide-future .map-marker.status-scheduled,
body.wme-hide-future .map-marker.status-not-started { display:none!important; }
.wme-ui-fix-submenu {
list-style:none;
padding:0;
margin:0;
transition: opacity 0.2s, max-height 0.2s;
overflow: hidden;
max-height: 200px;
opacity: 1;
}
.wme-ui-fix-submenu.layer-off {
max-height: 0;
opacity: 0;
margin: 0;
pointer-events: none;
}
.wme-ui-fix-item {
display:flex;
align-items:center;
height:${UI_CONFIG.itemHeight};
padding-left:${UI_CONFIG.indentation};
gap:10px;
cursor:pointer;
font-family:${UI_CONFIG.fontFamily};
font-size:${UI_CONFIG.fontSize};
color:${UI_CONFIG.colorText};
user-select:none;
}
.wme-ui-fix-icon {
width:${UI_CONFIG.iconSize};
height:${UI_CONFIG.iconSize};
min-width:${UI_CONFIG.iconSize};
border:1px solid ${UI_CONFIG.colorGray};
border-radius:50%;
background:${UI_CONFIG.colorBg};
position:relative;
box-sizing:border-box;
}
.wme-ui-fix-item.checked .wme-ui-fix-icon {
border-color:${UI_CONFIG.colorBlue};
}
.wme-ui-fix-item.checked .wme-ui-fix-icon::after {
content:'';
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
width:${UI_CONFIG.dotSize};
height:${UI_CONFIG.dotSize};
background:${UI_CONFIG.colorBlue};
border-radius:50%;
}
@keyframes uiFadeIn {
from { opacity:0; transform:translateY(-3px); }
to { opacity:1; transform:translateY(0); }
}
`;
const style = document.createElement('style');
style.id = 'wme-ui-fix-styles';
style.textContent = css;
document.head.appendChild(style);
}
function buildMenu() {
const ul = document.createElement('ul');
ul.className = 'wme-ui-fix-submenu';
const lang = I18n.locale || 'en';
const strings = TRANSLATIONS[lang] || TRANSLATIONS.en;
const createItem = (label, key) => {
const li = document.createElement('li');
li.className = `wme-ui-fix-item ${settings[key] ? 'checked' : ''}`;
li.innerHTML = `<div class="wme-ui-fix-icon"></div><span>${label}</span>`;
li.onclick = e => {
e.stopPropagation();
settings[key] = li.classList.toggle('checked');
saveSettings();
};
return li;
};
ul.appendChild(createItem(strings.active, 'showActive'));
ul.appendChild(createItem(strings.scheduled, 'showFuture'));
ul.appendChild(createItem(strings.finished, 'showInactive'));
return ul;
}
function findClosureLayerLi() {
const directId = document.getElementById('layer-switcher-item_closures');
if (directId) return directId.closest('li');
return null;
}
function syncMenuVisibility(checkbox, menuUl) {
if (!checkbox || !menuUl) return;
const isChecked = checkbox.checked || checkbox.hasAttribute('checked');
if (isChecked) {
menuUl.classList.remove('layer-off');
} else {
menuUl.classList.add('layer-off');
}
}
function attemptIntegration() {
try {
const li = findClosureLayerLi();
if (!li || li.querySelector('.wme-ui-fix-submenu')) return;
const menu = buildMenu();
li.appendChild(menu);
const nativeCheckbox = document.getElementById('layer-switcher-item_closures');
if (nativeCheckbox) {
syncMenuVisibility(nativeCheckbox, menu);
nativeCheckbox.addEventListener('click', () => {
setTimeout(() => syncMenuVisibility(nativeCheckbox, menu), 50);
});
const attrObserver = new MutationObserver(() => {
syncMenuVisibility(nativeCheckbox, menu);
});
attrObserver.observe(nativeCheckbox, { attributes: true, attributeFilter: ['checked'] });
}
} catch (e) {
console.error('WME Closures Control: Erro na integração', e);
}
}
function init() {
injectStyles();
loadSettings();
const observer = new MutationObserver(muts => {
attemptIntegration();
});
observer.observe(document.getElementById('sidebar') || document.body, {
childList: true,
subtree: true
});
W.map.events.register('moveend', null, processMarkers);
W.map.events.register('zoomend', null, processMarkers);
setInterval(processMarkers, 2500);
attemptIntegration();
}
function bootstrap() {
if (window.WazeWrap?.Interface?.onWmeReady) {
WazeWrap.Interface.onWmeReady(init);
} else {
const wait = setInterval(() => {
if (window.W && window.W.map && window.I18n) {
clearInterval(wait);
init();
}
}, 250);
}
}
bootstrap();
})();