From c8ac92e7d354daa13a991470df842686ff251c12 Mon Sep 17 00:00:00 2001 From: AEmotionStudio Date: Mon, 5 Jan 2026 17:34:42 -0800 Subject: [PATCH] feat: improve canvas highlight hook robustness and update node highlight style to a thicker blue border. --- src/info-panel/CanvasHighlighter.ts | 73 +++++++++++++++++-------- web/info-panel/CanvasHighlighter.js | 55 +++++++++++++------ web/info-panel/CanvasHighlighter.js.map | 2 +- 3 files changed, 90 insertions(+), 40 deletions(-) diff --git a/src/info-panel/CanvasHighlighter.ts b/src/info-panel/CanvasHighlighter.ts index 3998ea2..fbdd979 100644 --- a/src/info-panel/CanvasHighlighter.ts +++ b/src/info-panel/CanvasHighlighter.ts @@ -37,21 +37,18 @@ export class CanvasHighlighter { this.originalOnDrawForeground = canvas.onDrawForeground; // Override - canvas.onDrawForeground = (ctx: CanvasRenderingContext2D, visible_nodes: any) => { - // Call original first - if (this.originalOnDrawForeground) { - this.originalOnDrawForeground.call(canvas, ctx, visible_nodes); - } - - // Draw our highlight - this.drawHighlight(ctx, canvas.ds.scale); - }; + canvas.onDrawForeground = this.boundOnDrawForeground; } + /** + * Set the node ID to highlight. + */ /** * Set the node ID to highlight. */ setHighlightedNode(nodeId: number | null): void { + this.ensureHook(); // Ensure we are still hooked + if (this.highlightedNodeId === nodeId) return; this.highlightedNodeId = nodeId; // Force redraw to update highlight immediately @@ -61,6 +58,35 @@ export class CanvasHighlighter { } } + /** + * Ensure the canvas hook is active. + */ + private ensureHook(): void { + const app = (window as any).app; + if (!app || !app.canvas) return; + + // If our hook was overwritten (e.g. by another extension), re-hook + if (app.canvas.onDrawForeground !== this.boundOnDrawForeground) { + // console.log('[MagnifyGlass] Re-hooking CanvasHighlighter'); + this.originalOnDrawForeground = app.canvas.onDrawForeground; + app.canvas.onDrawForeground = this.boundOnDrawForeground; + } + } + + // Bound method to preserve 'this' and allow equality check + private boundOnDrawForeground = (ctx: CanvasRenderingContext2D, visible_nodes: any) => { + // Call original first + if (this.originalOnDrawForeground) { + this.originalOnDrawForeground.call((window as any).app.canvas, ctx, visible_nodes); + } + + // Draw our highlight + const app = (window as any).app; + if (app && app.canvas) { + this.drawHighlight(ctx, app.canvas.ds.scale); + } + }; + /** * Draw the highlight rectangle around the target node. */ @@ -78,23 +104,24 @@ export class CanvasHighlighter { // Note: ctx passed to onDrawForeground is already transformed by LiteGraph to graph coordinates // so we can draw using node.pos directly. - const x = node.pos[0] - this.HIGHLIGHT_PADDING; - const y = node.pos[1] - this.HIGHLIGHT_PADDING; - const w = node.size[0] + (this.HIGHLIGHT_PADDING * 2); - const h = node.size[1] + (this.HIGHLIGHT_PADDING * 2); + const padding = 10; + const x = node.pos[0] - padding; + const y = node.pos[1] - padding; + const w = node.size[0] + (padding * 2); + const h = node.size[1] + (padding * 2); - // Draw glow/outline - ctx.lineWidth = this.HIGHLIGHT_WIDTH; - ctx.strokeStyle = this.HIGHLIGHT_COLOR; - ctx.shadowColor = this.HIGHLIGHT_COLOR; - ctx.shadowBlur = 10 * scale; // Scale shadow with zoom - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; + // Draw large blue bar/border + ctx.lineWidth = 10; // Thicker border + ctx.strokeStyle = '#007bff'; // Blue + // ctx.shadowColor = '#007bff'; // Optional glow + // ctx.shadowBlur = 0; + ctx.lineJoin = 'round'; + ctx.lineCap = 'round'; // Use roundRect if available (modern browsers), else rect ctx.beginPath(); if (typeof ctx.roundRect === 'function') { - const radius = 10; // LiteGraph nodes usually have round corners + const radius = 10; ctx.roundRect(x, y, w, h, radius); } else { ctx.rect(x, y, w, h); @@ -111,7 +138,9 @@ export class CanvasHighlighter { cleanup(): void { const app = (window as any).app; if (app && app.canvas && this.originalOnDrawForeground) { - app.canvas.onDrawForeground = this.originalOnDrawForeground; + if (app.canvas.onDrawForeground === this.boundOnDrawForeground) { + app.canvas.onDrawForeground = this.originalOnDrawForeground; + } } } } diff --git a/web/info-panel/CanvasHighlighter.js b/web/info-panel/CanvasHighlighter.js index 644671b..e813823 100644 --- a/web/info-panel/CanvasHighlighter.js +++ b/web/info-panel/CanvasHighlighter.js @@ -12,6 +12,16 @@ class CanvasHighlighter { __publicField(this, "HIGHLIGHT_WIDTH", 2); // px __publicField(this, "HIGHLIGHT_PADDING", 0); + // Bound method to preserve 'this' and allow equality check + __publicField(this, "boundOnDrawForeground", (ctx, visible_nodes) => { + if (this.originalOnDrawForeground) { + this.originalOnDrawForeground.call(window.app.canvas, ctx, visible_nodes); + } + const app = window.app; + if (app && app.canvas) { + this.drawHighlight(ctx, app.canvas.ds.scale); + } + }); this.hookCanvas(); } /** @@ -25,17 +35,16 @@ class CanvasHighlighter { } const canvas = app.canvas; this.originalOnDrawForeground = canvas.onDrawForeground; - canvas.onDrawForeground = (ctx, visible_nodes) => { - if (this.originalOnDrawForeground) { - this.originalOnDrawForeground.call(canvas, ctx, visible_nodes); - } - this.drawHighlight(ctx, canvas.ds.scale); - }; + canvas.onDrawForeground = this.boundOnDrawForeground; } + /** + * Set the node ID to highlight. + */ /** * Set the node ID to highlight. */ setHighlightedNode(nodeId) { + this.ensureHook(); if (this.highlightedNodeId === nodeId) return; this.highlightedNodeId = nodeId; const app = window.app; @@ -43,6 +52,17 @@ class CanvasHighlighter { app.canvas.setDirty(true, true); } } + /** + * Ensure the canvas hook is active. + */ + ensureHook() { + const app = window.app; + if (!app || !app.canvas) return; + if (app.canvas.onDrawForeground !== this.boundOnDrawForeground) { + this.originalOnDrawForeground = app.canvas.onDrawForeground; + app.canvas.onDrawForeground = this.boundOnDrawForeground; + } + } /** * Draw the highlight rectangle around the target node. */ @@ -53,16 +73,15 @@ class CanvasHighlighter { const node = app.graph.getNodeById(this.highlightedNodeId); if (!node) return; ctx.save(); - const x = node.pos[0] - this.HIGHLIGHT_PADDING; - const y = node.pos[1] - this.HIGHLIGHT_PADDING; - const w = node.size[0] + this.HIGHLIGHT_PADDING * 2; - const h = node.size[1] + this.HIGHLIGHT_PADDING * 2; - ctx.lineWidth = this.HIGHLIGHT_WIDTH; - ctx.strokeStyle = this.HIGHLIGHT_COLOR; - ctx.shadowColor = this.HIGHLIGHT_COLOR; - ctx.shadowBlur = 10 * scale; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; + const padding = 10; + const x = node.pos[0] - padding; + const y = node.pos[1] - padding; + const w = node.size[0] + padding * 2; + const h = node.size[1] + padding * 2; + ctx.lineWidth = 10; + ctx.strokeStyle = "#007bff"; + ctx.lineJoin = "round"; + ctx.lineCap = "round"; ctx.beginPath(); if (typeof ctx.roundRect === "function") { const radius = 10; @@ -79,7 +98,9 @@ class CanvasHighlighter { cleanup() { const app = window.app; if (app && app.canvas && this.originalOnDrawForeground) { - app.canvas.onDrawForeground = this.originalOnDrawForeground; + if (app.canvas.onDrawForeground === this.boundOnDrawForeground) { + app.canvas.onDrawForeground = this.originalOnDrawForeground; + } } } } diff --git a/web/info-panel/CanvasHighlighter.js.map b/web/info-panel/CanvasHighlighter.js.map index 0596d72..de686c0 100644 --- a/web/info-panel/CanvasHighlighter.js.map +++ b/web/info-panel/CanvasHighlighter.js.map @@ -1 +1 @@ -{"version":3,"file":"CanvasHighlighter.js","sources":["../../src/info-panel/CanvasHighlighter.ts"],"sourcesContent":["/**\n * CanvasHighlighter.ts\n * \n * Handles drawing a visual highlight on the main canvas around the currently inspected node.\n * Hooks into LiteGraph's canvas drawing cycle.\n */\n\nimport { ComfyApp, ComfyNode } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\n\nexport class CanvasHighlighter {\n private originalOnDrawForeground: ((ctx: CanvasRenderingContext2D, visible_nodes: any) => void) | null = null;\n private highlightedNodeId: number | null = null;\n\n // Configuration\n private readonly HIGHLIGHT_COLOR = '#007bff'; // Bootstrap blue\n private readonly HIGHLIGHT_WIDTH = 2; // px\n private readonly HIGHLIGHT_PADDING = 0; // px\n\n constructor() {\n this.hookCanvas();\n }\n\n /**\n * Hook into the main canvas onDrawForeground method.\n */\n private hookCanvas(): void {\n const app = (window as any).app;\n if (!app || !app.canvas) {\n console.warn('[MagnifyGlass] Canvas not found, cannot hook highlighter');\n return;\n }\n const canvas = app.canvas;\n\n // Save original method\n this.originalOnDrawForeground = canvas.onDrawForeground;\n\n // Override\n canvas.onDrawForeground = (ctx: CanvasRenderingContext2D, visible_nodes: any) => {\n // Call original first\n if (this.originalOnDrawForeground) {\n this.originalOnDrawForeground.call(canvas, ctx, visible_nodes);\n }\n\n // Draw our highlight\n this.drawHighlight(ctx, canvas.ds.scale);\n };\n }\n\n /**\n * Set the node ID to highlight.\n */\n setHighlightedNode(nodeId: number | null): void {\n if (this.highlightedNodeId === nodeId) return;\n this.highlightedNodeId = nodeId;\n // Force redraw to update highlight immediately\n const app = (window as any).app;\n if (app && app.canvas) {\n app.canvas.setDirty(true, true);\n }\n }\n\n /**\n * Draw the highlight rectangle around the target node.\n */\n private drawHighlight(ctx: CanvasRenderingContext2D, scale: number): void {\n if (this.highlightedNodeId === null) return;\n const app = (window as any).app;\n if (!app) return;\n const node = app.graph.getNodeById(this.highlightedNodeId);\n if (!node) return;\n\n // Save context\n ctx.save();\n\n // Reset transform to draw in graph coordinates\n // Note: ctx passed to onDrawForeground is already transformed by LiteGraph to graph coordinates\n // so we can draw using node.pos directly.\n\n const x = node.pos[0] - this.HIGHLIGHT_PADDING;\n const y = node.pos[1] - this.HIGHLIGHT_PADDING;\n const w = node.size[0] + (this.HIGHLIGHT_PADDING * 2);\n const h = node.size[1] + (this.HIGHLIGHT_PADDING * 2);\n\n // Draw glow/outline\n ctx.lineWidth = this.HIGHLIGHT_WIDTH;\n ctx.strokeStyle = this.HIGHLIGHT_COLOR;\n ctx.shadowColor = this.HIGHLIGHT_COLOR;\n ctx.shadowBlur = 10 * scale; // Scale shadow with zoom\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n\n // Use roundRect if available (modern browsers), else rect\n ctx.beginPath();\n if (typeof ctx.roundRect === 'function') {\n const radius = 10; // LiteGraph nodes usually have round corners\n ctx.roundRect(x, y, w, h, radius);\n } else {\n ctx.rect(x, y, w, h);\n }\n ctx.stroke();\n\n // Restore context\n ctx.restore();\n }\n\n /**\n * Clean up hooks.\n */\n cleanup(): void {\n const app = (window as any).app;\n if (app && app.canvas && this.originalOnDrawForeground) {\n app.canvas.onDrawForeground = this.originalOnDrawForeground;\n }\n }\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,kBAAkB;AAAA;AAAA,EAS3B,cAAc;AARN,oDAAiG;AACjG,6CAAmC;AAG1B;AAAA,2CAAkB;AAClB;AAAA,2CAAkB;AAClB;AAAA,6CAAoB;AAGjC,SAAK,WAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AACvB,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,OAAO,CAAC,IAAI,QAAQ;AACrB,cAAQ,KAAK,0DAA0D;AACvE;AAAA,IACJ;AACA,UAAM,SAAS,IAAI;AAGnB,SAAK,2BAA2B,OAAO;AAGvC,WAAO,mBAAmB,CAAC,KAA+B,kBAAuB;AAE7E,UAAI,KAAK,0BAA0B;AAC/B,aAAK,yBAAyB,KAAK,QAAQ,KAAK,aAAa;AAAA,MACjE;AAGA,WAAK,cAAc,KAAK,OAAO,GAAG,KAAK;AAAA,IAC3C;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAA6B;AAC5C,QAAI,KAAK,sBAAsB,OAAQ;AACvC,SAAK,oBAAoB;AAEzB,UAAM,MAAO,OAAe;AAC5B,QAAI,OAAO,IAAI,QAAQ;AACnB,UAAI,OAAO,SAAS,MAAM,IAAI;AAAA,IAClC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,KAA+B,OAAqB;AACtE,QAAI,KAAK,sBAAsB,KAAM;AACrC,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,OAAO,IAAI,MAAM,YAAY,KAAK,iBAAiB;AACzD,QAAI,CAAC,KAAM;AAGX,QAAI,KAAA;AAMJ,UAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK;AAC7B,UAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK;AAC7B,UAAM,IAAI,KAAK,KAAK,CAAC,IAAK,KAAK,oBAAoB;AACnD,UAAM,IAAI,KAAK,KAAK,CAAC,IAAK,KAAK,oBAAoB;AAGnD,QAAI,YAAY,KAAK;AACrB,QAAI,cAAc,KAAK;AACvB,QAAI,cAAc,KAAK;AACvB,QAAI,aAAa,KAAK;AACtB,QAAI,gBAAgB;AACpB,QAAI,gBAAgB;AAGpB,QAAI,UAAA;AACJ,QAAI,OAAO,IAAI,cAAc,YAAY;AACrC,YAAM,SAAS;AACf,UAAI,UAAU,GAAG,GAAG,GAAG,GAAG,MAAM;AAAA,IACpC,OAAO;AACH,UAAI,KAAK,GAAG,GAAG,GAAG,CAAC;AAAA,IACvB;AACA,QAAI,OAAA;AAGJ,QAAI,QAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,UAAM,MAAO,OAAe;AAC5B,QAAI,OAAO,IAAI,UAAU,KAAK,0BAA0B;AACpD,UAAI,OAAO,mBAAmB,KAAK;AAAA,IACvC;AAAA,EACJ;AACJ;"} \ No newline at end of file +{"version":3,"file":"CanvasHighlighter.js","sources":["../../src/info-panel/CanvasHighlighter.ts"],"sourcesContent":["/**\n * CanvasHighlighter.ts\n * \n * Handles drawing a visual highlight on the main canvas around the currently inspected node.\n * Hooks into LiteGraph's canvas drawing cycle.\n */\n\nimport { ComfyApp, ComfyNode } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\n\nexport class CanvasHighlighter {\n private originalOnDrawForeground: ((ctx: CanvasRenderingContext2D, visible_nodes: any) => void) | null = null;\n private highlightedNodeId: number | null = null;\n\n // Configuration\n private readonly HIGHLIGHT_COLOR = '#007bff'; // Bootstrap blue\n private readonly HIGHLIGHT_WIDTH = 2; // px\n private readonly HIGHLIGHT_PADDING = 0; // px\n\n constructor() {\n this.hookCanvas();\n }\n\n /**\n * Hook into the main canvas onDrawForeground method.\n */\n private hookCanvas(): void {\n const app = (window as any).app;\n if (!app || !app.canvas) {\n console.warn('[MagnifyGlass] Canvas not found, cannot hook highlighter');\n return;\n }\n const canvas = app.canvas;\n\n // Save original method\n this.originalOnDrawForeground = canvas.onDrawForeground;\n\n // Override\n canvas.onDrawForeground = this.boundOnDrawForeground;\n }\n\n /**\n * Set the node ID to highlight.\n */\n /**\n * Set the node ID to highlight.\n */\n setHighlightedNode(nodeId: number | null): void {\n this.ensureHook(); // Ensure we are still hooked\n\n if (this.highlightedNodeId === nodeId) return;\n this.highlightedNodeId = nodeId;\n // Force redraw to update highlight immediately\n const app = (window as any).app;\n if (app && app.canvas) {\n app.canvas.setDirty(true, true);\n }\n }\n\n /**\n * Ensure the canvas hook is active.\n */\n private ensureHook(): void {\n const app = (window as any).app;\n if (!app || !app.canvas) return;\n\n // If our hook was overwritten (e.g. by another extension), re-hook\n if (app.canvas.onDrawForeground !== this.boundOnDrawForeground) {\n // console.log('[MagnifyGlass] Re-hooking CanvasHighlighter');\n this.originalOnDrawForeground = app.canvas.onDrawForeground;\n app.canvas.onDrawForeground = this.boundOnDrawForeground;\n }\n }\n\n // Bound method to preserve 'this' and allow equality check\n private boundOnDrawForeground = (ctx: CanvasRenderingContext2D, visible_nodes: any) => {\n // Call original first\n if (this.originalOnDrawForeground) {\n this.originalOnDrawForeground.call((window as any).app.canvas, ctx, visible_nodes);\n }\n\n // Draw our highlight\n const app = (window as any).app;\n if (app && app.canvas) {\n this.drawHighlight(ctx, app.canvas.ds.scale);\n }\n };\n\n /**\n * Draw the highlight rectangle around the target node.\n */\n private drawHighlight(ctx: CanvasRenderingContext2D, scale: number): void {\n if (this.highlightedNodeId === null) return;\n const app = (window as any).app;\n if (!app) return;\n const node = app.graph.getNodeById(this.highlightedNodeId);\n if (!node) return;\n\n // Save context\n ctx.save();\n\n // Reset transform to draw in graph coordinates\n // Note: ctx passed to onDrawForeground is already transformed by LiteGraph to graph coordinates\n // so we can draw using node.pos directly.\n\n const padding = 10;\n const x = node.pos[0] - padding;\n const y = node.pos[1] - padding;\n const w = node.size[0] + (padding * 2);\n const h = node.size[1] + (padding * 2);\n\n // Draw large blue bar/border\n ctx.lineWidth = 10; // Thicker border\n ctx.strokeStyle = '#007bff'; // Blue\n // ctx.shadowColor = '#007bff'; // Optional glow\n // ctx.shadowBlur = 0; \n ctx.lineJoin = 'round';\n ctx.lineCap = 'round';\n\n // Use roundRect if available (modern browsers), else rect\n ctx.beginPath();\n if (typeof ctx.roundRect === 'function') {\n const radius = 10;\n ctx.roundRect(x, y, w, h, radius);\n } else {\n ctx.rect(x, y, w, h);\n }\n ctx.stroke();\n\n // Restore context\n ctx.restore();\n }\n\n /**\n * Clean up hooks.\n */\n cleanup(): void {\n const app = (window as any).app;\n if (app && app.canvas && this.originalOnDrawForeground) {\n if (app.canvas.onDrawForeground === this.boundOnDrawForeground) {\n app.canvas.onDrawForeground = this.originalOnDrawForeground;\n }\n }\n }\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,kBAAkB;AAAA;AAAA,EAS3B,cAAc;AARN,oDAAiG;AACjG,6CAAmC;AAG1B;AAAA,2CAAkB;AAClB;AAAA,2CAAkB;AAClB;AAAA,6CAAoB;AA0D7B;AAAA,iDAAwB,CAAC,KAA+B,kBAAuB;AAEnF,UAAI,KAAK,0BAA0B;AAC/B,aAAK,yBAAyB,KAAM,OAAe,IAAI,QAAQ,KAAK,aAAa;AAAA,MACrF;AAGA,YAAM,MAAO,OAAe;AAC5B,UAAI,OAAO,IAAI,QAAQ;AACnB,aAAK,cAAc,KAAK,IAAI,OAAO,GAAG,KAAK;AAAA,MAC/C;AAAA,IACJ;AAlEI,SAAK,WAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AACvB,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,OAAO,CAAC,IAAI,QAAQ;AACrB,cAAQ,KAAK,0DAA0D;AACvE;AAAA,IACJ;AACA,UAAM,SAAS,IAAI;AAGnB,SAAK,2BAA2B,OAAO;AAGvC,WAAO,mBAAmB,KAAK;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB,QAA6B;AAC5C,SAAK,WAAA;AAEL,QAAI,KAAK,sBAAsB,OAAQ;AACvC,SAAK,oBAAoB;AAEzB,UAAM,MAAO,OAAe;AAC5B,QAAI,OAAO,IAAI,QAAQ;AACnB,UAAI,OAAO,SAAS,MAAM,IAAI;AAAA,IAClC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AACvB,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,OAAO,CAAC,IAAI,OAAQ;AAGzB,QAAI,IAAI,OAAO,qBAAqB,KAAK,uBAAuB;AAE5D,WAAK,2BAA2B,IAAI,OAAO;AAC3C,UAAI,OAAO,mBAAmB,KAAK;AAAA,IACvC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAmBQ,cAAc,KAA+B,OAAqB;AACtE,QAAI,KAAK,sBAAsB,KAAM;AACrC,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,OAAO,IAAI,MAAM,YAAY,KAAK,iBAAiB;AACzD,QAAI,CAAC,KAAM;AAGX,QAAI,KAAA;AAMJ,UAAM,UAAU;AAChB,UAAM,IAAI,KAAK,IAAI,CAAC,IAAI;AACxB,UAAM,IAAI,KAAK,IAAI,CAAC,IAAI;AACxB,UAAM,IAAI,KAAK,KAAK,CAAC,IAAK,UAAU;AACpC,UAAM,IAAI,KAAK,KAAK,CAAC,IAAK,UAAU;AAGpC,QAAI,YAAY;AAChB,QAAI,cAAc;AAGlB,QAAI,WAAW;AACf,QAAI,UAAU;AAGd,QAAI,UAAA;AACJ,QAAI,OAAO,IAAI,cAAc,YAAY;AACrC,YAAM,SAAS;AACf,UAAI,UAAU,GAAG,GAAG,GAAG,GAAG,MAAM;AAAA,IACpC,OAAO;AACH,UAAI,KAAK,GAAG,GAAG,GAAG,CAAC;AAAA,IACvB;AACA,QAAI,OAAA;AAGJ,QAAI,QAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,UAAM,MAAO,OAAe;AAC5B,QAAI,OAAO,IAAI,UAAU,KAAK,0BAA0B;AACpD,UAAI,IAAI,OAAO,qBAAqB,KAAK,uBAAuB;AAC5D,YAAI,OAAO,mBAAmB,KAAK;AAAA,MACvC;AAAA,IACJ;AAAA,EACJ;AACJ;"} \ No newline at end of file