General Calculator

Calculator that should work on every page :p

// ==UserScript==
// @name         General Calculator
// @namespace    https://greasyfork.org/en/users/1291009
// @version      2.2.1
// @description  Calculator that should work on every page :p
// @author       BadOrBest
// @license      MIT
// @icon         https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSITQfCUO2ak5KcOYOskQl6nrLCyC7v73kkB0eIUyu5rsErnN45
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM.registerMenuCommand
// @run-at       document-end
// ==/UserScript==
(function() {
    'use strict';

    var MAX_MESSAGES = 400; // Maximum number of messages to store

    var chatBox = document.createElement('div');
    chatBox.id = 'calculator-chat-box';
    chatBox.style.position = 'fixed';
    chatBox.style.bottom = '10vh'; // Adjusted for mobile viewport
    chatBox.style.right = '5vw'; // Adjusted for mobile viewport
    chatBox.style.width = 'calc(90vw - 10px)'; // Adjusted for mobile viewport
    chatBox.style.maxWidth = '300px';
    chatBox.style.overflowY = 'hidden';
    chatBox.style.backgroundColor = '#222';
    chatBox.style.borderRadius = '20px';
    chatBox.style.padding = '0';
    chatBox.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
    chatBox.style.zIndex = '9999';
    chatBox.style.display = 'flex';
    chatBox.style.flexDirection = 'column';

    // Draggable functionality
    var isDragging = false;
    var initialX, initialY;
    var offsetX = 0;
    var offsetY = 0;
    var selectedForArrowKeys = false; // Added variable to track if chatBox is selected for arrow key dragging

    chatBox.addEventListener('mousedown', startDragging);
    chatBox.addEventListener('touchstart', startDragging);
    chatBox.addEventListener('click', selectForArrowKeys); // Listen for click to select chatBox for arrow key dragging

    // Listen for click events on the document to unselect the chatbox
    document.addEventListener('click', function(event) {
        if (!chatBox.contains(event.target)) {
            selectedForArrowKeys = false; // Clicked outside of the chatbox, unselect it
        }
    });

    document.addEventListener('keydown', handleArrowKeys);

    function startDragging(event) {
        if (event.target === chatInput) {
            return; // Don't start dragging if clicking on the input field
        }
        event.preventDefault();
        if (event.type === 'mousedown') {
            initialX = event.clientX - offsetX;
            initialY = event.clientY - offsetY;
        } else if (event.type === 'touchstart') {
            initialX = event.touches[0].clientX - offsetX;
            initialY = event.touches[0].clientY - offsetY;
        }
        isDragging = true;
        chatBox.classList.add('chat-dragging');

        document.addEventListener('mousemove', drag);
        document.addEventListener('touchmove', drag);

        document.addEventListener('mouseup', stopDragging);
        document.addEventListener('touchend', stopDragging);
    }

    function drag(event) {
        event.preventDefault();
        if (isDragging) {
            var currentX, currentY;
            if (event.type === 'mousemove') {
                currentX = event.clientX - initialX;
                currentY = event.clientY - initialY;
            } else if (event.type === 'touchmove') {
                currentX = event.touches[0].clientX - initialX;
                currentY = event.touches[0].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);
        document.removeEventListener('touchmove', drag);

        document.removeEventListener('mouseup', stopDragging);
        document.removeEventListener('touchend', stopDragging);
    }

    function handleArrowKeys(event) {
        if (!selectedForArrowKeys) {
            return; // If chatBox is not selected for arrow key dragging, exit function
        }
        var step = 10;
        switch (event.key) {
            case 'ArrowUp':
                offsetY -= step;
                break;
            case 'ArrowDown':
                offsetY += step;
                break;
            case 'ArrowLeft':
                offsetX -= step;
                break;
            case 'ArrowRight':
                offsetX += step;
                break;
        }
        chatBox.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
    }

    function selectForArrowKeys(event) {
        selectedForArrowKeys = true; // Set chatBox as selected for arrow key dragging
    }

    // Title
    var chatTitle = document.createElement('div');
    chatTitle.style.background = '#444';
    chatTitle.style.color = 'white';
    chatTitle.style.padding = '10px';
    chatTitle.style.textAlign = 'center';
    chatTitle.style.fontWeight = 'bold';
    chatTitle.textContent = 'Calculator';
    chatBox.appendChild(chatTitle);

    // Collapse Button
    var collapseButton = document.createElement('button');
    collapseButton.textContent = '▼';
    collapseButton.style.position = 'absolute';
    collapseButton.style.top = '0';
    collapseButton.style.right = '0';
    collapseButton.style.margin = '10px';
    collapseButton.style.width = '30px';
    collapseButton.style.height = '30px';
    collapseButton.style.fontSize = '20px';
    collapseButton.style.border = 'none';
    collapseButton.style.backgroundColor = '#444';
    collapseButton.style.color = 'white';
    collapseButton.style.cursor = 'pointer';
    collapseButton.style.borderRadius = '50%';

    // Add click event listener
    collapseButton.addEventListener('click', function(event) {
        toggleChatBox(event);
    });

    // Add touch event listener
    collapseButton.addEventListener('touchstart', function(event) {
        toggleChatBox(event);
    });

    chatBox.appendChild(collapseButton);

    // Toggle chat box visibility
    function toggleChatBox(event) {
        // Stop propagation of the click event to prevent it from triggering dragging
        event.stopPropagation();

        if (chatBox.style.height === 'auto' || chatBox.style.height === '') {
            chatBox.style.height = '70px'; // Set to a fixed height
            collapseButton.textContent = '▲';
            chatInput.style.display = 'none'; // Hide the input field
        } else {
            chatBox.style.height = 'auto';
            collapseButton.textContent = '▼';
            chatInput.style.display = 'block'; // Show the input field
        }
    }

    // Chat history container
    var chatHistory = document.createElement('div');
    chatHistory.id = 'chat-history';
    chatHistory.style.height = 'calc(50vh - 110px)';
    chatHistory.style.overflowY = 'auto';
    chatHistory.style.backgroundColor = '#333';
    chatBox.appendChild(chatHistory);

    // Function to load messages from localStorage
    function loadMessagesFromStorage() {
        var messages = [];
        try {
            messages = JSON.parse(localStorage.getItem('chatHistory')) || [];
        } catch (error) {
            console.error('Error loading chat history from localStorage:', error);
            // Clear localStorage if there's an error (e.g., quota exceeded)
            clearLocalStorage();
        }
        return messages;
    }

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

// Register menu commands using GM.registerMenuCommand
GM.registerMenuCommand('Clear Chat History', clearLocalStorage);


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

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

// Function to scroll to the bottom of the chat history
function scrollToBottom() {
    chatHistory.scrollTop = chatHistory.scrollHeight;
}

// Function to check and scroll to bottom if necessary
function checkAndScrollToBottom() {
    // Check if user is already at the bottom or if there's a new message
    if (chatHistory.scrollTop + chatHistory.clientHeight === chatHistory.scrollHeight) {
        scrollToBottom();
    }
}
      // Initial scroll to bottom when the page loads or chat initializes
scrollToBottom();

        // Save message to localStorage
        var messages = loadMessagesFromStorage();
        messages.push({ message: message, isInput: isInput });

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

        try {
            localStorage.setItem('chatHistory', JSON.stringify(messages));
        } catch (error) {
            console.error('Error saving chat history to localStorage:', error);
            // Clear localStorage if there's an error (e.g., quota exceeded)
            clearLocalStorage();
        }
    }

    // Function to evaluate and display the result
    function evaluateExpression(expression) {
        try {
            // Replace 'x' with '*' for mathematical operations
            expression = expression.replace(/(x)/g, '*');
            // Handling algebraic expressions
            var result;
            if (/\b[A-Za-z]+\b/.test(expression)) {
                // If expression contains letters
                var match = expression.match(/\b[A-Za-z]+\b/);
                var letter = match[0];
                var equation = expression.replace(/\b[A-Za-z]+\b/g, '0'); // Replace letters with '0' for evaluation
                result = solveEquation(equation, letter);
            } else {
                // If expression is purely mathematical
                result = eval(expression);
            }
            addMessage('<span class="bot-label">(Calc):</span> ' + result, false);
        } catch (error) {
            addMessage('<span class="bot-label">(Calc):</span> Error: ' + error.message, false);
        }
    }

    // Function to solve algebraic equation
    function solveEquation(equation, letter) {
        // For simplicity, let's assume linear equations of the form 'ax = b'
        var sides = equation.split('=');
        var a = eval(sides[0]);
        var b = eval(sides[1]);
        var result = b / a;
        return letter + ' = ' + result;
    }

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

    // Input field
    var chatInput = document.createElement('input');
    chatInput.type = 'text';
    chatInput.placeholder = 'Type here...';
    chatInput.style.width = 'calc(100% - 20px)';
    chatInput.style.padding = '10px';
    chatInput.style.margin = '10px auto'; // Centered horizontally
    chatInput.style.borderRadius = '10px';
    chatInput.style.border = 'none';
    chatInput.style.backgroundColor = '#444';
    chatInput.style.color = 'white';
    chatInput.style.zIndex = '10000'; // Ensure input field is above other elements
    chatBox.appendChild(chatInput);

    // Listen for Enter key press to send message
    chatInput.addEventListener('keydown', function(event) {
        if (event.key === 'Enter') {
            event.preventDefault();
            sendMessage();
        }
    });

    // CSS style for user and bot messages
    var style = document.createElement('style');
    style.innerHTML = `
        .message {
            clear: both;
            padding: 5px 10px;
            color: white;
        }
        .input {
            text-align: left;
        }
        .bot-label {
            float: left;
            margin-left: 5px;
            background-color: blue;
            color: white;
            border-radius: 10px;
            padding: 3px 6px;
        }
        .user-label {
            float: left;
            margin-left: 5px;
            background-color: green;
            color: white;
            border-radius: 10px;
            padding: 3px 6px;
        }
        hr {
            border-top: 1px solid white;
            margin: 5px 0;
        }
        .chat-dragging * {
            /* Removed user-select: none; to enable touch interactions */
        }
    `;
    document.head.appendChild(style);

    // Append chatBox to body
    document.body.appendChild(chatBox);

    // Load messages from localStorage on page load
    window.addEventListener('load', function() {
        var messages = loadMessagesFromStorage();
        messages.forEach(function(msg) {
            addMessage(msg.message, msg.isInput);
        });
    });

})();


