donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
200 lines • 8.67 kB
JavaScript
;
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