Advanced Calculator

A unified calculator tool with chat history and draggable UI.

// ==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);
    }
})();