Files
parcer/src/components/organisms/BrowserWindow.tsx

386 lines
16 KiB
TypeScript
Raw Normal View History

2024-06-14 21:47:42 +05:30
import React, { useCallback, useEffect, useState } from 'react';
import { useSocketStore } from '../../context/socket';
2024-08-21 21:57:41 +05:30
import { Button } from '@mui/material';
2024-06-14 21:47:42 +05:30
import Canvas from "../atoms/canvas";
import { useBrowserDimensionsStore } from "../../context/browserDimensions";
2024-07-23 21:58:28 +05:30
import { Highlighter } from "../atoms/Highlighter";
import { GenericModal } from '../atoms/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-07-27 02:06:05 +05:30
interface ElementInfo {
tagName: string;
hasOnlyText?: boolean;
innerText?: string;
url?: string;
imageUrl?: string;
2024-07-27 02:11:59 +05:30
}
2024-08-04 02:54:06 +05:30
interface AttributeOption {
label: string;
value: string;
}
const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): AttributeOption[] => {
if (!elementInfo) return [];
2024-08-04 02:54:06 +05:30
switch (tagName.toLowerCase()) {
case 'a':
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':
const imgOptions: AttributeOption[] = [];
if (elementInfo.innerText) {
imgOptions.push({ label: `Alt Text: ${elementInfo.innerText}`, value: 'alt' });
}
if (elementInfo.imageUrl) {
imgOptions.push({ label: `Image URL: ${elementInfo.imageUrl}`, value: 'src' });
}
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 = () => {
const [canvasRef, setCanvasReference] = useState<React.RefObject<HTMLCanvasElement> | undefined>(undefined);
const [screenShot, setScreenShot] = useState<string>("");
const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] } | 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);
const [currentListId, setCurrentListId] = useState<number | null>(null);
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
2024-06-14 21:47:42 +05:30
const { socket } = useSocketStore();
const { width, height } = useBrowserDimensionsStore();
const { getText, getList, paginationMode, paginationType, limitMode } = useActionContext();
2024-08-09 06:23:10 +05:30
const { addTextStep, addListStep } = useBrowserSteps();
2024-06-14 21:47:42 +05:30
2024-07-23 21:58:28 +05:30
const onMouseMove = (e: MouseEvent) => {
if (canvasRef && canvasRef.current && highlighterData) {
const canvasRect = canvasRef.current.getBoundingClientRect();
// 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);
}
}
};
const resetListState = useCallback(() => {
setListSelector(null);
setFields({});
setCurrentListId(null);
}, []);
useEffect(() => {
if (!getList) {
resetListState();
}
}, [getList, resetListState]);
2024-06-14 21:47:42 +05:30
const screencastHandler = useCallback((data: string) => {
setScreenShot(data);
}, [screenShot]);
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);
}
if (canvasRef?.current) {
drawImage(screenShot, canvasRef.current);
2024-07-23 21:59:20 +05:30
} else {
console.log('Canvas is not initialized');
2024-06-14 21:47:42 +05:30
}
return () => {
socket?.off("screencast", screencastHandler);
}
}, [screenShot, canvasRef, socket, screencastHandler]);
const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] }) => {
2024-09-06 11:06:44 +05:30
if (getList === true) {
if (listSelector) {
socket?.emit('listSelector', { selector: listSelector });
if (limitMode) {
setHighlighterData(null);
} else if (paginationMode) {
2024-09-09 03:19:42 +05:30
// only set highlighterData if type is not empty, 'none', 'scrollDown', or 'scrollUp'
if (paginationType !== '' && !['none', 'scrollDown', 'scrollUp'].includes(paginationType)) {
setHighlighterData(data);
} else {
setHighlighterData(null);
}
2024-09-06 11:06:44 +05:30
} else if (data.childSelectors && data.childSelectors.includes(data.selector)) {
2024-09-09 03:19:42 +05:30
// highlight only valid child elements within the listSelector
2024-09-06 11:06:44 +05:30
setHighlighterData(data);
} else {
2024-09-09 03:19:42 +05:30
// if !valid child in normal mode, clear the highlighter
2024-09-06 11:06:44 +05:30
setHighlighterData(null);
}
2024-09-06 11:04:59 +05:30
} else {
2024-09-09 03:19:42 +05:30
// set highlighterData for the initial listSelector selection
setHighlighterData(data);
2024-09-03 10:20:07 +05:30
}
} else {
2024-09-09 03:19:42 +05:30
// for non-list steps
setHighlighterData(data);
2024-08-08 06:29:59 +05:30
}
}, [highlighterData, getList, socket, listSelector, paginationMode, paginationType]);
2024-09-06 11:04:59 +05:30
2024-06-14 21:47:42 +05:30
2024-06-14 23:17:32 +05:30
useEffect(() => {
2024-07-23 21:58:28 +05:30
document.addEventListener('mousemove', onMouseMove, false);
2024-06-14 21:47:42 +05:30
if (socket) {
socket.on("highlighter", highlighterHandler);
}
return () => {
2024-07-23 21:58:28 +05:30
document.removeEventListener('mousemove', onMouseMove);
2024-06-14 21:47:42 +05:30
socket?.off("highlighter", highlighterHandler);
};
2024-07-23 21:58:28 +05:30
}, [socket, onMouseMove]);
2024-06-25 22:39:29 +05:30
2024-08-09 08:54:26 +05:30
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (highlighterData && canvasRef?.current) {
const canvasRect = canvasRef.current.getBoundingClientRect();
const clickX = e.clientX - canvasRect.left;
const clickY = e.clientY - canvasRect.top;
2024-08-21 23:38:33 +05:30
const highlightRect = highlighterData.rect;
if (
clickX >= highlightRect.left &&
clickX <= highlightRect.right &&
clickY >= highlightRect.top &&
clickY <= highlightRect.bottom
) {
const options = getAttributeOptions(highlighterData.elementInfo?.tagName || '', highlighterData.elementInfo);
2024-08-21 23:38:33 +05:30
2024-08-09 00:19:34 +05:30
if (getText === true) {
if (options.length === 1) {
// Directly use the available attribute if only one option is present
const attribute = options[0].value;
const data = attribute === 'href' ? highlighterData.elementInfo?.url || '' :
2024-08-21 23:38:33 +05:30
attribute === 'src' ? highlighterData.elementInfo?.imageUrl || '' :
highlighterData.elementInfo?.innerText || '';
addTextStep('', data, {
selector: highlighterData.selector,
tag: highlighterData.elementInfo?.tagName,
attribute
});
} else {
// Show the modal if there are multiple options
2024-08-06 02:17:28 +05:30
setAttributeOptions(options);
setSelectedElement({
selector: highlighterData.selector,
info: highlighterData.elementInfo
});
setShowAttributeModal(true);
}
2024-08-09 09:25:43 +05:30
}
2024-08-21 23:38:33 +05:30
if (paginationMode && getList) {
// Only allow selection in pagination mode if type is not empty, 'scrollDown', or 'scrollUp'
2024-09-07 00:36:39 +05:30
if (paginationType !== '' && paginationType !== 'scrollDown' && paginationType !== 'scrollUp' && paginationType !== 'none') {
setPaginationSelector(highlighterData.selector);
addListStep(listSelector!, fields, currentListId || 0, { type: paginationType, selector: highlighterData.selector });
}
2024-09-06 22:07:00 +05:30
return;
2024-09-06 11:04:22 +05:30
}
2024-09-06 11:06:44 +05:30
2024-08-09 06:23:10 +05:30
if (getList === true && !listSelector) {
setListSelector(highlighterData.selector);
setCurrentListId(Date.now());
setFields({});
} 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 || '';
2024-09-06 11:04:22 +05:30
// Add fields to the list
if (options.length === 1) {
const attribute = options[0].value;
const newField: TextStep = {
id: Date.now(),
type: 'text',
label: `Label ${Object.keys(fields).length + 1}`,
data: data,
selectorObj: {
selector: highlighterData.selector,
tag: highlighterData.elementInfo?.tagName,
attribute
}
};
setFields(prevFields => {
const updatedFields = {
...prevFields,
2024-09-21 15:56:51 +05:30
[newField.id]: newField
};
2024-09-21 15:09:03 +05:30
return updatedFields;
});
2024-09-06 11:04:22 +05:30
2024-09-21 15:56:51 +05:30
if (listSelector) {
addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId, { type: '', selector: paginationSelector });
}
} else {
setAttributeOptions(options);
setSelectedElement({
selector: highlighterData.selector,
info: highlighterData.elementInfo
});
setShowAttributeModal(true);
2024-08-09 06:23:10 +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-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,
attribute: attribute
});
}
2024-09-07 22:20:53 +05:30
if (getList === true && listSelector && currentListId) {
const newField: TextStep = {
id: Date.now(),
type: 'text',
label: `Label ${Object.keys(fields).length + 1}`,
data: data,
selectorObj: {
selector: selectedElement.selector,
tag: selectedElement.info?.tagName,
attribute: attribute
}
};
setFields(prevFields => {
const updatedFields = {
...prevFields,
2024-09-21 15:56:51 +05:30
[newField.id]: newField
};
2024-09-21 15:09:03 +05:30
return updatedFields;
});
2024-09-21 15:56:51 +05:30
if (listSelector) {
addListStep(listSelector, { ...fields, [newField.id]: newField }, currentListId, { type: '', selector: paginationSelector });
}
}
2024-08-06 02:16:53 +05:30
}
}
2024-08-04 03:18:41 +05:30
setShowAttributeModal(false);
};
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-09-06 22:07:00 +05:30
2024-06-14 21:47:42 +05:30
return (
<div onClick={handleClick}>
{
2024-08-21 22:32:20 +05:30
getText === true || getList === true ? (
<GenericModal
isOpen={showAttributeModal}
onClose={() => { }}
canBeClosed={false}
>
<div>
<h2>Select Attribute</h2>
2024-08-21 22:42:54 +05:30
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', marginTop: '30px' }}>
2024-08-21 22:32:20 +05:30
{attributeOptions.map((option) => (
<Button
variant="outlined"
size="medium"
key={option.value}
onClick={() => handleAttributeSelection(option.value)}
style={{
justifyContent: 'flex-start',
2024-08-21 22:37:32 +05:30
maxWidth: '80%',
2024-08-21 22:32:20 +05:30
overflow: 'hidden',
padding: '5px 10px',
}}
>
<span style={{
display: 'block',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%'
}}>
{option.label}
</span>
</Button>
))}
</div>
</div>
</GenericModal>
) : null
}
2024-08-08 00:39:28 +05:30
{((getText === true || getList === true) && !showAttributeModal && highlighterData?.rect != null && highlighterData?.rect.top != null) && canvasRef?.current ?
2024-07-23 21:58:28 +05:30
<Highlighter
unmodifiedRect={highlighterData?.rect}
displayedSelector={highlighterData?.selector}
width={width}
height={height}
canvasRect={canvasRef.current.getBoundingClientRect()}
/>
: null}
2024-06-14 23:17:32 +05:30
<Canvas
onCreateRef={setCanvasReference}
width={width}
height={height}
2024-06-14 21:47:42 +05:30
/>
2024-07-21 23:51:55 +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');
2024-06-14 21:47:42 +05:30
const img = new Image();
2024-06-14 21:47:42 +05:30
img.src = image;
img.onload = () => {
URL.revokeObjectURL(img.src);
2024-07-14 00:46:39 +05:30
ctx?.drawImage(img, 0, 0, 1280, 720);
2024-06-14 21:47:42 +05:30
};
2024-07-14 00:46:39 +05:30
};