米家中枢极客版助手

登录极客版页面后,自动开启助手插件,显示设备、变量与自动化的关系,方便查找设备或变量用在了哪些自动化中。点击自动化规则名称即可跳转到自动化页面并高亮所对应的设备或变量卡片。支持快捷键折叠/展开,关闭,适应画布布局,设备高亮,日志高亮,自动适应画布、设置自动化列表布局样式等功能。

// ==UserScript==
// @name         米家中枢极客版助手
// @namespace    http://tampermonkey.net/
// @version      v0.9.5
// @description  登录极客版页面后,自动开启助手插件,显示设备、变量与自动化的关系,方便查找设备或变量用在了哪些自动化中。点击自动化规则名称即可跳转到自动化页面并高亮所对应的设备或变量卡片。支持快捷键折叠/展开,关闭,适应画布布局,设备高亮,日志高亮,自动适应画布、设置自动化列表布局样式等功能。
// @author       王丰,sk163
// @license      MIT
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==
/**
 * v0.9.5更新:
 * 1、自动化中设备和变量列表默认隐藏,可通过详情按钮进行展开。非常感谢原作者:【枋柚梓】PR的代码
 *
 * v0.9.4更新:
 * 1、修复变量可能引起的初始化错误
 *
 * v0.9.3更新:
 * 1、增加了在自动化中高亮显示指定的设备或变量名称
 * 2、修改了描述内容,[规则]的命名统一替换为了[自动化],方便理解
 * 3、修复其它已知问题
 *
 * v0.9.2更新:
 * 1、修复已知问题
 *
 * v0.9.1更新:
 * 1、增加了对变量视图的支持(变量类型、变量名称、变量值的筛选、变量排序、对应规则编排中的变量卡片高亮显示等)
 * 2、调整了选项布局
 * 3、支持了对表格文本的选择,方便复制
 * 注意:仅兼容极客版v1.6.0版本
 *
 * v0.9.0更新:
 * 1、适配v1.6.0极客版
 * 注意:仅兼容极客版v1.6.0版本
 *
 * v0.8.17更新:
 * 1、修正文本变量排序(作者:Derstood)
 *
 * v0.8.16更新:
 * 1、新增变量按照名称排序(作者:Derstood)
 *
 * v0.8.15更新:
 * 1、修复自动化列表在多列显示时,筛选格式混乱问题(作者:lqs1848)
 * 2、修复自动化列表在多列显示时,第一个自动化显示错位问题(作者:lqs1848)
 *
 * v0.8.14更新:
 * 1、增加刷新助手按钮,同时增加了快捷键Ctrl+R。查看所有的快捷键,可在极客版画布的【使用指南】中进行查看。
 * 2、优化了自动化规则列表在一行多列下的显示效果。
 * 3、修正了一些已知问题
 *
 * v0.8.13更新:
 * 1、修复了一行显示多列时,由于规则名称过长导致显示错位的问题
 *
 * v0.8.12更新:
 * 1、增加了快捷键Ctrl+W,关闭当前的画布
 * 2、将快捷键说明增加到了原生的【使用指南】中
 *
 * v0.8.11更新:
 * 1、规则列表换行显示
 *
 * v0.8.10更新:
 * 1、修复了窗口过小的问题
 * 2、取消了规则列表最小宽度限制
 *
 * v0.8.9更新:
 * 1、修改了日志高亮逻辑,优化了执行效率,提升了性能,减少无用的循环
 * 2、参考了米家自动化极客版样式优化(感谢原作者:lqs1848,https://greatest.deepsurf.us/zh-CN/scripts/495833),增加了规则列表样式设置选项,可选择每行显示1-5条规则
 * 3、增加了自动折叠窗口的选项
 * 4、修正了一些已知问题
 *
 * v0.8.8更新:
 * 1. 更改了插件名称
 * 2、增加了快捷键,Ctrl/Command+E折叠/展开,Ctrl/Command+Q关闭,Ctrl/Command+B适应画布布局
 * 3、自动画布布局修改为仅在初次进入规则编排页面或激活编排页面时触发
 * 4、极客版登录后自动启动插件,无需再点击设备列表激活
 * 5、修正了一些已知问题
 */
