UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

200 lines 8.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ToolTipper = void 0; const Logger_1 = require("../utils/Logger"); const uuid_1 = require("uuid"); class ToolTipper { constructor(defaultDuration) { this.defaultDuration = defaultDuration; } /** * Show the given tooltip text in the top-left corner of the page for the given duration. * If duration <= 0 or toolTip is empty, does nothing. Blocks execution for the duration. */ async blipToolTip(page, toolTip, duration = this.defaultDuration) { if (!toolTip.trim() || !duration || duration <= 0) { return; } try { const toolTipId = await this.displayToolTip(page, toolTip); await page.waitForTimeout(duration); await ToolTipper.removeElementById(page, toolTipId); } catch (e) { Logger_1.appLogger.error('Failed to display tooltip at top-left corner', e); } } /** * Show the given tooltip text visually adjacent to the given element for the given duration. * Blocks execution for the duration. */ async blipToolTipAtElement(page, element, toolTip, duration = this.defaultDuration) { if (!toolTip.trim() || !duration || duration <= 0) { return; } try { const toolTipId = await this.displayToolTipForElement(element, toolTip); await page.waitForTimeout(duration); await ToolTipper.useElementToRemoveElementById(element, toolTipId); } catch (e) { Logger_1.appLogger.error('Failed to display tooltip at element', e); } } /** * Creates a tooltip in the top-left corner, inside a shadow root to avoid CSS collisions. * Returns the tooltip's unique ID. */ async displayToolTip(page, toolTip) { const toolTipId = `tool-tip-${(0, uuid_1.v4)()}`; await page.evaluate(([toolTipId, toolTip]) => { // 1) Find or create the container in the main document let container = document.getElementById('tool-tip-shadow-container'); if (!container) { container = document.createElement('div'); container.id = 'tool-tip-shadow-container'; // Absolutely position so child tooltips can use absolute coords container.style.position = 'absolute'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.pointerEvents = 'none'; // let clicks pass through document.body.appendChild(container); const shadowRoot = container.attachShadow({ mode: 'open' }); // Insert style to reset everything in the shadow root & define .tooltip const styleEl = document.createElement('style'); styleEl.textContent = ` :host { all: initial; /* Reset inherited styles */ } .tooltip { z-index: 2147483647; /* Very high to float above overlays */ background-color: black; color: white; padding: 8px; border-radius: 5px; font-size: 12px; font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; display: block; /* visible once inserted */ max-width: 300px; white-space: pre-wrap; box-shadow: 2px 2px 10px rgba(0,0,0,0.2); position: absolute; } `; shadowRoot.appendChild(styleEl); } // 2) Insert the tooltip into the shadow root const shadow = container.shadowRoot; const tooltipElement = document.createElement('div'); tooltipElement.id = toolTipId; tooltipElement.className = 'tooltip'; tooltipElement.textContent = toolTip; // Position top-left tooltipElement.style.left = '10px'; tooltipElement.style.top = '10px'; shadow.appendChild(tooltipElement); }, [toolTipId, toolTip]); return toolTipId; } /** * Creates a tooltip adjacent to the element, inside a shadow root to avoid CSS collisions. * Returns the tooltip's unique ID. */ async displayToolTipForElement(element, toolTip) { const toolTipId = `tool-tip-${(0, uuid_1.v4)()}`; await element.evaluate((targetElement, [toolTipId, toolTip]) => { // 1) Find or create the container let container = document.getElementById('tool-tip-shadow-container'); if (!container) { container = document.createElement('div'); container.id = 'tool-tip-shadow-container'; container.style.position = 'absolute'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.pointerEvents = 'none'; document.body.appendChild(container); const shadowRoot = container.attachShadow({ mode: 'open' }); // Insert style const styleEl = document.createElement('style'); styleEl.textContent = ` :host { all: initial; } .tooltip { z-index: 2147483647; background-color: black; color: white; padding: 8px; border-radius: 5px; font-size: 12px; font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif; display: block; max-width: 300px; white-space: pre-wrap; box-shadow: 2px 2px 10px rgba(0,0,0,0.2); position: absolute; } `; shadowRoot.appendChild(styleEl); } // 2) Insert tooltip const shadow = container.shadowRoot; const tooltipElement = document.createElement('div'); tooltipElement.id = toolTipId; tooltipElement.className = 'tooltip'; tooltipElement.textContent = toolTip; // Temporarily place off-screen to measure size tooltipElement.style.left = '-9999px'; tooltipElement.style.top = '-9999px'; shadow.appendChild(tooltipElement); // 3) Compute final position const rect = targetElement.getBoundingClientRect(); let x = rect.left + window.scrollX; let y = rect.bottom + window.scrollY + 10; tooltipElement.style.left = `${x}px`; tooltipElement.style.top = `${y}px`; // Adjust if it goes off-screen to the right const tipRect = tooltipElement.getBoundingClientRect(); const overflowRight = tipRect.right - window.innerWidth; if (overflowRight > 0) { tooltipElement.style.left = `${x - overflowRight - 10}px`; } // Adjust if it goes off-screen to the left if (tipRect.left < 0) { tooltipElement.style.left = `${window.scrollX + 10}px`; } }, [toolTipId, toolTip]); return toolTipId; } /** * Removes the tooltip with the given ID from the shadow root (top-level). */ static async removeElementById(page, elementId) { await page.evaluate((elementId) => { const container = document.getElementById('tool-tip-shadow-container'); if (!container || !container.shadowRoot) return; const el = container.shadowRoot.getElementById(elementId); if (el) el.remove(); }, elementId); } /** * Same as removeElementById but uses the element's frame context * in case the tooltip was created via that element's evaluate call. */ static async useElementToRemoveElementById(element, elementId) { await element.evaluate((_, elementId) => { const container = document.getElementById('tool-tip-shadow-container'); if (!container || !container.shadowRoot) return; const el = container.shadowRoot.getElementById(elementId); if (el) el.remove(); }, elementId); } } exports.ToolTipper = ToolTipper; //# sourceMappingURL=ToolTipper.js.map