2025-06-12 14:07:18 +05:30
|
|
|
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
2024-06-14 21:47:42 +05:30
|
|
|
import { useSocketStore } from '../../context/socket';
|
2024-08-21 21:57:41 +05:30
|
|
|
import { Button } from '@mui/material';
|
2025-06-27 04:31:20 +05:30
|
|
|
import Canvas from "../recorder/Canvas";
|
2025-01-09 20:07:42 +05:30
|
|
|
import { Highlighter } from "../recorder/Highlighter";
|
2025-01-09 19:49:20 +05:30
|
|
|
import { GenericModal } from '../ui/GenericModal';
|
2024-07-24 20:54:13 +05:30
|
|
|
import { useActionContext } from '../../context/browserActions';
|
2024-08-09 06:23:10 +05:30
|
|
|
import { useBrowserSteps, TextStep } from '../../context/browserSteps';
|
2024-10-28 06:38:24 +05:30
|
|
|
import { useGlobalInfoStore } from '../../context/globalInfo';
|
2024-12-21 16:33:21 +05:30
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-03-08 17:10:30 +05:30
|
|
|
import { AuthContext } from '../../context/auth';
|
2025-03-14 12:35:35 +05:30
|
|
|
import { coordinateMapper } from '../../helpers/coordinateMapper';
|
2025-03-18 04:01:55 +05:30
|
|
|
import { useBrowserDimensionsStore } from '../../context/browserDimensions';
|
2025-07-06 16:18:13 +05:30
|
|
|
import { clientSelectorGenerator, ElementFingerprint } from "../../helpers/clientSelectorGenerator";
|
2025-06-23 14:30:06 +05:30
|
|
|
import DatePicker from "../pickers/DatePicker";
|
|
|
|
|
import Dropdown from "../pickers/Dropdown";
|
|
|
|
|
import TimePicker from "../pickers/TimePicker";
|
|
|
|
|
import DateTimeLocalPicker from "../pickers/DateTimeLocalPicker";
|
|
|
|
|
import { DOMBrowserRenderer } from '../recorder/DOMBrowserRenderer';
|
2024-07-23 22:28:32 +05:30
|
|
|
|
2024-07-27 02:06:05 +05:30
|
|
|
interface ElementInfo {
|
|
|
|
|
tagName: string;
|
|
|
|
|
hasOnlyText?: boolean;
|
2025-01-04 14:47:51 +05:30
|
|
|
isIframeContent?: boolean;
|
2024-12-30 02:39:27 +05:30
|
|
|
isShadowRoot?: boolean;
|
2024-07-27 02:06:05 +05:30
|
|
|
innerText?: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
imageUrl?: string;
|
2024-10-04 22:33:40 +05:30
|
|
|
attributes?: Record<string, string>;
|
|
|
|
|
innerHTML?: string;
|
|
|
|
|
outerHTML?: string;
|
2025-06-23 14:30:06 +05:30
|
|
|
isDOMMode?: boolean;
|
2024-07-27 02:11:59 +05:30
|
|
|
}
|
|
|
|
|
|
2024-08-04 02:54:06 +05:30
|
|
|
interface AttributeOption {
|
|
|
|
|
label: string;
|
|
|
|
|
value: string;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 17:10:30 +05:30
|
|
|
interface ScreencastData {
|
|
|
|
|
image: string;
|
|
|
|
|
userId: string;
|
2025-03-14 12:35:35 +05:30
|
|
|
viewport?: ViewportInfo | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ViewportInfo {
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
2025-03-08 17:10:30 +05:30
|
|
|
}
|
|
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
interface RRWebSnapshot {
|
|
|
|
|
type: number;
|
|
|
|
|
childNodes?: RRWebSnapshot[];
|
|
|
|
|
tagName?: string;
|
|
|
|
|
attributes?: Record<string, string>;
|
|
|
|
|
textContent: string;
|
|
|
|
|
id: number;
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ProcessedSnapshot {
|
|
|
|
|
snapshot: RRWebSnapshot;
|
|
|
|
|
resources: {
|
|
|
|
|
stylesheets: Array<{
|
|
|
|
|
href: string;
|
|
|
|
|
content: string;
|
|
|
|
|
media?: string;
|
|
|
|
|
}>;
|
|
|
|
|
images: Array<{
|
|
|
|
|
src: string;
|
|
|
|
|
dataUrl: string;
|
|
|
|
|
alt?: string;
|
|
|
|
|
}>;
|
|
|
|
|
fonts: Array<{
|
|
|
|
|
url: string;
|
|
|
|
|
dataUrl: string;
|
|
|
|
|
format?: string;
|
|
|
|
|
}>;
|
|
|
|
|
scripts: Array<{
|
|
|
|
|
src: string;
|
|
|
|
|
content: string;
|
|
|
|
|
type?: string;
|
|
|
|
|
}>;
|
|
|
|
|
media: Array<{
|
|
|
|
|
src: string;
|
|
|
|
|
dataUrl: string;
|
|
|
|
|
type: string;
|
|
|
|
|
}>;
|
|
|
|
|
};
|
|
|
|
|
baseUrl: string;
|
|
|
|
|
viewport: { width: number; height: number };
|
|
|
|
|
timestamp: number;
|
|
|
|
|
processingStats: {
|
|
|
|
|
totalReplacements: number;
|
|
|
|
|
discoveredResources: {
|
|
|
|
|
images: number;
|
|
|
|
|
stylesheets: number;
|
|
|
|
|
scripts: number;
|
|
|
|
|
fonts: number;
|
|
|
|
|
media: number;
|
|
|
|
|
};
|
|
|
|
|
cachedResources: {
|
|
|
|
|
stylesheets: number;
|
|
|
|
|
images: number;
|
|
|
|
|
fonts: number;
|
|
|
|
|
scripts: number;
|
|
|
|
|
media: number;
|
|
|
|
|
};
|
|
|
|
|
totalCacheSize: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface RRWebDOMCastData {
|
|
|
|
|
snapshotData: ProcessedSnapshot;
|
|
|
|
|
userId: string;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
}
|
2025-03-08 17:10:30 +05:30
|
|
|
|
2024-08-21 22:10:24 +05:30
|
|
|
const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): AttributeOption[] => {
|
|
|
|
|
if (!elementInfo) return [];
|
2024-08-04 02:54:06 +05:30
|
|
|
switch (tagName.toLowerCase()) {
|
|
|
|
|
case 'a':
|
2024-08-21 23:34:30 +05:30
|
|
|
const anchorOptions: AttributeOption[] = [];
|
|
|
|
|
if (elementInfo.innerText) {
|
|
|
|
|
anchorOptions.push({ label: `Text: ${elementInfo.innerText}`, value: 'innerText' });
|
|
|
|
|
}
|
|
|
|
|
if (elementInfo.url) {
|
|
|
|
|
anchorOptions.push({ label: `URL: ${elementInfo.url}`, value: 'href' });
|
|
|
|
|
}
|
|
|
|
|
return anchorOptions;
|
2024-08-04 02:54:06 +05:30
|
|
|
case 'img':
|
2024-08-21 23:34:30 +05:30
|
|
|
const imgOptions: AttributeOption[] = [];
|
2024-08-21 23:11:51 +05:30
|
|
|
if (elementInfo.innerText) {
|
2024-08-21 23:34:30 +05:30
|
|
|
imgOptions.push({ label: `Alt Text: ${elementInfo.innerText}`, value: 'alt' });
|
2024-08-21 23:11:51 +05:30
|
|
|
}
|
|
|
|
|
if (elementInfo.imageUrl) {
|
2024-08-21 23:34:30 +05:30
|
|
|
imgOptions.push({ label: `Image URL: ${elementInfo.imageUrl}`, value: 'src' });
|
2024-08-21 23:11:51 +05:30
|
|
|
}
|
2024-08-21 23:34:30 +05:30
|
|
|
return imgOptions;
|
2024-08-04 02:54:06 +05:30
|
|
|
default:
|
2024-08-21 22:51:10 +05:30
|
|
|
return [{ label: `Text: ${elementInfo.innerText}`, value: 'innerText' }];
|
2024-08-04 02:54:06 +05:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-14 21:47:42 +05:30
|
|
|
export const BrowserWindow = () => {
|
2024-12-21 16:33:21 +05:30
|
|
|
const { t } = useTranslation();
|
2025-03-18 04:01:55 +05:30
|
|
|
const { browserWidth, browserHeight } = useBrowserDimensionsStore();
|
2024-06-14 21:47:42 +05:30
|
|
|
const [canvasRef, setCanvasReference] = useState<React.RefObject<HTMLCanvasElement> | undefined>(undefined);
|
|
|
|
|
const [screenShot, setScreenShot] = useState<string>("");
|
2025-07-16 00:27:47 +05:30
|
|
|
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);
|
2024-08-04 03:18:41 +05:30
|
|
|
const [showAttributeModal, setShowAttributeModal] = useState(false);
|
|
|
|
|
const [attributeOptions, setAttributeOptions] = useState<AttributeOption[]>([]);
|
2024-08-04 03:23:02 +05:30
|
|
|
const [selectedElement, setSelectedElement] = useState<{ selector: string, info: ElementInfo | null } | null>(null);
|
2024-09-04 05:32:39 +05:30
|
|
|
const [currentListId, setCurrentListId] = useState<number | null>(null);
|
2025-03-18 04:01:55 +05:30
|
|
|
const [viewportInfo, setViewportInfo] = useState<ViewportInfo>({ width: browserWidth, height: browserHeight });
|
2025-06-23 14:30:06 +05:30
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2025-07-06 16:18:13 +05:30
|
|
|
const [cachedChildSelectors, setCachedChildSelectors] = useState<string[]>([]);
|
2024-09-04 05:32:39 +05:30
|
|
|
|
2024-08-09 06:23:10 +05:30
|
|
|
const [listSelector, setListSelector] = useState<string | null>(null);
|
|
|
|
|
const [fields, setFields] = useState<Record<string, TextStep>>({});
|
2024-09-06 21:04:24 +05:30
|
|
|
const [paginationSelector, setPaginationSelector] = useState<string>('');
|
2024-09-06 22:55:54 +05:30
|
|
|
|
2025-06-12 14:07:18 +05:30
|
|
|
const highlighterUpdateRef = useRef<number>(0);
|
2025-07-16 00:27:47 +05:30
|
|
|
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);
|
2025-06-12 14:07:18 +05:30
|
|
|
|
2024-06-14 21:47:42 +05:30
|
|
|
const { socket } = useSocketStore();
|
2025-07-06 21:43:28 +05:30
|
|
|
const { notify, currentTextActionId, currentListActionId, updateDOMMode, isDOMMode, currentSnapshot } = useGlobalInfoStore();
|
2024-12-29 17:52:58 +05:30
|
|
|
const { getText, getList, paginationMode, paginationType, limitMode, captureStage } = useActionContext();
|
2025-07-16 00:27:47 +05:30
|
|
|
const { addTextStep, addListStep } = useBrowserSteps();
|
2025-07-06 16:18:13 +05:30
|
|
|
|
|
|
|
|
const [currentGroupInfo, setCurrentGroupInfo] = useState<{
|
|
|
|
|
isGroupElement: boolean;
|
|
|
|
|
groupSize: number;
|
|
|
|
|
groupElements: HTMLElement[];
|
|
|
|
|
} | null>(null);
|
2025-03-11 21:31:57 +05:30
|
|
|
|
2025-03-08 17:10:30 +05:30
|
|
|
const { state } = useContext(AuthContext);
|
2025-03-11 21:31:57 +05:30
|
|
|
const { user } = state;
|
2024-06-14 21:47:42 +05:30
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
const [datePickerInfo, setDatePickerInfo] = useState<{
|
|
|
|
|
coordinates: { x: number; y: number };
|
|
|
|
|
selector: string;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
|
|
|
|
|
const [dropdownInfo, setDropdownInfo] = useState<{
|
|
|
|
|
coordinates: { x: number; y: number };
|
|
|
|
|
selector: string;
|
|
|
|
|
options: Array<{
|
|
|
|
|
value: string;
|
|
|
|
|
text: string;
|
|
|
|
|
disabled: boolean;
|
|
|
|
|
selected: boolean;
|
|
|
|
|
}>;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
|
|
|
|
|
const [timePickerInfo, setTimePickerInfo] = useState<{
|
|
|
|
|
coordinates: { x: number; y: number };
|
|
|
|
|
selector: string;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
|
|
|
|
|
const [dateTimeLocalInfo, setDateTimeLocalInfo] = useState<{
|
|
|
|
|
coordinates: { x: number; y: number };
|
|
|
|
|
selector: string;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
|
2025-03-18 04:01:55 +05:30
|
|
|
const dimensions = {
|
|
|
|
|
width: browserWidth,
|
|
|
|
|
height: browserHeight
|
|
|
|
|
};
|
2025-03-15 14:14:43 +05:30
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
const handleShowDatePicker = useCallback(
|
|
|
|
|
(info: { coordinates: { x: number; y: number }; selector: string }) => {
|
|
|
|
|
setDatePickerInfo(info);
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleShowDropdown = useCallback(
|
|
|
|
|
(info: {
|
|
|
|
|
coordinates: { x: number; y: number };
|
|
|
|
|
selector: string;
|
|
|
|
|
options: Array<{
|
|
|
|
|
value: string;
|
|
|
|
|
text: string;
|
|
|
|
|
disabled: boolean;
|
|
|
|
|
selected: boolean;
|
|
|
|
|
}>;
|
|
|
|
|
}) => {
|
|
|
|
|
setDropdownInfo(info);
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleShowTimePicker = useCallback(
|
|
|
|
|
(info: { coordinates: { x: number; y: number }; selector: string }) => {
|
|
|
|
|
setTimePickerInfo(info);
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleShowDateTimePicker = useCallback(
|
|
|
|
|
(info: { coordinates: { x: number; y: number }; selector: string }) => {
|
|
|
|
|
setDateTimeLocalInfo(info);
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const rrwebSnapshotHandler = useCallback(
|
|
|
|
|
(data: RRWebDOMCastData) => {
|
|
|
|
|
if (!data.userId || data.userId === user?.id) {
|
|
|
|
|
if (data.snapshotData && data.snapshotData.snapshot) {
|
2025-07-06 21:43:28 +05:30
|
|
|
updateDOMMode(true, data.snapshotData);
|
2025-06-23 14:30:06 +05:30
|
|
|
socket?.emit("dom-mode-enabled");
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
} else {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-07-06 21:43:28 +05:30
|
|
|
[user?.id, socket, updateDOMMode]
|
2025-06-23 14:30:06 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const domModeHandler = useCallback(
|
|
|
|
|
(data: any) => {
|
|
|
|
|
if (!data.userId || data.userId === user?.id) {
|
2025-07-06 21:43:28 +05:30
|
|
|
updateDOMMode(true);
|
2025-06-23 14:30:06 +05:30
|
|
|
socket?.emit("dom-mode-enabled");
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-07-06 21:43:28 +05:30
|
|
|
[user?.id, socket, updateDOMMode]
|
2025-06-23 14:30:06 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const domModeErrorHandler = useCallback(
|
|
|
|
|
(data: any) => {
|
|
|
|
|
if (!data.userId || data.userId === user?.id) {
|
2025-07-06 21:43:28 +05:30
|
|
|
updateDOMMode(false);
|
2025-06-23 14:30:06 +05:30
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-07-06 21:43:28 +05:30
|
|
|
[user?.id, updateDOMMode]
|
2025-06-23 14:30:06 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isDOMMode) {
|
|
|
|
|
clientSelectorGenerator.setGetList(getList);
|
|
|
|
|
clientSelectorGenerator.setListSelector(listSelector || "");
|
|
|
|
|
clientSelectorGenerator.setPaginationMode(paginationMode);
|
|
|
|
|
}
|
|
|
|
|
}, [isDOMMode, getList, listSelector, paginationMode]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-07-16 00:27:47 +05:30
|
|
|
if (isDOMMode && listSelector) {
|
|
|
|
|
socket?.emit("setGetList", { getList: true });
|
|
|
|
|
socket?.emit("listSelector", { selector: listSelector });
|
|
|
|
|
|
|
|
|
|
clientSelectorGenerator.setListSelector(listSelector);
|
|
|
|
|
|
|
|
|
|
if (currentSnapshot && cachedListSelector !== listSelector) {
|
|
|
|
|
setCachedChildSelectors([]);
|
|
|
|
|
setIsCachingChildSelectors(true);
|
|
|
|
|
setCachedListSelector(listSelector);
|
|
|
|
|
|
|
|
|
|
const iframeElement = document.querySelector(
|
|
|
|
|
"#dom-browser-iframe"
|
|
|
|
|
) as HTMLIFrameElement;
|
|
|
|
|
|
|
|
|
|
if (iframeElement?.contentDocument) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
try {
|
|
|
|
|
const childSelectors =
|
|
|
|
|
clientSelectorGenerator.getChildSelectors(
|
|
|
|
|
iframeElement.contentDocument as Document,
|
|
|
|
|
listSelector
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
clientSelectorGenerator.precomputeChildSelectorMappings(
|
|
|
|
|
childSelectors,
|
|
|
|
|
iframeElement.contentDocument as Document
|
|
|
|
|
);
|
2025-07-06 16:18:13 +05:30
|
|
|
|
2025-07-16 00:27:47 +05:30
|
|
|
setCachedChildSelectors(childSelectors);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error during child selector caching:", error);
|
|
|
|
|
} finally {
|
|
|
|
|
setIsCachingChildSelectors(false);
|
2025-07-06 16:18:13 +05:30
|
|
|
|
2025-07-16 00:27:47 +05:30
|
|
|
if (pendingNotification) {
|
|
|
|
|
notify(pendingNotification.type, pendingNotification.message);
|
|
|
|
|
setPendingNotification(null);
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
2025-07-16 00:27:47 +05:30
|
|
|
}
|
|
|
|
|
}, 100);
|
|
|
|
|
} else {
|
|
|
|
|
setIsCachingChildSelectors(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [
|
|
|
|
|
isDOMMode,
|
|
|
|
|
listSelector,
|
|
|
|
|
socket,
|
|
|
|
|
getList,
|
|
|
|
|
currentSnapshot,
|
|
|
|
|
cachedListSelector,
|
|
|
|
|
pendingNotification,
|
|
|
|
|
notify,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!listSelector) {
|
|
|
|
|
setCachedListSelector(null);
|
2025-06-23 14:30:06 +05:30
|
|
|
}
|
2025-07-16 00:27:47 +05:30
|
|
|
}, [listSelector]);
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2025-03-14 12:35:35 +05:30
|
|
|
useEffect(() => {
|
2025-03-15 14:14:43 +05:30
|
|
|
coordinateMapper.updateDimensions(dimensions.width, dimensions.height, viewportInfo.width, viewportInfo.height);
|
2025-03-18 04:01:55 +05:30
|
|
|
}, [viewportInfo, dimensions.width, dimensions.height]);
|
2025-03-14 12:35:35 +05:30
|
|
|
|
2025-03-11 12:58:36 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (listSelector) {
|
2025-06-12 14:07:18 +05:30
|
|
|
sessionStorage.setItem('recordingListSelector', listSelector);
|
2025-03-11 12:58:36 +05:30
|
|
|
}
|
|
|
|
|
}, [listSelector]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-06-12 14:07:18 +05:30
|
|
|
const storedListSelector = sessionStorage.getItem('recordingListSelector');
|
2025-03-11 12:58:36 +05:30
|
|
|
|
|
|
|
|
// Only restore state if it exists in sessionStorage
|
|
|
|
|
if (storedListSelector && !listSelector) {
|
|
|
|
|
setListSelector(storedListSelector);
|
|
|
|
|
}
|
2025-03-11 21:31:57 +05:30
|
|
|
}, []);
|
2025-03-11 12:58:36 +05:30
|
|
|
|
2024-07-23 21:58:28 +05:30
|
|
|
const onMouseMove = (e: MouseEvent) => {
|
|
|
|
|
if (canvasRef && canvasRef.current && highlighterData) {
|
|
|
|
|
const canvasRect = canvasRef.current.getBoundingClientRect();
|
2024-07-23 22:28:32 +05:30
|
|
|
// mousemove outside the browser window
|
2024-07-23 21:58:28 +05:30
|
|
|
if (
|
|
|
|
|
e.pageX < canvasRect.left
|
|
|
|
|
|| e.pageX > canvasRect.right
|
|
|
|
|
|| e.pageY < canvasRect.top
|
|
|
|
|
|| e.pageY > canvasRect.bottom
|
|
|
|
|
) {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-04 05:32:39 +05:30
|
|
|
const resetListState = useCallback(() => {
|
|
|
|
|
setListSelector(null);
|
|
|
|
|
setFields({});
|
|
|
|
|
setCurrentListId(null);
|
2025-07-06 16:18:13 +05:30
|
|
|
setCachedChildSelectors([]);
|
2024-10-27 19:25:10 +05:30
|
|
|
}, []);
|
2024-09-04 05:32:39 +05:30
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!getList) {
|
|
|
|
|
resetListState();
|
|
|
|
|
}
|
|
|
|
|
}, [getList, resetListState]);
|
|
|
|
|
|
2025-03-08 17:10:30 +05:30
|
|
|
const screencastHandler = useCallback((data: string | ScreencastData) => {
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
setScreenShot(data);
|
|
|
|
|
} else if (data && typeof data === 'object' && 'image' in data) {
|
|
|
|
|
if (!data.userId || data.userId === user?.id) {
|
|
|
|
|
setScreenShot(data.image);
|
2025-03-14 12:35:35 +05:30
|
|
|
|
|
|
|
|
if (data.viewport) {
|
|
|
|
|
setViewportInfo(data.viewport);
|
|
|
|
|
}
|
2025-03-08 17:10:30 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [screenShot, user?.id]);
|
2024-06-14 21:47:42 +05:30
|
|
|
|
2024-06-14 23:17:32 +05:30
|
|
|
useEffect(() => {
|
2024-06-14 21:47:42 +05:30
|
|
|
if (socket) {
|
|
|
|
|
socket.on("screencast", screencastHandler);
|
2025-06-23 14:30:06 +05:30
|
|
|
socket.on("domcast", rrwebSnapshotHandler);
|
|
|
|
|
socket.on("dom-mode-enabled", domModeHandler);
|
|
|
|
|
socket.on("dom-mode-error", domModeErrorHandler);
|
2024-06-14 21:47:42 +05:30
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
|
|
|
|
if (canvasRef?.current && !isDOMMode && screenShot) {
|
2024-06-14 21:47:42 +05:30
|
|
|
drawImage(screenShot, canvasRef.current);
|
|
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2024-06-14 21:47:42 +05:30
|
|
|
return () => {
|
2025-06-23 14:30:06 +05:30
|
|
|
if (socket) {
|
|
|
|
|
socket.off("screencast", screencastHandler);
|
|
|
|
|
socket.off("domcast", rrwebSnapshotHandler);
|
|
|
|
|
socket.off("dom-mode-enabled", domModeHandler);
|
|
|
|
|
socket.off("dom-mode-error", domModeErrorHandler);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [
|
|
|
|
|
socket,
|
|
|
|
|
screenShot,
|
|
|
|
|
canvasRef,
|
|
|
|
|
isDOMMode,
|
|
|
|
|
screencastHandler,
|
|
|
|
|
rrwebSnapshotHandler,
|
|
|
|
|
domModeHandler,
|
|
|
|
|
domModeErrorHandler,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const domHighlighterHandler = useCallback(
|
|
|
|
|
(data: {
|
|
|
|
|
rect: DOMRect;
|
|
|
|
|
selector: string;
|
|
|
|
|
elementInfo: ElementInfo | null;
|
|
|
|
|
childSelectors?: string[];
|
2025-07-16 00:27:47 +05:30
|
|
|
isShadow?: boolean;
|
2025-07-06 16:18:13 +05:30
|
|
|
groupInfo?: {
|
|
|
|
|
isGroupElement: boolean;
|
|
|
|
|
groupSize: number;
|
|
|
|
|
groupElements: HTMLElement[];
|
|
|
|
|
groupFingerprint: ElementFingerprint;
|
|
|
|
|
};
|
2025-07-16 00:27:47 +05:30
|
|
|
similarElements?: {
|
|
|
|
|
elements: HTMLElement[];
|
|
|
|
|
rects: DOMRect[];
|
|
|
|
|
};
|
2025-06-23 14:30:06 +05:30
|
|
|
isDOMMode?: boolean;
|
|
|
|
|
}) => {
|
2025-07-06 16:18:13 +05:30
|
|
|
if (!getText && !getList) {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
if (!isDOMMode || !currentSnapshot) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let iframeElement = document.querySelector(
|
|
|
|
|
"#dom-browser-iframe"
|
|
|
|
|
) as HTMLIFrameElement;
|
|
|
|
|
|
|
|
|
|
if (!iframeElement) {
|
|
|
|
|
iframeElement = document.querySelector(
|
2025-07-06 16:18:13 +05:30
|
|
|
"#browser-window iframe"
|
2025-06-23 14:30:06 +05:30
|
|
|
) as HTMLIFrameElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!iframeElement) {
|
|
|
|
|
console.error("Could not find iframe element for DOM highlighting");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const iframeRect = iframeElement.getBoundingClientRect();
|
|
|
|
|
const IFRAME_BODY_PADDING = 16;
|
|
|
|
|
|
2025-07-16 00:27:47 +05:30
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
if (data.groupInfo) {
|
|
|
|
|
setCurrentGroupInfo(data.groupInfo);
|
|
|
|
|
} else {
|
|
|
|
|
setCurrentGroupInfo(null);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
const absoluteRect = new DOMRect(
|
|
|
|
|
data.rect.x + iframeRect.left - IFRAME_BODY_PADDING,
|
|
|
|
|
data.rect.y + iframeRect.top - IFRAME_BODY_PADDING,
|
|
|
|
|
data.rect.width,
|
|
|
|
|
data.rect.height
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const mappedData = {
|
|
|
|
|
...data,
|
|
|
|
|
rect: absoluteRect,
|
2025-07-06 16:18:13 +05:30
|
|
|
childSelectors: data.childSelectors || cachedChildSelectors,
|
2025-07-16 01:07:39 +05:30
|
|
|
similarElements: mappedSimilarElements,
|
2025-06-23 14:30:06 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (getList === true) {
|
2025-07-06 16:18:13 +05:30
|
|
|
if (!listSelector && data.groupInfo?.isGroupElement) {
|
|
|
|
|
const updatedGroupElements = data.groupInfo.groupElements.map(
|
|
|
|
|
(element) => {
|
|
|
|
|
const elementRect = element.getBoundingClientRect();
|
|
|
|
|
return {
|
|
|
|
|
element,
|
|
|
|
|
rect: new DOMRect(
|
|
|
|
|
elementRect.x + iframeRect.left - IFRAME_BODY_PADDING,
|
|
|
|
|
elementRect.y + iframeRect.top - IFRAME_BODY_PADDING,
|
|
|
|
|
elementRect.width,
|
|
|
|
|
elementRect.height
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const mappedData = {
|
|
|
|
|
...data,
|
|
|
|
|
rect: absoluteRect,
|
|
|
|
|
groupElements: updatedGroupElements,
|
|
|
|
|
childSelectors: data.childSelectors || cachedChildSelectors,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
} else if (listSelector) {
|
|
|
|
|
const hasChildSelectors =
|
2025-06-23 14:30:06 +05:30
|
|
|
Array.isArray(mappedData.childSelectors) &&
|
|
|
|
|
mappedData.childSelectors.length > 0;
|
|
|
|
|
|
|
|
|
|
if (limitMode) {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
} else if (paginationMode) {
|
|
|
|
|
if (
|
2025-07-06 16:18:13 +05:30
|
|
|
paginationType !== "" &&
|
|
|
|
|
!["none", "scrollDown", "scrollUp"].includes(paginationType)
|
2025-06-23 14:30:06 +05:30
|
|
|
) {
|
|
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
} else {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
}
|
2025-07-06 16:18:13 +05:30
|
|
|
} else if (hasChildSelectors) {
|
2025-06-23 14:30:06 +05:30
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
} else {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
isDOMMode,
|
|
|
|
|
currentSnapshot,
|
2025-07-06 16:18:13 +05:30
|
|
|
getText,
|
2025-06-23 14:30:06 +05:30
|
|
|
getList,
|
|
|
|
|
socket,
|
|
|
|
|
listSelector,
|
|
|
|
|
paginationMode,
|
|
|
|
|
paginationType,
|
|
|
|
|
limitMode,
|
2025-07-06 16:18:13 +05:30
|
|
|
cachedChildSelectors,
|
2025-06-23 14:30:06 +05:30
|
|
|
]
|
|
|
|
|
);
|
2024-06-14 21:47:42 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[], isDOMMode?: boolean; }) => {
|
|
|
|
|
if (isDOMMode || data.isDOMMode) {
|
|
|
|
|
domHighlighterHandler(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-12 14:07:18 +05:30
|
|
|
const now = performance.now();
|
|
|
|
|
if (now - highlighterUpdateRef.current < 16) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
highlighterUpdateRef.current = now;
|
|
|
|
|
|
2025-03-14 12:35:35 +05:30
|
|
|
// Map the incoming DOMRect from browser coordinates to canvas coordinates
|
|
|
|
|
const mappedRect = new DOMRect(
|
|
|
|
|
data.rect.x,
|
|
|
|
|
data.rect.y,
|
|
|
|
|
data.rect.width,
|
|
|
|
|
data.rect.height
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const mappedData = {
|
|
|
|
|
...data,
|
|
|
|
|
rect: mappedRect
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-06 11:06:44 +05:30
|
|
|
if (getList === true) {
|
|
|
|
|
if (listSelector) {
|
|
|
|
|
socket?.emit('listSelector', { selector: listSelector });
|
2025-03-14 12:35:35 +05:30
|
|
|
const hasValidChildSelectors = Array.isArray(mappedData.childSelectors) && mappedData.childSelectors.length > 0;
|
2025-01-01 16:15:13 +05:30
|
|
|
|
2024-09-14 07:03:41 +05:30
|
|
|
if (limitMode) {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
} else if (paginationMode) {
|
2025-01-04 14:47:51 +05:30
|
|
|
// Only set highlighterData if type is not empty, 'none', 'scrollDown', or 'scrollUp'
|
2024-09-08 14:31:06 +05:30
|
|
|
if (paginationType !== '' && !['none', 'scrollDown', 'scrollUp'].includes(paginationType)) {
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(mappedData);
|
2024-09-06 23:14:41 +05:30
|
|
|
} else {
|
|
|
|
|
setHighlighterData(null);
|
|
|
|
|
}
|
2025-03-14 12:35:35 +05:30
|
|
|
} else if (mappedData.childSelectors && mappedData.childSelectors.includes(mappedData.selector)) {
|
2025-01-04 14:47:51 +05:30
|
|
|
// Highlight only valid child elements within the listSelector
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(mappedData);
|
|
|
|
|
} else if (mappedData.elementInfo?.isIframeContent && mappedData.childSelectors) {
|
|
|
|
|
// Handle iframe elements
|
|
|
|
|
const isIframeChild = mappedData.childSelectors.some(childSelector =>
|
|
|
|
|
mappedData.selector.includes(':>>') &&
|
2025-01-09 17:15:08 +05:30
|
|
|
childSelector.split(':>>').some(part =>
|
2025-03-14 12:35:35 +05:30
|
|
|
mappedData.selector.includes(part.trim())
|
2025-01-04 14:47:51 +05:30
|
|
|
)
|
|
|
|
|
);
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(isIframeChild ? mappedData : null);
|
|
|
|
|
} else if (mappedData.selector.includes(':>>') && hasValidChildSelectors) {
|
2025-01-04 14:47:51 +05:30
|
|
|
// Handle mixed DOM cases with iframes
|
2025-03-14 12:35:35 +05:30
|
|
|
const selectorParts = mappedData.selector.split(':>>').map(part => part.trim());
|
2025-01-09 17:15:08 +05:30
|
|
|
const isValidMixedSelector = selectorParts.some(part =>
|
2025-03-14 12:35:35 +05:30
|
|
|
mappedData.childSelectors!.some(childSelector =>
|
2025-01-04 14:47:51 +05:30
|
|
|
childSelector.includes(part)
|
|
|
|
|
)
|
|
|
|
|
);
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(isValidMixedSelector ? mappedData : null);
|
|
|
|
|
} else if (mappedData.elementInfo?.isShadowRoot && mappedData.childSelectors) {
|
|
|
|
|
// Handle Shadow DOM elements
|
|
|
|
|
const isShadowChild = mappedData.childSelectors.some(childSelector =>
|
|
|
|
|
mappedData.selector.includes('>>') &&
|
2025-01-09 17:15:08 +05:30
|
|
|
childSelector.split('>>').some(part =>
|
2025-03-14 12:35:35 +05:30
|
|
|
mappedData.selector.includes(part.trim())
|
2025-01-01 16:15:13 +05:30
|
|
|
)
|
|
|
|
|
);
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(isShadowChild ? mappedData : null);
|
|
|
|
|
} else if (mappedData.selector.includes('>>') && hasValidChildSelectors) {
|
|
|
|
|
// Handle mixed DOM cases
|
|
|
|
|
const selectorParts = mappedData.selector.split('>>').map(part => part.trim());
|
2025-01-09 17:15:08 +05:30
|
|
|
const isValidMixedSelector = selectorParts.some(part =>
|
2025-03-14 12:35:35 +05:30
|
|
|
mappedData.childSelectors!.some(childSelector =>
|
2025-01-01 16:15:13 +05:30
|
|
|
childSelector.includes(part)
|
|
|
|
|
)
|
|
|
|
|
);
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(isValidMixedSelector ? mappedData : null);
|
2025-01-09 17:15:08 +05:30
|
|
|
} else {
|
2025-03-14 12:35:35 +05:30
|
|
|
// If not a valid child in normal mode, clear the highlighter
|
2024-09-06 11:06:44 +05:30
|
|
|
setHighlighterData(null);
|
2025-01-09 17:15:08 +05:30
|
|
|
}
|
|
|
|
|
} else {
|
2025-01-04 14:47:51 +05:30
|
|
|
// Set highlighterData for the initial listSelector selection
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(mappedData);
|
2025-01-09 17:15:08 +05:30
|
|
|
}
|
|
|
|
|
} else {
|
2025-01-04 14:47:51 +05:30
|
|
|
// For non-list steps
|
2025-03-14 12:35:35 +05:30
|
|
|
setHighlighterData(mappedData);
|
2025-01-09 17:15:08 +05:30
|
|
|
}
|
2025-03-14 12:35:35 +05:30
|
|
|
}, [getList, socket, listSelector, paginationMode, paginationType, limitMode]);
|
2024-09-06 11:04:59 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
document.addEventListener("mousemove", onMouseMove, false);
|
|
|
|
|
if (socket) {
|
|
|
|
|
socket.off("highlighter", highlighterHandler);
|
|
|
|
|
socket.on("highlighter", highlighterHandler);
|
|
|
|
|
}
|
|
|
|
|
return () => {
|
|
|
|
|
document.removeEventListener("mousemove", onMouseMove);
|
|
|
|
|
if (socket) {
|
|
|
|
|
socket.off("highlighter", highlighterHandler);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [socket, highlighterHandler, getList, listSelector]);
|
|
|
|
|
|
2025-03-11 12:58:36 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (socket && listSelector) {
|
|
|
|
|
socket.emit('setGetList', { getList: true });
|
|
|
|
|
socket.emit('listSelector', { selector: listSelector });
|
|
|
|
|
}
|
|
|
|
|
}, [socket, listSelector]);
|
2024-06-25 22:39:29 +05:30
|
|
|
|
2024-12-29 17:52:58 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (captureStage === 'initial' && listSelector) {
|
|
|
|
|
socket?.emit('setGetList', { getList: true });
|
|
|
|
|
socket?.emit('listSelector', { selector: listSelector });
|
|
|
|
|
}
|
|
|
|
|
}, [captureStage, listSelector, socket]);
|
|
|
|
|
|
2025-06-23 14:30:06 +05:30
|
|
|
const handleDOMElementSelection = useCallback(
|
2025-07-06 16:18:13 +05:30
|
|
|
(highlighterData: {
|
|
|
|
|
rect: DOMRect;
|
|
|
|
|
selector: string;
|
2025-07-16 00:27:47 +05:30
|
|
|
isShadow?: boolean;
|
2025-07-06 16:18:13 +05:30
|
|
|
elementInfo: ElementInfo | null;
|
|
|
|
|
childSelectors?: string[];
|
|
|
|
|
groupInfo?: {
|
|
|
|
|
isGroupElement: boolean;
|
|
|
|
|
groupSize: number;
|
|
|
|
|
groupElements: HTMLElement[];
|
|
|
|
|
};
|
|
|
|
|
}) => {
|
|
|
|
|
setShowAttributeModal(false);
|
|
|
|
|
setSelectedElement(null);
|
|
|
|
|
setAttributeOptions([]);
|
|
|
|
|
|
|
|
|
|
if (paginationMode && getList) {
|
|
|
|
|
if (
|
|
|
|
|
paginationType !== "" &&
|
|
|
|
|
paginationType !== "scrollDown" &&
|
|
|
|
|
paginationType !== "scrollUp" &&
|
|
|
|
|
paginationType !== "none"
|
|
|
|
|
) {
|
|
|
|
|
setPaginationSelector(highlighterData.selector);
|
|
|
|
|
notify(
|
|
|
|
|
`info`,
|
|
|
|
|
t(
|
|
|
|
|
"browser_window.attribute_modal.notifications.pagination_select_success"
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
addListStep(
|
2025-07-16 00:27:47 +05:30
|
|
|
listSelector!,
|
|
|
|
|
fields,
|
|
|
|
|
currentListId || 0,
|
|
|
|
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
|
|
|
|
{
|
|
|
|
|
type: paginationType,
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
isShadow: highlighterData.isShadow
|
|
|
|
|
},
|
|
|
|
|
undefined,
|
|
|
|
|
highlighterData.isShadow
|
2025-07-06 16:18:13 +05:30
|
|
|
);
|
|
|
|
|
socket?.emit("setPaginationMode", { pagination: false });
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
if (
|
|
|
|
|
getList === true &&
|
|
|
|
|
!listSelector &&
|
|
|
|
|
highlighterData.groupInfo?.isGroupElement
|
|
|
|
|
) {
|
|
|
|
|
let cleanedSelector = highlighterData.selector;
|
|
|
|
|
|
|
|
|
|
setListSelector(cleanedSelector);
|
|
|
|
|
notify(
|
|
|
|
|
`info`,
|
|
|
|
|
t(
|
2025-07-06 16:49:20 +05:30
|
|
|
"browser_window.attribute_modal.notifications.list_select_success",
|
2025-07-06 16:18:13 +05:30
|
|
|
{
|
|
|
|
|
count: highlighterData.groupInfo.groupSize,
|
|
|
|
|
}
|
|
|
|
|
) ||
|
|
|
|
|
`Selected group with ${highlighterData.groupInfo.groupSize} similar elements`
|
|
|
|
|
);
|
|
|
|
|
setCurrentListId(Date.now());
|
|
|
|
|
setFields({});
|
|
|
|
|
|
|
|
|
|
socket?.emit("setGetList", { getList: true });
|
|
|
|
|
socket?.emit("listSelector", { selector: cleanedSelector });
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (getList === true && listSelector && currentListId) {
|
|
|
|
|
const options = getAttributeOptions(
|
2025-06-23 14:30:06 +05:30
|
|
|
highlighterData.elementInfo?.tagName || "",
|
|
|
|
|
highlighterData.elementInfo
|
2025-07-06 16:18:13 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (options.length === 1) {
|
|
|
|
|
const attribute = options[0].value;
|
|
|
|
|
let currentSelector = highlighterData.selector;
|
|
|
|
|
|
|
|
|
|
const data =
|
|
|
|
|
attribute === "href"
|
|
|
|
|
? highlighterData.elementInfo?.url || ""
|
|
|
|
|
: attribute === "src"
|
|
|
|
|
? highlighterData.elementInfo?.imageUrl || ""
|
|
|
|
|
: highlighterData.elementInfo?.innerText || "";
|
|
|
|
|
|
|
|
|
|
const newField: TextStep = {
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
type: "text",
|
|
|
|
|
label: `Label ${Object.keys(fields).length + 1}`,
|
|
|
|
|
data: data,
|
|
|
|
|
selectorObj: {
|
|
|
|
|
selector: currentSelector,
|
|
|
|
|
tag: highlighterData.elementInfo?.tagName,
|
2025-07-16 01:07:39 +05:30
|
|
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
2025-07-06 16:18:13 +05:30
|
|
|
attribute,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updatedFields = {
|
|
|
|
|
...fields,
|
|
|
|
|
[newField.id]: newField,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setFields(updatedFields);
|
|
|
|
|
|
|
|
|
|
if (listSelector) {
|
|
|
|
|
addListStep(
|
|
|
|
|
listSelector,
|
|
|
|
|
updatedFields,
|
|
|
|
|
currentListId,
|
|
|
|
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
2025-07-16 00:27:47 +05:30
|
|
|
{ type: "", selector: paginationSelector },
|
|
|
|
|
undefined,
|
|
|
|
|
highlighterData.isShadow
|
2025-07-06 16:18:13 +05:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setAttributeOptions(options);
|
|
|
|
|
setSelectedElement({
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
info: highlighterData.elementInfo,
|
|
|
|
|
});
|
|
|
|
|
setShowAttributeModal(true);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
|
|
|
|
if (getText === true) {
|
2025-07-06 16:18:13 +05:30
|
|
|
const options = getAttributeOptions(
|
|
|
|
|
highlighterData.elementInfo?.tagName || "",
|
|
|
|
|
highlighterData.elementInfo
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (options.length === 1) {
|
|
|
|
|
const attribute = options[0].value;
|
|
|
|
|
const data =
|
|
|
|
|
attribute === "href"
|
|
|
|
|
? highlighterData.elementInfo?.url || ""
|
|
|
|
|
: attribute === "src"
|
|
|
|
|
? highlighterData.elementInfo?.imageUrl || ""
|
|
|
|
|
: highlighterData.elementInfo?.innerText || "";
|
|
|
|
|
|
|
|
|
|
addTextStep(
|
|
|
|
|
"",
|
|
|
|
|
data,
|
|
|
|
|
{
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
tag: highlighterData.elementInfo?.tagName,
|
2025-07-16 00:27:47 +05:30
|
|
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
2025-07-06 16:18:13 +05:30
|
|
|
attribute,
|
|
|
|
|
},
|
|
|
|
|
currentTextActionId || `text-${crypto.randomUUID()}`
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
setAttributeOptions(options);
|
|
|
|
|
setSelectedElement({
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
info: highlighterData.elementInfo,
|
|
|
|
|
});
|
|
|
|
|
setShowAttributeModal(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
getText,
|
|
|
|
|
getList,
|
|
|
|
|
listSelector,
|
|
|
|
|
paginationMode,
|
|
|
|
|
paginationType,
|
|
|
|
|
limitMode,
|
|
|
|
|
fields,
|
|
|
|
|
currentListId,
|
|
|
|
|
currentTextActionId,
|
|
|
|
|
currentListActionId,
|
|
|
|
|
addTextStep,
|
|
|
|
|
addListStep,
|
|
|
|
|
notify,
|
|
|
|
|
socket,
|
|
|
|
|
t,
|
|
|
|
|
paginationSelector,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
if (highlighterData) {
|
|
|
|
|
let shouldProcessClick = false;
|
|
|
|
|
|
|
|
|
|
if (!isDOMMode && canvasRef?.current) {
|
|
|
|
|
const canvasRect = canvasRef.current.getBoundingClientRect();
|
|
|
|
|
const clickX = e.clientX - canvasRect.left;
|
|
|
|
|
const clickY = e.clientY - canvasRect.top;
|
|
|
|
|
const highlightRect = highlighterData.rect;
|
|
|
|
|
const mappedRect =
|
|
|
|
|
coordinateMapper.mapBrowserRectToCanvas(highlightRect);
|
|
|
|
|
|
|
|
|
|
shouldProcessClick =
|
|
|
|
|
clickX >= mappedRect.left &&
|
|
|
|
|
clickX <= mappedRect.right &&
|
|
|
|
|
clickY >= mappedRect.top &&
|
|
|
|
|
clickY <= mappedRect.bottom;
|
|
|
|
|
} else {
|
|
|
|
|
shouldProcessClick = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldProcessClick) {
|
|
|
|
|
const options = getAttributeOptions(
|
|
|
|
|
highlighterData.elementInfo?.tagName || "",
|
|
|
|
|
highlighterData.elementInfo
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (getText === true) {
|
2025-06-23 14:30:06 +05:30
|
|
|
if (options.length === 1) {
|
2025-07-06 16:18:13 +05:30
|
|
|
const attribute = options[0].value;
|
|
|
|
|
const data =
|
|
|
|
|
attribute === "href"
|
|
|
|
|
? highlighterData.elementInfo?.url || ""
|
|
|
|
|
: attribute === "src"
|
|
|
|
|
? highlighterData.elementInfo?.imageUrl || ""
|
|
|
|
|
: highlighterData.elementInfo?.innerText || "";
|
|
|
|
|
|
|
|
|
|
addTextStep(
|
|
|
|
|
"",
|
|
|
|
|
data,
|
|
|
|
|
{
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
tag: highlighterData.elementInfo?.tagName,
|
2025-07-16 00:27:47 +05:30
|
|
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
2025-07-06 16:18:13 +05:30
|
|
|
attribute,
|
|
|
|
|
},
|
|
|
|
|
currentTextActionId || `text-${crypto.randomUUID()}`
|
|
|
|
|
);
|
2025-06-23 14:30:06 +05:30
|
|
|
} else {
|
2025-07-06 16:18:13 +05:30
|
|
|
setAttributeOptions(options);
|
|
|
|
|
setSelectedElement({
|
|
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
info: highlighterData.elementInfo,
|
|
|
|
|
});
|
|
|
|
|
setShowAttributeModal(true);
|
2025-06-23 14:30:06 +05:30
|
|
|
}
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
if (paginationMode && getList) {
|
2025-06-23 14:30:06 +05:30
|
|
|
if (
|
2025-07-06 16:18:13 +05:30
|
|
|
paginationType !== "" &&
|
|
|
|
|
paginationType !== "scrollDown" &&
|
|
|
|
|
paginationType !== "scrollUp" &&
|
|
|
|
|
paginationType !== "none"
|
2025-06-23 14:30:06 +05:30
|
|
|
) {
|
2025-07-06 16:18:13 +05:30
|
|
|
setPaginationSelector(highlighterData.selector);
|
|
|
|
|
notify(
|
|
|
|
|
`info`,
|
|
|
|
|
t(
|
|
|
|
|
"browser_window.attribute_modal.notifications.pagination_select_success"
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
addListStep(
|
|
|
|
|
listSelector!,
|
|
|
|
|
fields,
|
|
|
|
|
currentListId || 0,
|
|
|
|
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
2025-07-16 00:27:47 +05:30
|
|
|
{ type: paginationType, selector: highlighterData.selector, isShadow: highlighterData.isShadow },
|
|
|
|
|
undefined,
|
|
|
|
|
highlighterData.isShadow
|
2025-07-06 16:18:13 +05:30
|
|
|
);
|
|
|
|
|
socket?.emit("setPaginationMode", { pagination: false });
|
2025-06-23 14:30:06 +05:30
|
|
|
}
|
|
|
|
|
return;
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
if (getList === true && !listSelector) {
|
2025-06-23 14:30:06 +05:30
|
|
|
let cleanedSelector = highlighterData.selector;
|
2025-07-06 16:18:13 +05:30
|
|
|
if (
|
|
|
|
|
cleanedSelector.includes("[") &&
|
|
|
|
|
cleanedSelector.match(/\[\d+\]/)
|
|
|
|
|
) {
|
|
|
|
|
cleanedSelector = cleanedSelector.replace(/\[\d+\]/g, "");
|
2025-06-23 14:30:06 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setListSelector(cleanedSelector);
|
|
|
|
|
notify(
|
2025-07-06 16:18:13 +05:30
|
|
|
`info`,
|
|
|
|
|
t(
|
|
|
|
|
"browser_window.attribute_modal.notifications.list_select_success"
|
|
|
|
|
)
|
2025-06-23 14:30:06 +05:30
|
|
|
);
|
|
|
|
|
setCurrentListId(Date.now());
|
|
|
|
|
setFields({});
|
2025-07-06 16:18:13 +05:30
|
|
|
} else if (getList === true && listSelector && currentListId) {
|
|
|
|
|
const attribute = options[0].value;
|
|
|
|
|
const data =
|
|
|
|
|
attribute === "href"
|
|
|
|
|
? highlighterData.elementInfo?.url || ""
|
|
|
|
|
: attribute === "src"
|
|
|
|
|
? highlighterData.elementInfo?.imageUrl || ""
|
|
|
|
|
: highlighterData.elementInfo?.innerText || "";
|
2025-06-23 14:30:06 +05:30
|
|
|
|
|
|
|
|
if (options.length === 1) {
|
2025-07-06 16:18:13 +05:30
|
|
|
let currentSelector = highlighterData.selector;
|
|
|
|
|
|
|
|
|
|
if (currentSelector.includes("/")) {
|
|
|
|
|
const xpathParts = currentSelector
|
|
|
|
|
.split("/")
|
|
|
|
|
.filter((part) => part);
|
|
|
|
|
const cleanedParts = xpathParts.map((part) => {
|
|
|
|
|
return part.replace(/\[\d+\]/g, "");
|
|
|
|
|
});
|
2025-06-23 14:30:06 +05:30
|
|
|
|
2025-07-06 16:18:13 +05:30
|
|
|
if (cleanedParts.length > 0) {
|
|
|
|
|
currentSelector = "//" + cleanedParts.join("/");
|
2025-06-23 14:30:06 +05:30
|
|
|
}
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newField: TextStep = {
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
type: "text",
|
|
|
|
|
label: `Label ${Object.keys(fields).length + 1}`,
|
|
|
|
|
data: data,
|
|
|
|
|
selectorObj: {
|
|
|
|
|
selector: currentSelector,
|
|
|
|
|
tag: highlighterData.elementInfo?.tagName,
|
2025-07-16 01:07:39 +05:30
|
|
|
isShadow: highlighterData.isShadow || highlighterData.elementInfo?.isShadowRoot,
|
2025-07-06 16:18:13 +05:30
|
|
|
attribute,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updatedFields = {
|
|
|
|
|
...fields,
|
|
|
|
|
[newField.id]: newField,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setFields(updatedFields);
|
|
|
|
|
|
|
|
|
|
if (listSelector) {
|
|
|
|
|
addListStep(
|
|
|
|
|
listSelector,
|
|
|
|
|
updatedFields,
|
|
|
|
|
currentListId,
|
|
|
|
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
2025-07-16 00:27:47 +05:30
|
|
|
{ type: "", selector: paginationSelector, isShadow: highlighterData.isShadow },
|
|
|
|
|
undefined,
|
|
|
|
|
highlighterData.isShadow
|
2025-07-06 16:18:13 +05:30
|
|
|
);
|
|
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
} else {
|
2025-07-06 16:18:13 +05:30
|
|
|
setAttributeOptions(options);
|
|
|
|
|
setSelectedElement({
|
2025-06-23 14:30:06 +05:30
|
|
|
selector: highlighterData.selector,
|
|
|
|
|
info: highlighterData.elementInfo,
|
2025-07-06 16:18:13 +05:30
|
|
|
});
|
|
|
|
|
setShowAttributeModal(true);
|
2024-10-27 19:25:10 +05:30
|
|
|
}
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
2024-08-04 03:18:41 +05:30
|
|
|
}
|
2025-07-06 16:18:13 +05:30
|
|
|
}
|
2024-08-04 03:18:41 +05:30
|
|
|
};
|
2024-08-21 23:38:33 +05:30
|
|
|
|
2024-08-04 03:18:41 +05:30
|
|
|
const handleAttributeSelection = (attribute: string) => {
|
|
|
|
|
if (selectedElement) {
|
|
|
|
|
let data = '';
|
|
|
|
|
switch (attribute) {
|
|
|
|
|
case 'href':
|
|
|
|
|
data = selectedElement.info?.url || '';
|
|
|
|
|
break;
|
|
|
|
|
case 'src':
|
|
|
|
|
data = selectedElement.info?.imageUrl || '';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
data = selectedElement.info?.innerText || '';
|
2024-07-23 22:28:32 +05:30
|
|
|
}
|
2024-08-06 02:16:53 +05:30
|
|
|
{
|
2024-08-09 06:23:10 +05:30
|
|
|
if (getText === true) {
|
2024-08-06 03:11:28 +05:30
|
|
|
addTextStep('', data, {
|
2024-08-06 02:16:53 +05:30
|
|
|
selector: selectedElement.selector,
|
|
|
|
|
tag: selectedElement.info?.tagName,
|
2025-07-16 00:27:47 +05:30
|
|
|
isShadow: highlighterData?.isShadow || selectedElement.info?.isShadowRoot,
|
2024-08-06 02:16:53 +05:30
|
|
|
attribute: attribute
|
2025-05-26 21:41:40 +05:30
|
|
|
}, currentTextActionId || `text-${crypto.randomUUID()}`);
|
2024-08-06 02:16:53 +05:30
|
|
|
}
|
2024-09-07 22:20:53 +05:30
|
|
|
if (getList === true && listSelector && currentListId) {
|
2024-08-10 22:39:56 +05:30
|
|
|
const newField: TextStep = {
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
type: 'text',
|
2024-09-14 08:05:33 +05:30
|
|
|
label: `Label ${Object.keys(fields).length + 1}`,
|
2024-08-30 02:54:11 +05:30
|
|
|
data: data,
|
2024-08-10 22:39:56 +05:30
|
|
|
selectorObj: {
|
|
|
|
|
selector: selectedElement.selector,
|
|
|
|
|
tag: selectedElement.info?.tagName,
|
2025-07-16 01:07:39 +05:30
|
|
|
isShadow: highlighterData?.isShadow || highlighterData?.elementInfo?.isShadowRoot,
|
2024-08-10 23:16:09 +05:30
|
|
|
attribute: attribute
|
2024-08-10 22:39:56 +05:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-07 09:18:08 +05:30
|
|
|
const updatedFields = {
|
|
|
|
|
...fields,
|
|
|
|
|
[newField.id]: newField
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setFields(updatedFields);
|
2024-09-21 15:56:51 +05:30
|
|
|
|
|
|
|
|
if (listSelector) {
|
2025-05-07 09:18:08 +05:30
|
|
|
addListStep(
|
|
|
|
|
listSelector,
|
|
|
|
|
updatedFields,
|
|
|
|
|
currentListId,
|
2025-05-26 21:41:40 +05:30
|
|
|
currentListActionId || `list-${crypto.randomUUID()}`,
|
2025-07-16 00:27:47 +05:30
|
|
|
{ type: "", selector: paginationSelector, isShadow: highlighterData?.isShadow },
|
|
|
|
|
undefined,
|
|
|
|
|
highlighterData?.isShadow
|
2025-05-07 09:18:08 +05:30
|
|
|
);
|
2024-09-21 15:56:51 +05:30
|
|
|
}
|
2024-08-10 22:39:56 +05:30
|
|
|
}
|
2024-08-06 02:16:53 +05:30
|
|
|
}
|
2024-07-23 22:28:32 +05:30
|
|
|
}
|
2025-06-23 14:30:06 +05:30
|
|
|
|
|
|
|
|
setShowAttributeModal(false);
|
|
|
|
|
setSelectedElement(null);
|
|
|
|
|
setAttributeOptions([]);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2024-08-04 03:18:41 +05:30
|
|
|
setShowAttributeModal(false);
|
2025-06-23 14:30:06 +05:30
|
|
|
}, 0);
|
2024-07-23 22:28:32 +05:30
|
|
|
};
|
|
|
|
|
|
2024-09-06 21:08:22 +05:30
|
|
|
const resetPaginationSelector = useCallback(() => {
|
|
|
|
|
setPaginationSelector('');
|
|
|
|
|
}, []);
|
2024-09-06 22:07:00 +05:30
|
|
|
|
2024-09-06 21:08:22 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (!paginationMode) {
|
|
|
|
|
resetPaginationSelector();
|
|
|
|
|
}
|
|
|
|
|
}, [paginationMode, resetPaginationSelector]);
|
|
|
|
|
|
2024-06-14 21:47:42 +05:30
|
|
|
return (
|
2025-07-16 00:27:47 +05:30
|
|
|
<div
|
|
|
|
|
onClick={handleClick}
|
|
|
|
|
style={{ width: browserWidth }}
|
|
|
|
|
id="browser-window"
|
|
|
|
|
>
|
|
|
|
|
{/* Attribute selection modal */}
|
|
|
|
|
{(getText === true || getList === true) && (
|
|
|
|
|
<GenericModal
|
|
|
|
|
isOpen={showAttributeModal}
|
|
|
|
|
onClose={() => {
|
|
|
|
|
setShowAttributeModal(false);
|
|
|
|
|
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%",
|
|
|
|
|
}}
|
2024-08-21 22:32:20 +05:30
|
|
|
>
|
2025-07-16 00:27:47 +05:30
|
|
|
{option.label}
|
|
|
|
|
</span>
|
|
|
|
|
</Button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</GenericModal>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{datePickerInfo && (
|
|
|
|
|
<DatePicker
|
|
|
|
|
coordinates={datePickerInfo.coordinates}
|
|
|
|
|
selector={datePickerInfo.selector}
|
|
|
|
|
onClose={() => setDatePickerInfo(null)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{dropdownInfo && (
|
|
|
|
|
<Dropdown
|
|
|
|
|
coordinates={dropdownInfo.coordinates}
|
|
|
|
|
selector={dropdownInfo.selector}
|
|
|
|
|
options={dropdownInfo.options}
|
|
|
|
|
onClose={() => setDropdownInfo(null)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{timePickerInfo && (
|
|
|
|
|
<TimePicker
|
|
|
|
|
coordinates={timePickerInfo.coordinates}
|
|
|
|
|
selector={timePickerInfo.selector}
|
|
|
|
|
onClose={() => setTimePickerInfo(null)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{dateTimeLocalInfo && (
|
|
|
|
|
<DateTimeLocalPicker
|
|
|
|
|
coordinates={dateTimeLocalInfo.coordinates}
|
|
|
|
|
selector={dateTimeLocalInfo.selector}
|
|
|
|
|
onClose={() => setDateTimeLocalInfo(null)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Main content area */}
|
|
|
|
|
<div style={{ height: dimensions.height, overflow: "hidden" }}>
|
|
|
|
|
{/* Add CSS for the spinner animation */}
|
|
|
|
|
<style>{`
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
0% { transform: rotate(0deg); }
|
|
|
|
|
100% { transform: rotate(360deg); }
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
|
|
|
|
|
{(getText === true || getList === true) &&
|
|
|
|
|
!showAttributeModal &&
|
|
|
|
|
highlighterData?.rect != null && (
|
|
|
|
|
<>
|
|
|
|
|
{!isDOMMode && canvasRef?.current && (
|
|
|
|
|
<Highlighter
|
|
|
|
|
unmodifiedRect={highlighterData?.rect}
|
|
|
|
|
displayedSelector={highlighterData?.selector}
|
|
|
|
|
width={dimensions.width}
|
|
|
|
|
height={dimensions.height}
|
|
|
|
|
canvasRect={canvasRef.current.getBoundingClientRect()}
|
|
|
|
|
/>
|
2025-06-23 14:30:06 +05:30
|
|
|
)}
|
|
|
|
|
|
2025-07-16 00:27:47 +05:30
|
|
|
{isDOMMode && highlighterData && (
|
|
|
|
|
<>
|
|
|
|
|
{/* Individual element highlight (for non-group or hovered element) */}
|
2025-07-17 13:40:27 +05:30
|
|
|
{((getText && !listSelector) ||
|
|
|
|
|
(getList && paginationMode && paginationType !== "" &&
|
|
|
|
|
!["none", "scrollDown", "scrollUp"].includes(paginationType))) && (
|
2025-07-16 00:27:47 +05:30
|
|
|
<div
|
2025-06-23 14:30:06 +05:30
|
|
|
style={{
|
2025-07-16 00:27:47 +05:30
|
|
|
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",
|
2025-06-23 14:30:06 +05:30
|
|
|
}}
|
2025-07-16 00:27:47 +05:30
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Group elements highlighting with real-time coordinates */}
|
|
|
|
|
{getList &&
|
|
|
|
|
!listSelector &&
|
|
|
|
|
currentGroupInfo?.isGroupElement &&
|
|
|
|
|
highlighterData.groupElements &&
|
|
|
|
|
highlighterData.groupElements.map(
|
|
|
|
|
(groupElement, index) => (
|
|
|
|
|
<React.Fragment key={index}>
|
|
|
|
|
{/* Highlight box */}
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
left: Math.max(0, groupElement.rect.x),
|
|
|
|
|
top: Math.max(0, groupElement.rect.y),
|
|
|
|
|
width: Math.min(
|
|
|
|
|
groupElement.rect.width,
|
|
|
|
|
dimensions.width
|
|
|
|
|
),
|
|
|
|
|
height: Math.min(
|
|
|
|
|
groupElement.rect.height,
|
|
|
|
|
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
|
|
|
|
|
style={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
left: Math.max(0, groupElement.rect.x),
|
|
|
|
|
top: Math.max(0, groupElement.rect.y - 20),
|
|
|
|
|
background: "#ff00c3",
|
|
|
|
|
color: "white",
|
|
|
|
|
padding: "2px 6px",
|
|
|
|
|
fontSize: "10px",
|
|
|
|
|
fontWeight: "bold",
|
|
|
|
|
borderRadius: "2px",
|
|
|
|
|
pointerEvents: "none",
|
|
|
|
|
zIndex: 1001,
|
|
|
|
|
whiteSpace: "nowrap",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
List item {index + 1}
|
|
|
|
|
</div>
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
)
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{getList &&
|
|
|
|
|
listSelector &&
|
|
|
|
|
!paginationMode &&
|
|
|
|
|
!limitMode &&
|
|
|
|
|
highlighterData?.similarElements &&
|
|
|
|
|
highlighterData.similarElements.rects.map(
|
|
|
|
|
(rect, index) => (
|
|
|
|
|
<React.Fragment key={`item-${index}`}>
|
|
|
|
|
{/* Highlight box for similar element */}
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
position: "absolute",
|
|
|
|
|
left: Math.max(0, rect.x),
|
|
|
|
|
top: Math.max(0, rect.y),
|
|
|
|
|
width: Math.min(rect.width, dimensions.width),
|
|
|
|
|
height: Math.min(
|
|
|
|
|
rect.height,
|
|
|
|
|
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",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* 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>
|
|
|
|
|
)
|
|
|
|
|
)}
|
|
|
|
|
</>
|
2025-06-23 14:30:06 +05:30
|
|
|
)}
|
2025-07-16 00:27:47 +05:30
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{isDOMMode ? (
|
|
|
|
|
<div
|
|
|
|
|
style={{ position: "relative", width: "100%", height: "100%" }}
|
|
|
|
|
>
|
|
|
|
|
{currentSnapshot ? (
|
|
|
|
|
<DOMBrowserRenderer
|
|
|
|
|
width={dimensions.width}
|
|
|
|
|
height={dimensions.height}
|
|
|
|
|
snapshot={currentSnapshot}
|
|
|
|
|
getList={getList}
|
|
|
|
|
getText={getText}
|
|
|
|
|
listSelector={listSelector}
|
|
|
|
|
cachedChildSelectors={cachedChildSelectors}
|
|
|
|
|
paginationMode={paginationMode}
|
|
|
|
|
paginationType={paginationType}
|
|
|
|
|
limitMode={limitMode}
|
|
|
|
|
onHighlight={(data) => {
|
|
|
|
|
domHighlighterHandler(data);
|
|
|
|
|
}}
|
|
|
|
|
isCachingChildSelectors={isCachingChildSelectors}
|
|
|
|
|
onElementSelect={handleDOMElementSelection}
|
|
|
|
|
onShowDatePicker={handleShowDatePicker}
|
|
|
|
|
onShowDropdown={handleShowDropdown}
|
|
|
|
|
onShowTimePicker={handleShowTimePicker}
|
|
|
|
|
onShowDateTimePicker={handleShowDateTimePicker}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
width: dimensions.width,
|
|
|
|
|
height: dimensions.height,
|
|
|
|
|
display: "flex",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
justifyContent: "center",
|
|
|
|
|
background: "#f5f5f5",
|
|
|
|
|
borderRadius: "5px",
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
gap: "20px",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
width: "60px",
|
|
|
|
|
height: "60px",
|
|
|
|
|
borderTop: "4px solid transparent",
|
|
|
|
|
borderRadius: "50%",
|
|
|
|
|
animation: "spin 1s linear infinite",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: "18px",
|
|
|
|
|
color: "#ff00c3",
|
|
|
|
|
fontWeight: "bold",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Loading website...
|
|
|
|
|
</div>
|
|
|
|
|
<style>{`
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
0% { transform: rotate(0deg); }
|
|
|
|
|
100% { transform: rotate(360deg); }
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 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>
|
|
|
|
|
)}
|
2024-10-18 19:07:31 +05:30
|
|
|
</div>
|
2025-07-16 00:27:47 +05:30
|
|
|
) : (
|
|
|
|
|
/* Screenshot mode canvas */
|
|
|
|
|
<Canvas
|
|
|
|
|
onCreateRef={setCanvasReference}
|
|
|
|
|
width={dimensions.width}
|
|
|
|
|
height={dimensions.height}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2024-07-21 23:51:55 +05:30
|
|
|
</div>
|
2025-07-16 00:27:47 +05:30
|
|
|
</div>
|
2024-06-14 21:47:42 +05:30
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-14 23:17:32 +05:30
|
|
|
const drawImage = (image: string, canvas: HTMLCanvasElement): void => {
|
2024-06-14 21:47:42 +05:30
|
|
|
const ctx = canvas.getContext('2d');
|
2025-06-12 14:07:18 +05:30
|
|
|
if (!ctx) return;
|
2024-07-23 22:28:32 +05:30
|
|
|
|
2024-06-14 21:47:42 +05:30
|
|
|
const img = new Image();
|
|
|
|
|
img.onload = () => {
|
2025-06-12 14:07:18 +05:30
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
});
|
|
|
|
|
if (image.startsWith('blob:')) {
|
|
|
|
|
URL.revokeObjectURL(image);
|
|
|
|
|
}
|
2024-06-14 21:47:42 +05:30
|
|
|
};
|
2025-06-12 14:07:18 +05:30
|
|
|
img.onerror = () => {
|
|
|
|
|
console.warn('Failed to load image');
|
|
|
|
|
};
|
|
|
|
|
img.src = image;
|
2024-10-25 00:46:44 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const modalStyle = {
|
|
|
|
|
top: '50%',
|
|
|
|
|
left: '50%',
|
|
|
|
|
transform: 'translate(-50%, -50%)',
|
|
|
|
|
width: '30%',
|
|
|
|
|
backgroundColor: 'background.paper',
|
|
|
|
|
p: 4,
|
|
|
|
|
height: 'fit-content',
|
|
|
|
|
display: 'block',
|
|
|
|
|
padding: '20px',
|
2024-10-30 15:45:47 +05:30
|
|
|
};
|