// Mathematical functions
Math.sinh = function(x) { return (Math.exp(x) - Math.exp(-x)) / 2; };
Math.cosh = function(x) { return (Math.exp(x) + Math.exp(-x)) / 2; };
Math.tanh = function(x) { return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); };
Math.asinh = function(x) { return Math.log(x + Math.sqrt(x * x + 1)); };
Math.acosh = function(x) { return Math.log(x + Math.sqrt(x * x - 1)); };
Math.atanh = function(x) { return Math.log((1 + x) / (1 - x)) / 2; };

// Mathematical constants
var E = Math.E; // Euler's number
var PI = Math.PI; // Pi
var SQRT2 = Math.SQRT2; // Square root of 2
var SQRT1_2 = Math.SQRT1_2; // Square root of 1/2

// Additional mathematical operations
Math.factorial = function(n) {
    if (n === 0 || n === 1)
        return 1;
    for (var i = n - 1; i >= 1; i--) {
        n *= i;
    }
    return n;
};

Math.permutation = function(n, r) {
    return Math.factorial(n) / Math.factorial(n - r);
};

Math.combination = function(n, r) {
    return Math.factorial(n) / (Math.factorial(r) * Math.factorial(n - r));
};

Math.roundTo = function(value, decimalPlaces) {
    var multiplier = Math.pow(10, decimalPlaces);
    return Math.round(value * multiplier) / multiplier;
};

