feat: precompute selector mapping, shadow dom support
This commit is contained in:
@@ -147,7 +147,18 @@ export const BrowserWindow = () => {
|
|||||||
const { browserWidth, browserHeight } = useBrowserDimensionsStore();
|
const { browserWidth, browserHeight } = useBrowserDimensionsStore();
|
||||||
const [canvasRef, setCanvasReference] = useState<React.RefObject<HTMLCanvasElement> | undefined>(undefined);
|
const [canvasRef, setCanvasReference] = useState<React.RefObject<HTMLCanvasElement> | undefined>(undefined);
|
||||||
const [screenShot, setScreenShot] = useState<string>("");
|
const [screenShot, setScreenShot] = useState<string>("");
|
||||||
const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], groupElements?: Array<{ element: HTMLElement; rect: DOMRect } >} | null>(null);
|
const [highlighterData, setHighlighterData] = useState<{
|
||||||
|
rect: DOMRect;
|
||||||
|
selector: string;
|
||||||
|
elementInfo: ElementInfo | null;
|
||||||
|
isShadow?: boolean;
|
||||||
|
childSelectors?: string[];
|
||||||
|
groupElements?: Array<{ element: HTMLElement; rect: DOMRect }>;
|
||||||
|
similarElements?: {
|
||||||
|
elements: HTMLElement[];
|
||||||
|
rects: DOMRect[];
|
||||||
|
};
|
||||||
|
} | null>(null);
|
||||||
const [showAttributeModal, setShowAttributeModal] = useState(false);
|
const [showAttributeModal, setShowAttributeModal] = useState(false);
|
||||||
const [attributeOptions, setAttributeOptions] = useState<AttributeOption[]>([]);
|
const [attributeOptions, setAttributeOptions] = useState<AttributeOption[]>([]);
|
||||||
const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null);
|
const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null);
|
||||||
@@ -161,11 +172,20 @@ export const BrowserWindow = () => {
|
|||||||
const [paginationSelector, setPaginationSelector] = useState<string>('');
|
const [paginationSelector, setPaginationSelector] = useState<string>('');
|
||||||
|
|
||||||
const highlighterUpdateRef = useRef<number>(0);
|
const highlighterUpdateRef = useRef<number>(0);
|
||||||
|
const [isCachingChildSelectors, setIsCachingChildSelectors] = useState(false);
|
||||||
|
const [cachedListSelector, setCachedListSelector] = useState<string | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [pendingNotification, setPendingNotification] = useState<{
|
||||||
|
type: "error" | "warning" | "info" | "success";
|
||||||
|
message: string;
|
||||||
|
count?: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { notify, currentTextActionId, currentListActionId, updateDOMMode, isDOMMode, currentSnapshot } = useGlobalInfoStore();
|
const { notify, currentTextActionId, currentListActionId, updateDOMMode, isDOMMode, currentSnapshot } = useGlobalInfoStore();
|
||||||
const { getText, getList, paginationMode, paginationType, limitMode, captureStage } = useActionContext();
|
const { getText, getList, paginationMode, paginationType, limitMode, captureStage } = useActionContext();
|
||||||
const { addTextStep, addListStep, updateListStepData } = useBrowserSteps();
|
const { addTextStep, addListStep } = useBrowserSteps();
|
||||||
|
|
||||||
const [currentGroupInfo, setCurrentGroupInfo] = useState<{
|
const [currentGroupInfo, setCurrentGroupInfo] = useState<{
|
||||||
isGroupElement: boolean;
|
isGroupElement: boolean;
|
||||||
@@ -270,17 +290,6 @@ export const BrowserWindow = () => {
|
|||||||
[user?.id, socket, updateDOMMode]
|
[user?.id, socket, updateDOMMode]
|
||||||
);
|
);
|
||||||
|
|
||||||
const screenshotModeHandler = useCallback(
|
|
||||||
(data: any) => {
|
|
||||||
if (!data.userId || data.userId === user?.id) {
|
|
||||||
updateDOMMode(false);
|
|
||||||
socket?.emit("screenshot-mode-enabled");
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[user?.id, updateDOMMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const domModeErrorHandler = useCallback(
|
const domModeErrorHandler = useCallback(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
if (!data.userId || data.userId === user?.id) {
|
if (!data.userId || data.userId === user?.id) {
|
||||||
@@ -300,28 +309,68 @@ export const BrowserWindow = () => {
|
|||||||
}, [isDOMMode, getList, listSelector, paginationMode]);
|
}, [isDOMMode, getList, listSelector, paginationMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDOMMode && listSelector) {
|
if (isDOMMode && listSelector) {
|
||||||
socket?.emit("setGetList", { getList: true });
|
socket?.emit("setGetList", { getList: true });
|
||||||
socket?.emit("listSelector", { selector: listSelector });
|
socket?.emit("listSelector", { selector: listSelector });
|
||||||
|
|
||||||
clientSelectorGenerator.setListSelector(listSelector);
|
clientSelectorGenerator.setListSelector(listSelector);
|
||||||
|
|
||||||
setCachedChildSelectors([]);
|
if (currentSnapshot && cachedListSelector !== listSelector) {
|
||||||
|
setCachedChildSelectors([]);
|
||||||
|
setIsCachingChildSelectors(true);
|
||||||
|
setCachedListSelector(listSelector);
|
||||||
|
|
||||||
if (currentSnapshot) {
|
const iframeElement = document.querySelector(
|
||||||
const iframeElement = document.querySelector(
|
"#dom-browser-iframe"
|
||||||
"#dom-browser-iframe"
|
) as HTMLIFrameElement;
|
||||||
) as HTMLIFrameElement;
|
|
||||||
if (iframeElement?.contentDocument) {
|
if (iframeElement?.contentDocument) {
|
||||||
const childSelectors = clientSelectorGenerator.getChildSelectors(
|
setTimeout(() => {
|
||||||
iframeElement.contentDocument,
|
try {
|
||||||
listSelector
|
const childSelectors =
|
||||||
);
|
clientSelectorGenerator.getChildSelectors(
|
||||||
setCachedChildSelectors(childSelectors);
|
iframeElement.contentDocument as Document,
|
||||||
|
listSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
clientSelectorGenerator.precomputeChildSelectorMappings(
|
||||||
|
childSelectors,
|
||||||
|
iframeElement.contentDocument as Document
|
||||||
|
);
|
||||||
|
|
||||||
|
setCachedChildSelectors(childSelectors);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during child selector caching:", error);
|
||||||
|
} finally {
|
||||||
|
setIsCachingChildSelectors(false);
|
||||||
|
|
||||||
|
if (pendingNotification) {
|
||||||
|
notify(pendingNotification.type, pendingNotification.message);
|
||||||
|
setPendingNotification(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
setIsCachingChildSelectors(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [isDOMMode, listSelector, socket, getList, currentSnapshot]);
|
}
|
||||||
|
}, [
|
||||||
|
isDOMMode,
|
||||||
|
listSelector,
|
||||||
|
socket,
|
||||||
|
getList,
|
||||||
|
currentSnapshot,
|
||||||
|
cachedListSelector,
|
||||||
|
pendingNotification,
|
||||||
|
notify,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!listSelector) {
|
||||||
|
setCachedListSelector(null);
|
||||||
|
}
|
||||||
|
}, [listSelector]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
coordinateMapper.updateDimensions(dimensions.width, dimensions.height, viewportInfo.width, viewportInfo.height);
|
coordinateMapper.updateDimensions(dimensions.width, dimensions.height, viewportInfo.width, viewportInfo.height);
|
||||||
@@ -389,7 +438,6 @@ export const BrowserWindow = () => {
|
|||||||
socket.on("screencast", screencastHandler);
|
socket.on("screencast", screencastHandler);
|
||||||
socket.on("domcast", rrwebSnapshotHandler);
|
socket.on("domcast", rrwebSnapshotHandler);
|
||||||
socket.on("dom-mode-enabled", domModeHandler);
|
socket.on("dom-mode-enabled", domModeHandler);
|
||||||
// socket.on("screenshot-mode-enabled", screenshotModeHandler);
|
|
||||||
socket.on("dom-mode-error", domModeErrorHandler);
|
socket.on("dom-mode-error", domModeErrorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +451,6 @@ export const BrowserWindow = () => {
|
|||||||
socket.off("screencast", screencastHandler);
|
socket.off("screencast", screencastHandler);
|
||||||
socket.off("domcast", rrwebSnapshotHandler);
|
socket.off("domcast", rrwebSnapshotHandler);
|
||||||
socket.off("dom-mode-enabled", domModeHandler);
|
socket.off("dom-mode-enabled", domModeHandler);
|
||||||
// socket.off("screenshot-mode-enabled", screenshotModeHandler);
|
|
||||||
socket.off("dom-mode-error", domModeErrorHandler);
|
socket.off("dom-mode-error", domModeErrorHandler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -415,7 +462,6 @@ export const BrowserWindow = () => {
|
|||||||
screencastHandler,
|
screencastHandler,
|
||||||
rrwebSnapshotHandler,
|
rrwebSnapshotHandler,
|
||||||
domModeHandler,
|
domModeHandler,
|
||||||
// screenshotModeHandler,
|
|
||||||
domModeErrorHandler,
|
domModeErrorHandler,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -425,12 +471,17 @@ export const BrowserWindow = () => {
|
|||||||
selector: string;
|
selector: string;
|
||||||
elementInfo: ElementInfo | null;
|
elementInfo: ElementInfo | null;
|
||||||
childSelectors?: string[];
|
childSelectors?: string[];
|
||||||
|
isShadow?: boolean;
|
||||||
groupInfo?: {
|
groupInfo?: {
|
||||||
isGroupElement: boolean;
|
isGroupElement: boolean;
|
||||||
groupSize: number;
|
groupSize: number;
|
||||||
groupElements: HTMLElement[];
|
groupElements: HTMLElement[];
|
||||||
groupFingerprint: ElementFingerprint;
|
groupFingerprint: ElementFingerprint;
|
||||||
};
|
};
|
||||||
|
similarElements?: {
|
||||||
|
elements: HTMLElement[];
|
||||||
|
rects: DOMRect[];
|
||||||
|
};
|
||||||
isDOMMode?: boolean;
|
isDOMMode?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!getText && !getList) {
|
if (!getText && !getList) {
|
||||||
@@ -460,6 +511,22 @@ export const BrowserWindow = () => {
|
|||||||
const iframeRect = iframeElement.getBoundingClientRect();
|
const iframeRect = iframeElement.getBoundingClientRect();
|
||||||
const IFRAME_BODY_PADDING = 16;
|
const IFRAME_BODY_PADDING = 16;
|
||||||
|
|
||||||
|
let mappedSimilarElements;
|
||||||
|
if (data.similarElements) {
|
||||||
|
mappedSimilarElements = {
|
||||||
|
elements: data.similarElements.elements,
|
||||||
|
rects: data.similarElements.rects.map(
|
||||||
|
(rect) =>
|
||||||
|
new DOMRect(
|
||||||
|
rect.x + iframeRect.left - IFRAME_BODY_PADDING,
|
||||||
|
rect.y + iframeRect.top - IFRAME_BODY_PADDING,
|
||||||
|
rect.width,
|
||||||
|
rect.height
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (data.groupInfo) {
|
if (data.groupInfo) {
|
||||||
setCurrentGroupInfo(data.groupInfo);
|
setCurrentGroupInfo(data.groupInfo);
|
||||||
} else {
|
} else {
|
||||||
@@ -686,6 +753,7 @@ export const BrowserWindow = () => {
|
|||||||
(highlighterData: {
|
(highlighterData: {
|
||||||
rect: DOMRect;
|
rect: DOMRect;
|
||||||
selector: string;
|
selector: string;
|
||||||
|
isShadow?: boolean;
|
||||||
elementInfo: ElementInfo | null;
|
elementInfo: ElementInfo | null;
|
||||||
childSelectors?: string[];
|
childSelectors?: string[];
|
||||||
groupInfo?: {
|
groupInfo?: {
|
||||||
@@ -713,11 +781,17 @@ export const BrowserWindow = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
addListStep(
|
addListStep(
|
||||||
listSelector!,
|
listSelector!,
|
||||||
fields,
|
fields,
|
||||||
currentListId || 0,
|
currentListId || 0,
|
||||||
currentListActionId || `list-${crypto.randomUUID()}`,
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
||||||
{ type: paginationType, selector: highlighterData.selector }
|
{
|
||||||
|
type: paginationType,
|
||||||
|
selector: highlighterData.selector,
|
||||||
|
isShadow: highlighterData.isShadow
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
highlighterData.isShadow
|
||||||
);
|
);
|
||||||
socket?.emit("setPaginationMode", { pagination: false });
|
socket?.emit("setPaginationMode", { pagination: false });
|
||||||
}
|
}
|
||||||
@@ -776,7 +850,7 @@ export const BrowserWindow = () => {
|
|||||||
selectorObj: {
|
selectorObj: {
|
||||||
selector: currentSelector,
|
selector: currentSelector,
|
||||||
tag: highlighterData.elementInfo?.tagName,
|
tag: highlighterData.elementInfo?.tagName,
|
||||||
shadow: highlighterData.elementInfo?.isShadowRoot,
|
isShadow: highlighterData.elementInfo?.isShadowRoot,
|
||||||
attribute,
|
attribute,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -794,7 +868,9 @@ export const BrowserWindow = () => {
|
|||||||
updatedFields,
|
updatedFields,
|
||||||
currentListId,
|
currentListId,
|
||||||
currentListActionId || `list-${crypto.randomUUID()}`,
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
||||||
{ type: "", selector: paginationSelector }
|
{ type: "", selector: paginationSelector },
|
||||||
|
undefined,
|
||||||
|
highlighterData.isShadow
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -829,7 +905,7 @@ export const BrowserWindow = () => {
|
|||||||
{
|
{
|
||||||
selector: highlighterData.selector,
|
selector: highlighterData.selector,
|
||||||
tag: highlighterData.elementInfo?.tagName,
|
tag: highlighterData.elementInfo?.tagName,
|
||||||
shadow: highlighterData.elementInfo?.isShadowRoot,
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
||||||
attribute,
|
attribute,
|
||||||
},
|
},
|
||||||
currentTextActionId || `text-${crypto.randomUUID()}`
|
currentTextActionId || `text-${crypto.randomUUID()}`
|
||||||
@@ -908,7 +984,7 @@ export const BrowserWindow = () => {
|
|||||||
{
|
{
|
||||||
selector: highlighterData.selector,
|
selector: highlighterData.selector,
|
||||||
tag: highlighterData.elementInfo?.tagName,
|
tag: highlighterData.elementInfo?.tagName,
|
||||||
shadow: highlighterData.elementInfo?.isShadowRoot,
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
||||||
attribute,
|
attribute,
|
||||||
},
|
},
|
||||||
currentTextActionId || `text-${crypto.randomUUID()}`
|
currentTextActionId || `text-${crypto.randomUUID()}`
|
||||||
@@ -942,7 +1018,9 @@ export const BrowserWindow = () => {
|
|||||||
fields,
|
fields,
|
||||||
currentListId || 0,
|
currentListId || 0,
|
||||||
currentListActionId || `list-${crypto.randomUUID()}`,
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
||||||
{ type: paginationType, selector: highlighterData.selector }
|
{ type: paginationType, selector: highlighterData.selector, isShadow: highlighterData.isShadow },
|
||||||
|
undefined,
|
||||||
|
highlighterData.isShadow
|
||||||
);
|
);
|
||||||
socket?.emit("setPaginationMode", { pagination: false });
|
socket?.emit("setPaginationMode", { pagination: false });
|
||||||
}
|
}
|
||||||
@@ -1000,7 +1078,7 @@ export const BrowserWindow = () => {
|
|||||||
selectorObj: {
|
selectorObj: {
|
||||||
selector: currentSelector,
|
selector: currentSelector,
|
||||||
tag: highlighterData.elementInfo?.tagName,
|
tag: highlighterData.elementInfo?.tagName,
|
||||||
shadow: highlighterData.elementInfo?.isShadowRoot,
|
isShadow: highlighterData.elementInfo?.isShadowRoot,
|
||||||
attribute,
|
attribute,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1018,7 +1096,9 @@ export const BrowserWindow = () => {
|
|||||||
updatedFields,
|
updatedFields,
|
||||||
currentListId,
|
currentListId,
|
||||||
currentListActionId || `list-${crypto.randomUUID()}`,
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
||||||
{ type: "", selector: paginationSelector }
|
{ type: "", selector: paginationSelector, isShadow: highlighterData.isShadow },
|
||||||
|
undefined,
|
||||||
|
highlighterData.isShadow
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1052,7 +1132,7 @@ export const BrowserWindow = () => {
|
|||||||
addTextStep('', data, {
|
addTextStep('', data, {
|
||||||
selector: selectedElement.selector,
|
selector: selectedElement.selector,
|
||||||
tag: selectedElement.info?.tagName,
|
tag: selectedElement.info?.tagName,
|
||||||
shadow: selectedElement.info?.isShadowRoot,
|
isShadow: highlighterData?.isShadow || selectedElement.info?.isShadowRoot,
|
||||||
attribute: attribute
|
attribute: attribute
|
||||||
}, currentTextActionId || `text-${crypto.randomUUID()}`);
|
}, currentTextActionId || `text-${crypto.randomUUID()}`);
|
||||||
}
|
}
|
||||||
@@ -1065,7 +1145,7 @@ export const BrowserWindow = () => {
|
|||||||
selectorObj: {
|
selectorObj: {
|
||||||
selector: selectedElement.selector,
|
selector: selectedElement.selector,
|
||||||
tag: selectedElement.info?.tagName,
|
tag: selectedElement.info?.tagName,
|
||||||
shadow: selectedElement.info?.isShadowRoot,
|
isShadow: selectedElement.info?.isShadowRoot,
|
||||||
attribute: attribute
|
attribute: attribute
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1083,7 +1163,9 @@ export const BrowserWindow = () => {
|
|||||||
updatedFields,
|
updatedFields,
|
||||||
currentListId,
|
currentListId,
|
||||||
currentListActionId || `list-${crypto.randomUUID()}`,
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
||||||
{ type: '', selector: paginationSelector }
|
{ type: "", selector: paginationSelector, isShadow: highlighterData?.isShadow },
|
||||||
|
undefined,
|
||||||
|
highlighterData?.isShadow
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1110,260 +1192,370 @@ export const BrowserWindow = () => {
|
|||||||
}, [paginationMode, resetPaginationSelector]);
|
}, [paginationMode, resetPaginationSelector]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={handleClick} style={{ width: browserWidth }} id="browser-window">
|
<div
|
||||||
{
|
onClick={handleClick}
|
||||||
getText === true || getList === true ? (
|
style={{ width: browserWidth }}
|
||||||
<GenericModal
|
id="browser-window"
|
||||||
isOpen={showAttributeModal}
|
>
|
||||||
onClose={() => {
|
{/* Attribute selection modal */}
|
||||||
setShowAttributeModal(false);
|
{(getText === true || getList === true) && (
|
||||||
setSelectedElement(null);
|
<GenericModal
|
||||||
setAttributeOptions([]);
|
isOpen={showAttributeModal}
|
||||||
}}
|
onClose={() => {
|
||||||
canBeClosed={true}
|
setShowAttributeModal(false);
|
||||||
modalStyle={modalStyle}
|
setSelectedElement(null);
|
||||||
|
setAttributeOptions([]);
|
||||||
|
}}
|
||||||
|
canBeClosed={true}
|
||||||
|
modalStyle={modalStyle}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2>Select Attribute</h2>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
|
marginTop: "30px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{attributeOptions.map((option) => (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="medium"
|
||||||
|
key={option.value}
|
||||||
|
onClick={() => {
|
||||||
|
handleAttributeSelection(option.value);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
maxWidth: "80%",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: "#ff00c3 !important",
|
||||||
|
borderColor: "#ff00c3 !important",
|
||||||
|
backgroundColor: "whitesmoke !important",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
maxWidth: "100%",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
{option.label}
|
||||||
<h2>Select Attribute</h2>
|
</span>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', marginTop: '30px' }}>
|
</Button>
|
||||||
{attributeOptions.map((option) => (
|
))}
|
||||||
<Button
|
</div>
|
||||||
variant="outlined"
|
</div>
|
||||||
size="medium"
|
</GenericModal>
|
||||||
key={option.value}
|
)}
|
||||||
onClick={() => handleAttributeSelection(option.value)}
|
|
||||||
style={{
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
maxWidth: '80%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
padding: '5px 10px',
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
color: '#ff00c3 !important',
|
|
||||||
borderColor: '#ff00c3 !important',
|
|
||||||
backgroundColor: 'whitesmoke !important',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{
|
|
||||||
display: 'block',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
maxWidth: '100%'
|
|
||||||
}}>
|
|
||||||
{option.label}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</GenericModal>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
{datePickerInfo && (
|
{datePickerInfo && (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
coordinates={datePickerInfo.coordinates}
|
coordinates={datePickerInfo.coordinates}
|
||||||
selector={datePickerInfo.selector}
|
selector={datePickerInfo.selector}
|
||||||
onClose={() => setDatePickerInfo(null)}
|
onClose={() => setDatePickerInfo(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{dropdownInfo && (
|
{dropdownInfo && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
coordinates={dropdownInfo.coordinates}
|
coordinates={dropdownInfo.coordinates}
|
||||||
selector={dropdownInfo.selector}
|
selector={dropdownInfo.selector}
|
||||||
options={dropdownInfo.options}
|
options={dropdownInfo.options}
|
||||||
onClose={() => setDropdownInfo(null)}
|
onClose={() => setDropdownInfo(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{timePickerInfo && (
|
{timePickerInfo && (
|
||||||
<TimePicker
|
<TimePicker
|
||||||
coordinates={timePickerInfo.coordinates}
|
coordinates={timePickerInfo.coordinates}
|
||||||
selector={timePickerInfo.selector}
|
selector={timePickerInfo.selector}
|
||||||
onClose={() => setTimePickerInfo(null)}
|
onClose={() => setTimePickerInfo(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{dateTimeLocalInfo && (
|
{dateTimeLocalInfo && (
|
||||||
<DateTimeLocalPicker
|
<DateTimeLocalPicker
|
||||||
coordinates={dateTimeLocalInfo.coordinates}
|
coordinates={dateTimeLocalInfo.coordinates}
|
||||||
selector={dateTimeLocalInfo.selector}
|
selector={dateTimeLocalInfo.selector}
|
||||||
onClose={() => setDateTimeLocalInfo(null)}
|
onClose={() => setDateTimeLocalInfo(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ height: dimensions.height, overflow: "hidden" }}>
|
{/* Main content area */}
|
||||||
{(getText === true || getList === true) &&
|
<div style={{ height: dimensions.height, overflow: "hidden" }}>
|
||||||
!showAttributeModal &&
|
{/* Add CSS for the spinner animation */}
|
||||||
highlighterData?.rect != null && (
|
<style>{`
|
||||||
<>
|
@keyframes spin {
|
||||||
{!isDOMMode && canvasRef?.current && (
|
0% { transform: rotate(0deg); }
|
||||||
<Highlighter
|
100% { transform: rotate(360deg); }
|
||||||
unmodifiedRect={highlighterData?.rect}
|
}
|
||||||
displayedSelector={highlighterData?.selector}
|
`}</style>
|
||||||
width={dimensions.width}
|
|
||||||
height={dimensions.height}
|
{(getText === true || getList === true) &&
|
||||||
canvasRect={canvasRef.current.getBoundingClientRect()}
|
!showAttributeModal &&
|
||||||
/>
|
highlighterData?.rect != null && (
|
||||||
|
<>
|
||||||
|
{!isDOMMode && canvasRef?.current && (
|
||||||
|
<Highlighter
|
||||||
|
unmodifiedRect={highlighterData?.rect}
|
||||||
|
displayedSelector={highlighterData?.selector}
|
||||||
|
width={dimensions.width}
|
||||||
|
height={dimensions.height}
|
||||||
|
canvasRect={canvasRef.current.getBoundingClientRect()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isDOMMode && highlighterData && (
|
||||||
|
<>
|
||||||
|
{/* Individual element highlight (for non-group or hovered element) */}
|
||||||
|
{getText && !listSelector && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: Math.max(0, highlighterData.rect.x),
|
||||||
|
top: Math.max(0, highlighterData.rect.y),
|
||||||
|
width: Math.min(
|
||||||
|
highlighterData.rect.width,
|
||||||
|
dimensions.width
|
||||||
|
),
|
||||||
|
height: Math.min(
|
||||||
|
highlighterData.rect.height,
|
||||||
|
dimensions.height
|
||||||
|
),
|
||||||
|
background: "rgba(255, 0, 195, 0.15)",
|
||||||
|
border: "2px solid #ff00c3",
|
||||||
|
borderRadius: "3px",
|
||||||
|
pointerEvents: "none",
|
||||||
|
zIndex: 1000,
|
||||||
|
boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.8)",
|
||||||
|
transition: "all 0.1s ease-out",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isDOMMode && highlighterData && (
|
{/* Group elements highlighting with real-time coordinates */}
|
||||||
<>
|
{getList &&
|
||||||
{/* Individual element highlight (for non-group or hovered element) */}
|
!listSelector &&
|
||||||
{(!getList ||
|
currentGroupInfo?.isGroupElement &&
|
||||||
listSelector ||
|
highlighterData.groupElements &&
|
||||||
!currentGroupInfo?.isGroupElement) && (
|
highlighterData.groupElements.map(
|
||||||
<div
|
(groupElement, index) => (
|
||||||
style={{
|
<React.Fragment key={index}>
|
||||||
position: "absolute",
|
{/* Highlight box */}
|
||||||
left: Math.max(0, highlighterData.rect.x),
|
<div
|
||||||
top: Math.max(0, highlighterData.rect.y),
|
style={{
|
||||||
width: Math.min(
|
position: "absolute",
|
||||||
highlighterData.rect.width,
|
left: Math.max(0, groupElement.rect.x),
|
||||||
dimensions.width
|
top: Math.max(0, groupElement.rect.y),
|
||||||
),
|
width: Math.min(
|
||||||
height: Math.min(
|
groupElement.rect.width,
|
||||||
highlighterData.rect.height,
|
dimensions.width
|
||||||
dimensions.height
|
),
|
||||||
),
|
height: Math.min(
|
||||||
background: "rgba(255, 0, 195, 0.15)",
|
groupElement.rect.height,
|
||||||
border: "2px solid #ff00c3",
|
dimensions.height
|
||||||
borderRadius: "3px",
|
),
|
||||||
pointerEvents: "none",
|
background: "rgba(255, 0, 195, 0.15)",
|
||||||
zIndex: 1000,
|
border: "2px dashed #ff00c3",
|
||||||
boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.8)",
|
borderRadius: "3px",
|
||||||
transition: "all 0.1s ease-out",
|
pointerEvents: "none",
|
||||||
}}
|
zIndex: 1000,
|
||||||
/>
|
boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.8)",
|
||||||
)}
|
transition: "all 0.1s ease-out",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Group elements highlighting with real-time coordinates */}
|
<div
|
||||||
{getList &&
|
style={{
|
||||||
!listSelector &&
|
position: "absolute",
|
||||||
currentGroupInfo?.isGroupElement &&
|
left: Math.max(0, groupElement.rect.x),
|
||||||
highlighterData.groupElements &&
|
top: Math.max(0, groupElement.rect.y - 20),
|
||||||
highlighterData.groupElements.map((groupElement, index) => (
|
background: "#ff00c3",
|
||||||
<React.Fragment key={index}>
|
color: "white",
|
||||||
{/* Highlight box */}
|
padding: "2px 6px",
|
||||||
<div
|
fontSize: "10px",
|
||||||
style={{
|
fontWeight: "bold",
|
||||||
position: "absolute",
|
borderRadius: "2px",
|
||||||
left: Math.max(0, groupElement.rect.x),
|
pointerEvents: "none",
|
||||||
top: Math.max(0, groupElement.rect.y),
|
zIndex: 1001,
|
||||||
width: Math.min(
|
whiteSpace: "nowrap",
|
||||||
groupElement.rect.width,
|
}}
|
||||||
dimensions.width
|
>
|
||||||
),
|
List item {index + 1}
|
||||||
height: Math.min(
|
</div>
|
||||||
groupElement.rect.height,
|
</React.Fragment>
|
||||||
dimensions.height
|
)
|
||||||
),
|
)}
|
||||||
background: "rgba(255, 0, 195, 0.15)",
|
|
||||||
border: "2px dashed #ff00c3",
|
|
||||||
borderRadius: "3px",
|
|
||||||
pointerEvents: "none",
|
|
||||||
zIndex: 1000,
|
|
||||||
boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.8)",
|
|
||||||
transition: "all 0.1s ease-out",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
{getList &&
|
||||||
style={{
|
listSelector &&
|
||||||
position: "absolute",
|
!paginationMode &&
|
||||||
left: Math.max(0, groupElement.rect.x),
|
!limitMode &&
|
||||||
top: Math.max(0, groupElement.rect.y - 20),
|
highlighterData?.similarElements &&
|
||||||
background: "#ff00c3",
|
highlighterData.similarElements.rects.map(
|
||||||
color: "white",
|
(rect, index) => (
|
||||||
padding: "2px 6px",
|
<React.Fragment key={`item-${index}`}>
|
||||||
fontSize: "10px",
|
{/* Highlight box for similar element */}
|
||||||
fontWeight: "bold",
|
<div
|
||||||
borderRadius: "2px",
|
style={{
|
||||||
pointerEvents: "none",
|
position: "absolute",
|
||||||
zIndex: 1001,
|
left: Math.max(0, rect.x),
|
||||||
whiteSpace: "nowrap",
|
top: Math.max(0, rect.y),
|
||||||
}}
|
width: Math.min(rect.width, dimensions.width),
|
||||||
>
|
height: Math.min(
|
||||||
List item {index + 1}
|
rect.height,
|
||||||
</div>
|
dimensions.height
|
||||||
</React.Fragment>
|
),
|
||||||
))}
|
background: "rgba(255, 0, 195, 0.15)",
|
||||||
</>
|
border: "2px dashed #ff00c3",
|
||||||
)}
|
borderRadius: "3px",
|
||||||
</>
|
pointerEvents: "none",
|
||||||
|
zIndex: 1000,
|
||||||
|
boxShadow: "0 0 0 1px rgba(255, 255, 255, 0.8)",
|
||||||
|
transition: "all 0.1s ease-out",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Label for similar element */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: Math.max(0, rect.x),
|
||||||
|
top: Math.max(0, rect.y - 20),
|
||||||
|
background: "#ff00c3",
|
||||||
|
color: "white",
|
||||||
|
padding: "2px 6px",
|
||||||
|
fontSize: "10px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "2px",
|
||||||
|
pointerEvents: "none",
|
||||||
|
zIndex: 1001,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Item {index + 1}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{isDOMMode ? (
|
{isDOMMode ? (
|
||||||
currentSnapshot ? (
|
<div
|
||||||
<DOMBrowserRenderer
|
style={{ position: "relative", width: "100%", height: "100%" }}
|
||||||
width={dimensions.width}
|
>
|
||||||
height={dimensions.height}
|
{currentSnapshot ? (
|
||||||
snapshot={currentSnapshot}
|
<DOMBrowserRenderer
|
||||||
getList={getList}
|
width={dimensions.width}
|
||||||
getText={getText}
|
height={dimensions.height}
|
||||||
listSelector={listSelector}
|
snapshot={currentSnapshot}
|
||||||
cachedChildSelectors={cachedChildSelectors}
|
getList={getList}
|
||||||
paginationMode={paginationMode}
|
getText={getText}
|
||||||
paginationType={paginationType}
|
listSelector={listSelector}
|
||||||
limitMode={limitMode}
|
cachedChildSelectors={cachedChildSelectors}
|
||||||
onHighlight={(data: any) => {
|
paginationMode={paginationMode}
|
||||||
domHighlighterHandler(data);
|
paginationType={paginationType}
|
||||||
}}
|
limitMode={limitMode}
|
||||||
onElementSelect={handleDOMElementSelection}
|
onHighlight={(data) => {
|
||||||
onShowDatePicker={handleShowDatePicker}
|
domHighlighterHandler(data);
|
||||||
onShowDropdown={handleShowDropdown}
|
}}
|
||||||
onShowTimePicker={handleShowTimePicker}
|
isCachingChildSelectors={isCachingChildSelectors}
|
||||||
onShowDateTimePicker={handleShowDateTimePicker}
|
onElementSelect={handleDOMElementSelection}
|
||||||
/>
|
onShowDatePicker={handleShowDatePicker}
|
||||||
) : (
|
onShowDropdown={handleShowDropdown}
|
||||||
<div
|
onShowTimePicker={handleShowTimePicker}
|
||||||
style={{
|
onShowDateTimePicker={handleShowDateTimePicker}
|
||||||
width: dimensions.width,
|
/>
|
||||||
height: dimensions.height,
|
) : (
|
||||||
display: "flex",
|
<div
|
||||||
alignItems: "center",
|
style={{
|
||||||
justifyContent: "center",
|
width: dimensions.width,
|
||||||
background: "#f5f5f5",
|
height: dimensions.height,
|
||||||
borderRadius: "5px",
|
display: "flex",
|
||||||
flexDirection: "column",
|
alignItems: "center",
|
||||||
gap: "20px",
|
justifyContent: "center",
|
||||||
}}
|
background: "#f5f5f5",
|
||||||
>
|
borderRadius: "5px",
|
||||||
<div
|
flexDirection: "column",
|
||||||
style={{
|
gap: "20px",
|
||||||
width: "60px",
|
}}
|
||||||
height: "60px",
|
>
|
||||||
borderTop: "4px solid transparent",
|
<div
|
||||||
borderRadius: "50%",
|
style={{
|
||||||
animation: "spin 1s linear infinite",
|
width: "60px",
|
||||||
}}
|
height: "60px",
|
||||||
/>
|
borderTop: "4px solid transparent",
|
||||||
<div
|
borderRadius: "50%",
|
||||||
style={{
|
animation: "spin 1s linear infinite",
|
||||||
fontSize: "18px",
|
}}
|
||||||
color: "#ff00c3",
|
/>
|
||||||
fontWeight: "bold",
|
<div
|
||||||
}}
|
style={{
|
||||||
>
|
fontSize: "18px",
|
||||||
Loading website...
|
color: "#ff00c3",
|
||||||
</div>
|
fontWeight: "bold",
|
||||||
<style>{`
|
}}
|
||||||
@keyframes spin {
|
>
|
||||||
0% { transform: rotate(0deg); }
|
Loading website...
|
||||||
100% { transform: rotate(360deg); }
|
</div>
|
||||||
}
|
<style>{`
|
||||||
`}</style>
|
@keyframes spin {
|
||||||
</div>
|
0% { transform: rotate(0deg); }
|
||||||
)
|
100% { transform: rotate(360deg); }
|
||||||
) : (
|
}
|
||||||
/* Screenshot mode canvas */
|
`}</style>
|
||||||
<Canvas
|
</div>
|
||||||
onCreateRef={setCanvasReference}
|
)}
|
||||||
width={dimensions.width}
|
|
||||||
height={dimensions.height}
|
{/* Loading overlay positioned specifically over DOM content */}
|
||||||
/>
|
{isCachingChildSelectors && (
|
||||||
)}
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
background: "rgba(255, 255, 255, 0.8)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
zIndex: 9999,
|
||||||
|
pointerEvents: "none",
|
||||||
|
borderRadius: "0px 0px 5px 5px", // Match the DOM renderer border radius
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "40px",
|
||||||
|
height: "40px",
|
||||||
|
border: "4px solid #f3f3f3",
|
||||||
|
borderTop: "4px solid #ff00c3",
|
||||||
|
borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
/* Screenshot mode canvas */
|
||||||
|
<Canvas
|
||||||
|
onCreateRef={setCanvasReference}
|
||||||
|
width={dimensions.width}
|
||||||
|
height={dimensions.height}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user