diff --git a/src/components/robot/RobotDuplicate.tsx b/legacy/src/RobotDuplicate.tsx similarity index 100% rename from src/components/robot/RobotDuplicate.tsx rename to legacy/src/RobotDuplicate.tsx diff --git a/src/components/robot/RobotEdit.tsx b/legacy/src/RobotEdit.tsx similarity index 100% rename from src/components/robot/RobotEdit.tsx rename to legacy/src/RobotEdit.tsx diff --git a/src/components/robot/RobotSettings.tsx b/legacy/src/RobotSettings.tsx similarity index 100% rename from src/components/robot/RobotSettings.tsx rename to legacy/src/RobotSettings.tsx diff --git a/src/components/robot/ScheduleSettings.tsx b/legacy/src/ScheduleSettings.tsx similarity index 100% rename from src/components/robot/ScheduleSettings.tsx rename to legacy/src/ScheduleSettings.tsx diff --git a/src/components/action/ActionDescriptionBox.tsx b/src/components/action/ActionDescriptionBox.tsx index d36db407..fb3a87d9 100644 --- a/src/components/action/ActionDescriptionBox.tsx +++ b/src/components/action/ActionDescriptionBox.tsx @@ -16,7 +16,7 @@ const CustomBoxContainer = styled.div` min-height: 100px; height: auto; border-radius: 5px; - background-color: ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')}; + background-color: ${({ isDarkMode }) => (isDarkMode ? '#1d1c1cff' : 'white')}; color: ${({ isDarkMode }) => (isDarkMode ? 'white' : 'black')}; margin: 80px 13px 25px 13px; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); @@ -31,7 +31,7 @@ const Triangle = styled.div` height: 0; border-left: 20px solid transparent; border-right: 20px solid transparent; - border-bottom: 20px solid ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')}; + border-bottom: 20px solid ${({ isDarkMode }) => (isDarkMode ? '#1d1c1cff' : 'white')}; `; const Logo = styled.img` @@ -102,7 +102,7 @@ const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { sx={{ color: isDarkMode ? 'white' : 'default', '&.Mui-checked': { - color: '#ff33cc', + color: '#ff00c3', }, }} /> @@ -138,4 +138,4 @@ const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { ); }; -export default ActionDescriptionBox; \ No newline at end of file +export default ActionDescriptionBox; diff --git a/src/components/browser/BrowserNavBar.tsx b/src/components/browser/BrowserNavBar.tsx index a06b7b4e..e651ed26 100644 --- a/src/components/browser/BrowserNavBar.tsx +++ b/src/components/browser/BrowserNavBar.tsx @@ -14,18 +14,18 @@ import { useThemeMode } from '../../context/theme-provider'; const StyledNavBar = styled.div<{ browserWidth: number; isDarkMode: boolean }>` display: flex; padding: 12px 0px; - background-color: ${({ isDarkMode }) => (isDarkMode ? '#2C2F33' : '#f6f6f6')}; + background-color: ${({ isDarkMode }) => (isDarkMode ? '#1d1c1cff' : '#f6f6f6')}; width: ${({ browserWidth }) => browserWidth}px; border-radius: 0px 5px 0px 0px; `; const IconButton = styled(NavBarButton) <{ mode: string }>` - background-color: ${({ mode }) => (mode === 'dark' ? '#2C2F33' : '#f6f6f6')}; + background-color: ${({ mode }) => (mode === 'dark' ? '#1d1c1cff' : '#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')}; + background-color: ${({ mode }) => (mode === 'dark' ? '#1d1c1cff' : '#D0D0D0')}; } `; diff --git a/src/components/browser/BrowserTabs.tsx b/src/components/browser/BrowserTabs.tsx index f34fda37..e375fb93 100644 --- a/src/components/browser/BrowserTabs.tsx +++ b/src/components/browser/BrowserTabs.tsx @@ -49,7 +49,7 @@ export const BrowserTabs = ( background: 'white', borderRadius: '5px 5px 0px 0px', '&.Mui-selected': { - backgroundColor: ` ${isDarkMode ? "#2a2a2a" : "#f5f5f5"}`, + backgroundColor: ` ${isDarkMode ? "#121111ff" : "#f5f5f5"}`, color: '#ff00c3', // Slightly lighter text when selected }, }} diff --git a/src/components/dashboard/MainMenu.tsx b/src/components/dashboard/MainMenu.tsx index 1a3168b9..a1eb5797 100644 --- a/src/components/dashboard/MainMenu.tsx +++ b/src/components/dashboard/MainMenu.tsx @@ -3,8 +3,8 @@ import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; import { useNavigate, useLocation } from 'react-router-dom'; -import { Paper, Button, useTheme, Modal, Typography, Stack, TextField, InputAdornment, IconButton } from "@mui/material"; -import { AutoAwesome, FormatListBulleted, VpnKey, Usb, CloudQueue, Description, Favorite, ContentCopy, SlowMotionVideo } from "@mui/icons-material"; +import { Paper, Button, useTheme, Modal, Typography, Stack, Divider } from "@mui/material"; +import { AutoAwesome, FormatListBulleted, VpnKey, Usb, CloudQueue, Description, Favorite, SlowMotionVideo } from "@mui/icons-material"; import { useTranslation } from 'react-i18next'; import { useGlobalInfoStore } from "../../context/globalInfo"; @@ -87,19 +87,41 @@ export const MainMenu = ({ value = 'robots', handleChangeContent }: MainMenuProp textColor="primary" indicatorColor="primary" orientation="vertical" - sx={{ alignItems: 'flex-start' }} + sx={{ alignItems: 'flex-start', '& .MuiTabs-indicator': { display: 'none' }} > - } iconPosition="start" sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} onClick={handleRobotsClick} /> - } iconPosition="start" sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> - } iconPosition="start" sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> - } iconPosition="start" sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> + } + iconPosition="start" + disableRipple={true} + sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} + onClick={handleRobotsClick} /> + } + iconPosition="start" + disableRipple={true} + sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> + } + iconPosition="start" + disableRipple={true} + sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> + } + iconPosition="start" + disableRipple={true} + sx={{ justifyContent: 'flex-start', textAlign: 'left', fontSize: 'medium' }} /> -
+ @@ -133,10 +155,10 @@ export const MainMenu = ({ value = 'robots', handleChangeContent }: MainMenuProp href='https://app.maxun.dev/' target="_blank" rel="noopener noreferrer" - sx={buttonStyles} startIcon={}> + sx={buttonStyles} startIcon={}> Join Maxun Cloud - diff --git a/src/components/dashboard/NavBar.tsx b/src/components/dashboard/NavBar.tsx index 24e11d09..33a52d65 100644 --- a/src/components/dashboard/NavBar.tsx +++ b/src/components/dashboard/NavBar.tsx @@ -27,10 +27,10 @@ import { GitHub, Update, Close, - Language, Description, LightMode, - DarkMode + DarkMode, + Translate } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; @@ -158,14 +158,11 @@ export const NavBar: React.FC = ({ }; const renderThemeToggle = () => ( - + {darkMode ? : } @@ -236,7 +233,7 @@ export const NavBar: React.FC = ({ cursor: 'pointer' }} onClick={() => navigate('/')}> - +
{t('navbar.project_name')}
= ({ {t('navbar.menu_items.logout')} - {t('navbar.menu_items.language')} + {t('navbar.menu_items.language')}
{ @@ -516,10 +513,10 @@ export const NavBar: React.FC = ({ alignItems: "center", borderRadius: "5px", padding: "8px", - marginRight: "8px", + marginRight: "4px", }} > - {t("Language")} +
= ({ const NavBarWrapper = styled.div<{ mode: 'light' | 'dark' }>` grid-area: navbar; - background-color: ${({ mode }) => (mode === 'dark' ? '#1e2124' : '#ffffff')}; + background-color: ${({ mode }) => (mode === 'dark' ? '#000000ff' : '#ffffff')}; padding: 5px; display: flex; justify-content: space-between; - border-bottom: 1px solid ${({ mode }) => (mode === 'dark' ? '#333' : '#e0e0e0')}; + border-bottom: 1px solid ${({ mode }) => (mode === 'dark' ? '#000000ff' : '#e0e0e0')}; `; const ProjectName = styled.b<{ mode: 'light' | 'dark' }>` diff --git a/src/components/recorder/DOMBrowserRenderer.tsx b/src/components/recorder/DOMBrowserRenderer.tsx index e07f87be..9e818e31 100644 --- a/src/components/recorder/DOMBrowserRenderer.tsx +++ b/src/components/recorder/DOMBrowserRenderer.tsx @@ -902,6 +902,7 @@ export const DOMBrowserRenderer: React.FC = ({ rebuild(snapshotData.snapshot, { doc: iframeDoc, mirror: mirror, + hackCss: false, cache: { stylesWithHoverClass: new Map() }, afterAppend: (node) => { if (node.nodeType === Node.TEXT_NODE && node.textContent) { diff --git a/src/components/recorder/RightSidePanel.tsx b/src/components/recorder/RightSidePanel.tsx index e22ab08b..e506bc6a 100644 --- a/src/components/recorder/RightSidePanel.tsx +++ b/src/components/recorder/RightSidePanel.tsx @@ -785,6 +785,136 @@ 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: isDarkMode ? "#1d1c1cff" : 'white', color: isDarkMode ? "white" : 'black' }}> + { + step.type === 'text' && ( + <> + handleTextLabelChange(step.id, e.target.value)} + fullWidth + size="small" + margin="normal" + error={!!errors[step.id]} + helperText={errors[step.id]} + InputProps={{ + readOnly: confirmedTextSteps[step.id], + startAdornment: ( + + + + ) + }} + /> + + + + ) + }} + /> + {!confirmedTextSteps[step.id] ? ( + + + + + ) : !isCaptureTextConfirmed && ( + + + + )} + + )} + {step.type === 'screenshot' && ( + + + + {step.fullPage ? + t('right_panel.screenshot.display_fullpage') : + t('right_panel.screenshot.display_visible')} + + + )} + {step.type === 'list' && ( + Object.entries(step.fields).length === 0 ? ( + {t('right_panel.messages.list_empty')} + ) : ( + <> + {t('right_panel.messages.list_selected')} + {Object.entries(step.fields).map(([key, field]) => ( + + handleTextLabelChange(field.id, e.target.value, step.id, key)} + fullWidth + margin="normal" + InputProps={{ + readOnly: confirmedListTextFields[field.id]?.[key], + startAdornment: ( + + + + ) + }} + /> + + + + ) + }} + /> + {!confirmedListTextFields[step.id]?.[key] && ( + + + + + )} + + ))} + + ) + )} + + ))} + ); }; diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index a1f0daaa..504cfa43 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -533,7 +533,7 @@ export const RecordingsTable = ({ <> - + {/* */} {columns.map((column) => ( ))} - + {/* */} {visibleRows.map((row) => ( *": { marginBottom: "20px" }, + marginTop: "-20px", }} > <> diff --git a/src/components/run/InterpretationLog.tsx b/src/components/run/InterpretationLog.tsx index 96fd2867..712a8bc5 100644 --- a/src/components/run/InterpretationLog.tsx +++ b/src/components/run/InterpretationLog.tsx @@ -21,14 +21,17 @@ import { useThemeMode } from '../../context/theme-provider'; import { useTranslation } from 'react-i18next'; import { useBrowserSteps } from '../../context/browserSteps'; import { useActionContext } from '../../context/browserActions'; +import { useTutorial } from '../../context/tutorial'; interface InterpretationLogProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; + tutorialMode?: boolean; } -export const InterpretationLog: React.FC = ({ isOpen, setIsOpen }) => { +export const InterpretationLog: React.FC = ({ isOpen, setIsOpen, tutorialMode = false }) => { const { t } = useTranslation(); + const tutorial = tutorialMode ? useTutorial() : null; const [captureListData, setCaptureListData] = useState([]); const [captureTextData, setCaptureTextData] = useState([]); @@ -98,12 +101,18 @@ export const InterpretationLog: React.FC = ({ isOpen, se updateListTextFieldLabel(editingField.listId, editingField.fieldKey, editingValue.trim()); + // Emit updated action to backend after state update completes if (actionId) { setTimeout(() => emitForStepId(actionId), 0); } setEditingField(null); setEditingValue(''); + + // Advance tutorial if in tutorial mode + if (tutorialMode && tutorial) { + tutorial.handleFieldLabelsEdited(); + } } }; @@ -118,6 +127,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se removeListTextField(listId, fieldKey); + // Emit updated action to backend after state update completes if (actionId) { setTimeout(() => emitForStepId(actionId), 0); } @@ -135,13 +145,20 @@ export const InterpretationLog: React.FC = ({ isOpen, se updateListStepName(editingListName, finalName); + // Use ref-synced version of browserSteps via emitForStepId const listStep = browserSteps.find(step => step.id === editingListName); if (listStep?.actionId) { + // small async delay ensures React state commit setTimeout(() => emitForStepId(listStep.actionId!), 0); } setEditingListName(null); setEditingListNameValue(''); + + // Advance tutorial if in tutorial mode + if (tutorialMode && tutorial) { + tutorial.handleListNameEdited(); + } } }; @@ -154,9 +171,11 @@ export const InterpretationLog: React.FC = ({ isOpen, se const trimmedName = editingTextGroupNameValue.trim(); const finalName = trimmedName || 'Text Data'; + console.log("SAVING TEXT GROUP NAME:", finalName); setCurrentTextGroupName(finalName); setEditingTextGroupName(false); + // Emit after React updates global state setTimeout(() => { const activeTextStep = captureTextData.find(step => step.actionId); if (activeTextStep?.actionId) emitForStepId(activeTextStep.actionId); @@ -176,6 +195,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se updateBrowserTextStepLabel(editingTextLabel, editingTextLabelValue.trim()); + // Emit updated action to backend after state update completes if (actionId) { setTimeout(() => emitForStepId(actionId), 0); } @@ -196,7 +216,9 @@ export const InterpretationLog: React.FC = ({ isOpen, se deleteBrowserStep(textId); + // Emit updated action to backend after deletion if (actionId) { + // Small delay to ensure state update completes setTimeout(() => emitForStepId(actionId), 0); } }; @@ -212,7 +234,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se const screenshotSteps = browserSteps.filter(step => step.type === 'screenshot'); const screenshotIndex = screenshotSteps.findIndex(s => s.id === editingScreenshotName); const finalName = trimmedName || `Screenshot ${screenshotIndex + 1}`; - + updateScreenshotStepName(editingScreenshotName, finalName); const screenshotStep = browserSteps.find(step => step.id === editingScreenshotName); @@ -220,8 +242,11 @@ export const InterpretationLog: React.FC = ({ isOpen, se const originalName = screenshotStep.name?.trim() || ""; const trimmedName = editingScreenshotNameValue.trim(); + // 🚫 Only emit if name actually changed if (trimmedName && trimmedName !== originalName) { setTimeout(() => emitForStepId(screenshotStep.actionId!), 500); + } else { + console.log("🧠 Skipping emit — screenshot name unchanged."); } } @@ -251,6 +276,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se setActiveTab(availableTabs.findIndex(tab => tab.id === 'captureText')); } else if (hasNewScreenshotData && availableTabs.findIndex(tab => tab.id === 'captureScreenshot') !== -1) { setActiveTab(availableTabs.findIndex(tab => tab.id === 'captureScreenshot')); + // Set the active screenshot tab to the latest screenshot setActiveScreenshotTab(screenshotData.length - 1); } }, [captureListData.length, captureTextData.length, screenshotData.length]); @@ -313,24 +339,24 @@ export const InterpretationLog: React.FC = ({ isOpen, se const getAvailableTabs = useCallback(() => { const tabs = []; - + if (captureListData.length > 0) { tabs.push({ id: 'captureList', label: 'Lists' }); } - + if (captureTextData.length > 0) { tabs.push({ id: 'captureText', label: 'Texts' }); } - + if (screenshotData.length > 0) { tabs.push({ id: 'captureScreenshot', label: 'Screenshots' }); } - + return tabs; }, [captureListData.length, captureTextData.length, screenshotData.length, showPreviewData]); const availableTabs = getAvailableTabs(); - + useEffect(() => { if (activeTab >= availableTabs.length && availableTabs.length > 0) { setActiveTab(0); @@ -503,7 +529,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se {showPreviewData && availableTabs.length > 0 && ( <> {shouldShowTabs && ( - = ({ isOpen, se px: 4, py: 2, cursor: 'pointer', + // borderBottom: activeTab === index ? '2px solid' : 'none', borderColor: activeTab === index ? (darkMode ? '#ff00c3' : '#ff00c3') : 'transparent', backgroundColor: activeTab === index ? (darkMode ? '#121111ff' : '#e9ecef') : 'transparent', color: darkMode ? 'white' : 'black', @@ -537,12 +564,13 @@ export const InterpretationLog: React.FC = ({ isOpen, se ))} )} - + {(activeTab === availableTabs.findIndex(tab => tab.id === 'captureList') || singleContentType === 'captureList') && captureListData.length > 0 && ( + {/* List Tabs */} = ({ isOpen, se return ( { if (!isEditing) { setActiveListTab(index); @@ -647,6 +676,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se })} + {/* Table Below Tabs */} = ({ isOpen, se {Object.entries(captureListData[activeListTab]?.fields || {}).map(([fieldKey, field]: [string, any]) => { const isEditing = editingField?.listId === captureListData[activeListTab]?.id && editingField?.fieldKey === fieldKey; + const isFirstField = Object.keys(captureListData[activeListTab]?.fields || {}).indexOf(fieldKey) === 0; + return ( = ({ isOpen, se {(captureListData[activeListTab]?.data || []) - .slice(0, Math.min(captureListData[activeListTab]?.limit || 10, 5)) + .slice(0, tutorialMode ? (captureListData[activeListTab]?.data?.length || 0) : Math.min(captureListData[activeListTab]?.limit || 10, 5)) .map((row: any, rowIndex: any) => ( {Object.values(captureListData[activeListTab]?.fields || {}).map((field: any, colIndex) => ( - = ({ isOpen, se {(activeTab === availableTabs.findIndex(tab => tab.id === 'captureScreenshot') || singleContentType === 'captureScreenshot') && screenshotData.length > 0 && ( + {/* Screenshot Tabs */} = ({ isOpen, se })()} + {/* Screenshot Image */} = ({ isOpen, se ); -}; +}; \ No newline at end of file diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx index 75dd4b8b..ef16069f 100644 --- a/src/context/theme-provider.tsx +++ b/src/context/theme-provider.tsx @@ -93,8 +93,8 @@ const darkTheme = createTheme({ contrastText: '#ffffff', }, background: { - default: '#121212', - paper: '#1e1e1e', + default: '#000000ff', + paper: '#000000ff', }, text: { primary: '#ffffff', @@ -112,7 +112,7 @@ const darkTheme = createTheme({ color: '#ffffff', "&:hover": { borderColor: '#ffffff', - backgroundColor: 'rgba(255, 255, 255, 0.08)', + backgroundColor: 'inherit', }, }, }, @@ -127,14 +127,14 @@ const darkTheme = createTheme({ borderColor: '#ff00c3', color: '#ff00c3', "&:hover": { - backgroundColor: 'rgba(255, 0, 195, 0.08)', + // backgroundColor: 'rgba(255, 0, 195, 0.08)', borderColor: '#ff66d9', }, '&.MuiButton-outlinedError': { borderColor: '#f44336', color: '#f44336', "&:hover": { - backgroundColor: 'rgba(244, 67, 54, 0.08)', + // backgroundColor: 'rgba(244, 67, 54, 0.08)', borderColor: '#d32f2f', }, }, @@ -155,14 +155,14 @@ const darkTheme = createTheme({ styleOverrides: { root: { color: '#ffffff', - "&:hover": { - backgroundColor: 'rgba(255, 0, 195, 0.08)', - }, + // "&:hover": { + // backgroundColor: 'rgba(255, 0, 195, 0.08)', + // }, '&.MuiIconButton-colorError': { color: '#f44336', - "&:hover": { - backgroundColor: 'rgba(244, 67, 54, 0.08)', - }, + // "&:hover": { + // backgroundColor: 'rgba(244, 67, 54, 0.08)', + // }, }, }, }, @@ -202,38 +202,47 @@ const darkTheme = createTheme({ MuiPaper: { styleOverrides: { root: { - backgroundColor: '#1e1e1e', + backgroundColor: '#000000ff', + border: '1px solid #080808ff', }, }, }, MuiAppBar: { styleOverrides: { root: { - backgroundColor: '#121212', + backgroundColor: '#080808ff', }, }, }, MuiDrawer: { styleOverrides: { paper: { - backgroundColor: '#121212', + backgroundColor: '#080808ff', }, }, }, MuiTableCell: { styleOverrides: { root: { - borderBottom: '1px solid rgba(255, 255, 255, 0.12)', + borderBottom: '1px solid #080808ff', }, }, }, MuiDivider: { styleOverrides: { root: { - borderColor: 'rgba(255, 255, 255, 0.12)', + borderColor: '#494949ff', }, }, }, + // MuiTextField:{ + // styleOverrides: { + // root: { + // '& .MuiInputBase-root': { + // backgroundColor: '#1d1c1cff', + // }, + // } + // }} }, }); @@ -273,4 +282,4 @@ const ThemeModeProvider = ({ children }: { children: React.ReactNode }) => { ); }; -export default ThemeModeProvider; +export default ThemeModeProvider; \ No newline at end of file diff --git a/src/helpers/clientSelectorGenerator.ts b/src/helpers/clientSelectorGenerator.ts index 385aa8ec..d4e5051c 100644 --- a/src/helpers/clientSelectorGenerator.ts +++ b/src/helpers/clientSelectorGenerator.ts @@ -555,38 +555,25 @@ class ClientSelectorGenerator { */ private isMeaningfulElement(element: HTMLElement): boolean { const tagName = element.tagName.toLowerCase(); - - // Fast path for common meaningful elements - if (["a", "img", "input", "button", "select"].includes(tagName)) { + + if (tagName === "img") { + return element.hasAttribute("src"); + } + + if (tagName === "a" && element.hasAttribute("href")) { return true; } + if (element.children.length > 0) { + return false; + } + const text = (element.textContent || "").trim(); - const hasHref = element.hasAttribute("href"); - const hasSrc = element.hasAttribute("src"); - - // Quick checks first - if (text.length > 0 || hasHref || hasSrc) { + + if (text.length > 0) { return true; } - const isCustomElement = tagName.includes("-"); - - // For custom elements, be more lenient about what's considered meaningful - if (isCustomElement) { - const hasChildren = element.children.length > 0; - const hasSignificantAttributes = Array.from(element.attributes).some( - (attr) => !["class", "style", "id"].includes(attr.name.toLowerCase()) - ); - - return ( - hasChildren || - hasSignificantAttributes || - element.hasAttribute("role") || - element.hasAttribute("aria-label") - ); - } - return false; } @@ -2561,12 +2548,9 @@ class ClientSelectorGenerator { const MAX_MEANINGFUL_ELEMENTS = 300; const MAX_NODES_TO_CHECK = 1200; - const MAX_DEPTH = 12; + const MAX_DEPTH = 20; let nodesChecked = 0; - let adjustedMaxDepth = MAX_DEPTH; - const elementDensityThreshold = 50; - const depths: number[] = [0]; let queueIndex = 0; @@ -2576,14 +2560,10 @@ class ClientSelectorGenerator { queueIndex++; nodesChecked++; - if (currentDepth <= 3 && meaningfulDescendants.length > elementDensityThreshold) { - adjustedMaxDepth = Math.max(6, adjustedMaxDepth - 2); - } - if ( nodesChecked > MAX_NODES_TO_CHECK || meaningfulDescendants.length >= MAX_MEANINGFUL_ELEMENTS || - currentDepth > adjustedMaxDepth + currentDepth > MAX_DEPTH ) { break; } @@ -2592,7 +2572,7 @@ class ClientSelectorGenerator { meaningfulDescendants.push(element); } - if (currentDepth >= adjustedMaxDepth) { + if (currentDepth >= MAX_DEPTH) { continue; } @@ -2607,7 +2587,7 @@ class ClientSelectorGenerator { } } - if (element.shadowRoot && currentDepth < adjustedMaxDepth - 1) { + if (element.shadowRoot && currentDepth < MAX_DEPTH - 1) { const shadowChildren = element.shadowRoot.children; const shadowLimit = Math.min(shadowChildren.length, 20); for (let i = 0; i < shadowLimit; i++) { @@ -2716,22 +2696,46 @@ class ClientSelectorGenerator { } if (!addPositionToAll) { - const meaningfulAttrs = ["role", "type", "name", "src", "aria-label"]; + const meaningfulAttrs = ["role", "type"]; for (const attrName of meaningfulAttrs) { if (element.hasAttribute(attrName)) { const value = element.getAttribute(attrName)!.replace(/'/g, "\\'"); - return `${tagName}[@${attrName}='${value}']`; + const isCommonAttribute = this.isAttributeCommonAcrossLists( + element, + attrName, + value, + otherListElements + ); + if (isCommonAttribute) { + return `${tagName}[@${attrName}='${value}']`; + } } } } const testId = element.getAttribute("data-testid"); if (testId && !addPositionToAll) { - return `${tagName}[@data-testid='${testId}']`; + const isCommon = this.isAttributeCommonAcrossLists( + element, + "data-testid", + testId, + otherListElements + ); + if (isCommon) { + return `${tagName}[@data-testid='${testId}']`; + } } if (element.id && !element.id.match(/^\d/) && !addPositionToAll) { - return `${tagName}[@id='${element.id}']`; + const isCommon = this.isAttributeCommonAcrossLists( + element, + "id", + element.id, + otherListElements + ); + if (isCommon) { + return `${tagName}[@id='${element.id}']`; + } } if (!addPositionToAll) { @@ -2742,7 +2746,15 @@ class ClientSelectorGenerator { attr.name !== "data-mx-id" && attr.value ) { - return `${tagName}[@${attr.name}='${attr.value}']`; + const isCommon = this.isAttributeCommonAcrossLists( + element, + attr.name, + attr.value, + otherListElements + ); + if (isCommon) { + return `${tagName}[@${attr.name}='${attr.value}']`; + } } } } @@ -2906,12 +2918,70 @@ class ClientSelectorGenerator { const result = pathParts.length > 0 ? "/" + pathParts.join("/") : null; this.pathCache.set(targetElement, result); - + return result; } + private isAttributeCommonAcrossLists( + targetElement: HTMLElement, + attrName: string, + attrValue: string, + otherListElements: HTMLElement[] + ): boolean { + if (otherListElements.length === 0) { + return true; + } + + const targetPath = this.getElementPath(targetElement); + + for (const otherListElement of otherListElements) { + const correspondingElement = this.findCorrespondingElement( + otherListElement, + targetPath + ); + if (correspondingElement) { + const otherValue = correspondingElement.getAttribute(attrName); + if (otherValue !== attrValue) { + return false; + } + } + } + + return true; + } + + private getElementPath(element: HTMLElement): number[] { + const path: number[] = []; + let current: HTMLElement | null = element; + + while (current && current.parentElement) { + const siblings = Array.from(current.parentElement.children); + path.unshift(siblings.indexOf(current)); + current = current.parentElement; + } + + return path; + } + + private findCorrespondingElement( + rootElement: HTMLElement, + path: number[] + ): HTMLElement | null { + let current: HTMLElement = rootElement; + + for (const index of path) { + const children = Array.from(current.children); + if (index >= children.length) { + return null; + } + current = children[index] as HTMLElement; + } + + return current; + } + private getCommonClassesAcrossLists( - targetElement: HTMLElement, + targetElement: HTMLElement, otherListElements: HTMLElement[] ): string[] { if (otherListElements.length === 0) { @@ -3919,9 +3989,48 @@ class ClientSelectorGenerator { ); if (!deepestElement) return null; + if (!this.isMeaningfulElementCached(deepestElement)) { + const atomicChild = this.findAtomicChildAtPoint(deepestElement, x, y); + if (atomicChild) { + return atomicChild; + } + } + return deepestElement; } + private findAtomicChildAtPoint( + parent: HTMLElement, + x: number, + y: number + ): HTMLElement | null { + const stack: HTMLElement[] = [parent]; + const visited = new Set(); + + while (stack.length > 0) { + const element = stack.pop()!; + if (visited.has(element)) continue; + visited.add(element); + + if (element !== parent && this.isMeaningfulElementCached(element)) { + const rect = element.getBoundingClientRect(); + if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { + return element; + } + } + + for (let i = element.children.length - 1; i >= 0; i--) { + const child = element.children[i] as HTMLElement; + const rect = child.getBoundingClientRect(); + if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { + stack.push(child); + } + } + } + + return null; + } + /** * Helper methods used by the unified getDeepestElementFromPoint */ diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 2d34bd81..dd0d0b03 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -80,7 +80,7 @@ const Login = () => { maxHeight: "100vh", mt: 6, padding: 4, - backgroundColor: darkMode ? "#121212" : "#ffffff", + backgroundColor: "inherit", }} > { onSubmit={submitForm} sx={{ textAlign: "center", - backgroundColor: darkMode ? "#1e1e1e" : "#ffffff", + backgroundColor: darkMode ? "#121111ff" : "#ffffff", color: darkMode ? "#ffffff" : "#333333", padding: 6, borderRadius: 5, @@ -100,7 +100,8 @@ const Login = () => { width: "100%", }} > - logo + logo {t('login.title')} diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 7dbed8b2..6e362471 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -61,7 +61,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { useEffect(() => { if (darkMode) { - document.body.style.background = 'rgba(18,18,18,1)'; + document.body.style.background = '#080808ff'; } 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%)'; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index bc4faf27..41ac133e 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -73,7 +73,7 @@ const Register = () => { maxHeight: "100vh", mt: 6, padding: 4, - backgroundColor: darkMode ? "#121212" : "#ffffff", + backgroundColor: "inherit", }} > { onSubmit={submitForm} sx={{ textAlign: "center", - backgroundColor: darkMode ? "#1e1e1e" : "#ffffff", + backgroundColor: darkMode ? "#121111ff" : "#ffffff", color: darkMode ? "#ffffff" : "#333333", padding: 6, borderRadius: 5, @@ -97,8 +97,8 @@ const Register = () => {