WME Quick Zoom Button

Adds a button for temporary zoom for better chat visibility and auto-visibility

// ==UserScript==
// @name         WME Quick Zoom Button
// @name:en      WME Quick Zoom Button
// @name:es      Botón de Zoom Rápido WME
// @version      2025.08.03
// @description  Fügt einen Button für temporären Zoom hinzu für bessere Chat sichtbarkeit und Auto-Sichtbarkeit
// @description:en  Adds a button for temporary zoom for better chat visibility and auto-visibility
// @description:es  Agrega un botón para zoom temporal para una mejor visibilidad del chat y auto-visibilidad
// @author       Hiwi234
// @match        https://www.waze.com/editor*
// @match        https://www.waze.com/*/editor*
// @match        https://beta.waze.com/editor*
// @match        https://beta.waze.com/*/editor*
// @grant        none
// @license      MIT
// @namespace    https://greasyfork.org/de/users/863740-horst-wittlich
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_KEY = {
        AUTO: 'wme-quick-zoom-auto',
        ZOOM: 'wme-quick-zoom-level',
        VISIBILITY: 'wme-auto-visibility'
    };

    const translations = {
        'de': {
            buttonText: 'Quick Zoom',
            buttonTooltip: 'Temporär auf Zoomstufe zoomen',
            sliderLabel: 'Maximale Zoomstufe:',
            autoLoadLabel: 'Automatisch beim Laden',
            visibilityLabel: 'Immer sichtbar bleiben'
        },
        'en': {
            buttonText: 'Quick Zoom',
            buttonTooltip: 'Temporarily zoom to level',
            sliderLabel: 'Maximum zoom level:',
            autoLoadLabel: 'Automatic on load',
            visibilityLabel: 'Always stay visible'
        },
        'es': {
            buttonText: 'Zoom Rápido',
            buttonTooltip: 'Zoom temporal al nivel',
            sliderLabel: 'Nivel máximo de zoom:',
            autoLoadLabel: 'Automático al cargar',
            visibilityLabel: 'Permanecer siempre visible'
        }
    };

    // State management
    let isZooming = false;
    let visibilityObserver = null;
    let visibilityInterval = null;
    let focusHandler = null;

    function getLanguage() {
        const lang = navigator.language.split('-')[0];
        return translations[lang] ? lang : 'en';
    }

    function getAutoZoomSetting() {
        try {
            return localStorage.getItem(STORAGE_KEY.AUTO) === 'true';
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage access failed:', e);
            return false;
        }
    }

    function setAutoZoomSetting(value) {
        try {
            localStorage.setItem(STORAGE_KEY.AUTO, String(value));
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage write failed:', e);
        }
    }

    function getVisibilitySetting() {
        try {
            return localStorage.getItem(STORAGE_KEY.VISIBILITY) === 'true';
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage access failed:', e);
            return false;
        }
    }

    function setVisibilitySetting(value) {
        try {
            localStorage.setItem(STORAGE_KEY.VISIBILITY, String(value));
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage write failed:', e);
        }
    }

    function getZoomLevel() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY.ZOOM);
            return stored ? parseInt(stored) : 7;
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage access failed:', e);
            return 7;
        }
    }

    function setZoomLevel(value) {
        try {
            localStorage.setItem(STORAGE_KEY.ZOOM, String(value));
        } catch (e) {
            console.warn('[WME Quick Zoom] localStorage write failed:', e);
        }
    }

    function ensureVisibility() {
        if (!getVisibilitySetting()) return;

        try {
            // Verschiedene Selektoren für Sichtbarkeits-Elemente
            const selectors = [
                'span.editor-visibility.label',
                '.editor-visibility',
                '[class*="visibility"]',
                '[aria-label*="visible"], [aria-label*="sichtbar"]',
                '[title*="visible"], [title*="sichtbar"]'
            ];

            for (const selector of selectors) {
                const elements = document.querySelectorAll(selector);

                for (const element of elements) {
                    const text = (element.textContent || '').toLowerCase().trim();
                    const ariaLabel = (element.getAttribute('aria-label') || '').toLowerCase();
                    const title = (element.getAttribute('title') || '').toLowerCase();

                    // Prüfe auf "unsichtbar" oder "invisible"
                    if (text.includes('unsichtbar') || text.includes('invisible') ||
                        ariaLabel.includes('unsichtbar') || ariaLabel.includes('invisible') ||
                        title.includes('unsichtbar') || title.includes('invisible')) {

                        // Suche nach klickbarem Element
                        const clickable = element.closest('button, wz-button, [role="button"]') ||
                                        element.querySelector('button, wz-button, [role="button"]') ||
                                        element.parentElement?.querySelector('button, wz-button, [role="button"]');

                        if (clickable && typeof clickable.click === 'function') {
                            console.log('[WME Quick Zoom] Unsichtbar erkannt, stelle auf sichtbar...');
                            clickable.click();
                            return;
                        }
                    }
                }
            }
        } catch (error) {
            console.warn('[WME Quick Zoom] Fehler beim Setzen der Sichtbarkeit:', error);
        }
    }

    function stopVisibilityMonitoring() {
        if (visibilityObserver) {
            visibilityObserver.disconnect();
            visibilityObserver = null;
        }

        if (visibilityInterval) {
            clearInterval(visibilityInterval);
            visibilityInterval = null;
        }

        if (focusHandler) {
            window.removeEventListener('focus', focusHandler);
            focusHandler = null;
        }
    }

    function startVisibilityMonitoring() {
        if (!getVisibilitySetting()) return;

        // Stoppe vorherige Überwachung
        stopVisibilityMonitoring();

        console.log('[WME Quick Zoom] Starte Sichtbarkeits-Überwachung...');

        // MutationObserver für DOM-Änderungen
        visibilityObserver = new MutationObserver((mutations) => {
            try {
                let shouldCheck = false;

                for (const mutation of mutations) {
                    if (mutation.type === 'childList' || mutation.type === 'attributes') {
                        const target = mutation.target;

                        // Prüfe relevante Bereiche
                        if (target.classList?.contains('online-editors-list') ||
                            target.classList?.contains('editor-visibility') ||
                            target.tagName === 'WZ-BUTTON' ||
                            target.querySelector?.('.editor-visibility, wz-button')) {
                            shouldCheck = true;
                            break;
                        }
                    }
                }

                if (shouldCheck) {
                    setTimeout(ensureVisibility, 200);
                }
            } catch (error) {
                console.warn('[WME Quick Zoom] Fehler in MutationObserver:', error);
            }
        });

        // Beobachte relevante Container
        const containers = [
            document.getElementById('online-editors'),
            document.querySelector('.map-editors'),
            document.body
        ].filter(Boolean);

        for (const container of containers) {
            try {
                visibilityObserver.observe(container, {
                    childList: true,
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['aria-label', 'title', 'class']
                });
                break; // Nur ersten verfügbaren Container überwachen
            } catch (error) {
                console.warn('[WME Quick Zoom] Fehler beim Starten des Observers:', error);
            }
        }

        // Interval-basierte Überprüfung als Fallback
        visibilityInterval = setInterval(() => {
            try {
                ensureVisibility();
            } catch (error) {
                console.warn('[WME Quick Zoom] Fehler im Visibility-Interval:', error);
            }
        }, 20000); // Alle 20 Sekunden

        // Focus-Event-Handler
        focusHandler = () => {
            setTimeout(() => {
                try {
                    ensureVisibility();
                } catch (error) {
                    console.warn('[WME Quick Zoom] Fehler bei Focus-Überprüfung:', error);
                }
            }, 1000);
        };

        window.addEventListener('focus', focusHandler, { passive: true });

        // Erste Überprüfung
        setTimeout(() => {
            try {
                ensureVisibility();
            } catch (error) {
                console.warn('[WME Quick Zoom] Fehler bei erster Überprüfung:', error);
            }
        }, 2000);
    }

    async function performQuickZoom() {
        if (isZooming) {
            console.log('[WME Quick Zoom] Zoom bereits aktiv, überspringe...');
            return;
        }

        try {
            if (!window.W?.map?.olMap) {
                console.warn('[WME Quick Zoom] WME map nicht verfügbar');
                return;
            }

            isZooming = true;
            const originalZoom = W.map.olMap.getZoom();
            const targetZoom = getZoomLevel();

            console.log(`[WME Quick Zoom] Zooming from ${originalZoom} to ${targetZoom}`);
            W.map.olMap.zoomTo(targetZoom);

            // Promise für verzögerte Rückkehr zum ursprünglichen Zoom
            return new Promise(resolve => {
                setTimeout(() => {
                    try {
                        if (window.W?.map?.olMap) {
                            W.map.olMap.zoomTo(originalZoom);
                            console.log(`[WME Quick Zoom] Restored zoom to ${originalZoom}`);
                        }
                    } catch (error) {
                        console.error('[WME Quick Zoom] Error restoring zoom:', error);
                    } finally {
                        isZooming = false;
                        resolve();
                    }
                }, 2000);
            });

        } catch (error) {
            console.error('[WME Quick Zoom] Error in performQuickZoom:', error);
            isZooming = false;
        }
    }

    function createStyles() {
        // Prüfe ob Styles bereits existieren
        if (document.getElementById('wme-quick-zoom-styles')) {
            return;
        }

        const style = document.createElement('style');
        style.id = 'wme-quick-zoom-styles';
        style.textContent = `
            .quick-zoom-container {
                display: flex;
                flex-direction: column;
                gap: 10px;
                padding: 10px;
            }
            .quick-zoom-slider-container {
                display: flex;
                flex-direction: column;
                gap: 5px;
            }
            .quick-zoom-checkbox-container {
                display: flex;
                align-items: center;
                gap: 5px;
            }
            .quick-zoom-label {
                font-size: 12px;
                color: inherit;
            }
            .quick-zoom-slider {
                width: 100%;
            }
            .quick-zoom-floating-button {
                position: fixed;
                bottom: 20px;
                left: 20px;
                z-index: 1000;
                background-color: #ffffff;
                border: 1px solid #cccccc;
                padding: 8px 15px;
                border-radius: 20px;
                box-shadow: 0 2px 4px rgba(0,0,0,0.2);
                cursor: pointer;
                font-weight: bold;
                font-family: inherit;
                font-size: 12px;
                transition: background-color 0.3s ease, box-shadow 0.3s ease;
            }
            .quick-zoom-floating-button:hover {
                background-color: #f0f0f0;
                box-shadow: 0 4px 8px rgba(0,0,0,0.3);
            }
            .quick-zoom-floating-button:active {
                background-color: #e0e0e0;
                transform: translateY(1px);
            }
        `;

        document.head.appendChild(style);
    }

    async function initializeQuickZoom() {
        try {
            // Warte bis WME vollständig geladen ist
            if (!window.W?.userscripts?.registerSidebarTab) {
                console.warn('[WME Quick Zoom] WME userscripts nicht verfügbar, warte...');
                setTimeout(initializeQuickZoom, 1000);
                return;
            }

            console.log('[WME Quick Zoom] Initialisiere...');

            const i18n = translations[getLanguage()];

            // Erstelle CSS-Styles
            createStyles();

            // Registriere Sidebar-Tab
            const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("quick-zoom-script");
            tabLabel.innerText = 'QZ';
            tabLabel.title = i18n.buttonText;

            // Erstelle Container
            const container = document.createElement('div');
            container.className = 'quick-zoom-container';

            // Sidebar Button
            const sidebarButton = document.createElement('button');
            sidebarButton.className = 'waze-btn waze-btn-small';
            sidebarButton.innerText = i18n.buttonText;
            sidebarButton.title = `${i18n.buttonTooltip} ${getZoomLevel()}`;
            sidebarButton.type = 'button';

            // Floating Button
            const floatingButton = document.createElement('button');
            floatingButton.innerText = 'QZ';
            floatingButton.title = `${i18n.buttonTooltip} ${getZoomLevel()}`;
            floatingButton.className = 'quick-zoom-floating-button';
            floatingButton.type = 'button';

            // Slider Container
            const sliderContainer = document.createElement('div');
            sliderContainer.className = 'quick-zoom-slider-container';

            const sliderLabel = document.createElement('label');
            sliderLabel.textContent = i18n.sliderLabel;
            sliderLabel.className = 'quick-zoom-label';

            const sliderValue = document.createElement('span');
            sliderValue.className = 'quick-zoom-label';
            sliderValue.textContent = getZoomLevel();

            const slider = document.createElement('input');
            slider.type = 'range';
            slider.min = '4';
            slider.max = '12';
            slider.value = getZoomLevel();
            slider.className = 'quick-zoom-slider';

            // Event Handlers mit passiven Events
            const zoomHandler = (event) => {
                event.preventDefault();
                if (!isZooming) {
                    performQuickZoom().catch(error => {
                        console.error('[WME Quick Zoom] Zoom-Fehler:', error);
                    });
                }
            };

            const sliderHandler = (event) => {
                const value = event.target.value;
                sliderValue.textContent = value;
                setZoomLevel(value);
                const tooltip = `${i18n.buttonTooltip} ${value}`;
                sidebarButton.title = tooltip;
                floatingButton.title = tooltip;
            };

            // Event Listeners hinzufügen
            slider.addEventListener('input', sliderHandler, { passive: true });
            sidebarButton.addEventListener('click', zoomHandler);
            floatingButton.addEventListener('click', zoomHandler);

            // Auto-Zoom Checkbox
            const checkboxContainer = document.createElement('div');
            checkboxContainer.className = 'quick-zoom-checkbox-container';

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = 'auto-quick-zoom-' + Date.now();
            checkbox.checked = getAutoZoomSetting();

            const label = document.createElement('label');
            label.htmlFor = checkbox.id;
            label.textContent = i18n.autoLoadLabel;
            label.className = 'quick-zoom-label';

            checkbox.addEventListener('change', (event) => {
                setAutoZoomSetting(event.target.checked);
            }, { passive: true });

            // Visibility Checkbox
            const visibilityCheckboxContainer = document.createElement('div');
            visibilityCheckboxContainer.className = 'quick-zoom-checkbox-container';

            const visibilityCheckbox = document.createElement('input');
            visibilityCheckbox.type = 'checkbox';
            visibilityCheckbox.id = 'auto-visibility-' + Date.now();
            visibilityCheckbox.checked = getVisibilitySetting();

            const visibilityLabel = document.createElement('label');
            visibilityLabel.htmlFor = visibilityCheckbox.id;
            visibilityLabel.textContent = i18n.visibilityLabel;
            visibilityLabel.className = 'quick-zoom-label';

            visibilityCheckbox.addEventListener('change', (event) => {
                setVisibilitySetting(event.target.checked);
                if (event.target.checked) {
                    setTimeout(startVisibilityMonitoring, 500);
                } else {
                    stopVisibilityMonitoring();
                }
            }, { passive: true });

            // DOM aufbauen
            sliderContainer.appendChild(sliderLabel);
            sliderContainer.appendChild(slider);
            sliderContainer.appendChild(sliderValue);

            checkboxContainer.appendChild(checkbox);
            checkboxContainer.appendChild(label);

            visibilityCheckboxContainer.appendChild(visibilityCheckbox);
            visibilityCheckboxContainer.appendChild(visibilityLabel);

            container.appendChild(sidebarButton);
            container.appendChild(sliderContainer);
            container.appendChild(checkboxContainer);
            container.appendChild(visibilityCheckboxContainer);

            tabPane.appendChild(container);
            document.body.appendChild(floatingButton);

            // Warte bis Tab verbunden ist
            await W.userscripts.waitForElementConnected(tabPane);

            // Auto-Zoom ausführen wenn aktiviert
            if (getAutoZoomSetting()) {
                setTimeout(() => {
                    performQuickZoom().catch(error => {
                        console.error('[WME Quick Zoom] Auto-Zoom Fehler:', error);
                    });
                }, 2000);
            }

            // Visibility Monitoring starten wenn aktiviert
            if (getVisibilitySetting()) {
                setTimeout(startVisibilityMonitoring, 1000);
            }

            console.log('[WME Quick Zoom] Erfolgreich initialisiert');

        } catch (error) {
            console.error('[WME Quick Zoom] Initialisierungs-Fehler:', error);
        }
    }

    // Cleanup beim Verlassen der Seite
    window.addEventListener('beforeunload', () => {
        stopVisibilityMonitoring();
    }, { passive: true });

    // Verbesserte Initialisierung
    function initialize() {
        try {
            if (window.W?.userscripts?.state?.isReady) {
                initializeQuickZoom();
            } else if (window.W?.userscripts) {
                document.addEventListener("wme-ready", initializeQuickZoom, {
                    once: true,
                    passive: true
                });
            } else {
                // Warte auf WME
                let attempts = 0;
                const maxAttempts = 60; // 30 Sekunden maximum

                const checkWME = () => {
                    attempts++;

                    if (window.W?.userscripts) {
                        if (window.W.userscripts.state?.isReady) {
                            initializeQuickZoom();
                        } else {
                            document.addEventListener("wme-ready", initializeQuickZoom, {
                                once: true,
                                passive: true
                            });
                        }
                    } else if (attempts < maxAttempts) {
                        setTimeout(checkWME, 500);
                    } else {
                        console.error('[WME Quick Zoom] WME konnte nicht geladen werden nach', maxAttempts * 500, 'ms');
                    }
                };

                checkWME();
            }
        } catch (error) {
            console.error('[WME Quick Zoom] Initialisierungs-Setup-Fehler:', error);
        }
    }

    // Start initialization
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize, {
            once: true,
            passive: true
        });
    } else {
        initialize();
    }

})();