@@ -90,7 +90,12 @@ export class RemoteBrowser {
|
||||
*/
|
||||
public initialize = async (options: RemoteBrowserOptions): Promise<void> => {
|
||||
this.browser = <Browser>(await options.browser.launch(options.launchOptions));
|
||||
const context = await this.browser.newContext();
|
||||
const context = await this.browser.newContext(
|
||||
{
|
||||
viewport: { height: 400, width: 900 },
|
||||
// recordVideo: { dir: 'videos/' }
|
||||
}
|
||||
);
|
||||
this.currentPage = await context.newPage();
|
||||
const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch);
|
||||
await blocker.enableBlockingInPage(this.currentPage);
|
||||
@@ -275,7 +280,7 @@ export class RemoteBrowser {
|
||||
if (page) {
|
||||
await this.stopScreencast();
|
||||
this.currentPage = page;
|
||||
await this.currentPage.setViewportSize({ height: 500, width: 1280 })
|
||||
await this.currentPage.setViewportSize({ height: 400, width: 900 })
|
||||
this.client = await this.currentPage.context().newCDPSession(this.currentPage);
|
||||
this.socket.emit('urlChanged', this.currentPage.url());
|
||||
await this.makeAndEmitScreenshot();
|
||||
|
||||
64
src/App.tsx
64
src/App.tsx
@@ -1,15 +1,69 @@
|
||||
import React from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||
import { GlobalInfoProvider } from "./context/globalInfo";
|
||||
import { PageWrapper } from "./pages/PageWrappper";
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#ff00c3",
|
||||
contrastText: "#ffffff",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
// Default styles for all buttons (optional)
|
||||
textTransform: "none",
|
||||
},
|
||||
containedPrimary: {
|
||||
// Styles for 'contained' variant with 'primary' color
|
||||
'&:hover': {
|
||||
backgroundColor: "#ff66d9",
|
||||
},
|
||||
},
|
||||
outlined: {
|
||||
// Apply white background for all 'outlined' variant buttons
|
||||
backgroundColor: "#ffffff",
|
||||
'&:hover': {
|
||||
backgroundColor: "#f0f0f0", // Optional lighter background on hover
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
color: "#ff00c3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
color: "#ff66d9",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<GlobalInfoProvider>
|
||||
<Routes>
|
||||
<Route path="/*" element={<PageWrapper />} />
|
||||
</Routes>
|
||||
</GlobalInfoProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalInfoProvider>
|
||||
<Routes>
|
||||
<Route path="/*" element={<PageWrapper />} />
|
||||
</Routes>
|
||||
</GlobalInfoProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ const defaultModalStyle = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
width: 1000,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
height: '30%',
|
||||
height: '50%',
|
||||
display: 'block',
|
||||
overflow: 'scroll',
|
||||
padding: '5px 25px 10px 25px',
|
||||
|
||||
@@ -1,86 +1,73 @@
|
||||
import styled from "styled-components";
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
export const Loader = () => {
|
||||
return (
|
||||
<Stack direction="column" sx={{ margin: "30px 0px 291px 0px" }}>
|
||||
<StyledLoader />
|
||||
<StyledParagraph>
|
||||
Loading...
|
||||
</StyledParagraph>
|
||||
</Stack>
|
||||
);
|
||||
interface LoaderProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const Loader: React.FC<LoaderProps> = ({ text }) => {
|
||||
return (
|
||||
<Stack direction="column" sx={{ margin: "30px 0px", alignItems: "center" }}>
|
||||
<DotsContainer>
|
||||
<Dot />
|
||||
<Dot />
|
||||
<Dot />
|
||||
<Dot />
|
||||
</DotsContainer>
|
||||
<StyledParagraph>{text}</StyledParagraph>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledParagraph = styled.p`
|
||||
font-size: x-large;
|
||||
font-size: large;
|
||||
font-family: inherit;
|
||||
color: #1976d2;
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
`;
|
||||
|
||||
const StyledLoader = styled.div`
|
||||
const DotsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px; /* Space between dots */
|
||||
`;
|
||||
|
||||
const Dot = styled.div`
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background-color: #ff00c3;
|
||||
border-radius: 50%;
|
||||
color: #1976d2;
|
||||
font-size: 11px;
|
||||
text-indent: -99999em;
|
||||
margin: 55px auto;
|
||||
position: relative;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
box-shadow: inset 0 0 0 1em;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
width: 5.2em;
|
||||
height: 10.2em;
|
||||
background: #ffffff;
|
||||
border-radius: 10.2em 0 0 10.2em;
|
||||
top: -0.1em;
|
||||
left: -0.1em;
|
||||
-webkit-transform-origin: 5.1em 5.1em;
|
||||
transform-origin: 5.1em 5.1em;
|
||||
-webkit-animation: load2 2s infinite ease 1.5s;
|
||||
animation: load2 2s infinite ease 1.5s;
|
||||
animation: intensePulse 1.2s infinite ease-in-out both, bounceAndPulse 1.5s infinite ease-in-out;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
width: 5.2em;
|
||||
height: 10.2em;
|
||||
background: #ffffff;
|
||||
border-radius: 0 10.2em 10.2em 0;
|
||||
top: -0.1em;
|
||||
left: 4.9em;
|
||||
-webkit-transform-origin: 0.1em 5.1em;
|
||||
transform-origin: 0.1em 5.1em;
|
||||
-webkit-animation: load2 2s infinite ease;
|
||||
animation: load2 2s infinite ease;
|
||||
&:nth-child(2) {
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
@-webkit-keyframes load2 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
&:nth-child(3) {
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
@keyframes bounceAndPulse {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
50% {
|
||||
transform: translateY(-10px) scale(1.3);
|
||||
}
|
||||
}
|
||||
@keyframes load2 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
|
||||
@keyframes intensePulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgba(255, 0, 195, 0.7);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
50% {
|
||||
box-shadow: 0 0 15px 10px rgba(255, 0, 195, 0.3);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const NavBarButton = styled.button<{ disabled: boolean }>`
|
||||
margin-left: 5px;
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
@@ -26,7 +26,7 @@ export const NavBarButton = styled.button<{ disabled: boolean }>`
|
||||
export const UrlFormButton = styled.button`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
right: 10px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
@@ -45,34 +45,35 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => {
|
||||
}, [getText, getList]);
|
||||
|
||||
const onMouseEvent = useCallback((event: MouseEvent) => {
|
||||
if (socket) {
|
||||
const coordinates = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
}
|
||||
if (socket && canvasRef.current) {
|
||||
// Get the canvas bounding rectangle
|
||||
const rect = canvasRef.current.getBoundingClientRect();
|
||||
const clickCoordinates = {
|
||||
x: event.clientX - rect.left, // Use relative x coordinate
|
||||
y: event.clientY - rect.top, // Use relative y coordinate
|
||||
};
|
||||
|
||||
switch (event.type) {
|
||||
case 'mousedown':
|
||||
const clickCoordinates = getMappedCoordinates(event, canvasRef.current, width, height);
|
||||
if (getTextRef.current === true) {
|
||||
console.log('Capturing Text...');
|
||||
} else if (getListRef.current === true){
|
||||
} else if (getListRef.current === true) {
|
||||
console.log('Capturing List...');
|
||||
}else {
|
||||
} else {
|
||||
socket.emit('input:mousedown', clickCoordinates);
|
||||
}
|
||||
notifyLastAction('click');
|
||||
break;
|
||||
case 'mousemove':
|
||||
const coordinates = getMappedCoordinates(event, canvasRef.current, width, height);
|
||||
if (lastMousePosition.current.x !== coordinates.x ||
|
||||
lastMousePosition.current.y !== coordinates.y) {
|
||||
if (lastMousePosition.current.x !== clickCoordinates.x ||
|
||||
lastMousePosition.current.y !== clickCoordinates.y) {
|
||||
lastMousePosition.current = {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x: clickCoordinates.x,
|
||||
y: clickCoordinates.y,
|
||||
};
|
||||
socket.emit('input:mousemove', {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
x: clickCoordinates.x,
|
||||
y: clickCoordinates.y,
|
||||
});
|
||||
notifyLastAction('move');
|
||||
}
|
||||
@@ -140,8 +141,8 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => {
|
||||
<canvas
|
||||
tabIndex={0}
|
||||
ref={canvasRef}
|
||||
height={720}
|
||||
width={1280}
|
||||
height={400}
|
||||
width={900}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
121
src/components/molecules/ActionDescriptionBox.tsx
Normal file
121
src/components/molecules/ActionDescriptionBox.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { PaginationType, useActionContext, LimitType } from '../../context/browserActions';
|
||||
import { Typography, FormControlLabel, Checkbox, Box } from '@mui/material';
|
||||
|
||||
const CustomBoxContainer = styled.div`
|
||||
position: relative;
|
||||
min-width: 250px;
|
||||
width: auto;
|
||||
min-height: 100px;
|
||||
height: auto;
|
||||
border: 2px solid #ff00c3;
|
||||
background-color: white;
|
||||
margin: 30px 15px;
|
||||
`;
|
||||
|
||||
const Triangle = styled.div`
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 20px solid transparent;
|
||||
border-right: 20px solid transparent;
|
||||
border-bottom: 20px solid #ff00c3;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const ActionDescriptionBox = () => {
|
||||
const { getText, getScreenshot, getList, captureStage } = useActionContext();
|
||||
|
||||
const messages = [
|
||||
{
|
||||
stage: 'initial' as const,
|
||||
text: 'Select the list you want to extract along with the texts inside it',
|
||||
},
|
||||
{
|
||||
stage: 'pagination' as const,
|
||||
text: 'Select how the robot can capture the rest of the list',
|
||||
},
|
||||
{
|
||||
stage: 'limit' as const,
|
||||
text: 'Choose the number of items to extract',
|
||||
},
|
||||
{
|
||||
stage: 'complete' as const,
|
||||
text: 'Capture is complete',
|
||||
},
|
||||
];
|
||||
|
||||
const renderActionDescription = () => {
|
||||
if (getText) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Capture Text</Typography>
|
||||
<Typography variant="body2" gutterBottom>Hover over the texts you want to extract and click to select them</Typography>
|
||||
</>
|
||||
)
|
||||
} else if (getScreenshot) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Capture Screenshot</Typography>
|
||||
<Typography variant="body2" gutterBottom>Capture a partial or full page screenshot of the current page. </Typography>
|
||||
</>
|
||||
)
|
||||
} else if (getList) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Capture List</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Hover over the list you want to extract. Once selected, you can hover over all texts inside the list you selected. Click to select them.
|
||||
</Typography>
|
||||
<Box>
|
||||
{messages.map(({ stage, text }, index) => (
|
||||
<FormControlLabel
|
||||
key={stage}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
(stage === 'initial' && captureStage !== '') || // Checked if captureStage is at least 'initial'
|
||||
(stage === 'pagination' && (captureStage === 'pagination' || captureStage === 'limit' || captureStage === 'complete')) || // captureStage is at least 'pagination'
|
||||
(stage === 'limit' && (captureStage === 'limit' || captureStage === 'complete')) || // captureStage is at least 'limit'
|
||||
(stage === 'complete' && captureStage === 'complete') // captureStage is 'complete'
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2" gutterBottom>{text}</Typography>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>What data do you want to extract?</Typography>
|
||||
<Typography variant="body2" gutterBottom>A robot is designed to perform one action at a time. You can choose any of the options below.</Typography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomBoxContainer>
|
||||
<Triangle />
|
||||
<Content>
|
||||
{renderActionDescription()}
|
||||
</Content>
|
||||
</CustomBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionDescriptionBox;
|
||||
@@ -16,7 +16,7 @@ import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
|
||||
const StyledNavBar = styled.div<{ browserWidth: number }>`
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
padding: 12px 0px;
|
||||
background-color: #f6f6f6;
|
||||
width: ${({ browserWidth }) => browserWidth}px;
|
||||
`;
|
||||
|
||||
49
src/components/molecules/BrowserRecordingSave.tsx
Normal file
49
src/components/molecules/BrowserRecordingSave.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import { Paper, Grid, IconButton, Button, Box } from '@mui/material';
|
||||
import { SaveRecording } from "./SaveRecording";
|
||||
import { Circle, Add, Logout, Clear } from "@mui/icons-material";
|
||||
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
import { stopRecording } from "../../api/recording";
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
|
||||
const BrowserRecordingSave = () => {
|
||||
const { recordingName, browserId, setBrowserId, notify } = useGlobalInfoStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goToMainMenu = async () => {
|
||||
if (browserId) {
|
||||
await stopRecording(browserId);
|
||||
notify('warning', 'Current Recording was terminated');
|
||||
setBrowserId(null);
|
||||
}
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={3} lg={3}>
|
||||
<div style={{
|
||||
marginTop: '10px',
|
||||
marginLeft: '10px',
|
||||
color: 'white',
|
||||
position: 'absolute',
|
||||
background: '#ff00c3',
|
||||
border: 'none',
|
||||
padding: '7.5px',
|
||||
width: 'calc(100% - 20px)', // Ensure it takes full width but with padding
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<Button onClick={goToMainMenu} variant="outlined" sx={{ marginLeft: "20px" }} size="small" color="error">
|
||||
Discard
|
||||
</Button>
|
||||
<SaveRecording fileName={recordingName} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default BrowserRecordingSave
|
||||
@@ -46,6 +46,9 @@ export const BrowserTabs = (
|
||||
<Tab
|
||||
key={`tab-${index}`}
|
||||
id={`tab-${index}`}
|
||||
sx={{
|
||||
background: 'white',
|
||||
}}
|
||||
icon={<CloseButton closeTab={() => {
|
||||
tabWasClosed = true;
|
||||
handleCloseTab(index);
|
||||
@@ -64,7 +67,7 @@ export const BrowserTabs = (
|
||||
})}
|
||||
</Tabs>
|
||||
</Box>
|
||||
<AddButton handleClick={handleAddNewTab} />
|
||||
<AddButton handleClick={handleAddNewTab} style={{ background: 'white' }} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ interface CollapsibleRowProps {
|
||||
abortRunHandler: () => void;
|
||||
runningRecordingName: string;
|
||||
}
|
||||
export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRunHandler,runningRecordingName }: CollapsibleRowProps) => {
|
||||
export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRunHandler, runningRecordingName }: CollapsibleRowProps) => {
|
||||
const [open, setOpen] = useState(isOpen);
|
||||
|
||||
const logEndRef = useRef<HTMLDivElement|null>(null);
|
||||
const logEndRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const scrollToLogBottom = () => {
|
||||
if (logEndRef.current) {
|
||||
@@ -52,7 +52,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun
|
||||
</TableCell>
|
||||
{columns.map((column) => {
|
||||
// @ts-ignore
|
||||
const value : any = row[column.id];
|
||||
const value: any = row[column.id];
|
||||
if (value !== undefined) {
|
||||
return (
|
||||
<TableCell key={column.id} align={column.align}>
|
||||
@@ -64,14 +64,14 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun
|
||||
case 'delete':
|
||||
return (
|
||||
<TableCell key={column.id} align={column.align}>
|
||||
<IconButton aria-label="add" size= "small" onClick={() => {
|
||||
<IconButton aria-label="add" size="small" onClick={() => {
|
||||
deleteRunFromStorage(`${row.runId}`).then((result: boolean) => {
|
||||
if (result) {
|
||||
handleDelete();
|
||||
}
|
||||
})
|
||||
}} sx={{'&:hover': { color: '#1976d2', backgroundColor: 'transparent' }}}>
|
||||
<DeleteForever/>
|
||||
}}>
|
||||
<DeleteForever />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
);
|
||||
@@ -85,7 +85,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<RunContent row={row} abortRunHandler={handleAbort} currentLog={currentLog}
|
||||
logEndRef={logEndRef} interpretationInProgress={runningRecordingName === row.name} />
|
||||
logEndRef={logEndRef} interpretationInProgress={runningRecordingName === row.name} />
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
|
||||
import { PauseCircle, PlayCircle, StopCircle } from "@mui/icons-material";
|
||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import { PlayCircle } from "@mui/icons-material";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { interpretCurrentRecording, stopCurrentInterpretation } from "../../api/recording";
|
||||
import { useSocketStore } from "../../context/socket";
|
||||
@@ -20,10 +20,10 @@ interface InterpretationInfo {
|
||||
const interpretationInfo: InterpretationInfo = {
|
||||
running: false,
|
||||
isPaused: false,
|
||||
}
|
||||
};
|
||||
|
||||
export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsProps) => {
|
||||
const [info, setInfo] = React.useState<InterpretationInfo>(interpretationInfo);
|
||||
const [info, setInfo] = useState<InterpretationInfo>(interpretationInfo);
|
||||
const [decisionModal, setDecisionModal] = useState<{
|
||||
pair: WhereWhatPair | null,
|
||||
actionType: string,
|
||||
@@ -44,52 +44,47 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP
|
||||
|
||||
const breakpointHitHandler = useCallback(() => {
|
||||
setInfo({ running: false, isPaused: true });
|
||||
notify('warning', 'Please restart the interpretation, after updating the recording');
|
||||
notify('warning', 'Please restart the interpretation after updating the recording');
|
||||
enableStepping(true);
|
||||
}, [info, enableStepping]);
|
||||
}, [enableStepping]);
|
||||
|
||||
const decisionHandler = useCallback(
|
||||
({ pair, actionType, lastData }
|
||||
: { pair: WhereWhatPair | null, actionType: string, lastData: { selector: string, action: string, tagName: string, innerText: string } }) => {
|
||||
({ pair, actionType, lastData }: { pair: WhereWhatPair | null, actionType: string, lastData: { selector: string, action: string, tagName: string, innerText: string } }) => {
|
||||
const { selector, action, tagName, innerText } = lastData;
|
||||
setDecisionModal((prevState) => {
|
||||
return {
|
||||
pair,
|
||||
actionType,
|
||||
selector,
|
||||
action,
|
||||
tagName,
|
||||
innerText,
|
||||
open: true,
|
||||
}
|
||||
})
|
||||
}, [decisionModal]);
|
||||
setDecisionModal((prevState) => ({
|
||||
pair,
|
||||
actionType,
|
||||
selector,
|
||||
action,
|
||||
tagName,
|
||||
innerText,
|
||||
open: true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleDecision = (decision: boolean) => {
|
||||
const { pair, actionType } = decisionModal;
|
||||
socket?.emit('decision', { pair, actionType, decision });
|
||||
setDecisionModal({ pair: null, actionType: '', selector: '', action: '', tagName: '', innerText: '', open: false });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDescription = () => {
|
||||
switch (decisionModal.actionType) {
|
||||
case 'customAction':
|
||||
return (
|
||||
<React.Fragment>
|
||||
if (decisionModal.actionType === 'customAction') {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
Do you want to use your previous selection as a condition for performing this action?
|
||||
</Typography>
|
||||
<Box style={{ marginTop: '4px' }}>
|
||||
<Typography>
|
||||
Do you want to use your previous selection as a condition for performing this action?
|
||||
Your previous action was: <b>{decisionModal.action}</b>, on an element with text <b>{decisionModal.innerText}</b>
|
||||
</Typography>
|
||||
<Box style={{ marginTop: '4px' }}>
|
||||
<Typography>Your previous action was:
|
||||
<b>{decisionModal.action.charAt(0).toUpperCase() + decisionModal.action.slice(1)} </b>,
|
||||
on an element with text
|
||||
<b>{decisionModal.innerText} </b>
|
||||
</Typography>
|
||||
</Box>
|
||||
</React.Fragment>);
|
||||
default: return null;
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
@@ -101,15 +96,11 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP
|
||||
socket?.off('finished', finishedHandler);
|
||||
socket?.off('breakpointHit', breakpointHitHandler);
|
||||
socket?.off('decision', decisionHandler);
|
||||
}
|
||||
};
|
||||
}, [socket, finishedHandler, breakpointHitHandler]);
|
||||
|
||||
const handlePlay = async () => {
|
||||
if (info.isPaused) {
|
||||
socket?.emit("resume");
|
||||
setInfo({ running: true, isPaused: false });
|
||||
enableStepping(false);
|
||||
} else {
|
||||
if (!info.running) {
|
||||
setInfo({ ...info, running: true });
|
||||
const finished = await interpretCurrentRecording();
|
||||
setInfo({ ...info, running: false });
|
||||
@@ -121,40 +112,39 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP
|
||||
}
|
||||
};
|
||||
|
||||
// pause and stop logic (do not delete - we wil bring this back!)
|
||||
/*
|
||||
const handlePause = async () => {
|
||||
if (info.running) {
|
||||
socket?.emit("pause");
|
||||
setInfo({ running: false, isPaused: true });
|
||||
notify('warning', 'Please restart the interpretation after updating the recording');
|
||||
enableStepping(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStop = async () => {
|
||||
setInfo({ running: false, isPaused: false });
|
||||
enableStepping(false);
|
||||
await stopCurrentInterpretation();
|
||||
};
|
||||
|
||||
const handlePause = async () => {
|
||||
if (info.running) {
|
||||
socket?.emit("pause");
|
||||
setInfo({ running: false, isPaused: true });
|
||||
notify('warning', 'Please restart the interpretation, after updating the recording');
|
||||
enableStepping(true);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing={3}
|
||||
sx={{ marginTop: '10px', marginBottom: '5px', justifyContent: 'space-evenly', }} >
|
||||
<IconButton disabled={!info.running} sx={{ display: 'grid', '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}
|
||||
aria-label="pause" size="small" title="Pause" onClick={handlePause}>
|
||||
<PauseCircle sx={{ fontSize: 30, justifySelf: 'center' }} />
|
||||
Pause
|
||||
</IconButton>
|
||||
<IconButton disabled={info.running} sx={{ display: 'grid', '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}
|
||||
aria-label="play" size="small" title="Play" onClick={handlePlay}>
|
||||
<PlayCircle sx={{ fontSize: 30, justifySelf: 'center' }} />
|
||||
{info.isPaused ? 'Resume' : 'Start'}
|
||||
</IconButton>
|
||||
<IconButton disabled={!info.running && !info.isPaused} sx={{ display: 'grid', '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}
|
||||
aria-label="stop" size="small" title="Stop" onClick={handleStop}>
|
||||
<StopCircle sx={{ fontSize: 30, justifySelf: 'center' }} />
|
||||
Stop
|
||||
</IconButton>
|
||||
<GenericModal onClose={() => { }} isOpen={decisionModal.open} canBeClosed={false}
|
||||
<Stack direction="row" spacing={3} sx={{ marginTop: '30px', marginBottom: '5px', justifyContent: 'center' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handlePlay}
|
||||
disabled={info.running}
|
||||
sx={{ display: 'grid' }}
|
||||
>
|
||||
{info.running ? 'Extracting data...almost there!' : 'Get Preview of Output Data'}
|
||||
</Button>
|
||||
<GenericModal
|
||||
onClose={() => { }}
|
||||
isOpen={decisionModal.open}
|
||||
canBeClosed={false}
|
||||
modalStyle={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
@@ -168,15 +158,14 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP
|
||||
display: 'block',
|
||||
overflow: 'scroll',
|
||||
padding: '5px 25px 10px 25px',
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '15px' }}>
|
||||
<HelpIcon />
|
||||
{
|
||||
handleDescription()
|
||||
}
|
||||
{handleDescription()}
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={() => handleDecision(true)} color='success'>yes</Button>
|
||||
<Button onClick={() => handleDecision(false)} color='error'>no</Button>
|
||||
<Button onClick={() => handleDecision(true)} color='success'>Yes</Button>
|
||||
<Button onClick={() => handleDecision(false)} color='error'>No</Button>
|
||||
</div>
|
||||
</div>
|
||||
</GenericModal>
|
||||
|
||||
@@ -3,7 +3,7 @@ import SwipeableDrawer from '@mui/material/SwipeableDrawer';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import RadioGroup from '@mui/material/RadioGroup';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { Button, TextField, Grid } from '@mui/material';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormLabel from '@mui/material/FormLabel';
|
||||
@@ -19,6 +19,8 @@ import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import { SidePanelHeader } from './SidePanelHeader';
|
||||
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
|
||||
interface InterpretationLogProps {
|
||||
isOpen: boolean;
|
||||
@@ -27,7 +29,6 @@ interface InterpretationLogProps {
|
||||
|
||||
export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, setIsOpen }) => {
|
||||
const [log, setLog] = useState<string>('');
|
||||
const [selectedOption, setSelectedOption] = useState<string>('10');
|
||||
const [customValue, setCustomValue] = useState('');
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
|
||||
@@ -35,6 +36,7 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
|
||||
|
||||
const { width } = useBrowserDimensionsStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { currentWorkflowActionsState } = useGlobalInfoStore();
|
||||
|
||||
const toggleDrawer = (newOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||
if (
|
||||
@@ -83,10 +85,6 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
|
||||
scrollLogToBottom();
|
||||
}, [log, scrollLogToBottom]);
|
||||
|
||||
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedOption(event.target.value);
|
||||
};
|
||||
|
||||
const handleCustomValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCustomValue(event.target.value);
|
||||
};
|
||||
@@ -105,107 +103,108 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
|
||||
// Extract columns dynamically from the first item of tableData
|
||||
const columns = tableData.length > 0 ? Object.keys(tableData[0]) : [];
|
||||
|
||||
const { hasScrapeListAction, hasScreenshotAction, hasScrapeSchemaAction } = currentWorkflowActionsState
|
||||
|
||||
useEffect(() => {
|
||||
if (hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [hasScrapeListAction, hasScrapeSchemaAction, hasScreenshotAction, setIsOpen]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={toggleDrawer(true)}
|
||||
style={{
|
||||
color: 'white',
|
||||
background: '#3f4853',
|
||||
border: 'none',
|
||||
padding: '10px 20px',
|
||||
width: 1280,
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
Interpretation Log
|
||||
</button>
|
||||
<SwipeableDrawer
|
||||
anchor="bottom"
|
||||
open={isOpen}
|
||||
onClose={toggleDrawer(false)}
|
||||
onOpen={toggleDrawer(true)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
background: 'white',
|
||||
color: 'black',
|
||||
padding: '10px',
|
||||
height: 720,
|
||||
width: width - 10,
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<StorageIcon /> Output Data Preview
|
||||
</Typography>
|
||||
<div style={{
|
||||
height: '50vh',
|
||||
overflow: 'none',
|
||||
padding: '10px',
|
||||
}}>
|
||||
{/* <Highlight className="javascript">
|
||||
{log}
|
||||
</Highlight> */}
|
||||
{tableData.length > 0 && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} stickyHeader aria-label="output data table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column}>{column}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tableData.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column}>{row[column]}</TableCell>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={9} lg={9}>
|
||||
<Button
|
||||
onClick={toggleDrawer(true)}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{
|
||||
marginTop: '10px',
|
||||
color: 'white',
|
||||
position: 'absolute',
|
||||
background: '#ff00c3',
|
||||
border: 'none',
|
||||
padding: '10px 20px',
|
||||
width: '900px',
|
||||
overflow: 'hidden',
|
||||
textAlign: 'left',
|
||||
justifyContent: 'flex-start',
|
||||
'&:hover': {
|
||||
backgroundColor: '#ff00c3',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Output Data Preview
|
||||
</Button>
|
||||
<SwipeableDrawer
|
||||
anchor="bottom"
|
||||
open={isOpen}
|
||||
onClose={toggleDrawer(false)}
|
||||
onOpen={toggleDrawer(true)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
background: 'white',
|
||||
color: 'black',
|
||||
padding: '10px',
|
||||
height: 500,
|
||||
width: width - 10,
|
||||
display: 'flex',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<StorageIcon /> Output Data Preview
|
||||
</Typography>
|
||||
<div
|
||||
style={{
|
||||
height: '50vh',
|
||||
overflow: 'none',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
{tableData.length > 0 ? (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} stickyHeader aria-label="output data table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column}>{column}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tableData.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column}>{row[column]}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '200px' }}>
|
||||
<FormControl>
|
||||
<FormLabel>
|
||||
<h4>What is the maximum number of rows you want to extract?</h4>
|
||||
</FormLabel>
|
||||
<RadioGroup row value={selectedOption} onChange={handleRadioChange} sx={{ width: '500px' }}>
|
||||
<FormControlLabel value="10" control={<Radio />} label="10" />
|
||||
<FormControlLabel value="100" control={<Radio />} label="100" />
|
||||
<FormControlLabel value="custom" control={<Radio />} label="Custom" />
|
||||
{selectedOption === 'custom' && (
|
||||
<TextField
|
||||
type="number"
|
||||
value={customValue}
|
||||
onChange={handleCustomValueChange}
|
||||
placeholder="Enter number"
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
marginTop: '-3px',
|
||||
'& input': {
|
||||
padding: '10px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<div style={{ paddingBottom: '40px' }}>
|
||||
<h4>How can we find the next item?</h4>
|
||||
<p>Select and review the pagination setting this webpage is using</p>
|
||||
<Button variant="outlined">
|
||||
Select Pagination Setting
|
||||
</Button>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<Grid container justifyContent="center" alignItems="center" style={{ height: '100%' }}>
|
||||
<Grid item>
|
||||
{hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction ? (
|
||||
<>
|
||||
<Typography variant="h6" gutterBottom align="left">
|
||||
You've successfully trained the robot to perform actions! Click on the button below to get a preview of the data your robot will extract.
|
||||
</Typography>
|
||||
<SidePanelHeader />
|
||||
</>
|
||||
) : (
|
||||
<Typography variant="h6" gutterBottom align="left">
|
||||
It looks like you have not selected anything for extraction yet. Once you do, the robot will show a preview of your selections here.
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<div style={{ float: 'left', clear: 'both' }} ref={logEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ float: "left", clear: "both" }}
|
||||
ref={logEndRef} />
|
||||
</div>
|
||||
</SwipeableDrawer>
|
||||
</div>
|
||||
);
|
||||
</SwipeableDrawer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import axios from 'axios';
|
||||
import styled from "styled-components";
|
||||
import { stopRecording } from "../../api/recording";
|
||||
import { useGlobalInfoStore } from "../../context/globalInfo";
|
||||
import { Button, IconButton } from "@mui/material";
|
||||
import { Button, IconButton, Typography } from "@mui/material";
|
||||
import { RecordingIcon } from "../atoms/RecorderIcon";
|
||||
import { SaveRecording } from "./SaveRecording";
|
||||
import { Circle, Add, Logout, Clear } from "@mui/icons-material";
|
||||
@@ -121,7 +121,9 @@ export const NavBar: React.FC<NavBarProps> = ({ newRecording, recordingName, isR
|
||||
<Logout sx={{ marginRight: '5px' }} />
|
||||
Logout</IconButton>
|
||||
</>
|
||||
) : <IconButton sx={{
|
||||
) :
|
||||
<>
|
||||
<IconButton sx={{
|
||||
width: '140px',
|
||||
borderRadius: '5px',
|
||||
padding: '8px',
|
||||
@@ -137,23 +139,20 @@ export const NavBar: React.FC<NavBarProps> = ({ newRecording, recordingName, isR
|
||||
}} onClick={goToMainMenu}>
|
||||
<Clear sx={{ marginRight: '5px' }} />
|
||||
Discard</IconButton>
|
||||
}
|
||||
{
|
||||
recordingLength > 0
|
||||
? <SaveRecording fileName={recordingName} />
|
||||
: null
|
||||
<SaveRecording fileName={recordingName} />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<GenericModal isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
|
||||
<div style={{ padding: '20px' }}>
|
||||
<h2>Enter URL</h2>
|
||||
<Typography variant="h6" gutterBottom>Enter URL To Extract Data</Typography>
|
||||
<TextField
|
||||
label="URL"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={recordingUrl}
|
||||
onChange={(e: any) => setRecordingUrl(e.target.value)}
|
||||
style={{ marginBottom: '20px' }}
|
||||
style={{ marginBottom: '20px', marginTop: '20px' }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -161,7 +160,7 @@ export const NavBar: React.FC<NavBarProps> = ({ newRecording, recordingName, isR
|
||||
onClick={startRecording}
|
||||
disabled={!recordingUrl}
|
||||
>
|
||||
Submit & Start Recording
|
||||
Start Training Robot
|
||||
</Button>
|
||||
</div>
|
||||
</GenericModal>
|
||||
|
||||
@@ -200,7 +200,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
||||
fetchRecordings();
|
||||
}
|
||||
})
|
||||
}} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
||||
}}>
|
||||
<DeleteForever />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
@@ -239,7 +239,7 @@ const InterpretButton = ({ handleInterpret }: InterpretButtonProps) => {
|
||||
<IconButton aria-label="add" size="small" onClick={() => {
|
||||
handleInterpret();
|
||||
}}
|
||||
sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
||||
>
|
||||
<PlayCircle />
|
||||
</IconButton>
|
||||
)
|
||||
@@ -255,7 +255,7 @@ const ScheduleButton = ({ handleSchedule }: ScheduleButtonProps) => {
|
||||
<IconButton aria-label="add" size="small" onClick={() => {
|
||||
handleSchedule();
|
||||
}}
|
||||
sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
||||
>
|
||||
<Schedule />
|
||||
</IconButton>
|
||||
)
|
||||
@@ -270,7 +270,7 @@ const IntegrateButton = ({ handleIntegrate }: IntegrateButtonProps) => {
|
||||
<IconButton aria-label="add" size="small" onClick={() => {
|
||||
handleIntegrate();
|
||||
}}
|
||||
sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
@@ -69,22 +69,9 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton sx={{
|
||||
width: '140px',
|
||||
background: 'green',
|
||||
color: 'white',
|
||||
'&:hover': { background: 'green', color: 'white' },
|
||||
padding: '13px',
|
||||
marginRight: '10px',
|
||||
borderRadius: '5px',
|
||||
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
|
||||
fontWeight: '500',
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.75',
|
||||
letterSpacing: '0.02857em',
|
||||
}} onClick={() => setOpenModal(true)}>
|
||||
<DoneAll sx={{ marginRight: '5px' }} /> Finish
|
||||
</IconButton>
|
||||
<Button onClick={() => setOpenModal(true)} variant="outlined" sx={{ marginRight: '20px' }} size="small" color="success">
|
||||
Finish
|
||||
</Button>
|
||||
|
||||
<GenericModal isOpen={openModal} onClose={() => setOpenModal(false)} modalStyle={modalStyle}>
|
||||
<form onSubmit={handleSaveRecording} style={{ paddingTop: '20px', display: 'flex', flexDirection: 'column' }} >
|
||||
|
||||
@@ -12,18 +12,18 @@ import { useGlobalInfoStore } from "../../context/globalInfo";
|
||||
|
||||
export const SidePanelHeader = () => {
|
||||
|
||||
const [steppingIsDisabled, setSteppingIsDisabled] = useState(true);
|
||||
const [steppingIsDisabled, setSteppingIsDisabled] = useState(true);
|
||||
|
||||
const { socket } = useSocketStore();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const handleStep = () => {
|
||||
socket?.emit('step');
|
||||
};
|
||||
const handleStep = () => {
|
||||
socket?.emit('step');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{width: 'inherit'}}>
|
||||
<InterpretationButtons enableStepping={(isPaused) => setSteppingIsDisabled(!isPaused)}/>
|
||||
<Button
|
||||
return (
|
||||
<div style={{ width: 'inherit' }}>
|
||||
<InterpretationButtons enableStepping={(isPaused) => setSteppingIsDisabled(!isPaused)} />
|
||||
{/* <Button
|
||||
variant='outlined'
|
||||
disabled={steppingIsDisabled}
|
||||
onClick={handleStep}
|
||||
@@ -31,8 +31,8 @@ export const SidePanelHeader = () => {
|
||||
>
|
||||
step
|
||||
<FastForward/>
|
||||
</Button>
|
||||
<hr/>
|
||||
</div>
|
||||
);
|
||||
</Button> */}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -62,6 +62,7 @@ export const UrlForm = ({
|
||||
type="text"
|
||||
value={address}
|
||||
onChange={onChange}
|
||||
readOnly
|
||||
/>
|
||||
<UrlFormButton type="submit">
|
||||
<KeyboardArrowRightIcon />
|
||||
|
||||
@@ -7,18 +7,20 @@ import { BrowserTabs } from "../molecules/BrowserTabs";
|
||||
import { useSocketStore } from "../../context/socket";
|
||||
import { getCurrentTabs, getCurrentUrl, interpretCurrentRecording } from "../../api/recording";
|
||||
import { Box } from '@mui/material';
|
||||
import { InterpretationLog } from "../molecules/InterpretationLog";
|
||||
|
||||
// TODO: Tab !show currentUrl after recordingUrl global state
|
||||
export const BrowserContent = () => {
|
||||
const { width } = useBrowserDimensionsStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { width } = useBrowserDimensionsStore();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const [tabs, setTabs] = useState<string[]>(['current']);
|
||||
const [tabIndex, setTabIndex] = React.useState(0);
|
||||
const [tabs, setTabs] = useState<string[]>(['current']);
|
||||
const [tabIndex, setTabIndex] = React.useState(0);
|
||||
const [showOutputData, setShowOutputData] = useState(false);
|
||||
|
||||
const handleChangeIndex = useCallback((index: number) => {
|
||||
setTabIndex(index);
|
||||
}, [tabIndex])
|
||||
const handleChangeIndex = useCallback((index: number) => {
|
||||
setTabIndex(index);
|
||||
}, [tabIndex])
|
||||
|
||||
const handleCloseTab = useCallback((index: number) => {
|
||||
// the tab needs to be closed on the backend
|
||||
@@ -51,17 +53,17 @@ export const BrowserContent = () => {
|
||||
handleChangeIndex(tabs.length);
|
||||
}, [socket, tabs]);
|
||||
|
||||
const handleNewTab = useCallback((tab: string) => {
|
||||
// Adds a new tab to the end of the tabs array and shifts focus
|
||||
setTabs((prevState) => [...prevState, tab]);
|
||||
// changes focus on the new tab - same happens in the remote browser
|
||||
handleChangeIndex(tabs.length);
|
||||
handleTabChange(tabs.length);
|
||||
}, [tabs]);
|
||||
const handleNewTab = useCallback((tab: string) => {
|
||||
// Adds a new tab to the end of the tabs array and shifts focus
|
||||
setTabs((prevState) => [...prevState, tab]);
|
||||
// changes focus on the new tab - same happens in the remote browser
|
||||
handleChangeIndex(tabs.length);
|
||||
handleTabChange(tabs.length);
|
||||
}, [tabs]);
|
||||
|
||||
const handleTabChange = useCallback((index: number) => {
|
||||
// page screencast and focus needs to be changed on backend
|
||||
socket?.emit('changeTab', index);
|
||||
socket?.emit('changeTab', index);
|
||||
}, [socket]);
|
||||
|
||||
const handleUrlChanged = (url: string) => {
|
||||
@@ -91,18 +93,18 @@ export const BrowserContent = () => {
|
||||
handleCloseTab(index);
|
||||
}, [handleCloseTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
socket.on('newTab', handleNewTab);
|
||||
socket.on('tabHasBeenClosed', tabHasBeenClosedHandler);
|
||||
}
|
||||
return () => {
|
||||
if (socket) {
|
||||
socket.off('newTab', handleNewTab);
|
||||
socket.off('tabHasBeenClosed', tabHasBeenClosedHandler);
|
||||
}
|
||||
}
|
||||
}, [socket, handleNewTab])
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
socket.on('newTab', handleNewTab);
|
||||
socket.on('tabHasBeenClosed', tabHasBeenClosedHandler);
|
||||
}
|
||||
return () => {
|
||||
if (socket) {
|
||||
socket.off('newTab', handleNewTab);
|
||||
socket.off('tabHasBeenClosed', tabHasBeenClosedHandler);
|
||||
}
|
||||
}
|
||||
}, [socket, handleNewTab])
|
||||
|
||||
useEffect(() => {
|
||||
getCurrentTabs().then((response) => {
|
||||
@@ -115,7 +117,7 @@ export const BrowserContent = () => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="browser">
|
||||
<BrowserTabs
|
||||
tabs={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
@@ -129,8 +131,8 @@ export const BrowserContent = () => {
|
||||
browserWidth={900}
|
||||
handleUrlChanged={handleUrlChanged}
|
||||
/>
|
||||
<BrowserWindow/>
|
||||
</>
|
||||
<BrowserWindow />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export const BrowserWindow = () => {
|
||||
const [paginationSelector, setPaginationSelector] = useState<string>('');
|
||||
|
||||
const { socket } = useSocketStore();
|
||||
const { width, height } = useBrowserDimensionsStore();
|
||||
//const { width, height } = useBrowserDimensionsStore();
|
||||
const { getText, getList, paginationMode, paginationType, limitMode } = useActionContext();
|
||||
const { addTextStep, addListStep } = useBrowserSteps();
|
||||
|
||||
@@ -316,7 +316,7 @@ export const BrowserWindow = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div onClick={handleClick}>
|
||||
<div onClick={handleClick} style={{ width: '900px'}} id="browser-window">
|
||||
{
|
||||
getText === true || getList === true ? (
|
||||
<GenericModal
|
||||
@@ -356,20 +356,22 @@ export const BrowserWindow = () => {
|
||||
</GenericModal>
|
||||
) : null
|
||||
}
|
||||
<div style={{ height: '400px', overflow: 'hidden' }}>
|
||||
{((getText === true || getList === true) && !showAttributeModal && highlighterData?.rect != null && highlighterData?.rect.top != null) && canvasRef?.current ?
|
||||
<Highlighter
|
||||
unmodifiedRect={highlighterData?.rect}
|
||||
displayedSelector={highlighterData?.selector}
|
||||
width={width}
|
||||
height={height}
|
||||
width={900}
|
||||
height={400}
|
||||
canvasRect={canvasRef.current.getBoundingClientRect()}
|
||||
/>
|
||||
: null}
|
||||
<Canvas
|
||||
onCreateRef={setCanvasReference}
|
||||
width={width}
|
||||
height={height}
|
||||
width={900}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -383,7 +385,7 @@ const drawImage = (image: string, canvas: HTMLCanvasElement): void => {
|
||||
img.src = image;
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(img.src);
|
||||
ctx?.drawImage(img, 0, 0, 1280, 720);
|
||||
ctx?.drawImage(img, 0, 0, 900, 400);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -105,7 +105,7 @@ export const LeftSidePanel = (
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<SidePanelHeader />
|
||||
{/* <SidePanelHeader /> */}
|
||||
<TabContext value={tab}>
|
||||
<Tabs value={tab} onChange={(e, newTab) => setTab(newTab)}>
|
||||
<Tab label="Recording" value='recording' />
|
||||
|
||||
@@ -21,6 +21,7 @@ import RadioGroup from '@mui/material/RadioGroup';
|
||||
import { emptyWorkflow } from "../../shared/constants";
|
||||
import { getActiveWorkflow } from "../../api/workflow";
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import ActionDescriptionBox from '../molecules/ActionDescriptionBox';
|
||||
|
||||
const fetchWorkflow = (id: string, callback: (response: WorkflowFile) => void) => {
|
||||
getActiveWorkflow(id).then(
|
||||
@@ -52,11 +53,10 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
const [showCaptureList, setShowCaptureList] = useState(true);
|
||||
const [showCaptureScreenshot, setShowCaptureScreenshot] = useState(true);
|
||||
const [showCaptureText, setShowCaptureText] = useState(true);
|
||||
const [captureStage, setCaptureStage] = useState<'initial' | 'pagination' | 'limit' | 'complete'>('initial');
|
||||
const [hoverStates, setHoverStates] = useState<{ [id: string]: boolean }>({});
|
||||
|
||||
const { lastAction, notify } = useGlobalInfoStore();
|
||||
const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode } = useActionContext();
|
||||
const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState } = useGlobalInfoStore();
|
||||
const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext();
|
||||
const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps();
|
||||
const { id, socket } = useSocketStore();
|
||||
|
||||
@@ -87,7 +87,6 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
|
||||
useEffect(() => {
|
||||
const hasPairs = workflow.workflow.length > 0;
|
||||
|
||||
if (!hasPairs) {
|
||||
setShowCaptureList(true);
|
||||
setShowCaptureScreenshot(true);
|
||||
@@ -96,19 +95,25 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
}
|
||||
|
||||
const hasScrapeListAction = workflow.workflow.some(pair =>
|
||||
pair.what.some(action => action.action === "scrapeList")
|
||||
pair.what.some(action => action.action === 'scrapeList')
|
||||
);
|
||||
|
||||
const hasScreenshotAction = workflow.workflow.some(pair =>
|
||||
pair.what.some(action => action.action === "screenshot")
|
||||
pair.what.some(action => action.action === 'screenshot')
|
||||
);
|
||||
|
||||
const hasScrapeSchemaAction = workflow.workflow.some(pair =>
|
||||
pair.what.some(action => action.action === "scrapeSchema")
|
||||
pair.what.some(action => action.action === 'scrapeSchema')
|
||||
);
|
||||
|
||||
setShowCaptureList(!(hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction));
|
||||
setShowCaptureScreenshot(!(hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction));
|
||||
setCurrentWorkflowActionsState({
|
||||
hasScrapeListAction,
|
||||
hasScreenshotAction,
|
||||
hasScrapeSchemaAction,
|
||||
});
|
||||
|
||||
const shouldHideActions = hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction;
|
||||
|
||||
setShowCaptureList(!shouldHideActions);
|
||||
setShowCaptureScreenshot(!shouldHideActions);
|
||||
setShowCaptureText(!(hasScrapeListAction || hasScreenshotAction));
|
||||
}, [workflow]);
|
||||
|
||||
@@ -369,11 +374,11 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" sx={{ height: '100%', width: '100%', backgroundColor: 'white', alignItems: "center" }}>
|
||||
<Paper variant="outlined" sx={{ height: '520px', width: 'auto', alignItems: "center", background: 'inherit' }} id="browser-actions">
|
||||
<SimpleBox height={60} width='100%' background='lightGray' radius='0%'>
|
||||
<Typography sx={{ padding: '10px' }}>Last action: {` ${lastAction}`}</Typography>
|
||||
</SimpleBox>
|
||||
<SidePanelHeader />
|
||||
<ActionDescriptionBox />
|
||||
<Box display="flex" flexDirection="column" gap={2} style={{ margin: '15px' }}>
|
||||
{!getText && !getScreenshot && !getList && showCaptureList && <Button variant="contained" onClick={startGetList}>Capture List</Button>}
|
||||
{getList && (
|
||||
@@ -458,7 +463,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
</Box>
|
||||
<Box>
|
||||
{browserSteps.map(step => (
|
||||
<Box key={step.id} sx={{ boxShadow: 5, padding: '10px', margin: '13px', borderRadius: '4px', position: 'relative', }} onMouseEnter={() => handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)}>
|
||||
<Box key={step.id} onMouseEnter={() => handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ boxShadow: 5, padding: '10px', margin: '13px', borderRadius: '4px', position: 'relative', background: 'white' }}>
|
||||
{
|
||||
step.type === 'text' && (
|
||||
<>
|
||||
@@ -514,7 +519,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
{!confirmedTextSteps[step.id] && (
|
||||
<Box display="flex" justifyContent="space-between" gap={2}>
|
||||
<Button variant="contained" onClick={() => handleTextStepConfirm(step.id)} disabled={!textLabels[step.id]?.trim()}>Confirm</Button>
|
||||
<Button variant="contained" onClick={() => handleTextStepDiscard(step.id)}>Discard</Button>
|
||||
<Button variant="contained" color="error" onClick={() => handleTextStepDiscard(step.id)}>Discard</Button>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
@@ -572,6 +577,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => handleListTextFieldDiscard(step.id, key)}
|
||||
>
|
||||
Discard
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const VIEWPORT_W = 1280;
|
||||
export const VIEWPORT_H = 720;
|
||||
export const VIEWPORT_W = 900;
|
||||
export const VIEWPORT_H = 400;
|
||||
|
||||
export const ONE_PERCENT_OF_VIEWPORT_W = VIEWPORT_W / 100;
|
||||
export const ONE_PERCENT_OF_VIEWPORT_H = VIEWPORT_H / 100;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useSocketStore } from './socket';
|
||||
|
||||
export type PaginationType = 'scrollDown' | 'scrollUp' | 'clickNext' | 'clickLoadMore' | 'none' | '';
|
||||
export type LimitType = '10' | '100' | 'custom' | '';
|
||||
export type CaptureStage = 'initial' | 'pagination' | 'limit' | 'complete';
|
||||
|
||||
interface ActionContextProps {
|
||||
getText: boolean;
|
||||
@@ -13,6 +14,8 @@ interface ActionContextProps {
|
||||
paginationType: PaginationType;
|
||||
limitType: LimitType;
|
||||
customLimit: string;
|
||||
captureStage: CaptureStage; // New captureStage property
|
||||
setCaptureStage: (stage: CaptureStage) => void; // Setter for captureStage
|
||||
startPaginationMode: () => void;
|
||||
startGetText: () => void;
|
||||
stopGetText: () => void;
|
||||
@@ -39,17 +42,26 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [paginationType, setPaginationType] = useState<PaginationType>('');
|
||||
const [limitType, setLimitType] = useState<LimitType>('');
|
||||
const [customLimit, setCustomLimit] = useState<string>('');
|
||||
const [captureStage, setCaptureStage] = useState<CaptureStage>('initial'); // New captureStage state
|
||||
|
||||
const {socket} = useSocketStore();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const updatePaginationType = (type: PaginationType) => setPaginationType(type);
|
||||
const updateLimitType = (type: LimitType) => setLimitType(type);
|
||||
const updateCustomLimit = (limit: string) => setCustomLimit(limit);
|
||||
|
||||
const startPaginationMode = () => setPaginationMode(true);
|
||||
const startPaginationMode = () => {
|
||||
setPaginationMode(true);
|
||||
setCaptureStage('pagination');
|
||||
};
|
||||
|
||||
const stopPaginationMode = () => setPaginationMode(false);
|
||||
|
||||
const startLimitMode = () => setLimitMode(true);
|
||||
const startLimitMode = () => {
|
||||
setLimitMode(true);
|
||||
setCaptureStage('limit');
|
||||
};
|
||||
|
||||
const stopLimitMode = () => setLimitMode(false);
|
||||
|
||||
const startGetText = () => setGetText(true);
|
||||
@@ -59,35 +71,38 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => {
|
||||
setGetList(true);
|
||||
socket?.emit('setGetList', { getList: true });
|
||||
}
|
||||
|
||||
|
||||
const stopGetList = () => {
|
||||
setGetList(false);
|
||||
socket?.emit('setGetList', { getList: false });
|
||||
setPaginationType('');
|
||||
setLimitType('');
|
||||
setCustomLimit('');
|
||||
setCaptureStage('initial'); // Reset captureStage when stopping getList
|
||||
};
|
||||
|
||||
const startGetScreenshot = () => setGetScreenshot(true);
|
||||
const stopGetScreenshot = () => setGetScreenshot(false);
|
||||
|
||||
return (
|
||||
<ActionContext.Provider value={{
|
||||
getText,
|
||||
getList,
|
||||
getScreenshot,
|
||||
paginationMode,
|
||||
<ActionContext.Provider value={{
|
||||
getText,
|
||||
getList,
|
||||
getScreenshot,
|
||||
paginationMode,
|
||||
limitMode,
|
||||
paginationType,
|
||||
paginationType,
|
||||
limitType,
|
||||
customLimit,
|
||||
startGetText,
|
||||
stopGetText,
|
||||
startGetList,
|
||||
stopGetList,
|
||||
startGetScreenshot,
|
||||
stopGetScreenshot,
|
||||
startPaginationMode,
|
||||
captureStage,
|
||||
setCaptureStage,
|
||||
startGetText,
|
||||
stopGetText,
|
||||
startGetList,
|
||||
stopGetList,
|
||||
startGetScreenshot,
|
||||
stopGetScreenshot,
|
||||
startPaginationMode,
|
||||
stopPaginationMode,
|
||||
startLimitMode,
|
||||
stopLimitMode,
|
||||
@@ -106,4 +121,4 @@ export const useActionContext = () => {
|
||||
throw new Error('useActionContext must be used within an ActionProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@ interface BrowserDimensions {
|
||||
setWidth: (newWidth: number) => void;
|
||||
};
|
||||
|
||||
class BrowserDimensionsStore implements Partial<BrowserDimensions>{
|
||||
width: number = 1280;
|
||||
height: number = 720;
|
||||
class BrowserDimensionsStore implements Partial<BrowserDimensions> {
|
||||
width: number = 900;
|
||||
height: number = 400;
|
||||
};
|
||||
|
||||
const browserDimensionsStore = new BrowserDimensionsStore();
|
||||
|
||||
@@ -22,6 +22,16 @@ interface GlobalInfo {
|
||||
setRecordingName: (recordingName: string) => void;
|
||||
recordingUrl: string;
|
||||
setRecordingUrl: (recordingUrl: string) => void;
|
||||
currentWorkflowActionsState: {
|
||||
hasScrapeListAction: boolean;
|
||||
hasScreenshotAction: boolean;
|
||||
hasScrapeSchemaAction: boolean;
|
||||
};
|
||||
setCurrentWorkflowActionsState: (actionsState: {
|
||||
hasScrapeListAction: boolean;
|
||||
hasScreenshotAction: boolean;
|
||||
hasScrapeSchemaAction: boolean;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
class GlobalInfoStore implements Partial<GlobalInfo> {
|
||||
@@ -38,6 +48,11 @@ class GlobalInfoStore implements Partial<GlobalInfo> {
|
||||
rerenderRuns = false;
|
||||
recordingName = '';
|
||||
recordingUrl = 'https://';
|
||||
currentWorkflowActionsState = {
|
||||
hasScrapeListAction: false,
|
||||
hasScreenshotAction: false,
|
||||
hasScrapeSchemaAction: false,
|
||||
};
|
||||
};
|
||||
|
||||
const globalInfoStore = new GlobalInfoStore();
|
||||
@@ -55,6 +70,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const [recordingId, setRecordingId] = useState<string | null>(globalInfoStore.recordingId);
|
||||
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
|
||||
const [recordingUrl, setRecordingUrl] = useState<string>(globalInfoStore.recordingUrl);
|
||||
const [currentWorkflowActionsState, setCurrentWorkflowActionsState] = useState(globalInfoStore.currentWorkflowActionsState);
|
||||
|
||||
const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => {
|
||||
setNotification({ severity, message, isOpen: true });
|
||||
@@ -93,6 +109,8 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
|
||||
setRecordingName,
|
||||
recordingUrl,
|
||||
setRecordingUrl,
|
||||
currentWorkflowActionsState,
|
||||
setCurrentWorkflowActionsState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
140
src/index.css
140
src/index.css
@@ -5,12 +5,18 @@ body {
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scrollbar-gutter: stable;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scrollbar-gutter: stable;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -18,6 +24,124 @@ code {
|
||||
monospace;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-y:scroll;
|
||||
#browser-actions {
|
||||
right: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#browser-recorder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#browser-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: scale(1); /* Ensure no scaling */
|
||||
transform-origin: top left; /* Keep the position fixed */
|
||||
}
|
||||
|
||||
#browser {
|
||||
|
||||
}
|
||||
|
||||
#browser-window {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right-side-panel {
|
||||
margin: 0;
|
||||
transform: scale(1);
|
||||
transform-origin: top left;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* For laptops (between 1024px and 1440px) */
|
||||
@media (min-width: 1024px) and (max-width: 1440px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 0.6rem);
|
||||
margin: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* For desktops (between 1441px and 1920px) */
|
||||
@media (min-width: 1441px) and (max-width: 1500px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1501px) and (max-width: 1700px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1701px) and (max-width: 1800px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1801px) and (max-width: 1900px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 18.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1900px) and (max-width: 1920px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 20rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* For very large desktops (greater than 1920px) */
|
||||
@media (min-width: 1921px) and (max-width: 2000px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 20rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 2001px) and (max-width: 2500px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 24rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 2501px) and (max-width: 2999px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 40rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 3000px) {
|
||||
#browser-recorder {
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - 2rem);
|
||||
margin: 1rem 55rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,11 @@ export const PageWrapper = () => {
|
||||
<AuthProvider>
|
||||
<SocketProvider>
|
||||
<React.Fragment>
|
||||
<NavBar newRecording={handleNewRecording} recordingName={recordingName} isRecording={!!browserId} />
|
||||
{
|
||||
!!browserId ? (
|
||||
""
|
||||
) : <NavBar newRecording={handleNewRecording} recordingName={recordingName} isRecording={!!browserId} />
|
||||
}
|
||||
<Routes>
|
||||
<Route element={<UserRoute />}>
|
||||
<Route path="/" element={<MainPage handleEditRecording={handleEditRecording} />} />
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useGlobalInfoStore } from "../context/globalInfo";
|
||||
import { editRecordingFromStorage } from "../api/storage";
|
||||
import { WhereWhatPair } from "maxun-core";
|
||||
import styled from "styled-components";
|
||||
import BrowserRecordingSave from '../components/molecules/BrowserRecordingSave';
|
||||
|
||||
interface RecordingPageProps {
|
||||
recordingName?: string;
|
||||
@@ -54,6 +55,17 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
|
||||
|
||||
useEffect(() => changeBrowserDimensions(), [isLoaded])
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.background = 'radial-gradient(circle, rgba(255, 255, 255, 1) 0%, rgba(232, 191, 222, 1) 100%, rgba(255, 255, 255, 1) 100%)';
|
||||
document.body.style.filter = 'progid:DXImageTransform.Microsoft.gradient(startColorstr="#ffffff",endColorstr="#ffffff",GradientType=1);'
|
||||
|
||||
return () => {
|
||||
// Cleanup the background when leaving the page
|
||||
document.body.style.background = '';
|
||||
document.body.style.filter = '';
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
const handleRecording = async () => {
|
||||
@@ -110,35 +122,38 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => {
|
||||
}
|
||||
}, [socket, handleLoaded]);
|
||||
|
||||
|
||||
return (
|
||||
<ActionProvider>
|
||||
<BrowserStepsProvider>
|
||||
<div>
|
||||
{isLoaded ?
|
||||
<Grid container direction="row" spacing={0}>
|
||||
<Grid item xs={2} ref={workflowListRef} style={{ display: "flex", flexDirection: "row" }}>
|
||||
<LeftSidePanel
|
||||
sidePanelRef={workflowListRef.current}
|
||||
alreadyHasScrollbar={hasScrollbar}
|
||||
recordingName={recordingName ? recordingName : ''}
|
||||
handleSelectPairForEdit={handleSelectPairForEdit}
|
||||
/>
|
||||
<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' }}>
|
||||
<div style={{ height: '100%', overflow: 'auto' }}>
|
||||
<BrowserContent />
|
||||
<InterpretationLog isOpen={showOutputData} setIsOpen={setShowOutputData} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3} lg={3} style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<div className="right-side-panel" style={{ height: '100%' }}>
|
||||
<RightSidePanel onFinishCapture={handleShowOutputData} />
|
||||
<BrowserRecordingSave />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid id="browser-content" ref={browserContentRef} item xs>
|
||||
<BrowserContent />
|
||||
<InterpretationLog isOpen={showOutputData} setIsOpen={setShowOutputData} />
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<RightSidePanel onFinishCapture={handleShowOutputData} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
: <Loader />}
|
||||
</>
|
||||
) : (
|
||||
<Loader text={'Spinning up a browser just for you...'} />
|
||||
)}
|
||||
</div>
|
||||
</BrowserStepsProvider>
|
||||
</ActionProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const RecordingPageWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
|
||||
Reference in New Issue
Block a user