Advanced Calculator

A unified calculator tool with chat history and draggable UI.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Advanced Calculator
// @namespace    https://greasyfork.org/en/users/1291009
// @version      6.1.5
// @description  A unified calculator tool with chat history and draggable UI.
// @author       BadOrBest
// @license      MIT
// @icon         https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQE4akrlePwM0brye6bimtz0ziOengL_C9rhQ&s
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==
(function() {
    'use strict';

    // Load external libraries
    const scriptLoad = url => new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
    });

    Promise.all([
        scriptLoad('https://cdnjs.cloudflare.com/ajax/libs/mathjs/13.1.1/math.min.js'),
        scriptLoad('https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js')
    ]).then(() => {
        console.log('Libraries loaded successfully.');
        initCalculator();
    }).catch(error => console.error('Error loading libraries:', error));

    // General settings
    const settings = {
        theme: GM_getValue('theme', 'dark'),
        historyPageSize: GM_getValue('historyPageSize', 50),
        interpretXAsMultiply: GM_getValue('interpretXAsMultiply', true)  // Default to true
    };

    // Calculator Initialization
    function initCalculator() {
        const MAX_MESSAGES = 400;
        const STORAGE_KEY = 'chatHistory';
        let lastResult = null;  // Store the last result

        const chatBox = document.createElement('div');
        chatBox.id = 'calculator-chat-box';
        Object.assign(chatBox.style, {
            position: 'fixed',
            bottom: '10vh',
            right: '5vw',
            width: 'calc(90vw - 10px)',
            maxWidth: '350px',
            backgroundColor: settings.theme === 'dark' ? '#1f1f1f' : '#f9f9f9',
            borderRadius: '12px',
            padding: '10px',
            boxShadow: '0 0 15px rgba(0, 0, 0, 0.3)',
            zIndex: '9999',
            display: 'flex',
            flexDirection: 'column',
            fontFamily: 'Arial, sans-serif'
        });

        const chatHeader = document.createElement('div');
        chatHeader.style.display = 'flex';
        chatHeader.style.justifyContent = 'space-between';
        chatHeader.style.alignItems = 'center';
        chatHeader.style.backgroundColor = settings.theme === 'dark' ? '#333' : '#ddd';
        chatHeader.style.color = settings.theme === 'dark' ? '#fff' : '#333';
        chatHeader.style.padding = '10px';
        chatHeader.style.borderRadius = '10px 10px 0 0';
        chatHeader.style.fontWeight = 'bold';
        chatHeader.textContent = 'Advanced Calculator';

        const collapseButton = document.createElement('button');
        collapseButton.textContent = '▼';
        collapseButton.style.padding = '5px 10px';
        collapseButton.style.border = 'none';
        collapseButton.style.backgroundColor = '#555';
        collapseButton.style.color = '#fff';
        collapseButton.style.cursor = 'pointer';
        collapseButton.style.borderRadius = '5px';
        collapseButton.addEventListener('click', toggleChatBox);

        chatHeader.appendChild(collapseButton);
        chatBox.appendChild(chatHeader);

        const chatHistory = document.createElement('div');
        chatHistory.id = 'chat-history';
        Object.assign(chatHistory.style, {
            height: '300px',
            overflowY: 'auto',
            backgroundColor: settings.theme === 'dark' ? '#333' : '#fff',
            color: settings.theme === 'dark' ? '#fff' : '#333',
            padding: '10px',
            borderRadius: '8px'
        });
        chatBox.appendChild(chatHistory);

        const chatInput = document.createElement('input');
        chatInput.id = 'chat-input';
        chatInput.type = 'text';
        Object.assign(chatInput.style, {
            width: 'calc(100% - 20px)',
            padding: '10px',
            margin: '10px auto',
            borderRadius: '10px',
            border: 'none',
            backgroundColor: '#444',
            color: 'white'
        });
        chatInput.placeholder = 'Type here...';
        chatInput.addEventListener('keydown', event => {
            if (event.key === 'Enter') {
                event.preventDefault();
                if (chatInput.value.trim() === '') {
                    if (lastResult !== null) {
                        chatInput.value = lastResult;  // Insert the last result into the input field
                    }
                } else {
                    sendMessage();
                }
            }
        });
        chatBox.appendChild(chatInput);

        // Draggable functionality
        let isDragging = false;
        let initialX, initialY;
        let offsetX = 0, offsetY = 0;

        chatBox.addEventListener('mousedown', startDragging);
        chatBox.addEventListener('touchstart', startDragging);

        document.addEventListener('mousemove', drag);
        document.addEventListener('touchmove', drag);
        document.addEventListener('mouseup', stopDragging);
        document.addEventListener('touchend', stopDragging);

        function startDragging(event) {
            if (event.target === chatInput) return;
            event.preventDefault();
            initialX = event.clientX - offsetX;
            initialY = event.clientY - offsetY;
            isDragging = true;
            chatBox.classList.add('chat-dragging');
            document.addEventListener('mousemove', drag); // Attach listeners only on start
            document.addEventListener('touchmove', drag);
        }

        function drag(event) {
            if (!isDragging) return;
            event.preventDefault();
            const currentX = event.clientX - initialX;
            const currentY = event.clientY - initialY;
            offsetX = currentX;
            offsetY = currentY;
            chatBox.style.transform = `translate(${currentX}px, ${currentY}px)`;
        }

        function stopDragging() {
            isDragging = false;
            chatBox.classList.remove('chat-dragging');
            document.removeEventListener('mousemove', drag); // Detach listeners on stop
            document.removeEventListener('touchmove', drag);
        }

        function toggleChatBox() {
            if (chatBox.style.height === '70px') {
                chatBox.style.height = 'auto';
                collapseButton.textContent = '▼';
                chatInput.style.display = 'block';
            } else {
                chatBox.style.height = '70px';
                collapseButton.textContent = '▲';
                chatInput.style.display = 'none';
            }
        }

        // Function to load messages from localStorage
        function loadMessagesFromStorage() {
            let messages = [];
            try {
                messages = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
            } catch (error) {
                console.error('Error loading chat history from localStorage:', error);
                clearLocalStorage();
            }
            return messages;
        }

        // Function to clear localStorage
        function clearLocalStorage() {
            try {
                localStorage.removeItem(STORAGE_KEY);
            } catch (error) {
                console.error('Error clearing localStorage:', error);
            }
        }

        // Function to add message to conversation and save to localStorage
        function addMessage(message, isInput) {
            const messageElement = document.createElement('div');
            messageElement.className = 'message ' + (isInput ? 'input' : 'output');
            messageElement.innerHTML = message;
            chatHistory.appendChild(messageElement);

            if (!isInput) {
                const line = document.createElement('hr');
                line.style.borderTop = '1px solid white';
                line.style.margin = '5px 0';
                chatHistory.appendChild(line);
            }

            function scrollToBottom() {
                chatHistory.scrollTop = chatHistory.scrollHeight;
            }

            scrollToBottom();

            let messages = loadMessagesFromStorage();
            messages.push({ message: message, isInput: isInput });

            if (messages.length > MAX_MESSAGES) {
                messages = messages.slice(-MAX_MESSAGES);
            }

            try {
                localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
            } catch (error) {
                if (error.name === 'QuotaExceededError') {
                    console.error('LocalStorage is full, clearing oldest messages.');
                    messages.shift(); // Remove the oldest message and try saving again
                    localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
                } else {
                    console.error('Error saving chat history:', error);
                    clearLocalStorage();
                }
            }
        }

        function sendMessage() {
            const expression = chatInput.value.trim();
            if (expression !== '') {
                addMessage('<span class="user-label">(User):</span> ' + expression, true);
                evaluateExpression(expression);
                chatInput.value = '';
            }
        }

        function evaluateExpression(expression) {
            try {
                if (settings.interpretXAsMultiply) {
                    expression = expression.replace(/(\d+)\s*x\s*(\d+)/gi, '$1*$2'); // Handles better
                }
                const result = math.evaluate(expression);
                addMessage('<span class="result-label">(Result):</span> ' + result, false);
                lastResult = result;  // Store the result for reuse
            } catch (error) {
                addMessage('<span class="result-label">(Result):</span> Error: ' + error.message, false);
            }
        }

        window.addEventListener('load', () => {
            // Prevent duplicate loading of messages
            chatHistory.innerHTML = ''; // Clear existing content before loading new messages
            loadMessagesFromStorage().forEach(msg => {
                addMessage(msg.message, msg.isInput);
            });
            scrollToBottom();
        });

        GM_registerMenuCommand('Clear Chat History', clearLocalStorage);
        GM_registerMenuCommand('Toggle x as multiply/variable', () => {
            settings.interpretXAsMultiply = !settings.interpretXAsMultiply;
            GM_setValue('interpretXAsMultiply', settings.interpretXAsMultiply);
            alert(`x is now interpreted as ${settings.interpretXAsMultiply ? 'multiplication' : 'variable'}.`);
        });

        GM_addStyle(`
            #calculator-chat-box {
                font-family: Arial, sans-serif;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            }
            #calculator-chat-box .message {
                clear: both;
                padding: 5px 10px;
                color: white;
            }
            #calculator-chat-box .input {
                text-align: left;
            }
            #calculator-chat-box .result-label {
                float: left;
                margin-left: 5px;
                background-color: #1a73e8; /* Improved contrast */
                color: white;
                border-radius: 10px;
                padding: 3px 6px;
            }
            #calculator-chat-box .user-label {
                float: left;
                margin-left: 5px;
                background-color: #34a853; /* Improved contrast */
                color: white;
                border-radius: 10px;
                padding: 3px 6px;
            }
            #calculator-chat-box hr {
                border-top: 1px solid white;
                margin: 5px 0;
            }
            #calculator-chat-box.chat-dragging {
                cursor: move;
            }
        `);

        document.body.appendChild(chatBox);
    }
})();