Drawaria Symbols Loader Menu

Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu

// ==UserScript==
// @name         Drawaria Symbols Loader Menu
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu
// @author       YouTubeDrawaria, Barsik Hacker
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    const SYM_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/BarsikSymbols.json";
    const PAGE_SIZE = 30; // Número de símbolos por página

    let allSymbols = []; // Almacena todos los símbolos cargados
    let currentPage = 0;
    let symLoaderBox; // Referencia al cuadro principal de la UI

    // Variables para la funcionalidad de arrastre
    let isDragging = false;
    let offsetX, offsetY;

    /**
     * Crea y añade la interfaz de usuario al DOM.
     * Incluye estilos CSS y la estructura HTML básica.
     */
    function createUI() {
        // --- Estilos CSS ---
        const style = document.createElement('style');
        style.textContent = `
            #symLoaderBox {
                position: fixed;
                top: 60px;
                right: 20px;
                background: #2b2b2b; /* Fondo oscuro */
                color: #e0e0e0; /* Texto claro */
                z-index: 9999; /* Asegura que esté por encima de otros elementos */
                border-radius: 10px;
                padding: 15px;
                width: 280px; /* Ancho ligeramente mayor */
                font-size: 15px;
                box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4); /* Sombra suave */
                font-family: 'Arial', sans-serif;
                border: 1px solid #444; /* Borde sutil */
                cursor: grab; /* Cursor para indicar que es arrastrable */
            }

            #symLoaderHeader {
                display: flex;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #3a3a3a; /* Separador */
                cursor: grab; /* Cursor para indicar que el header es arrastrable */
            }
            #symLoaderHeader span {
                font-size: 18px;
                font-weight: bold;
                color: #fff;
            }

            #symLoaderBox button {
                margin: 0 2px;
                padding: 5px 9px;
                font-size: 17px;
                vertical-align: middle;
                border-radius: 5px;
                border: none;
                background: #444;
                color: #fff;
                cursor: pointer;
                transition: background 0.2s ease, transform 0.1s ease; /* Transiciones suaves */
            }
            #symLoaderBox button:hover {
                background: #666;
                transform: translateY(-1px); /* Efecto de "levantar" */
            }
            #symLoaderBox button:active {
                transform: translateY(0);
            }

            #symLoaderDL {
                background: #3a7bd5; /* Color distintivo para descargar */
            }
            #symLoaderDL:hover {
                background: #2a6bc5;
            }

            #symLoaderClose {
                background: #d32f2f; /* Rojo para cerrar */
            }
            #symLoaderClose:hover {
                background: #c31f1f;
            }

            #symLoaderSymbols {
                max-height: 280px; /* Altura máxima para el scroll */
                overflow-y: auto;
                word-break: break-all; /* Rompe palabras largas */
                white-space: normal;
                padding-right: 5px; /* Espacio para la barra de desplazamiento */
                margin-bottom: 10px;
            }

            /* Estilos de la barra de desplazamiento (para navegadores Webkit como Chrome, Safari) */
            #symLoaderSymbols::-webkit-scrollbar {
                width: 8px;
            }
            #symLoaderSymbols::-webkit-scrollbar-track {
                background: #333;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb {
                background: #666;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb:hover {
                background: #888;
            }

            .symBtn { /* Estilo para los botones de símbolos individuales */
                display: inline-block;
                background: #555;
                color: #fff;
                padding: 4px 8px;
                margin: 3px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
                transition: background 0.15s ease, transform 0.05s ease;
                user-select: none; /* Previene la selección de texto */
            }
            .symBtn:hover {
                background: #777;
                transform: scale(1.02);
            }
            .symBtn:active {
                transform: scale(1.0);
            }

            #symLoaderPager {
                display: flex;
                justify-content: center;
                align-items: center;
                margin-top: 5px;
                padding-top: 5px;
                border-top: 1px solid #3a3a3a; /* Separador */
            }
            #symLoaderPager .pager-button { /* Clase para los botones de paginación */
                background: #007bff;
                color: #fff;
                padding: 3px 8px;
                margin: 0 5px;
                font-size: 15px;
                border-radius: 4px;
                transition: background 0.2s ease;
            }
            #symLoaderPager .pager-button:hover {
                background: #0056b3;
            }
            #symLoaderPager .pager-button:disabled {
                background: #555;
                cursor: not-allowed;
            }
            #symLoaderPager span {
                font-size: 15px;
                color: #bbb;
            }
        `;
        document.head.appendChild(style);

        // --- Estructura HTML ---
        symLoaderBox = document.createElement('div');
        symLoaderBox.id = 'symLoaderBox';
        symLoaderBox.innerHTML = `
            <div id="symLoaderHeader">
                <span>Drawaria Symbols</span>
                <div>
                    <button id="symLoaderDL">💾</button>
                    <button id="symLoaderClose">❌</button>
                </div>
            </div>
            <div id="symLoaderSymbols">
                Cargando símbolos...
            </div>
            <div id="symLoaderPager">
                <!-- Los botones de paginación se insertarán aquí -->
            </div>
        `;
        document.body.appendChild(symLoaderBox);

        // --- Event Listeners ---
        document.getElementById('symLoaderClose').onclick = () => symLoaderBox.remove();
        document.getElementById('symLoaderDL').onclick = downloadJSON;

        // Delegación de eventos para los botones de símbolos (eficiente para muchos botones)
        document.getElementById('symLoaderBox').addEventListener('click', e => {
            if (e.target.classList.contains('symBtn')) {
                insertToChat(e.target.dataset.symbol);
            }
        });

        // --- Draggable functionality ---
        const symLoaderHeader = document.getElementById('symLoaderHeader');

        symLoaderHeader.addEventListener('mousedown', (e) => {
            isDragging = true;
            // Calcular el desplazamiento del cursor dentro del elemento
            offsetX = e.clientX - symLoaderBox.getBoundingClientRect().left;
            offsetY = e.clientY - symLoaderBox.getBoundingClientRect().top;
            symLoaderBox.style.cursor = 'grabbing'; // Cambiar cursor al arrastrar
            // Prevenir la selección de texto durante el arrastre
            symLoaderBox.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            // Calcular la nueva posición del elemento
            let newLeft = e.clientX - offsetX;
            let newTop = e.clientY - offsetY;

            // Opcional: Limitar el arrastre dentro de la ventana
            const maxX = window.innerWidth - symLoaderBox.offsetWidth;
            const maxY = window.innerHeight - symLoaderBox.offsetHeight;

            newLeft = Math.max(0, Math.min(newLeft, maxX));
            newTop = Math.max(0, Math.min(newTop, maxY));

            symLoaderBox.style.left = `${newLeft}px`;
            symLoaderBox.style.top = `${newTop}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            symLoaderBox.style.cursor = 'grab'; // Restaurar cursor
            symLoaderBox.style.userSelect = 'auto'; // Habilitar selección de texto
        });
    }

    /**
     * Rellena el contenedor de símbolos con los símbolos de la página actual.
     * Siempre usa `allSymbols` ya que no hay filtro.
     * @param {number} page - El número de página a mostrar (0-indexado).
     */
    function populateSymbols(page = 0) {
        if (!allSymbols || allSymbols.length === 0) {
            fillSymbols('No hay símbolos disponibles.');
            updatePager(0, 0, 0); // Actualiza la paginación a "sin páginas"
            return;
        }

        currentPage = page;
        const wrap = document.getElementById('symLoaderSymbols');
        wrap.innerHTML = ''; // Limpia los símbolos anteriores

        let start = page * PAGE_SIZE;
        let end = Math.min(start + PAGE_SIZE, allSymbols.length);

        const fragment = document.createDocumentFragment(); // Para mejor rendimiento al añadir muchos elementos
        for (let i = start; i < end; i++) {
            const symbol = allSymbols[i];
            const button = document.createElement('button');
            button.classList.add('symBtn');
            button.dataset.symbol = symbol; // Almacena el símbolo en un atributo de datos
            button.textContent = symbol;
            fragment.appendChild(button);
        }
        wrap.appendChild(fragment);

        updatePager(page, allSymbols.length, PAGE_SIZE);
    }

    /**
     * Actualiza los botones y la información de la paginación.
     * @param {number} currentPage - La página actual (0-indexada).
     * @param {number} totalSymbols - El número total de símbolos disponibles.
     * @param {number} pageSize - El número de símbolos por página.
     */
    function updatePager(currentPage, totalSymbols, pageSize) {
        const pager = document.getElementById('symLoaderPager');
        pager.innerHTML = ''; // Limpia la paginación existente

        if (totalSymbols <= pageSize) {
            return; // No se necesita paginación si hay una sola página o menos
        }

        const totalPages = Math.ceil(totalSymbols / pageSize);

        // Botón "Anterior"
        const prevButton = document.createElement('button');
        prevButton.classList.add('pager-button');
        prevButton.textContent = '<';
        prevButton.disabled = currentPage === 0; // Deshabilitar si es la primera página
        prevButton.onclick = () => populateSymbols(currentPage - 1);
        pager.appendChild(prevButton);

        // Información de la página (ej: "1/5")
        const pageInfo = document.createElement('span');
        pageInfo.textContent = `${currentPage + 1}/${totalPages}`;
        pager.appendChild(pageInfo);

        // Botón "Siguiente"
        const nextButton = document.createElement('button');
        nextButton.classList.add('pager-button');
        nextButton.textContent = '>';
        nextButton.disabled = currentPage >= totalPages - 1; // Deshabilitar si es la última página
        nextButton.onclick = () => populateSymbols(currentPage + 1);
        pager.appendChild(nextButton);
    }

    /**
     * Rellena el contenedor de símbolos con un mensaje de estado.
     * @param {string} msg - El mensaje a mostrar.
     */
    function fillSymbols(msg) {
        document.getElementById('symLoaderSymbols').textContent = msg;
    }

    /**
     * Descarga los símbolos cargados como un archivo JSON.
     */
    function downloadJSON() {
        const a = document.createElement('a');
        // Convierte el array a JSON con indentación para que sea legible
        a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(allSymbols, null, 2));
        a.download = 'BarsikSymbols.json';
        a.click();
    }

    /**
     * Inserta el texto dado en el campo de entrada del chat.
     * Se actualizó el selector para el chatbox de Drawaria.
     * @param {string} txt - El texto a insertar.
     */
    function insertToChat(txt) {
        const chatInput = document.getElementById('chatbox_textinput'); // Selector actualizado
        if (!chatInput) {
            alert('No se ha encontrado el input de chat con ID "chatbox_textinput".');
            return;
        }
        chatInput.value = txt;
        // Dispara un evento 'input' para asegurar que frameworks como React/Vue detecten el cambio
        chatInput.dispatchEvent(new Event('input', { bubbles: true }));
        // Opcional: enviar el mensaje (descomenta la siguiente línea, ¡bajo tu responsabilidad!)
        // document.querySelector('.chat__message-form button')?.click();
    }

    /**
     * Carga los símbolos desde la URL especificada.
     */
    async function loadSymbols() {
        fillSymbols("Cargando símbolos..."); // Mostrar mensaje de carga
        try {
            const response = await fetch(SYM_URL);
            if (!response.ok) {
                throw new Error(`Error HTTP! estado: ${response.status}`);
            }
            const obj = await response.json();
            // Soporta si el JSON es un array directamente o un objeto con una propiedad 'symbols'
            allSymbols = Array.isArray(obj) ? obj : obj.symbols ?? [];

            if (allSymbols.length === 0) {
                fillSymbols("No se encontraron símbolos en el archivo JSON o el formato es incorrecto.");
            } else {
                populateSymbols(0); // Carga la página inicial de símbolos
            }
        } catch (e) {
            console.error("Error al cargar símbolos:", e);
            fillSymbols(`Error al cargar símbolos: ${e.message}. Por favor, intenta recargar la página.`);
        }
    }

    // Inicializa la interfaz de usuario y carga los símbolos al iniciar el script
    createUI();
    loadSymbols();
})();