diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..7f0715a9 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { GlobalInfoProvider } from "./context/globalInfo"; +import { PageWrapper } from "./pages/PageWrappper"; + +function App() { + + return ( + + + + ); +} + +export default App; diff --git a/src/components/atoms/Highlighter.tsx b/src/components/atoms/Highlighter.tsx index b72cac06..3199b083 100644 --- a/src/components/atoms/Highlighter.tsx +++ b/src/components/atoms/Highlighter.tsx @@ -36,8 +36,6 @@ export const Highlighter = ({ unmodifiedRect, displayedSelector = '', width, hei height: unmodifiedRect.height, }; - const adjustedWidth = Math.min(rect.width, width - rect.left); // Adjust width if it extends beyond canvas boundary - const adjustedHeight = Math.min(rect.height, height - rect.top); // Adjust height if it extends beyond canvas boundary console.log('unmodifiedRect:', unmodifiedRect) console.log('rectangle:', rect) diff --git a/src/constants/const.ts b/src/constants/const.ts new file mode 100644 index 00000000..8aec049e --- /dev/null +++ b/src/constants/const.ts @@ -0,0 +1,5 @@ +export const VIEWPORT_W = 1280; +export const VIEWPORT_H = 720; + +export const ONE_PERCENT_OF_VIEWPORT_W = VIEWPORT_W / 100; +export const ONE_PERCENT_OF_VIEWPORT_H = VIEWPORT_H / 100; diff --git a/src/context/globalInfo.tsx b/src/context/globalInfo.tsx new file mode 100644 index 00000000..e7eb2ed2 --- /dev/null +++ b/src/context/globalInfo.tsx @@ -0,0 +1,83 @@ +import React, { createContext, useContext, useState } from "react"; +import { AlertSnackbarProps } from "../components/atoms/AlertSnackbar"; + + +interface GlobalInfo { + browserId: string | null; + setBrowserId: (newId: string | null) => void; + lastAction: string; + setLastAction: (action: string) => void; + notification: AlertSnackbarProps; + notify: (severity: 'error' | 'warning' | 'info' | 'success', message: string) => void; + closeNotify: () => void; + recordings: string[]; + setRecordings: (recordings: string[]) => void; + rerenderRuns: boolean; + setRerenderRuns: (rerenderRuns: boolean) => void; + recordingLength: number; + setRecordingLength: (recordingLength: number) => void; +}; + +class GlobalInfoStore implements Partial { + browserId = null; + lastAction = ''; + recordingLength = 0; + notification: AlertSnackbarProps = { + severity: 'info', + message: '', + isOpen: false, + }; + recordings: string[] = []; + rerenderRuns = false; +}; + +const globalInfoStore = new GlobalInfoStore(); +const globalInfoContext = createContext(globalInfoStore as GlobalInfo); + +export const useGlobalInfoStore = () => useContext(globalInfoContext); + +export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => { + const [browserId, setBrowserId] = useState(globalInfoStore.browserId); + const [lastAction, setLastAction] = useState(globalInfoStore.lastAction); + const [notification, setNotification] = useState(globalInfoStore.notification); + const [recordings, setRecordings] = useState(globalInfoStore.recordings); + const [rerenderRuns, setRerenderRuns] = useState(globalInfoStore.rerenderRuns); + const [recordingLength, setRecordingLength] = useState(globalInfoStore.recordingLength); + + const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => { + setNotification({ severity, message, isOpen: true }); + } + + const closeNotify = () => { + setNotification(globalInfoStore.notification); + } + + const setBrowserIdWithValidation = (browserId: string | null) => { + setBrowserId(browserId); + if (!browserId) { + setRecordingLength(0); + } + } + + return ( + + {children} + + ); +}; diff --git a/src/context/socket.tsx b/src/context/socket.tsx new file mode 100644 index 00000000..678f5520 --- /dev/null +++ b/src/context/socket.tsx @@ -0,0 +1,52 @@ +import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; +import { io, Socket } from 'socket.io-client'; + +const SERVER_ENDPOINT = 'http://localhost:8080'; + +interface SocketState { + socket: Socket | null; + id: string; + setId: (id: string) => void; +}; + +class SocketStore implements Partial { + socket = null; + id = ''; +}; + +const socketStore = new SocketStore(); +const socketStoreContext = createContext(socketStore as SocketState); + +export const useSocketStore = () => useContext(socketStoreContext); + +export const SocketProvider = ({ children }: { children: JSX.Element }) => { + const [socket, setSocket] = useState(socketStore.socket); + const [id, setActiveId] = useState(socketStore.id); + + const setId = useCallback((id: string) => { + // the socket client connection is recomputed whenever id changes -> the new browser has been initialized + const socket = + io(`${SERVER_ENDPOINT}/${id}`, { + transports: ["websocket"], + rejectUnauthorized: false + }); + + socket.on('connect', () => console.log('connected to socket')); + socket.on("connect_error", (err) => console.log(`connect_error due to ${err.message}`)); + + setSocket(socket); + setActiveId(id); + }, [setSocket]); + + return ( + + {children} + + ); +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..21808ea5 --- /dev/null +++ b/src/index.css @@ -0,0 +1,17 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +html { + overflow-y:scroll; +} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 00000000..b7688442 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + + +); + diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 00000000..d7ce0bd0 --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,3 @@ +import { WorkflowFile } from "@wbr-project/wbr-interpret"; + +export const emptyWorkflow: WorkflowFile = { workflow: [] }; diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 00000000..72e86a0e --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,26 @@ +import { WorkflowFile } from "@wbr-project/wbr-interpret"; +import { Locator } from "playwright"; + +export type Workflow = WorkflowFile["workflow"]; + +export interface ScreenshotSettings { + animations?: "disabled" | "allow"; + caret?: "hide" | "initial"; + clip?: { + x: number; + y: number; + width: number; + height: number; + }; + fullPage?: boolean; + mask?: Locator[]; + omitBackground?: boolean; + // is this still needed? - @wbr-project/wbr-interpret outputs to a binary output + path?: string; + quality?: number; + scale?: "css" | "device"; + timeout?: number; + type?: "jpeg" | "png"; +}; + +export declare type CustomActions = 'scrape' | 'scrapeSchema' | 'scroll' | 'screenshot' | 'script' | 'enqueueLinks' | 'flag';