嘉立创EDA专业版增强脚本

对PC端嘉立创EDA专业版进行触摸适配,以及显示FPS等功能增强

目前為 2025-03-12 提交的版本,檢視 最新版本

// ==UserScript==
// @name         嘉立创EDA专业版增强脚本
// @namespace    http://tampermonkey.net/
// @version      1.4
// @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: true,
    enablePinch: true,
    enablePress: false,
    showFPS: false,        // 显示FPS
  };

  constructor () {
    this.config = null;
    this.listeners = new Set();
    this.menuCommands = new Map(); // 存储菜单命令的引用
    this.menuItems = [
      { key: 'enablePan', text: '启用触摸拖动' },
      { key: 'enablePinch', text: '启用触摸缩放' },
      { key: 'enablePress', text: '启用触摸长按' },
      { key: 'showFPS', text: '显示FPS' }
    ];
  }

  // 加载配置
  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();

    for (const { key, text } of this.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 = this.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);



})();

QingJ © 2025

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