您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
对PC端嘉立创EDA专业版进行触摸适配,以及显示FPS等功能增强
当前为
// ==UserScript== // @name 嘉立创EDA专业版增强脚本 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 对PC端嘉立创EDA专业版进行触摸适配,以及显示FPS等功能增强 // @author github@xiaowine // @match https://pro.lceda.cn/editor?* // @require https://cdn.jsdelivr.net/gh/hammerjs/hammer.js@ff687ea0daa3c806b9accd2ecb1a46165ea3c00a/hammer.min.js // @grant GM.getValue // @grant GM.setValue // @grant GM.registerMenuCommand // @run-at document-end // @license GPL // ==/UserScript== // 配置管理类 class ConfigManager { static DEFAULT_CONFIG = { enablePan: false, enablePinch: false, enablePress: false, showFPS: false, }; constructor () { this.config = null; this.listeners = new Set(); this.menuCommands = new Map(); // 存储菜单命令的引用 } // 加载配置 async load () { try { const savedConfig = await GM.getValue('touchConfig', null); this.config = savedConfig ? JSON.parse(savedConfig) : ConfigManager.DEFAULT_CONFIG; } catch (error) { console.error('加载配置失败:', error); this.config = ConfigManager.DEFAULT_CONFIG; } return this.config; } // 保存配置 async save () { await GM.setValue('touchConfig', JSON.stringify(this.config)); this.notifyListeners(); } // 切换配置项 async toggleSetting (key) { if (this._updating) return; // 防止重复触发 this.config[key] = !this.config[key]; await this.save(); await this.updateMenu(key); } // 获取配置 getConfig () { return this.config; } // 注册配置变更监听器 addChangeListener (listener) { this.listeners.add(listener); } // 移除配置变更监听器 removeChangeListener (listener) { this.listeners.delete(listener); } // 通知所有监听器 notifyListeners () { this.listeners.forEach(listener => listener(this.config)); } // 初始化菜单 async initMenu () { // 确保先清理旧菜单 await this.unregisterAllMenus(); const menuItems = [ { key: 'enablePan', text: '启用触摸拖动' }, { key: 'enablePinch', text: '启用触摸缩放' }, { key: 'enablePress', text: '启用触摸长按' }, { key: 'showFPS', text: '显示FPS' } ]; for (const { key, text } of menuItems) { const command = await GM.registerMenuCommand( `${this.config[key] ? '✅' : '❌'} ${text}`, () => this.toggleSetting(key) ); this.menuCommands.set(key, command); } } // 更新菜单项显示 async updateMenu (key) { // 防止重复更新 if (this._updating) return; this._updating = true; try { const item = menuItems.find(item => item.key === key); if (item) { // 先注销所有旧的菜单命令 await this.unregisterAllMenus(); // 重新注册所有菜单命令 await this.initMenu(); } } finally { this._updating = false; } } // 注销所有菜单命令 async unregisterAllMenus () { for (const command of this.menuCommands.values()) { if (command) { await GM.unregisterMenuCommand(command); } } this.menuCommands.clear(); } } // TouchEventHandler类修改 class TouchEventHandler { constructor (targetElement, configManager) { this.targetElement = targetElement; this.hammer = null; this.scaleThreshold = 0.1; this.scrollSensitivity = 0.1; this.lastScale = 1; this.configManager = configManager; // 监听配置变更 this.configManager.addChangeListener(() => { this.destroy(); this.init(); }); } init () { if (!this.targetElement) { console.log("未找到目标元素"); return false; } console.log("目标元素已获取,初始化Hammer.js:", this.targetElement); // 修改Hammer初始化方式 this.hammer = new Hammer.Manager(this.targetElement, { touchAction: 'none', inputClass: Hammer.TouchInput }); // 添加识别器 this.hammer.add(new Hammer.Pan({ direction: Hammer.DIRECTION_ALL })); this.hammer.add(new Hammer.Pinch()); this.hammer.add(new Hammer.Press({ time: 500 })); this.addEventListeners(); return true; } getSVGCenter () { const rect = this.targetElement.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2, }; } addEventListeners () { console.log("添加Hammer.js事件监听"); const config = this.configManager.getConfig(); // 添加调试监听 this.hammer.on('hammer.input', (ev) => { console.log('Hammer input:', ev.type, ev); }); this.hammer.on('panstart', (ev) => { console.log('Pan start triggered:', ev); const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true, cancelable: true, button: 1, clientX: ev.center.x, clientY: ev.center.y }); this.targetElement.dispatchEvent(mouseDownEvent); }); this.hammer.on('panmove', (ev) => { console.log('Pan move:', { x: ev.center.x, y: ev.center.y }); const mouseMoveEvent = new MouseEvent("mousemove", { bubbles: true, cancelable: true, button: 1, clientX: ev.center.x, clientY: ev.center.y, }); this.targetElement.dispatchEvent(mouseMoveEvent); }); this.hammer.on('pinch', (ev) => { console.log('Pinch:', { scale: ev.scale, lastScale: this.lastScale }); const scaleDiff = ev.scale - this.lastScale; if (Math.abs(scaleDiff) > this.scaleThreshold) { const center = this.getSVGCenter(); console.log('Pinch threshold reached:', { scaleDiff, center }); const scrollEvent = new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: scaleDiff > 0 ? -10 : 10, clientX: center.x, clientY: center.y, }); this.targetElement.dispatchEvent(scrollEvent); this.lastScale = ev.scale; } }); this.hammer.on('press', (ev) => { const rightClickEvent = new MouseEvent("contextmenu", { bubbles: true, cancelable: true, button: 2, clientX: ev.center.x, clientY: ev.center.y, }); this.targetElement.parentNode.dispatchEvent(rightClickEvent); }); } destroy () { if (this.hammer) { console.log("正在移除Hammer.js事件监听"); this.hammer.destroy(); this.hammer = null; } console.log("Hammer.js事件监听已移除"); } } // UI增强处理类 class UIEnhancer { constructor (configManager) { console.log('UIEnhancer: Initializing...'); this.configManager = configManager; this.fpsCounter = { element: null, frameCount: 0, previousTimestamp: 0, animationFrameHandle: null, currentFps: 0, interval: 1000, originalText: '' // 保存原始用户名 }; this.configManager.addChangeListener(() => { console.log('UIEnhancer: Config changed, updating UI'); this.updateUI(); }); } init () { console.log('UIEnhancer: Starting initialization'); this.findElements(); this.updateUI(); } findElements () { console.log('UIEnhancer: Finding username element'); // 查找用户名span元素 const usernameSpan = document.querySelector('#loginUsername span'); if (usernameSpan) { console.log('UIEnhancer: Username element found'); this.fpsCounter.element = usernameSpan; this.fpsCounter.originalText = usernameSpan.textContent; } else { console.log('UIEnhancer: Username element not found'); } } updateFps () { if (!this.configManager.getConfig().showFPS) return; const currentTimestamp = Date.now(); this.fpsCounter.frameCount++; if (currentTimestamp > this.fpsCounter.interval + this.fpsCounter.previousTimestamp) { this.fpsCounter.currentFps = Math.round( (this.fpsCounter.frameCount * 1000) / (currentTimestamp - this.fpsCounter.previousTimestamp) ); this.fpsCounter.frameCount = 0; this.fpsCounter.previousTimestamp = currentTimestamp; if (this.fpsCounter.element) { console.log('UIEnhancer: FPS updated to', this.fpsCounter.currentFps); this.fpsCounter.element.textContent = `FPS: ${this.fpsCounter.currentFps}`; } } this.fpsCounter.animationFrameHandle = requestAnimationFrame(() => this.updateFps()); } startFPSCounter () { console.log('UIEnhancer: Starting FPS counter'); if (!this.fpsCounter.animationFrameHandle && this.fpsCounter.element) { this.fpsCounter.frameCount = 0; this.fpsCounter.previousTimestamp = Date.now(); this.updateFps(); } } stopFPSCounter () { console.log('UIEnhancer: Stopping FPS counter'); if (this.fpsCounter.animationFrameHandle) { cancelAnimationFrame(this.fpsCounter.animationFrameHandle); this.fpsCounter.animationFrameHandle = null; this.fpsCounter.currentFps = 0; if (this.fpsCounter.element) { this.fpsCounter.element.textContent = this.fpsCounter.originalText; } } } destroy () { console.log('UIEnhancer: Destroying'); this.stopFPSCounter(); } // 更新UI显示 updateUI () { console.log('UIEnhancer: Updating UI'); const config = this.configManager.getConfig(); if (config.showFPS) { this.startFPSCounter(); } else { this.stopFPSCounter(); } } } // 主程序入口 (async function () { "use strict"; if (window.isScriptLoaded) { return; } window.isScriptLoaded = true; // 初始化配置管理器 const configManager = new ConfigManager(); await configManager.load(); await configManager.initMenu(); // 初始化UI增强 const uiEnhancer = new UIEnhancer(configManager); uiEnhancer.init(); document.documentElement.style.touchAction = "none"; let touchHandler = null; let lastUrl = ""; /** * 移除现有的事件监听器 */ function removeEventListeners () { if (touchHandler) { touchHandler.destroy(); touchHandler = null; return true; } return false; } /** * 初始化触摸事件处理 * @param {Element} element - 要监听触摸事件的目标元素 */ function initTouchHandler (element) { // 先移除现有的事件监听 removeEventListeners(); if (element) { console.log("正在初始化触摸处理器,目标元素:", element); touchHandler = new TouchEventHandler(element, configManager); const success = touchHandler.init(); if (success) { // 添加触摸调试信息 // element.addEventListener('touchstart', (e) => console.log('Native touchstart:', e), false); console.log("触摸事件处理已初始化"); return true; } } return false; } /** * 解析 URL 中的 tab 参数并匹配 div 和 iframe */ function parseTabParams () { const currentUrl = window.location.href; // 如果 URL 没变,则不执行解析 if (currentUrl === lastUrl) { console.log("URL 未变化,跳过执行"); return; } // 更新上次的 URL 记录 lastUrl = currentUrl; console.log("检测到 URL 变化:", currentUrl); // 解析 tab 参数 const hash = window.location.hash; const tabMatch = hash.match(/tab=([^&#]*)/); if (tabMatch) { const tabList = tabMatch[1].split("|"); console.log("Tab 参数解析为列表:", tabList); // 过滤出以 "*" 开头的项,并去除 "*" const starredTabs = tabList .filter((tab) => tab.startsWith("*")) .map((tab) => tab.substring(1)); if (starredTabs.length > 0) { console.log('以 "*" 开头的项:', starredTabs); // 调用单独的函数解析 div 和 iframe const rootElement = parseDivAndIframe(starredTabs); if (rootElement) { console.log("解析成功,初始化触摸事件处理"); initTouchHandler(rootElement); } else { console.log("未找到符合条件的元素,无法初始化触摸事件"); } } } else { console.log("未找到 tab 参数"); } } /** * 在 #tabbar_bodies 下查找 div 元素,并匹配 uuid * 如果找到符合条件的 div,则继续查找其内部的 iframe,获取 #root 或 #canvas * @param {Array} starredTabs - 以 "*" 开头的 tab 列表(去除了 "*") * @returns {Element|boolean} 如果找到匹配的元素返回该元素,未找到返回 false */ function parseDivAndIframe (starredTabs) { // 获取 #tabbar_bodies 下的所有 div const divs = document.querySelectorAll("#tabbar_bodies div"); if (divs.length === 0) { console.log("未找到任何 div"); return false; } // 遍历 div 并匹配 uuid for (let div of divs) { const uuid = div.getAttribute("uuid"); // 获取 uuid 属性 if (uuid && starredTabs.includes(uuid)) { console.log("匹配的 div:", div); // 查找 div 内的 iframe const iframe = div.querySelector("iframe"); if (iframe) { console.log("找到匹配的 iframe:", iframe); // 尝试访问 iframe 的内容 try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; // 查找 #root 或 #canvas let rootElement = iframeDoc.querySelector("#root"); if (!rootElement) { rootElement = iframeDoc.querySelector("#canvas"); } if (rootElement) { console.log("找到目标元素:", rootElement); return rootElement; } else { console.log("未找到 #root 或 #canvas"); } } catch (error) { console.error("无法访问 iframe 内容:", error); } } else { console.log("匹配的 div 内未找到 iframe"); } } } return false; // 如果没有找到匹配的 div 和 iframe } window.addEventListener("popstate", parseTabParams); })();