// Trigonometric functions
Math.degToRad = function(degrees) {
    return degrees * (Math.PI / 180);
};

Math.radToDeg = function(radians) {
    return radians * (180 / Math.PI);
};

// Logarithmic functions
Math.log10 = function(x) {
    return Math.log(x) / Math.log(10);
};

Math.log2 = function(x) {
    return Math.log(x) / Math.log(2);
};

// Exponential functions
Math.exp10 = function(x) {
    return Math.pow(10, x);
};

Math.exp2 = function(x) {
    return Math.pow(2, x);
};

// Additional mathematical functions
Math.abs = function(x) { return Math.abs(x); };
Math.ceil = function(x) { return Math.ceil(x); };
Math.floor = function(x) { return Math.floor(x); };
Math.max = function(...args) { return Math.max(...args); };
Math.min = function(...args) { return Math.min(...args); };
Math.pow = function(x, y) { return Math.pow(x, y); };
Math.sqrt = function(x) { return Math.sqrt(x); };
Math.sign = function(x) { return Math.sign(x); };
Math.randomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; };
Math.clamp = function(x, min, max) { return Math.max(min, Math.min(max, x)); };
Math.hypot = function(...args) { return Math.hypot(...args); };

// Constants
var LN2 = Math.LN2; // Natural logarithm of 2
var LN10 = Math.LN10; // Natural logarithm of 10
var LOG2E = Math.LOG2E; // Base 2 logarithm of E
var LOG10E = Math.LOG10E; // Base 10 logarithm of E
var SQRT3 = Math.SQRT3; // Square root of 3

// Exponential constants
var EXP = Math.exp(1); // Euler's number

// Trigonometric constants
var DEG_PER_RAD = 180 / Math.PI; // Degrees per radian
var RAD_PER_DEG = Math.PI / 180; // Radians per degree
var TWO_PI = 2 * Math.PI; // 2 * Pi

// Logarithmic constants
var LN_2 = Math.LN2; // Natural logarithm of 2
var LN_10 = Math.LN10; // Natural logarithm of 10
var LOG2_E = Math.LOG2E; // Base 2 logarithm of E
var LOG10_E = Math.LOG10E; // Base 10 logarithm of E

// Exponential constants
var SQRT_2 = Math.SQRT2; // Square root of 2
var SQRT_1_2 = Math.SQRT1_2; // Square root of 1/2

// Additional mathematical operations
Math.lcm = function(a, b) { return (!a || !b) ? 0 : Math.abs((a * b) / Math.gcd(a, b)); };
Math.gcd = function(a, b) { return (!b) ? a : Math.gcd(b, a % b); };
Math.factorial = function(n) { return (n !== 1 && n !== 0) ? n * Math.factorial(n - 1) : 1; };
Math.toDegrees = function(radians) { return radians * DEG_PER_RAD; };
Math.toRadians = function(degrees) { return degrees * RAD_PER_DEG; };
Math.isPrime = function(n) {
    if (n <= 1) return false;
    if (n <= 3) return true;
    if (n % 2 === 0 || n % 3 === 0) return false;
    let i = 5;
    while (i * i <= n) {
        if (n % i === 0 || n % (i + 2) === 0) return false;
        i += 6;
    }
    return true;
};