(async () => {
  const callAPI = (api, params) => {
    return new Promise(res => editor.gateway.callAPI(api, params, res));
  };
  let scriptTitle = GM_info.script.name;
  let scriptVersion = GM_info.script.version;
  let isInit = false;
  let selectCardIds = '';
  let devMap = null;
  let defaultColor = '#43ad7f7f'
  let defaultWindowWidth = 1200;
  let defaultWindowHeight = 600;
  let defaultRuleStyle = '4';
  let minWindowWidth = 500;
  let minWindowHeight = 100;
  let enableEnhancedDisplayLog = GM_getValue("enableEnhancedDisplayLog");
  let enableAutoFitContent = GM_getValue("enableAutoFitContent");
  let enableAutoCollapseCheck = GM_getValue("enableAutoCollapseCheck");
  let backgroundColor = GM_getValue("backgroundColor");
  let windowWidth = GM_getValue("windowWidth");
  let windowHeight = GM_getValue("windowHeight");
  let ruleStyle = GM_getValue("ruleStyle");

  if (enableEnhancedDisplayLog === undefined || enableEnhancedDisplayLog === null || enableEnhancedDisplayLog === "") {
    enableEnhancedDisplayLog = true;
  }
  if (enableAutoFitContent === undefined || enableAutoFitContent === null || enableAutoFitContent === "") {
    enableAutoFitContent = true;
  }
  if (enableAutoCollapseCheck === undefined || enableAutoCollapseCheck === null || enableAutoCollapseCheck === "") {
    enableAutoCollapseCheck = false;
  }
  if (backgroundColor === undefined || backgroundColor === null || backgroundColor === "") {
    backgroundColor = defaultColor;
  }
  if (windowWidth === undefined || windowWidth === null || windowWidth === "" || isNaN(windowWidth)) {
    windowWidth = defaultWindowWidth;
  } else {
    windowWidth = parseInt(windowWidth, 10) < minWindowWidth ? minWindowWidth : parseInt(windowWidth, 10);
  }
  if (windowHeight === undefined || windowHeight === null || windowHeight === "" || isNaN(windowHeight)) {
    windowHeight = defaultWindowHeight;
  } else {
    windowHeight = parseInt(windowHeight, 10) < minWindowHeight ? minWindowHeight : parseInt(windowHeight, 10)
  }
  if (ruleStyle === undefined || ruleStyle === null || ruleStyle === "") {
    ruleStyle = defaultRuleStyle;
  }

  const executeScript = async () => {
    if (document.getElementById('device-rule-map') || isInit === true) {
      return;
    }

    if (typeof editor === 'undefined' || typeof editor.gateway === 'undefined' || typeof editor.gateway.callAPI === 'undefined') {
      console.error('editor.gateway.callAPI 方法未定义。请确保在正确的环境中运行此脚本。');
      return;
    }

    try {
      isInit = true;
      const devListResponse = await callAPI('getDevList');
      devMap = devListResponse.devList;
      const roomNames = Array.from(new Set(Object.values(devMap).map(device => device.roomName)));
      let varRuleMap = {};
      let devRuleMap = {};
      let varMap = {};

      const varScopes = (await callAPI('getVarScopeList', {})).scopes;
      for (const scope of varScopes) {
        const vars = await callAPI('getVarList', { scope: scope });
        Object.entries(vars).forEach(([vid, v]) => {
          varRuleMap[vid] = [];
          varMap[vid] = { name: v.userData.name, scope: (scope === "global" ? "全局" : "局部"), type: (v.type === "string" ? "文本" : "数值"), value: v.value }
        });
      }

      const ruleList = await callAPI('getGraphList');

      for (const rule of ruleList) {
        const content = await callAPI('getGraph', { id: rule.id });
        const dids = new Set(content.nodes.map(n => n.props?.did).filter(did => did !== undefined));
        const devCards = new Set(content.nodes.map(n => {
          return (n.props && n.id) ? { did: n.props.did, cid: n.id } : undefined;
        }).filter(card => card !== undefined));

        dids.forEach(did => {
          devRuleMap[did] = devRuleMap[did] ?? [];
          const cardIds = Array.from(devCards)
            .filter(card => card.did === did)
            .map(card => card.cid).join(',');
          const tempDevRule = {
            ruleId: rule.id,
            cardIds: cardIds,
            totalCardNum: devCards.size
          };

          devRuleMap[did].push(tempDevRule);
        });
        const varCards = new Set(content.nodes.map(node => {
          return (node.props && node.id && node.props.scope) ? { vid: node.props.id, cid: node.id } : undefined;
        }).filter(card => card !== undefined));
        const varids = new Set(content.nodes.filter(node => node.props && node.props.scope).map(node => node.props.id));
        varids.forEach(vid => {
          varRuleMap[vid] = varRuleMap[vid] ?? [];
          const cardIds = Array.from(varCards)
            .filter(card => card.vid === vid)
            .map(card => card.cid).join(',');
          const varRule = {
            ruleId: rule.id,
            cardIds: cardIds,
            totalCardNum: varCards.size
          };
          varRuleMap[vid].push(varRule);
        }
        );
      }

      const devRuleData = Object.fromEntries(
        Object.entries(devRuleMap).map(([did, devRules]) => [
          did,
          {
            device: {
              name: devMap[did]?.name ?? `did: ${did}`,
              roomName: devMap[did]?.roomName ?? `未知`
            },
            rules: devRules.map(dr => {
              const rule = ruleList.find(r => r.id === dr.ruleId);
              return {
                id: dr.ruleId,
                cardIds: dr.cardIds,
                totalCardNum: dr.totalCardNum,
                name: rule ? rule.userData.name : 'Unknown'
              };
            })
          }
        ])
      );

      const varRuleData = Object.fromEntries(
        Object.entries(varRuleMap).map(([vid, varRules]) => [
          vid,
          {
            rules: varRules.map(vr => {
              const rule = ruleList.find(r => r.id === vr.ruleId);
              return {
                id: vr.ruleId,
                cardIds: vr.cardIds,
                totalCardNum: vr.totalCardNum,
                name: rule ? rule.userData.name : 'Unknown'
              };
            })
          }
        ])
      );

      const container = document.createElement('div');
      container.id = 'device-rule-map';
      container.style.position = 'fixed';
      container.style.top = '10px';
      container.style.right = '40px';
      container.style.width = windowWidth + 'px';
      container.style.height = windowHeight + 'px';
      container.style.overflowY = 'scroll';
      container.style.backgroundColor = 'white';
      container.style.border = '1px solid #ccc';
      container.style.paddingTop = '45px';
      container.style.zIndex = 10000;
      container.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';

      const topBar = document.createElement('div');
      topBar.id = 'topBar';
      topBar.style.position = 'fixed';
      topBar.style.top = '0';
      topBar.style.right = '40px';
      topBar.style.width = windowWidth + 'px';
      topBar.style.height = '38px';
      topBar.style.backgroundColor = 'white';
      topBar.style.zIndex = 10001;
      topBar.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
      topBar.style.display = 'flex';
      topBar.style.justifyContent = 'space-between';
      topBar.style.alignItems = 'center';
      topBar.style.padding = '0 10px';

      const titleDiv = document.createElement('div');
      titleDiv.style.display = 'flex';
      const title = document.createElement('h2');
      title.style.margin = '0';
      title.textContent = scriptTitle;
      titleDiv.appendChild(title);
      const version = document.createElement('span');
      version.textContent = scriptVersion;
      version.style.marginLeft = '2px';
      version.style.paddingTop = '16px';
      version.style.fontSize = '9px';
      titleDiv.appendChild(version);

      const buttonContainer = document.createElement('div');
      buttonContainer.style.display = 'flex';
      buttonContainer.style.gap = '10px';

      const optionsButton = document.createElement('button');
      optionsButton.id = "optionsButton";
      optionsButton.textContent = '选项';
      optionsButton.title = '选项';
      optionsButton.onclick = () => {
        handleOptionsBtnClick();
      };

      const collapseButton = document.createElement('button');
      collapseButton.id = "collapseButton";
      collapseButton.textContent = '折叠';
      collapseButton.title = '快捷键为Ctrl+E';
      collapseButton.onclick = () => {
        handleCollapseBtnClick();
      };

      const closeButton = document.createElement('button');
      closeButton.id = "closeButton";
      closeButton.title = '快捷键为Ctrl+Q';
      closeButton.textContent = '关闭';
      closeButton.onclick = () => {
        closeGraphContainer();
        document.body.removeChild(container);
        isInit = false;
      }

      const refreshButton = document.createElement('button');
      refreshButton.id = "refreshButton";
      refreshButton.title = '快捷键为Ctrl+R';
      refreshButton.textContent = '刷新';
      refreshButton.onclick = () => {
        document.body.removeChild(container);
        isInit = false;
        executeScript();
        handleUrlChange();
      }


      const graphListButton = document.createElement('button');
      graphListButton.id = "graphListButton";
      graphListButton.textContent = '详情';
      graphListButton.style.display = 'none';
      graphListButton.onclick = () => {
        closeGraphContainer(true);
      }

      buttonContainer.appendChild(collapseButton);
      buttonContainer.appendChild(graphListButton);
      buttonContainer.appendChild(optionsButton);
      buttonContainer.appendChild(refreshButton);
      buttonContainer.appendChild(closeButton);

      topBar.appendChild(titleDiv);
      topBar.appendChild(buttonContainer);

      const optionsContainer = document.createElement('div');
      optionsContainer.id = 'optionsContainer';
      optionsContainer.style.display = 'flex';
      optionsContainer.style.gap = '10px';
      optionsContainer.style.position = 'fixed';
      optionsContainer.style.top = '40px';
      optionsContainer.style.right = '40px';
      optionsContainer.style.width = '890px';
      optionsContainer.style.height = '50px';
      optionsContainer.style.border = '1px solid #ccc';
      optionsContainer.style.backgroundColor = '#ddd';
      optionsContainer.style.paddingTop = '10px';
      optionsContainer.style.zIndex = 10003;
      optionsContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
      optionsContainer.style.display = 'none';

      const widthInput = document.createElement('input');
      widthInput.type = 'text';
      widthInput.placeholder = windowWidth + 'px';
      widthInput.style.width = '60px';
      widthInput.style.marginBottom = '10px';
      widthInput.style.marginLeft = '10px';
      widthInput.style.height = '28px';
      widthInput.style.borderStyle = 'solid';
      widthInput.style.borderWidth = '1px';
      widthInput.onchange = () => {
        windowWidth = parseInt(widthInput.value, 10) < minWindowWidth ? minWindowWidth : parseInt(widthInput.value, 10);
        GM_setValue("windowWidth", windowWidth);
        container.style.width = windowWidth + 'px';
        topBar.style.width = windowWidth + 'px';
      };
      const spanW = document.createElement('span');
      spanW.textContent = '宽度:';
      spanW.style.marginLeft = '10px';
      optionsContainer.appendChild(spanW);
      optionsContainer.appendChild(widthInput);

      const heightInput = document.createElement('input');
      heightInput.type = 'text';
      heightInput.placeholder = windowHeight + 'px';
      heightInput.style.width = '60px';
      heightInput.style.marginBottom = '10px';
      heightInput.style.marginLeft = '10px';
      heightInput.style.height = '28px';
      heightInput.style.borderStyle = 'solid';
      heightInput.style.borderWidth = '1px';
      heightInput.onchange = () => {
        windowHeight = parseInt(heightInput.value, 10) < minWindowHeight ? minWindowHeight : parseInt(heightInput.value, 10);
        GM_setValue("windowHeight", windowHeight);
        container.style.height = windowHeight + 'px';
      };
      const spanH = document.createElement('span');
      spanH.textContent = '高度:';
      spanH.style.marginLeft = '10px';
      optionsContainer.appendChild(spanH);
      optionsContainer.appendChild(heightInput);

      const ruleStyleSelect = document.createElement('select');
      ruleStyleSelect.style.marginBottom = '10px';
      ruleStyleSelect.style.height = '32px';
      ruleStyleSelect.style.marginLeft = '10px';
      ruleStyleSelect.style.borderStyle = 'solid';
      ruleStyleSelect.style.borderWidth = '1px';
      ruleStyleSelect.innerHTML = '<option value="1">每行1列</option>' +
        '<option value="2">每行2列</option>' +
        '<option value="3">每行3列</option>' +
        '<option value="4">每行4列</option>' +
        '<option value="5">每行5列</option>';
      ruleStyleSelect.onchange = () => {
        GM_setValue("ruleStyle", ruleStyleSelect.value);
        changeRuleListStyle(ruleStyleSelect.value);
      };
      const spanS = document.createElement('span');
      spanS.textContent = '自动化列表:';
      spanS.style.marginLeft = '10px';
      optionsContainer.appendChild(spanS);
      optionsContainer.appendChild(ruleStyleSelect);
      ruleStyleSelect.value = ruleStyle;
      changeRuleListStyle(ruleStyle);

      const colorInput = document.createElement('input');
      colorInput.type = 'text';
      colorInput.placeholder = defaultColor;
      colorInput.style.width = '80px';
      colorInput.style.marginBottom = '10px';
      colorInput.style.marginLeft = '10px';
      colorInput.style.height = '28px';
      colorInput.style.borderStyle = 'solid';
      colorInput.style.borderWidth = '1px';
      colorInput.oninput = () => {
        backgroundColor = colorInput.value;
        GM_setValue("backgroundColor", backgroundColor);
      };
      const spanC = document.createElement('span');
      spanC.textContent = '卡片颜色:';
      spanC.style.marginLeft = '10px';
      optionsContainer.appendChild(spanC);
      optionsContainer.appendChild(colorInput);

      const logLabel = document.createElement('label');
      logLabel.htmlFor = 'highlightLogCheck';
      logLabel.appendChild(document.createTextNode('日志高亮'));
      logLabel.style.marginBottom = '10px';
      logLabel.style.marginLeft = '10px';
      optionsContainer.appendChild(logLabel);

      const highlightLogCheck = document.createElement('input');
      highlightLogCheck.type = 'checkbox';
      highlightLogCheck.id = 'highlightLogCheck';
      highlightLogCheck.checked = enableEnhancedDisplayLog;
      highlightLogCheck.style.marginLeft = '2px';
      highlightLogCheck.onchange = function () {
        enableEnhancedDisplayLog = highlightLogCheck.checked;
        GM_setValue("enableEnhancedDisplayLog", enableEnhancedDisplayLog);
      };
      optionsContainer.appendChild(highlightLogCheck);

      const fitLabel = document.createElement('label');
      fitLabel.htmlFor = 'autoFitCheck';
      fitLabel.appendChild(document.createTextNode('自动画布'));
      fitLabel.style.marginBottom = '10px';
      fitLabel.style.marginLeft = '10px';
      optionsContainer.appendChild(fitLabel);

      const autoFitCheck = document.createElement('input');
      autoFitCheck.type = 'checkbox';
      autoFitCheck.id = 'autoFitCheck';
      autoFitCheck.checked = enableAutoFitContent;
      autoFitCheck.style.marginLeft = '2px';
      autoFitCheck.onchange = function () {
        enableAutoFitContent = autoFitCheck.checked;
        GM_setValue("enableAutoFitContent", enableAutoFitContent);
      };
      optionsContainer.appendChild(autoFitCheck);

      const autoCollapseLabel = document.createElement('label');
      autoCollapseLabel.htmlFor = 'autoCollapseCheck';
      autoCollapseLabel.appendChild(document.createTextNode('自动折叠'));
      autoCollapseLabel.style.marginBottom = '10px';
      autoCollapseLabel.style.marginLeft = '10px';
      optionsContainer.appendChild(autoCollapseLabel);
      const autoCollapseCheck = document.createElement('input');
      autoCollapseCheck.type = 'checkbox';
      autoCollapseCheck.id = 'autoCollapseCheck';
      autoCollapseCheck.checked = enableAutoCollapseCheck;
      autoCollapseCheck.style.marginLeft = '2px';
      autoCollapseCheck.onchange = function () {
        enableAutoCollapseCheck = autoCollapseCheck.checked;
        GM_setValue("enableAutoCollapseCheck", enableAutoCollapseCheck);
        autoCollapse();
      };
      optionsContainer.appendChild(autoCollapseCheck);
      container.appendChild(optionsContainer);


      //设备表格
      const devTable = document.createElement('table');
      devTable.id = 'devTable';
      devTable.border = '1';
      devTable.cellSpacing = '0';
      devTable.cellPadding = '5';
      devTable.style.width = '100%';
      devTable.style.border = '1px soild black';
      devTable.style.userSelect = 'text';
      devTable.style.borderCollapse = 'collapse';

      const devThead = document.createElement('thead');
      const devHeaderRow = document.createElement('tr');
      const devRoomHeader = document.createElement('th');
      const devDeviceHeader = document.createElement('th');
      const devRuleHeader = document.createElement('th');
      const devTbody = document.createElement('tbody');

      let roomSortOrder = 'asc';
      let deviceSortOrder = 'asc';
      let ruleSortOrder = 'asc';

      const devUpdateSortMarkers = () => {
        devRoomHeader.innerHTML = `房间 ${roomSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        devDeviceHeader.innerHTML = `设备 ${deviceSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        devRuleHeader.innerHTML = `自动化名称 ${ruleSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
      };

      devRoomHeader.textContent = '房间';
      devRoomHeader.style.textWrap = 'nowrap';
      devDeviceHeader.textContent = '设备';
      devDeviceHeader.style.textWrap = 'nowrap';
      devRuleHeader.textContent = '自动化名称';

      devRoomHeader.onclick = () => {
        roomSortOrder = roomSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(devTbody, 0, roomSortOrder);
        devUpdateSortMarkers();
      };
      devDeviceHeader.onclick = () => {
        deviceSortOrder = deviceSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(devTbody, 1, deviceSortOrder);
        devUpdateSortMarkers();
      };
      devRuleHeader.onclick = () => {
        ruleSortOrder = ruleSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(devTbody, 2, ruleSortOrder);
        devUpdateSortMarkers();
      };

      devHeaderRow.appendChild(devRoomHeader);
      devHeaderRow.appendChild(devDeviceHeader);
      devHeaderRow.appendChild(devRuleHeader);
      devThead.appendChild(devHeaderRow);
      devTable.appendChild(devThead);

      const devFilterContainer = document.createElement('span');
      devFilterContainer.id = 'devFilterContainer';
      devFilterContainer.style.display = '';

      const roomFilterSelect = document.createElement('select');
      roomFilterSelect.style.marginBottom = '10px';
      roomFilterSelect.style.height = '32px';
      roomFilterSelect.style.borderStyle = 'solid';
      roomFilterSelect.style.borderWidth = '1px';
      roomFilterSelect.innerHTML = `<option value="">所有房间</option>` + roomNames.map(room => `<option value="${room}">${room}</option>`).join('');
      roomFilterSelect.onchange = () => {
        filterDevTable(roomFilterSelect.value, deviceFilterInput.value, devRuleFilterInput.value);
      };
      const deviceFilterInput = document.createElement('input');
      deviceFilterInput.type = 'text';
      deviceFilterInput.placeholder = '设备筛选';
      deviceFilterInput.style.width = '200px';
      deviceFilterInput.style.marginBottom = '10px';
      deviceFilterInput.style.marginLeft = '10px';
      deviceFilterInput.style.height = '28px';
      deviceFilterInput.style.borderStyle = 'solid';
      deviceFilterInput.style.borderWidth = '1px';
      deviceFilterInput.oninput = () => {
        filterDevTable(roomFilterSelect.value, deviceFilterInput.value, devRuleFilterInput.value);
      };
      const devRuleFilterInput = document.createElement('input');
      devRuleFilterInput.type = 'text';
      devRuleFilterInput.placeholder = '自动化名称筛选';
      devRuleFilterInput.style.width = '200px';
      devRuleFilterInput.style.marginBottom = '10px';
      devRuleFilterInput.style.marginLeft = '10px';
      devRuleFilterInput.style.height = '28px';
      devRuleFilterInput.style.borderStyle = 'solid';
      devRuleFilterInput.style.borderWidth = '1px';
      devRuleFilterInput.oninput = () => {
        filterDevTable(roomFilterSelect.value, deviceFilterInput.value, devRuleFilterInput.value);
      };

      devFilterContainer.appendChild(roomFilterSelect);
      devFilterContainer.appendChild(deviceFilterInput);
      devFilterContainer.appendChild(devRuleFilterInput);
      container.appendChild(devFilterContainer);

      const varFilterContainer = document.createElement('span');
      varFilterContainer.id = 'varFilterContainer';
      varFilterContainer.style.display = 'none';

      const varScopeFilterSelect = document.createElement('select');
      varScopeFilterSelect.style.marginBottom = '10px';
      varScopeFilterSelect.style.height = '32px';
      varScopeFilterSelect.style.marginLeft = '10px';
      varScopeFilterSelect.style.borderStyle = 'solid';
      varScopeFilterSelect.style.borderWidth = '1px';
      varScopeFilterSelect.innerHTML =
        '<option value="">所有变量范围</option>' +
        '<option value="全局">全局</option>' +
        '<option value="局部">局部</option>';
      varScopeFilterSelect.onchange = () => {
        filterVarTable(varScopeFilterSelect.value, varTypeFilterSelect.value, varNameFilterInput.value, varValueFilterInput.value, varRuleFilterInput.value);
      };
      const varTypeFilterSelect = document.createElement('select');
      varTypeFilterSelect.style.marginBottom = '10px';
      varTypeFilterSelect.style.height = '32px';
      varTypeFilterSelect.style.marginLeft = '10px';
      varTypeFilterSelect.style.borderStyle = 'solid';
      varTypeFilterSelect.style.borderWidth = '1px';
      varTypeFilterSelect.innerHTML =
        '<option value="">所有变量类型</option>' +
        '<option value="文本">文本</option>' +
        '<option value="数值">数值</option>';
      varTypeFilterSelect.onchange = () => {
        filterVarTable(varScopeFilterSelect.value, varTypeFilterSelect.value, varNameFilterInput.value, varValueFilterInput.value, varRuleFilterInput.value);
      };
      const varNameFilterInput = document.createElement('input');
      varNameFilterInput.type = 'text';
      varNameFilterInput.placeholder = '变量名称筛选';
      varNameFilterInput.style.width = '200px';
      varNameFilterInput.style.marginBottom = '10px';
      varNameFilterInput.style.marginLeft = '10px';
      varNameFilterInput.style.height = '28px';
      varNameFilterInput.style.borderStyle = 'thin';
      varNameFilterInput.style.borderWidth = '1px';
      varNameFilterInput.oninput = () => {
        filterVarTable(varScopeFilterSelect.value, varTypeFilterSelect.value, varNameFilterInput.value, varValueFilterInput.value, varRuleFilterInput.value);
      };

      const varValueFilterInput = document.createElement('input');
      varValueFilterInput.type = 'text';
      varValueFilterInput.placeholder = '变量值称筛选';
      varValueFilterInput.style.width = '200px';
      varValueFilterInput.style.marginBottom = '10px';
      varValueFilterInput.style.marginLeft = '10px';
      varValueFilterInput.style.height = '28px';
      varValueFilterInput.style.borderStyle = 'thin';
      varValueFilterInput.style.borderWidth = '1px';
      varValueFilterInput.oninput = () => {
        filterVarTable(varScopeFilterSelect.value, varTypeFilterSelect.value, varNameFilterInput.value, varValueFilterInput.value, varRuleFilterInput.value);
      };
      const varRuleFilterInput = document.createElement('input');
      varRuleFilterInput.type = 'text';
      varRuleFilterInput.placeholder = '自动化名称筛选';
      varRuleFilterInput.style.width = '200px';
      varRuleFilterInput.style.marginBottom = '10px';
      varRuleFilterInput.style.marginLeft = '10px';
      varRuleFilterInput.style.height = '28px';
      varRuleFilterInput.style.borderStyle = 'solid';
      varRuleFilterInput.style.borderWidth = '1px';
      varRuleFilterInput.oninput = () => {
        filterVarTable(varScopeFilterSelect.value, varTypeFilterSelect.value, varNameFilterInput.value, varValueFilterInput.value, varRuleFilterInput.value);
      };
      varFilterContainer.appendChild(varScopeFilterSelect);
      varFilterContainer.appendChild(varTypeFilterSelect);
      varFilterContainer.appendChild(varNameFilterInput);
      varFilterContainer.appendChild(varValueFilterInput);
      varFilterContainer.appendChild(varRuleFilterInput);
      container.appendChild(varFilterContainer);

      const modeButton = document.createElement('button');
      modeButton.id = "modeButton";
      modeButton.textContent = '切换至变量视图';
      modeButton.title = '切换模式';
      modeButton.style.marginRight = '15px';
      modeButton.style.float = 'right';
      modeButton.style.padding = '5px';
      modeButton.onclick = () => {
        handleModeBtnClick();
      };
      container.appendChild(modeButton);


      Object.entries(devRuleData).forEach(([did, data]) => {
        const device = data.device;
        const rules = data.rules;
        const row = document.createElement('tr');
        const roomCell = document.createElement('td');
        roomCell.textContent = device.roomName;
        roomCell.style.textWrap = 'nowrap';
        const deviceCell = document.createElement('td');
        deviceCell.textContent = device.name;
        deviceCell.style.textWrap = 'nowrap';
        const ruleCell = document.createElement('td');

        const host = window.location.host;
        let sequence = 0;
        rules.forEach(rule => {
          const link = document.createElement('a');
          link.href = `http://${host}/#/graph/${rule.id}`;
          link.target = '_self';
          link.textContent = ++sequence + "、" + rule.name + "[" + rule.cardIds.split(',').length + "/" + rule.totalCardNum + "]";
          link.onclick = () => {
            window.location.hash = '#/';
            selectCardIds = rule.cardIds;
          };
          ruleCell.appendChild(link);
          ruleCell.appendChild(document.createElement('br'));
        });
        row.appendChild(roomCell);
        row.appendChild(deviceCell);
        row.appendChild(ruleCell);
        devTbody.appendChild(row);
      });
      devTable.appendChild(devTbody);

      //变量表格
      const varTable = document.createElement('table');
      varTable.id = 'varTable';
      varTable.border = '1';
      varTable.cellSpacing = '0';
      varTable.cellPadding = '5';
      varTable.style.width = '100%';
      varTable.style.border = '1px soild black';
      varTable.style.userSelect = 'text';
      varTable.style.borderCollapse = 'collapse';
      varTable.style.display = 'none';

      const varThead = document.createElement('thead');
      const varHeaderRow = document.createElement('tr');
      const varScopeHeader = document.createElement('th');
      const varTypeHeader = document.createElement('th');
      const varNameHeader = document.createElement('th');
      const varValueHeader = document.createElement('th');
      const varRuleHeader = document.createElement('th');
      const varTbody = document.createElement('tbody');

      let varScopeSortOrder = 'asc';
      let varTypeSortOrder = 'asc';
      let varNameSortOrder = 'asc';
      let varValueSortOrder = 'asc';
      let varRuleSortOrder = 'asc';
      const varUpdateSortMarkers = () => {
        varScopeHeader.innerHTML = `变量范围 ${varScopeSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        varTypeHeader.innerHTML = `变量类型 ${varTypeSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        varNameHeader.innerHTML = `变量名 ${varNameSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        varValueHeader.innerHTML = `变量值 ${varValueSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
        varRuleHeader.innerHTML = `自动化名称 ${varRuleSortOrder === 'asc' ? '⬆️' : '⬇️'}`;
      };

      varScopeHeader.textContent = '变量范围';
      varScopeHeader.style.textWrap = 'nowrap';
      varTypeHeader.textContent = '变量类型';
      varTypeHeader.style.textWrap = 'nowrap';
      varNameHeader.textContent = '变量名';
      varNameHeader.style.textWrap = 'nowrap';
      varValueHeader.textContent = '变量值';
      varValueHeader.style.textWrap = 'nowrap';
      varRuleHeader.textContent = '自动化名称';
      varRuleHeader.style.textWrap = 'nowrap';

      varScopeHeader.onclick = () => {
        varScopeSortOrder = varScopeSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(varTbody, 0, varScopeSortOrder);
        varUpdateSortMarkers();
      };
      varTypeHeader.onclick = () => {
        varTypeSortOrder = varTypeSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(varTbody, 1, varTypeSortOrder);
        varUpdateSortMarkers();
      };
      varNameHeader.onclick = () => {
        varNameSortOrder = varNameSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(varTbody, 2, varNameSortOrder);
        varUpdateSortMarkers();
      };
      varValueHeader.onclick = () => {
        varValueSortOrder = varValueSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(varTbody, 3, varValueSortOrder);
        varUpdateSortMarkers();
      };
      varRuleHeader.onclick = () => {
        varRuleSortOrder = varRuleSortOrder === 'asc' ? 'desc' : 'asc';
        sortTable(varTbody, 4, varRuleSortOrder);
        varUpdateSortMarkers();
      };

      varHeaderRow.appendChild(varScopeHeader);
      varHeaderRow.appendChild(varTypeHeader);
      varHeaderRow.appendChild(varNameHeader);
      varHeaderRow.appendChild(varValueHeader);
      varHeaderRow.appendChild(varRuleHeader);
      varThead.appendChild(varHeaderRow);
      varTable.appendChild(varThead);
      Object.entries(varRuleData).forEach(([vid, data]) => {
        const rules = data.rules;
        const varData = varMap[vid];
        if (varData != null) {
          const row = document.createElement('tr');

          const varScopeCell = document.createElement('td');
          varScopeCell.textContent = varData.scope;
          varScopeCell.style.textWrap = 'nowrap';
          const varTypeCell = document.createElement('td');
          varTypeCell.textContent = varData.type;
          varTypeCell.style.textWrap = 'nowrap';
          const varNameCell = document.createElement('td');
          varNameCell.textContent = varData.name;
          varNameCell.style.textWrap = 'nowrap';
          const varValueCell = document.createElement('td');
          varValueCell.textContent = varData.value;
          varValueCell.style.textWrap = 'nowrap';

          const varRuleCell = document.createElement('td');
          const host = window.location.host;
          let sequence = 0;
          rules.forEach(rule => {
            const link = document.createElement('a');
            link.href = `http://${host}/#/graph/${rule.id}`;
            link.target = '_self';
            link.textContent = ++sequence + "、" + rule.name + "[" + rule.cardIds.split(',').length + "/" + rule.totalCardNum + "]";
            link.onclick = () => {
              window.location.hash = '#/';
              selectCardIds = rule.cardIds;
            };
            varRuleCell.appendChild(link);
            varRuleCell.appendChild(document.createElement('br'));
          });
          row.appendChild(varScopeCell);
          row.appendChild(varTypeCell);
          row.appendChild(varNameCell);
          row.appendChild(varValueCell);
          row.appendChild(varRuleCell);
          varTbody.appendChild(row);
        }
      });
      varTable.appendChild(varTbody);

      container.appendChild(topBar);
      container.appendChild(devTable);
      container.appendChild(varTable);
      document.body.appendChild(container);

      devUpdateSortMarkers();
      varUpdateSortMarkers();

      function filterDevTable(roomName, deviceKeyword, ruleKeyword) {
        const rows = Array.from(devTable.rows);
        rows.forEach(row => {
          const roomText = row.cells[0].textContent;
          const deviceText = row.cells[1].textContent.toLowerCase();
          const ruleText = row.cells[2].textContent.toLowerCase();
          if ((roomName === '' || roomText === roomName) && deviceText.includes(deviceKeyword.toLowerCase()) && ruleText.includes(ruleKeyword.toLowerCase())) {
            row.style.display = '';
          } else {
            row.style.display = 'none';
          }
        });
      }
      function filterVarTable(varScope, varType, varName, varValue, ruleName) {
        const rows = Array.from(varTable.rows);
        rows.forEach(row => {
          const varScopeText = row.cells[0].textContent;
          const varTypeText = row.cells[1].textContent;
          const varNameText = row.cells[2].textContent.toLowerCase();
          const varValueText = row.cells[3].textContent.toLowerCase();
          const ruleNameText = row.cells[4].textContent.toLowerCase();
          if ((varScope === '' || varScope === varScopeText)
            && (varType === '' || varType === varTypeText)
            && varNameText.includes(varName.toLowerCase())
            && varValueText.includes(varValue.toLowerCase())
            && ruleNameText.includes(ruleName.toLowerCase())) {
            row.style.display = '';
          } else {
            row.style.display = 'none';
          }
        });
      }
      autoCollapse();
    } catch (error) {
      isInit = false;
      console.error('调用 API 时出错:', error);
    }
  };

  const selectDevices = async () => {
    // await sleep(1000);
    const cardIds = selectCardIds.split(',');
    for (const cardId of cardIds) {
      if (cardId.trim() !== '') {
        let targetElement = document.querySelector("[id='" + cardId.trim() + "'] > div > div");
        if (targetElement) {
          targetElement.style.backgroundColor = backgroundColor === '' ? defaultColor : backgroundColor;
        }
      }
    }
    selectCardIds = '';
    closeGraphContainer();

    const graphDevTableDiv = document.createElement('div');
    const graphDevSpan = document.createElement('span');
    graphDevSpan.textContent = "当前自动化涉及的设备列表:";
    graphDevTableDiv.id = 'graphDevTableDiv';
    graphDevTableDiv.appendChild(graphDevSpan);
    const graphDevTable = document.createElement('table');
    graphDevTable.border = '1';
    graphDevTable.cellSpacing = '0';
    graphDevTable.cellPadding = '5';
    graphDevTable.style.width = '100%';
    graphDevTable.style.border = '1px soild black';
    graphDevTable.style.borderCollapse = 'collapse';
    graphDevTable.style.userSelect = 'text';

    const graphDevThead = document.createElement('thead');
    const graphDevHeaderRow = document.createElement('tr');
    const graphDevRoomHeader = document.createElement('th');
    const graphDevInfoHeader = document.createElement('th');
    const graphDevTbody = document.createElement('tbody');

    graphDevRoomHeader.textContent = '房间';
    graphDevRoomHeader.style.textWrap = 'nowrap';
    graphDevInfoHeader.textContent = '设备名称';
    graphDevInfoHeader.style.textWrap = 'nowrap';

    let ruleId = window.location.hash.split('/')[2];
    const ruleContent = await callAPI('getGraph', { id: ruleId });

    const dids = new Set(ruleContent.nodes.map(n => n.props?.did).filter(did => did !== undefined));
    dids.forEach(did => {
      const row = document.createElement('tr');
      const roomCell = document.createElement('td');
      roomCell.textContent = devMap[did]?.roomName ?? `未知`;
      roomCell.style.textWrap = 'nowrap';
      const deviceCell = document.createElement('td');
      deviceCell.style.textWrap = 'nowrap';
      const cards = ruleContent.nodes
        .filter(node => node.props && node.id && node.props.did === did)
        .map(node => node.id);
      const link = document.createElement('a');
      link.href = `javascript:void(0);`;
      link.textContent = devMap[did]?.name + " [" + cards.length + "]" ?? `did: ${did}`;
      link.onclick = () => {
        const cssCards = document.querySelectorAll('.card,.simple-card');
        if (cssCards) {
          cssCards.forEach(card => {
            card.style.backgroundColor = '';
          });
        }
        if (cards) {
          cards.forEach(cardId => {
            let targetElement = document.querySelector("[id='" + cardId.trim() + "'] > div > div");
            if (targetElement) {
              targetElement.style.backgroundColor = backgroundColor === '' ? defaultColor : backgroundColor;
            }
          });
        }
      };
      deviceCell.appendChild(link);
      row.appendChild(roomCell);
      row.appendChild(deviceCell);
      graphDevTbody.appendChild(row);
    });

    const graphVarTableDiv = document.createElement('div');
    graphVarTableDiv.textContent = "当前自动化涉及的变量列表:";
    graphVarTableDiv.style.marginTop = '10px';
    const graphVarTable = document.createElement('table');
    graphVarTable.border = '1';
    graphVarTable.cellSpacing = '0';
    graphVarTable.cellPadding = '5';
    graphVarTable.style.width = '100%';
    graphVarTable.style.border = '1px soild black';
    graphVarTable.style.borderCollapse = 'collapse';
    graphVarTable.style.userSelect = 'text';

    const graphVarThead = document.createElement('thead');
    const graphVarHeaderRow = document.createElement('tr');
    const graphVarScopeHeader = document.createElement('th');
    const graphVarTypeHeader = document.createElement('th');
    const graphVarNameHeader = document.createElement('th');
    const graphVarValueHeader = document.createElement('th');
    const graphVarTbody = document.createElement('tbody');

    graphVarScopeHeader.textContent = '范围';
    graphVarScopeHeader.style.textWrap = 'nowrap';
    graphVarTypeHeader.textContent = '类型';
    graphVarTypeHeader.style.textWrap = 'nowrap';
    graphVarNameHeader.textContent = '变量名称';
    graphVarNameHeader.style.textWrap = 'nowrap';
    graphVarValueHeader.textContent = '变量值';

    let varMap = {};
    const varScopes = (await callAPI('getVarScopeList', {})).scopes;
    for (const scope of varScopes) {
      if (scope === "global" || scope === "R" + ruleId) {
        const vars = await callAPI('getVarList', { scope: scope });
        Object.entries(vars).forEach(([vid, v]) => {
          varMap[vid] = {
            name: v.userData.name,
            scope: (scope === "global" ? "全局" : "局部"),
            type: (v.type === "string" ? "文本" : "数值"),
            value: v.value
          }
        });
      }
    }

    const varids = new Set(ruleContent.nodes.filter(node => node.props && node.props.scope).map(node => node.props.id));
    varids.forEach(vid => {
      const row = document.createElement('tr');
      const varScopeCell = document.createElement('td');
      varScopeCell.textContent = varMap[vid]?.scope ?? `未知`;
      varScopeCell.style.textWrap = 'nowrap';
      const varTypeCell = document.createElement('td');
      varTypeCell.textContent = varMap[vid]?.type ?? `未知`;
      varTypeCell.style.textWrap = 'nowrap';
      const varNameCell = document.createElement('td');
      varNameCell.style.textWrap = 'nowrap';
      const cards = ruleContent.nodes
        .filter(node => node.props && node.id && node.props.scope && node.props.id === vid)
        .map(node => node.id);
      const link = document.createElement('a');
      link.href = `javascript:void(0);`;
      link.textContent = varMap[vid]?.name + " [" + cards.length + "]" ?? `未知`;
      link.onclick = () => {
        const cssCards = document.querySelectorAll('.card,.simple-card');
        if (cssCards) {
          cssCards.forEach(card => {
            card.style.backgroundColor = '';
          });
        }
        if (cards) {
          cards.forEach(cardId => {
            let targetElement = document.querySelector("[id='" + cardId.trim() + "'] > div > div");
            if (targetElement) {
              targetElement.style.backgroundColor = backgroundColor === '' ? defaultColor : backgroundColor;
            }
          });
        }
      };
      varNameCell.appendChild(link);
      const varValueCell = document.createElement('td');
      varValueCell.textContent = varMap[vid]?.value ?? `未知`;
      row.appendChild(varScopeCell);
      row.appendChild(varTypeCell);
      row.appendChild(varNameCell);
      row.appendChild(varValueCell);
      graphVarTbody.appendChild(row);
    });

    const graphRemarkDiv = document.createElement('div');
    graphRemarkDiv.textContent = "提示:设备名称或变量名称后的括号内显示的数字为当前自动化涉及的卡片数量,点击设备名称或变量名称可高亮显示对应卡片!";
    graphRemarkDiv.style.color = 'red';
    graphRemarkDiv.style.marginTop = '10px';

    graphDevHeaderRow.appendChild(graphDevRoomHeader);
    graphDevHeaderRow.appendChild(graphDevInfoHeader);
    graphDevThead.appendChild(graphDevHeaderRow);
    graphDevTable.appendChild(graphDevThead);
    graphDevTable.appendChild(graphDevTbody);
    sortTable(graphDevTbody, 0, 'asc');

    const graphContainer = document.getElementById('graph-list-container') || document.createElement('div');
    graphContainer.id = 'graph-list-container';
    graphContainer.style.position = 'fixed';
    graphContainer.style.top = '39px';
    graphContainer.style.right = '40px';
    graphContainer.style.width = '500px';
    graphContainer.style.height = 'auto';
    graphContainer.style.overflowY = 'scroll';
    graphContainer.style.backgroundColor = 'white';
    graphContainer.style.border = '1px solid #ccc';
    graphContainer.style.paddingTop = '0px';
    graphContainer.style.zIndex = 9999;
    graphContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
    graphContainer.style.display = 'none';

    if (!document.getElementById('graphDevTableDiv')) {
      graphContainer.appendChild(graphDevTableDiv);
      graphContainer.appendChild(graphDevTable);
    }

    graphVarHeaderRow.appendChild(graphVarScopeHeader);
    graphVarHeaderRow.appendChild(graphVarTypeHeader);
    graphVarHeaderRow.appendChild(graphVarNameHeader);
    graphVarHeaderRow.appendChild(graphVarValueHeader);
    graphVarThead.appendChild(graphVarHeaderRow);
    graphVarTable.appendChild(graphVarThead);
    graphVarTable.appendChild(graphVarTbody);
    sortTable(graphVarTbody, 0, 'asc');
    if (varids.size > 0) {
      graphContainer.appendChild(graphVarTableDiv);
      graphContainer.appendChild(graphVarTable);
    }
    //graphContainer.appendChild(graphRemarkDiv);
    if (!document.getElementById('graph-list-container')) {
      document.body.appendChild(graphContainer);
    }
  };

  function sortTable(tbody, columnIndex, sortOrder) {
    const rows = Array.from(tbody.rows);
    const sortedRows = rows.sort((a, b) => {
      const aText = a.cells[columnIndex].textContent;
      const bText = b.cells[columnIndex].textContent;
      if (sortOrder === 'asc') {
        return aText.localeCompare(bText);
      } else {
        return bText.localeCompare(aText);
      }
    });
    tbody.innerHTML = '';
    sortedRows.forEach(row => tbody.appendChild(row));
  }
  function handleModeBtnClick() {
    const varTable = document.getElementById('varTable');
    const devTable = document.getElementById('devTable');
    const modeButton = document.getElementById('modeButton');
    const devFilterContainer = document.getElementById('devFilterContainer');
    const varFilterContainer = document.getElementById('varFilterContainer');

    if (varTable && devTable && modeButton) {
      if (modeButton.textContent === "切换至变量视图") {
        devTable.style.display = 'none';
        varTable.style.display = '';
        devFilterContainer.style.display = 'none';
        varFilterContainer.style.display = '';
        modeButton.textContent = '切换至设备视图';
      } else {
        devTable.style.display = '';
        varTable.style.display = 'none';
        devFilterContainer.style.display = '';
        varFilterContainer.style.display = 'none';
        modeButton.textContent = '切换至变量视图';
      }
    }
  }
  function handleOptionsBtnClick() {
    const optionsContainer = document.getElementById('optionsContainer');
    if (optionsContainer) {
      if (optionsContainer.style.display === 'none') {
        optionsContainer.style.display = '';
      } else {
        optionsContainer.style.display = 'none';
      }
    }
  }
  function handleCollapseBtnClick() {
    const container = document.getElementById('device-rule-map');
    if (container) {
      if (container.style.height === windowHeight + 'px') {
        collapse();
      } else {
        expand();
      }
    }
  }
  function collapse() {
    const container = document.getElementById('device-rule-map');
    if (container) {
      const collapseButton = document.getElementById('collapseButton');
      const topBar = document.getElementById('topBar');
      topBar.style.width = '500px';
      container.style.height = '0px';
      container.style.width = '0px';
      container.style.top = '0';
      container.style.right = '-1px';
      collapseButton.textContent = '展开';
    }
  }

  function expand() {
    const container = document.getElementById('device-rule-map');
    if (container) {
      const collapseButton = document.getElementById('collapseButton');
      const topBar = document.getElementById('topBar');
      topBar.style.width = windowWidth + 'px';
      container.style.width = windowWidth + 'px';
      container.style.height = windowHeight + 'px';
      container.style.top = '10px';
      container.style.right = '40px';
      collapseButton.textContent = '折叠';
    }
  }

  function autoFitContent() {
    if (enableAutoFitContent && editor && editor.transformTool) {
      editor.transformTool.fitToBestPos();
    }
  }
  function autoCollapse() {
    if (enableAutoCollapseCheck) {
      collapse();
    }
  }

  function enhancedDisplayLog() {
    //监听画布变化
    const canvas = document.getElementById('canvas-root');
    if (canvas) {
      const config = { attributes: false, childList: true, subtree: true };
      const callback = function (mutationsList, observer) {
        if (enableEnhancedDisplayLog) {
          let element = document.querySelector('.panel-log-card-blink');
          if (element && element.style.outline !== "red solid 20px") {
            element.style.outline = "red solid 10px";
          }
          let animateElement = document.querySelector('animate');
          if (animateElement && animateElement.getAttribute('stroke-width') != '10') {
            let pathElement = animateElement.parentElement;
            pathElement.setAttribute('stroke-width', '10');
            if (pathElement) {
              let gElement = pathElement.parentElement;
              gElement.setAttribute('stroke', 'red');
            }
          }
        }
      };
      const observer = new MutationObserver(callback);
      observer.observe(canvas, config);
    }
  }
  function addShortcutHelp() {
    const addedSchortcut = document.getElementById('addedSchortcut');
    if (addedSchortcut) {
      return;
    }
    const helpLeft = document.querySelector('.help-modal-left');
    if (helpLeft) {
      const leftContent = `
          <div></div>
          <div class="help-modal-item-label" id="addedSchortcut">自适应画布</div>
          <div class="help-modal-item-btn">Ctrl 或 ⌘</div>
          <div class="help-modal-item-btn" style="width: 41px;">B</div>
          <div class="help-modal-item-label">关闭画布</div>
          <div class="help-modal-item-btn">Ctrl</div>
          <div class="help-modal-item-btn" style="width: 41px;">W</div>
          <div class="help-modal-item-label">关闭助手</div>
          <div class="help-modal-item-btn">Ctrl</div>
          <div class="help-modal-item-btn" style="width: 41px;">Q</div>
          `;
      helpLeft.insertAdjacentHTML('beforeend', leftContent);
    }
    const helpRight = document.querySelector('.help-modal-right');
    if (helpRight) {
      const rightContent = `
          <div></div>
          <div class="help-modal-item-label">折叠助手</div>
          <div class="help-modal-item-btn" style="width: auto;">Ctrl 或 ⌘</div>
          <div class="help-modal-item-btn" style="width: 41px;">E</div>
          <div class="help-modal-item-label">刷新助手</div>
          <div class="help-modal-item-btn">Ctrl</div>
          <div class="help-modal-item-btn" style="width: 41px;">R</div>
          `;
      helpRight.insertAdjacentHTML('beforeend', rightContent);
    }

    GM_addStyle('.help-modal .help-modal-middle {grid-template-rows: 32px 32px 32px 32px 32px;}');
    GM_addStyle('.help-modal .help-modal-right {grid-template-rows: 32px 32px 32px 32px 32px;}');
    GM_addStyle('.help-modal {height: 238px;}');
  }

  function changeRuleListStyle(count) {
    GM_addStyle('.automation-rule-page .ant-spin-container .content-scroll-wrapper .rule-list {    grid-template-columns: repeat(' + count + ', 1fr);}')
  }
  function sortVars() {
    const varLists = document.querySelectorAll(".var-list");
    varLists.forEach(varList => {
      const commonVarCells = Array.from(varList.querySelectorAll(".common-var-cell"));

      const cellsWithTitle = commonVarCells.map(cell => ({
        cell: cell,
        title: (cell.querySelector(".var-name")?.getAttribute("title") || "").toUpperCase()
      }));

      cellsWithTitle.sort((a, b) => a.title.localeCompare(b.title));

      const fragment = document.createDocumentFragment();
      cellsWithTitle.forEach(item => fragment.appendChild(item.cell));

      varList.innerHTML = '';
      varList.appendChild(fragment);
    });
  }

  function isMiJiaJiKePage() {
    return document.title === "米家自动化极客版" && !document.querySelector('.pin-code-with-keyboard') && editor;
  }
  function closeGraphContainer(flag = false) {
    const graphContainer = document.getElementById('graph-list-container');
    const graphListButton = document.getElementById('graphListButton');
    if (!!graphContainer) {
      if (flag) {
        graphContainer.style.display = graphContainer.style.display === 'none' ? '' : 'none';
      } else {
        document.body.removeChild(graphContainer);
      }
    }
    if (!!graphListButton) {
      if (window.location.hash.match(/^#\/graph\/.*/g)) {
        graphListButton.style.display = '';
      } else {
        graphListButton.style.display = 'none';
      }
    }

  }
  function handleUrlChange() {
    if (isMiJiaJiKePage()) {
      executeScript();
      closeGraphContainer();
      if (window.location.hash.match(/^#\/graph\/.*/g)) {
        setTimeout(function () {
          autoFitContent();
          enhancedDisplayLog();
          autoCollapse();
          addShortcutHelp();
          selectDevices();
        }, 500);
      }
      if (window.location.hash.match(/^#\/vars/g)) {
        setTimeout(function () {
          sortVars();
        }, 500);
        sortVars();
      }
    }
  }

  //页面变化
  window.addEventListener('popstate', handleUrlChange);
  window.addEventListener('hashchange', handleUrlChange);
  const originalPushState = history.pushState;
  const originalReplaceState = history.replaceState;
  history.pushState = function () {
    originalPushState.apply(this, arguments);
    handleUrlChange();
  };
  history.replaceState = function () {
    originalReplaceState.apply(this, arguments);
    handleUrlChange();
  };
  //快捷键
  document.addEventListener('keydown', function (event) {
    if ((event.metaKey || event.ctrlKey) && event.key === 'e') {
      event.preventDefault();
      if (document.getElementById('collapseButton')) {
        document.getElementById('collapseButton').click();
      }
    }
    if (event.ctrlKey && event.key === 'q') {
      event.preventDefault();
      if (document.getElementById('closeButton')) {
        document.getElementById('closeButton').click();
      }
    }
    if (event.ctrlKey && event.key === 'r') {
      event.preventDefault();
      if (document.getElementById('refreshButton')) {
        document.getElementById('refreshButton').click();
      }
    }
    if ((event.metaKey || event.ctrlKey) && event.key === 'b') {
      event.preventDefault();
      if (editor && editor.transformTool) {
        editor.transformTool.fitToBestPos();
      }
    }
    if (event.ctrlKey && event.key === 'w') {
      event.preventDefault();
      const selectedMenuItem = document.querySelector('.app-header-menu-item-selected');
      if (selectedMenuItem) {
        const actionElement = selectedMenuItem.querySelector('.app-header-menu-item-action');
        if (actionElement) {
          actionElement.click();
        }
      }
    }

  });
  window.onload = function () {
    //监控登录页面
    const loginForm = document.querySelector('.account-content');
    if (loginForm) {
      const config = { attributes: true, childList: true, subtree: true };
      const callback = function (mutationsList, observer) {
        setTimeout(function () {
          handleUrlChange();
        }, 1500);
      };
      const observer = new MutationObserver(callback);
      observer.observe(loginForm, config);
    }

  };
})();