v0.70.0
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [0.70.0] - 2025-04-14
|
||||
- 设置菜单对UI完全自定义
|
||||
- 整合notification到gululu统一控制
|
||||
- 新增GuLuLu
|
||||
- 新增GuLuLu右键菜单
|
||||
- 新增GuLuLu设置项
|
||||
- 新增GuLuLu继承KayTool通知为流式输出
|
||||
- 修复Settings中设置项的bug
|
||||
- 修复Monitor的初始位置问题
|
||||
- 增强Clean VRAM兼容性
|
||||
- 修复setget颜色设置问题
|
||||
- 新增KayToolActions适配GuLuLu右键菜单
|
||||
- UI初始配色调整
|
||||
|
||||
## [0.65.0] - 2025-04-9
|
||||
- 大幅度提升Monitor性能
|
||||
- 增加Clean VRAM
|
||||
|
||||
@@ -14,6 +14,8 @@ This is a custom node utility package developed for ComfyUI. Initially created f
|
||||
|
||||
**📌 所有 KayTool 节点包用到的资源都在 `ComfyUI/custom_nodes/kaytool` 文件夹内。| All KayTool node packages use resources in the `ComfyUI/custom_nodes/kaytool` folder.**
|
||||
|
||||
**⚙️ 在ComfyUI左下角的设置菜单里,所有 KayTool 的相关功能都支持自定义设置。| All KayTool features support custom settings in the ComfyUI settings menu.**
|
||||
|
||||
|
||||
## 资源监视器 Resource Monitor
|
||||
|
||||
@@ -43,6 +45,15 @@ https://github.com/user-attachments/assets/2cd4179c-57fa-42dc-ae50-2feae9708e42
|
||||
|
||||
https://github.com/user-attachments/assets/b8d1d3f0-04d1-46c5-968a-e433778b73e6
|
||||
|
||||
## 咕噜噜 GuLuLu
|
||||
|
||||
- 主打陪伴的咕噜噜,支持调整KayTool的通知位置
|
||||
GuLuLu, featuring companionship, supports adjusting KayTool notification positions.
|
||||
- 继承KayTool的通知为流式输出
|
||||
Inherits KayTool notifications as streaming output.
|
||||
- 提供强大的右键菜单项目,快速调用KayTool的进阶功能
|
||||
Provides powerful right-click menu items, quickly calling KayTool's advanced features.
|
||||
|
||||
## workflow PNG功能 Workflow Export to PNG
|
||||
|
||||
- 支持在右键菜单中`KayTool-workflow PNG`将当前工作流节点地图保存为PNG格式并内嵌工作流信息。
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"Custom Web Logo (BETA)": "自定义网页徽标",
|
||||
"Workflow PNG": "Workflow PNG",
|
||||
"NodeAlignment": "节点对齐工具栏",
|
||||
"Resource Monitor": "资源监视器"
|
||||
"Resource Monitor": "资源监视器",
|
||||
"GuLuLu": "咕噜噜"
|
||||
},
|
||||
"nodeCategories": {
|
||||
"Image": "图像",
|
||||
|
||||
@@ -40,7 +40,13 @@
|
||||
"name": "文件必须 < 1MB",
|
||||
"tooltip": "这是一个测试功能,文件最好 <1MB ,否则可能无法显示"
|
||||
},
|
||||
|
||||
"KayTool_GuLuLuSize": {
|
||||
"name": "你要一个多大的咕噜噜~啊?"
|
||||
},
|
||||
"KayTool_EnableGuLuLu": {
|
||||
"name": "让咕噜噜陪着你吧~!",
|
||||
"tooltip": "咕噜噜会继承KayTool的通知项为流式输出,并支持通知框位置调整,以及强大的右键菜单项"
|
||||
},
|
||||
"KayTool_EnableAlignmentShortcuts": {
|
||||
"name": "启用快捷键'Shift+W/A/S/D'",
|
||||
"tooltip": "W/A/S/D 对应上、左、下、右对齐"
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "kaytool"
|
||||
description ="KayTool boosts ComfyUI workflow efficiency with powerful tools like: image processing (resize, color, blur, expand, merge, crop), background/mask tasks via BiRefNet/RemBG, wireless data transfer (Get & Out), AI translation (Tencent/Baidu), Professional high-precision resource monitoring, dynamic math operations, flexible text handling, precision sliders, metadata/color profile-supported image saving, batch processing via folder loading, canvas node map export as PNG with metadata, quick Run options via right-click, node alignment toolbar with custom styles, and custom ComfyUI logo integration."
|
||||
version = "0.65.1"
|
||||
version = "0.70.0"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["rembg, timm, opencv-python, py-cpuinfo, pynvml"]
|
||||
|
||||
|
||||
+64
-53
@@ -1,5 +1,66 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { showNotification, hideNotification } from "./notification.js";
|
||||
import { showNotification, hideNotification } from "./gululu.js";
|
||||
|
||||
// 全局动作注册表
|
||||
const KayToolActions = window.KayToolActions || {};
|
||||
window.KayToolActions = KayToolActions;
|
||||
|
||||
async function cleanVRAM() {
|
||||
const notifyElement = showNotification({
|
||||
message: "🧹 GuLuLu: Working...",
|
||||
size: "medium",
|
||||
bgColor: "#fff3cd"
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch("/kaytool/clean_vram", { method: "POST" });
|
||||
const message = await response.text();
|
||||
|
||||
hideNotification(notifyElement);
|
||||
await new Promise(resolve => setTimeout(resolve, 350));
|
||||
|
||||
if (!response.ok) {
|
||||
showNotification({
|
||||
message: message || "🧹 GuLuLu: Failed due to server error",
|
||||
bgColor: "#f8d7da",
|
||||
size: "medium",
|
||||
timeout: 7000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const match = message.match(/Freed ([\d.]+) GB \(Allocated: ([\d.]+) GB -> ([\d.]+) GB, Total: ([\d.]+) GB -> ([\d.]+) GB\)/);
|
||||
if (match) {
|
||||
showNotification({
|
||||
message: `🧹 GuLuLu: Done!
|
||||
**${match[2]} GB** ➔ **${match[3]} GB**
|
||||
(Total: ${match[4]} GB ➔ ${match[5]} GB)`,
|
||||
bgColor: "#d4edda",
|
||||
size: "medium",
|
||||
timeout: 8000
|
||||
});
|
||||
} else {
|
||||
showNotification({
|
||||
message: `🧹 GuLuLu: ${message}`,
|
||||
bgColor: "#d4edda",
|
||||
size: "medium",
|
||||
timeout: 8000
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
hideNotification(notifyElement);
|
||||
await new Promise(resolve => setTimeout(resolve, 350));
|
||||
showNotification({
|
||||
message: `🧹 GuLuLu: ${err.message}`,
|
||||
bgColor: "#f8d7da",
|
||||
size: "medium",
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 Clean VRAM 动作
|
||||
KayToolActions.cleanVRAM = cleanVRAM;
|
||||
|
||||
app.registerExtension({
|
||||
name: "KayTool.CleanVRAM",
|
||||
@@ -8,7 +69,7 @@ app.registerExtension({
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
|
||||
const newOptions = [...options];
|
||||
const showCleanVRAM = app.ui.settings.getSettingValue("KayTool.ShowCleanVRAM");
|
||||
const showCleanVRAM = app.ui.settings.getSettingValue("KayTool.ShowCleanVRAM") ?? true;
|
||||
|
||||
if (showCleanVRAM) {
|
||||
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
|
||||
@@ -23,57 +84,7 @@ app.registerExtension({
|
||||
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "🧹 Clean VRAM")) {
|
||||
kaytoolMenu.submenu.options.push({
|
||||
content: "🧹 Clean VRAM",
|
||||
async callback() {
|
||||
const notifyElement = showNotification({
|
||||
message: "🧹 **Clean:** Working...",
|
||||
size: "medium",
|
||||
bgColor: "#fff3cd"
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch("/kaytool/clean_vram", { method: "POST" });
|
||||
const message = await response.text();
|
||||
|
||||
hideNotification(notifyElement);
|
||||
|
||||
if (!response.ok) {
|
||||
showNotification({
|
||||
message: message || "🧹 **Clean:** Failed due to server error",
|
||||
bgColor: "#f8d7da",
|
||||
size: "medium",
|
||||
timeout: 7000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const match = message.match(/Freed ([\d.]+) GB \(Allocated: ([\d.]+) GB -> ([\d.]+) GB, Total: ([\d.]+) GB -> ([\d.]+) GB\)/);
|
||||
if (match) {
|
||||
showNotification({
|
||||
message: `🧹 **Clean:** Done!
|
||||
**${match[2]} GB** ➔ **${match[3]} GB**
|
||||
(Total: ${match[4]} GB ➔ ${match[5]} GB)`,
|
||||
bgColor: "#d4edda",
|
||||
size: "medium",
|
||||
timeout: 8000
|
||||
});
|
||||
} else {
|
||||
showNotification({
|
||||
message: `🧹 **Clean:** ${message}`,
|
||||
bgColor: "#d4edda",
|
||||
size: "medium",
|
||||
timeout: 8000
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
hideNotification(notifyElement);
|
||||
showNotification({
|
||||
message: `🧹 **Clean:** ${err.message}`,
|
||||
bgColor: "#f8d7da",
|
||||
size: "medium",
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
callback: cleanVRAM
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -18,7 +18,6 @@ function showAlert(message) {
|
||||
app.registerExtension({
|
||||
name: "KayTool.SetGet",
|
||||
registerCustomNodes() {
|
||||
|
||||
class KaySetNode extends LGraphNode {
|
||||
defaultVisibility = true;
|
||||
serialize_widgets = true;
|
||||
@@ -27,7 +26,6 @@ app.registerExtension({
|
||||
constructor(title) {
|
||||
super(title);
|
||||
|
||||
// 从 settings 获取当前前景色和背景色
|
||||
const fgColor = app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
|
||||
const bgColor = app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
|
||||
this.color = "#" + fgColor;
|
||||
@@ -182,7 +180,6 @@ app.registerExtension({
|
||||
constructor(title) {
|
||||
super(title);
|
||||
|
||||
// 从 settings 获取当前颜色,默认为 "#000000"
|
||||
const fgColor = app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
|
||||
const bgColor = app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
|
||||
this.color = "#" + fgColor;
|
||||
@@ -289,5 +286,5 @@ app.registerExtension({
|
||||
title: "𝙆 🛜Get"
|
||||
}));
|
||||
KayGetNode.category = "KayTool";
|
||||
},
|
||||
}
|
||||
});
|
||||
+868
@@ -0,0 +1,868 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { marked } from "./lib/marked.esm.js";
|
||||
|
||||
// 全局动作注册表
|
||||
const KayToolActions = window.KayToolActions || {};
|
||||
window.KayToolActions = KayToolActions;
|
||||
|
||||
// GuLuLu 管理器
|
||||
const KayGuLuLuManager = {
|
||||
container: null,
|
||||
dragState: { isDragging: false, offsetX: 0, offsetY: 0 },
|
||||
state: { left: 100, top: 100, transform: "scaleX(1)" },
|
||||
pixelCanvas: null,
|
||||
pixelCtx: null,
|
||||
pixelDataCache: null,
|
||||
imgSrc: "/extensions/kaytool/resources/GuLuLu.gif",
|
||||
effectiveBounds: null,
|
||||
isAnimating: false,
|
||||
animationTimer: null,
|
||||
contextMenu: null,
|
||||
isEnabled: true,
|
||||
size: 39, // 默认尺寸
|
||||
// GuLuLu 右键菜单配置
|
||||
menuConfig: [
|
||||
{ label: "Say Hello", action: "playAnimation" },
|
||||
{ label: "🧹 Clean VRAM", action: "cleanVRAM" },
|
||||
{ label: "📦 Workflow PNG", action: "exportWorkflowPNG" },
|
||||
{ label: "⭐ Star to me", action: "starToMe" }
|
||||
],
|
||||
|
||||
init() {
|
||||
this.isEnabled = app.ui.settings.getSettingValue("KayTool.EnableGuLuLu") ?? true;
|
||||
this.size = app.ui.settings.getSettingValue("KayTool.GuLuLuSize") ?? 39;
|
||||
this.loadState();
|
||||
this.injectStyles();
|
||||
this.setupGuLuLu();
|
||||
this.updatePosition();
|
||||
window.KayGuLuLuManager = this;
|
||||
window.addEventListener("resize", () => this.updatePosition());
|
||||
if (this.isEnabled) {
|
||||
this.startRandomAnimation();
|
||||
}
|
||||
},
|
||||
|
||||
injectStyles() {
|
||||
document.head.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`<style>
|
||||
#kay-gululu-container {
|
||||
position: fixed;
|
||||
z-index: 10002;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
#kay-gululu-container.enabled {
|
||||
display: block;
|
||||
}
|
||||
#kay-gululu-container img {
|
||||
width: ${this.size}px;
|
||||
height: auto;
|
||||
display: block;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.gululu-context-menu {
|
||||
position: fixed;
|
||||
background: #333;
|
||||
border: 2px solid #000;
|
||||
border-radius: 8px;
|
||||
box-shadow: 4px 4px 0 #999;
|
||||
padding: 8px 0;
|
||||
z-index: 10002;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.gululu-context-menu::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
.gululu-context-menu::-webkit-scrollbar-track {
|
||||
background: #333;
|
||||
border-left: 2px solid #000;
|
||||
}
|
||||
.gululu-context-menu::-webkit-scrollbar-thumb {
|
||||
background: #f0ff00;
|
||||
border: 50px solid #f0ff00;
|
||||
}
|
||||
.gululu-context-menu-item {
|
||||
padding: 4px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.gululu-context-menu-item:hover {
|
||||
background: #555;
|
||||
}
|
||||
</style>`
|
||||
);
|
||||
},
|
||||
|
||||
setupGuLuLu() {
|
||||
if (!this.container) {
|
||||
this.container = document.createElement("div");
|
||||
this.container.id = "kay-gululu-container";
|
||||
const img = document.createElement("img");
|
||||
img.src = this.imgSrc;
|
||||
img.alt = "GuLuLu";
|
||||
img.onload = () => {
|
||||
this.updatePixelDataCache();
|
||||
this.updatePosition();
|
||||
};
|
||||
this.container.appendChild(img);
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
this.pixelCanvas = document.createElement("canvas");
|
||||
this.pixelCanvas.style.display = "none";
|
||||
document.body.appendChild(this.pixelCanvas);
|
||||
this.pixelCtx = this.pixelCanvas.getContext("2d", { willReadFrequently: true });
|
||||
}
|
||||
|
||||
this.container.classList.toggle("enabled", this.isEnabled);
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
bindEvents() {
|
||||
if (this.container) {
|
||||
this.container.removeEventListener("mousemove", this.handleMouseMove);
|
||||
this.container.removeEventListener("mousedown", this.onDragStart);
|
||||
this.container.removeEventListener("contextmenu", this.onContextMenu);
|
||||
}
|
||||
document.removeEventListener("mousemove", this.onDragging);
|
||||
document.removeEventListener("mouseup", this.onDragEnd);
|
||||
document.removeEventListener("click", this.hideContextMenu);
|
||||
|
||||
if (this.container && this.isEnabled) {
|
||||
this.container.addEventListener("mousemove", this.handleMouseMove.bind(this));
|
||||
this.container.addEventListener("mousedown", this.onDragStart.bind(this));
|
||||
this.container.addEventListener("contextmenu", this.onContextMenu.bind(this));
|
||||
}
|
||||
document.addEventListener("mousemove", this.onDragging.bind(this));
|
||||
document.addEventListener("mouseup", this.onDragEnd.bind(this));
|
||||
document.addEventListener("click", this.hideContextMenu.bind(this));
|
||||
},
|
||||
|
||||
updateSize(newSize) {
|
||||
this.size = newSize;
|
||||
const img = this.container?.querySelector("img");
|
||||
if (img) {
|
||||
img.style.width = `${this.size}px`;
|
||||
this.updatePixelDataCache();
|
||||
this.updatePosition();
|
||||
}
|
||||
},
|
||||
|
||||
playAnimation() {
|
||||
if (!this.isEnabled || this.isAnimating || !this.container) return;
|
||||
|
||||
this.isAnimating = true;
|
||||
const img = this.container.querySelector("img");
|
||||
if (img) {
|
||||
const src = img.src;
|
||||
img.src = "";
|
||||
img.src = src;
|
||||
setTimeout(() => {
|
||||
this.isAnimating = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
|
||||
startRandomAnimation() {
|
||||
const getRandomInterval = () => {
|
||||
const min = 300 * 1000; // 10 秒
|
||||
const max = 1200 * 1000; // 30 秒
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
const scheduleNextAnimation = () => {
|
||||
clearTimeout(this.animationTimer);
|
||||
if (!this.isEnabled) return;
|
||||
const interval = getRandomInterval();
|
||||
this.animationTimer = setTimeout(() => {
|
||||
this.playAnimation();
|
||||
scheduleNextAnimation();
|
||||
}, interval);
|
||||
};
|
||||
|
||||
scheduleNextAnimation();
|
||||
},
|
||||
|
||||
updateEnabledState(enabled) {
|
||||
this.isEnabled = enabled;
|
||||
if (!this.container) {
|
||||
this.setupGuLuLu();
|
||||
} else {
|
||||
this.container.classList.toggle("enabled", enabled);
|
||||
this.bindEvents();
|
||||
}
|
||||
if (enabled) {
|
||||
this.updatePosition();
|
||||
this.startRandomAnimation();
|
||||
} else {
|
||||
clearTimeout(this.animationTimer);
|
||||
}
|
||||
},
|
||||
|
||||
loadState() {
|
||||
const savedState = localStorage.getItem("kay-gululu-state");
|
||||
if (savedState) {
|
||||
this.state = JSON.parse(savedState);
|
||||
}
|
||||
},
|
||||
|
||||
saveState() {
|
||||
localStorage.setItem("kay-gululu-state", JSON.stringify(this.state));
|
||||
},
|
||||
|
||||
updatePosition() {
|
||||
if (!this.container || !this.isEnabled) return;
|
||||
const { windowRect, containerRect } = this.getRect();
|
||||
|
||||
if (!this.pixelDataCache && this.container.querySelector("img").complete) {
|
||||
this.updatePixelDataCache();
|
||||
}
|
||||
|
||||
const bounds = this.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
|
||||
this.state.left = Math.max(-bounds.left, Math.min(this.state.left, windowRect.width - (bounds.left + bounds.width)));
|
||||
this.state.top = Math.max(-bounds.top, Math.min(this.state.top, windowRect.height - (bounds.top + bounds.height)));
|
||||
|
||||
this.container.style.left = `${this.state.left}px`;
|
||||
this.container.style.top = `${this.state.top}px`;
|
||||
|
||||
const midPoint = windowRect.width / 2;
|
||||
this.state.transform = containerRect.left + containerRect.width / 2 > midPoint ? "scaleX(-1)" : "scaleX(1)";
|
||||
this.container.style.transform = this.state.transform;
|
||||
},
|
||||
|
||||
getRect() {
|
||||
return {
|
||||
windowRect: { width: window.innerWidth, height: window.innerHeight },
|
||||
containerRect: this.container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 },
|
||||
};
|
||||
},
|
||||
|
||||
updatePixelDataCache() {
|
||||
const { containerRect } = this.getRect();
|
||||
if (containerRect.width === 0 || containerRect.height === 0) {
|
||||
return;
|
||||
}
|
||||
this.pixelCanvas.width = containerRect.width;
|
||||
this.pixelCanvas.height = containerRect.height;
|
||||
this.pixelCtx.clearRect(0, 0, this.pixelCanvas.width, this.pixelCanvas.height);
|
||||
this.pixelCtx.drawImage(this.container.querySelector("img"), 0, 0, containerRect.width, containerRect.height);
|
||||
this.pixelDataCache = this.pixelCtx.getImageData(0, 0, this.pixelCanvas.width, this.pixelCanvas.height);
|
||||
|
||||
this.calculateEffectiveBounds();
|
||||
},
|
||||
|
||||
calculateEffectiveBounds() {
|
||||
if (!this.pixelDataCache) return;
|
||||
|
||||
const data = this.pixelDataCache.data;
|
||||
const width = this.pixelCanvas.width;
|
||||
const height = this.pixelCanvas.height;
|
||||
let minX = width,
|
||||
minY = height,
|
||||
maxX = 0,
|
||||
maxY = 0;
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const index = (y * width + x) * 4;
|
||||
if (data[index + 3] !== 0) {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.effectiveBounds = {
|
||||
left: minX,
|
||||
top: minY,
|
||||
width: maxX - minX + 1,
|
||||
height: maxY - minY + 1,
|
||||
};
|
||||
},
|
||||
|
||||
isPixelTransparent(x, y) {
|
||||
if (!this.pixelDataCache) return true;
|
||||
const index = (y * this.pixelDataCache.width + x) * 4;
|
||||
return this.pixelDataCache.data[index + 3] === 0;
|
||||
},
|
||||
|
||||
handleMouseMove(e) {
|
||||
const { containerRect } = this.getRect();
|
||||
const offsetX = e.clientX - containerRect.left;
|
||||
const offsetY = e.clientY - containerRect.top;
|
||||
|
||||
if (offsetX >= 0 && offsetX < containerRect.width && offsetY >= 0 && offsetY < containerRect.height) {
|
||||
this.container.style.cursor = this.isPixelTransparent(offsetX, offsetY) ? "default" : "move";
|
||||
}
|
||||
},
|
||||
|
||||
onDragStart(e) {
|
||||
e.preventDefault();
|
||||
const { containerRect } = this.getRect();
|
||||
const offsetX = e.clientX - containerRect.left;
|
||||
const offsetY = e.clientY - containerRect.top;
|
||||
|
||||
if (this.isPixelTransparent(offsetX, offsetY)) return;
|
||||
|
||||
this.dragState.isDragging = true;
|
||||
this.dragState.offsetX = offsetX;
|
||||
this.dragState.offsetY = offsetY;
|
||||
},
|
||||
|
||||
onDragging(e) {
|
||||
if (!this.dragState.isDragging) return;
|
||||
|
||||
const { windowRect, containerRect } = this.getRect();
|
||||
this.state.left = e.clientX - this.dragState.offsetX;
|
||||
this.state.top = e.clientY - this.dragState.offsetY;
|
||||
|
||||
const bounds = this.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
|
||||
this.state.left = Math.max(-bounds.left, Math.min(this.state.left, windowRect.width - (bounds.left + bounds.width)));
|
||||
this.state.top = Math.max(-bounds.top, Math.min(this.state.top, windowRect.height - (bounds.top + bounds.height)));
|
||||
|
||||
this.updatePosition();
|
||||
},
|
||||
|
||||
onDragEnd() {
|
||||
if (this.dragState.isDragging) {
|
||||
this.dragState.isDragging = false;
|
||||
this.saveState();
|
||||
this.updatePosition();
|
||||
}
|
||||
},
|
||||
|
||||
onContextMenu(e) {
|
||||
e.preventDefault();
|
||||
const { containerRect } = this.getRect();
|
||||
const offsetX = e.clientX - containerRect.left;
|
||||
const offsetY = e.clientY - containerRect.top;
|
||||
|
||||
if (this.isPixelTransparent(offsetX, offsetY)) return;
|
||||
|
||||
this.hideContextMenu();
|
||||
this.showContextMenu(e.clientX, e.clientY);
|
||||
},
|
||||
|
||||
showContextMenu(x, y) {
|
||||
const menuItems = [];
|
||||
|
||||
// 根据 menuConfig 动态生成菜单项
|
||||
this.menuConfig.forEach((config) => {
|
||||
let action;
|
||||
if (config.action === "playAnimation") {
|
||||
action = () => this.playAnimation();
|
||||
} else if (KayToolActions[config.action]) {
|
||||
action = () => KayToolActions[config.action]();
|
||||
}
|
||||
if (action) {
|
||||
menuItems.push({
|
||||
label: config.label,
|
||||
action,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.contextMenu = document.createElement("div");
|
||||
this.contextMenu.className = "gululu-context-menu";
|
||||
document.body.appendChild(this.contextMenu);
|
||||
|
||||
// 先创建菜单项以计算宽度
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.style.visibility = "hidden";
|
||||
tempDiv.style.position = "absolute";
|
||||
tempDiv.style.fontFamily = "'Courier New', monospace";
|
||||
tempDiv.style.fontSize = "14px";
|
||||
tempDiv.style.fontWeight = "bold";
|
||||
tempDiv.style.padding = "4px 12px";
|
||||
tempDiv.style.whiteSpace = "nowrap";
|
||||
document.body.appendChild(tempDiv);
|
||||
|
||||
let maxWidth = 0;
|
||||
menuItems.forEach((item) => {
|
||||
tempDiv.textContent = item.label;
|
||||
const itemWidth = tempDiv.getBoundingClientRect().width;
|
||||
maxWidth = Math.max(maxWidth, itemWidth);
|
||||
});
|
||||
document.body.removeChild(tempDiv);
|
||||
|
||||
// 设置菜单固定宽度(包括 padding)
|
||||
this.contextMenu.style.width = `${maxWidth + 6}px`; // 12px 左右 padding
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
const menuItem = document.createElement("div");
|
||||
menuItem.className = "gululu-context-menu-item";
|
||||
menuItem.textContent = item.label;
|
||||
menuItem.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
item.action();
|
||||
this.hideContextMenu();
|
||||
});
|
||||
this.contextMenu.appendChild(menuItem);
|
||||
});
|
||||
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
const { containerRect } = this.getRect();
|
||||
const gululuCenterY = containerRect.top + containerRect.height / 2;
|
||||
const isAboveHalf = gululuCenterY < windowHeight / 2;
|
||||
|
||||
let menuLeft = x;
|
||||
let menuTop = y;
|
||||
|
||||
// 计算菜单高度并检查边界
|
||||
let { height: menuHeight } = this.contextMenu.getBoundingClientRect();
|
||||
|
||||
// 垂直方向调整,避免遮挡 GuLuLu,并处理滚动
|
||||
if (isAboveHalf) {
|
||||
menuTop = y;
|
||||
if (menuTop + menuHeight > windowHeight - 10) {
|
||||
// 接近底部边界,限制高度,启用滚动
|
||||
this.contextMenu.style.maxHeight = `${windowHeight - 20}px`; // 上下各留10px
|
||||
menuTop = 10; // 确保顶部不贴边
|
||||
}
|
||||
} else {
|
||||
menuTop = y - menuHeight;
|
||||
if (menuTop < 10) {
|
||||
// 接近顶部边界,限制高度,启用滚动
|
||||
this.contextMenu.style.maxHeight = `${windowHeight - 20}px`; // 上下各留10px
|
||||
menuTop = 10; // 确保顶部不贴边
|
||||
}
|
||||
}
|
||||
|
||||
// 水平方向:优先贴近点击点,允许超出右边界
|
||||
const { width: menuWidth } = this.contextMenu.getBoundingClientRect();
|
||||
if (menuLeft + menuWidth > windowWidth) {
|
||||
menuLeft = windowWidth - menuWidth;
|
||||
}
|
||||
|
||||
this.contextMenu.style.left = `${menuLeft - 10}px`;
|
||||
this.contextMenu.style.top = `${menuTop}px`;
|
||||
},
|
||||
|
||||
hideContextMenu() {
|
||||
if (this.contextMenu) {
|
||||
this.contextMenu.remove();
|
||||
this.contextMenu = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 通知框函数(保持不变)
|
||||
export function showNotification({
|
||||
message,
|
||||
bgColor = "#fff3cd",
|
||||
timeout = 0,
|
||||
onYes = null,
|
||||
onNo = null,
|
||||
size = "small"
|
||||
}) {
|
||||
const sizes = {
|
||||
small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
|
||||
medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
|
||||
large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
|
||||
};
|
||||
const selectedSize = sizes[size] || sizes.small;
|
||||
|
||||
const div = document.createElement("div");
|
||||
Object.assign(div.style, {
|
||||
position: "fixed",
|
||||
width: selectedSize.maxWidth,
|
||||
padding: selectedSize.padding,
|
||||
backgroundColor: bgColor,
|
||||
color: "#333",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "14px",
|
||||
wordWrap: "break-word",
|
||||
zIndex: "10000",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "4px 4px 0 #999",
|
||||
opacity: "0",
|
||||
transition: "opacity 0.3s ease-in-out",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
boxSizing: "border-box"
|
||||
});
|
||||
document.body.appendChild(div);
|
||||
|
||||
const isGuLuLuEnabled = window.KayGuLuLuManager && window.KayGuLuLuManager.isEnabled;
|
||||
if (isGuLuLuEnabled) {
|
||||
window.KayGuLuLuManager.playAnimation();
|
||||
}
|
||||
|
||||
let tail, tailBorder;
|
||||
if (isGuLuLuEnabled) {
|
||||
tail = document.createElement("div");
|
||||
Object.assign(tail.style, {
|
||||
position: "absolute",
|
||||
width: "0",
|
||||
height: "0",
|
||||
borderLeft: "6px solid transparent",
|
||||
borderRight: "6px solid transparent",
|
||||
zIndex: "-1"
|
||||
});
|
||||
div.appendChild(tail);
|
||||
|
||||
tailBorder = document.createElement("div");
|
||||
Object.assign(tailBorder.style, {
|
||||
position: "absolute",
|
||||
width: "0",
|
||||
height: "0",
|
||||
borderLeft: "8px solid transparent",
|
||||
borderRight: "8px solid transparent",
|
||||
zIndex: "-2"
|
||||
});
|
||||
div.appendChild(tailBorder);
|
||||
}
|
||||
|
||||
const closeButton = document.createElement("div");
|
||||
closeButton.textContent = "X";
|
||||
Object.assign(closeButton.style, {
|
||||
position: "absolute",
|
||||
top: "12px",
|
||||
left: "12px",
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
backgroundColor: "#dc3545",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "50%",
|
||||
fontSize: "10px",
|
||||
lineHeight: "10px",
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
closeButton.addEventListener("click", () => hideNotification(div));
|
||||
closeButton.addEventListener("mousedown", () => {
|
||||
closeButton.style.transform = "translate(1px, 1px)";
|
||||
closeButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
closeButton.style.transform = "translate(0, 0)";
|
||||
closeButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
div.appendChild(closeButton);
|
||||
|
||||
const contentDiv = document.createElement("div");
|
||||
Object.assign(contentDiv.style, {
|
||||
marginTop: "20px",
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
padding: "0 4px 0 0",
|
||||
boxSizing: "border-box",
|
||||
wordBreak: "break-word",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
whiteSpace: "pre-wrap"
|
||||
});
|
||||
contentDiv.style.cssText += `
|
||||
::-webkit-scrollbar { width: 8px; }
|
||||
::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
|
||||
::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
|
||||
`;
|
||||
div.appendChild(contentDiv);
|
||||
|
||||
let buttonContainer = null;
|
||||
if (onYes || onNo) {
|
||||
buttonContainer = document.createElement("div");
|
||||
Object.assign(buttonContainer.style, {
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "4px",
|
||||
paddingBottom: "4px"
|
||||
});
|
||||
div.appendChild(buttonContainer);
|
||||
|
||||
if (onYes) {
|
||||
const yesButton = document.createElement("button");
|
||||
yesButton.textContent = "Yes";
|
||||
Object.assign(yesButton.style, {
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "#28a745",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "8px",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
yesButton.addEventListener("mousedown", () => {
|
||||
yesButton.style.transform = "translate(1px, 1px)";
|
||||
yesButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
yesButton.style.transform = "translate(0, 0)";
|
||||
yesButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
yesButton.addEventListener("click", () => {
|
||||
hideNotification(div);
|
||||
onYes();
|
||||
});
|
||||
buttonContainer.appendChild(yesButton);
|
||||
}
|
||||
|
||||
if (onNo) {
|
||||
const noButton = document.createElement("button");
|
||||
noButton.textContent = "No";
|
||||
Object.assign(noButton.style, {
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "#dc3545",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "8px",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
noButton.addEventListener("mousedown", () => {
|
||||
noButton.style.transform = "translate(1px, 1px)";
|
||||
noButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
noButton.style.transform = "translate(0, 0)";
|
||||
noButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
noButton.addEventListener("click", () => {
|
||||
hideNotification(div);
|
||||
onNo();
|
||||
});
|
||||
buttonContainer.appendChild(noButton);
|
||||
}
|
||||
}
|
||||
|
||||
const updatePosition = () => {
|
||||
if (isGuLuLuEnabled && window.KayGuLuLuManager && window.KayGuLuLuManager.container) {
|
||||
const gululu = window.KayGuLuLuManager;
|
||||
const containerRect = gululu.container.getBoundingClientRect();
|
||||
const windowRect = { width: window.innerWidth, height: window.innerHeight };
|
||||
const notificationRect = div.getBoundingClientRect();
|
||||
const bounds = gululu.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
|
||||
|
||||
const gululuCenterX = containerRect.left + containerRect.width / 2;
|
||||
const isLeftSide = gululuCenterX < windowRect.width / 2;
|
||||
const horizontalOffset = 10;
|
||||
|
||||
const gululuCenterY = containerRect.top + containerRect.height / 2;
|
||||
const isTopHalf = gululuCenterY < windowRect.height / 2;
|
||||
const verticalOffset = -26;
|
||||
|
||||
let left, top;
|
||||
|
||||
if (isLeftSide) {
|
||||
left = containerRect.right + horizontalOffset;
|
||||
if (tail) {
|
||||
tail.style.left = "-8px";
|
||||
tail.style.right = "auto";
|
||||
tail.style.borderRight = `8px solid ${bgColor}`;
|
||||
tail.style.borderLeft = "none";
|
||||
tail.style.borderTop = "6px solid transparent";
|
||||
tail.style.borderBottom = "6px solid transparent";
|
||||
}
|
||||
if (tailBorder) {
|
||||
tailBorder.style.left = "-10px";
|
||||
tailBorder.style.right = "auto";
|
||||
tailBorder.style.borderRight = "10px solid #000";
|
||||
tailBorder.style.borderLeft = "none";
|
||||
tailBorder.style.borderTop = "8px solid transparent";
|
||||
tailBorder.style.borderBottom = "8px solid transparent";
|
||||
}
|
||||
} else {
|
||||
left = containerRect.left - notificationRect.width - horizontalOffset;
|
||||
if (tail) {
|
||||
tail.style.right = "-8px";
|
||||
tail.style.left = "auto";
|
||||
tail.style.borderLeft = `8px solid ${bgColor}`;
|
||||
tail.style.borderRight = "none";
|
||||
tail.style.borderTop = "6px solid transparent";
|
||||
tail.style.borderBottom = "6px solid transparent";
|
||||
}
|
||||
if (tailBorder) {
|
||||
tailBorder.style.right = "-10px";
|
||||
tailBorder.style.left = "auto";
|
||||
tailBorder.style.borderLeft = "10px solid #000";
|
||||
tailBorder.style.borderRight = "none";
|
||||
tailBorder.style.borderTop = "8px solid transparent";
|
||||
tailBorder.style.borderBottom = "8px solid transparent";
|
||||
}
|
||||
}
|
||||
|
||||
if (isTopHalf) {
|
||||
top = containerRect.top + bounds.top + bounds.height + verticalOffset;
|
||||
if (tail) {
|
||||
tail.style.top = "12px";
|
||||
tail.style.bottom = "auto";
|
||||
tail.style.transform = "none";
|
||||
}
|
||||
if (tailBorder) {
|
||||
tailBorder.style.top = "10px";
|
||||
tailBorder.style.bottom = "auto";
|
||||
tailBorder.style.transform = "none";
|
||||
}
|
||||
} else {
|
||||
top = containerRect.top + bounds.top - notificationRect.height - verticalOffset;
|
||||
if (tail) {
|
||||
tail.style.top = "auto";
|
||||
tail.style.bottom = "12px";
|
||||
tail.style.transform = "none";
|
||||
}
|
||||
if (tailBorder) {
|
||||
tailBorder.style.top = "auto";
|
||||
tailBorder.style.bottom = "10px";
|
||||
tailBorder.style.transform = "none";
|
||||
}
|
||||
}
|
||||
|
||||
left = Math.max(0, Math.min(left, windowRect.width - notificationRect.width));
|
||||
top = Math.max(0, Math.min(top, windowRect.height - notificationRect.height));
|
||||
|
||||
div.style.left = `${left}px`;
|
||||
div.style.top = `${top}px`;
|
||||
} else {
|
||||
div.style.left = "20px";
|
||||
div.style.bottom = "20px";
|
||||
div.style.top = "auto";
|
||||
}
|
||||
};
|
||||
|
||||
const updateHeights = () => {
|
||||
const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
|
||||
const totalExtraHeight = buttonHeight + 36;
|
||||
div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
|
||||
contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
|
||||
updatePosition();
|
||||
};
|
||||
updateHeights();
|
||||
|
||||
const throttle = (func, limit) => {
|
||||
let lastCall = 0;
|
||||
return (...args) => {
|
||||
const now = Date.now();
|
||||
if (now - lastCall >= limit) {
|
||||
func(...args);
|
||||
lastCall = now;
|
||||
}
|
||||
};
|
||||
};
|
||||
const onGuLuLuMove = throttle(updatePosition, 16);
|
||||
if (isGuLuLuEnabled) {
|
||||
document.addEventListener("mousemove", onGuLuLuMove);
|
||||
document.addEventListener("mouseup", onGuLuLuMove);
|
||||
}
|
||||
window.addEventListener("resize", updatePosition);
|
||||
|
||||
const cleanup = () => {
|
||||
document.removeEventListener("mousemove", onGuLuLuMove);
|
||||
document.removeEventListener("mouseup", onGuLuLuMove);
|
||||
window.removeEventListener("resize", updatePosition);
|
||||
};
|
||||
|
||||
try {
|
||||
marked.setOptions({ breaks: true, gfm: true });
|
||||
|
||||
if (!message || typeof message !== "string" || message.trim() === "") {
|
||||
contentDiv.textContent = "无消息显示";
|
||||
updateHeights();
|
||||
return div;
|
||||
}
|
||||
|
||||
if (isGuLuLuEnabled) {
|
||||
const chars = message.split("");
|
||||
let charIndex = 0;
|
||||
let currentMarkdown = "";
|
||||
let renderedHtml = "";
|
||||
|
||||
function typeWriter() {
|
||||
if (charIndex >= chars.length) {
|
||||
if (currentMarkdown.trim()) {
|
||||
renderedHtml += marked.parse(currentMarkdown);
|
||||
}
|
||||
contentDiv.innerHTML = renderedHtml;
|
||||
updateHeights();
|
||||
return;
|
||||
}
|
||||
|
||||
currentMarkdown += chars[charIndex];
|
||||
const isNewline = chars[charIndex] === "\n";
|
||||
const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === "\n";
|
||||
|
||||
if (isDoubleNewline) {
|
||||
if (currentMarkdown.trim()) {
|
||||
renderedHtml += marked.parse(currentMarkdown);
|
||||
currentMarkdown = "";
|
||||
}
|
||||
contentDiv.innerHTML = renderedHtml;
|
||||
} else {
|
||||
contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
|
||||
}
|
||||
|
||||
charIndex++;
|
||||
setTimeout(typeWriter, 50);
|
||||
}
|
||||
|
||||
setTimeout(typeWriter, 300);
|
||||
} else {
|
||||
contentDiv.innerHTML = marked.parse(message);
|
||||
updateHeights();
|
||||
}
|
||||
} catch (e) {
|
||||
contentDiv.textContent = message || "显示消息出错";
|
||||
updateHeights();
|
||||
}
|
||||
|
||||
setTimeout(() => (div.style.opacity = "1"), 10);
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => {
|
||||
cleanup();
|
||||
hideNotification(div);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
const originalHide = hideNotification.bind(null, div);
|
||||
div.hide = () => {
|
||||
cleanup();
|
||||
originalHide();
|
||||
};
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
export function hideNotification(element) {
|
||||
if (element?.parentNode) {
|
||||
element.style.opacity = "0";
|
||||
setTimeout(() => element.parentNode?.removeChild(element), 300);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册扩展
|
||||
app.registerExtension({
|
||||
name: "KayTool.GuLuLu",
|
||||
init() {
|
||||
KayGuLuLuManager.init();
|
||||
},
|
||||
cleanup() {
|
||||
document.removeEventListener("mousemove", KayGuLuLuManager.onDragging);
|
||||
document.removeEventListener("mouseup", KayGuLuLuManager.onDragEnd);
|
||||
document.removeEventListener("click", KayGuLuLuManager.hideContextMenu);
|
||||
KayGuLuLuManager.container?.remove();
|
||||
KayGuLuLuManager.pixelCanvas?.remove();
|
||||
KayGuLuLuManager.contextMenu?.remove();
|
||||
clearTimeout(KayGuLuLuManager.animationTimer);
|
||||
delete window.KayGuLuLuManager;
|
||||
},
|
||||
});
|
||||
+336
-246
@@ -1,267 +1,357 @@
|
||||
import { marked } from "./lib/marked.esm.js";
|
||||
// import { marked } from "./lib/marked.esm.js";
|
||||
|
||||
export function showNotification({
|
||||
message,
|
||||
bgColor = "#fff3cd",
|
||||
timeout = 0,
|
||||
onYes = null,
|
||||
onNo = null,
|
||||
size = "small"
|
||||
}) {
|
||||
const sizes = {
|
||||
small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
|
||||
medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
|
||||
large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
|
||||
};
|
||||
const selectedSize = sizes[size] || sizes.small;
|
||||
// export function showNotification({
|
||||
// message,
|
||||
// bgColor = "#fff3cd",
|
||||
// timeout = 0,
|
||||
// onYes = null,
|
||||
// onNo = null,
|
||||
// size = "small"
|
||||
// }) {
|
||||
// const sizes = {
|
||||
// small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
|
||||
// medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
|
||||
// large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
|
||||
// };
|
||||
// const selectedSize = sizes[size] || sizes.small;
|
||||
|
||||
const div = document.createElement("div");
|
||||
Object.assign(div.style, {
|
||||
position: "fixed",
|
||||
bottom: "20px",
|
||||
left: "50px",
|
||||
width: selectedSize.maxWidth,
|
||||
padding: selectedSize.padding,
|
||||
backgroundColor: bgColor,
|
||||
color: "#333",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "14px",
|
||||
// lineHeight: "1.4",
|
||||
wordWrap: "break-word",
|
||||
zIndex: "10000",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "4px 4px 0 #999",
|
||||
opacity: "0",
|
||||
transition: "opacity 0.3s ease-in-out",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
boxSizing: "border-box"
|
||||
});
|
||||
document.body.appendChild(div);
|
||||
// const div = document.createElement("div");
|
||||
// Object.assign(div.style, {
|
||||
// position: "fixed",
|
||||
// // Position will be set dynamically below
|
||||
// width: selectedSize.maxWidth,
|
||||
// padding: selectedSize.padding,
|
||||
// backgroundColor: bgColor,
|
||||
// color: "#333",
|
||||
// fontFamily: "'Courier New', monospace",
|
||||
// fontSize: "14px",
|
||||
// wordWrap: "break-word",
|
||||
// zIndex: "10000",
|
||||
// border: "2px solid #000",
|
||||
// borderRadius: "12px",
|
||||
// boxShadow: "4px 4px 0 #999",
|
||||
// opacity: "0",
|
||||
// transition: "opacity 0.3s ease-in-out",
|
||||
// display: "flex",
|
||||
// flexDirection: "column",
|
||||
// gap: "8px",
|
||||
// boxSizing: "border-box"
|
||||
// });
|
||||
// document.body.appendChild(div);
|
||||
|
||||
const tail = document.createElement("div");
|
||||
Object.assign(tail.style, {
|
||||
position: "absolute",
|
||||
bottom: "-8px",
|
||||
left: "10px",
|
||||
width: "0",
|
||||
height: "0",
|
||||
borderLeft: "6px solid transparent",
|
||||
borderRight: "6px solid transparent",
|
||||
borderTop: `8px solid ${bgColor}`,
|
||||
zIndex: "-1"
|
||||
});
|
||||
div.appendChild(tail);
|
||||
// // Tail for visual connection to GuLuLu
|
||||
// const tail = document.createElement("div");
|
||||
// Object.assign(tail.style, {
|
||||
// position: "absolute",
|
||||
// width: "0",
|
||||
// height: "0",
|
||||
// borderLeft: "6px solid transparent",
|
||||
// borderRight: "6px solid transparent",
|
||||
// zIndex: "-1"
|
||||
// });
|
||||
// div.appendChild(tail);
|
||||
|
||||
const tailBorder = document.createElement("div");
|
||||
Object.assign(tailBorder.style, {
|
||||
position: "absolute",
|
||||
bottom: "-10px",
|
||||
left: "8px",
|
||||
width: "0",
|
||||
height: "0",
|
||||
borderLeft: "8px solid transparent",
|
||||
borderRight: "8px solid transparent",
|
||||
borderTop: "10px solid #000",
|
||||
zIndex: "-2"
|
||||
});
|
||||
div.appendChild(tailBorder);
|
||||
// const tailBorder = document.createElement("div");
|
||||
// Object.assign(tailBorder.style, {
|
||||
// position: "absolute",
|
||||
// width: "0",
|
||||
// height: "0",
|
||||
// borderLeft: "8px solid transparent",
|
||||
// borderRight: "8px solid transparent",
|
||||
// zIndex: "-2"
|
||||
// });
|
||||
// div.appendChild(tailBorder);
|
||||
|
||||
const closeButton = document.createElement("div");
|
||||
closeButton.textContent = "X";
|
||||
Object.assign(closeButton.style, {
|
||||
position: "absolute",
|
||||
top: "12px",
|
||||
left: "12px",
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
backgroundColor: "#dc3545",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "50%",
|
||||
fontSize: "10px",
|
||||
lineHeight: "10px",
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
closeButton.addEventListener("click", () => hideNotification(div));
|
||||
closeButton.addEventListener("mousedown", () => {
|
||||
closeButton.style.transform = "translate(1px, 1px)";
|
||||
closeButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
closeButton.style.transform = "translate(0, 0)";
|
||||
closeButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
div.appendChild(closeButton);
|
||||
// const closeButton = document.createElement("div");
|
||||
// closeButton.textContent = "X";
|
||||
// Object.assign(closeButton.style, {
|
||||
// position: "absolute",
|
||||
// top: "12px",
|
||||
// left: "12px",
|
||||
// width: "12px",
|
||||
// height: "12px",
|
||||
// backgroundColor: "#dc3545",
|
||||
// border: "2px solid #000",
|
||||
// borderRadius: "50%",
|
||||
// fontSize: "10px",
|
||||
// lineHeight: "10px",
|
||||
// textAlign: "center",
|
||||
// cursor: "pointer",
|
||||
// boxShadow: "2px 2px 0 #999"
|
||||
// });
|
||||
// closeButton.addEventListener("click", () => hideNotification(div));
|
||||
// closeButton.addEventListener("mousedown", () => {
|
||||
// closeButton.style.transform = "translate(1px, 1px)";
|
||||
// closeButton.style.boxShadow = "0 0 0 #999";
|
||||
// });
|
||||
// document.addEventListener("mouseup", () => {
|
||||
// closeButton.style.transform = "translate(0, 0)";
|
||||
// closeButton.style.boxShadow = "2px 2px 0 #999";
|
||||
// });
|
||||
// div.appendChild(closeButton);
|
||||
|
||||
const contentDiv = document.createElement("div");
|
||||
Object.assign(contentDiv.style, {
|
||||
marginTop: "20px",
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
padding: "0 4px 0 0",
|
||||
boxSizing: "border-box",
|
||||
wordBreak: "break-word",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
whiteSpace: "pre-wrap" // 保留换行和空格
|
||||
});
|
||||
contentDiv.style.cssText += `
|
||||
::-webkit-scrollbar { width: 8px; }
|
||||
::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
|
||||
::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
|
||||
`;
|
||||
div.appendChild(contentDiv);
|
||||
// const contentDiv = document.createElement("div");
|
||||
// Object.assign(contentDiv.style, {
|
||||
// marginTop: "20px",
|
||||
// overflowY: "auto",
|
||||
// overflowX: "hidden",
|
||||
// padding: "0 4px 0 0",
|
||||
// boxSizing: "border-box",
|
||||
// wordBreak: "break-word",
|
||||
// fontFamily: "'Courier New', monospace",
|
||||
// whiteSpace: "pre-wrap"
|
||||
// });
|
||||
// contentDiv.style.cssText += `
|
||||
// ::-webkit-scrollbar { width: 8px; }
|
||||
// ::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
|
||||
// ::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
|
||||
// `;
|
||||
// div.appendChild(contentDiv);
|
||||
|
||||
let buttonContainer = null;
|
||||
if (onYes || onNo) {
|
||||
buttonContainer = document.createElement("div");
|
||||
Object.assign(buttonContainer.style, {
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "4px",
|
||||
paddingBottom: "4px"
|
||||
});
|
||||
div.appendChild(buttonContainer);
|
||||
// let buttonContainer = null;
|
||||
// if (onYes || onNo) {
|
||||
// buttonContainer = document.createElement("div");
|
||||
// Object.assign(buttonContainer.style, {
|
||||
// display: "flex",
|
||||
// gap: "8px",
|
||||
// justifyContent: "flex-end",
|
||||
// marginTop: "4px",
|
||||
// paddingBottom: "4px"
|
||||
// });
|
||||
// div.appendChild(buttonContainer);
|
||||
|
||||
if (onYes) {
|
||||
const yesButton = document.createElement("button");
|
||||
yesButton.textContent = "Yes";
|
||||
Object.assign(yesButton.style, {
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "#28a745",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "8px",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
yesButton.addEventListener("mousedown", () => {
|
||||
yesButton.style.transform = "translate(1px, 1px)";
|
||||
yesButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
yesButton.style.transform = "translate(0, 0)";
|
||||
yesButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
yesButton.addEventListener("click", () => {
|
||||
hideNotification(div);
|
||||
onYes();
|
||||
});
|
||||
buttonContainer.appendChild(yesButton);
|
||||
}
|
||||
// if (onYes) {
|
||||
// const yesButton = document.createElement("button");
|
||||
// yesButton.textContent = "Yes";
|
||||
// Object.assign(yesButton.style, {
|
||||
// padding: "4px 8px",
|
||||
// backgroundColor: "#28a745",
|
||||
// border: "2px solid #000",
|
||||
// borderRadius: "8px",
|
||||
// fontFamily: "'Courier New', monospace",
|
||||
// fontSize: "12px",
|
||||
// fontWeight: "bold",
|
||||
// color: "#fff",
|
||||
// cursor: "pointer",
|
||||
// boxShadow: "2px 2px 0 #999"
|
||||
// });
|
||||
// yesButton.addEventListener("mousedown", () => {
|
||||
// yesButton.style.transform = "translate(1px, 1px)";
|
||||
// yesButton.style.boxShadow = "0 0 0 #999";
|
||||
// });
|
||||
// document.addEventListener("mouseup", () => {
|
||||
// yesButton.style.transform = "translate(0, 0)";
|
||||
// yesButton.style.boxShadow = "2px 2px 0 #999";
|
||||
// });
|
||||
// yesButton.addEventListener("click", () => {
|
||||
// hideNotification(div);
|
||||
// onYes();
|
||||
// });
|
||||
// buttonContainer.appendChild(yesButton);
|
||||
// }
|
||||
|
||||
if (onNo) {
|
||||
const noButton = document.createElement("button");
|
||||
noButton.textContent = "No";
|
||||
Object.assign(noButton.style, {
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "#dc3545",
|
||||
border: "2px solid #000",
|
||||
borderRadius: "8px",
|
||||
fontFamily: "'Courier New', monospace",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
cursor: "pointer",
|
||||
boxShadow: "2px 2px 0 #999"
|
||||
});
|
||||
noButton.addEventListener("mousedown", () => {
|
||||
noButton.style.transform = "translate(1px, 1px)";
|
||||
noButton.style.boxShadow = "0 0 0 #999";
|
||||
});
|
||||
document.addEventListener("mouseup", () => {
|
||||
noButton.style.transform = "translate(0, 0)";
|
||||
noButton.style.boxShadow = "2px 2px 0 #999";
|
||||
});
|
||||
noButton.addEventListener("click", () => {
|
||||
hideNotification(div);
|
||||
onNo();
|
||||
});
|
||||
buttonContainer.appendChild(noButton);
|
||||
}
|
||||
}
|
||||
// if (onNo) {
|
||||
// const noButton = document.createElement("button");
|
||||
// noButton.textContent = "No";
|
||||
// Object.assign(noButton.style, {
|
||||
// padding: "4px 8px",
|
||||
// backgroundColor: "#dc3545",
|
||||
// border: "2px solid #000",
|
||||
// borderRadius: "8px",
|
||||
// fontFamily: "'Courier New', monospace",
|
||||
// fontSize: "12px",
|
||||
// fontWeight: "bold",
|
||||
// color: "#fff",
|
||||
// cursor: "pointer",
|
||||
// boxShadow: "2px 2px 0 #999"
|
||||
// });
|
||||
// noButton.addEventListener("mousedown", () => {
|
||||
// noButton.style.transform = "translate(1px, 1px)";
|
||||
// noButton.style.boxShadow = "0 0 0 #999";
|
||||
// });
|
||||
// document.addEventListener("mouseup", () => {
|
||||
// noButton.style.transform = "translate(0, 0)";
|
||||
// noButton.style.boxShadow = "2px 2px 0 #999";
|
||||
// });
|
||||
// noButton.addEventListener("click", () => {
|
||||
// hideNotification(div);
|
||||
// onNo();
|
||||
// });
|
||||
// buttonContainer.appendChild(noButton);
|
||||
// }
|
||||
// }
|
||||
|
||||
const updateHeights = () => {
|
||||
const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
|
||||
const totalExtraHeight = buttonHeight + 36;
|
||||
div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
|
||||
contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
|
||||
};
|
||||
updateHeights();
|
||||
// // Function to update notification position based on GuLuLu
|
||||
// const updatePosition = () => {
|
||||
// if (!window.KayGuLuLuManager || !window.KayGuLuLuManager.container) {
|
||||
// console.warn("KayGuLuLuManager not found, using fallback position");
|
||||
// div.style.left = "50px";
|
||||
// div.style.bottom = "20px";
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 打字机效果:基于 Markdown 原始文本,仅双换行触发解析
|
||||
try {
|
||||
marked.setOptions({ breaks: true, gfm: true });
|
||||
// const gululu = window.KayGuLuLuManager;
|
||||
// const containerRect = gululu.container.getBoundingClientRect();
|
||||
// const windowRect = { width: window.innerWidth, height: window.innerHeight };
|
||||
// const notificationRect = div.getBoundingClientRect();
|
||||
// const bounds = gululu.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
|
||||
|
||||
if (!message || typeof message !== "string" || message.trim() === "") {
|
||||
contentDiv.textContent = "No message to display";
|
||||
updateHeights();
|
||||
return div;
|
||||
}
|
||||
// // Determine side placement
|
||||
// const gululuCenterX = containerRect.left + containerRect.width / 2;
|
||||
// const isLeftSide = gululuCenterX < windowRect.width / 2;
|
||||
// const horizontalOffset = 10; // Distance from GuLuLu
|
||||
|
||||
// 分割 Markdown 原始文本为字符数组
|
||||
const chars = message.split('');
|
||||
let charIndex = 0;
|
||||
let currentMarkdown = ''; // 当前累积的 Markdown 文本
|
||||
let renderedHtml = ''; // 已渲染的 HTML
|
||||
// // Determine vertical placement
|
||||
// const gululuCenterY = containerRect.top + containerRect.height / 2;
|
||||
// const isTopHalf = gululuCenterY < windowRect.height / 2;
|
||||
// const verticalOffset = 10; // Distance from GuLuLu
|
||||
|
||||
function typeWriter() {
|
||||
if (charIndex >= chars.length) {
|
||||
// 所有字符打完,解析剩余的 Markdown
|
||||
if (currentMarkdown.trim()) {
|
||||
renderedHtml += marked.parse(currentMarkdown);
|
||||
}
|
||||
contentDiv.innerHTML = renderedHtml;
|
||||
updateHeights();
|
||||
return;
|
||||
}
|
||||
// let left, top;
|
||||
|
||||
// 逐字符追加到 currentMarkdown
|
||||
currentMarkdown += chars[charIndex];
|
||||
const isNewline = chars[charIndex] === '\n';
|
||||
const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === '\n';
|
||||
// // Horizontal position
|
||||
// if (isLeftSide) {
|
||||
// // Notification to the right of GuLuLu
|
||||
// left = containerRect.left + bounds.left + bounds.width + horizontalOffset;
|
||||
// tail.style.left = "10px";
|
||||
// tail.style.right = "auto";
|
||||
// tailBorder.style.left = "8px";
|
||||
// tailBorder.style.right = "auto";
|
||||
// } else {
|
||||
// // Notification to the left of GuLuLu
|
||||
// left = containerRect.left + bounds.left - notificationRect.width - horizontalOffset;
|
||||
// tail.style.right = "10px";
|
||||
// tail.style.left = "auto";
|
||||
// tailBorder.style.right = "8px";
|
||||
// tailBorder.style.left = "auto";
|
||||
// }
|
||||
|
||||
if (isDoubleNewline) {
|
||||
// 遇到双换行,解析当前累积的 Markdown
|
||||
if (currentMarkdown.trim()) {
|
||||
renderedHtml += marked.parse(currentMarkdown);
|
||||
currentMarkdown = ''; // 重置当前 Markdown
|
||||
}
|
||||
contentDiv.innerHTML = renderedHtml;
|
||||
} else {
|
||||
// 显示已渲染的 HTML + 当前未解析的 Markdown
|
||||
contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
|
||||
}
|
||||
// // Vertical position
|
||||
// if (isTopHalf) {
|
||||
// // Notification below GuLuLu
|
||||
// top = containerRect.top + bounds.top + bounds.height + verticalOffset;
|
||||
// tail.style.top = "auto";
|
||||
// tail.style.bottom = "-8px";
|
||||
// tail.style.borderTop = `8px solid ${bgColor}`;
|
||||
// tail.style.borderBottom = "none";
|
||||
// tailBorder.style.top = "auto";
|
||||
// tailBorder.style.bottom = "-10px";
|
||||
// tailBorder.style.borderTop = "10px solid #000";
|
||||
// tailBorder.style.borderBottom = "none";
|
||||
// } else {
|
||||
// // Notification above GuLuLu
|
||||
// top = containerRect.top + bounds.top - notificationRect.height - verticalOffset;
|
||||
// tail.style.bottom = "auto";
|
||||
// tail.style.top = "-8px";
|
||||
// tail.style.borderBottom = `8px solid ${bgColor}`;
|
||||
// tail.style.borderTop = "none";
|
||||
// tailBorder.style.bottom = "auto";
|
||||
// tailBorder.style.top = "-10px";
|
||||
// tailBorder.style.borderBottom = "10px solid #000";
|
||||
// tailBorder.style.borderTop = "none";
|
||||
// }
|
||||
|
||||
charIndex++;
|
||||
setTimeout(typeWriter, 50); // 字符间间隔 100ms
|
||||
}
|
||||
// // Clamp to screen boundaries
|
||||
// left = Math.max(0, Math.min(left, windowRect.width - notificationRect.width));
|
||||
// top = Math.max(0, Math.min(top, windowRect.height - notificationRect.height));
|
||||
|
||||
setTimeout(typeWriter, 300);
|
||||
} catch (e) {
|
||||
console.error("Error in typewriter effect:", e);
|
||||
contentDiv.textContent = message || "Error displaying message";
|
||||
updateHeights();
|
||||
}
|
||||
// div.style.left = `${left}px`;
|
||||
// div.style.top = `${top}px`;
|
||||
// };
|
||||
|
||||
setTimeout(() => div.style.opacity = "1", 10);
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => hideNotification(div), timeout);
|
||||
}
|
||||
// // Update heights and allow vertical adjustments for typewriter effect
|
||||
// const updateHeights = () => {
|
||||
// const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
|
||||
// const totalExtraHeight = buttonHeight + 36;
|
||||
// div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
|
||||
// contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
|
||||
// updatePosition(); // Recompute position after height changes
|
||||
// };
|
||||
// updateHeights();
|
||||
|
||||
return div;
|
||||
}
|
||||
// // Update position when GuLuLu moves
|
||||
// const onGuLuLuMove = () => updatePosition();
|
||||
// document.addEventListener('mousemove', onGuLuLuMove);
|
||||
// document.addEventListener('mouseup', onGuLuLuMove);
|
||||
// window.addEventListener('resize', updatePosition);
|
||||
|
||||
export function hideNotification(element) {
|
||||
if (element?.parentNode) {
|
||||
element.style.opacity = "0";
|
||||
setTimeout(() => element.parentNode?.removeChild(element), 300);
|
||||
}
|
||||
}
|
||||
// // Clean up event listeners when notification is removed
|
||||
// const cleanup = () => {
|
||||
// document.removeEventListener('mousemove', onGuLuLuMove);
|
||||
// document.removeEventListener('mouseup', onGuLuLuMove);
|
||||
// window.removeEventListener('resize', updatePosition);
|
||||
// };
|
||||
|
||||
// // Typewriter effect
|
||||
// try {
|
||||
// marked.setOptions({ breaks: true, gfm: true });
|
||||
|
||||
// if (!message || typeof message !== "string" || message.trim() === "") {
|
||||
// contentDiv.textContent = "No message to display";
|
||||
// updateHeights();
|
||||
// return div;
|
||||
// }
|
||||
|
||||
// const chars = message.split('');
|
||||
// let charIndex = 0;
|
||||
// let currentMarkdown = '';
|
||||
// let renderedHtml = '';
|
||||
|
||||
// function typeWriter() {
|
||||
// if (charIndex >= chars.length) {
|
||||
// if (currentMarkdown.trim()) {
|
||||
// renderedHtml += marked.parse(currentMarkdown);
|
||||
// }
|
||||
// contentDiv.innerHTML = renderedHtml;
|
||||
// updateHeights();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// currentMarkdown += chars[charIndex];
|
||||
// const isNewline = chars[charIndex] === '\n';
|
||||
// const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === '\n';
|
||||
|
||||
// if (isDoubleNewline) {
|
||||
// if (currentMarkdown.trim()) {
|
||||
// renderedHtml += marked.parse(currentMarkdown);
|
||||
// currentMarkdown = '';
|
||||
// }
|
||||
// contentDiv.innerHTML = renderedHtml;
|
||||
// } else {
|
||||
// contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
|
||||
// }
|
||||
|
||||
// charIndex++;
|
||||
// setTimeout(typeWriter, 50);
|
||||
// }
|
||||
|
||||
// setTimeout(typeWriter, 300);
|
||||
// } catch (e) {
|
||||
// console.error("Error in typewriter effect:", e);
|
||||
// contentDiv.textContent = message || "Error displaying message";
|
||||
// updateHeights();
|
||||
// }
|
||||
|
||||
// setTimeout(() => div.style.opacity = "1", 10);
|
||||
// if (timeout > 0) {
|
||||
// setTimeout(() => {
|
||||
// cleanup();
|
||||
// hideNotification(div);
|
||||
// }, timeout);
|
||||
// }
|
||||
|
||||
// // Override hideNotification to include cleanup
|
||||
// const originalHide = hideNotification.bind(null, div);
|
||||
// div.hide = () => {
|
||||
// cleanup();
|
||||
// originalHide();
|
||||
// };
|
||||
|
||||
// return div;
|
||||
// }
|
||||
|
||||
// export function hideNotification(element) {
|
||||
// if (element?.parentNode) {
|
||||
// element.style.opacity = "0";
|
||||
// setTimeout(() => element.parentNode?.removeChild(element), 300);
|
||||
// }
|
||||
// }
|
||||
+15
-8
@@ -134,6 +134,11 @@ const KayResourceMonitor = {
|
||||
isEnabled: true,
|
||||
isInitialized: false,
|
||||
edgeFadeEnabled: true,
|
||||
// 硬编码菜单边界
|
||||
menuOffsets: {
|
||||
left: 0, // 左侧菜单宽度
|
||||
top: 0 // 顶部菜单高度
|
||||
},
|
||||
easeOutQuad(t) {
|
||||
return t * (2 - t);
|
||||
},
|
||||
@@ -142,10 +147,11 @@ const KayResourceMonitor = {
|
||||
this.isInitialized = true;
|
||||
this.currentWorkflow.percentage = 0;
|
||||
this.workflowProgress.currentNodePercentage = 0;
|
||||
this.isEnabled = app.ui.settings.getSettingValue("KayTool.EnableResourceMonitor");
|
||||
this.edgeFadeEnabled = app.ui.settings.getSettingValue("KayTool.ResourceMonitorEdgeFade");
|
||||
this.loadVisibility();
|
||||
this.startMonitoring();
|
||||
this.bindEvents();
|
||||
await this.loadSettings();
|
||||
this.setupUI();
|
||||
this.setupWorkflowListener();
|
||||
this.setupWebSocketListener();
|
||||
@@ -719,8 +725,10 @@ const KayResourceMonitor = {
|
||||
},
|
||||
loadPosition() {
|
||||
const pos = JSON.parse(localStorage.getItem('KayResourceMonitorPosition')) || {};
|
||||
this.position.left = pos.left || 0;
|
||||
this.position.top = pos.top || 0;
|
||||
const leftOffset = this.menuOffsets.left + 32; // 左侧菜单 + 20px
|
||||
const topOffset = window.innerHeight - this.size.height - 20; // 底部 20px
|
||||
this.position.left = pos.left != null ? pos.left : leftOffset;
|
||||
this.position.top = pos.top != null ? pos.top : topOffset;
|
||||
this.toolbar.style.left = `${this.position.left}px`;
|
||||
this.toolbar.style.top = `${this.position.top}px`;
|
||||
},
|
||||
@@ -739,8 +747,8 @@ const KayResourceMonitor = {
|
||||
if (!this.toolbar) return;
|
||||
const winW = window.innerWidth;
|
||||
const winH = window.innerHeight;
|
||||
this.position.left = Math.max(0, Math.min(this.position.left, winW - this.size.width));
|
||||
this.position.top = Math.max(0, Math.min(this.position.top, winH - this.size.height));
|
||||
this.position.left = Math.max(this.menuOffsets.left, Math.min(this.position.left, winW - this.size.width));
|
||||
this.position.top = Math.max(this.menuOffsets.top, Math.min(this.position.top, winH - this.size.height));
|
||||
this.toolbar.style.left = `${this.position.left}px`;
|
||||
this.toolbar.style.top = `${this.position.top}px`;
|
||||
this.chartCanvas.width = this.toolbar.clientWidth - 20;
|
||||
@@ -762,8 +770,8 @@ const KayResourceMonitor = {
|
||||
if (!this.dragState.isDragging) return;
|
||||
const winW = window.innerWidth;
|
||||
const winH = window.innerHeight;
|
||||
this.position.left = Math.max(0, Math.min(e.clientX - this.dragState.offsetX, winW - this.size.width));
|
||||
this.position.top = Math.max(0, Math.min(e.clientY - this.dragState.offsetY, winH - this.size.height));
|
||||
this.position.left = Math.max(this.menuOffsets.left, Math.min(e.clientX - this.dragState.offsetX, winW - this.size.width));
|
||||
this.position.top = Math.max(this.menuOffsets.top, Math.min(e.clientY - this.dragState.offsetY, winH - this.size.height));
|
||||
this.toolbar.style.left = `${this.position.left}px`;
|
||||
this.toolbar.style.top = `${this.position.top}px`;
|
||||
},
|
||||
@@ -822,7 +830,6 @@ app.registerExtension({
|
||||
if (app.menu?.settingsGroup) {
|
||||
app.menu.settingsGroup.append(showMenuButton);
|
||||
}
|
||||
KayResourceMonitor.updateEnabledState(KayResourceMonitor.isEnabled);
|
||||
window.KayResourceMonitor = KayResourceMonitor;
|
||||
}
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
+74
-85
@@ -3,6 +3,19 @@ import { app } from "../../../scripts/app.js";
|
||||
app.registerExtension({
|
||||
name: "KayTool.Settings",
|
||||
async setup() {
|
||||
function updateSetGetNodeColors() {
|
||||
const fgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
|
||||
const bgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
|
||||
const allNodes = app.graph._nodes;
|
||||
allNodes.forEach(node => {
|
||||
if (node.type === "KaySetNode" || node.type === "KayGetNode") {
|
||||
node.color = fgColor;
|
||||
node.bgcolor = bgColor;
|
||||
}
|
||||
});
|
||||
app.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
const settings = [
|
||||
{
|
||||
id: "KayTool.ShowRunOption",
|
||||
@@ -17,7 +30,6 @@ app.registerExtension({
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
category: ["KayTool", "🛜 Set/Get", "ShowSetGetOptions"],
|
||||
tooltip: "Toggles visibility of 'Set/Get' options in node context menus."
|
||||
},
|
||||
{
|
||||
id: "KayTool.ShiftR",
|
||||
@@ -41,7 +53,7 @@ app.registerExtension({
|
||||
category: ["KayTool", "Menu", "ShowStarToMe"],
|
||||
},
|
||||
{
|
||||
id: "KayTool.ShowCleanVRAM",
|
||||
id: "KayTool.ShowCleanVRAM",
|
||||
name: "Show '🧹 Clean VRAM' option in KayTool menu",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
@@ -54,19 +66,15 @@ app.registerExtension({
|
||||
defaultValue: 100,
|
||||
attrs: { min: 0, max: 200, step: 10 },
|
||||
category: ["KayTool", "Workflow PNG", "MarginSize"],
|
||||
tooltip: "Default value: 100."
|
||||
},
|
||||
{
|
||||
id: "KayTool.SetGetForegroundColor",
|
||||
name: "Set/Get Node Foreground Color",
|
||||
type: "color",
|
||||
defaultValue: "000000",
|
||||
category: ["KayTool", "🛜 Set/Get", "ForegroundColor"],
|
||||
category: ["KayTool", "🛜 Set/Get", "SetGetForegroundColor"],
|
||||
onChange: (newVal) => {
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
updateSetGetNodeColors();
|
||||
if (app.graph) app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
updateSetGetNodeColors();
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -74,12 +82,9 @@ app.registerExtension({
|
||||
name: "Set/Get Node Background Color",
|
||||
type: "color",
|
||||
defaultValue: "000000",
|
||||
category: ["KayTool", "🛜 Set/Get", "BackgroundColor"],
|
||||
category: ["KayTool", "🛜 Set/Get", "SetGetBackgroundColor"],
|
||||
onChange: (newVal) => {
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
updateSetGetNodeColors();
|
||||
if (app.graph) app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
updateSetGetNodeColors();
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -94,14 +99,9 @@ app.registerExtension({
|
||||
defaultValue: "permanent",
|
||||
category: ["KayTool", "NodeAlignment", "DisplayMode"],
|
||||
onChange: (value) => {
|
||||
const manager = window.KayNodeAlignmentManager;
|
||||
const initFn = window.initializeKayNodeAlignment;
|
||||
if (!manager || !initFn) return;
|
||||
|
||||
if (!manager.isInitialized) {
|
||||
initFn();
|
||||
if (window.KayNodeAlignmentManager) {
|
||||
window.KayNodeAlignmentManager.updateDisplayMode(value);
|
||||
}
|
||||
manager.updateDisplayMode(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -116,19 +116,15 @@ app.registerExtension({
|
||||
id: "KayTool.NodeAlignBackgroundOpacity",
|
||||
name: "Node Alignment Toolbar Background Opacity",
|
||||
type: "slider",
|
||||
defaultValue: 100,
|
||||
defaultValue: 0,
|
||||
attrs: { min: 0, max: 100, step: 1 },
|
||||
category: ["KayTool", "NodeAlignment", "BackgroundOpacity"],
|
||||
onChange: (newVal) => {
|
||||
const toolbar = document.getElementById('kay-node-alignment-toolbar');
|
||||
if (toolbar) {
|
||||
const bgColor = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundColor");
|
||||
const bgColor = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundColor") || "000000";
|
||||
const opacity = newVal / 100;
|
||||
if (opacity > 0 && /^[0-9A-Fa-f]{6}$/.test(bgColor)) {
|
||||
toolbar.style.background = `rgba(${parseInt(bgColor.substr(0, 2), 16)}, ${parseInt(bgColor.substr(2, 2), 16)}, ${parseInt(bgColor.substr(4, 2), 16)}, ${opacity})`;
|
||||
} else {
|
||||
toolbar.style.background = '';
|
||||
}
|
||||
toolbar.style.background = `rgba(${parseInt(bgColor.substr(0, 2), 16)}, ${parseInt(bgColor.substr(2, 2), 16)}, ${parseInt(bgColor.substr(4, 2), 16)}, ${opacity})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -140,13 +136,9 @@ app.registerExtension({
|
||||
category: ["KayTool", "NodeAlignment", "BackgroundColor"],
|
||||
onChange: (newVal) => {
|
||||
const toolbar = document.getElementById('kay-node-alignment-toolbar');
|
||||
if (toolbar && /^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
if (toolbar && newVal) {
|
||||
const opacity = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundOpacity") / 100;
|
||||
if (opacity > 0) {
|
||||
toolbar.style.background = `rgba(${parseInt(newVal.substr(0, 2), 16)}, ${parseInt(newVal.substr(2, 2), 16)}, ${parseInt(newVal.substr(4, 2), 16)}, ${opacity})`;
|
||||
} else {
|
||||
toolbar.style.background = '';
|
||||
}
|
||||
toolbar.style.background = `rgba(${parseInt(newVal.substr(0, 2), 16)}, ${parseInt(newVal.substr(2, 2), 16)}, ${parseInt(newVal.substr(4, 2), 16)}, ${opacity})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -154,43 +146,37 @@ app.registerExtension({
|
||||
id: "KayTool.NodeAlignIconBackgroundColor",
|
||||
name: "Node Alignment Icon Background Color",
|
||||
type: "color",
|
||||
defaultValue: "2b2b2b",
|
||||
defaultValue: "363636",
|
||||
category: ["KayTool", "NodeAlignment", "IconBackgroundColor"],
|
||||
tooltip: "Use parameter “363636” to match ComfyUI menu bar color for an icon-free background effect",
|
||||
onChange: (newVal) => {
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
const buttons = document.querySelectorAll('.kay-align-button');
|
||||
buttons.forEach(btn => btn.style.backgroundColor = `#${newVal}`);
|
||||
}
|
||||
const buttons = document.querySelectorAll('.kay-align-button');
|
||||
buttons.forEach(btn => btn.style.backgroundColor = `#${newVal}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "KayTool.NodeAlignIconColor",
|
||||
name: "Node Alignment Icon Color",
|
||||
type: "color",
|
||||
defaultValue: "666666",
|
||||
defaultValue: "999999",
|
||||
category: ["KayTool", "NodeAlignment", "IconColor"],
|
||||
onChange: (newVal) => {
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
const buttons = document.querySelectorAll('.kay-align-button');
|
||||
buttons.forEach(btn => {
|
||||
const svg = btn.querySelector('svg');
|
||||
if (svg) svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', `#${newVal}`));
|
||||
});
|
||||
}
|
||||
const buttons = document.querySelectorAll('.kay-align-button');
|
||||
buttons.forEach(btn => {
|
||||
const svg = btn.querySelector('svg');
|
||||
if (svg) svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', `#${newVal}`));
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "KayTool.NodeAlignDividerColor",
|
||||
name: "Node Alignment Divider Color",
|
||||
type: "color",
|
||||
defaultValue: "1e1e1e",
|
||||
defaultValue: "f0ff00",
|
||||
category: ["KayTool", "NodeAlignment", "DividerColor"],
|
||||
onChange: (newVal) => {
|
||||
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
|
||||
const dividers = document.querySelectorAll('.kay-toolbar-divider');
|
||||
dividers.forEach(divider => divider.style.background = `#${newVal}`);
|
||||
}
|
||||
const dividers = document.querySelectorAll('.kay-toolbar-divider');
|
||||
dividers.forEach(divider => divider.style.background = `#${newVal}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -214,9 +200,7 @@ app.registerExtension({
|
||||
onChange: (newVal) => {
|
||||
if (window.KayResourceMonitor) {
|
||||
window.KayResourceMonitor.edgeFadeEnabled = newVal;
|
||||
if (window.KayResourceMonitor.isVisible && window.KayResourceMonitor.isEnabled) {
|
||||
window.KayResourceMonitor.drawCurves();
|
||||
}
|
||||
window.KayResourceMonitor.drawCurves();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -224,7 +208,7 @@ app.registerExtension({
|
||||
id: "KayTool.ResourceMonitorBackgroundOpacity",
|
||||
name: "Resource Monitor Background Opacity",
|
||||
type: "slider",
|
||||
defaultValue: 80,
|
||||
defaultValue: 50,
|
||||
attrs: { min: 0, max: 100, step: 1 },
|
||||
category: ["KayTool", "Resource Monitor", "BackgroundOpacity"],
|
||||
onChange: (newVal) => {
|
||||
@@ -233,38 +217,43 @@ app.registerExtension({
|
||||
window.KayResourceMonitor.toolbar.style.background = `rgba(0, 0, 0, ${opacity})`;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "KayTool.GuLuLuSize",
|
||||
name: "GuLuLu Size (px)",
|
||||
type: "slider",
|
||||
defaultValue: 39,
|
||||
attrs: { min: 36, max: 120, step: 1 },
|
||||
category: ["KayTool", "GuLuLu", "Size"],
|
||||
onChange: (newVal) => {
|
||||
if (window.KayGuLuLuManager) {
|
||||
window.KayGuLuLuManager.updateSize(newVal);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "KayTool.EnableGuLuLu",
|
||||
name: "GuLuLu stay with you!",
|
||||
tooltip: "GuLuLu will inherit KayTool's notification items for streaming output, support adjustments to the notification box position, and include powerful right-click menu options.",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
category: ["KayTool", "GuLuLu", "EnableGuLuLu"],
|
||||
onChange: (newVal) => {
|
||||
if (window.KayGuLuLuManager) {
|
||||
window.KayGuLuLuManager.updateEnabledState(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
for (const setting of settings) {
|
||||
app.ui.settings.addSetting(setting);
|
||||
}
|
||||
},
|
||||
init() {
|
||||
if (!app.graph) {
|
||||
const checkGraph = setInterval(() => {
|
||||
if (app.graph) {
|
||||
clearInterval(checkGraph);
|
||||
updateSetGetNodeColors();
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
updateSetGetNodeColors();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateSetGetNodeColors() {
|
||||
const fgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
|
||||
const bgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
|
||||
if (app.graph && app.graph._nodes) {
|
||||
app.graph._nodes.forEach(node => {
|
||||
if (node.type === "KaySetNode" || node.type === "KayGetNode" || node.type === "KayGet多元Node") {
|
||||
node.color = fgColor;
|
||||
node.bgcolor = bgColor;
|
||||
node.updateColors();
|
||||
try {
|
||||
for (const setting of settings) {
|
||||
app.ui.settings.addSetting(setting);
|
||||
}
|
||||
});
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
updateSetGetNodeColors();
|
||||
} catch (error) {
|
||||
console.error("Error in KayTool.Settings setup:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
+24
-24
@@ -1,44 +1,44 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
// 全局动作注册表
|
||||
const KayToolActions = window.KayToolActions || {};
|
||||
window.KayToolActions = KayToolActions;
|
||||
|
||||
function starToMe() {
|
||||
window.open("https://github.com/kk8bit/kaytool", "_blank");
|
||||
}
|
||||
|
||||
// 注册 StarToMe 动作
|
||||
KayToolActions.starToMe = starToMe;
|
||||
|
||||
app.registerExtension({
|
||||
name: "KayTool.StarToMe",
|
||||
setup() {
|
||||
const originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = originalGetCanvasMenuOptions.apply(this, arguments) || [];
|
||||
const showStarToMe = app.ui.settings.getSettingValue("KayTool.ShowStarToMe");
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
|
||||
const newOptions = [...options];
|
||||
const showStarToMe = app.ui.settings.getSettingValue("KayTool.ShowStarToMe") ?? true;
|
||||
|
||||
if (showStarToMe) {
|
||||
const newOptions = [...options];
|
||||
let kaytoolMenu = newOptions.find(opt => opt && opt.content === "KayTool");
|
||||
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
|
||||
content: "KayTool",
|
||||
submenu: { options: [] }
|
||||
};
|
||||
|
||||
if (!kaytoolMenu) {
|
||||
kaytoolMenu = {
|
||||
content: "KayTool",
|
||||
submenu: {
|
||||
options: []
|
||||
}
|
||||
};
|
||||
newOptions.push(null, kaytoolMenu);
|
||||
if (!newOptions.includes(kaytoolMenu)) {
|
||||
newOptions.push(null, kaytoolMenu);
|
||||
}
|
||||
|
||||
kaytoolMenu.submenu.options = kaytoolMenu.submenu.options || [];
|
||||
const starOptionExists = kaytoolMenu.submenu.options.some(
|
||||
opt => opt && opt.content === "⭐ Star to me"
|
||||
);
|
||||
if (!starOptionExists) {
|
||||
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "⭐ Star to me")) {
|
||||
kaytoolMenu.submenu.options.push({
|
||||
content: "⭐ Star to me",
|
||||
callback: () => {
|
||||
window.open("https://github.com/kk8bit/kaytool", "_blank");
|
||||
}
|
||||
callback: starToMe
|
||||
});
|
||||
}
|
||||
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
return options;
|
||||
return newOptions;
|
||||
};
|
||||
}
|
||||
});
|
||||
+31
-26
@@ -1,12 +1,16 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
import { showNotification, hideNotification } from "./notification.js";
|
||||
import { showNotification, hideNotification } from "./gululu.js";
|
||||
|
||||
// 全局动作注册表
|
||||
const KayToolActions = window.KayToolActions || {};
|
||||
window.KayToolActions = KayToolActions;
|
||||
|
||||
class KayWorkflowImage {
|
||||
extension = "png";
|
||||
|
||||
getBounds() {
|
||||
const marginSize = app.ui.settings.getSettingValue("KayTool.WorkflowPNG");
|
||||
const marginSize = app.ui.settings.getSettingValue("KayTool.WorkflowPNG") || 50;
|
||||
const bounds = app.graph._nodes.reduce(
|
||||
(p, node) => {
|
||||
if (node.pos[0] < p[0]) p[0] = node.pos[0];
|
||||
@@ -33,7 +37,7 @@ class KayWorkflowImage {
|
||||
width: app.canvas.canvas.width,
|
||||
height: app.canvas.canvas.height,
|
||||
offset: app.canvas.ds.offset,
|
||||
transform: app.canvas.canvas.getContext('2d').getTransform(),
|
||||
transform: app.canvas.canvas.getContext("2d").getTransform(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -157,6 +161,19 @@ class KayWorkflowImage {
|
||||
}
|
||||
}
|
||||
|
||||
function exportWorkflowPNG() {
|
||||
showNotification({
|
||||
message: `GuLuLu: 你需要把工作流信息嵌入到PNG中吗?啊?Do you need to embed Workflow information into PNG? *GuLuLu~Gulu*`,
|
||||
bgColor: "#fff3cd",
|
||||
size: "medium",
|
||||
onYes: () => new KayWorkflowImage().export(true),
|
||||
onNo: () => new KayWorkflowImage().export(false)
|
||||
});
|
||||
}
|
||||
|
||||
// 注册 Export Workflow PNG 动作
|
||||
KayToolActions.exportWorkflowPNG = exportWorkflowPNG;
|
||||
|
||||
app.registerExtension({
|
||||
name: "KayTool.WorkflowPNG",
|
||||
init() {
|
||||
@@ -236,37 +253,25 @@ app.registerExtension({
|
||||
},
|
||||
setup() {
|
||||
const originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = originalGetCanvasMenuOptions.apply(this, arguments) || [];
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
|
||||
const newOptions = [...options];
|
||||
const showWorkflowPNG = app.ui.settings.getSettingValue("KayTool.ShowWorkflowPNG");
|
||||
const showWorkflowPNG = app.ui.settings.getSettingValue("KayTool.ShowWorkflowPNG") ?? true;
|
||||
|
||||
if (showWorkflowPNG) {
|
||||
let kaytoolMenu = newOptions.find(opt => opt && opt.content === "KayTool");
|
||||
if (!kaytoolMenu) {
|
||||
kaytoolMenu = {
|
||||
content: "KayTool",
|
||||
submenu: { options: [] }
|
||||
};
|
||||
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
|
||||
content: "KayTool",
|
||||
submenu: { options: [] }
|
||||
};
|
||||
|
||||
if (!newOptions.includes(kaytoolMenu)) {
|
||||
newOptions.push(null, kaytoolMenu);
|
||||
}
|
||||
|
||||
kaytoolMenu.submenu.options = kaytoolMenu.submenu.options || [];
|
||||
const exportOptionExists = kaytoolMenu.submenu.options.some(
|
||||
opt => opt && opt.content === "📦 Workflow PNG"
|
||||
);
|
||||
if (!exportOptionExists) {
|
||||
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "📦 Workflow PNG")) {
|
||||
kaytoolMenu.submenu.options.push({
|
||||
content: "📦 Workflow PNG",
|
||||
callback: () => {
|
||||
showNotification({
|
||||
message: `𝙆:你需要把工作流信息嵌入到PNG中吗?Do you need to embed Workflow information into PNG?`,
|
||||
bgColor: "#fff3cd",
|
||||
size: "medium",
|
||||
onYes: () => new KayWorkflowImage().export(true),
|
||||
onNo: () => new KayWorkflowImage().export(false)
|
||||
});
|
||||
}
|
||||
callback: exportWorkflowPNG
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user