From f4a0327c9a37f90b4d9775df17c07624ee81a15b Mon Sep 17 00:00:00 2001 From: amit Date: Thu, 7 Nov 2024 00:46:47 +0530 Subject: [PATCH 001/199] dark theme added --- src/App.tsx | 84 +--------------- src/components/molecules/NavBar.tsx | 139 +++++++++++++------------- src/components/organisms/MainMenu.tsx | 50 +++------ src/context/theme-provider.tsx | 64 ++++++++++++ 4 files changed, 154 insertions(+), 183 deletions(-) create mode 100644 src/context/theme-provider.tsx diff --git a/src/App.tsx b/src/App.tsx index c37de9ea..896723a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,96 +1,22 @@ import React from 'react'; import { Routes, Route } from 'react-router-dom'; -import { ThemeProvider, createTheme } from "@mui/material/styles"; +import { createTheme } from "@mui/material/styles"; import { GlobalInfoProvider } from "./context/globalInfo"; import { PageWrapper } from "./pages/PageWrappper"; +import ThemeModeProvider from './context/theme-provider'; + -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", - // }, - }, - }, - }, - MuiTab: { - styleOverrides: { - root: { - textTransform: "none", - }, - }, - }, - MuiAlert: { - styleOverrides: { - standardInfo: { - backgroundColor: "#fce1f4", - color: "#ff00c3", - '& .MuiAlert-icon': { - color: "#ff00c3", - }, - }, - }, - }, - MuiAlertTitle: { - styleOverrides: { - root: { - '& .MuiAlert-icon': { - color: "#ffffff", - }, - }, - }, - }, - }, -}); function App() { return ( - + } /> - + ); } diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 69fba352..b82f2a5d 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -3,14 +3,15 @@ import axios from 'axios'; import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; -import { IconButton, Menu, MenuItem, Typography, Avatar } from "@mui/material"; -import { AccountCircle, Logout, Clear } from "@mui/icons-material"; +import { IconButton, Menu, MenuItem, Typography, Avatar, Tooltip } from "@mui/material"; +import { AccountCircle, Logout, Clear, Brightness4, Brightness7 } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; import DiscordIcon from '../atoms/DiscordIcon'; import { apiUrl } from '../../apiConfig'; import MaxunLogo from "../../assets/maxunlogo.png"; +import { useThemeMode } from '../../context/theme-provider'; interface NavBarProps { recordingName: string; @@ -18,10 +19,11 @@ interface NavBarProps { } export const NavBar: React.FC = ({ recordingName, isRecording }) => { - const { notify, browserId, setBrowserId, recordingUrl } = useGlobalInfoStore(); + const { notify, browserId, setBrowserId } = useGlobalInfoStore(); const { state, dispatch } = useContext(AuthContext); const { user } = state; const navigate = useNavigate(); + const { darkMode, toggleTheme } = useThemeMode(); const [anchorEl, setAnchorEl] = useState(null); @@ -51,20 +53,18 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => }; return ( - -
+ +
-
Maxun
+
+ Maxun +
- { - user ? ( -
- {!isRecording ? ( - <> - + {!isRecording ? ( + <> + = ({ recordingName, isRecording }) => padding: '8px', marginRight: '10px', }} - > + > - + - - - {user.email} + + + {user.email} + + + { handleMenuClose(); logout(); }}> + Logout + + + {/* Theme Toggle Button */} + + + {darkMode ? : } - - { handleMenuClose(); logout(); }}> - Logout - - - - ) : ( - <> - - - Discard - - - - )} -
- ) : "" - } + + + ) : ( + <> + + + Discard + + + + )} +
+ ) : null}
); }; -const NavBarWrapper = styled.div` +const NavBarWrapper = styled.div<{ mode: 'light' | 'dark' }>` grid-area: navbar; - background-color: white; - padding:5px; + background-color: ${({ mode }) => (mode === 'dark' ? '#1e2124' : '#ffffff')}; + padding: 5px; display: flex; justify-content: space-between; - border-bottom: 1px solid #e0e0e0; + border-bottom: 1px solid ${({ mode }) => (mode === 'dark' ? '#333' : '#e0e0e0')}; `; -const ProjectName = styled.b` - color: #3f4853; +const ProjectName = styled.b<{ mode: 'light' | 'dark' }>` + color: ${({ mode }) => (mode === 'dark' ? 'white' : 'black')}; font-size: 1.3em; `; diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index edb6ed29..c6bae196 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -1,10 +1,9 @@ -import * as React from 'react'; +import React from 'react'; import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; -import { Paper, Button } from "@mui/material"; -import { AutoAwesome, FormatListBulleted, VpnKey, Usb, Article, Link, CloudQueue } from "@mui/icons-material"; -import { apiUrl } from "../../apiConfig"; +import { Paper, Button, useTheme } from "@mui/material"; +import { AutoAwesome, FormatListBulleted, VpnKey, Usb, Article, CloudQueue } from "@mui/icons-material"; interface MainMenuProps { value: string; @@ -12,6 +11,7 @@ interface MainMenuProps { } export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenuProps) => { + const theme = useTheme(); const handleChange = (event: React.SyntheticEvent, newValue: string) => { handleChangeContent(newValue); @@ -22,16 +22,14 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu sx={{ height: 'auto', width: '250px', - backgroundColor: 'white', + backgroundColor: theme.palette.background.paper, paddingTop: '0.5rem', + color: theme.palette.text.primary, }} variant="outlined" square > - + } iconPosition="start" /> } iconPosition="start" /> } iconPosition="start" /> } @@ -87,7 +69,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
- } {getList && ( <> @@ -426,7 +426,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} {showLimitOptions && ( - +

What is the maximum number of rows you want to extract?

= ({ onFinishCapture marginLeft: '10px', '& input': { padding: '10px', - background: 'white', + }, - width: '150px', // Ensure the text field does not go outside the panel + width: '150px', + background: isDarkMode ? "#1E2124" : 'white', + color: isDarkMode ? "white" : 'black', // Ensure the text field does not go outside the panel }} /> )} @@ -503,6 +505,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} + sx={{ background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }} /> = ({ onFinishCapture ) }} + sx={{ background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }} /> {!confirmedTextSteps[step.id] && ( @@ -553,6 +557,8 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} + + style={{ background: isDarkMode ? "#1E2124" : 'white' }} /> = ({ onFinishCapture ) }} + style={{ background: isDarkMode ? "#1E2124" : 'white' }} /> {!confirmedListTextFields[step.id]?.[key] && ( From f71822f844e4b88a27c47b613da6589d734dc32d Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 24 Nov 2024 00:49:39 +0530 Subject: [PATCH 009/199] some fixes --- src/components/atoms/Loader.tsx | 6 +- src/components/atoms/buttons/buttons.tsx | 19 +- src/components/atoms/canvas.tsx | 2 +- .../molecules/ActionDescriptionBox.tsx | 1 + src/components/molecules/BrowserNavBar.tsx | 64 ++-- .../molecules/BrowserRecordingSave.tsx | 7 +- src/components/molecules/BrowserTabs.tsx | 71 ++++- .../molecules/InterpretationLog.tsx | 11 +- src/components/molecules/NavBar.tsx | 288 ++++++++++++------ src/components/molecules/RunContent.tsx | 49 ++- src/components/molecules/SaveRecording.tsx | 2 +- src/components/organisms/BrowserContent.tsx | 12 +- src/components/organisms/BrowserWindow.tsx | 2 +- src/components/organisms/RightSidePanel.tsx | 27 +- src/pages/RecordingPage.tsx | 2 + 15 files changed, 399 insertions(+), 164 deletions(-) diff --git a/src/components/atoms/Loader.tsx b/src/components/atoms/Loader.tsx index 35f9a506..529068a7 100644 --- a/src/components/atoms/Loader.tsx +++ b/src/components/atoms/Loader.tsx @@ -2,16 +2,13 @@ import styled from "styled-components"; import { Stack } from "@mui/material"; import { useThemeMode } from "../../context/theme-provider"; - interface LoaderProps { text: string; } export const Loader: React.FC = ({ text }) => { - const { darkMode } = useThemeMode(); - return ( @@ -29,14 +26,13 @@ interface StyledParagraphProps { darkMode: boolean; } - - const StyledParagraph = styled.p` font-size: medium; font-weight: 700; font-family: inherit; color: ${({ darkMode }) => (darkMode ? 'white' : 'black')}; margin-top: 20px; + flex-wrap: wrap; `; const DotsContainer = styled.div` diff --git a/src/components/atoms/buttons/buttons.tsx b/src/components/atoms/buttons/buttons.tsx index afc4a483..0dd72e0c 100644 --- a/src/components/atoms/buttons/buttons.tsx +++ b/src/components/atoms/buttons/buttons.tsx @@ -1,26 +1,23 @@ import styled from 'styled-components'; +import { useThemeMode } from '../../../context/theme-provider'; -export const NavBarButton = styled.button<{ disabled: boolean }>` + + +export const NavBarButton = styled.button<{ disabled: boolean, mode: 'light' | 'dark' }>` margin-left: 10px; margin-right: 5px; padding: 0; border: none; - background-color: transparent; + background-color: ${mode => mode ? '#333' : '#ffffff'}; cursor: ${({ disabled }) => disabled ? 'default' : 'pointer'}; width: 24px; height: 24px; border-radius: 12px; outline: none; - color: ${({ disabled }) => disabled ? '#999' : '#333'}; + color: ${mode => mode ? '#ffffff' : '#333333'}; + + - ${({ disabled }) => disabled ? null : ` - &:hover { - background-color: #ddd; - } - &:active { - background-color: #d0d0d0; - } - `}; `; export const UrlFormButton = styled.button` diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 1dd88e19..e31a7094 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -142,7 +142,7 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { diff --git a/src/components/molecules/ActionDescriptionBox.tsx b/src/components/molecules/ActionDescriptionBox.tsx index e064c01b..747cad18 100644 --- a/src/components/molecules/ActionDescriptionBox.tsx +++ b/src/components/molecules/ActionDescriptionBox.tsx @@ -18,6 +18,7 @@ const CustomBoxContainer = styled.div` background-color: ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')}; color: ${({ isDarkMode }) => (isDarkMode ? 'white' : 'black')}; margin: 80px 13px 25px 13px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); `; const Triangle = styled.div` diff --git a/src/components/molecules/BrowserNavBar.tsx b/src/components/molecules/BrowserNavBar.tsx index 8fe1ba05..d3cb781f 100644 --- a/src/components/molecules/BrowserNavBar.tsx +++ b/src/components/molecules/BrowserNavBar.tsx @@ -1,6 +1,4 @@ -import type { - FC, -} from 'react'; +import type { FC } from 'react'; import styled from 'styled-components'; import ReplayIcon from '@mui/icons-material/Replay'; @@ -13,13 +11,39 @@ import { useCallback, useEffect, useState } from "react"; import { useSocketStore } from "../../context/socket"; import { getCurrentUrl } from "../../api/recording"; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useThemeMode } from '../../context/theme-provider'; -const StyledNavBar = styled.div<{ browserWidth: number }>` - display: flex; - padding: 12px 0px; - background-color: theme.palette.background.paper; - width: ${({ browserWidth }) => browserWidth}px; - border-radius: 0px 5px 0px 0px; +const StyledNavBar = styled.div<{ browserWidth: number; isDarkMode: boolean }>` + display: flex; + align-items: center; + padding: 10px 20px; + background-color: ${({ isDarkMode }) => (isDarkMode ? '#2C2F33' : '#F5F5F5')}; + width: ${({ browserWidth }) => `${browserWidth}px`}; + border-radius: 0px 0px 8px 8px; + box-shadow: ${({ isDarkMode }) => (isDarkMode ? '0px 2px 10px rgba(0, 0, 0, 0.2)' : '0px 2px 10px rgba(0, 0, 0, 0.1)')}; + transition: background-color 0.3s ease, box-shadow 0.3s ease; + margin-bottom: 15px; +`; + +const IconButton = styled(NavBarButton)<{ mode: string }>` + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + margin-right: 12px; + background-color: ${({ mode }) => (mode === 'dark' ? '#40444B' : '#E0E0E0')}; + border-radius: 50%; + transition: background-color 0.3s ease, transform 0.1s ease; + color: ${({ mode }) => (mode === 'dark' ? '#FFFFFF' : '#333')}; + cursor: pointer; + + &:hover { + background-color: ${({ mode }) => (mode === 'dark' ? '#586069' : '#D0D0D0')}; + } + + &:active { + transform: scale(0.95); + } `; interface NavBarProps { @@ -31,6 +55,7 @@ const BrowserNavBar: FC = ({ browserWidth, handleUrlChanged, }) => { + const isDarkMode = useThemeMode().darkMode; const { socket } = useSocketStore(); const { recordingUrl, setRecordingUrl } = useGlobalInfoStore(); @@ -67,7 +92,7 @@ const BrowserNavBar: FC = ({ socket.off('urlChanged', handleCurrentUrlChange); } } - }, [socket, handleCurrentUrlChange]) + }, [socket, handleCurrentUrlChange]); const addAddress = (address: string) => { if (socket) { @@ -78,38 +103,41 @@ const BrowserNavBar: FC = ({ }; return ( - - + { socket?.emit('input:back'); }} disabled={false} + mode={isDarkMode ? 'dark' : 'light'} > - + - { socket?.emit('input:forward'); }} disabled={false} + mode={isDarkMode ? 'dark' : 'light'} > - + - { if (socket) { - handleRefresh() + handleRefresh(); } }} disabled={false} + mode={isDarkMode ? 'dark' : 'light'} > - + {
- ) : null} + + )} ); }; +// Styles +const styles = { + socialButton: { + display: 'flex', + alignItems: 'center', + borderRadius: '5px', + padding: '8px', + marginRight: '30px', + color: '#333333', + '&:hover': { + color: '#ff00c3' + } + }, + userButton: (darkMode: boolean) => ({ + display: 'flex', + alignItems: 'center', + borderRadius: '5px', + padding: '8px', + marginRight: '10px', + color: darkMode ? '#ffffff' : '#333333', + '&:hover': { + backgroundColor: darkMode ? '#333' : '#F5F5F5', + color: '#ff00c3' + } + }), + discardButton: { + borderRadius: '5px', + padding: '8px', + background: 'red', + color: 'white', + marginRight: '10px', + '&:hover': { + color: 'white', + backgroundColor: '#ff0000' + } + } +}; + +// Styled Components const NavBarWrapper = styled.div<{ mode: 'light' | 'dark' }>` grid-area: navbar; background-color: ${({ mode }) => (mode === 'dark' ? '#1e2124' : '#ffffff')}; @@ -159,7 +235,27 @@ const NavBarWrapper = styled.div<{ mode: 'light' | 'dark' }>` border-bottom: 1px solid ${({ mode }) => (mode === 'dark' ? '#333' : '#e0e0e0')}; `; -const ProjectName = styled.b<{ mode: 'light' | 'dark' }>` - color: ${({ mode }) => (mode === 'dark' ? 'white' : 'black')}; - font-size: 1.3em; +const BrandContainer = styled.div` + display: flex; + justify-content: flex-start; `; + +const LogoImage = styled.img.attrs({ + width: 45, + height: 40, +})` + border-radius: 5px; + margin: 5px 0px 5px 15px; +`; + +const ProjectName = styled.b<{ mode: 'light' | 'dark' }>` + color: ${({ mode }) => (mode === 'dark' ? 'white' : '#333333')}; + font-size: 1.3em; + padding: 11px; +`; + +const ControlsContainer = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; +`; \ No newline at end of file diff --git a/src/components/molecules/RunContent.tsx b/src/components/molecules/RunContent.tsx index ff414628..b9c0f5fe 100644 --- a/src/components/molecules/RunContent.tsx +++ b/src/components/molecules/RunContent.tsx @@ -75,10 +75,50 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - setTab(newTab)} aria-label="run-content-tabs"> - - - + setTab(newTab)} + aria-label="run-content-tabs" + sx={{ + // Remove the default blue indicator + '& .MuiTabs-indicator': { + backgroundColor: '#FF00C3', // Change to pink + }, + // Remove default transition effects + '& .MuiTab-root': { + '&.Mui-selected': { + color: '#FF00C3', + }, + } + }} +> + theme.palette.mode === 'dark' ? '#fff' : '#000', + '&:hover': { + color: '#FF00C3' + }, + '&.Mui-selected': { + color: '#FF00C3', + } + }} + /> + theme.palette.mode === 'dark' ? '#fff' : '#000', + '&:hover': { + color: '#FF00C3' + }, + '&.Mui-selected': { + color: '#FF00C3', + } + }} + /> +
                     {JSON.stringify(row.serializableOutput, null, 2)}
diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx
index 60ef3fa6..da7d3198 100644
--- a/src/components/molecules/SaveRecording.tsx
+++ b/src/components/molecules/SaveRecording.tsx
@@ -76,7 +76,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
 
   return (
     
- diff --git a/src/components/organisms/BrowserContent.tsx b/src/components/organisms/BrowserContent.tsx index 11af4f2f..07410bd6 100644 --- a/src/components/organisms/BrowserContent.tsx +++ b/src/components/organisms/BrowserContent.tsx @@ -12,6 +12,7 @@ import { } from "../../api/recording"; import { Box } from "@mui/material"; import { InterpretationLog } from "../molecules/InterpretationLog"; +import { Height } from "@mui/icons-material"; // TODO: Tab !show currentUrl after recordingUrl global state export const BrowserContent = () => { @@ -139,7 +140,7 @@ export const BrowserContent = () => { }, [handleUrlChanged]); return ( -
+
{ // todo: use width from browser dimension once fixed browserWidth={900} handleUrlChanged={handleUrlChanged} + /> - +
); }; -const BrowserContentWrapper = styled.div``; +const BrowserContentWrapper = styled.div` + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden;`; diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 697b4adb..30e67fa4 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -319,7 +319,7 @@ export const BrowserWindow = () => { return ( -
+
{ getText === true || getList === true ? ( = ({ onFinishCapture const theme = useThemeMode(); const isDarkMode = theme.darkMode; return ( - + {/* Last action: {` ${lastAction}`} */} @@ -484,7 +499,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture {browserSteps.map(step => ( - handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ padding: '10px', margin: '11px', borderRadius: '5px', position: 'relative', background: 'white' }}> + handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ padding: '10px', margin: '11px', borderRadius: '5px', position: 'relative', background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }}> { step.type === 'text' && ( <> @@ -520,7 +535,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} - sx={{ background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }} + /> {!confirmedTextSteps[step.id] && ( @@ -542,7 +557,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture <> List Selected Successfully {Object.entries(step.fields).map(([key, field]) => ( - + = ({ onFinishCapture ) }} - style={{ background: isDarkMode ? "#1E2124" : 'white' }} + /> = ({ onFinishCapture ) }} - style={{ background: isDarkMode ? "#1E2124" : 'white' }} + /> {!confirmedListTextFields[step.id]?.[key] && ( diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index e82d1f74..8f9eeda0 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -59,7 +59,9 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { useEffect(() => { if (darkMode) { + document.body.style.background = 'rgba(18,18,18,1)'; + } else { 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%)'; } From e9e9070fe23b2a7887c924f516a15842c59702a3 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 24 Nov 2024 01:10:22 +0530 Subject: [PATCH 010/199] tab indicator default color changed to ff00c3 --- src/components/organisms/MainMenu.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index b8f2aaa8..27c42fe1 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -43,6 +43,13 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu value={value} onChange={handleChange} orientation="vertical" + TabIndicatorProps={{ + style: { + backgroundColor: '#ff00c3', // Set the custom color for the indicator here + width: '2px', // Ensure the indicator width is 2px as per your requirement + right: 0, // Position it on the right if needed + }, + }} sx={{ alignItems: 'flex-start', '& .MuiTab-root': { @@ -54,6 +61,10 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu '&.Mui-selected': { color: selectedPink, // Darker pink for selected tab }, + '& .MuiTabs-indicator': { + backgroundColor: '#ff00c3', // Custom color for the indicator + }, + }, }} > @@ -62,24 +73,28 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu label="Robots" icon={} iconPosition="start" + /> } iconPosition="start" + /> } iconPosition="start" + /> } iconPosition="start" + />
From 3cf0786389d00bbcaaea71f67d1ce7645c0babe4 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 24 Nov 2024 19:36:07 +0530 Subject: [PATCH 011/199] pinkish buttons --- src/components/organisms/BrowserContent.tsx | 2 +- src/components/organisms/RightSidePanel.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/organisms/BrowserContent.tsx b/src/components/organisms/BrowserContent.tsx index 07410bd6..fcb45087 100644 --- a/src/components/organisms/BrowserContent.tsx +++ b/src/components/organisms/BrowserContent.tsx @@ -140,7 +140,7 @@ export const BrowserContent = () => { }, [handleUrlChanged]); return ( -
+
= ({ onFinishCapture */} - {!getText && !getScreenshot && !getList && showCaptureList && } + {!getText && !getScreenshot && !getList && showCaptureList && } {getList && ( <> @@ -479,7 +479,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> @@ -488,7 +488,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } - {!getText && !getScreenshot && !getList && showCaptureScreenshot && } + {!getText && !getScreenshot && !getList && showCaptureScreenshot && } {getScreenshot && ( From 587dacdaf14cbba65c84321205d0b4c952045780 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 8 Dec 2024 04:49:29 +0530 Subject: [PATCH 012/199] mui blue color fixes --- .../molecules/IntegrationSettings.tsx | 30 +++++-- src/components/organisms/MainMenu.tsx | 5 ++ src/components/organisms/ProxyForm.tsx | 80 ++++++++++++++++--- src/context/theme-provider.tsx | 44 +++++++++- src/pages/Login.tsx | 2 +- src/pages/Register.tsx | 2 + 6 files changed, 143 insertions(+), 20 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index c31605de..8e38b5a0 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -14,18 +14,33 @@ import axios from "axios"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRecording } from "../../api/storage"; import { apiUrl } from "../../apiConfig.js"; -import Cookies from 'js-cookie'; + interface IntegrationProps { isOpen: boolean; handleStart: (data: IntegrationSettings) => void; handleClose: () => void; } + export interface IntegrationSettings { spreadsheetId: string; spreadsheetName: string; data: string; } +// Helper functions to replace js-cookie functionality +const getCookie = (name: string): string | null => { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return parts.pop()?.split(';').shift() || null; + } + return null; +}; + +const removeCookie = (name: string): void => { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; +}; + export const IntegrationSettingsModal = ({ isOpen, handleStart, @@ -106,7 +121,7 @@ export const IntegrationSettingsModal = ({ }, { withCredentials: true } ); - notify(`success`, `Google Sheet selected successfully`) + notify(`success`, `Google Sheet selected successfully`); console.log("Google Sheet ID updated:", response.data); } catch (error: any) { console.error( @@ -137,14 +152,14 @@ export const IntegrationSettingsModal = ({ useEffect(() => { // Check if there is a success message in cookies - const status = Cookies.get("robot_auth_status"); - const message = Cookies.get("robot_auth_message"); + const status = getCookie("robot_auth_status"); + const message = getCookie("robot_auth_message"); if (status === "success" && message) { notify("success", message); // Clear the cookies after reading - Cookies.remove("robot_auth_status"); - Cookies.remove("robot_auth_message"); + removeCookie("robot_auth_status"); + removeCookie("robot_auth_message"); } // Check if we're on the callback URL @@ -177,7 +192,6 @@ export const IntegrationSettingsModal = ({ > Integrate with Google Sheet{" "} - {/* */} {recording && recording.google_sheet_id ? ( @@ -320,4 +334,4 @@ export const modalStyle = { height: "fit-content", display: "block", padding: "20px", -}; +}; \ No newline at end of file diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index 27c42fe1..60c9a082 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -124,4 +124,9 @@ const buttonStyles = { alignItems: 'center', textTransform: 'none', color: 'inherit', + backgroundColor: 'transparent', + '&:hover': { + backgroundColor: 'rgba(255, 0, 195, 0.1)', + color: '#ff00c3', + }, }; diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index a581144b..acd71e4e 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -1,8 +1,48 @@ import React, { useState, useEffect } from 'react'; import { styled } from '@mui/system'; -import { Alert, AlertTitle, TextField, Button, Switch, FormControlLabel, Box, Typography, Tabs, Tab, Table, TableContainer, TableHead, TableRow, TableBody, TableCell, Paper } from '@mui/material'; +import { + Alert, + AlertTitle, + TextField, + Button, + Switch, + FormControlLabel, + Box, + Typography, + Tabs, + Tab, + Table, + TableContainer, + TableHead, + TableRow, + TableBody, + TableCell, + Paper +} from '@mui/material'; import { sendProxyConfig, getProxyConfig, testProxyConfig, deleteProxyConfig } from '../../api/proxy'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useThemeMode } from '../../context/theme-provider'; + +// Custom styled Tabs component +const CustomTabs = styled(Tabs)(({ theme }) => ({ + '& .MuiTabs-indicator': { + backgroundColor: '#ff00c3', // Pink indicator + }, +})); + +// Custom styled Tab component +const CustomTab = styled(Tab)(({ theme }) => ({ + '&.Mui-selected': { + color: '#ff00c3', // Pink for selected tab + }, + '&:hover': { + color: '#ff00c3', // Pink on hover + // Subtle hover effect + }, + '&.MuiTab-root': { + textTransform: 'none', // Removes uppercase transformation + }, +})); const FormContainer = styled(Box)({ display: 'flex', @@ -132,16 +172,37 @@ const ProxyForm: React.FC = () => { fetchProxyConfig(); }, []); + const theme = useThemeMode(); + const isDarkMode = theme.darkMode; + return ( <> Proxy Configuration - - - - + + + + {tabIndex === 0 && ( isProxyConfigured ? ( @@ -236,15 +297,15 @@ const ProxyForm: React.FC = () => { Coming Soon - In Open Source (Basic Rotation) & Cloud (Advanced Rotation). If you don't want to manage the infrastructure, join our cloud waitlist to get early access. - )} - - If your proxy requires a username and password, always provide them separately from the proxy URL. + + If your proxy requires a username and password, always provide them separately from the proxy URL.
The right way
@@ -258,9 +319,10 @@ const ProxyForm: React.FC = () => { The wrong way
Proxy URL: http://myusername:mypassword@proxy.com:1337 +
); }; -export default ProxyForm; +export default ProxyForm; \ No newline at end of file diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx index 02052cf5..a6097f95 100644 --- a/src/context/theme-provider.tsx +++ b/src/context/theme-provider.tsx @@ -6,7 +6,7 @@ const lightTheme = createTheme({ palette: { mode: 'light', primary: { - main: '#1e88e5', + main: '#ff00c3', // Pink as the primary color }, background: { default: '#ffffff', @@ -16,13 +16,33 @@ const lightTheme = createTheme({ primary: '#000000', }, }, + components: { + MuiTabs: { + styleOverrides: { + indicator: { + backgroundColor: '#ff00c3', // Pink for tab indicators + }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + backgroundColor: '#ff00c3', // Pink button background + color: '#ffffff', + '&:hover': { + backgroundColor: '#e600b3', // Slightly darker pink on hover + }, + }, + }, + }, + }, }); const darkTheme = createTheme({ palette: { mode: 'dark', primary: { - main: '#90caf9', + main: '#ff00c3', // Pink as the primary color }, background: { default: '#121212', @@ -32,6 +52,26 @@ const darkTheme = createTheme({ primary: '#ffffff', }, }, + components: { + MuiTabs: { + styleOverrides: { + indicator: { + backgroundColor: '#ff00c3', // Pink for tab indicators + }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + backgroundColor: '#ff00c3', // Pink button background + color: '#ffffff', + '&:hover': { + backgroundColor: '#e600b3', // Slightly darker pink on hover + }, + }, + }, + }, + }, }); const ThemeModeContext = createContext({ diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 87f90b53..de5f4838 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -38,7 +38,7 @@ const Login = () => { const { data } = await axios.post(`${apiUrl}/auth/login`, { email, password, - }); + }, { withCredentials: true }); dispatch({ type: "LOGIN", payload: data }); notify("success", "Welcome to Maxun!"); window.localStorage.setItem("user", JSON.stringify(data)); diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index b2a3eebf..ad7fb89f 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -39,6 +39,8 @@ const Register = () => { email, password, }); + + console.log(data) dispatch({ type: "LOGIN", payload: data }); notify("success", "Registration Successful!"); window.localStorage.setItem("user", JSON.stringify(data)); From 655eadc068820b34504226ba51d32b012f5760a0 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 8 Dec 2024 05:41:11 +0530 Subject: [PATCH 013/199] forms --- src/components/molecules/NavBar.tsx | 2 +- src/pages/Login.tsx | 186 +++++++++++++++++----------- src/pages/PageWrappper.tsx | 3 + src/pages/Register.tsx | 87 +++++++++---- 4 files changed, 175 insertions(+), 103 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index d8831ea6..d7cb726c 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -258,4 +258,4 @@ const ControlsContainer = styled.div` display: flex; align-items: center; justify-content: flex-end; -`; \ No newline at end of file +`; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index de5f4838..12f49e57 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,22 +1,21 @@ import axios from "axios"; -import { useState, useContext, useEffect, FormEvent } from "react"; +import { useState, useContext, useEffect } from "react"; import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; -import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material"; +import { Box, Typography, TextField, Button, CircularProgress } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; +import { useThemeMode } from "../context/theme-provider"; const Login = () => { - const [form, setForm] = useState({ - email: "", - password: "", - }); + const [form, setForm] = useState({ email: "", password: "" }); const [loading, setLoading] = useState(false); const { notify } = useGlobalInfoStore(); const { email, password } = form; const { state, dispatch } = useContext(AuthContext); const { user } = state; + const { darkMode } = useThemeMode(); const navigate = useNavigate(); @@ -35,10 +34,11 @@ const Login = () => { e.preventDefault(); setLoading(true); try { - const { data } = await axios.post(`${apiUrl}/auth/login`, { - email, - password, - }, { withCredentials: true }); + const { data } = await axios.post( + `${apiUrl}/auth/login`, + { email, password }, + { withCredentials: true } + ); dispatch({ type: "LOGIN", payload: data }); notify("success", "Welcome to Maxun!"); window.localStorage.setItem("user", JSON.stringify(data)); @@ -58,76 +58,112 @@ const Login = () => { maxHeight: "100vh", mt: 6, padding: 4, + backgroundColor: darkMode ? "#121212" : "#ffffff", + }} > - - + logo + + Welcome Back! + + + + - - Don’t have an account?{" "} - - Register - - - -
- + {loading ? ( + <> + + Loading + + ) : ( + "Login" + )} + + + Don’t have an account?{" "} + + Register + + +
+
); }; diff --git a/src/pages/PageWrappper.tsx b/src/pages/PageWrappper.tsx index 87157349..5a00cc4a 100644 --- a/src/pages/PageWrappper.tsx +++ b/src/pages/PageWrappper.tsx @@ -12,6 +12,7 @@ import Login from './Login'; import Register from './Register'; import UserRoute from '../routes/userRoute'; import { Routes, Route, useNavigate } from 'react-router-dom'; +import { AppBar } from '@mui/material'; export const PageWrapper = () => { const [open, setOpen] = useState(false); @@ -50,7 +51,9 @@ export const PageWrapper = () => { + {!browserId && } + }> } /> diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index ad7fb89f..474f0182 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -5,18 +5,17 @@ import { AuthContext } from "../context/auth"; import { Box, Typography, TextField, Button, CircularProgress } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; +import { useThemeMode } from "../context/theme-provider"; const Register = () => { - const [form, setForm] = useState({ - email: "", - password: "", - }); + const [form, setForm] = useState({ email: "", password: "" }); const [loading, setLoading] = useState(false); const { notify } = useGlobalInfoStore(); const { email, password } = form; const { state, dispatch } = useContext(AuthContext); const { user } = state; + const { darkMode } = useThemeMode(); const navigate = useNavigate(); @@ -35,18 +34,14 @@ const Register = () => { e.preventDefault(); setLoading(true); try { - const { data } = await axios.post(`${apiUrl}/auth/register`, { - email, - password, - }); - - console.log(data) + const { data } = await axios.post(`${apiUrl}/auth/register`, { email, password }); + console.log(data); dispatch({ type: "LOGIN", payload: data }); notify("success", "Registration Successful!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); - } catch (error:any) { - notify("error", error.response.data || "Registration Failed. Please try again."); + } catch (error: any) { + notify("error", error.response?.data || "Registration Failed. Please try again."); setLoading(false); } }; @@ -60,25 +55,38 @@ const Register = () => { maxHeight: "100vh", mt: 6, padding: 4, + backgroundColor: darkMode ? "#121212" : "#ffffff", + }} > - logo + logo Create an Account @@ -91,6 +99,15 @@ const Register = () => { margin="normal" variant="outlined" required + sx={{ + input: { color: darkMode ? "#ffffff" : "#000000" }, + label: { color: darkMode ? "#bbbbbb" : "#000000" }, + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: darkMode ? "#555555" : "#cccccc" }, + "&:hover fieldset": { borderColor: darkMode ? "#ffffff" : "#000000" }, + "&.Mui-focused fieldset": { borderColor: "#ff33cc" }, + }, + }} /> { margin="normal" variant="outlined" required + sx={{ + input: { color: darkMode ? "#ffffff" : "#000000" }, + label: { color: darkMode ? "#bbbbbb" : "#000000" }, + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: darkMode ? "#555555" : "#cccccc" }, + "&:hover fieldset": { borderColor: darkMode ? "#ffffff" : "#000000" }, + "&.Mui-focused fieldset": { borderColor: "#ff33cc" }, + }, + }} /> - + Already have an account?{" "} Login From f11b36ba4ef04d691e88ef6b979bb143a00fbfa4 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sun, 8 Dec 2024 05:50:25 +0530 Subject: [PATCH 014/199] forms --- src/pages/Register.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 626921f0..474f0182 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -40,10 +40,8 @@ const Register = () => { notify("success", "Registration Successful!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); - } catch (error: any) { notify("error", error.response?.data || "Registration Failed. Please try again."); - } setLoading(false); } }; From a5a35c1c35521d5485ebb87539e74a859c902869 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 28 Dec 2024 22:44:21 +0530 Subject: [PATCH 015/199] feat: add language option --- src/components/molecules/NavBar.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 142d45ab..a953b178 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -376,6 +376,14 @@ export const NavBar: React.FC = ({ > Deutsch + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Add Language + From 5419b70f616c4046bd0e65c8b917671b3ed39600 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 28 Dec 2024 22:44:41 +0530 Subject: [PATCH 016/199] feat: add language option --- src/components/molecules/NavBar.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index a953b178..d0c084a7 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -470,6 +470,14 @@ export const NavBar: React.FC = ({ > Deutsch + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Add Language + )} From 1630e0eb7b8cb6dfab0c9b503ca42fed845dc4ce Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 28 Dec 2024 22:44:53 +0530 Subject: [PATCH 017/199] fix: format --- src/components/molecules/NavBar.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index d0c084a7..6daf53e0 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -471,13 +471,13 @@ export const NavBar: React.FC = ({ Deutsch { - changeLanguage("de"); - handleMenuClose(); - }} - > - Add Language - + onClick={() => { + changeLanguage("de"); + handleMenuClose(); + }} + > + Add Language + )} From 6ac7068f45f7493136897ec54e0651f6f8778f81 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 28 Dec 2024 22:54:28 +0530 Subject: [PATCH 018/199] chore: i18n docs link to do --- src/components/molecules/NavBar.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 6daf53e0..be9f8acd 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -376,6 +376,7 @@ export const NavBar: React.FC = ({ > Deutsch + {/* WIP: Replace change language with i18n docs link */} { changeLanguage("de"); @@ -470,6 +471,7 @@ export const NavBar: React.FC = ({ > Deutsch + {/* WIP: Replace change language with i18n docs link */} { changeLanguage("de"); From edfcd8f869f194f7525744d050e30ba81a8bafef Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 2 Jan 2025 23:15:03 +0530 Subject: [PATCH 019/199] fix: format --- src/components/organisms/BrowserWindow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index b69a0921..421bb680 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -326,7 +326,6 @@ export const BrowserWindow = () => { } }, [paginationMode, resetPaginationSelector]); - return (
{ From 230f40bc9a8356215be7999ad47c59ba3a72b52c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 4 Jan 2025 15:09:09 +0530 Subject: [PATCH 020/199] feat: monitor web recorder performance --- perf/performance.ts | 181 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 perf/performance.ts diff --git a/perf/performance.ts b/perf/performance.ts new file mode 100644 index 00000000..c50ef850 --- /dev/null +++ b/perf/performance.ts @@ -0,0 +1,181 @@ +// Frontend Performance Monitoring +export class FrontendPerformanceMonitor { + private metrics: { + fps: number[]; + memoryUsage: MemoryInfo[]; + renderTime: number[]; + eventLatency: number[]; + }; + private lastFrameTime: number; + private frameCount: number; + + constructor() { + this.metrics = { + fps: [], + memoryUsage: [], + renderTime: [], + eventLatency: [], + }; + this.lastFrameTime = performance.now(); + this.frameCount = 0; + + // Start monitoring + this.startMonitoring(); + } + + private startMonitoring(): void { + // Monitor FPS + const measureFPS = () => { + const currentTime = performance.now(); + const elapsed = currentTime - this.lastFrameTime; + this.frameCount++; + + if (elapsed >= 1000) { // Calculate FPS every second + const fps = Math.round((this.frameCount * 1000) / elapsed); + this.metrics.fps.push(fps); + this.frameCount = 0; + this.lastFrameTime = currentTime; + } + requestAnimationFrame(measureFPS); + }; + requestAnimationFrame(measureFPS); + + // Monitor Memory Usage + if (window.performance && (performance as any).memory) { + setInterval(() => { + const memory = (performance as any).memory; + this.metrics.memoryUsage.push({ + usedJSHeapSize: memory.usedJSHeapSize, + totalJSHeapSize: memory.totalJSHeapSize, + timestamp: Date.now() + }); + }, 1000); + } + } + + // Monitor Canvas Render Time + public measureRenderTime(renderFunction: () => void): void { + const startTime = performance.now(); + renderFunction(); + const endTime = performance.now(); + this.metrics.renderTime.push(endTime - startTime); + } + + // Monitor Event Latency + public measureEventLatency(event: MouseEvent | KeyboardEvent): void { + const latency = performance.now() - event.timeStamp; + this.metrics.eventLatency.push(latency); + } + + // Get Performance Report + public getPerformanceReport(): PerformanceReport { + return { + averageFPS: this.calculateAverage(this.metrics.fps), + averageRenderTime: this.calculateAverage(this.metrics.renderTime), + averageEventLatency: this.calculateAverage(this.metrics.eventLatency), + memoryTrend: this.getMemoryTrend(), + lastMemoryUsage: this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1] + }; + } + + private calculateAverage(array: number[]): number { + return array.length ? array.reduce((a, b) => a + b) / array.length : 0; + } + + private getMemoryTrend(): MemoryTrend { + if (this.metrics.memoryUsage.length < 2) return 'stable'; + const latest = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1]; + const previous = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 2]; + const change = latest.usedJSHeapSize - previous.usedJSHeapSize; + if (change > 1000000) return 'increasing'; // 1MB threshold + if (change < -1000000) return 'decreasing'; + return 'stable'; + } +} + +// Backend Performance Monitoring +export class BackendPerformanceMonitor { + private metrics: { + screenshotTimes: number[]; + emitTimes: number[]; + memoryUsage: NodeJS.MemoryUsage[]; + }; + + constructor() { + this.metrics = { + screenshotTimes: [], + emitTimes: [], + memoryUsage: [] + }; + this.startMonitoring(); + } + + private startMonitoring(): void { + // Monitor Memory Usage + setInterval(() => { + this.metrics.memoryUsage.push(process.memoryUsage()); + }, 1000); + } + + public async measureScreenshotPerformance( + makeScreenshot: () => Promise + ): Promise { + const startTime = process.hrtime(); + await makeScreenshot(); + const [seconds, nanoseconds] = process.hrtime(startTime); + this.metrics.screenshotTimes.push(seconds * 1000 + nanoseconds / 1000000); + } + + public measureEmitPerformance(emitFunction: () => void): void { + const startTime = process.hrtime(); + emitFunction(); + const [seconds, nanoseconds] = process.hrtime(startTime); + this.metrics.emitTimes.push(seconds * 1000 + nanoseconds / 1000000); + } + + public getPerformanceReport(): BackendPerformanceReport { + return { + averageScreenshotTime: this.calculateAverage(this.metrics.screenshotTimes), + averageEmitTime: this.calculateAverage(this.metrics.emitTimes), + currentMemoryUsage: this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1], + memoryTrend: this.getMemoryTrend() + }; + } + + private calculateAverage(array: number[]): number { + return array.length ? array.reduce((a, b) => a + b) / array.length : 0; + } + + private getMemoryTrend(): MemoryTrend { + if (this.metrics.memoryUsage.length < 2) return 'stable'; + const latest = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1]; + const previous = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 2]; + const change = latest.heapUsed - previous.heapUsed; + if (change > 1000000) return 'increasing'; + if (change < -1000000) return 'decreasing'; + return 'stable'; + } +} + +interface MemoryInfo { + usedJSHeapSize: number; + totalJSHeapSize: number; + timestamp: number; +} + +type MemoryTrend = 'increasing' | 'decreasing' | 'stable'; + +interface PerformanceReport { + averageFPS: number; + averageRenderTime: number; + averageEventLatency: number; + memoryTrend: MemoryTrend; + lastMemoryUsage: MemoryInfo; +} + +interface BackendPerformanceReport { + averageScreenshotTime: number; + averageEmitTime: number; + currentMemoryUsage: NodeJS.MemoryUsage; + memoryTrend: MemoryTrend; +} \ No newline at end of file From e20a4073de08931b947a8461db0c8c4eba681ad3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 4 Jan 2025 15:57:18 +0530 Subject: [PATCH 021/199] feat: monitor web recorder frontend performance --- src/components/atoms/canvas.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index e71a4d93..24e03bae 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -7,6 +7,7 @@ import DatePicker from './DatePicker'; import Dropdown from './Dropdown'; import TimePicker from './TimePicker'; import DateTimeLocalPicker from './DateTimeLocalPicker'; +import { FrontendPerformanceMonitor } from '../../../perf/performance'; interface CreateRefCallback { (ref: React.RefObject): void; @@ -28,6 +29,9 @@ export interface Coordinates { const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { + const performanceMonitor = useRef(new FrontendPerformanceMonitor()); + console.log('Frontend Performance Report:', performanceMonitor.current.getPerformanceReport()); + const canvasRef = useRef(null); const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); @@ -111,6 +115,7 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { }, [socket]); const onMouseEvent = useCallback((event: MouseEvent) => { + performanceMonitor.current.measureEventLatency(event); if (socket && canvasRef.current) { // Get the canvas bounding rectangle const rect = canvasRef.current.getBoundingClientRect(); @@ -177,6 +182,15 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { } }, [socket]); + // performance logging + useEffect(() => { + const intervalId = setInterval(() => { + const report = performanceMonitor.current.getPerformanceReport(); + console.log('Frontend Performance Report:', report); + }, 5000); + + return () => clearInterval(intervalId); + }, []); useEffect(() => { if (canvasRef.current) { From 0da5cb0f67575226d8a2dbab8cfeb114e7778678 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 4 Jan 2025 15:57:41 +0530 Subject: [PATCH 022/199] feat: monitor web recorder backend performance --- .../classes/RemoteBrowser.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 2c45d146..2b80b037 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -16,6 +16,7 @@ import { WorkflowGenerator } from "../../workflow-management/classes/Generator"; import { WorkflowInterpreter } from "../../workflow-management/classes/Interpreter"; import { getDecryptedProxyConfig } from '../../routes/proxy'; import { getInjectableScript } from 'idcac-playwright'; +import { BackendPerformanceMonitor } from '../../../../perf/performance' chromium.use(stealthPlugin()); @@ -78,6 +79,17 @@ export class RemoteBrowser { */ public interpreter: WorkflowInterpreter; + private performanceMonitor: BackendPerformanceMonitor; + + private startPerformanceReporting() { + setInterval(() => { + const report = this.performanceMonitor.getPerformanceReport(); + + console.log('Backend Performance Report:', report); + + }, 5000); + } + /** * Initializes a new instances of the {@link Generator} and {@link WorkflowInterpreter} classes and * assigns the socket instance everywhere. @@ -88,6 +100,8 @@ export class RemoteBrowser { this.socket = socket; this.interpreter = new WorkflowInterpreter(socket); this.generator = new WorkflowGenerator(socket); + this.performanceMonitor = new BackendPerformanceMonitor(); + this.startPerformanceReporting(); } /** @@ -519,8 +533,10 @@ export class RemoteBrowser { * @returns void */ private emitScreenshot = (payload: any): void => { - const dataWithMimeType = ('data:image/jpeg;base64,').concat(payload); - this.socket.emit('screencast', dataWithMimeType); - logger.log('debug', `Screenshot emitted`); + this.performanceMonitor.measureEmitPerformance(() => { + const dataWithMimeType = ('data:image/jpeg;base64,').concat(payload); + this.socket.emit('screencast', dataWithMimeType); + logger.log('debug', `Screenshot emitted`); + }); }; } From e74616a17734d8c9854f04e4e21b6a79f912fb6e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 5 Jan 2025 22:23:52 +0530 Subject: [PATCH 023/199] feat: reduce memory usage by canvas --- src/components/atoms/canvas.tsx | 434 ++++++++++++++++++-------------- 1 file changed, 251 insertions(+), 183 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 24e03bae..3cb8c090 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, useRef } from 'react'; +// Canvas.tsx +import React, { useCallback, useEffect, useRef, useMemo } from 'react'; import { useSocketStore } from '../../context/socket'; -import { getMappedCoordinates } from "../../helpers/inputHelpers"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; import DatePicker from './DatePicker'; @@ -9,6 +9,7 @@ import TimePicker from './TimePicker'; import DateTimeLocalPicker from './DateTimeLocalPicker'; import { FrontendPerformanceMonitor } from '../../../perf/performance'; +// Types interface CreateRefCallback { (ref: React.RefObject): void; } @@ -19,246 +20,313 @@ interface CanvasProps { onCreateRef: CreateRefCallback; } -/** - * Interface for mouse's x,y coordinates - */ export interface Coordinates { x: number; y: number; +} + +interface DropdownOption { + value: string; + text: string; + disabled: boolean; + selected: boolean; +} + +interface CanvasState { + datePickerInfo: { + coordinates: Coordinates; + selector: string; + } | null; + dropdownInfo: { + coordinates: Coordinates; + selector: string; + options: DropdownOption[]; + } | null; + timePickerInfo: { + coordinates: Coordinates; + selector: string; + } | null; + dateTimeLocalInfo: { + coordinates: Coordinates; + selector: string; + } | null; +} + +type CanvasAction = + | { type: 'SET_DATE_PICKER'; payload: CanvasState['datePickerInfo'] } + | { type: 'SET_DROPDOWN'; payload: CanvasState['dropdownInfo'] } + | { type: 'SET_TIME_PICKER'; payload: CanvasState['timePickerInfo'] } + | { type: 'SET_DATETIME_PICKER'; payload: CanvasState['dateTimeLocalInfo'] }; + +// Helper functions +const throttle = any>(func: T, limit: number): T => { + let inThrottle = false; + return ((...args: Parameters): ReturnType | void => { + if (!inThrottle) { + func.apply(null, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }) as T; }; -const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { +const createOffscreenCanvas = (width: number, height: number) => { + if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(width, height); + } + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +}; +// Reducer +const canvasReducer = (state: CanvasState, action: CanvasAction): CanvasState => { + switch (action.type) { + case 'SET_DATE_PICKER': + return { ...state, datePickerInfo: action.payload }; + case 'SET_DROPDOWN': + return { ...state, dropdownInfo: action.payload }; + case 'SET_TIME_PICKER': + return { ...state, timePickerInfo: action.payload }; + case 'SET_DATETIME_PICKER': + return { ...state, dateTimeLocalInfo: action.payload }; + default: + return state; + } +}; + +// Main Component +const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { + // Refs const performanceMonitor = useRef(new FrontendPerformanceMonitor()); - console.log('Frontend Performance Report:', performanceMonitor.current.getPerformanceReport()); - const canvasRef = useRef(null); + const lastMousePosition = useRef({ x: 0, y: 0 }); + const frameRequest = useRef(); + const renderingContext = useRef(null); + const offscreenCanvas = useRef( + createOffscreenCanvas(width || 900, height || 400) + ); + + // Hooks const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); const getTextRef = useRef(getText); const getListRef = useRef(getList); - const [datePickerInfo, setDatePickerInfo] = React.useState<{ - coordinates: Coordinates; - selector: string; - } | null>(null); + // State + const [state, dispatch] = React.useReducer(canvasReducer, { + datePickerInfo: null, + dropdownInfo: null, + timePickerInfo: null, + dateTimeLocalInfo: null + }); - const [dropdownInfo, setDropdownInfo] = React.useState<{ - coordinates: Coordinates; - selector: string; - options: Array<{ - value: string; - text: string; - disabled: boolean; - selected: boolean; - }>; - } | null>(null); + // Memoized values + const canvasSize = useMemo(() => ({ + width: width || 900, + height: height || 400 + }), [width, height]); - const [timePickerInfo, setTimePickerInfo] = React.useState<{ - coordinates: Coordinates; - selector: string; - } | null>(null); - - const [dateTimeLocalInfo, setDateTimeLocalInfo] = React.useState<{ - coordinates: Coordinates; - selector: string; - } | null>(null); - - const notifyLastAction = (action: string) => { + const notifyLastAction = useCallback((action: string) => { if (lastAction !== action) { setLastAction(action); } - }; + }, [lastAction, setLastAction]); - const lastMousePosition = useRef({ x: 0, y: 0 }); + // Socket event handlers + const socketHandlers = useMemo(() => ({ + showDatePicker: (info: CanvasState['datePickerInfo']) => { + dispatch({ type: 'SET_DATE_PICKER', payload: info }); + }, + showDropdown: (info: CanvasState['dropdownInfo']) => { + dispatch({ type: 'SET_DROPDOWN', payload: info }); + }, + showTimePicker: (info: CanvasState['timePickerInfo']) => { + dispatch({ type: 'SET_TIME_PICKER', payload: info }); + }, + showDateTimePicker: (info: CanvasState['dateTimeLocalInfo']) => { + dispatch({ type: 'SET_DATETIME_PICKER', payload: info }); + } + }), []); + // Event handlers + const handleMouseMove = useCallback( + throttle((coordinates: Coordinates) => { + if (!socket) return; + + if ( + lastMousePosition.current.x !== coordinates.x || + lastMousePosition.current.y !== coordinates.y + ) { + lastMousePosition.current = coordinates; + socket.emit('input:mousemove', coordinates); + notifyLastAction('move'); + } + }, 16), + [socket, notifyLastAction] + ); + + const onMouseEvent = useCallback((event: MouseEvent) => { + performanceMonitor.current.measureEventLatency(event); + if (!socket || !canvasRef.current) return; + + const rect = canvasRef.current.getBoundingClientRect(); + const clickCoordinates = { + x: event.clientX - rect.left, + y: event.clientY - rect.top, + }; + + switch (event.type) { + case 'mousedown': + if (getTextRef.current) { + console.log('Capturing Text...'); + } else if (getListRef.current) { + console.log('Capturing List...'); + } else { + socket.emit('input:mousedown', clickCoordinates); + } + notifyLastAction('click'); + break; + + case 'mousemove': + handleMouseMove(clickCoordinates); + break; + + case 'wheel': + if (frameRequest.current) { + cancelAnimationFrame(frameRequest.current); + } + frameRequest.current = requestAnimationFrame(() => { + const wheelEvent = event as WheelEvent; + socket.emit('input:wheel', { + deltaX: Math.round(wheelEvent.deltaX), + deltaY: Math.round(wheelEvent.deltaY), + }); + notifyLastAction('scroll'); + }); + break; + } + }, [socket, handleMouseMove, notifyLastAction]); + + const onKeyboardEvent = useCallback((event: KeyboardEvent) => { + if (!socket) return; + + switch (event.type) { + case 'keydown': + socket.emit('input:keydown', { + key: event.key, + coordinates: lastMousePosition.current + }); + notifyLastAction(`${event.key} pressed`); + break; + + case 'keyup': + socket.emit('input:keyup', event.key); + break; + } + }, [socket, notifyLastAction]); + + // Effects useEffect(() => { getTextRef.current = getText; getListRef.current = getList; }, [getText, getList]); useEffect(() => { - if (socket) { - socket.on('showDatePicker', (info: {coordinates: Coordinates, selector: string}) => { - setDatePickerInfo(info); + if (!socket) return; + + Object.entries(socketHandlers).forEach(([event, handler]) => { + socket.on(event, handler); + }); + + return () => { + Object.keys(socketHandlers).forEach(event => { + socket.off(event); }); + }; + }, [socket, socketHandlers]); - socket.on('showDropdown', (info: { - coordinates: Coordinates, - selector: string, - options: Array<{ - value: string; - text: string; - disabled: boolean; - selected: boolean; - }>; - }) => { - setDropdownInfo(info); - }); - - socket.on('showTimePicker', (info: {coordinates: Coordinates, selector: string}) => { - setTimePickerInfo(info); - }); - - socket.on('showDateTimePicker', (info: {coordinates: Coordinates, selector: string}) => { - setDateTimeLocalInfo(info); - }); - - return () => { - socket.off('showDatePicker'); - socket.off('showDropdown'); - socket.off('showTimePicker'); - socket.off('showDateTimePicker'); - }; - } - }, [socket]); - - const onMouseEvent = useCallback((event: MouseEvent) => { - performanceMonitor.current.measureEventLatency(event); - 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': - if (getTextRef.current === true) { - console.log('Capturing Text...'); - } else if (getListRef.current === true) { - console.log('Capturing List...'); - } else { - socket.emit('input:mousedown', clickCoordinates); - } - notifyLastAction('click'); - break; - case 'mousemove': - if (lastMousePosition.current.x !== clickCoordinates.x || - lastMousePosition.current.y !== clickCoordinates.y) { - lastMousePosition.current = { - x: clickCoordinates.x, - y: clickCoordinates.y, - }; - socket.emit('input:mousemove', { - x: clickCoordinates.x, - y: clickCoordinates.y, - }); - notifyLastAction('move'); - } - break; - case 'wheel': - const wheelEvent = event as WheelEvent; - const deltas = { - deltaX: Math.round(wheelEvent.deltaX), - deltaY: Math.round(wheelEvent.deltaY), - }; - socket.emit('input:wheel', deltas); - notifyLastAction('scroll'); - break; - default: - console.log('Default mouseEvent registered'); - return; - } - } - }, [socket]); - - const onKeyboardEvent = useCallback((event: KeyboardEvent) => { - if (socket) { - switch (event.type) { - case 'keydown': - socket.emit('input:keydown', { key: event.key, coordinates: lastMousePosition.current }); - notifyLastAction(`${event.key} pressed`); - break; - case 'keyup': - socket.emit('input:keyup', event.key); - break; - default: - console.log('Default keyEvent registered'); - return; - } - } - }, [socket]); - - // performance logging useEffect(() => { + const monitor = performanceMonitor.current; const intervalId = setInterval(() => { - const report = performanceMonitor.current.getPerformanceReport(); + const report = monitor.getPerformanceReport(); console.log('Frontend Performance Report:', report); - }, 5000); + }, 10000); - return () => clearInterval(intervalId); + return () => { + clearInterval(intervalId); + if (frameRequest.current) { + cancelAnimationFrame(frameRequest.current); + } + }; }, []); useEffect(() => { - if (canvasRef.current) { - onCreateRef(canvasRef); - canvasRef.current.addEventListener('mousedown', onMouseEvent); - canvasRef.current.addEventListener('mousemove', onMouseEvent); - canvasRef.current.addEventListener('wheel', onMouseEvent, { passive: true }); - canvasRef.current.addEventListener('keydown', onKeyboardEvent); - canvasRef.current.addEventListener('keyup', onKeyboardEvent); + if (!canvasRef.current) return; - return () => { - if (canvasRef.current) { - canvasRef.current.removeEventListener('mousedown', onMouseEvent); - canvasRef.current.removeEventListener('mousemove', onMouseEvent); - canvasRef.current.removeEventListener('wheel', onMouseEvent); - canvasRef.current.removeEventListener('keydown', onKeyboardEvent); - canvasRef.current.removeEventListener('keyup', onKeyboardEvent); - } + renderingContext.current = canvasRef.current.getContext('2d'); + onCreateRef(canvasRef); - }; - } else { - console.log('Canvas not initialized'); - } + const canvas = canvasRef.current; + canvas.addEventListener('mousedown', onMouseEvent); + canvas.addEventListener('mousemove', onMouseEvent); + canvas.addEventListener('wheel', onMouseEvent, { passive: true }); + canvas.addEventListener('keydown', onKeyboardEvent); + canvas.addEventListener('keyup', onKeyboardEvent); - }, [onMouseEvent]); + return () => { + canvas.removeEventListener('mousedown', onMouseEvent); + canvas.removeEventListener('mousemove', onMouseEvent); + canvas.removeEventListener('wheel', onMouseEvent); + canvas.removeEventListener('keydown', onKeyboardEvent); + canvas.removeEventListener('keyup', onKeyboardEvent); + }; + }, [onMouseEvent, onKeyboardEvent, onCreateRef]); return ( -
+
- {datePickerInfo && ( + {state.datePickerInfo && ( setDatePickerInfo(null)} + coordinates={state.datePickerInfo.coordinates} + selector={state.datePickerInfo.selector} + onClose={() => dispatch({ type: 'SET_DATE_PICKER', payload: null })} /> )} - {dropdownInfo && ( + {state.dropdownInfo && ( setDropdownInfo(null)} + coordinates={state.dropdownInfo.coordinates} + selector={state.dropdownInfo.selector} + options={state.dropdownInfo.options} + onClose={() => dispatch({ type: 'SET_DROPDOWN', payload: null })} /> )} - {timePickerInfo && ( + {state.timePickerInfo && ( setTimePickerInfo(null)} + coordinates={state.timePickerInfo.coordinates} + selector={state.timePickerInfo.selector} + onClose={() => dispatch({ type: 'SET_TIME_PICKER', payload: null })} /> )} - {dateTimeLocalInfo && ( + {state.dateTimeLocalInfo && ( setDateTimeLocalInfo(null)} + coordinates={state.dateTimeLocalInfo.coordinates} + selector={state.dateTimeLocalInfo.selector} + onClose={() => dispatch({ type: 'SET_DATETIME_PICKER', payload: null })} /> )}
); +}); -}; - +Canvas.displayName = 'Canvas'; export default Canvas; \ No newline at end of file From f26718f7e2b1ab56eb0c0ab949ea455f5334fb7a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 5 Jan 2025 23:31:11 +0530 Subject: [PATCH 024/199] feat: decreasing memory trend & latency --- src/components/atoms/canvas.tsx | 349 +++++++++++++++----------------- 1 file changed, 165 insertions(+), 184 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 3cb8c090..cd6536a4 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -1,5 +1,5 @@ -// Canvas.tsx import React, { useCallback, useEffect, useRef, useMemo } from 'react'; +import { unstable_batchedUpdates } from 'react-dom'; import { useSocketStore } from '../../context/socket'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; @@ -9,7 +9,36 @@ import TimePicker from './TimePicker'; import DateTimeLocalPicker from './DateTimeLocalPicker'; import { FrontendPerformanceMonitor } from '../../../perf/performance'; -// Types +// Optimized throttle with RAF +const rafThrottle = any>(callback: T) => { + let requestId: number | null = null; + let lastArgs: Parameters; + + const later = () => { + requestId = null; + callback.apply(null, lastArgs); + }; + + return (...args: Parameters) => { + lastArgs = args; + if (requestId === null) { + requestId = requestAnimationFrame(later); + } + }; +}; + +// Cache DOM measurements +let measurementCache = new WeakMap(); +const getBoundingClientRectCached = (element: HTMLElement) => { + let rect = measurementCache.get(element); + if (!rect) { + rect = element.getBoundingClientRect(); + measurementCache.set(element, rect); + } + return rect; +}; + +// Types (kept the same) interface CreateRefCallback { (ref: React.RefObject): void; } @@ -25,256 +54,208 @@ export interface Coordinates { y: number; } -interface DropdownOption { - value: string; - text: string; - disabled: boolean; - selected: boolean; -} - -interface CanvasState { - datePickerInfo: { - coordinates: Coordinates; - selector: string; - } | null; - dropdownInfo: { - coordinates: Coordinates; - selector: string; - options: DropdownOption[]; - } | null; - timePickerInfo: { - coordinates: Coordinates; - selector: string; - } | null; - dateTimeLocalInfo: { - coordinates: Coordinates; - selector: string; - } | null; -} - -type CanvasAction = - | { type: 'SET_DATE_PICKER'; payload: CanvasState['datePickerInfo'] } - | { type: 'SET_DROPDOWN'; payload: CanvasState['dropdownInfo'] } - | { type: 'SET_TIME_PICKER'; payload: CanvasState['timePickerInfo'] } - | { type: 'SET_DATETIME_PICKER'; payload: CanvasState['dateTimeLocalInfo'] }; - -// Helper functions -const throttle = any>(func: T, limit: number): T => { - let inThrottle = false; - return ((...args: Parameters): ReturnType | void => { - if (!inThrottle) { - func.apply(null, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }) as T; +// Batch updates helper +const batchedUpdates = (updates: Array<() => void>) => { + unstable_batchedUpdates(() => { + updates.forEach(update => update()); + }); }; -const createOffscreenCanvas = (width: number, height: number) => { - if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(width, height); - } - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; -}; - -// Reducer -const canvasReducer = (state: CanvasState, action: CanvasAction): CanvasState => { - switch (action.type) { - case 'SET_DATE_PICKER': - return { ...state, datePickerInfo: action.payload }; - case 'SET_DROPDOWN': - return { ...state, dropdownInfo: action.payload }; - case 'SET_TIME_PICKER': - return { ...state, timePickerInfo: action.payload }; - case 'SET_DATETIME_PICKER': - return { ...state, dateTimeLocalInfo: action.payload }; - default: - return state; - } -}; - -// Main Component const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { - // Refs const performanceMonitor = useRef(new FrontendPerformanceMonitor()); const canvasRef = useRef(null); - const lastMousePosition = useRef({ x: 0, y: 0 }); - const frameRequest = useRef(); - const renderingContext = useRef(null); - const offscreenCanvas = useRef( - createOffscreenCanvas(width || 900, height || 400) - ); - - // Hooks const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); - const getTextRef = useRef(getText); - const getListRef = useRef(getList); + + // Use a single ref object to reduce memory allocations + const refs = useRef({ + getText, + getList, + lastMousePosition: { x: 0, y: 0 }, + frameRequest: 0, + eventQueue: [] as Array<() => void>, + isProcessing: false + }); - // State - const [state, dispatch] = React.useReducer(canvasReducer, { + // Consolidated state using a single reducer + const [state, dispatch] = React.useReducer((state: any, action: any) => { + switch (action.type) { + case 'BATCH_UPDATE': + return { ...state, ...action.payload }; + default: + return state; + } + }, { datePickerInfo: null, dropdownInfo: null, timePickerInfo: null, dateTimeLocalInfo: null }); - // Memoized values - const canvasSize = useMemo(() => ({ - width: width || 900, - height: height || 400 - }), [width, height]); + // Process events in batches + const processEventQueue = useCallback(() => { + if (refs.current.isProcessing || refs.current.eventQueue.length === 0) return; + + refs.current.isProcessing = true; + const events = [...refs.current.eventQueue]; + refs.current.eventQueue = []; - const notifyLastAction = useCallback((action: string) => { - if (lastAction !== action) { - setLastAction(action); + batchedUpdates(events.map(event => () => event())); + + refs.current.isProcessing = false; + + if (refs.current.eventQueue.length > 0) { + requestAnimationFrame(processEventQueue); } - }, [lastAction, setLastAction]); + }, []); - // Socket event handlers - const socketHandlers = useMemo(() => ({ - showDatePicker: (info: CanvasState['datePickerInfo']) => { - dispatch({ type: 'SET_DATE_PICKER', payload: info }); - }, - showDropdown: (info: CanvasState['dropdownInfo']) => { - dispatch({ type: 'SET_DROPDOWN', payload: info }); - }, - showTimePicker: (info: CanvasState['timePickerInfo']) => { - dispatch({ type: 'SET_TIME_PICKER', payload: info }); - }, - showDateTimePicker: (info: CanvasState['dateTimeLocalInfo']) => { - dispatch({ type: 'SET_DATETIME_PICKER', payload: info }); - } - }), []); - - // Event handlers - const handleMouseMove = useCallback( - throttle((coordinates: Coordinates) => { + // Optimized mouse move handler using RAF throttle + const handleMouseMove = useMemo( + () => rafThrottle((coordinates: Coordinates) => { if (!socket) return; - if ( - lastMousePosition.current.x !== coordinates.x || - lastMousePosition.current.y !== coordinates.y - ) { - lastMousePosition.current = coordinates; + const current = refs.current.lastMousePosition; + if (current.x !== coordinates.x || current.y !== coordinates.y) { + refs.current.lastMousePosition = coordinates; socket.emit('input:mousemove', coordinates); - notifyLastAction('move'); + refs.current.eventQueue.push(() => setLastAction('move')); + requestAnimationFrame(processEventQueue); } - }, 16), - [socket, notifyLastAction] + }), + [socket, processEventQueue] ); + // Optimized event handler with better performance characteristics const onMouseEvent = useCallback((event: MouseEvent) => { - performanceMonitor.current.measureEventLatency(event); if (!socket || !canvasRef.current) return; - const rect = canvasRef.current.getBoundingClientRect(); - const clickCoordinates = { + performanceMonitor.current.measureEventLatency(event); + const rect = getBoundingClientRectCached(canvasRef.current); + const coordinates = { x: event.clientX - rect.left, y: event.clientY - rect.top, }; switch (event.type) { case 'mousedown': - if (getTextRef.current) { - console.log('Capturing Text...'); - } else if (getListRef.current) { - console.log('Capturing List...'); - } else { - socket.emit('input:mousedown', clickCoordinates); - } - notifyLastAction('click'); + refs.current.eventQueue.push(() => { + if (refs.current.getText) { + console.log('Capturing Text...'); + } else if (refs.current.getList) { + console.log('Capturing List...'); + } else { + socket.emit('input:mousedown', coordinates); + } + setLastAction('click'); + }); break; case 'mousemove': - handleMouseMove(clickCoordinates); + handleMouseMove(coordinates); break; case 'wheel': - if (frameRequest.current) { - cancelAnimationFrame(frameRequest.current); + if (refs.current.frameRequest) { + cancelAnimationFrame(refs.current.frameRequest); } - frameRequest.current = requestAnimationFrame(() => { + refs.current.frameRequest = requestAnimationFrame(() => { const wheelEvent = event as WheelEvent; socket.emit('input:wheel', { deltaX: Math.round(wheelEvent.deltaX), - deltaY: Math.round(wheelEvent.deltaY), + deltaY: Math.round(wheelEvent.deltaY) }); - notifyLastAction('scroll'); + refs.current.eventQueue.push(() => setLastAction('scroll')); }); break; } - }, [socket, handleMouseMove, notifyLastAction]); - const onKeyboardEvent = useCallback((event: KeyboardEvent) => { - if (!socket) return; + requestAnimationFrame(processEventQueue); + }, [socket, handleMouseMove, processEventQueue]); - switch (event.type) { - case 'keydown': - socket.emit('input:keydown', { - key: event.key, - coordinates: lastMousePosition.current - }); - notifyLastAction(`${event.key} pressed`); - break; + // Optimized keyboard handler + const onKeyboardEvent = useMemo( + () => rafThrottle((event: KeyboardEvent) => { + if (!socket) return; - case 'keyup': - socket.emit('input:keyup', event.key); - break; - } - }, [socket, notifyLastAction]); + refs.current.eventQueue.push(() => { + switch (event.type) { + case 'keydown': + socket.emit('input:keydown', { + key: event.key, + coordinates: refs.current.lastMousePosition + }); + setLastAction(`${event.key} pressed`); + break; + case 'keyup': + socket.emit('input:keyup', event.key); + break; + } + }); + requestAnimationFrame(processEventQueue); + }), + [socket, processEventQueue] + ); - // Effects + // Update refs useEffect(() => { - getTextRef.current = getText; - getListRef.current = getList; + refs.current.getText = getText; + refs.current.getList = getList; }, [getText, getList]); + // Socket event setup with optimized cleanup useEffect(() => { if (!socket) return; - Object.entries(socketHandlers).forEach(([event, handler]) => { + const handlers = { + showDatePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { datePickerInfo: info } }), + showDropdown: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { dropdownInfo: info } }), + showTimePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { timePickerInfo: info } }), + showDateTimePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { dateTimeLocalInfo: info } }) + }; + + Object.entries(handlers).forEach(([event, handler]) => { socket.on(event, handler); }); return () => { - Object.keys(socketHandlers).forEach(event => { + Object.keys(handlers).forEach(event => { socket.off(event); }); }; - }, [socket, socketHandlers]); + }, [socket]); useEffect(() => { const monitor = performanceMonitor.current; const intervalId = setInterval(() => { - const report = monitor.getPerformanceReport(); - console.log('Frontend Performance Report:', report); - }, 10000); - + console.log('Frontend Performance Report:', monitor.getPerformanceReport()); + }, 15000); // Increased to 15 seconds + return () => { clearInterval(intervalId); - if (frameRequest.current) { - cancelAnimationFrame(frameRequest.current); + if (refs.current.frameRequest) { + cancelAnimationFrame(refs.current.frameRequest); } + + // Clear measurement cache on unmount + measurementCache = new WeakMap(); // Reset the WeakMap }; }, []); + + // Canvas setup with optimized event binding useEffect(() => { if (!canvasRef.current) return; - renderingContext.current = canvasRef.current.getContext('2d'); onCreateRef(canvasRef); - const canvas = canvasRef.current; - canvas.addEventListener('mousedown', onMouseEvent); - canvas.addEventListener('mousemove', onMouseEvent); - canvas.addEventListener('wheel', onMouseEvent, { passive: true }); - canvas.addEventListener('keydown', onKeyboardEvent); - canvas.addEventListener('keyup', onKeyboardEvent); + + const options = { passive: true }; + canvas.addEventListener('mousedown', onMouseEvent, options); + canvas.addEventListener('mousemove', onMouseEvent, options); + canvas.addEventListener('wheel', onMouseEvent, options); + canvas.addEventListener('keydown', onKeyboardEvent, options); + canvas.addEventListener('keyup', onKeyboardEvent, options); return () => { canvas.removeEventListener('mousedown', onMouseEvent); @@ -285,28 +266,28 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { }; }, [onMouseEvent, onKeyboardEvent, onCreateRef]); + const memoizedSize = useMemo(() => ({ + width: width || 900, + height: height || 400 + }), [width, height]); + return (
{state.datePickerInfo && ( dispatch({ type: 'SET_DATE_PICKER', payload: null })} - /> - )} - {state.dropdownInfo && ( - dispatch({ type: 'SET_DROPDOWN', payload: null })} + onClose={() => dispatch({ + type: 'BATCH_UPDATE', + payload: { datePickerInfo: null } + })} /> )} {state.timePickerInfo && ( From ba0d84934bf3298188df4d0008bff5195c2c1c01 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 01:46:54 +0530 Subject: [PATCH 025/199] feat: higher fps & latency less than 5ms --- src/components/atoms/canvas.tsx | 419 ++++++++++++++++++-------------- 1 file changed, 240 insertions(+), 179 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index cd6536a4..2f3cb6c9 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -1,84 +1,177 @@ -import React, { useCallback, useEffect, useRef, useMemo } from 'react'; -import { unstable_batchedUpdates } from 'react-dom'; +import React, { useCallback, useEffect, useRef, useMemo, Suspense } from 'react'; import { useSocketStore } from '../../context/socket'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; -import DatePicker from './DatePicker'; -import Dropdown from './Dropdown'; -import TimePicker from './TimePicker'; -import DateTimeLocalPicker from './DateTimeLocalPicker'; import { FrontendPerformanceMonitor } from '../../../perf/performance'; -// Optimized throttle with RAF -const rafThrottle = any>(callback: T) => { - let requestId: number | null = null; - let lastArgs: Parameters; +// Lazy load components that aren't always needed +const DatePicker = React.lazy(() => import('./DatePicker')); +const Dropdown = React.lazy(() => import('./Dropdown')); +const TimePicker = React.lazy(() => import('./TimePicker')); +const DateTimeLocalPicker = React.lazy(() => import('./DateTimeLocalPicker')); - const later = () => { - requestId = null; - callback.apply(null, lastArgs); - }; +// High-performance RAF scheduler +class RAFScheduler { + private queue: Set<() => void> = new Set(); + private isProcessing: boolean = false; + private frameId: number | null = null; - return (...args: Parameters) => { - lastArgs = args; - if (requestId === null) { - requestId = requestAnimationFrame(later); + schedule(callback: () => void): void { + this.queue.add(callback); + if (!this.isProcessing) { + this.process(); } - }; -}; - -// Cache DOM measurements -let measurementCache = new WeakMap(); -const getBoundingClientRectCached = (element: HTMLElement) => { - let rect = measurementCache.get(element); - if (!rect) { - rect = element.getBoundingClientRect(); - measurementCache.set(element, rect); } - return rect; -}; -// Types (kept the same) -interface CreateRefCallback { - (ref: React.RefObject): void; + private process = (): void => { + this.isProcessing = true; + this.frameId = requestAnimationFrame(() => { + const callbacks = Array.from(this.queue); + this.queue.clear(); + + callbacks.forEach(callback => { + try { + callback(); + } catch (error) { + console.error('RAF Scheduler error:', error); + } + }); + + this.isProcessing = false; + this.frameId = null; + + if (this.queue.size > 0) { + this.process(); + } + }); + } + + clear(): void { + this.queue.clear(); + if (this.frameId !== null) { + cancelAnimationFrame(this.frameId); + this.frameId = null; + } + this.isProcessing = false; + } +} + +// Enhanced event debouncer with priority queue +class EventDebouncer { + private highPriorityQueue: Array<() => void> = []; + private lowPriorityQueue: Array<() => void> = []; + private processing: boolean = false; + private scheduler: RAFScheduler; + + constructor(scheduler: RAFScheduler) { + this.scheduler = scheduler; + } + + add(callback: () => void, highPriority: boolean = false): void { + if (highPriority) { + this.highPriorityQueue.push(callback); + } else { + this.lowPriorityQueue.push(callback); + } + + if (!this.processing) { + this.process(); + } + } + + private process(): void { + this.processing = true; + this.scheduler.schedule(() => { + while (this.highPriorityQueue.length > 0) { + const callback = this.highPriorityQueue.shift(); + callback?.(); + } + + if (this.lowPriorityQueue.length > 0) { + const callback = this.lowPriorityQueue.shift(); + callback?.(); + + if (this.lowPriorityQueue.length > 0) { + this.process(); + } + } + + this.processing = false; + }); + } + + clear(): void { + this.highPriorityQueue = []; + this.lowPriorityQueue = []; + this.processing = false; + } +} + +// Optimized measurement cache with LRU +class MeasurementCache { + private cache: Map; + private maxSize: number; + + constructor(maxSize: number = 100) { + this.cache = new Map(); + this.maxSize = maxSize; + } + + get(element: HTMLElement): DOMRect | undefined { + const cached = this.cache.get(element); + if (cached) { + // Refresh the entry + this.cache.delete(element); + this.cache.set(element, cached); + } + return cached; + } + + set(element: HTMLElement, rect: DOMRect): void { + if (this.cache.size >= this.maxSize) { + // Remove oldest entry + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } + } + this.cache.set(element, rect); + } + + clear(): void { + this.cache.clear(); + } } interface CanvasProps { width: number; height: number; - onCreateRef: CreateRefCallback; + onCreateRef: (ref: React.RefObject) => void; } -export interface Coordinates { - x: number; - y: number; -} - -// Batch updates helper -const batchedUpdates = (updates: Array<() => void>) => { - unstable_batchedUpdates(() => { - updates.forEach(update => update()); - }); -}; - const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { - const performanceMonitor = useRef(new FrontendPerformanceMonitor()); + // Core refs and state const canvasRef = useRef(null); const { socket } = useSocketStore(); - const { setLastAction, lastAction } = useGlobalInfoStore(); + const { setLastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); - - // Use a single ref object to reduce memory allocations + + // Performance optimization instances + const scheduler = useRef(new RAFScheduler()); + const debouncer = useRef(new EventDebouncer(scheduler.current)); + const measurementCache = useRef(new MeasurementCache(50)); + const performanceMonitor = useRef(new FrontendPerformanceMonitor()); + + // Consolidated refs const refs = useRef({ getText, getList, lastMousePosition: { x: 0, y: 0 }, - frameRequest: 0, - eventQueue: [] as Array<() => void>, - isProcessing: false + lastFrameTime: 0, + context: null as CanvasRenderingContext2D | null, }); - // Consolidated state using a single reducer + // Optimized state management const [state, dispatch] = React.useReducer((state: any, action: any) => { switch (action.type) { case 'BATCH_UPDATE': @@ -93,53 +186,32 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { dateTimeLocalInfo: null }); - // Process events in batches - const processEventQueue = useCallback(() => { - if (refs.current.isProcessing || refs.current.eventQueue.length === 0) return; - - refs.current.isProcessing = true; - const events = [...refs.current.eventQueue]; - refs.current.eventQueue = []; + // Optimized coordinate calculation + const getEventCoordinates = useCallback((event: MouseEvent): { x: number; y: number } => { + if (!canvasRef.current) return { x: 0, y: 0 }; - batchedUpdates(events.map(event => () => event())); - - refs.current.isProcessing = false; - - if (refs.current.eventQueue.length > 0) { - requestAnimationFrame(processEventQueue); + let rect = measurementCache.current.get(canvasRef.current); + if (!rect) { + rect = canvasRef.current.getBoundingClientRect(); + measurementCache.current.set(canvasRef.current, rect); } + + return { + x: event.clientX - rect.left, + y: event.clientY - rect.top + }; }, []); - // Optimized mouse move handler using RAF throttle - const handleMouseMove = useMemo( - () => rafThrottle((coordinates: Coordinates) => { - if (!socket) return; - - const current = refs.current.lastMousePosition; - if (current.x !== coordinates.x || current.y !== coordinates.y) { - refs.current.lastMousePosition = coordinates; - socket.emit('input:mousemove', coordinates); - refs.current.eventQueue.push(() => setLastAction('move')); - requestAnimationFrame(processEventQueue); - } - }), - [socket, processEventQueue] - ); - - // Optimized event handler with better performance characteristics - const onMouseEvent = useCallback((event: MouseEvent) => { + // High-performance mouse handler + const handleMouseEvent = useCallback((event: MouseEvent) => { if (!socket || !canvasRef.current) return; performanceMonitor.current.measureEventLatency(event); - const rect = getBoundingClientRectCached(canvasRef.current); - const coordinates = { - x: event.clientX - rect.left, - y: event.clientY - rect.top, - }; + const coordinates = getEventCoordinates(event); switch (event.type) { case 'mousedown': - refs.current.eventQueue.push(() => { + debouncer.current.add(() => { if (refs.current.getText) { console.log('Capturing Text...'); } else if (refs.current.getList) { @@ -148,62 +220,95 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { socket.emit('input:mousedown', coordinates); } setLastAction('click'); - }); + }, true); // High priority break; case 'mousemove': - handleMouseMove(coordinates); + if (refs.current.lastMousePosition.x !== coordinates.x || + refs.current.lastMousePosition.y !== coordinates.y) { + debouncer.current.add(() => { + refs.current.lastMousePosition = coordinates; + socket.emit('input:mousemove', coordinates); + setLastAction('move'); + }); + } break; case 'wheel': - if (refs.current.frameRequest) { - cancelAnimationFrame(refs.current.frameRequest); - } - refs.current.frameRequest = requestAnimationFrame(() => { - const wheelEvent = event as WheelEvent; + const wheelEvent = event as WheelEvent; + debouncer.current.add(() => { socket.emit('input:wheel', { deltaX: Math.round(wheelEvent.deltaX), deltaY: Math.round(wheelEvent.deltaY) }); - refs.current.eventQueue.push(() => setLastAction('scroll')); + setLastAction('scroll'); }); break; } - - requestAnimationFrame(processEventQueue); - }, [socket, handleMouseMove, processEventQueue]); + }, [socket, getEventCoordinates]); // Optimized keyboard handler - const onKeyboardEvent = useMemo( - () => rafThrottle((event: KeyboardEvent) => { - if (!socket) return; + const handleKeyboardEvent = useCallback((event: KeyboardEvent) => { + if (!socket) return; - refs.current.eventQueue.push(() => { - switch (event.type) { - case 'keydown': - socket.emit('input:keydown', { - key: event.key, - coordinates: refs.current.lastMousePosition - }); - setLastAction(`${event.key} pressed`); - break; - case 'keyup': - socket.emit('input:keyup', event.key); - break; - } - }); - requestAnimationFrame(processEventQueue); - }), - [socket, processEventQueue] - ); + debouncer.current.add(() => { + switch (event.type) { + case 'keydown': + socket.emit('input:keydown', { + key: event.key, + coordinates: refs.current.lastMousePosition + }); + setLastAction(`${event.key} pressed`); + break; + case 'keyup': + socket.emit('input:keyup', event.key); + break; + } + }, event.type === 'keydown'); // High priority for keydown + }, [socket]); - // Update refs + // Setup and cleanup useEffect(() => { - refs.current.getText = getText; - refs.current.getList = getList; - }, [getText, getList]); + if (!canvasRef.current) return; - // Socket event setup with optimized cleanup + const canvas = canvasRef.current; + refs.current.context = canvas.getContext('2d', { + alpha: false, + desynchronized: true + }); + + onCreateRef(canvasRef); + + const options = { passive: true }; + canvas.addEventListener('mousedown', handleMouseEvent, options); + canvas.addEventListener('mousemove', handleMouseEvent, options); + canvas.addEventListener('wheel', handleMouseEvent, options); + canvas.addEventListener('keydown', handleKeyboardEvent, options); + canvas.addEventListener('keyup', handleKeyboardEvent, options); + + return () => { + canvas.removeEventListener('mousedown', handleMouseEvent); + canvas.removeEventListener('mousemove', handleMouseEvent); + canvas.removeEventListener('wheel', handleMouseEvent); + canvas.removeEventListener('keydown', handleKeyboardEvent); + canvas.removeEventListener('keyup', handleKeyboardEvent); + + scheduler.current.clear(); + debouncer.current.clear(); + measurementCache.current.clear(); + }; + }, [handleMouseEvent, handleKeyboardEvent, onCreateRef]); + + // Performance monitoring + useEffect(() => { + const intervalId = setInterval(() => { + console.log('Performance Report:', performanceMonitor.current.getPerformanceReport()); + }, 20000); // Reduced frequency + + return () => clearInterval(intervalId); + }, []); + + // Socket events useEffect(() => { if (!socket) return; @@ -214,59 +319,13 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { showDateTimePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { dateTimeLocalInfo: info } }) }; - Object.entries(handlers).forEach(([event, handler]) => { - socket.on(event, handler); - }); - + Object.entries(handlers).forEach(([event, handler]) => socket.on(event, handler)); return () => { - Object.keys(handlers).forEach(event => { - socket.off(event); - }); + Object.keys(handlers).forEach(event => socket.off(event)); }; }, [socket]); - useEffect(() => { - const monitor = performanceMonitor.current; - const intervalId = setInterval(() => { - console.log('Frontend Performance Report:', monitor.getPerformanceReport()); - }, 15000); // Increased to 15 seconds - - return () => { - clearInterval(intervalId); - if (refs.current.frameRequest) { - cancelAnimationFrame(refs.current.frameRequest); - } - - // Clear measurement cache on unmount - measurementCache = new WeakMap(); // Reset the WeakMap - }; - }, []); - - - // Canvas setup with optimized event binding - useEffect(() => { - if (!canvasRef.current) return; - - onCreateRef(canvasRef); - const canvas = canvasRef.current; - - const options = { passive: true }; - canvas.addEventListener('mousedown', onMouseEvent, options); - canvas.addEventListener('mousemove', onMouseEvent, options); - canvas.addEventListener('wheel', onMouseEvent, options); - canvas.addEventListener('keydown', onKeyboardEvent, options); - canvas.addEventListener('keyup', onKeyboardEvent, options); - - return () => { - canvas.removeEventListener('mousedown', onMouseEvent); - canvas.removeEventListener('mousemove', onMouseEvent); - canvas.removeEventListener('wheel', onMouseEvent); - canvas.removeEventListener('keydown', onKeyboardEvent); - canvas.removeEventListener('keyup', onKeyboardEvent); - }; - }, [onMouseEvent, onKeyboardEvent, onCreateRef]); - - const memoizedSize = useMemo(() => ({ + const memoizedDimensions = useMemo(() => ({ width: width || 900, height: height || 400 }), [width, height]); @@ -276,10 +335,11 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { + {state.datePickerInfo && ( { onClose={() => dispatch({ type: 'SET_DATETIME_PICKER', payload: null })} /> )} +
); }); From 2a568ed631038747ca17f6a2647f082dbe50368b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 02:01:07 +0530 Subject: [PATCH 026/199] chore: cleanup --- src/components/atoms/canvas.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 2f3cb6c9..e7cd8597 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -4,13 +4,11 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; import { FrontendPerformanceMonitor } from '../../../perf/performance'; -// Lazy load components that aren't always needed const DatePicker = React.lazy(() => import('./DatePicker')); const Dropdown = React.lazy(() => import('./Dropdown')); const TimePicker = React.lazy(() => import('./TimePicker')); const DateTimeLocalPicker = React.lazy(() => import('./DateTimeLocalPicker')); -// High-performance RAF scheduler class RAFScheduler { private queue: Set<() => void> = new Set(); private isProcessing: boolean = false; @@ -56,7 +54,6 @@ class RAFScheduler { } } -// Enhanced event debouncer with priority queue class EventDebouncer { private highPriorityQueue: Array<() => void> = []; private lowPriorityQueue: Array<() => void> = []; @@ -150,19 +147,16 @@ interface CanvasProps { } const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { - // Core refs and state const canvasRef = useRef(null); const { socket } = useSocketStore(); const { setLastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); - // Performance optimization instances const scheduler = useRef(new RAFScheduler()); const debouncer = useRef(new EventDebouncer(scheduler.current)); const measurementCache = useRef(new MeasurementCache(50)); const performanceMonitor = useRef(new FrontendPerformanceMonitor()); - // Consolidated refs const refs = useRef({ getText, getList, @@ -171,7 +165,6 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { context: null as CanvasRenderingContext2D | null, }); - // Optimized state management const [state, dispatch] = React.useReducer((state: any, action: any) => { switch (action.type) { case 'BATCH_UPDATE': @@ -186,7 +179,6 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { dateTimeLocalInfo: null }); - // Optimized coordinate calculation const getEventCoordinates = useCallback((event: MouseEvent): { x: number; y: number } => { if (!canvasRef.current) return { x: 0, y: 0 }; @@ -202,7 +194,6 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { }; }, []); - // High-performance mouse handler const handleMouseEvent = useCallback((event: MouseEvent) => { if (!socket || !canvasRef.current) return; @@ -247,7 +238,6 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { } }, [socket, getEventCoordinates]); - // Optimized keyboard handler const handleKeyboardEvent = useCallback((event: KeyboardEvent) => { if (!socket) return; @@ -303,12 +293,11 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { useEffect(() => { const intervalId = setInterval(() => { console.log('Performance Report:', performanceMonitor.current.getPerformanceReport()); - }, 20000); // Reduced frequency + }, 20000); return () => clearInterval(intervalId); }, []); - // Socket events useEffect(() => { if (!socket) return; From b06db2e77839f7ee460ded236ff9fe283e90699d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 02:01:20 +0530 Subject: [PATCH 027/199] chore: lint --- src/components/atoms/canvas.tsx | 58 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index e7cd8597..d198adb0 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -26,7 +26,7 @@ class RAFScheduler { this.frameId = requestAnimationFrame(() => { const callbacks = Array.from(this.queue); this.queue.clear(); - + callbacks.forEach(callback => { try { callback(); @@ -37,7 +37,7 @@ class RAFScheduler { this.isProcessing = false; this.frameId = null; - + if (this.queue.size > 0) { this.process(); } @@ -87,12 +87,12 @@ class EventDebouncer { if (this.lowPriorityQueue.length > 0) { const callback = this.lowPriorityQueue.shift(); callback?.(); - + if (this.lowPriorityQueue.length > 0) { this.process(); } } - + this.processing = false; }); } @@ -282,7 +282,7 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { canvas.removeEventListener('wheel', handleMouseEvent); canvas.removeEventListener('keydown', handleKeyboardEvent); canvas.removeEventListener('keyup', handleKeyboardEvent); - + scheduler.current.clear(); debouncer.current.clear(); measurementCache.current.clear(); @@ -329,30 +329,30 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { className="block" /> - {state.datePickerInfo && ( - dispatch({ - type: 'BATCH_UPDATE', - payload: { datePickerInfo: null } - })} - /> - )} - {state.timePickerInfo && ( - dispatch({ type: 'SET_TIME_PICKER', payload: null })} - /> - )} - {state.dateTimeLocalInfo && ( - dispatch({ type: 'SET_DATETIME_PICKER', payload: null })} - /> - )} + {state.datePickerInfo && ( + dispatch({ + type: 'BATCH_UPDATE', + payload: { datePickerInfo: null } + })} + /> + )} + {state.timePickerInfo && ( + dispatch({ type: 'SET_TIME_PICKER', payload: null })} + /> + )} + {state.dateTimeLocalInfo && ( + dispatch({ type: 'SET_DATETIME_PICKER', payload: null })} + /> + )}
); From bc59ae55b2acb836c3c64a04a8e89e6169f078e4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 10:55:33 +0530 Subject: [PATCH 028/199] chore(deps): install lodash & sharp --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e89f13de..f8975ea6 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "joi": "^17.6.0", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", + "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.7", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", @@ -66,6 +66,7 @@ "react-transition-group": "^4.4.2", "sequelize": "^6.37.3", "sequelize-typescript": "^2.1.6", + "sharp": "^0.33.5", "socket.io": "^4.4.1", "socket.io-client": "^4.4.1", "styled-components": "^5.3.3", @@ -97,6 +98,7 @@ "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.13", "@types/js-cookie": "^3.0.6", + "@types/lodash": "^4.17.14", "@types/loglevel": "^1.6.3", "@types/node": "22.7.9", "@types/node-cron": "^3.0.11", From 20912a73c632d7c527753bbbcb536377646cf84d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 10:56:31 +0530 Subject: [PATCH 029/199] fix: missing coordinates interface export --- src/components/atoms/canvas.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index d198adb0..96933030 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -3,7 +3,6 @@ import { useSocketStore } from '../../context/socket'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; import { FrontendPerformanceMonitor } from '../../../perf/performance'; - const DatePicker = React.lazy(() => import('./DatePicker')); const Dropdown = React.lazy(() => import('./Dropdown')); const TimePicker = React.lazy(() => import('./TimePicker')); @@ -146,6 +145,14 @@ interface CanvasProps { onCreateRef: (ref: React.RefObject) => void; } +/** + * Interface for mouse's x,y coordinates + */ +export interface Coordinates { + x: number; + y: number; +}; + const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { const canvasRef = useRef(null); const { socket } = useSocketStore(); From 7fba807525caa2f137f4e098993f52bdca0727fd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:43:02 +0530 Subject: [PATCH 030/199] feat: screenshot queue --- server/src/browser-management/classes/RemoteBrowser.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 2b80b037..dd97a809 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -9,6 +9,8 @@ import { chromium } from 'playwright-extra'; import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import { PlaywrightBlocker } from '@cliqz/adblocker-playwright'; import fetch from 'cross-fetch'; +import { throttle } from 'lodash'; +import sharp from 'sharp'; import logger from '../../logger'; import { InterpreterSettings, RemoteBrowserOptions } from "../../types"; @@ -81,6 +83,10 @@ export class RemoteBrowser { private performanceMonitor: BackendPerformanceMonitor; + private screenshotQueue: Buffer[] = []; + private isProcessingScreenshot = false; + private screencastInterval: NodeJS.Timeout | null = null; + private startPerformanceReporting() { setInterval(() => { const report = this.performanceMonitor.getPerformanceReport(); From b3f17bca4cf317c944f729407da0377f44f5a2d0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:46:06 +0530 Subject: [PATCH 031/199] feat: memory management for screenshot streaming --- .../browser-management/classes/RemoteBrowser.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index dd97a809..ebdb8cac 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -110,6 +110,23 @@ export class RemoteBrowser { this.startPerformanceReporting(); } + private initializeMemoryManagement(): void { + setInterval(() => { + const memoryUsage = process.memoryUsage(); + const heapUsageRatio = memoryUsage.heapUsed / MEMORY_CONFIG.maxHeapSize; + + if (heapUsageRatio > MEMORY_CONFIG.heapUsageThreshold) { + logger.warn('High memory usage detected, triggering cleanup'); + this.performMemoryCleanup(); + } + + // Clear screenshot queue if it's too large + if (this.screenshotQueue.length > SCREENCAST_CONFIG.maxQueueSize) { + this.screenshotQueue = this.screenshotQueue.slice(-SCREENCAST_CONFIG.maxQueueSize); + } + }, MEMORY_CONFIG.gcInterval); + } + /** * Normalizes URLs to prevent navigation loops while maintaining consistent format */ From 9d0964727adbc773298124d3ea3c094a230f3514 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:47:38 +0530 Subject: [PATCH 032/199] feat: perform memory cleanup --- .../classes/RemoteBrowser.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index ebdb8cac..e084e2b3 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -127,6 +127,29 @@ export class RemoteBrowser { }, MEMORY_CONFIG.gcInterval); } + private async performMemoryCleanup(): Promise { + this.screenshotQueue = []; + this.isProcessingScreenshot = false; + + if (global.gc) { + global.gc(); + } + + // Reset CDP session if needed + if (this.client) { + try { + await this.stopScreencast(); + this.client = null; + if (this.currentPage) { + this.client = await this.currentPage.context().newCDPSession(this.currentPage); + await this.startScreencast(); + } + } catch (error) { + logger.error('Error resetting CDP session:', error); + } + } + } + /** * Normalizes URLs to prevent navigation loops while maintaining consistent format */ From d79b2fd80757a93592da9cbb3e9aba2368368bd6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:47:56 +0530 Subject: [PATCH 033/199] feat: memory management config --- server/src/browser-management/classes/RemoteBrowser.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index e084e2b3..d2f83fdf 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -21,6 +21,14 @@ import { getInjectableScript } from 'idcac-playwright'; import { BackendPerformanceMonitor } from '../../../../perf/performance' chromium.use(stealthPlugin()); +// Memory management configuration +const MEMORY_CONFIG = { + gcInterval: 60000, // 1 minute + maxHeapSize: 2048 * 1024 * 1024, // 2GB + heapUsageThreshold: 0.85 // 85% +}; + + /** * This class represents a remote browser instance. From 65c30c0ef4cf4c38bf420f122f1f5253fd8ab807 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:54:31 +0530 Subject: [PATCH 034/199] feat: screencast config --- server/src/browser-management/classes/RemoteBrowser.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index d2f83fdf..f64c0613 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -21,13 +21,21 @@ import { getInjectableScript } from 'idcac-playwright'; import { BackendPerformanceMonitor } from '../../../../perf/performance' chromium.use(stealthPlugin()); -// Memory management configuration const MEMORY_CONFIG = { gcInterval: 60000, // 1 minute maxHeapSize: 2048 * 1024 * 1024, // 2GB heapUsageThreshold: 0.85 // 85% }; +const SCREENCAST_CONFIG = { + format: 'jpeg', + quality: 75, + maxWidth: 1280, + maxHeight: 720, + targetFPS: 30, + compressionQuality: 0.8, + maxQueueSize: 2 +}; /** From 60131e12a79f9cec5e08c96f4c16b2b78c24a3a0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:54:48 +0530 Subject: [PATCH 035/199] feat: optimize screenshot --- .../classes/RemoteBrowser.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index f64c0613..53df4900 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -426,6 +426,26 @@ export class RemoteBrowser { } }; + private async optimizeScreenshot(screenshot: Buffer): Promise { + try { + return await sharp(screenshot) + .jpeg({ + quality: Math.round(SCREENCAST_CONFIG.compressionQuality * 100), + progressive: true + }) + .resize({ + width: SCREENCAST_CONFIG.maxWidth, + height: SCREENCAST_CONFIG.maxHeight, + fit: 'inside', + withoutEnlargement: true + }) + .toBuffer(); + } catch (error) { + logger.error('Screenshot optimization failed:', error); + return screenshot; + } + } + /** * Makes and emits a single screenshot to the client side. * @returns {Promise} From c766df6164ce5dcde6b3aa617d8d29c436d7fc7e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:55:15 +0530 Subject: [PATCH 036/199] feat: emit screenshot using screenshot queue --- .../classes/RemoteBrowser.ts | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 53df4900..62958fe1 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -614,11 +614,39 @@ export class RemoteBrowser { * @param payload the screenshot binary data * @returns void */ - private emitScreenshot = (payload: any): void => { - this.performanceMonitor.measureEmitPerformance(() => { - const dataWithMimeType = ('data:image/jpeg;base64,').concat(payload); - this.socket.emit('screencast', dataWithMimeType); - logger.log('debug', `Screenshot emitted`); - }); - }; + private emitScreenshot = throttle(async (payload: Buffer): Promise => { + if (this.isProcessingScreenshot) { + if (this.screenshotQueue.length < SCREENCAST_CONFIG.maxQueueSize) { + this.screenshotQueue.push(payload); + } + return; + } + + this.isProcessingScreenshot = true; + + try { + await this.performanceMonitor.measureEmitPerformance(async () => { + const optimizedScreenshot = await this.optimizeScreenshot(payload); + const base64Data = optimizedScreenshot.toString('base64'); + const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; + + await new Promise((resolve) => { + this.socket.emit('screencast', dataWithMimeType, () => resolve()); + }); + }); + } catch (error) { + logger.error('Screenshot emission failed:', error); + } finally { + this.isProcessingScreenshot = false; + + // Process next screenshot in queue if any + if (this.screenshotQueue.length > 0) { + const nextScreenshot = this.screenshotQueue.shift(); + if (nextScreenshot) { + this.emitScreenshot(nextScreenshot); + } + } + } + }, 1000 / SCREENCAST_CONFIG.targetFPS); + } From 8c80d5ce6c6c47e49354910d26b8102372b2ef41 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:55:45 +0530 Subject: [PATCH 037/199] feat: start/stop screencast --- .../classes/RemoteBrowser.ts | 61 +++++++++++++------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 62958fe1..1b3156e6 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -395,7 +395,7 @@ export class RemoteBrowser { return; } this.client.on('Page.screencastFrame', ({ data: base64, sessionId }) => { - this.emitScreenshot(base64) + this.emitScreenshot(Buffer.from(base64, 'base64')) setTimeout(async () => { try { if (!this.client) { @@ -454,7 +454,7 @@ export class RemoteBrowser { try { const screenshot = await this.currentPage?.screenshot(); if (screenshot) { - this.emitScreenshot(screenshot.toString('base64')); + this.emitScreenshot(screenshot); } } catch (e) { const { message } = e as Error; @@ -586,28 +586,51 @@ export class RemoteBrowser { * Should be called only once after the browser is fully initialized. * @returns {Promise} */ - private startScreencast = async (): Promise => { + private async startScreencast(): Promise { if (!this.client) { - logger.log('warn', 'client is not initialized'); + logger.warn('Client is not initialized'); return; } - await this.client.send('Page.startScreencast', { format: 'jpeg', quality: 75 }); - logger.log('info', `Browser started with screencasting a page.`); - }; - /** - * Unsubscribes the current page from the screencast session. - * @returns {Promise} - */ - private stopScreencast = async (): Promise => { - if (!this.client) { - logger.log('error', 'client is not initialized'); - logger.log('error', 'Screencast stop failed'); - } else { - await this.client.send('Page.stopScreencast'); - logger.log('info', `Browser stopped with screencasting.`); + try { + await this.client.send('Page.startScreencast', { + format: SCREENCAST_CONFIG.format, + quality: SCREENCAST_CONFIG.quality + }); + + // Set up screencast frame handler + this.client.on('Page.screencastFrame', async ({ data, sessionId }) => { + try { + const buffer = Buffer.from(data, 'base64'); + await this.emitScreenshot(buffer); + await this.client?.send('Page.screencastFrameAck', { sessionId }); + } catch (error) { + logger.error('Screencast frame processing failed:', error); + } + }); + + logger.info('Screencast started successfully'); + } catch (error) { + logger.error('Failed to start screencast:', error); } - }; + } + + private async stopScreencast(): Promise { + if (!this.client) { + logger.error('Client is not initialized'); + return; + } + + try { + await this.client.send('Page.stopScreencast'); + this.screenshotQueue = []; + this.isProcessingScreenshot = false; + logger.info('Screencast stopped successfully'); + } catch (error) { + logger.error('Failed to stop screencast:', error); + } + } + /** * Helper for emitting the screenshot of browser's active page through websocket. From 6521eac2d7bfdadf66334755be1d74ab065ccc58 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 11:56:10 +0530 Subject: [PATCH 038/199] feat: set screencast type as jpeg | png --- server/src/browser-management/classes/RemoteBrowser.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 1b3156e6..53248aa4 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -27,7 +27,15 @@ const MEMORY_CONFIG = { heapUsageThreshold: 0.85 // 85% }; -const SCREENCAST_CONFIG = { +const SCREENCAST_CONFIG: { + format: "jpeg" | "png"; + quality: number; + maxWidth: number; + maxHeight: number; + targetFPS: number; + compressionQuality: number; + maxQueueSize: number; +} = { format: 'jpeg', quality: 75, maxWidth: 1280, From d25ffe32b8dced7de2e2c70eaf544a714acd66d8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:33:52 +0530 Subject: [PATCH 039/199] feat: change screencast config --- server/src/browser-management/classes/RemoteBrowser.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 53248aa4..27465f0a 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -29,7 +29,6 @@ const MEMORY_CONFIG = { const SCREENCAST_CONFIG: { format: "jpeg" | "png"; - quality: number; maxWidth: number; maxHeight: number; targetFPS: number; @@ -37,9 +36,8 @@ const SCREENCAST_CONFIG: { maxQueueSize: number; } = { format: 'jpeg', - quality: 75, - maxWidth: 1280, - maxHeight: 720, + maxWidth: 900, + maxHeight: 400, targetFPS: 30, compressionQuality: 0.8, maxQueueSize: 2 @@ -603,7 +601,6 @@ export class RemoteBrowser { try { await this.client.send('Page.startScreencast', { format: SCREENCAST_CONFIG.format, - quality: SCREENCAST_CONFIG.quality }); // Set up screencast frame handler From 9cfffd801d036d1b2815d43f708f816d38447127 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:34:13 +0530 Subject: [PATCH 040/199] chore: lint --- .../classes/RemoteBrowser.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 27465f0a..fb61cfb6 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -43,7 +43,6 @@ const SCREENCAST_CONFIG: { maxQueueSize: 2 }; - /** * This class represents a remote browser instance. * It is used to allow a variety of interaction with the Playwright's browser instance. @@ -112,9 +111,9 @@ export class RemoteBrowser { private startPerformanceReporting() { setInterval(() => { const report = this.performanceMonitor.getPerformanceReport(); - + console.log('Backend Performance Report:', report); - + }, 5000); } @@ -152,11 +151,11 @@ export class RemoteBrowser { private async performMemoryCleanup(): Promise { this.screenshotQueue = []; this.isProcessingScreenshot = false; - + if (global.gc) { global.gc(); } - + // Reset CDP session if needed if (this.client) { try { @@ -239,7 +238,7 @@ export class RemoteBrowser { 'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.62 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:118.0) Gecko/20100101 Firefox/118.0', ]; - + return userAgents[Math.floor(Math.random() * userAgents.length)]; } @@ -260,7 +259,7 @@ export class RemoteBrowser { "--disable-extensions", "--no-sandbox", "--disable-dev-shm-usage", - ], + ], })); const proxyConfig = await getDecryptedProxyConfig(userId); let proxyOptions: { server: string, username?: string, password?: string } = { server: '' }; @@ -333,11 +332,11 @@ export class RemoteBrowser { this.client = await this.currentPage.context().newCDPSession(this.currentPage); await blocker.disableBlockingInPage(this.currentPage); console.log('Adblocker initialized'); - } catch (error: any) { + } catch (error: any) { console.warn('Failed to initialize adblocker, continuing without it:', error.message); // Still need to set up the CDP session even if blocker fails this.client = await this.currentPage.context().newCDPSession(this.currentPage); - } + } }; /** @@ -657,7 +656,7 @@ export class RemoteBrowser { const optimizedScreenshot = await this.optimizeScreenshot(payload); const base64Data = optimizedScreenshot.toString('base64'); const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; - + await new Promise((resolve) => { this.socket.emit('screencast', dataWithMimeType, () => resolve()); }); @@ -666,7 +665,7 @@ export class RemoteBrowser { logger.error('Screenshot emission failed:', error); } finally { this.isProcessingScreenshot = false; - + // Process next screenshot in queue if any if (this.screenshotQueue.length > 0) { const nextScreenshot = this.screenshotQueue.shift(); From d33a064dd54b5283cb45f687933083abef861c83 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:38:22 +0530 Subject: [PATCH 041/199] feat: set screenshot queue empty while switch off --- .../classes/RemoteBrowser.ts | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index fb61cfb6..b6c0bf5f 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -89,7 +89,7 @@ export class RemoteBrowser { maxConcurrency: 1, maxRepeats: 1, }; - + private lastEmittedUrl: string | null = null; /** @@ -420,16 +420,29 @@ export class RemoteBrowser { * If an interpretation was running it will be stopped. * @returns {Promise} */ - public switchOff = async (): Promise => { - await this.interpreter.stopInterpretation(); - if (this.browser) { - await this.stopScreencast(); - await this.browser.close(); - } else { - logger.log('error', 'Browser wasn\'t initialized'); - logger.log('error', 'Switching off the browser failed'); + public async switchOff(): Promise { + try { + await this.interpreter.stopInterpretation(); + + if (this.screencastInterval) { + clearInterval(this.screencastInterval); + } + + if (this.client) { + await this.stopScreencast(); + } + + if (this.browser) { + await this.browser.close(); + } + + this.screenshotQueue = []; + //this.performanceMonitor.reset(); + + } catch (error) { + logger.error('Error during browser shutdown:', error); } - }; + } private async optimizeScreenshot(screenshot: Buffer): Promise { try { From 4e7ce637e68d9a3f0e56d8bb880ed7628aefa559 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:38:37 +0530 Subject: [PATCH 042/199] chore: lint --- .../src/browser-management/classes/RemoteBrowser.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index b6c0bf5f..4419f0b3 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -89,7 +89,7 @@ export class RemoteBrowser { maxConcurrency: 1, maxRepeats: 1, }; - + private lastEmittedUrl: string | null = null; /** @@ -423,22 +423,22 @@ export class RemoteBrowser { public async switchOff(): Promise { try { await this.interpreter.stopInterpretation(); - + if (this.screencastInterval) { clearInterval(this.screencastInterval); } - + if (this.client) { await this.stopScreencast(); } - + if (this.browser) { await this.browser.close(); } - + this.screenshotQueue = []; //this.performanceMonitor.reset(); - + } catch (error) { logger.error('Error during browser shutdown:', error); } From 82e24f9483ba86eca59094c84dd82fff511a2de9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:39:27 +0530 Subject: [PATCH 043/199] feat: disable browser performance monitoring --- server/src/browser-management/classes/RemoteBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 4419f0b3..2e08a506 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -128,7 +128,7 @@ export class RemoteBrowser { this.interpreter = new WorkflowInterpreter(socket); this.generator = new WorkflowGenerator(socket); this.performanceMonitor = new BackendPerformanceMonitor(); - this.startPerformanceReporting(); + //this.startPerformanceReporting(); } private initializeMemoryManagement(): void { From 9b920a111dfd50773683be7bd005582499995383 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:41:10 +0530 Subject: [PATCH 044/199] feat: temporarily disable browser performance monitoring --- .../classes/RemoteBrowser.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 2e08a506..a45fc93b 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -102,20 +102,20 @@ export class RemoteBrowser { */ public interpreter: WorkflowInterpreter; - private performanceMonitor: BackendPerformanceMonitor; + //private performanceMonitor: BackendPerformanceMonitor; private screenshotQueue: Buffer[] = []; private isProcessingScreenshot = false; private screencastInterval: NodeJS.Timeout | null = null; - private startPerformanceReporting() { - setInterval(() => { - const report = this.performanceMonitor.getPerformanceReport(); + // private startPerformanceReporting() { + // setInterval(() => { + // const report = this.performanceMonitor.getPerformanceReport(); - console.log('Backend Performance Report:', report); + // console.log('Backend Performance Report:', report); - }, 5000); - } + // }, 5000); + // } /** * Initializes a new instances of the {@link Generator} and {@link WorkflowInterpreter} classes and @@ -127,7 +127,7 @@ export class RemoteBrowser { this.socket = socket; this.interpreter = new WorkflowInterpreter(socket); this.generator = new WorkflowGenerator(socket); - this.performanceMonitor = new BackendPerformanceMonitor(); + //this.performanceMonitor = new BackendPerformanceMonitor(); //this.startPerformanceReporting(); } @@ -665,7 +665,6 @@ export class RemoteBrowser { this.isProcessingScreenshot = true; try { - await this.performanceMonitor.measureEmitPerformance(async () => { const optimizedScreenshot = await this.optimizeScreenshot(payload); const base64Data = optimizedScreenshot.toString('base64'); const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; @@ -673,7 +672,6 @@ export class RemoteBrowser { await new Promise((resolve) => { this.socket.emit('screencast', dataWithMimeType, () => resolve()); }); - }); } catch (error) { logger.error('Screenshot emission failed:', error); } finally { From 5ddfc91f8e31d5cdb1defc336246710e9682ccce Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:41:31 +0530 Subject: [PATCH 045/199] chore: remove unused import --- server/src/browser-management/classes/RemoteBrowser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index a45fc93b..36df52cf 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -18,7 +18,6 @@ import { WorkflowGenerator } from "../../workflow-management/classes/Generator"; import { WorkflowInterpreter } from "../../workflow-management/classes/Interpreter"; import { getDecryptedProxyConfig } from '../../routes/proxy'; import { getInjectableScript } from 'idcac-playwright'; -import { BackendPerformanceMonitor } from '../../../../perf/performance' chromium.use(stealthPlugin()); const MEMORY_CONFIG = { From 0497653ca96b6721bf20bd682ca22c3075038a83 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 19:44:04 +0530 Subject: [PATCH 046/199] feat: disable canvas events performance monitoring --- src/components/atoms/canvas.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 96933030..7c216733 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useMemo, Suspense } from 'react' import { useSocketStore } from '../../context/socket'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; -import { FrontendPerformanceMonitor } from '../../../perf/performance'; const DatePicker = React.lazy(() => import('./DatePicker')); const Dropdown = React.lazy(() => import('./Dropdown')); const TimePicker = React.lazy(() => import('./TimePicker')); @@ -162,7 +161,7 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { const scheduler = useRef(new RAFScheduler()); const debouncer = useRef(new EventDebouncer(scheduler.current)); const measurementCache = useRef(new MeasurementCache(50)); - const performanceMonitor = useRef(new FrontendPerformanceMonitor()); + //const performanceMonitor = useRef(new FrontendPerformanceMonitor()); const refs = useRef({ getText, @@ -204,7 +203,7 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { const handleMouseEvent = useCallback((event: MouseEvent) => { if (!socket || !canvasRef.current) return; - performanceMonitor.current.measureEventLatency(event); + //performanceMonitor.current.measureEventLatency(event); const coordinates = getEventCoordinates(event); switch (event.type) { @@ -297,13 +296,13 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { }, [handleMouseEvent, handleKeyboardEvent, onCreateRef]); // Performance monitoring - useEffect(() => { - const intervalId = setInterval(() => { - console.log('Performance Report:', performanceMonitor.current.getPerformanceReport()); - }, 20000); + // useEffect(() => { + // const intervalId = setInterval(() => { + // console.log('Performance Report:', performanceMonitor.current.getPerformanceReport()); + // }, 20000); - return () => clearInterval(intervalId); - }, []); + // return () => clearInterval(intervalId); + // }, []); useEffect(() => { if (!socket) return; From de2ddfd2b8d46555e2191a940578076872985a5d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 20:18:27 +0530 Subject: [PATCH 047/199] fix: missing dropdown --- src/components/atoms/canvas.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 7c216733..8fd4f791 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -155,7 +155,7 @@ export interface Coordinates { const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { const canvasRef = useRef(null); const { socket } = useSocketStore(); - const { setLastAction } = useGlobalInfoStore(); + const { setLastAction, lastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); const scheduler = useRef(new RAFScheduler()); @@ -345,6 +345,17 @@ const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { })} /> )} + {state.dropdownInfo && ( + dispatch({ + type: 'BATCH_UPDATE', + payload: { dropdownInfo: null } + })} + /> + )} {state.timePickerInfo && ( Date: Mon, 6 Jan 2025 21:10:39 +0530 Subject: [PATCH 048/199] feat: remove throttle wrapper from emit screenshot --- .../classes/RemoteBrowser.ts | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 36df52cf..a843fe45 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -18,6 +18,7 @@ import { WorkflowGenerator } from "../../workflow-management/classes/Generator"; import { WorkflowInterpreter } from "../../workflow-management/classes/Interpreter"; import { getDecryptedProxyConfig } from '../../routes/proxy'; import { getInjectableScript } from 'idcac-playwright'; + chromium.use(stealthPlugin()); const MEMORY_CONFIG = { @@ -88,7 +89,7 @@ export class RemoteBrowser { maxConcurrency: 1, maxRepeats: 1, }; - + private lastEmittedUrl: string | null = null; /** @@ -101,20 +102,10 @@ export class RemoteBrowser { */ public interpreter: WorkflowInterpreter; - //private performanceMonitor: BackendPerformanceMonitor; private screenshotQueue: Buffer[] = []; private isProcessingScreenshot = false; - private screencastInterval: NodeJS.Timeout | null = null; - - // private startPerformanceReporting() { - // setInterval(() => { - // const report = this.performanceMonitor.getPerformanceReport(); - - // console.log('Backend Performance Report:', report); - - // }, 5000); - // } + private screencastInterval: NodeJS.Timeout | null = null /** * Initializes a new instances of the {@link Generator} and {@link WorkflowInterpreter} classes and @@ -126,8 +117,6 @@ export class RemoteBrowser { this.socket = socket; this.interpreter = new WorkflowInterpreter(socket); this.generator = new WorkflowGenerator(socket); - //this.performanceMonitor = new BackendPerformanceMonitor(); - //this.startPerformanceReporting(); } private initializeMemoryManagement(): void { @@ -422,22 +411,22 @@ export class RemoteBrowser { public async switchOff(): Promise { try { await this.interpreter.stopInterpretation(); - + if (this.screencastInterval) { clearInterval(this.screencastInterval); } - + if (this.client) { await this.stopScreencast(); } - + if (this.browser) { await this.browser.close(); } - + this.screenshotQueue = []; //this.performanceMonitor.reset(); - + } catch (error) { logger.error('Error during browser shutdown:', error); } @@ -653,37 +642,35 @@ export class RemoteBrowser { * @param payload the screenshot binary data * @returns void */ - private emitScreenshot = throttle(async (payload: Buffer): Promise => { + private emitScreenshot = async (payload: Buffer): Promise => { if (this.isProcessingScreenshot) { if (this.screenshotQueue.length < SCREENCAST_CONFIG.maxQueueSize) { this.screenshotQueue.push(payload); } return; } - + this.isProcessingScreenshot = true; - + try { - const optimizedScreenshot = await this.optimizeScreenshot(payload); - const base64Data = optimizedScreenshot.toString('base64'); - const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; - - await new Promise((resolve) => { - this.socket.emit('screencast', dataWithMimeType, () => resolve()); - }); + const optimizedScreenshot = await this.optimizeScreenshot(payload); + const base64Data = optimizedScreenshot.toString('base64'); + const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; + + this.socket.emit('screencast', dataWithMimeType); + logger.debug('Screenshot emitted'); } catch (error) { logger.error('Screenshot emission failed:', error); } finally { this.isProcessingScreenshot = false; - - // Process next screenshot in queue if any + if (this.screenshotQueue.length > 0) { const nextScreenshot = this.screenshotQueue.shift(); if (nextScreenshot) { - this.emitScreenshot(nextScreenshot); + setTimeout(() => this.emitScreenshot(nextScreenshot), 1000 / SCREENCAST_CONFIG.targetFPS); } } } - }, 1000 / SCREENCAST_CONFIG.targetFPS); + }; } From 434409ead3e58754ca3c08c6e7d2efd9cd153501 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 21:11:11 +0530 Subject: [PATCH 049/199] chore: lint --- .../classes/RemoteBrowser.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index a843fe45..8ff4f601 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -89,7 +89,7 @@ export class RemoteBrowser { maxConcurrency: 1, maxRepeats: 1, }; - + private lastEmittedUrl: string | null = null; /** @@ -411,22 +411,22 @@ export class RemoteBrowser { public async switchOff(): Promise { try { await this.interpreter.stopInterpretation(); - + if (this.screencastInterval) { clearInterval(this.screencastInterval); } - + if (this.client) { await this.stopScreencast(); } - + if (this.browser) { await this.browser.close(); } - + this.screenshotQueue = []; //this.performanceMonitor.reset(); - + } catch (error) { logger.error('Error during browser shutdown:', error); } @@ -649,21 +649,21 @@ export class RemoteBrowser { } return; } - + this.isProcessingScreenshot = true; - + try { const optimizedScreenshot = await this.optimizeScreenshot(payload); const base64Data = optimizedScreenshot.toString('base64'); const dataWithMimeType = `data:image/jpeg;base64,${base64Data}`; - + this.socket.emit('screencast', dataWithMimeType); logger.debug('Screenshot emitted'); } catch (error) { logger.error('Screenshot emission failed:', error); } finally { this.isProcessingScreenshot = false; - + if (this.screenshotQueue.length > 0) { const nextScreenshot = this.screenshotQueue.shift(); if (nextScreenshot) { From 2d79591a7682fd0e2d967a07ae672bba851d5432 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 21:24:27 +0530 Subject: [PATCH 050/199] chore(deps): use maxun-core 0.0.7 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f8975ea6..b8ca18b9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", + "maxun-core": "^0.0.7", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", From 1ab4631da8f356bddb484fdcb2edea7b63542e8d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 23:27:43 +0530 Subject: [PATCH 051/199] feat: handle select tags in getElementInfo --- server/src/workflow-management/selector.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 82464e63..4b7c9e31 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -175,6 +175,13 @@ export const getElementInformation = async ( info.innerText = targetElement.textContent ?? ''; } else if (targetElement.tagName === 'IMG') { info.imageUrl = (targetElement as HTMLImageElement).src; + } else if (targetElement?.tagName === 'SELECT') { + const selectElement = targetElement as HTMLSelectElement; + info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; + info.attributes = { + ...info.attributes, + selectedValue: selectElement.value, + }; } else { info.hasOnlyText = targetElement.children.length === 0 && (targetElement.textContent !== null && From c86fdf8013251e4d5b0829a014209bc4019d65b0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 6 Jan 2025 23:29:15 +0530 Subject: [PATCH 052/199] feat: handle input tags in getElementInfo --- server/src/workflow-management/selector.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 4b7c9e31..8a9096ec 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -182,7 +182,10 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; - } else { + } else if (targetElement?.tagName === 'INPUT' && (targetElement as HTMLInputElement).type === 'time' || (targetElement as HTMLInputElement).type === 'date') { + info.innerText = (targetElement as HTMLInputElement).value; + } + else { info.hasOnlyText = targetElement.children.length === 0 && (targetElement.textContent !== null && targetElement.textContent.trim().length > 0); From e87a7be18a819c35f58e2ccb4c963f75e5c36ed9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 7 Jan 2025 09:39:09 +0530 Subject: [PATCH 053/199] chore: maxun-core 0.0.8 --- maxun-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/package.json b/maxun-core/package.json index 7c92d08e..ddaaa510 100644 --- a/maxun-core/package.json +++ b/maxun-core/package.json @@ -1,6 +1,6 @@ { "name": "maxun-core", - "version": "0.0.7", + "version": "0.0.8", "description": "Core package for Maxun, responsible for data extraction", "main": "build/index.js", "typings": "build/index.d.ts", From 4d73342424ea9114934fee4486a7c4a77e64ff99 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 7 Jan 2025 09:47:58 +0530 Subject: [PATCH 054/199] chore(deps): upgrade maxun-core -> 0.0.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8ca18b9..daf8a2bb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "lodash": "^4.17.21", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.7", + "maxun-core": "^0.0.8", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", From 56b17a4710a8d3c359b1f2c2c1ceae7912de1ac6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 7 Jan 2025 09:48:25 +0530 Subject: [PATCH 055/199] chore: v0.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index daf8a2bb..36062666 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxun", - "version": "0.0.5", + "version": "0.0.6", "author": "Maxun", "license": "AGPL-3.0-or-later", "dependencies": { From 73d4497b39a7b77c4e04e722ea68eb15311d84a5 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 7 Jan 2025 09:51:25 +0530 Subject: [PATCH 056/199] chore: BE 0.0.10, FE 0.0.6 --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 874e48d6..6506a4c5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,7 @@ services: #build: #context: . #dockerfile: server/Dockerfile - image: getmaxun/maxun-backend:v0.0.9 + image: getmaxun/maxun-backend:v0.0.10 ports: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env @@ -70,7 +70,7 @@ services: #build: #context: . #dockerfile: Dockerfile - image: getmaxun/maxun-frontend:v0.0.5 + image: getmaxun/maxun-frontend:v0.0.6 ports: - "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}" env_file: .env From 3134edf385742327867514e8ac36ae60591ee356 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 12:58:08 +0530 Subject: [PATCH 057/199] feat: rm ThemeProvider --- src/App.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3b6a1ccd..cdee8d40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -94,15 +94,15 @@ function App() { - + // ); } From 0d5ac2b8a123f8a71e7a78f6ebdb01642edf0749 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 13:03:02 +0530 Subject: [PATCH 058/199] feat: add dark mode to project name --- src/components/molecules/NavBar.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 3bb8d758..72c72724 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -1,24 +1,7 @@ import { useTranslation } from "react-i18next"; import React, { useState, useContext, useEffect } from 'react'; import axios from 'axios'; -import { useNavigate } from 'react-router-dom'; -import { - IconButton, - Menu, - MenuItem, - Typography, - Tooltip, - Chip -} from "@mui/material"; -import { - AccountCircle, - Logout, - Clear, - Brightness4, - Brightness7 -} from "@mui/icons-material"; import styled from "styled-components"; - import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box, Snackbar } from "@mui/material"; @@ -305,7 +288,7 @@ export const NavBar: React.FC = ({ justifyContent: 'flex-start', }}> -
{t('navbar.project_name')}
+
{t('navbar.project_name')}
Date: Wed, 8 Jan 2025 13:09:00 +0530 Subject: [PATCH 059/199] feat: add toggle dark theme button --- src/components/molecules/NavBar.tsx | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 72c72724..01fa01b7 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,8 +4,8 @@ import axios from 'axios'; import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; -import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box, Snackbar } from "@mui/material"; -import { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language } from "@mui/icons-material"; +import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box, Snackbar, Tooltip } from "@mui/material"; +import { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language, Brightness7, Brightness4 } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -176,21 +176,21 @@ export const NavBar: React.FC = ({ // // ); -// const renderThemeToggle = () => ( -// -// -// {darkMode ? : } -// -// -// ); + const renderThemeToggle = () => ( + + + {darkMode ? : } + + + ); // const renderRecordingControls = () => ( // <> @@ -502,6 +502,7 @@ export const NavBar: React.FC = ({ + {renderThemeToggle()} ) : ( <> @@ -587,7 +588,7 @@ export const NavBar: React.FC = ({ Deutsch - )} + )} ); From 65cdef3c3becaef2bf7b55cf0be1af0ef6de4808 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 13:14:41 +0530 Subject: [PATCH 060/199] feat: change comment for button --- src/components/molecules/SaveRecording.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index f0919239..1d03bcf4 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -77,8 +77,8 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { return (
- + {/* } - + {!getText && !getScreenshot && !getList && showCaptureList && } */} + {getList && ( <> @@ -533,8 +533,8 @@ export const RightSidePanel: React.FC = ({ onFinishCapture

{t('right_panel.limit.title')}

- + {/* +

{t('right_panel.limit.title')}

*/}
Date: Wed, 8 Jan 2025 13:30:20 +0530 Subject: [PATCH 063/199] feat: reset light theme --- src/context/theme-provider.tsx | 81 +++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx index a6097f95..fdf9fef5 100644 --- a/src/context/theme-provider.tsx +++ b/src/context/theme-provider.tsx @@ -4,33 +4,74 @@ import CssBaseline from '@mui/material/CssBaseline'; const lightTheme = createTheme({ palette: { - mode: 'light', primary: { - main: '#ff00c3', // Pink as the primary color - }, - background: { - default: '#ffffff', - paper: '#f5f5f5', - }, - text: { - primary: '#000000', + main: "#ff00c3", + contrastText: "#ffffff", }, }, components: { - MuiTabs: { - styleOverrides: { - indicator: { - backgroundColor: '#ff00c3', // Pink for tab indicators - }, - }, - }, MuiButton: { styleOverrides: { root: { - backgroundColor: '#ff00c3', // Pink button background - color: '#ffffff', - '&:hover': { - backgroundColor: '#e600b3', // Slightly darker pink on hover + // 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", + // }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + MuiAlert: { + styleOverrides: { + standardInfo: { + backgroundColor: "#fce1f4", + color: "#ff00c3", + "& .MuiAlert-icon": { + color: "#ff00c3", + }, + }, + }, + }, + MuiAlertTitle: { + styleOverrides: { + root: { + "& .MuiAlert-icon": { + color: "#ffffff", }, }, }, From 70086dd520a21011d2ea6ae457f4c55633666edf Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 15:04:05 +0530 Subject: [PATCH 064/199] feat: change dark theme config for navbar --- src/context/theme-provider.tsx | 126 +++++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx index fdf9fef5..d53ced24 100644 --- a/src/context/theme-provider.tsx +++ b/src/context/theme-provider.tsx @@ -83,33 +83,135 @@ const darkTheme = createTheme({ palette: { mode: 'dark', primary: { - main: '#ff00c3', // Pink as the primary color + main: "#ff00c3", + contrastText: "#ffffff", }, background: { default: '#121212', - paper: '#1e2124', + paper: '#1e1e1e', }, text: { primary: '#ffffff', + secondary: '#b3b3b3', }, }, components: { - MuiTabs: { - styleOverrides: { - indicator: { - backgroundColor: '#ff00c3', // Pink for tab indicators - }, - }, - }, MuiButton: { styleOverrides: { root: { - backgroundColor: '#ff00c3', // Pink button background + textTransform: "none", color: '#ffffff', - '&:hover': { - backgroundColor: '#e600b3', // Slightly darker pink on hover + '&.MuiButton-outlined': { + borderColor: '#ffffff', + color: '#ffffff', + "&:hover": { + borderColor: '#ffffff', + backgroundColor: 'rgba(255, 255, 255, 0.08)', + }, }, }, + containedPrimary: { + "&:hover": { + backgroundColor: "#ff66d9", + }, + }, + outlined: { + // Dark mode outlined buttons + backgroundColor: '#1e1e1e', + borderColor: '#ff00c3', + color: '#ff00c3', + "&:hover": { + backgroundColor: 'rgba(255, 0, 195, 0.08)', + borderColor: '#ff66d9', + }, + }, + }, + }, + MuiLink: { + styleOverrides: { + root: { + color: '#ff66d9', + "&:hover": { + color: "#ff00c3", + }, + }, + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + color: '#ffffff', + "&:hover": { + backgroundColor: 'rgba(255, 0, 195, 0.08)', + }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + color: '#ffffff', + "&.Mui-selected": { + color: '#ff00c3', + }, + }, + }, + }, + MuiAlert: { + styleOverrides: { + standardInfo: { + backgroundColor: "rgba(255, 0, 195, 0.15)", + color: "#ff66d9", + "& .MuiAlert-icon": { + color: "#ff66d9", + }, + }, + }, + }, + MuiAlertTitle: { + styleOverrides: { + root: { + "& .MuiAlert-icon": { + color: "#ff66d9", + }, + }, + }, + }, + // Additional dark mode specific components + MuiPaper: { + styleOverrides: { + root: { + backgroundColor: '#1e1e1e', + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + backgroundColor: '#121212', + }, + }, + }, + MuiDrawer: { + styleOverrides: { + paper: { + backgroundColor: '#121212', + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + borderBottom: '1px solid rgba(255, 255, 255, 0.12)', + }, + }, + }, + MuiDivider: { + styleOverrides: { + root: { + borderColor: 'rgba(255, 255, 255, 0.12)', + }, }, }, }, From eb50f4f1b48939b95a1b0cfcba24044d9e143662 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 16:02:27 +0530 Subject: [PATCH 065/199] feat: rm bg color for right side panel --- src/components/organisms/RightSidePanel.tsx | 31 +++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 3efb38d7..2eb41947 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -468,15 +468,16 @@ export const RightSidePanel: React.FC = ({ onFinishCapture return ( = ({ onFinishCapture Last action: {` ${lastAction}`} */} - - {!getText && !getScreenshot && !getList && showCaptureList && } - {/* - - {!getText && !getScreenshot && !getList && showCaptureList && } */} + {/* */} + {/* {!getText && !getScreenshot && !getList && showCaptureList && } */} + {/* */} + + {!getText && !getScreenshot && !getList && showCaptureList && } {getList && ( <> @@ -586,9 +587,9 @@ export const RightSidePanel: React.FC = ({ onFinishCapture
)} - {!getText && !getScreenshot && !getList && showCaptureText && } + {/* {!getText && !getScreenshot && !getList && showCaptureText && } */} -// {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> @@ -597,8 +598,8 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } - {!getText && !getScreenshot && !getList && showCaptureScreenshot && } -// {!getText && !getScreenshot && !getList && showCaptureScreenshot && } + {/* {!getText && !getScreenshot && !getList && showCaptureScreenshot && } */} + {!getText && !getScreenshot && !getList && showCaptureScreenshot && } {getScreenshot && ( From f9644d3d636e2cc61298e870b28e52437ac02b5a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 16:42:57 +0530 Subject: [PATCH 066/199] feat: change finish button color --- src/components/molecules/SaveRecording.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index 1d03bcf4..38720d07 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -80,7 +80,19 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { {/* From afcd69b61788d254b4035abdcc85e50473d37818 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 16:43:19 +0530 Subject: [PATCH 067/199] feat: change finish button border color --- src/components/molecules/SaveRecording.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index 38720d07..80685841 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -87,6 +87,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { sx={{ marginRight: '20px', color: '#00c853 !important', + borderColor: '#00c853 !important', '&:hover': { borderColor: '#00c853 !important', } From 5b891114311e5670ac52d3d5f9312ac89b074c25 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 16:44:40 +0530 Subject: [PATCH 068/199] feat: change navbar icons styling --- src/components/molecules/BrowserNavBar.tsx | 39 ++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/components/molecules/BrowserNavBar.tsx b/src/components/molecules/BrowserNavBar.tsx index d3cb781f..0f821d56 100644 --- a/src/components/molecules/BrowserNavBar.tsx +++ b/src/components/molecules/BrowserNavBar.tsx @@ -13,37 +13,34 @@ import { getCurrentUrl } from "../../api/recording"; import { useGlobalInfoStore } from '../../context/globalInfo'; import { useThemeMode } from '../../context/theme-provider'; +// const StyledNavBar = styled.div<{ browserWidth: number; isDarkMode: boolean }>` +// display: flex; +// align-items: center; +// padding: 10px 20px; +// background-color: ${({ isDarkMode }) => (isDarkMode ? '#2C2F33' : '#F5F5F5')}; +// width: ${({ browserWidth }) => `${browserWidth}px`}; +// border-radius: 0px 0px 8px 8px; +// box-shadow: ${({ isDarkMode }) => (isDarkMode ? '0px 2px 10px rgba(0, 0, 0, 0.2)' : '0px 2px 10px rgba(0, 0, 0, 0.1)')}; +// transition: background-color 0.3s ease, box-shadow 0.3s ease; +// margin-bottom: 15px; +// `; + const StyledNavBar = styled.div<{ browserWidth: number; isDarkMode: boolean }>` - display: flex; - align-items: center; - padding: 10px 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? '#2C2F33' : '#F5F5F5')}; - width: ${({ browserWidth }) => `${browserWidth}px`}; - border-radius: 0px 0px 8px 8px; - box-shadow: ${({ isDarkMode }) => (isDarkMode ? '0px 2px 10px rgba(0, 0, 0, 0.2)' : '0px 2px 10px rgba(0, 0, 0, 0.1)')}; - transition: background-color 0.3s ease, box-shadow 0.3s ease; - margin-bottom: 15px; + display: flex; + padding: 12px 0px; + background-color: ${({ isDarkMode }) => (isDarkMode ? '#2C2F33' : '#f6f6f6')}; + width: ${({ browserWidth }) => browserWidth}px; + border-radius: 0px 5px 0px 0px; `; const IconButton = styled(NavBarButton)<{ mode: string }>` - display: flex; - align-items: center; - justify-content: center; - padding: 8px; - margin-right: 12px; - background-color: ${({ mode }) => (mode === 'dark' ? '#40444B' : '#E0E0E0')}; - border-radius: 50%; + background-color: ${({ mode }) => (mode === 'dark' ? '#2C2F33' : '#f6f6f6')}; transition: background-color 0.3s ease, transform 0.1s ease; color: ${({ mode }) => (mode === 'dark' ? '#FFFFFF' : '#333')}; cursor: pointer; - &:hover { background-color: ${({ mode }) => (mode === 'dark' ? '#586069' : '#D0D0D0')}; } - - &:active { - transform: scale(0.95); - } `; interface NavBarProps { From cefa5dfa108b819e1b5c8e18c2d228c2ec292390 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 16:50:15 +0530 Subject: [PATCH 069/199] feat: change bg color on hover --- src/components/molecules/SaveRecording.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index 80685841..51b4143b 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -83,13 +83,14 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { setOpenModal(false)} modalStyle={modalStyle}> From 8e6228d77bd0b777a8848aee7672f7f9b90b392a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 8 Jan 2025 17:22:01 +0530 Subject: [PATCH 071/199] fix(temp): revert to old canvas --- src/components/atoms/canvas.tsx | 502 ++++++++++++-------------------- 1 file changed, 186 insertions(+), 316 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 8fd4f791..e71a4d93 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -1,147 +1,21 @@ -import React, { useCallback, useEffect, useRef, useMemo, Suspense } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useSocketStore } from '../../context/socket'; +import { getMappedCoordinates } from "../../helpers/inputHelpers"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; -const DatePicker = React.lazy(() => import('./DatePicker')); -const Dropdown = React.lazy(() => import('./Dropdown')); -const TimePicker = React.lazy(() => import('./TimePicker')); -const DateTimeLocalPicker = React.lazy(() => import('./DateTimeLocalPicker')); +import DatePicker from './DatePicker'; +import Dropdown from './Dropdown'; +import TimePicker from './TimePicker'; +import DateTimeLocalPicker from './DateTimeLocalPicker'; -class RAFScheduler { - private queue: Set<() => void> = new Set(); - private isProcessing: boolean = false; - private frameId: number | null = null; - - schedule(callback: () => void): void { - this.queue.add(callback); - if (!this.isProcessing) { - this.process(); - } - } - - private process = (): void => { - this.isProcessing = true; - this.frameId = requestAnimationFrame(() => { - const callbacks = Array.from(this.queue); - this.queue.clear(); - - callbacks.forEach(callback => { - try { - callback(); - } catch (error) { - console.error('RAF Scheduler error:', error); - } - }); - - this.isProcessing = false; - this.frameId = null; - - if (this.queue.size > 0) { - this.process(); - } - }); - } - - clear(): void { - this.queue.clear(); - if (this.frameId !== null) { - cancelAnimationFrame(this.frameId); - this.frameId = null; - } - this.isProcessing = false; - } -} - -class EventDebouncer { - private highPriorityQueue: Array<() => void> = []; - private lowPriorityQueue: Array<() => void> = []; - private processing: boolean = false; - private scheduler: RAFScheduler; - - constructor(scheduler: RAFScheduler) { - this.scheduler = scheduler; - } - - add(callback: () => void, highPriority: boolean = false): void { - if (highPriority) { - this.highPriorityQueue.push(callback); - } else { - this.lowPriorityQueue.push(callback); - } - - if (!this.processing) { - this.process(); - } - } - - private process(): void { - this.processing = true; - this.scheduler.schedule(() => { - while (this.highPriorityQueue.length > 0) { - const callback = this.highPriorityQueue.shift(); - callback?.(); - } - - if (this.lowPriorityQueue.length > 0) { - const callback = this.lowPriorityQueue.shift(); - callback?.(); - - if (this.lowPriorityQueue.length > 0) { - this.process(); - } - } - - this.processing = false; - }); - } - - clear(): void { - this.highPriorityQueue = []; - this.lowPriorityQueue = []; - this.processing = false; - } -} - -// Optimized measurement cache with LRU -class MeasurementCache { - private cache: Map; - private maxSize: number; - - constructor(maxSize: number = 100) { - this.cache = new Map(); - this.maxSize = maxSize; - } - - get(element: HTMLElement): DOMRect | undefined { - const cached = this.cache.get(element); - if (cached) { - // Refresh the entry - this.cache.delete(element); - this.cache.set(element, cached); - } - return cached; - } - - set(element: HTMLElement, rect: DOMRect): void { - if (this.cache.size >= this.maxSize) { - // Remove oldest entry - const firstKey = this.cache.keys().next().value; - if (firstKey !== undefined) { - this.cache.delete(firstKey); - } - } - this.cache.set(element, rect); - } - - clear(): void { - this.cache.clear(); - } +interface CreateRefCallback { + (ref: React.RefObject): void; } interface CanvasProps { width: number; height: number; - onCreateRef: (ref: React.RefObject) => void; + onCreateRef: CreateRefCallback; } /** @@ -152,229 +26,225 @@ export interface Coordinates { y: number; }; -const Canvas = React.memo(({ width, height, onCreateRef }: CanvasProps) => { +const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { + const canvasRef = useRef(null); const { socket } = useSocketStore(); const { setLastAction, lastAction } = useGlobalInfoStore(); const { getText, getList } = useActionContext(); + const getTextRef = useRef(getText); + const getListRef = useRef(getList); - const scheduler = useRef(new RAFScheduler()); - const debouncer = useRef(new EventDebouncer(scheduler.current)); - const measurementCache = useRef(new MeasurementCache(50)); - //const performanceMonitor = useRef(new FrontendPerformanceMonitor()); + const [datePickerInfo, setDatePickerInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); - const refs = useRef({ - getText, - getList, - lastMousePosition: { x: 0, y: 0 }, - lastFrameTime: 0, - context: null as CanvasRenderingContext2D | null, - }); + const [dropdownInfo, setDropdownInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + options: Array<{ + value: string; + text: string; + disabled: boolean; + selected: boolean; + }>; + } | null>(null); - const [state, dispatch] = React.useReducer((state: any, action: any) => { - switch (action.type) { - case 'BATCH_UPDATE': - return { ...state, ...action.payload }; - default: - return state; + const [timePickerInfo, setTimePickerInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); + + const [dateTimeLocalInfo, setDateTimeLocalInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); + + const notifyLastAction = (action: string) => { + if (lastAction !== action) { + setLastAction(action); } - }, { - datePickerInfo: null, - dropdownInfo: null, - timePickerInfo: null, - dateTimeLocalInfo: null - }); + }; - const getEventCoordinates = useCallback((event: MouseEvent): { x: number; y: number } => { - if (!canvasRef.current) return { x: 0, y: 0 }; + const lastMousePosition = useRef({ x: 0, y: 0 }); - let rect = measurementCache.current.get(canvasRef.current); - if (!rect) { - rect = canvasRef.current.getBoundingClientRect(); - measurementCache.current.set(canvasRef.current, rect); + useEffect(() => { + getTextRef.current = getText; + getListRef.current = getList; + }, [getText, getList]); + + useEffect(() => { + if (socket) { + socket.on('showDatePicker', (info: {coordinates: Coordinates, selector: string}) => { + setDatePickerInfo(info); + }); + + socket.on('showDropdown', (info: { + coordinates: Coordinates, + selector: string, + options: Array<{ + value: string; + text: string; + disabled: boolean; + selected: boolean; + }>; + }) => { + setDropdownInfo(info); + }); + + socket.on('showTimePicker', (info: {coordinates: Coordinates, selector: string}) => { + setTimePickerInfo(info); + }); + + socket.on('showDateTimePicker', (info: {coordinates: Coordinates, selector: string}) => { + setDateTimeLocalInfo(info); + }); + + return () => { + socket.off('showDatePicker'); + socket.off('showDropdown'); + socket.off('showTimePicker'); + socket.off('showDateTimePicker'); + }; } + }, [socket]); - return { - x: event.clientX - rect.left, - y: event.clientY - rect.top - }; - }, []); + const onMouseEvent = useCallback((event: MouseEvent) => { + 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 + }; - const handleMouseEvent = useCallback((event: MouseEvent) => { - if (!socket || !canvasRef.current) return; - - //performanceMonitor.current.measureEventLatency(event); - const coordinates = getEventCoordinates(event); - - switch (event.type) { - case 'mousedown': - debouncer.current.add(() => { - if (refs.current.getText) { + switch (event.type) { + case 'mousedown': + if (getTextRef.current === true) { console.log('Capturing Text...'); - } else if (refs.current.getList) { + } else if (getListRef.current === true) { console.log('Capturing List...'); } else { - socket.emit('input:mousedown', coordinates); + socket.emit('input:mousedown', clickCoordinates); } - setLastAction('click'); - }, true); // High priority - break; - - case 'mousemove': - if (refs.current.lastMousePosition.x !== coordinates.x || - refs.current.lastMousePosition.y !== coordinates.y) { - debouncer.current.add(() => { - refs.current.lastMousePosition = coordinates; - socket.emit('input:mousemove', coordinates); - setLastAction('move'); - }); - } - break; - - case 'wheel': - const wheelEvent = event as WheelEvent; - debouncer.current.add(() => { - socket.emit('input:wheel', { + notifyLastAction('click'); + break; + case 'mousemove': + if (lastMousePosition.current.x !== clickCoordinates.x || + lastMousePosition.current.y !== clickCoordinates.y) { + lastMousePosition.current = { + x: clickCoordinates.x, + y: clickCoordinates.y, + }; + socket.emit('input:mousemove', { + x: clickCoordinates.x, + y: clickCoordinates.y, + }); + notifyLastAction('move'); + } + break; + case 'wheel': + const wheelEvent = event as WheelEvent; + const deltas = { deltaX: Math.round(wheelEvent.deltaX), - deltaY: Math.round(wheelEvent.deltaY) - }); - setLastAction('scroll'); - }); - break; + deltaY: Math.round(wheelEvent.deltaY), + }; + socket.emit('input:wheel', deltas); + notifyLastAction('scroll'); + break; + default: + console.log('Default mouseEvent registered'); + return; + } } - }, [socket, getEventCoordinates]); + }, [socket]); - const handleKeyboardEvent = useCallback((event: KeyboardEvent) => { - if (!socket) return; - - debouncer.current.add(() => { + const onKeyboardEvent = useCallback((event: KeyboardEvent) => { + if (socket) { switch (event.type) { case 'keydown': - socket.emit('input:keydown', { - key: event.key, - coordinates: refs.current.lastMousePosition - }); - setLastAction(`${event.key} pressed`); + socket.emit('input:keydown', { key: event.key, coordinates: lastMousePosition.current }); + notifyLastAction(`${event.key} pressed`); break; case 'keyup': socket.emit('input:keyup', event.key); break; + default: + console.log('Default keyEvent registered'); + return; } - }, event.type === 'keydown'); // High priority for keydown + } }, [socket]); - // Setup and cleanup - useEffect(() => { - if (!canvasRef.current) return; - - const canvas = canvasRef.current; - refs.current.context = canvas.getContext('2d', { - alpha: false, - desynchronized: true - }); - - onCreateRef(canvasRef); - - const options = { passive: true }; - canvas.addEventListener('mousedown', handleMouseEvent, options); - canvas.addEventListener('mousemove', handleMouseEvent, options); - canvas.addEventListener('wheel', handleMouseEvent, options); - canvas.addEventListener('keydown', handleKeyboardEvent, options); - canvas.addEventListener('keyup', handleKeyboardEvent, options); - - return () => { - canvas.removeEventListener('mousedown', handleMouseEvent); - canvas.removeEventListener('mousemove', handleMouseEvent); - canvas.removeEventListener('wheel', handleMouseEvent); - canvas.removeEventListener('keydown', handleKeyboardEvent); - canvas.removeEventListener('keyup', handleKeyboardEvent); - - scheduler.current.clear(); - debouncer.current.clear(); - measurementCache.current.clear(); - }; - }, [handleMouseEvent, handleKeyboardEvent, onCreateRef]); - - // Performance monitoring - // useEffect(() => { - // const intervalId = setInterval(() => { - // console.log('Performance Report:', performanceMonitor.current.getPerformanceReport()); - // }, 20000); - - // return () => clearInterval(intervalId); - // }, []); useEffect(() => { - if (!socket) return; + if (canvasRef.current) { + onCreateRef(canvasRef); + canvasRef.current.addEventListener('mousedown', onMouseEvent); + canvasRef.current.addEventListener('mousemove', onMouseEvent); + canvasRef.current.addEventListener('wheel', onMouseEvent, { passive: true }); + canvasRef.current.addEventListener('keydown', onKeyboardEvent); + canvasRef.current.addEventListener('keyup', onKeyboardEvent); - const handlers = { - showDatePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { datePickerInfo: info } }), - showDropdown: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { dropdownInfo: info } }), - showTimePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { timePickerInfo: info } }), - showDateTimePicker: (info: any) => dispatch({ type: 'BATCH_UPDATE', payload: { dateTimeLocalInfo: info } }) - }; + return () => { + if (canvasRef.current) { + canvasRef.current.removeEventListener('mousedown', onMouseEvent); + canvasRef.current.removeEventListener('mousemove', onMouseEvent); + canvasRef.current.removeEventListener('wheel', onMouseEvent); + canvasRef.current.removeEventListener('keydown', onKeyboardEvent); + canvasRef.current.removeEventListener('keyup', onKeyboardEvent); + } - Object.entries(handlers).forEach(([event, handler]) => socket.on(event, handler)); - return () => { - Object.keys(handlers).forEach(event => socket.off(event)); - }; - }, [socket]); + }; + } else { + console.log('Canvas not initialized'); + } - const memoizedDimensions = useMemo(() => ({ - width: width || 900, - height: height || 400 - }), [width, height]); + }, [onMouseEvent]); return ( -
+
- - {state.datePickerInfo && ( - dispatch({ - type: 'BATCH_UPDATE', - payload: { datePickerInfo: null } - })} - /> - )} - {state.dropdownInfo && ( - dispatch({ - type: 'BATCH_UPDATE', - payload: { dropdownInfo: null } - })} - /> - )} - {state.timePickerInfo && ( - dispatch({ type: 'SET_TIME_PICKER', payload: null })} - /> - )} - {state.dateTimeLocalInfo && ( - dispatch({ type: 'SET_DATETIME_PICKER', payload: null })} - /> - )} - + {datePickerInfo && ( + setDatePickerInfo(null)} + /> + )} + {dropdownInfo && ( + setDropdownInfo(null)} + /> + )} + {timePickerInfo && ( + setTimePickerInfo(null)} + /> + )} + {dateTimeLocalInfo && ( + setDateTimeLocalInfo(null)} + /> + )}
); -}); -Canvas.displayName = 'Canvas'; +}; + export default Canvas; \ No newline at end of file From c46ef9fda7c8acc581b8cb7300cdb22dca4848f7 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 8 Jan 2025 17:22:35 +0530 Subject: [PATCH 072/199] chore: FE 0.0.7 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6506a4c5..91b72428 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,7 +70,7 @@ services: #build: #context: . #dockerfile: Dockerfile - image: getmaxun/maxun-frontend:v0.0.6 + image: getmaxun/maxun-frontend:v0.0.7 ports: - "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}" env_file: .env From 843db677731e74611c448833e3f90f0c7ea2e6a2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 17:44:17 +0530 Subject: [PATCH 073/199] feat: add styling to right side panel buttons --- src/components/organisms/RightSidePanel.tsx | 103 ++++++++++++++++++-- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 2eb41947..30d976d9 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -500,6 +500,14 @@ export const RightSidePanel: React.FC = ({ onFinishCapture @@ -508,13 +516,32 @@ export const RightSidePanel: React.FC = ({ onFinishCapture variant="outlined" onClick={handleConfirmListCapture} disabled={captureStage === 'initial' ? isConfirmCaptureDisabled : hasUnconfirmedListTextFields} + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + '&:hover': { + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + } + }} > {captureStage === 'initial' ? t('right_panel.buttons.confirm_capture') : captureStage === 'pagination' ? t('right_panel.buttons.confirm_pagination') : captureStage === 'limit' ? t('right_panel.buttons.confirm_limit') : t('right_panel.buttons.finish_capture')} - @@ -523,11 +550,75 @@ export const RightSidePanel: React.FC = ({ onFinishCapture {showPaginationOptions && ( {t('right_panel.pagination.title')} - - - - - + + + + + )} {showLimitOptions && ( From 8ea90a7c2a2a3ac5d4d29a061eac2efdf882e7c4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 17:47:11 +0530 Subject: [PATCH 074/199] feat: add style to select attribute button --- src/components/organisms/BrowserWindow.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 409079a9..9e8902ba 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -403,6 +403,14 @@ export const BrowserWindow = () => { overflow: 'hidden', padding: '5px 10px', }} + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + '&:hover': { + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + } + }} > Date: Wed, 8 Jan 2025 17:49:38 +0530 Subject: [PATCH 075/199] feat: latest compose changes for upgrade --- src/components/molecules/NavBar.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 8aeeb05d..2a38ad39 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -261,6 +261,11 @@ export const NavBar: React.FC = ({ docker-compose down

+ # replace existing docker-compose file with new one by copy pasting the code from +
+ Latest Docker Compose +
+
# pull latest docker images
docker-compose pull From 07f1aba701ac3e81d506e802ab360158cced8067 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 17:59:36 +0530 Subject: [PATCH 076/199] feat: add default bg color on selection --- src/components/organisms/RightSidePanel.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 30d976d9..430377d8 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -556,7 +556,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture sx={{ color: '#ff00c3 !important', borderColor: '#ff00c3 !important', - backgroundColor: paginationType === 'clickNext' && isDarkMode ? 'whitesmoke !important' : 'transparent !important', + backgroundColor: paginationType === 'clickNext' ? 'whitesmoke !important' : 'transparent !important', '&:hover': { borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', @@ -570,7 +570,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture sx={{ color: '#ff00c3 !important', borderColor: '#ff00c3 !important', - backgroundColor: paginationType === 'clickLoadMore' && isDarkMode ? 'whitesmoke !important' : 'transparent !important', + backgroundColor: paginationType === 'clickLoadMore' ? 'whitesmoke !important' : 'transparent !important', '&:hover': { borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', @@ -584,7 +584,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture sx={{ color: '#ff00c3 !important', borderColor: '#ff00c3 !important', - backgroundColor: paginationType === 'scrollDown' && isDarkMode ? 'whitesmoke !important' : 'transparent !important', + backgroundColor: paginationType === 'scrollDown' ? 'whitesmoke !important' : 'transparent !important', '&:hover': { borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', @@ -598,7 +598,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture sx={{ color: '#ff00c3 !important', borderColor: '#ff00c3 !important', - backgroundColor: paginationType === 'scrollUp' && isDarkMode ? 'whitesmoke !important' : 'transparent !important', + backgroundColor: paginationType === 'scrollUp' ? 'whitesmoke !important' : 'transparent !important', '&:hover': { borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', @@ -612,7 +612,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture sx={{ color: '#ff00c3 !important', borderColor: '#ff00c3 !important', - backgroundColor: paginationType === 'none' && isDarkMode ? 'whitesmoke !important' : 'transparent !important', + backgroundColor: paginationType === 'none' ? 'whitesmoke !important' : 'transparent !important', '&:hover': { borderColor: '#ff00c3 !important', backgroundColor: 'whitesmoke !important', From 8cb72691d1b688e743c290bd764b4278c1aab38a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 18:06:18 +0530 Subject: [PATCH 077/199] feat: add styling for cancel button --- src/components/molecules/BrowserRecordingSave.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/BrowserRecordingSave.tsx b/src/components/molecules/BrowserRecordingSave.tsx index f2ededea..4eda4ec4 100644 --- a/src/components/molecules/BrowserRecordingSave.tsx +++ b/src/components/molecules/BrowserRecordingSave.tsx @@ -63,7 +63,17 @@ const BrowserRecordingSave = () => { - From e0c3cf7f55970098378d2d85aedd03b113ad357e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 18:11:23 +0530 Subject: [PATCH 078/199] feat: add styling for cancel button --- src/components/molecules/ScheduleSettings.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 917696c9..95282163 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -273,7 +273,19 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia - From baf0845f67bb9fa1bd6d01c0831889b4092dec87 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 18:11:43 +0530 Subject: [PATCH 079/199] feat: add styling for cancel button --- src/components/molecules/RobotEdit.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 6547d93b..30878f77 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -179,6 +179,14 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin color="primary" variant="outlined" style={{ marginLeft: '10px' }} + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + '&:hover': { + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + } + }} > {t('robot_edit.cancel')} From 9210d2f9ee579b78f871eb699cf2c21c2c240586 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 18:12:00 +0530 Subject: [PATCH 080/199] feat: add styling for cancel button --- src/components/molecules/RobotDuplicate.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index ce3ee5ca..68711aa0 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -152,7 +152,19 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia - From 94bb35cc8c0282adb5f5a061e909b4f91db8ed12 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 8 Jan 2025 19:58:50 +0530 Subject: [PATCH 081/199] feat: add theme icon and change colors --- src/components/molecules/NavBar.tsx | 174 ++-------------------------- 1 file changed, 10 insertions(+), 164 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 01fa01b7..c68873ff 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -103,79 +103,6 @@ export const NavBar: React.FC = ({ localStorage.setItem("language", lang); }; -// const renderBrandSection = () => ( -// -// -// Maxun -// -// -// ); - -// const renderSocialButtons = () => ( -// <> -// -// -// -//