Greasy Fork is available in English.

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