Advanced Calculator

A unified calculator tool with chat history and draggable UI.

  1. // ==UserScript==
  2. // @name Advanced Calculator
  3. // @namespace https://gf.qytechs.cn/en/users/1291009
  4. // @version 6.1.5
  5. // @description A unified calculator tool with chat history and draggable UI.
  6. // @author BadOrBest
  7. // @license MIT
  8. // @icon https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQE4akrlePwM0brye6bimtz0ziOengL_C9rhQ&s
  9. // @match *://*/*
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_deleteValue
  14. // @grant GM_registerMenuCommand
  15. // @run-at document-end
  16. // ==/UserScript==
  17. (function() {
  18. 'use strict';
  19.  
  20. // Load external libraries
  21. const scriptLoad = url => new Promise((resolve, reject) => {
  22. const script = document.createElement('script');
  23. script.src = url;
  24. script.onload = resolve;
  25. script.onerror = reject;
  26. document.head.appendChild(script);
  27. });
  28.  
  29. Promise.all([
  30. scriptLoad('https://cdnjs.cloudflare.com/ajax/libs/mathjs/13.1.1/math.min.js'),
  31. scriptLoad('https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js')
  32. ]).then(() => {
  33. console.log('Libraries loaded successfully.');
  34. initCalculator();
  35. }).catch(error => console.error('Error loading libraries:', error));
  36.  
  37. // General settings
  38. const settings = {
  39. theme: GM_getValue('theme', 'dark'),
  40. historyPageSize: GM_getValue('historyPageSize', 50),
  41. interpretXAsMultiply: GM_getValue('interpretXAsMultiply', true) // Default to true
  42. };
  43.  
  44. // Calculator Initialization
  45. function initCalculator() {
  46. const MAX_MESSAGES = 400;
  47. const STORAGE_KEY = 'chatHistory';
  48. let lastResult = null; // Store the last result
  49.  
  50. const chatBox = document.createElement('div');
  51. chatBox.id = 'calculator-chat-box';
  52. Object.assign(chatBox.style, {
  53. position: 'fixed',
  54. bottom: '10vh',
  55. right: '5vw',
  56. width: 'calc(90vw - 10px)',
  57. maxWidth: '350px',
  58. backgroundColor: settings.theme === 'dark' ? '#1f1f1f' : '#f9f9f9',
  59. borderRadius: '12px',
  60. padding: '10px',
  61. boxShadow: '0 0 15px rgba(0, 0, 0, 0.3)',
  62. zIndex: '9999',
  63. display: 'flex',
  64. flexDirection: 'column',
  65. fontFamily: 'Arial, sans-serif'
  66. });
  67.  
  68. const chatHeader = document.createElement('div');
  69. chatHeader.style.display = 'flex';
  70. chatHeader.style.justifyContent = 'space-between';
  71. chatHeader.style.alignItems = 'center';
  72. chatHeader.style.backgroundColor = settings.theme === 'dark' ? '#333' : '#ddd';
  73. chatHeader.style.color = settings.theme === 'dark' ? '#fff' : '#333';
  74. chatHeader.style.padding = '10px';
  75. chatHeader.style.borderRadius = '10px 10px 0 0';
  76. chatHeader.style.fontWeight = 'bold';
  77. chatHeader.textContent = 'Advanced Calculator';
  78.  
  79. const collapseButton = document.createElement('button');
  80. collapseButton.textContent = '▼';
  81. collapseButton.style.padding = '5px 10px';
  82. collapseButton.style.border = 'none';
  83. collapseButton.style.backgroundColor = '#555';
  84. collapseButton.style.color = '#fff';
  85. collapseButton.style.cursor = 'pointer';
  86. collapseButton.style.borderRadius = '5px';
  87. collapseButton.addEventListener('click', toggleChatBox);
  88.  
  89. chatHeader.appendChild(collapseButton);
  90. chatBox.appendChild(chatHeader);
  91.  
  92. const chatHistory = document.createElement('div');
  93. chatHistory.id = 'chat-history';
  94. Object.assign(chatHistory.style, {
  95. height: '300px',
  96. overflowY: 'auto',
  97. backgroundColor: settings.theme === 'dark' ? '#333' : '#fff',
  98. color: settings.theme === 'dark' ? '#fff' : '#333',
  99. padding: '10px',
  100. borderRadius: '8px'
  101. });
  102. chatBox.appendChild(chatHistory);
  103.  
  104. const chatInput = document.createElement('input');
  105. chatInput.id = 'chat-input';
  106. chatInput.type = 'text';
  107. Object.assign(chatInput.style, {
  108. width: 'calc(100% - 20px)',
  109. padding: '10px',
  110. margin: '10px auto',
  111. borderRadius: '10px',
  112. border: 'none',
  113. backgroundColor: '#444',
  114. color: 'white'
  115. });
  116. chatInput.placeholder = 'Type here...';
  117. chatInput.addEventListener('keydown', event => {
  118. if (event.key === 'Enter') {
  119. event.preventDefault();
  120. if (chatInput.value.trim() === '') {
  121. if (lastResult !== null) {
  122. chatInput.value = lastResult; // Insert the last result into the input field
  123. }
  124. } else {
  125. sendMessage();
  126. }
  127. }
  128. });
  129. chatBox.appendChild(chatInput);
  130.  
  131. // Draggable functionality
  132. let isDragging = false;
  133. let initialX, initialY;
  134. let offsetX = 0, offsetY = 0;
  135.  
  136. chatBox.addEventListener('mousedown', startDragging);
  137. chatBox.addEventListener('touchstart', startDragging);
  138.  
  139. document.addEventListener('mousemove', drag);
  140. document.addEventListener('touchmove', drag);
  141. document.addEventListener('mouseup', stopDragging);
  142. document.addEventListener('touchend', stopDragging);
  143.  
  144. function startDragging(event) {
  145. if (event.target === chatInput) return;
  146. event.preventDefault();
  147. initialX = event.clientX - offsetX;
  148. initialY = event.clientY - offsetY;
  149. isDragging = true;
  150. chatBox.classList.add('chat-dragging');
  151. document.addEventListener('mousemove', drag); // Attach listeners only on start
  152. document.addEventListener('touchmove', drag);
  153. }
  154.  
  155. function drag(event) {
  156. if (!isDragging) return;
  157. event.preventDefault();
  158. const currentX = event.clientX - initialX;
  159. const currentY = event.clientY - initialY;
  160. offsetX = currentX;
  161. offsetY = currentY;
  162. chatBox.style.transform = `translate(${currentX}px, ${currentY}px)`;
  163. }
  164.  
  165. function stopDragging() {
  166. isDragging = false;
  167. chatBox.classList.remove('chat-dragging');
  168. document.removeEventListener('mousemove', drag); // Detach listeners on stop
  169. document.removeEventListener('touchmove', drag);
  170. }
  171.  
  172. function toggleChatBox() {
  173. if (chatBox.style.height === '70px') {
  174. chatBox.style.height = 'auto';
  175. collapseButton.textContent = '▼';
  176. chatInput.style.display = 'block';
  177. } else {
  178. chatBox.style.height = '70px';
  179. collapseButton.textContent = '▲';
  180. chatInput.style.display = 'none';
  181. }
  182. }
  183.  
  184. // Function to load messages from localStorage
  185. function loadMessagesFromStorage() {
  186. let messages = [];
  187. try {
  188. messages = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
  189. } catch (error) {
  190. console.error('Error loading chat history from localStorage:', error);
  191. clearLocalStorage();
  192. }
  193. return messages;
  194. }
  195.  
  196. // Function to clear localStorage
  197. function clearLocalStorage() {
  198. try {
  199. localStorage.removeItem(STORAGE_KEY);
  200. } catch (error) {
  201. console.error('Error clearing localStorage:', error);
  202. }
  203. }
  204.  
  205. // Function to add message to conversation and save to localStorage
  206. function addMessage(message, isInput) {
  207. const messageElement = document.createElement('div');
  208. messageElement.className = 'message ' + (isInput ? 'input' : 'output');
  209. messageElement.innerHTML = message;
  210. chatHistory.appendChild(messageElement);
  211.  
  212. if (!isInput) {
  213. const line = document.createElement('hr');
  214. line.style.borderTop = '1px solid white';
  215. line.style.margin = '5px 0';
  216. chatHistory.appendChild(line);
  217. }
  218.  
  219. function scrollToBottom() {
  220. chatHistory.scrollTop = chatHistory.scrollHeight;
  221. }
  222.  
  223. scrollToBottom();
  224.  
  225. let messages = loadMessagesFromStorage();
  226. messages.push({ message: message, isInput: isInput });
  227.  
  228. if (messages.length > MAX_MESSAGES) {
  229. messages = messages.slice(-MAX_MESSAGES);
  230. }
  231.  
  232. try {
  233. localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
  234. } catch (error) {
  235. if (error.name === 'QuotaExceededError') {
  236. console.error('LocalStorage is full, clearing oldest messages.');
  237. messages.shift(); // Remove the oldest message and try saving again
  238. localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
  239. } else {
  240. console.error('Error saving chat history:', error);
  241. clearLocalStorage();
  242. }
  243. }
  244. }
  245.  
  246. function sendMessage() {
  247. const expression = chatInput.value.trim();
  248. if (expression !== '') {
  249. addMessage('<span class="user-label">(User):</span> ' + expression, true);
  250. evaluateExpression(expression);
  251. chatInput.value = '';
  252. }
  253. }
  254.  
  255. function evaluateExpression(expression) {
  256. try {
  257. if (settings.interpretXAsMultiply) {
  258. expression = expression.replace(/(\d+)\s*x\s*(\d+)/gi, '$1*$2'); // Handles better
  259. }
  260. const result = math.evaluate(expression);
  261. addMessage('<span class="result-label">(Result):</span> ' + result, false);
  262. lastResult = result; // Store the result for reuse
  263. } catch (error) {
  264. addMessage('<span class="result-label">(Result):</span> Error: ' + error.message, false);
  265. }
  266. }
  267.  
  268. window.addEventListener('load', () => {
  269. // Prevent duplicate loading of messages
  270. chatHistory.innerHTML = ''; // Clear existing content before loading new messages
  271. loadMessagesFromStorage().forEach(msg => {
  272. addMessage(msg.message, msg.isInput);
  273. });
  274. scrollToBottom();
  275. });
  276.  
  277. GM_registerMenuCommand('Clear Chat History', clearLocalStorage);
  278. GM_registerMenuCommand('Toggle x as multiply/variable', () => {
  279. settings.interpretXAsMultiply = !settings.interpretXAsMultiply;
  280. GM_setValue('interpretXAsMultiply', settings.interpretXAsMultiply);
  281. alert(`x is now interpreted as ${settings.interpretXAsMultiply ? 'multiplication' : 'variable'}.`);
  282. });
  283.  
  284. GM_addStyle(`
  285. #calculator-chat-box {
  286. font-family: Arial, sans-serif;
  287. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  288. }
  289. #calculator-chat-box .message {
  290. clear: both;
  291. padding: 5px 10px;
  292. color: white;
  293. }
  294. #calculator-chat-box .input {
  295. text-align: left;
  296. }
  297. #calculator-chat-box .result-label {
  298. float: left;
  299. margin-left: 5px;
  300. background-color: #1a73e8; /* Improved contrast */
  301. color: white;
  302. border-radius: 10px;
  303. padding: 3px 6px;
  304. }
  305. #calculator-chat-box .user-label {
  306. float: left;
  307. margin-left: 5px;
  308. background-color: #34a853; /* Improved contrast */
  309. color: white;
  310. border-radius: 10px;
  311. padding: 3px 6px;
  312. }
  313. #calculator-chat-box hr {
  314. border-top: 1px solid white;
  315. margin: 5px 0;
  316. }
  317. #calculator-chat-box.chat-dragging {
  318. cursor: move;
  319. }
  320. `);
  321.  
  322. document.body.appendChild(chatBox);
  323. }
  324. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址