feat: rm canvas logic, add in-browser loader

This commit is contained in:
Rohit Rajan
2025-11-03 16:00:46 +05:30
parent b032de4505
commit 1f99295c0f
3 changed files with 46 additions and 147 deletions

View File

@@ -13,7 +13,7 @@ import {
export const BrowserContent = () => { export const BrowserContent = () => {
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const [tabs, setTabs] = useState<string[]>(["current"]); const [tabs, setTabs] = useState<string[]>(["Loading..."]);
const [tabIndex, setTabIndex] = React.useState(0); const [tabIndex, setTabIndex] = React.useState(0);
const [showOutputData, setShowOutputData] = useState(false); const [showOutputData, setShowOutputData] = useState(false);
const { browserWidth } = useBrowserDimensionsStore(); const { browserWidth } = useBrowserDimensionsStore();
@@ -125,7 +125,7 @@ export const BrowserContent = () => {
useEffect(() => { useEffect(() => {
getCurrentTabs() getCurrentTabs()
.then((response) => { .then((response) => {
if (response) { if (response && response.length > 0) {
setTabs(response); setTabs(response);
} }
}) })

View File

@@ -1,8 +1,6 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { useSocketStore } from '../../context/socket'; import { useSocketStore } from '../../context/socket';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import Canvas from "../recorder/Canvas";
import { Highlighter } from "../recorder/Highlighter";
import { GenericModal } from '../ui/GenericModal'; import { GenericModal } from '../ui/GenericModal';
import { useActionContext } from '../../context/browserActions'; import { useActionContext } from '../../context/browserActions';
import { useBrowserSteps, TextStep, ListStep } from '../../context/browserSteps'; import { useBrowserSteps, TextStep, ListStep } from '../../context/browserSteps';
@@ -38,12 +36,6 @@ interface AttributeOption {
value: string; value: string;
} }
interface ScreencastData {
image: string;
userId: string;
viewport?: ViewportInfo | null;
}
interface ViewportInfo { interface ViewportInfo {
width: number; width: number;
height: number; height: number;
@@ -146,8 +138,6 @@ const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null):
export const BrowserWindow = () => { export const BrowserWindow = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { browserWidth, browserHeight } = useBrowserDimensionsStore(); const { browserWidth, browserHeight } = useBrowserDimensionsStore();
const [canvasRef, setCanvasReference] = useState<React.RefObject<HTMLCanvasElement> | undefined>(undefined);
const [screenShot, setScreenShot] = useState<string>("");
const [highlighterData, setHighlighterData] = useState<{ const [highlighterData, setHighlighterData] = useState<{
rect: DOMRect; rect: DOMRect;
selector: string; selector: string;
@@ -1303,17 +1293,6 @@ export const BrowserWindow = () => {
}, []); }, []);
const onMouseMove = (e: MouseEvent) => { const onMouseMove = (e: MouseEvent) => {
if (canvasRef && canvasRef.current && highlighterData) {
const canvasRect = canvasRef.current.getBoundingClientRect();
if (
e.pageX < canvasRect.left
|| e.pageX > canvasRect.right
|| e.pageY < canvasRect.top
|| e.pageY > canvasRect.bottom
) {
setHighlighterData(null);
}
}
}; };
const resetListState = useCallback(() => { const resetListState = useCallback(() => {
@@ -1331,35 +1310,15 @@ export const BrowserWindow = () => {
} }
}, [getList, resetListState]); }, [getList, resetListState]);
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);
if (data.viewport) {
setViewportInfo(data.viewport);
}
}
}
}, [user?.id]);
useEffect(() => { useEffect(() => {
if (socket) { if (socket) {
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("dom-mode-error", domModeErrorHandler); socket.on("dom-mode-error", domModeErrorHandler);
} }
if (canvasRef?.current && !isDOMMode && screenShot) {
drawImage(screenShot, canvasRef.current);
}
return () => { return () => {
if (socket) { if (socket) {
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("dom-mode-error", domModeErrorHandler); socket.off("dom-mode-error", domModeErrorHandler);
@@ -1367,10 +1326,6 @@ export const BrowserWindow = () => {
}; };
}, [ }, [
socket, socket,
screenShot,
canvasRef,
isDOMMode,
screencastHandler,
rrwebSnapshotHandler, rrwebSnapshotHandler,
domModeHandler, domModeHandler,
domModeErrorHandler, domModeErrorHandler,
@@ -1847,24 +1802,7 @@ export const BrowserWindow = () => {
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => { const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (highlighterData) { if (highlighterData) {
let shouldProcessClick = false; const shouldProcessClick = true;
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) { if (shouldProcessClick) {
const options = getAttributeOptions( const options = getAttributeOptions(
@@ -2209,17 +2147,7 @@ export const BrowserWindow = () => {
!showAttributeModal && !showAttributeModal &&
highlighterData?.rect != null && ( highlighterData?.rect != null && (
<> <>
{!isDOMMode && canvasRef?.current && ( {highlighterData && (
<Highlighter
unmodifiedRect={highlighterData?.rect}
displayedSelector={highlighterData?.selector}
width={dimensions.width}
height={dimensions.height}
canvasRect={canvasRef.current.getBoundingClientRect()}
/>
)}
{isDOMMode && highlighterData && (
<div <div
id="dom-highlight-overlay" id="dom-highlight-overlay"
style={{ style={{
@@ -2355,31 +2283,27 @@ export const BrowserWindow = () => {
borderRadius: "0px 0px 5px 5px", borderRadius: "0px 0px 5px 5px",
}} }}
> >
{isDOMMode ? ( {currentSnapshot ? (
<> <>
{currentSnapshot ? ( <DOMBrowserRenderer
<DOMBrowserRenderer width={dimensions.width}
width={dimensions.width} height={dimensions.height}
height={dimensions.height} snapshot={currentSnapshot}
snapshot={currentSnapshot} getList={getList}
getList={getList} getText={getText}
getText={getText} listSelector={listSelector}
listSelector={listSelector} cachedChildSelectors={cachedChildSelectors}
cachedChildSelectors={cachedChildSelectors} paginationMode={paginationMode}
paginationMode={paginationMode} paginationType={paginationType}
paginationType={paginationType} limitMode={limitMode}
limitMode={limitMode} isCachingChildSelectors={isCachingChildSelectors}
isCachingChildSelectors={isCachingChildSelectors} onHighlight={domHighlighterHandler}
onHighlight={domHighlighterHandler} onElementSelect={handleDOMElementSelection}
onElementSelect={handleDOMElementSelection} onShowDatePicker={handleShowDatePicker}
onShowDatePicker={handleShowDatePicker} onShowDropdown={handleShowDropdown}
onShowDropdown={handleShowDropdown} onShowTimePicker={handleShowTimePicker}
onShowTimePicker={handleShowTimePicker} onShowDateTimePicker={handleShowDateTimePicker}
onShowDateTimePicker={handleShowDateTimePicker} />
/>
) : (
<DOMLoadingIndicator />
)}
{/* --- Loading overlay --- */} {/* --- Loading overlay --- */}
{isCachingChildSelectors && ( {isCachingChildSelectors && (
@@ -2492,11 +2416,7 @@ export const BrowserWindow = () => {
)} )}
</> </>
) : ( ) : (
<Canvas <DOMLoadingIndicator />
onCreateRef={setCanvasReference}
width={dimensions.width}
height={dimensions.height}
/>
)} )}
</div> </div>
</div> </div>
@@ -2591,26 +2511,6 @@ const DOMLoadingIndicator: React.FC = () => {
); );
}; };
const drawImage = (image: string, canvas: HTMLCanvasElement): void => {
const ctx = canvas.getContext('2d');
if (!ctx) return;
const img = new Image();
img.onload = () => {
requestAnimationFrame(() => {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
});
if (image.startsWith('blob:')) {
URL.revokeObjectURL(image);
}
};
img.onerror = () => {
console.warn('Failed to load image');
};
img.src = image;
};
const modalStyle = { const modalStyle = {
top: '50%', top: '50%',
left: '50%', left: '50%',

View File

@@ -43,7 +43,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
const { setId, socket } = useSocketStore(); const { setId, socket } = useSocketStore();
const { setWidth } = useBrowserDimensionsStore(); const { setWidth } = useBrowserDimensionsStore();
const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId } = useGlobalInfoStore(); const { browserId, setBrowserId, recordingId, recordingUrl, setRecordingUrl, setRecordingName, setRetrainRobotId, setIsDOMMode } = useGlobalInfoStore();
const handleShowOutputData = useCallback(() => { const handleShowOutputData = useCallback(() => {
setShowOutputData(true); setShowOutputData(true);
@@ -77,6 +77,8 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
useEffect(() => { useEffect(() => {
let isCancelled = false; let isCancelled = false;
const handleRecording = async () => { const handleRecording = async () => {
setIsDOMMode(true);
const storedUrl = window.sessionStorage.getItem('recordingUrl'); const storedUrl = window.sessionStorage.getItem('recordingUrl');
if (storedUrl && !recordingUrl) { if (storedUrl && !recordingUrl) {
setRecordingUrl(storedUrl); setRecordingUrl(storedUrl);
@@ -137,9 +139,12 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
if (browserId === 'new-recording') { if (browserId === 'new-recording') {
socket?.emit('new-recording'); socket?.emit('new-recording');
} }
if (recordingUrl && socket) {
socket.emit('input:url', recordingUrl);
}
setIsLoaded(true); setIsLoaded(true);
} }
}, [socket, browserId, recordingName, recordingId, isLoaded]); }, [socket, browserId, recordingName, recordingId, recordingUrl, isLoaded]);
useEffect(() => { useEffect(() => {
socket?.on('loaded', handleLoaded); socket?.on('loaded', handleLoaded);
@@ -153,26 +158,20 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
<ActionProvider> <ActionProvider>
<BrowserStepsProvider> <BrowserStepsProvider>
<div id="browser-recorder"> <div id="browser-recorder">
{isLoaded ? ( <Grid container direction="row" style={{ flexGrow: 1, height: '100%' }}>
<> <Grid item xs={12} md={9} lg={9} style={{ height: '100%', overflow: 'hidden', position: 'relative' }}>
<Grid container direction="row" style={{ flexGrow: 1, height: '100%' }}> <div style={{ height: '100%', overflow: 'auto' }}>
<Grid item xs={12} md={9} lg={9} style={{ height: '100%', overflow: 'hidden', position: 'relative' }}> <BrowserContent />
<div style={{ height: '100%', overflow: 'auto' }}> <InterpretationLog isOpen={showOutputData} setIsOpen={setShowOutputData} />
<BrowserContent /> </div>
<InterpretationLog isOpen={showOutputData} setIsOpen={setShowOutputData} /> </Grid>
</div> <Grid item xs={12} md={3} lg={3} style={{ height: '100%', overflow: 'hidden' }}>
</Grid> <div className="right-side-panel" style={{ height: '100%' }}>
<Grid item xs={12} md={3} lg={3} style={{ height: '100%', overflow: 'hidden' }}> <RightSidePanel onFinishCapture={handleShowOutputData} />
<div className="right-side-panel" style={{ height: '100%' }}> <BrowserRecordingSave />
<RightSidePanel onFinishCapture={handleShowOutputData} /> </div>
<BrowserRecordingSave /> </Grid>
</div> </Grid>
</Grid>
</Grid>
</>
) : (
<Loader text={t('recording_page.loader.browser_startup', { url: recordingUrl })} />
)}
</div> </div>
</BrowserStepsProvider> </BrowserStepsProvider>
</ActionProvider> </ActionProvider>