From 31388bd196445895882b26c1500ae1d59c3a5303 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 4 Dec 2024 23:48:39 +0530 Subject: [PATCH 001/500] feat: upgrade backend image to v0.0.4 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 92b69c14..ca64f644 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.3 + image: getmaxun/maxun-backend:v0.0.4 ports: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env From fcad9570c98bcf31852c5976af39db228cfcf331 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 5 Dec 2024 21:06:21 +0530 Subject: [PATCH 002/500] hotfix: remove scrapeList action --- .../workflow-management/classes/Generator.ts | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index cfef4a30..c9dc3385 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -160,41 +160,6 @@ export class WorkflowGenerator { }) }; - /** - * New function to handle actionable check for scrapeList - * @param page The current Playwright Page object. - * @param config The scrapeList configuration object. - * @returns {Promise} Array of actionable selectors. - */ - private async getSelectorsForScrapeList(page: Page, config: { - listSelector: string; - fields: any; - limit?: number; - pagination: any; - }): Promise { - const { listSelector } = config; - - // Verify if the selectors are present and actionable on the current page - const actionableSelectors: string[] = []; - if (listSelector) { - const isActionable = await page.isVisible(listSelector).catch(() => false); - if (isActionable) { - actionableSelectors.push(listSelector); - logger.log('debug', `List selector ${listSelector} is actionable.`); - } else { - logger.log('warn', `List selector ${listSelector} is not visible on the page.`); - } - } - - return actionableSelectors; - } - - /** - * New function to handle actionable check for scrapeList - * @param page The current Playwright Page object. - * @param schema The scrapeSchema configuration object. - * @returns {Promise} Array of actionable selectors. - */ private async getSelectorsForSchema(page: Page, schema: Record): Promise { const selectors = Object.values(schema).map((field) => field.selector); @@ -243,14 +208,6 @@ export class WorkflowGenerator { pair.where.selectors = [...(pair.where.selectors || []), ...additionalSelectors]; } } - - if (pair.what[0].action === 'scrapeList') { - const config = pair.what[0]?.args?.[0]; - if (config) { - const actionableSelectors = await this.getSelectorsForScrapeList(page, config); - pair.where.selectors = [...(pair.where.selectors || []), ...actionableSelectors]; - } - } // Validate if the pair is already in the workflow if (pair.where.selectors && pair.where.selectors[0]) { @@ -923,4 +880,4 @@ export class WorkflowGenerator { public clearLastIndex = () => { this.generatedData.lastIndex = null; } -} +} \ No newline at end of file From cc2b1a37274013d5f3299dcc128c5df6205efbcb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 5 Dec 2024 21:06:58 +0530 Subject: [PATCH 003/500] fix: upgrade v0.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6b73537..38104584 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxun", - "version": "0.0.2", + "version": "0.0.3", "author": "Maxun", "license": "AGPL-3.0-or-later", "dependencies": { From b3a973fe59b66b419e645c8d7b13bd5bf3bae460 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 5 Dec 2024 21:09:24 +0530 Subject: [PATCH 004/500] hotfix: upgrade backend image to v0.0.5 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ca64f644..46cc72c4 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.4 + image: getmaxun/maxun-backend:v0.0.5 ports: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env From 5ed8e8ae427fc47e645c7b37dfb770df96f727dc Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 03:20:30 +0530 Subject: [PATCH 005/500] fix: use window.location.origin instead of base url --- maxun-core/src/browserSide/scraper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index 369a08be..467d0eb2 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -285,7 +285,7 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, } else if (attribute === 'src') { // Handle relative 'src' URLs const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, baseUrl).href : null; + record[label] = src ? new URL(src, window.location.origin).href : null; } else if (attribute === 'href') { // Handle relative 'href' URLs const href = fieldElement.getAttribute('href'); From 5985bc11f058c2e730b3daee180a6fa0ff710163 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 03:20:48 +0530 Subject: [PATCH 006/500] fix: use window.location.origin instead of base url --- maxun-core/src/browserSide/scraper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index 467d0eb2..d3410de4 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -289,7 +289,7 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, } else if (attribute === 'href') { // Handle relative 'href' URLs const href = fieldElement.getAttribute('href'); - record[label] = href ? new URL(href, baseUrl).href : null; + record[label] = href ? new URL(href, window.location.origin).href : null; } else { record[label] = fieldElement.getAttribute(attribute); } From 429ddaa5719634b84f481c2360702c1bc59aa296 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 03:21:18 +0530 Subject: [PATCH 007/500] chore: lint --- maxun-core/src/browserSide/scraper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index d3410de4..09b6578b 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -283,9 +283,9 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, } else if (attribute === 'innerHTML') { record[label] = fieldElement.innerHTML.trim(); } else if (attribute === 'src') { - // Handle relative 'src' URLs - const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, window.location.origin).href : null; + // Handle relative 'src' URLs + const src = fieldElement.getAttribute('src'); + record[label] = src ? new URL(src, window.location.origin).href : null; } else if (attribute === 'href') { // Handle relative 'href' URLs const href = fieldElement.getAttribute('href'); @@ -346,5 +346,5 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, return results; }; - + })(window); \ No newline at end of file From 079863d015c1d3883d3d4c2154239fe419117046 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 6 Dec 2024 03:39:15 +0530 Subject: [PATCH 008/500] feat: add earliest selectors logic for page state --- maxun-core/src/interpret.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index a7a5de47..06586038 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -121,6 +121,26 @@ export default class Interpreter extends EventEmitter { } } + private getPreviousSelectors(workflow: Workflow, actionId: number): string[] { + const selectors: string[] = []; + let index = actionId - 1; + + while (index >= 0) { + const previousSelectors = workflow[index]?.where?.selectors; + if (previousSelectors && previousSelectors.length > 0) { + previousSelectors.forEach((selector) => { + if (!selectors.includes(selector)) { + selectors.push(selector); // Avoid duplicates + } + }); + break; // Exit the loop once valid selectors are found + } + index--; // Move further back in the workflow + } + + return selectors; + } + /** * Returns the context object from given Page and the current workflow.\ * \ @@ -130,11 +150,11 @@ export default class Interpreter extends EventEmitter { * @param workflow Current **initialized** workflow (array of where-what pairs). * @returns {PageState} State of the current page. */ - private async getState(page: Page, workflow: Workflow): Promise { + private async getState(page: Page, workflow: Workflow, selectors: string[]): Promise { /** * All the selectors present in the current Workflow */ - const selectors = Preprocessor.extractSelectors(workflow); + // const selectors = Preprocessor.extractSelectors(workflow); /** * Determines whether the element targetted by the selector is [actionable](https://playwright.dev/docs/actionability). @@ -365,6 +385,7 @@ export default class Interpreter extends EventEmitter { console.log("MERGED results:", mergedResult); await this.options.serializableCallback(mergedResult); + // await this.options.serializableCallback(scrapeResult); }, scrapeList: async (config: { listSelector: string, fields: any, limit?: number, pagination: any }) => { @@ -550,6 +571,7 @@ export default class Interpreter extends EventEmitter { // apply ad-blocker to the current page await this.applyAdBlocker(p); const usedActions: string[] = []; + const selectors: string[] = []; let lastAction = null; let repeatCount = 0; @@ -579,7 +601,7 @@ export default class Interpreter extends EventEmitter { let pageState = {}; try { - pageState = await this.getState(p, workflow); + pageState = await this.getState(p, workflow, selectors); } catch (e: any) { this.log('The browser has been closed.'); return; @@ -615,6 +637,9 @@ export default class Interpreter extends EventEmitter { try { await this.carryOutSteps(p, action.what); usedActions.push(action.id ?? 'undefined'); + + selectors.push(...this.getPreviousSelectors(workflow, actionId)); + console.log("SELECTORS", selectors); } catch (e) { this.log(e, Level.ERROR); } From 964913775e77b31061428432fcc2093e8f6f7206 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 6 Dec 2024 03:40:58 +0530 Subject: [PATCH 009/500] fix: add on load emit urlChanged --- .../browser-management/classes/RemoteBrowser.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 769787da..e30632c3 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -402,14 +402,14 @@ export class RemoteBrowser { await this.currentPage?.close(); this.currentPage = newPage; if (this.currentPage) { - this.currentPage.on('framenavigated', (frame) => { - if (frame === this.currentPage?.mainFrame()) { - this.socket.emit('urlChanged', this.currentPage.url()); - } - }); - // this.currentPage.on('load', (page) => { - // this.socket.emit('urlChanged', page.url()); - // }) + // this.currentPage.on('framenavigated', (frame) => { + // if (frame === this.currentPage?.mainFrame()) { + // this.socket.emit('urlChanged', this.currentPage.url()); + // } + // }); + this.currentPage.on('load', (page) => { + this.socket.emit('urlChanged', page.url()); + }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); await this.subscribeToScreencast(); } else { From 95b2c508a7a3e9c6c3958e9b12c1e42a2519e657 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 04:29:40 +0530 Subject: [PATCH 010/500] chore: proper spacing --- src/components/molecules/IntegrationSettings.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index c31605de..a8c19961 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -15,11 +15,13 @@ 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; From 4550718e4d6079ee46c96d4c15348d20b73e9b05 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 04:37:39 +0530 Subject: [PATCH 011/500] chore: lint --- src/components/molecules/IntegrationSettings.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index a8c19961..b34bc0e9 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -77,8 +77,7 @@ export const IntegrationSettingsModal = ({ ); notify( "error", - `Error fetching spreadsheet files: ${ - error.response?.data?.message || error.message + `Error fetching spreadsheet files: ${error.response?.data?.message || error.message }` ); } From a6f4d0436b28298cdb6b0c388470ef1b2d4a33e2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 04:56:31 +0530 Subject: [PATCH 012/500] feat: show maxun version --- src/components/molecules/NavBar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index ee8c80e8..8d195ebc 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -11,6 +11,7 @@ import { SaveRecording } from '../molecules/SaveRecording'; import DiscordIcon from '../atoms/DiscordIcon'; import { apiUrl } from '../../apiConfig'; import MaxunLogo from "../../assets/maxunlogo.png"; +import packageJson from "../../../package.json" interface NavBarProps { recordingName: string; @@ -57,7 +58,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => justifyContent: 'flex-start', }}> -
Maxun
+
Maxun
{ user ? ( From bbf9699dfd314021f4164794c779289d1838e470 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 04:57:09 +0530 Subject: [PATCH 013/500] chore: lint --- src/components/molecules/NavBar.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 8d195ebc..bc373576 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -58,11 +58,13 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => justifyContent: 'flex-start', }}> -
Maxun
+
Maxun + +
{ user ? ( From 8a0f2b6ca54705bf6168bc269e4ae09161d966f9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 05:00:46 +0530 Subject: [PATCH 014/500] feat: format version chip --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index bc373576..6b2bfa92 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -58,14 +58,14 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => justifyContent: 'flex-start', }}> -
Maxun +
Maxun
- { user ? (
From 76255c21eaffd2eb705f234357d0be4b4ccff29a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 6 Dec 2024 05:05:25 +0530 Subject: [PATCH 015/500] chore: remove unused chip import --- src/components/molecules/Pair.tsx | 2 +- src/components/molecules/RobotDuplicate.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/Pair.tsx b/src/components/molecules/Pair.tsx index b05b912d..3c332600 100644 --- a/src/components/molecules/Pair.tsx +++ b/src/components/molecules/Pair.tsx @@ -1,5 +1,5 @@ import React, { FC, useState } from 'react'; -import { Stack, Button, IconButton, Tooltip, Chip, Badge } from "@mui/material"; +import { Stack, Button, IconButton, Tooltip, Badge } from "@mui/material"; import { AddPair, deletePair, UpdatePair } from "../../api/workflow"; import { WorkflowFile } from "maxun-core"; import { ClearButton } from "../atoms/buttons/ClearButton"; diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 850614b0..38b7b422 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { GenericModal } from "../atoms/GenericModal"; -import { TextField, Typography, Box, Button, Chip } from "@mui/material"; +import { TextField, Typography, Box, Button } from "@mui/material"; import { modalStyle } from "./AddWhereCondModal"; import { useGlobalInfoStore } from '../../context/globalInfo'; import { duplicateRecording, getStoredRecording } from '../../api/storage'; From 0d6633130596993aded87bf71420c9374e04a13c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 6 Dec 2024 16:57:55 +0530 Subject: [PATCH 016/500] fix: add on load socket emit --- server/src/browser-management/classes/RemoteBrowser.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index e30632c3..cfcc96f8 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -370,11 +370,11 @@ export class RemoteBrowser { await this.stopScreencast(); this.currentPage = page; - this.currentPage.on('framenavigated', (frame) => { - if (frame === this.currentPage?.mainFrame()) { - this.socket.emit('urlChanged', this.currentPage.url()); - } - }); + // this.currentPage.on('framenavigated', (frame) => { + // if (frame === this.currentPage?.mainFrame()) { + // this.socket.emit('urlChanged', this.currentPage.url()); + // } + // }); //await this.currentPage.setViewportSize({ height: 400, width: 900 }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); From bffe8389889d909c4913f6d68b4085100dc3bf8c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 6 Dec 2024 17:06:20 +0530 Subject: [PATCH 017/500] feat: add earliest selectors from workflow --- maxun-core/src/interpret.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 06586038..5c24317c 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -127,6 +127,7 @@ export default class Interpreter extends EventEmitter { while (index >= 0) { const previousSelectors = workflow[index]?.where?.selectors; + console.log("Previous Selectors:", previousSelectors); if (previousSelectors && previousSelectors.length > 0) { previousSelectors.forEach((selector) => { if (!selectors.includes(selector)) { @@ -156,6 +157,8 @@ export default class Interpreter extends EventEmitter { */ // const selectors = Preprocessor.extractSelectors(workflow); + console.log("All selectors:", selectors); + /** * Determines whether the element targetted by the selector is [actionable](https://playwright.dev/docs/actionability). * @param selector Selector to be queried @@ -164,8 +167,8 @@ export default class Interpreter extends EventEmitter { const actionable = async (selector: string): Promise => { try { const proms = [ - page.isEnabled(selector, { timeout: 500 }), - page.isVisible(selector, { timeout: 500 }), + page.isEnabled(selector, { timeout: 2000 }), + page.isVisible(selector, { timeout: 2000 }), ]; return await Promise.all(proms).then((bools) => bools.every((x) => x)); @@ -627,19 +630,26 @@ export default class Interpreter extends EventEmitter { if (this.options.debugChannel?.activeId) { this.options.debugChannel.activeId(actionId); } - + repeatCount = action === lastAction ? repeatCount + 1 : 0; - if (this.options.maxRepeats && repeatCount >= this.options.maxRepeats) { + + console.log("REPEAT COUNT", repeatCount); + if (this.options.maxRepeats && repeatCount > this.options.maxRepeats) { return; } lastAction = action; - + try { + console.log("Carrying out:", action.what); await this.carryOutSteps(p, action.what); usedActions.push(action.id ?? 'undefined'); - selectors.push(...this.getPreviousSelectors(workflow, actionId)); - console.log("SELECTORS", selectors); + const newSelectors = this.getPreviousSelectors(workflow, actionId); + newSelectors.forEach(selector => { + if (!selectors.includes(selector)) { + selectors.push(selector); + } + }); } catch (e) { this.log(e, Level.ERROR); } From 0ee50e1c26eaad4326135c76117ebe479e1606ed Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 6 Dec 2024 22:10:28 +0530 Subject: [PATCH 018/500] feat: add bottom up workflow traversal --- maxun-core/src/interpret.ts | 107 +++++++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 5c24317c..2457f79b 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -121,27 +121,55 @@ export default class Interpreter extends EventEmitter { } } - private getPreviousSelectors(workflow: Workflow, actionId: number): string[] { - const selectors: string[] = []; - let index = actionId - 1; + // private getPreviousSelectors(workflow: Workflow, actionId: number): string[] { + // const selectors: string[] = []; + // let index = actionId - 1; - while (index >= 0) { - const previousSelectors = workflow[index]?.where?.selectors; - console.log("Previous Selectors:", previousSelectors); - if (previousSelectors && previousSelectors.length > 0) { - previousSelectors.forEach((selector) => { + // while (index >= 0) { + // const previousSelectors = workflow[index]?.where?.selectors; + // console.log("Previous Selectors:", previousSelectors); + // if (previousSelectors && previousSelectors.length > 0) { + // previousSelectors.forEach((selector) => { + // if (!selectors.includes(selector)) { + // selectors.push(selector); // Avoid duplicates + // } + // }); + // break; // Exit the loop once valid selectors are found + // } + // index--; // Move further back in the workflow + // } + + // return selectors; + // } + + private getSelectors(workflow: Workflow, actionId: number): string[] { + const selectors: string[] = []; + + // Validate actionId + if (actionId <= 0) { + console.log("No previous selectors to collect."); + return selectors; // Empty array as there are no previous steps + } + + // Iterate from the start up to (but not including) actionId + for (let index = 0; index < actionId; index++) { + const currentSelectors = workflow[index]?.where?.selectors; + console.log(`Selectors at step ${index}:`, currentSelectors); + + if (currentSelectors && currentSelectors.length > 0) { + currentSelectors.forEach((selector) => { if (!selectors.includes(selector)) { selectors.push(selector); // Avoid duplicates } }); - break; // Exit the loop once valid selectors are found } - index--; // Move further back in the workflow } + console.log("Collected Selectors:", selectors); return selectors; } + /** * Returns the context object from given Page and the current workflow.\ * \ @@ -167,8 +195,8 @@ export default class Interpreter extends EventEmitter { const actionable = async (selector: string): Promise => { try { const proms = [ - page.isEnabled(selector, { timeout: 2000 }), - page.isVisible(selector, { timeout: 2000 }), + page.isEnabled(selector, { timeout: 500 }), + page.isVisible(selector, { timeout: 500 }), ]; return await Promise.all(proms).then((bools) => bools.every((x) => x)); @@ -198,7 +226,7 @@ export default class Interpreter extends EventEmitter { ...p, [cookie.name]: cookie.value, }), {}), - selectors: presentSelectors, + selectors: selectors, }; } @@ -570,11 +598,29 @@ export default class Interpreter extends EventEmitter { return allResults; } + private getMatchingActionId(workflow: Workflow, pageState: PageState, usedActions: string[]) { + for (let actionId = workflow.length - 1; actionId >= 0; actionId--) { + const step = workflow[actionId]; + const isApplicable = this.applicable(step.where, pageState, usedActions); + console.log("-------------------------------------------------------------"); + console.log(`Where:`, step.where); + console.log(`Page state:`, pageState); + console.log(`Match result: ${isApplicable}`); + console.log("-------------------------------------------------------------"); + + if (isApplicable) { + return actionId; + } + } + } + private async runLoop(p: Page, workflow: Workflow) { + const workflowCopy: Workflow = JSON.parse(JSON.stringify(workflow)); + // apply ad-blocker to the current page await this.applyAdBlocker(p); const usedActions: string[] = []; - const selectors: string[] = []; + let selectors: string[] = []; let lastAction = null; let repeatCount = 0; @@ -584,7 +630,7 @@ export default class Interpreter extends EventEmitter { * e.g. via `enqueueLinks`. */ p.on('popup', (popup) => { - this.concurrency.addJob(() => this.runLoop(popup, workflow)); + this.concurrency.addJob(() => this.runLoop(popup, workflowCopy)); }); /* eslint no-constant-condition: ["warn", { "checkLoops": false }] */ @@ -604,7 +650,8 @@ export default class Interpreter extends EventEmitter { let pageState = {}; try { - pageState = await this.getState(p, workflow, selectors); + pageState = await this.getState(p, workflowCopy, selectors); + selectors = []; } catch (e: any) { this.log('The browser has been closed.'); return; @@ -614,16 +661,22 @@ export default class Interpreter extends EventEmitter { this.log(`Current state is: \n${JSON.stringify(pageState, null, 2)}`, Level.WARN); } - const actionId = workflow.findIndex((step) => { - const isApplicable = this.applicable(step.where, pageState, usedActions); - console.log(`Where:`, step.where); - console.log(`Page state:`, pageState); - console.log(`Match result: ${isApplicable}`); - return isApplicable; - }); + // const actionId = workflow.findIndex((step) => { + // const isApplicable = this.applicable(step.where, pageState, usedActions); + // console.log("-------------------------------------------------------------"); + // console.log(`Where:`, step.where); + // console.log(`Page state:`, pageState); + // console.log(`Match result: ${isApplicable}`); + // console.log("-------------------------------------------------------------"); + // return isApplicable; + // }); - const action = workflow[actionId]; + const actionId = this.getMatchingActionId(workflowCopy, pageState, usedActions); + const action = workflowCopy[actionId]; + + console.log("MATCHED ACTION:", action); + console.log("MATCHED ACTION ID:", actionId); this.log(`Matched ${JSON.stringify(action?.where)}`, Level.LOG); if (action) { // action is matched @@ -643,8 +696,12 @@ export default class Interpreter extends EventEmitter { console.log("Carrying out:", action.what); await this.carryOutSteps(p, action.what); usedActions.push(action.id ?? 'undefined'); + + workflowCopy.splice(actionId, 1); + console.log(`Action with ID ${action.id} removed from the workflow copy.`); - const newSelectors = this.getPreviousSelectors(workflow, actionId); + // const newSelectors = this.getPreviousSelectors(workflow, actionId); + const newSelectors = this.getSelectors(workflowCopy, actionId); newSelectors.forEach(selector => { if (!selectors.includes(selector)) { selectors.push(selector); From d6be2683fdfc8863db235b6c679cd93eca15af45 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 7 Dec 2024 21:16:58 +0530 Subject: [PATCH 019/500] feat: add check to match action url and return --- maxun-core/src/interpret.ts | 63 ++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 2457f79b..fce67257 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -179,54 +179,62 @@ export default class Interpreter extends EventEmitter { * @param workflow Current **initialized** workflow (array of where-what pairs). * @returns {PageState} State of the current page. */ - private async getState(page: Page, workflow: Workflow, selectors: string[]): Promise { + private async getState(page: Page, workflowCopy: Workflow, selectors: string[]): Promise { /** * All the selectors present in the current Workflow */ // const selectors = Preprocessor.extractSelectors(workflow); - - console.log("All selectors:", selectors); + // console.log("Current selectors:", selectors); /** * Determines whether the element targetted by the selector is [actionable](https://playwright.dev/docs/actionability). * @param selector Selector to be queried * @returns True if the targetted element is actionable, false otherwise. */ - const actionable = async (selector: string): Promise => { - try { - const proms = [ - page.isEnabled(selector, { timeout: 500 }), - page.isVisible(selector, { timeout: 500 }), - ]; + // const actionable = async (selector: string): Promise => { + // try { + // const proms = [ + // page.isEnabled(selector, { timeout: 5000 }), + // page.isVisible(selector, { timeout: 5000 }), + // ]; - return await Promise.all(proms).then((bools) => bools.every((x) => x)); - } catch (e) { - // log(e, Level.ERROR); - return false; - } - }; + // return await Promise.all(proms).then((bools) => bools.every((x) => x)); + // } catch (e) { + // // log(e, Level.ERROR); + // return false; + // } + // }; /** * Object of selectors present in the current page. */ - const presentSelectors: SelectorArray = await Promise.all( - selectors.map(async (selector) => { - if (await actionable(selector)) { - return [selector]; - } - return []; - }), - ).then((x) => x.flat()); + // const presentSelectors: SelectorArray = await Promise.all( + // selectors.map(async (selector) => { + // if (await actionable(selector)) { + // return [selector]; + // } + // return []; + // }), + // ).then((x) => x.flat()); + const action = workflowCopy[workflowCopy.length - 1]; + + console.log("Next action:", action) + + let url: any = page.url(); + + if (action && action.where.url !== url && action.where.url !== "about:blank") { + url = action.where.url; + } return { - url: page.url(), + url, cookies: (await page.context().cookies([page.url()])) .reduce((p, cookie) => ( { ...p, [cookie.name]: cookie.value, }), {}), - selectors: selectors, + selectors, }; } @@ -622,6 +630,7 @@ export default class Interpreter extends EventEmitter { const usedActions: string[] = []; let selectors: string[] = []; let lastAction = null; + let actionId = -1 let repeatCount = 0; /** @@ -649,9 +658,11 @@ export default class Interpreter extends EventEmitter { } let pageState = {}; + let getStateTest = "Hello"; try { pageState = await this.getState(p, workflowCopy, selectors); selectors = []; + console.log("Empty selectors:", selectors) } catch (e: any) { this.log('The browser has been closed.'); return; @@ -671,7 +682,7 @@ export default class Interpreter extends EventEmitter { // return isApplicable; // }); - const actionId = this.getMatchingActionId(workflowCopy, pageState, usedActions); + actionId = this.getMatchingActionId(workflowCopy, pageState, usedActions); const action = workflowCopy[actionId]; From db37c72ce5b8a97f2fa9be6cd12c3b3f0bb07d62 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 7 Dec 2024 21:18:37 +0530 Subject: [PATCH 020/500] fix: add goto frame navigation --- server/src/browser-management/classes/RemoteBrowser.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index cfcc96f8..f1d18f3f 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -402,11 +402,11 @@ export class RemoteBrowser { await this.currentPage?.close(); this.currentPage = newPage; if (this.currentPage) { - // this.currentPage.on('framenavigated', (frame) => { - // if (frame === this.currentPage?.mainFrame()) { - // this.socket.emit('urlChanged', this.currentPage.url()); - // } - // }); + this.currentPage.on('framenavigated', (frame) => { + if (frame === this.currentPage?.mainFrame()) { + this.socket.emit('urlChanged', this.currentPage.url()); + } + }); this.currentPage.on('load', (page) => { this.socket.emit('urlChanged', page.url()); }) From a8e8c1de82b0ff47add118b8ab74377d4076f42c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 7 Dec 2024 21:53:55 +0530 Subject: [PATCH 021/500] fix: rm about:blank url check for action --- maxun-core/src/interpret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index fce67257..c2a1186c 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -222,7 +222,7 @@ export default class Interpreter extends EventEmitter { let url: any = page.url(); - if (action && action.where.url !== url && action.where.url !== "about:blank") { + if (action && action.where.url !== url) { url = action.where.url; } From 5684243215afcec2cf453568dad418455aea76d8 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 7 Dec 2024 22:20:17 +0530 Subject: [PATCH 022/500] langs --- package.json | 6 +- public/locales/ar.json | 0 public/locales/en.json | 13 + public/locales/es.json | 13 + public/locales/ja.json | 13 + public/locales/zh.json | 13 + src/App.tsx | 29 ++- src/components/molecules/NavBar.tsx | 370 +++++++++++++++++++++------- src/i18n.ts | 22 ++ src/index.tsx | 1 + src/pages/Login.tsx | 256 ++++++++++--------- 11 files changed, 508 insertions(+), 228 deletions(-) create mode 100644 public/locales/ar.json create mode 100644 public/locales/en.json create mode 100644 public/locales/es.json create mode 100644 public/locales/ja.json create mode 100644 public/locales/zh.json create mode 100644 src/i18n.ts diff --git a/package.json b/package.json index b6b73537..8b9d5d66 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,9 @@ "fortawesome": "^0.0.1-security", "google-auth-library": "^9.14.1", "googleapis": "^144.0.0", + "i18next": "^24.0.2", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^3.0.1", "ioredis": "^5.4.1", "joi": "^17.6.0", "jsonwebtoken": "^9.0.2", @@ -56,6 +59,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-highlight": "0.15.0", + "react-i18next": "^15.1.3", "react-router-dom": "^6.26.1", "react-simple-code-editor": "^0.11.2", "react-transition-group": "^4.4.2", @@ -110,4 +114,4 @@ "ts-node": "^10.4.0", "vite": "^5.4.10" } -} \ No newline at end of file +} diff --git a/public/locales/ar.json b/public/locales/ar.json new file mode 100644 index 00000000..e69de29b diff --git a/public/locales/en.json b/public/locales/en.json new file mode 100644 index 00000000..23281300 --- /dev/null +++ b/public/locales/en.json @@ -0,0 +1,13 @@ +{ + "login": { + "title": "Welcome Back!", + "email": "Email", + "password": "Password", + "button": "Login", + "loading": "Loading", + "register_prompt": "Don't have an account?", + "register_link": "Register", + "welcome_notification": "Welcome to Maxun!", + "error_notification": "Login Failed. Please try again." + } + } \ No newline at end of file diff --git a/public/locales/es.json b/public/locales/es.json new file mode 100644 index 00000000..00589622 --- /dev/null +++ b/public/locales/es.json @@ -0,0 +1,13 @@ +{ + "app": { + "name": "Maxun", + "version": "beta" + }, + "login": { + "title": "¡Bienvenido de nuevo!", + "email": "Correo electrónico", + "password": "Contraseña", + "button": "Iniciar sesión", + "register_prompt": "¿No tienes una cuenta? Regístrate" + } + } \ No newline at end of file diff --git a/public/locales/ja.json b/public/locales/ja.json new file mode 100644 index 00000000..80a594eb --- /dev/null +++ b/public/locales/ja.json @@ -0,0 +1,13 @@ +{ + "app": { + "name": "Maxun", + "version": "beta" + }, + "login": { + "title": "おかえりなさい!", + "email": "メールアドレス", + "password": "パスワード", + "button": "ログイン", + "register_prompt": "アカウントをお持ちでない方は、新規登録" + } + } \ No newline at end of file diff --git a/public/locales/zh.json b/public/locales/zh.json new file mode 100644 index 00000000..7fa8bb60 --- /dev/null +++ b/public/locales/zh.json @@ -0,0 +1,13 @@ +{ + "app": { + "name": "Maxun", + "version": "beta" + }, + "login": { + "title": "欢迎回来!", + "email": "电子邮件", + "password": "密码", + "button": "登录", + "register_prompt": "没有账号?注册" + } + } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index c37de9ea..02dff134 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { Routes, Route } from 'react-router-dom'; +import React from "react"; +import { Routes, Route } from "react-router-dom"; import { ThemeProvider, createTheme } from "@mui/material/styles"; import { GlobalInfoProvider } from "./context/globalInfo"; import { PageWrapper } from "./pages/PageWrappper"; +import i18n from "./i18n"; + const theme = createTheme({ palette: { @@ -20,14 +22,14 @@ const theme = createTheme({ }, containedPrimary: { // Styles for 'contained' variant with 'primary' color - '&:hover': { + "&:hover": { backgroundColor: "#ff66d9", }, }, outlined: { // Apply white background for all 'outlined' variant buttons backgroundColor: "#ffffff", - '&:hover': { + "&:hover": { backgroundColor: "#f0f0f0", // Optional lighter background on hover }, }, @@ -36,7 +38,7 @@ const theme = createTheme({ MuiLink: { styleOverrides: { root: { - '&:hover': { + "&:hover": { color: "#ff00c3", }, }, @@ -63,7 +65,7 @@ const theme = createTheme({ standardInfo: { backgroundColor: "#fce1f4", color: "#ff00c3", - '& .MuiAlert-icon': { + "& .MuiAlert-icon": { color: "#ff00c3", }, }, @@ -72,7 +74,7 @@ const theme = createTheme({ MuiAlertTitle: { styleOverrides: { root: { - '& .MuiAlert-icon': { + "& .MuiAlert-icon": { color: "#ffffff", }, }, @@ -81,15 +83,16 @@ const theme = createTheme({ }, }); - function App() { return ( - - - } /> - - + + + + } /> + + + ); } diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 4c0b7296..b068aa3a 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -1,134 +1,322 @@ -import React, { useState, useContext } from 'react'; -import axios from 'axios'; +import React, { useState, useContext } from "react"; +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, Chip, } from "@mui/material"; +import { IconButton, Menu, MenuItem, Typography, Chip } from "@mui/material"; import { AccountCircle, Logout, Clear } 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 { 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 { useTranslation } from "react-i18next"; // Import useTranslation hook interface NavBarProps { recordingName: string; isRecording: boolean; } -export const NavBar: React.FC = ({ recordingName, isRecording }) => { - const { notify, browserId, setBrowserId, recordingUrl } = useGlobalInfoStore(); +export const NavBar: React.FC = ({ + recordingName, + isRecording, +}) => { + const { notify, browserId, setBrowserId } = useGlobalInfoStore(); const { state, dispatch } = useContext(AuthContext); const { user } = state; const navigate = useNavigate(); + const { t, i18n } = useTranslation(); // Get translation function and i18n methods const [anchorEl, setAnchorEl] = useState(null); + const [langAnchorEl, setLangAnchorEl] = useState(null); const handleMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; + const handleLangMenuOpen = (event: React.MouseEvent) => { + setLangAnchorEl(event.currentTarget); + }; + const handleMenuClose = () => { setAnchorEl(null); + setLangAnchorEl(null); }; const logout = async () => { - dispatch({ type: 'LOGOUT' }); - window.localStorage.removeItem('user'); + dispatch({ type: "LOGOUT" }); + window.localStorage.removeItem("user"); const { data } = await axios.get(`${apiUrl}/auth/logout`); - notify('success', data.message); - navigate('/login'); + notify("success", data.message); + navigate("/login"); }; const goToMainMenu = async () => { if (browserId) { await stopRecording(browserId); - notify('warning', 'Current Recording was terminated'); + notify("warning", "Current Recording was terminated"); setBrowserId(null); } - navigate('/'); + navigate("/"); + }; + + const changeLanguage = (lang: string) => { + i18n.changeLanguage(lang); // Change language dynamically + localStorage.setItem("language", lang); // Persist language to localStorage }; return ( -
- -
Maxun
- +
+ +
+ Maxun +
+
- { - user ? ( -
- {!isRecording ? ( - <> - + {!isRecording ? ( + <> + + + + + + + {user.email} + + + { + handleMenuClose(); + logout(); }} > - - - - - - {user.email} - - - { handleMenuClose(); logout(); }}> - Logout - - - - ) : ( - <> - - - Discard - - - - )} -
- ) : "" - } + {t("logout")} + + + {/* Language dropdown */} + + ) : ( + <> + + + {t("discard")} + + + + )} + + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + +
+ ) : ( + <> + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + + )} + +
); }; @@ -136,7 +324,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => const NavBarWrapper = styled.div` grid-area: navbar; background-color: white; - padding:5px; + padding: 5px; display: flex; justify-content: space-between; border-bottom: 1px solid #e0e0e0; diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 00000000..f318cd5c --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,22 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import Backend from 'i18next-http-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: 'en', + debug: import.meta.env.DEV, + supportedLngs: ['en', 'es', 'ja', 'zh', 'ar'], + interpolation: { + escapeValue: false, // React already escapes + }, + backend: { + loadPath: '/locales/{{lng}}.json', + }, + }); + +export default i18n; \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 8c14f60a..96f914ff 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import { BrowserRouter } from 'react-router-dom'; import App from './App'; +import i18n from "./i18n" const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 87f90b53..3d70bfc2 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,134 +1,144 @@ -import axios from "axios"; -import { useState, useContext, useEffect, FormEvent } 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 { useGlobalInfoStore } from "../context/globalInfo"; +import axios from "axios"; +import { useState, useContext, useEffect, FormEvent } 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 { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; +import { useTranslation } from 'react-i18next'; +import i18n from '../i18n'; // Add this import const Login = () => { - const [form, setForm] = useState({ - email: "", - password: "", - }); - const [loading, setLoading] = useState(false); - const { notify } = useGlobalInfoStore(); - const { email, password } = form; + const { t } = useTranslation(); + console.log(i18n) // Add translation hook + console.log(t) + 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 { state, dispatch } = useContext(AuthContext); + const { user } = state; - const navigate = useNavigate(); + const navigate = useNavigate(); - useEffect(() => { - if (user) { - navigate("/"); - } - }, [user, navigate]); + useEffect(() => { + if (user) { + navigate("/"); + } + }, [user, navigate]); - const handleChange = (e: any) => { - const { name, value } = e.target; - setForm({ ...form, [name]: value }); - }; + const handleChange = (e: any) => { + const { name, value } = e.target; + setForm({ ...form, [name]: value }); + }; - const submitForm = async (e: any) => { - e.preventDefault(); - setLoading(true); - try { - const { data } = await axios.post(`${apiUrl}/auth/login`, { - email, - password, - }); - dispatch({ type: "LOGIN", payload: data }); - notify("success", "Welcome to Maxun!"); - window.localStorage.setItem("user", JSON.stringify(data)); - navigate("/"); - } catch (err) { - notify("error", "Login Failed. Please try again."); - setLoading(false); - } - }; + const submitForm = async (e: any) => { + e.preventDefault(); + setLoading(true); + try { + const { data } = await axios.post(`${apiUrl}/auth/login`, { + email, + password, + }); + dispatch({ type: "LOGIN", payload: data }); + notify("success", t('login.welcome_notification')); // Translated notification + window.localStorage.setItem("user", JSON.stringify(data)); + navigate("/"); + } catch (err) { + notify("error", t('login.error_notification')); // Translated error + setLoading(false); + } + }; - return ( - - - - logo - - Welcome Back! - - - - - - Don’t have an account?{" "} - - Register - - - - - - ); + // Language switcher function + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng); + }; + + return ( + + {/* Language Switcher Buttons */} + + + logo + + {t('login.title')} + + + + + + {t('login.register_prompt')}{" "} + + {t('login.register_link')} + + + + + ); }; -export default Login; +export default Login; \ No newline at end of file From 342fd79588e14a5c9b66a11329b9a06d3c4980b6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 7 Dec 2024 22:34:02 +0530 Subject: [PATCH 023/500] feat: add bottom up get selectors logic --- maxun-core/src/interpret.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index c2a1186c..844b46c7 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -142,31 +142,23 @@ export default class Interpreter extends EventEmitter { // return selectors; // } - private getSelectors(workflow: Workflow, actionId: number): string[] { - const selectors: string[] = []; + private getSelectors(workflow: Workflow): string[] { + const selectorsSet = new Set(); - // Validate actionId - if (actionId <= 0) { - console.log("No previous selectors to collect."); - return selectors; // Empty array as there are no previous steps + if (workflow.length === 0) { + return []; } - // Iterate from the start up to (but not including) actionId - for (let index = 0; index < actionId; index++) { + for (let index = workflow.length - 1; index >= 0; index--) { const currentSelectors = workflow[index]?.where?.selectors; - console.log(`Selectors at step ${index}:`, currentSelectors); if (currentSelectors && currentSelectors.length > 0) { - currentSelectors.forEach((selector) => { - if (!selectors.includes(selector)) { - selectors.push(selector); // Avoid duplicates - } - }); + currentSelectors.forEach((selector) => selectorsSet.add(selector)); + return Array.from(selectorsSet); } } - console.log("Collected Selectors:", selectors); - return selectors; + return []; } @@ -216,9 +208,8 @@ export default class Interpreter extends EventEmitter { // return []; // }), // ).then((x) => x.flat()); - const action = workflowCopy[workflowCopy.length - 1]; - console.log("Next action:", action) + const action = workflowCopy[workflowCopy.length - 1]; let url: any = page.url(); @@ -709,10 +700,10 @@ export default class Interpreter extends EventEmitter { usedActions.push(action.id ?? 'undefined'); workflowCopy.splice(actionId, 1); - console.log(`Action with ID ${action.id} removed from the workflow copy.`); + console.log(`Action with ID ${actionId} removed from the workflow copy.`); // const newSelectors = this.getPreviousSelectors(workflow, actionId); - const newSelectors = this.getSelectors(workflowCopy, actionId); + const newSelectors = this.getSelectors(workflowCopy); newSelectors.forEach(selector => { if (!selectors.includes(selector)) { selectors.push(selector); From ef571c4ea09ebc15b12bdbdc7398bef7c5d69d68 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 7 Dec 2024 23:28:31 +0530 Subject: [PATCH 024/500] feat: traverse dom tree for parent element selection --- server/src/workflow-management/selector.ts | 144 +++++++++++---------- 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 193de891..e0cd10c5 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,49 +20,6 @@ type Workflow = WorkflowFile["workflow"]; * @category WorkflowManagement-Selectors * @returns {Promise} */ -export const getRect = async (page: Page, coordinates: Coordinates) => { - try { - const rect = await page.evaluate( - async ({ x, y }) => { - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - const { parentElement } = el; - // Match the logic in recorder.ts for link clicks - const element = parentElement?.tagName === 'A' ? parentElement : el; - const rectangle = element?.getBoundingClientRect(); - // @ts-ignore - if (rectangle) { - return { - x: rectangle.x, - y: rectangle.y, - width: rectangle.width, - height: rectangle.height, - top: rectangle.top, - right: rectangle.right, - bottom: rectangle.bottom, - left: rectangle.left, - }; - } - } - }, - { x: coordinates.x, y: coordinates.y }, - ); - return rect; - } catch (error) { - const { message, stack } = error as Error; - logger.log('error', `Error while retrieving selector: ${message}`); - logger.log('error', `Stack: ${stack}`); - } -} - -/** - * Checks the basic info about an element and returns a {@link BaseActionInfo} object. - * If the element is not found, returns undefined. - * @param page The page instance. - * @param coordinates Coordinates of an element. - * @category WorkflowManagement-Selectors - * @returns {Promise} - */ export const getElementInformation = async ( page: Page, coordinates: Coordinates @@ -70,10 +27,15 @@ export const getElementInformation = async ( try { const elementInfo = await page.evaluate( async ({ x, y }) => { - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - const { parentElement } = el; - const element = parentElement?.tagName === 'A' ? parentElement : el; + // Find the initial element at the point + const initialElement = document.elementFromPoint(x, y) as HTMLElement; + + if (initialElement) { + // Simply use the direct parent, no complex logic + const parentElement = initialElement.parentElement; + + // Use the parent if it exists, otherwise use the initial element + const element = parentElement || initialElement; let info: { tagName: string; @@ -84,32 +46,41 @@ export const getElementInformation = async ( attributes?: Record; innerHTML?: string; outerHTML?: string; + parentTagName?: string; + parentClasses?: string[]; } = { - tagName: element?.tagName ?? '', + tagName: element.tagName, + parentTagName: element.parentElement?.tagName, + parentClasses: element.parentElement + ? Array.from(element.parentElement.classList) + : [] }; - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - } + // Collect attributes + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); - // Gather specific information based on the tag - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; + // Specific handling for different element types + if (element.tagName === 'A') { + const anchorElement = element as HTMLAnchorElement; + info.url = anchorElement.href; + info.innerText = anchorElement.innerText ?? ''; + } else if (element.tagName === 'IMG') { + const imgElement = element as HTMLImageElement; + info.imageUrl = imgElement.src; } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; + // Check if element contains only text + info.hasOnlyText = element.children.length === 0 && + (element.innerText?.length ?? 0) > 0; + info.innerText = element.innerText ?? ''; } + // HTML content info.innerHTML = element.innerHTML; info.outerHTML = element.outerHTML; @@ -127,6 +98,47 @@ export const getElementInformation = async ( } }; +export const getRect = async (page: Page, coordinates: Coordinates) => { + try { + const rect = await page.evaluate( + async ({ x, y }) => { + // Find the initial element at the point + const initialElement = document.elementFromPoint(x, y) as HTMLElement; + + if (initialElement) { + // Simply use the direct parent, no complex logic + const parentElement = initialElement.parentElement; + + // Use the parent if it exists, otherwise use the initial element + const element = parentElement || initialElement; + + // Get bounding rectangle + const rectangle = element?.getBoundingClientRect(); + + if (rectangle) { + return { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + }; + } + } + return null; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return rect; + } catch (error) { + const { message, stack } = error as Error; + console.error('Error while retrieving selector:', message); + console.error('Stack:', stack); + } +}; /** * Returns the best and unique css {@link Selectors} for the element on the page. From 9f24e0018c29efa30a7a49a6cea45ea7bc7a004a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 7 Dec 2024 23:36:01 +0530 Subject: [PATCH 025/500] feat: !return null --- server/src/workflow-management/selector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index e0cd10c5..aaa53a5d 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -128,7 +128,6 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { }; } } - return null; }, { x: coordinates.x, y: coordinates.y }, ); From 8c4c0b734d863bf1bc78f00e72fd709a89c626bc Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 7 Dec 2024 23:45:07 +0530 Subject: [PATCH 026/500] feat: handle selector generation if no parent element --- server/src/workflow-management/selector.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index aaa53a5d..7908edc2 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -753,7 +753,6 @@ interface SelectorResult { export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates): Promise => { try { const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { - function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); @@ -775,18 +774,25 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let depth = 0; const maxDepth = 2; - while (element && element !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(element); + // Ensure we start with a valid element + let currentElement = element; + while (currentElement && currentElement !== document.body && depth < maxDepth) { + const selector = getNonUniqueSelector(currentElement); path.unshift(selector); - element = element.parentElement; + currentElement = currentElement.parentElement; depth++; } return path.join(' > '); } - const element = document.elementFromPoint(x, y) as HTMLElement | null; - if (!element) return null; + // Find the initial element at the point + const initialElement = document.elementFromPoint(x, y) as HTMLElement; + + if (!initialElement) return null; + + // Prefer parent if exists, otherwise use initial element + const element = initialElement.parentElement || initialElement; const generalSelector = getSelectorPath(element); return { From be6d8ab249d6486e7af9266dd43b3bab53d21012 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:04:49 +0530 Subject: [PATCH 027/500] feat: add selectors in bottom up order --- maxun-core/src/interpret.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 844b46c7..de9a9845 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -121,24 +121,30 @@ export default class Interpreter extends EventEmitter { } } - // private getPreviousSelectors(workflow: Workflow, actionId: number): string[] { + // private getSelectors(workflow: Workflow, actionId: number): string[] { // const selectors: string[] = []; - // let index = actionId - 1; - // while (index >= 0) { - // const previousSelectors = workflow[index]?.where?.selectors; - // console.log("Previous Selectors:", previousSelectors); - // if (previousSelectors && previousSelectors.length > 0) { - // previousSelectors.forEach((selector) => { + // // Validate actionId + // if (actionId <= 0) { + // console.log("No previous selectors to collect."); + // return selectors; // Empty array as there are no previous steps + // } + + // // Iterate from the start up to (but not including) actionId + // for (let index = 0; index < actionId; index++) { + // const currentSelectors = workflow[index]?.where?.selectors; + // console.log(`Selectors at step ${index}:`, currentSelectors); + + // if (currentSelectors && currentSelectors.length > 0) { + // currentSelectors.forEach((selector) => { // if (!selectors.includes(selector)) { // selectors.push(selector); // Avoid duplicates // } // }); - // break; // Exit the loop once valid selectors are found // } - // index--; // Move further back in the workflow // } + // console.log("Collected Selectors:", selectors); // return selectors; // } @@ -208,12 +214,14 @@ export default class Interpreter extends EventEmitter { // return []; // }), // ).then((x) => x.flat()); - + const action = workflowCopy[workflowCopy.length - 1]; + // console.log("Next action:", action) + let url: any = page.url(); - if (action && action.where.url !== url) { + if (action && action.where.url !== url && action.where.url !== "about:blank") { url = action.where.url; } @@ -700,7 +708,7 @@ export default class Interpreter extends EventEmitter { usedActions.push(action.id ?? 'undefined'); workflowCopy.splice(actionId, 1); - console.log(`Action with ID ${actionId} removed from the workflow copy.`); + console.log(`Action with ID ${action.id} removed from the workflow copy.`); // const newSelectors = this.getPreviousSelectors(workflow, actionId); const newSelectors = this.getSelectors(workflowCopy); From 5259e3e386787267662755dd02ea9fc7c71d56b4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:06:57 +0530 Subject: [PATCH 028/500] feat: add on flag logic for InterpretRecording --- .../classes/Interpreter.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index d53259b7..b982b172 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -244,7 +244,12 @@ export class WorkflowInterpreter { * @param page The page instance used to interact with the browser. * @param settings The settings to use for the interpretation. */ - public InterpretRecording = async (workflow: WorkflowFile, page: Page, settings: InterpreterSettings) => { + public InterpretRecording = async ( + workflow: WorkflowFile, + page: Page, + updatePageOnPause: (page: Page) => void, + settings: InterpreterSettings + ) => { const params = settings.params ? settings.params : null; delete settings.params; @@ -262,7 +267,7 @@ export class WorkflowInterpreter { this.socket.emit('debugMessage', msg) }, }, - serializableCallback: (data: string) => { + serializableCallback: (data: any) => { this.serializableData.push(data); this.socket.emit('serializableCallback', data); }, @@ -275,6 +280,23 @@ export class WorkflowInterpreter { const interpreter = new Interpreter(decryptedWorkflow, options); this.interpreter = interpreter; + interpreter.on('flag', async (page, resume) => { + if (this.activeId !== null && this.breakpoints[this.activeId]) { + logger.log('debug', `breakpoint hit id: ${this.activeId}`); + this.socket.emit('breakpointHit'); + this.interpretationIsPaused = true; + } + + if (this.interpretationIsPaused) { + this.interpretationResume = resume; + logger.log('debug', `Paused inside of flag: ${page.url()}`); + updatePageOnPause(page); + this.socket.emit('log', '----- The interpretation has been paused -----', false); + } else { + resume(); + } + }); + const status = await interpreter.run(page, params); const lastArray = this.serializableData.length > 1 From aec65d1b2232fef9a2c70ac3b43e0001dbbafe42 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:07:40 +0530 Subject: [PATCH 029/500] feat: add flag generation logic --- server/src/api/record.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/server/src/api/record.ts b/server/src/api/record.ts index 5b33b12f..05560487 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -15,6 +15,8 @@ import { io, Socket } from "socket.io-client"; import { BinaryOutputService } from "../storage/mino"; import { AuthenticatedRequest } from "../routes/record" import {capture} from "../utils/analytics"; +import { Page } from "playwright"; +import { WorkflowFile } from "maxun-core"; chromium.use(stealthPlugin()); const formatRecording = (recordingData: any) => { @@ -533,6 +535,17 @@ function resetRecordingState(browserId: string, id: string) { id = ''; } +function AddGeneratedFlags(workflow: WorkflowFile) { + const copy = JSON.parse(JSON.stringify(workflow)); + for (let i = 0; i < workflow.workflow.length; i++) { + copy.workflow[i].what.unshift({ + action: 'flag', + args: ['generated'], + }); + } + return copy; +}; + async function executeRun(id: string) { try { const run = await Run.findOne({ where: { runId: id } }); @@ -560,13 +573,14 @@ async function executeRun(id: string) { throw new Error('Could not access browser'); } - const currentPage = await browser.getCurrentPage(); + let currentPage = await browser.getCurrentPage(); if (!currentPage) { throw new Error('Could not create a new page'); } + const workflow = AddGeneratedFlags(recording.recording); const interpretationInfo = await browser.interpreter.InterpretRecording( - recording.recording, currentPage, plainRun.interpreterSettings + workflow, currentPage, (newPage: Page) => currentPage = newPage, plainRun.interpreterSettings ); const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); From 0a81292bea115446c9323bc6df5647b1b19f684d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:08:05 +0530 Subject: [PATCH 030/500] feat: add flag generation logic --- server/src/routes/storage.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index d1f648f8..ddadf240 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -18,6 +18,8 @@ import { AuthenticatedRequest } from './record'; import { computeNextRun } from '../utils/schedule'; import { capture } from "../utils/analytics"; import { tryCatch } from 'bullmq'; +import { WorkflowFile } from 'maxun-core'; +import { Page } from 'playwright'; chromium.use(stealthPlugin()); export const router = Router(); @@ -422,6 +424,17 @@ router.get('/runs/run/:id', requireSignIn, async (req, res) => { } }); +function AddGeneratedFlags(workflow: WorkflowFile) { + const copy = JSON.parse(JSON.stringify(workflow)); + for (let i = 0; i < workflow.workflow.length; i++) { + copy.workflow[i].what.unshift({ + action: 'flag', + args: ['generated'], + }); + } + return copy; +}; + /** * PUT endpoint for finishing a run and saving it to the storage. */ @@ -443,10 +456,11 @@ router.post('/runs/run/:id', requireSignIn, async (req: AuthenticatedRequest, re // interpret the run in active browser const browser = browserPool.getRemoteBrowser(plainRun.browserId); - const currentPage = browser?.getCurrentPage(); + let currentPage = browser?.getCurrentPage(); if (browser && currentPage) { + const workflow = AddGeneratedFlags(recording.recording); const interpretationInfo = await browser.interpreter.InterpretRecording( - recording.recording, currentPage, plainRun.interpreterSettings); + workflow, currentPage, (newPage: Page) => currentPage = newPage, plainRun.interpreterSettings); const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); await destroyRemoteBrowser(plainRun.browserId); From 45f0c819ea05831ccc0fe20781adfebc39b48f47 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:10:53 +0530 Subject: [PATCH 031/500] feat: add flag generation logic --- .../workflow-management/scheduler/index.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/scheduler/index.ts b/server/src/workflow-management/scheduler/index.ts index 02ca905f..169b0061 100644 --- a/server/src/workflow-management/scheduler/index.ts +++ b/server/src/workflow-management/scheduler/index.ts @@ -11,6 +11,8 @@ import Run from "../../models/Run"; import { getDecryptedProxyConfig } from "../../routes/proxy"; import { BinaryOutputService } from "../../storage/mino"; import { capture } from "../../utils/analytics"; +import { WorkflowFile } from "maxun-core"; +import { Page } from "playwright"; chromium.use(stealthPlugin()); async function createWorkflowAndStoreMetadata(id: string, userId: string) { @@ -79,6 +81,17 @@ async function createWorkflowAndStoreMetadata(id: string, userId: string) { } } +function AddGeneratedFlags(workflow: WorkflowFile) { + const copy = JSON.parse(JSON.stringify(workflow)); + for (let i = 0; i < workflow.workflow.length; i++) { + copy.workflow[i].what.unshift({ + action: 'flag', + args: ['generated'], + }); + } + return copy; +}; + async function executeRun(id: string) { try { const run = await Run.findOne({ where: { runId: id } }); @@ -106,13 +119,15 @@ async function executeRun(id: string) { throw new Error('Could not access browser'); } - const currentPage = await browser.getCurrentPage(); + let currentPage = await browser.getCurrentPage(); if (!currentPage) { throw new Error('Could not create a new page'); } + const workflow = AddGeneratedFlags(recording.recording); const interpretationInfo = await browser.interpreter.InterpretRecording( - recording.recording, currentPage, plainRun.interpreterSettings); + workflow, currentPage, (newPage: Page) => currentPage = newPage, plainRun.interpreterSettings + ); const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); From a30211de6d3ed309a5477a44091b19a4f72a9dd0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 8 Dec 2024 18:55:19 +0530 Subject: [PATCH 032/500] fix: add frame navigation logic in place of load --- server/src/browser-management/classes/RemoteBrowser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index f1d18f3f..3f5b677c 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -407,9 +407,9 @@ export class RemoteBrowser { this.socket.emit('urlChanged', this.currentPage.url()); } }); - this.currentPage.on('load', (page) => { - this.socket.emit('urlChanged', page.url()); - }) + // this.currentPage.on('load', (page) => { + // this.socket.emit('urlChanged', page.url()); + // }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); await this.subscribeToScreencast(); } else { From 2ea64385c389178972e7d2a00930f55b7dfdab1e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:26:34 +0530 Subject: [PATCH 033/500] feat: youtube icon --- src/components/molecules/NavBar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 6b2bfa92..6ecf9b61 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { IconButton, Menu, MenuItem, Typography, Avatar, Chip, } from "@mui/material"; -import { AccountCircle, Logout, Clear } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, YouTube } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -114,6 +114,9 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => { handleMenuClose(); logout(); }}> Logout + { handleMenuClose(); logout(); }}> + YouTube + ) : ( From e2cb4c7613d570b0fd4cd7f8a826fe3a6e019c85 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:29:53 +0530 Subject: [PATCH 034/500] feat: youtube link --- src/components/molecules/NavBar.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 6ecf9b61..9e0f6642 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -114,7 +114,9 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => { handleMenuClose(); logout(); }}> Logout - { handleMenuClose(); logout(); }}> + { + window.open('https://www.youtube.com/@MaxunOSS/videos', '_blank'); + }}> YouTube From 99d62101cb512ff675b465959391c8dfe68c80ff Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:30:11 +0530 Subject: [PATCH 035/500] chore: lint --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 9e0f6642..d6855184 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -114,8 +114,8 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => { handleMenuClose(); logout(); }}> Logout - { - window.open('https://www.youtube.com/@MaxunOSS/videos', '_blank'); + { + window.open('https://www.youtube.com/@MaxunOSS/videos', '_blank'); }}> YouTube From 69757050a9ac59a6ceca27a5c74a880551aba120 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:32:41 +0530 Subject: [PATCH 036/500] feat: x link --- src/components/molecules/NavBar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index d6855184..28ca959a 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { IconButton, Menu, MenuItem, Typography, Avatar, Chip, } from "@mui/material"; -import { AccountCircle, Logout, Clear, YouTube } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, YouTube, X } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -119,6 +119,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => }}> YouTube + { + window.open('https://x.com/maxun_io', '_blank'); + }}> + Twiiter (X) + ) : ( From 7186540ed94dbace62f45bbfd94725ffc4c1c2c2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:37:48 +0530 Subject: [PATCH 037/500] feat: increase width of menu --- src/components/molecules/NavBar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 28ca959a..68b452db 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -110,6 +110,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => vertical: 'top', horizontal: 'right', }} + PaperProps={{sx: {width: '180px'}}} > { handleMenuClose(); logout(); }}> Logout From a8ea05527b28a109709d71249a69977466921ead Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:42:24 +0530 Subject: [PATCH 038/500] feat: move discord icon to menu --- 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 68b452db..e8d154ef 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -115,6 +115,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => { handleMenuClose(); logout(); }}> Logout + { + window.open('https://discord.gg/5GbPjBUkws', '_blank'); + }}> + Discord + { window.open('https://www.youtube.com/@MaxunOSS/videos', '_blank'); }}> From 8d6b962301ae1108b409afc251bb6b61340aba72 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 20:42:54 +0530 Subject: [PATCH 039/500] feat: remove discord icon button --- src/components/molecules/NavBar.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index e8d154ef..2d1096a5 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -71,21 +71,6 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - - - Date: Sun, 8 Dec 2024 20:55:24 +0530 Subject: [PATCH 040/500] feat: add ref to yt x links --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 2d1096a5..ef1f9878 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -106,12 +106,12 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => Discord { - window.open('https://www.youtube.com/@MaxunOSS/videos', '_blank'); + window.open('https://www.youtube.com/@MaxunOSS/videos?ref=app', '_blank'); }}> YouTube { - window.open('https://x.com/maxun_io', '_blank'); + window.open('https://x.com/maxun_io?ref=app', '_blank'); }}> Twiiter (X) From f8f1d926d99cd495e8196823e28cf5d62642ad5c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:08:20 +0530 Subject: [PATCH 041/500] wip: upgrade maxun button --- src/components/molecules/NavBar.tsx | 97 ++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index ef1f9878..9b1e9472 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -3,7 +3,7 @@ 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, Chip, } from "@mui/material"; +import { IconButton, Menu, MenuItem, Typography, Avatar, Chip, Button, Modal, Tabs, Tab, Box } from "@mui/material"; import { AccountCircle, Logout, Clear, YouTube, X } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; @@ -25,6 +25,38 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => const navigate = useNavigate(); const [anchorEl, setAnchorEl] = useState(null); + const currentVersion = "0.0.3"; // Dynamically fetch from package.json + + const [open, setOpen] = useState(false); + const [latestVersion, setLatestVersion] = useState(null); + const [tab, setTab] = useState(0); + + const fetchLatestVersion = async () => { + try { + const response = await fetch("https://api.github.com/repos/getmaxun/maxun/releases/latest"); + const data = await response.json(); + const version = data.tag_name.replace(/^v/, ""); // Remove 'v' prefix + setLatestVersion(version); + } catch (error) { + console.error("Failed to fetch latest version:", error); + setLatestVersion(null); // Handle errors gracefully + } + }; + + const handleOpen = () => { + setOpen(true); + fetchLatestVersion(); + }; + + const handleClose = () => { + setOpen(false); + setTab(0); // Reset tab to the first tab + }; + + const handleTabChange = (newValue: any) => { + setTab(newValue); + }; + const handleMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -71,6 +103,69 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> + + + + {latestVersion === null ? ( + Checking for updates... + ) : currentVersion === latestVersion ? ( + + 🎉 You're up to date! + + ) : ( + <> + + A new version is available: {latestVersion} + + + + + + {tab === 0 && ( + + Manual Upgrade + + git pull origin main +
+ npm install +
+ npm run start +
+
+ )} + {tab === 1 && ( + + Docker Compose Upgrade + + docker pull getmaxun/maxun:latest +
+ docker-compose up -d +
+
+ )} + + )} +
+
Date: Sun, 8 Dec 2024 21:09:42 +0530 Subject: [PATCH 042/500] refactor: rename menu & tab update functions --- src/components/molecules/NavBar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 9b1e9472..bef197fa 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -43,17 +43,17 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => } }; - const handleOpen = () => { + const handleUpdateOpen = () => { setOpen(true); fetchLatestVersion(); }; - const handleClose = () => { + const handleUpdateClose = () => { setOpen(false); setTab(0); // Reset tab to the first tab }; - const handleTabChange = (newValue: any) => { + const handleUpdateTabChange = (newValue: any) => { setTab(newValue); }; @@ -103,10 +103,10 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - - + = ({ recordingName, isRecording }) => From 37ed5fa555ca832f854071a885ee8fd5bf3c1454 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:09:58 +0530 Subject: [PATCH 043/500] chore: lint --- src/components/molecules/NavBar.tsx | 128 ++++++++++++++-------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index bef197fa..96e1f07e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -103,69 +103,69 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - - - - {latestVersion === null ? ( - Checking for updates... - ) : currentVersion === latestVersion ? ( - - 🎉 You're up to date! - - ) : ( - <> - - A new version is available: {latestVersion} - - - - - - {tab === 0 && ( - - Manual Upgrade - - git pull origin main -
- npm install -
- npm run start -
-
- )} - {tab === 1 && ( - - Docker Compose Upgrade - - docker pull getmaxun/maxun:latest -
- docker-compose up -d -
-
- )} - - )} -
-
+ + + + {latestVersion === null ? ( + Checking for updates... + ) : currentVersion === latestVersion ? ( + + 🎉 You're up to date! + + ) : ( + <> + + A new version is available: {latestVersion} + + + + + + {tab === 0 && ( + + Manual Upgrade + + git pull origin main +
+ npm install +
+ npm run start +
+
+ )} + {tab === 1 && ( + + Docker Compose Upgrade + + docker pull getmaxun/maxun:latest +
+ docker-compose up -d +
+
+ )} + + )} +
+
= ({ recordingName, isRecording }) => vertical: 'top', horizontal: 'right', }} - PaperProps={{sx: {width: '180px'}}} + PaperProps={{ sx: { width: '180px' } }} > { handleMenuClose(); logout(); }}> Logout From 929bd91a7ef794ef0bbb55e4cd858d1c978374cd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:11:58 +0530 Subject: [PATCH 044/500] feat: store package.json version in currentVersion --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 96e1f07e..4ca7cc17 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -25,7 +25,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => const navigate = useNavigate(); const [anchorEl, setAnchorEl] = useState(null); - const currentVersion = "0.0.3"; // Dynamically fetch from package.json + const currentVersion = packageJson.version; const [open, setOpen] = useState(false); const [latestVersion, setLatestVersion] = useState(null); @@ -92,7 +92,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
Maxun
Date: Sun, 8 Dec 2024 21:15:21 +0530 Subject: [PATCH 045/500] feat: margin right to 30px --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 4ca7cc17..cc6d2c6b 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -103,7 +103,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - From 6a2222e6b924697aac38c13fb6294c784870b291 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:21:47 +0530 Subject: [PATCH 046/500] feat: pass event prop to handle update tab change --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index cc6d2c6b..5cdae02e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -53,7 +53,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => setTab(0); // Reset tab to the first tab }; - const handleUpdateTabChange = (newValue: any) => { + const handleUpdateTabChange = (event: React.SyntheticEvent, newValue: number) => { setTab(newValue); }; From 06fdfbc65a0430468d1b34e05704d7be8851e0d9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:22:03 +0530 Subject: [PATCH 047/500] chore: lint --- src/components/molecules/NavBar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 5cdae02e..06393277 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -57,7 +57,6 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => setTab(newValue); }; - const handleMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -103,7 +102,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - From b30be4b976b478f115c0048b3f7c38ac0e4982e2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 21:23:00 +0530 Subject: [PATCH 048/500] feat: add setup --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 06393277..94676ecd 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -136,8 +136,8 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => sx={{ marginTop: 2, marginBottom: 2 }} centered > - - + + {tab === 0 && ( From 58aedacd4ffd99e8c8890a7ba6b63e52a69b6807 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 22:23:09 +0530 Subject: [PATCH 049/500] wip: updates ui --- src/components/molecules/NavBar.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 94676ecd..be4818df 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -112,7 +112,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => top: "50%", left: "50%", transform: "translate(-50%, -50%)", - width: 400, + width: 500, bgcolor: "background.paper", boxShadow: 24, p: 4, @@ -136,29 +136,27 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => sx={{ marginTop: 2, marginBottom: 2 }} centered > - - + + {tab === 0 && ( - Manual Upgrade - +
git pull origin main
npm install
npm run start - +
)} {tab === 1 && ( - Docker Compose Upgrade - +
docker pull getmaxun/maxun:latest
docker-compose up -d - +
)} From ebd866bc16c1b3d6f7a97ae303238da06160edc6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 22:31:17 +0530 Subject: [PATCH 050/500] feat: add comments for commands --- src/components/molecules/NavBar.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index be4818df..bc4222e2 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -141,19 +141,30 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {tab === 0 && ( -
- git pull origin main +
+

Run the commands below

+ # pull latest changes +
+ git pull origin master +
+ # install dependencies
npm install
+ # start maxun +
npm run start
)} {tab === 1 && ( -
- docker pull getmaxun/maxun:latest +
+

Run the commands below

+ # pull latest docker images +
+ docker-compose pull + # start maxun
docker-compose up -d
From 0c66e86e28bad574460eddf622ff74e27a7f57b0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 23:42:19 +0530 Subject: [PATCH 051/500] feat: format code blocks --- src/components/molecules/NavBar.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index bc4222e2..99535baf 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -140,34 +140,38 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {tab === 0 && ( - -
+ +

Run the commands below

# pull latest changes
git pull origin master
+
# install dependencies
npm install
+
# start maxun
npm run start -
+
)} {tab === 1 && ( - -
+ +

Run the commands below

# pull latest docker images
docker-compose pull +
+
# start maxun
docker-compose up -d -
+
)} From e82863ad9c8061b072ac3bbf07ce212f983d58c6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 23:42:29 +0530 Subject: [PATCH 052/500] chore: lint --- src/components/molecules/NavBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 99535baf..b048d720 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -141,7 +141,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {tab === 0 && ( - +

Run the commands below

# pull latest changes
@@ -161,8 +161,8 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => )} {tab === 1 && ( - -

Run the commands below

+ +

Run the commands below

# pull latest docker images
docker-compose pull From f7eccd47cbce57d0b66eb8ed6b57bc2a60583d69 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 23:44:33 +0530 Subject: [PATCH 053/500] feat: box border radius --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index b048d720..60b75835 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -140,7 +140,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {tab === 0 && ( - +

Run the commands below

# pull latest changes @@ -160,7 +160,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
)} {tab === 1 && ( - +

Run the commands below

# pull latest docker images From 885120cbb388b93e490a7479bed19c0394bb2dae Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 8 Dec 2024 23:53:02 +0530 Subject: [PATCH 054/500] feat: add changelog link --- src/components/molecules/NavBar.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 60b75835..7861340f 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -127,8 +127,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => ) : ( <> - - A new version is available: {latestVersion} + + A new version is available: {latestVersion}. Upgrade to the latest version for bug fixes, enhancements and new features! +
+ View all the new updates + {' '}here.
Date: Sun, 8 Dec 2024 23:53:13 +0530 Subject: [PATCH 055/500] chore: lint --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 7861340f..01e002ec 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -127,11 +127,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
) : ( <> - + A new version is available: {latestVersion}. Upgrade to the latest version for bug fixes, enhancements and new features!
View all the new updates - {' '}here. + {' '}here.
Date: Mon, 9 Dec 2024 00:00:51 +0530 Subject: [PATCH 056/500] chore: -rm unused import --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 01e002ec..9d5cc623 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -3,7 +3,7 @@ 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, Chip, Button, Modal, Tabs, Tab, Box } from "@mui/material"; +import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box } from "@mui/material"; import { AccountCircle, Logout, Clear, YouTube, X } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; From c8b95bd27c30af9133ea9e7940ceb399b15741c7 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 00:01:26 +0530 Subject: [PATCH 057/500] chore: -rm v --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 9d5cc623..08ab650e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -91,7 +91,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
Maxun
Date: Mon, 9 Dec 2024 00:25:13 +0530 Subject: [PATCH 058/500] feat: rename to upgrade maxun --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 08ab650e..db7f72c1 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -102,8 +102,8 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - Date: Mon, 9 Dec 2024 00:32:14 +0530 Subject: [PATCH 059/500] feat: use update icon --- src/components/molecules/NavBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index db7f72c1..fc638503 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,7 +4,7 @@ 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 } from "@mui/material"; -import { AccountCircle, Logout, Clear, YouTube, X } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, YouTube, X, Update } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -103,7 +103,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {!isRecording ? ( <> Date: Mon, 9 Dec 2024 00:39:03 +0530 Subject: [PATCH 060/500] feat: match upgrade icon style to rest of navbar elementa --- src/components/molecules/NavBar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index fc638503..db29e516 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -102,7 +102,12 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - From e5c045f0d567439d589f83f12162e60d2cf3b01b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 00:39:21 +0530 Subject: [PATCH 061/500] chore: lint --- src/components/molecules/NavBar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index db29e516..56e43c5e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -102,13 +102,13 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>
{!isRecording ? ( <> - Date: Mon, 9 Dec 2024 00:40:17 +0530 Subject: [PATCH 062/500] feat: increase margin right --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 56e43c5e..62275078 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -103,7 +103,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {!isRecording ? ( <> + } + /> +)}
= ({ recordingName, isRecording }) => ) : "" } + ); }; From e19094185214192cc5697c97dbb5a527f92ba656 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 00:54:55 +0530 Subject: [PATCH 064/500] chore: lint --- src/components/molecules/NavBar.tsx | 352 ++++++++++++++-------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 1755cb20..24fda0f0 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -96,186 +96,186 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => return ( <> - {isUpdateAvailable && ( - - Upgrade - - } - /> -)} - -
- -
Maxun
- + Upgrade + + } /> -
- { - user ? ( -
- {!isRecording ? ( - <> - - - - {latestVersion === null ? ( - Checking for updates... - ) : currentVersion === latestVersion ? ( - - 🎉 You're up to date! - - ) : ( - <> - - A new version is available: {latestVersion}. Upgrade to the latest version for bug fixes, enhancements and new features! -
- View all the new updates - {' '}here. + )} + +
+ +
Maxun
+ +
+ { + user ? ( +
+ {!isRecording ? ( + <> + + + + {latestVersion === null ? ( + Checking for updates... + ) : currentVersion === latestVersion ? ( + + 🎉 You're up to date! - - - - - {tab === 0 && ( - - -

Run the commands below

- # pull latest changes -
- git pull origin master -
-
- # install dependencies -
- npm install -
-
- # start maxun -
- npm run start -
-
- )} - {tab === 1 && ( - - -

Run the commands below

- # pull latest docker images -
- docker-compose pull -
-
- # start maxun -
- docker-compose up -d -
-
- )} - - )} -
-
- - - - {user.email} - - - { handleMenuClose(); logout(); }}> - Logout - - { - window.open('https://discord.gg/5GbPjBUkws', '_blank'); + ) : ( + <> + + A new version is available: {latestVersion}. Upgrade to the latest version for bug fixes, enhancements and new features! +
+ View all the new updates + {' '}here. +
+ + + + + {tab === 0 && ( + + +

Run the commands below

+ # pull latest changes +
+ git pull origin master +
+
+ # install dependencies +
+ npm install +
+
+ # start maxun +
+ npm run start +
+
+ )} + {tab === 1 && ( + + +

Run the commands below

+ # pull latest docker images +
+ docker-compose pull +
+
+ # start maxun +
+ docker-compose up -d +
+
+ )} + + )} + + + + - Discord -
- { - window.open('https://www.youtube.com/@MaxunOSS/videos?ref=app', '_blank'); + + {user.email} + + + { handleMenuClose(); logout(); }}> + Logout + + { + window.open('https://discord.gg/5GbPjBUkws', '_blank'); + }}> + Discord + + { + window.open('https://www.youtube.com/@MaxunOSS/videos?ref=app', '_blank'); + }}> + YouTube + + { + window.open('https://x.com/maxun_io?ref=app', '_blank'); + }}> + Twiiter (X) + + + + ) : ( + <> + - YouTube - - { - window.open('https://x.com/maxun_io?ref=app', '_blank'); - }}> - Twiiter (X) - -
- - ) : ( - <> - - - Discard - - - - )} -
- ) : "" - } -
+ + Discard + + + + )} +
+ ) : "" + } +
); }; From 612622725d8c8542e6b4666a692d0773f3255cea Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 01:05:39 +0530 Subject: [PATCH 065/500] feat: snackbar ui --- src/components/molecules/NavBar.tsx | 47 ++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 24fda0f0..3f3907a9 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,7 +4,7 @@ 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 } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, YouTube, X, Update, Close } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -98,14 +98,47 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => <> {isUpdateAvailable && ( + open={isUpdateAvailable} + onClose={() => setIsUpdateAvailable(false)} // Close when clicking the close button + message={ + + New version {latestVersion} available! Click "Upgrade Maxun" to update. + + } + action={ + <> + - } - /> + setIsUpdateAvailable(false)} // Close Snackbar + style={{ color: 'black' }} + > + + + + } + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} // Position of Snackbar + sx={{ + backgroundColor: 'white', + boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.2)', + }} + /> + )}
Date: Mon, 9 Dec 2024 01:16:07 +0530 Subject: [PATCH 066/500] feat: snackbar ui --- src/components/molecules/NavBar.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 3f3907a9..0b933b7e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -99,24 +99,23 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {isUpdateAvailable && ( setIsUpdateAvailable(false)} // Close when clicking the close button + onClose={() => setIsUpdateAvailable(false)} message={ - - New version {latestVersion} available! Click "Upgrade Maxun" to update. - + `New version ${latestVersion} available! Click "Upgrade" to update.` } action={ <> - setIsUpdateAvailable(false)} - style={{ color: 'black' }} - > - - - - } - ContentProps={{ - sx: { - background: "white", - color: "black", } - }} - /> - + action={ + <> + + setIsUpdateAvailable(false)} + style={{ color: 'black' }} + > + + + + } + ContentProps={{ + sx: { + background: "white", + color: "black", + } + }} + /> + )}
Date: Mon, 9 Dec 2024 05:43:51 +0530 Subject: [PATCH 068/500] feat: proper rect and element info --- server/src/workflow-management/selector.ts | 166 +++++++++++++-------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 7908edc2..9587e898 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -27,15 +27,33 @@ export const getElementInformation = async ( try { const elementInfo = await page.evaluate( async ({ x, y }) => { - // Find the initial element at the point - const initialElement = document.elementFromPoint(x, y) as HTMLElement; - - if (initialElement) { - // Simply use the direct parent, no complex logic - const parentElement = initialElement.parentElement; + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (originalEl) { + let element = originalEl; + + // Generic parent finding logic based on visual containment + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); - // Use the parent if it exists, otherwise use the initial element - const element = parentElement || initialElement; + // Check if parent visually contains the child + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + // Additional checks for more comprehensive containment + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } let info: { tagName: string; @@ -46,44 +64,34 @@ export const getElementInformation = async ( attributes?: Record; innerHTML?: string; outerHTML?: string; - parentTagName?: string; - parentClasses?: string[]; } = { - tagName: element.tagName, - parentTagName: element.parentElement?.tagName, - parentClasses: element.parentElement - ? Array.from(element.parentElement.classList) - : [] + tagName: element?.tagName ?? '', }; - // Collect attributes - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - - // Specific handling for different element types - if (element.tagName === 'A') { - const anchorElement = element as HTMLAnchorElement; - info.url = anchorElement.href; - info.innerText = anchorElement.innerText ?? ''; - } else if (element.tagName === 'IMG') { - const imgElement = element as HTMLImageElement; - info.imageUrl = imgElement.src; - } else { - // Check if element contains only text - info.hasOnlyText = element.children.length === 0 && - (element.innerText?.length ?? 0) > 0; - info.innerText = element.innerText ?? ''; + if (element) { + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); + } + + // Existing tag-specific logic + if (element?.tagName === 'A') { + info.url = (element as HTMLAnchorElement).href; + info.innerText = element.innerText ?? ''; + } else if (element?.tagName === 'IMG') { + info.imageUrl = (element as HTMLImageElement).src; + } else { + info.hasOnlyText = element?.children?.length === 0 && + element?.innerText?.length > 0; + info.innerText = element?.innerText ?? ''; } - // HTML content info.innerHTML = element.innerHTML; info.outerHTML = element.outerHTML; - return info; } return null; @@ -102,17 +110,32 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { try { const rect = await page.evaluate( async ({ x, y }) => { - // Find the initial element at the point - const initialElement = document.elementFromPoint(x, y) as HTMLElement; - - if (initialElement) { - // Simply use the direct parent, no complex logic - const parentElement = initialElement.parentElement; + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (originalEl) { + let element = originalEl; + + // Same parent-finding logic as in getElementInformation + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); - // Use the parent if it exists, otherwise use the initial element - const element = parentElement || initialElement; + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } - // Get bounding rectangle const rectangle = element?.getBoundingClientRect(); if (rectangle) { @@ -134,10 +157,11 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { return rect; } catch (error) { const { message, stack } = error as Error; - console.error('Error while retrieving selector:', message); - console.error('Stack:', stack); + logger.log('error', `Error while retrieving selector: ${message}`); + logger.log('error', `Stack: ${stack}`); } -}; +} + /** * Returns the best and unique css {@link Selectors} for the element on the page. @@ -774,25 +798,42 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let depth = 0; const maxDepth = 2; - // Ensure we start with a valid element - let currentElement = element; - while (currentElement && currentElement !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(currentElement); + while (element && element !== document.body && depth < maxDepth) { + const selector = getNonUniqueSelector(element); path.unshift(selector); - currentElement = currentElement.parentElement; + element = element.parentElement; depth++; } return path.join(' > '); } - // Find the initial element at the point - const initialElement = document.elementFromPoint(x, y) as HTMLElement; - - if (!initialElement) return null; + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (!originalEl) return null; - // Prefer parent if exists, otherwise use initial element - const element = initialElement.parentElement || initialElement; + let element = originalEl; + + // Find the most appropriate parent element + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } const generalSelector = getSelectorPath(element); return { @@ -807,7 +848,6 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates } }; - export const getChildSelectors = async (page: Page, parentSelector: string): Promise => { try { const childSelectors = await page.evaluate((parentSelector: string) => { From 560f0ea24f8f214e48867791b64c4b3630cb94b9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 14:41:59 +0530 Subject: [PATCH 069/500] fix: a tags --- server/src/workflow-management/selector.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 9587e898..6a80e7a3 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -31,6 +31,11 @@ export const getElementInformation = async ( if (originalEl) { let element = originalEl; + if (originalEl.tagName === 'A') { + element = originalEl; + } else if (originalEl.parentElement?.tagName === 'A') { + element = originalEl.parentElement; + } else { // Generic parent finding logic based on visual containment while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); @@ -53,7 +58,7 @@ export const getElementInformation = async ( } else { break; } - } + } } let info: { tagName: string; @@ -114,7 +119,11 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { if (originalEl) { let element = originalEl; - // Same parent-finding logic as in getElementInformation + if (originalEl.tagName === 'A') { + element = originalEl; + } else if (originalEl.parentElement?.tagName === 'A') { + element = originalEl.parentElement; + } else { while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -134,8 +143,9 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { } else { break; } - } + }} + //element = element?.parentElement?.tagName === 'A' ? element?.parentElement : element; const rectangle = element?.getBoundingClientRect(); if (rectangle) { From 4da462f48bc037ee2eed4db48428b58293c44f1b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 15:04:45 +0530 Subject: [PATCH 070/500] fix: a tags --- server/src/workflow-management/selector.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 6a80e7a3..f3925ec9 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -823,7 +823,11 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - // Find the most appropriate parent element + if (originalEl.tagName === 'A') { + element = originalEl; + } else if (originalEl.parentElement?.tagName === 'A') { + element = originalEl.parentElement; + } else { while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -844,6 +848,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates break; } } + } const generalSelector = getSelectorPath(element); return { From 3dfe9117b0061d0a265f938f16a551f41ef03dae Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 9 Dec 2024 17:57:28 +0530 Subject: [PATCH 071/500] feat: inject cookie remover script --- server/src/browser-management/classes/RemoteBrowser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 769787da..29beaa9b 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -15,6 +15,7 @@ import { InterpreterSettings, RemoteBrowserOptions } from "../../types"; 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()); @@ -168,6 +169,7 @@ export class RemoteBrowser { this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { + this.currentPage.evaluate(getInjectableScript()) this.socket.emit('urlChanged', this.currentPage.url()); } }); From 549a0d35fc4c7f8f7d02b25ab632f6e4f5940b0c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 9 Dec 2024 18:40:00 +0530 Subject: [PATCH 072/500] chore(deps): install idcac-playwright --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 977daada..a7d634f3 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "fortawesome": "^0.0.1-security", "google-auth-library": "^9.14.1", "googleapis": "^144.0.0", + "idcac-playwright": "^0.1.3", "ioredis": "^5.4.1", "joi": "^17.6.0", "jsonwebtoken": "^9.0.2", From 117dddc2ff8337320a162af350632ee69633221d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 9 Dec 2024 18:49:23 +0530 Subject: [PATCH 073/500] feat: inject cookie remover script --- server/src/browser-management/classes/RemoteBrowser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 29beaa9b..0081cb5c 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -374,6 +374,7 @@ export class RemoteBrowser { this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { + this.currentPage.evaluate(getInjectableScript()); this.socket.emit('urlChanged', this.currentPage.url()); } }); @@ -406,6 +407,7 @@ export class RemoteBrowser { if (this.currentPage) { this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { + this.currentPage.evaluate(getInjectableScript()); this.socket.emit('urlChanged', this.currentPage.url()); } }); From 386e7c9a98d3c519e6ad0d628318aaa355ce2134 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 9 Dec 2024 20:13:46 +0530 Subject: [PATCH 074/500] feat: add programmatic click event for clickNext --- maxun-core/src/interpret.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index a7a5de47..f0b10936 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -475,6 +475,8 @@ export default class Interpreter extends EventEmitter { case 'clickNext': const pageResults = await page.evaluate((cfg) => window.scrapeList(cfg), config); + // console.log("Page results:", pageResults); + // Filter out already scraped items const newResults = pageResults.filter(item => { const uniqueKey = JSON.stringify(item); @@ -482,9 +484,9 @@ export default class Interpreter extends EventEmitter { scrapedItems.add(uniqueKey); // Mark as scraped return true; }); - + allResults = allResults.concat(newResults); - + if (config.limit && allResults.length >= config.limit) { return allResults.slice(0, config.limit); } @@ -494,7 +496,7 @@ export default class Interpreter extends EventEmitter { return allResults; // No more pages to scrape } await Promise.all([ - nextButton.click(), + nextButton.dispatchEvent('click'), page.waitForNavigation({ waitUntil: 'networkidle' }) ]); From b84e9186b9ccf71cb05423b3923bb0c7c745b3d3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 9 Dec 2024 22:05:50 +0530 Subject: [PATCH 075/500] fix: inject cookie script on page load --- .../browser-management/classes/RemoteBrowser.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 0081cb5c..7e0a7d1a 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -167,13 +167,16 @@ export class RemoteBrowser { this.context = await this.browser.newContext(contextOptions); this.currentPage = await this.context.newPage(); - this.currentPage.on('framenavigated', (frame) => { + this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { - this.currentPage.evaluate(getInjectableScript()) this.socket.emit('urlChanged', this.currentPage.url()); } }); + this.currentPage.on('load', (page) => { + page.evaluate(getInjectableScript()) + }) + // await this.currentPage.setExtraHTTPHeaders({ // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' // }); @@ -374,11 +377,14 @@ export class RemoteBrowser { this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { - this.currentPage.evaluate(getInjectableScript()); this.socket.emit('urlChanged', this.currentPage.url()); } }); + this.currentPage.on('load', (page) => { + page.evaluate(getInjectableScript()) + }) + //await this.currentPage.setViewportSize({ height: 400, width: 900 }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.socket.emit('urlChanged', this.currentPage.url()); @@ -407,10 +413,13 @@ export class RemoteBrowser { if (this.currentPage) { this.currentPage.on('framenavigated', (frame) => { if (frame === this.currentPage?.mainFrame()) { - this.currentPage.evaluate(getInjectableScript()); this.socket.emit('urlChanged', this.currentPage.url()); } }); + + this.currentPage.on('load', (page) => { + page.evaluate(getInjectableScript()) + }) // this.currentPage.on('load', (page) => { // this.socket.emit('urlChanged', page.url()); // }) From f561ef7f559dac83913ca23773723088fc1d5b07 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 10 Dec 2024 04:02:28 +0530 Subject: [PATCH 076/500] feat: apply conditional visual containment --- server/src/workflow-management/selector.ts | 49 ++++++++++++++++------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index f3925ec9..fa58be67 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -31,16 +31,28 @@ export const getElementInformation = async ( if (originalEl) { let element = originalEl; - if (originalEl.tagName === 'A') { - element = originalEl; - } else if (originalEl.parentElement?.tagName === 'A') { - element = originalEl.parentElement; - } else { + // if (originalEl.tagName === 'A') { + // element = originalEl; + // } else if (originalEl.parentElement?.tagName === 'A') { + // element = originalEl.parentElement; + // } else { // Generic parent finding logic based on visual containment + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', + 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', + 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', + 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' + ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); + if (!containerTags.includes(element.parentElement.tagName)) { + break; + } + // Check if parent visually contains the child const fullyContained = parentRect.left <= childRect.left && @@ -57,7 +69,7 @@ export const getElementInformation = async ( element = element.parentElement; } else { break; - } + // } } } let info: { @@ -119,15 +131,28 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { if (originalEl) { let element = originalEl; - if (originalEl.tagName === 'A') { - element = originalEl; - } else if (originalEl.parentElement?.tagName === 'A') { - element = originalEl.parentElement; - } else { + // if (originalEl.tagName === 'A') { + // element = originalEl; + // } else if (originalEl.parentElement?.tagName === 'A') { + // element = originalEl.parentElement; + // } else { + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', + 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', + 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', + 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' + ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); + if (!containerTags.includes(element.parentElement.tagName)) { + break; + } + + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && @@ -142,7 +167,7 @@ export const getRect = async (page: Page, coordinates: Coordinates) => { element = element.parentElement; } else { break; - } + // } }} //element = element?.parentElement?.tagName === 'A' ? element?.parentElement : element; From 668a67057f9542027a08f37e274a0f6b78ea5e0e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 10 Dec 2024 19:24:28 +0530 Subject: [PATCH 077/500] fix: include visual containment for capture list selection --- server/src/workflow-management/selector.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index fa58be67..917ac561 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -847,16 +847,24 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates if (!originalEl) return null; let element = originalEl; + + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', + 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', + 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', + 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' + ]; - if (originalEl.tagName === 'A') { - element = originalEl; - } else if (originalEl.parentElement?.tagName === 'A') { - element = originalEl.parentElement; - } else { while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); + if (!containerTags.includes(element.parentElement.tagName)) { + break; + } + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && @@ -871,7 +879,6 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates element = element.parentElement; } else { break; - } } } From 142c90ae1c0e0fc9441bcedaff1430dd3eab7abd Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 10 Dec 2024 20:32:01 +0530 Subject: [PATCH 078/500] fix: handle context destroyed, frame navigation URL --- .../classes/RemoteBrowser.ts | 94 +++++++++++++------ 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 7e0a7d1a..417e8964 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -66,6 +66,8 @@ export class RemoteBrowser { maxRepeats: 1, }; + private lastEmittedUrl: string | null = null; + /** * {@link WorkflowGenerator} instance specific to the remote browser. */ @@ -88,6 +90,64 @@ export class RemoteBrowser { this.generator = new WorkflowGenerator(socket); } + /** + * Normalizes URLs to prevent navigation loops while maintaining consistent format + */ + private normalizeUrl(url: string): string { + try { + const parsedUrl = new URL(url); + // Remove trailing slashes except for root path + parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/'; + // Ensure consistent protocol handling + parsedUrl.protocol = parsedUrl.protocol.toLowerCase(); + return parsedUrl.toString(); + } catch { + return url; + } + } + + /** + * Determines if a URL change is significant enough to emit + */ + private shouldEmitUrlChange(newUrl: string): boolean { + if (!this.lastEmittedUrl) { + return true; + } + const normalizedNew = this.normalizeUrl(newUrl); + const normalizedLast = this.normalizeUrl(this.lastEmittedUrl); + return normalizedNew !== normalizedLast; + } + + private async setupPageEventListeners(page: Page) { + page.on('framenavigated', async (frame) => { + if (frame === page.mainFrame()) { + const currentUrl = page.url(); + if (this.shouldEmitUrlChange(currentUrl)) { + this.lastEmittedUrl = currentUrl; + this.socket.emit('urlChanged', currentUrl); + } + } + }); + + // Handle page load events with retry mechanism + page.on('load', async () => { + const injectScript = async (): Promise => { + try { + await page.waitForLoadState('networkidle', { timeout: 5000 }); + + await page.evaluate(getInjectableScript()); + return true; + } catch (error: any) { + logger.log('warn', `Script injection attempt failed: ${error.message}`); + return false; + } + }; + + const success = await injectScript(); + console.log("Script injection result:", success); + }); + } + /** * An asynchronous constructor for asynchronously initialized properties. * Must be called right after creating an instance of RemoteBrowser class. @@ -167,15 +227,7 @@ export class RemoteBrowser { this.context = await this.browser.newContext(contextOptions); this.currentPage = await this.context.newPage(); - this.currentPage.on('framenavigated', (frame) => { - if (frame === this.currentPage?.mainFrame()) { - this.socket.emit('urlChanged', this.currentPage.url()); - } - }); - - this.currentPage.on('load', (page) => { - page.evaluate(getInjectableScript()) - }) + await this.setupPageEventListeners(this.currentPage); // await this.currentPage.setExtraHTTPHeaders({ // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' @@ -375,15 +427,7 @@ export class RemoteBrowser { await this.stopScreencast(); this.currentPage = page; - this.currentPage.on('framenavigated', (frame) => { - if (frame === this.currentPage?.mainFrame()) { - this.socket.emit('urlChanged', this.currentPage.url()); - } - }); - - this.currentPage.on('load', (page) => { - page.evaluate(getInjectableScript()) - }) + await this.setupPageEventListeners(this.currentPage); //await this.currentPage.setViewportSize({ height: 400, width: 900 }) this.client = await this.currentPage.context().newCDPSession(this.currentPage); @@ -411,18 +455,8 @@ export class RemoteBrowser { await this.currentPage?.close(); this.currentPage = newPage; if (this.currentPage) { - this.currentPage.on('framenavigated', (frame) => { - if (frame === this.currentPage?.mainFrame()) { - this.socket.emit('urlChanged', this.currentPage.url()); - } - }); - - this.currentPage.on('load', (page) => { - page.evaluate(getInjectableScript()) - }) - // this.currentPage.on('load', (page) => { - // this.socket.emit('urlChanged', page.url()); - // }) + await this.setupPageEventListeners(this.currentPage); + this.client = await this.currentPage.context().newCDPSession(this.currentPage); await this.subscribeToScreencast(); } else { From c7af54ebe061abcf1c5c5bcb26e4182fa3f8d254 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 10 Dec 2024 20:33:35 +0530 Subject: [PATCH 079/500] feat: add fallback mechanism for click and waitForLoadState action --- maxun-core/src/interpret.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index bd90e40f..afea8e47 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -469,6 +469,16 @@ export default class Interpreter extends EventEmitter { }), }; + const executeAction = async (invokee: any, methodName: string, args: any) => { + console.log("Executing action:", methodName, args); + if (!args || Array.isArray(args)) { + await (invokee[methodName])(...(args ?? [])); + } else { + await (invokee[methodName])(args); + } + }; + + for (const step of steps) { this.log(`Launching ${String(step.action)}`, Level.LOG); @@ -486,10 +496,20 @@ export default class Interpreter extends EventEmitter { invokee = invokee[level]; } - if (!step.args || Array.isArray(step.args)) { - await (invokee[methodName])(...(step.args ?? [])); + if (methodName === 'waitForLoadState') { + try { + await executeAction(invokee, methodName, step.args); + } catch (error) { + await executeAction(invokee, methodName, 'domcontentloaded'); + } + } else if (methodName === 'click') { + try { + await executeAction(invokee, methodName, step.args); + } catch (error) { + await executeAction(invokee, methodName, [step.args[0], { force: true }]); + } } else { - await (invokee[methodName])(step.args); + await executeAction(invokee, methodName, step.args); } } @@ -571,7 +591,7 @@ export default class Interpreter extends EventEmitter { return allResults; } // Click the 'Load More' button to load additional items - await loadMoreButton.click(); + await loadMoreButton.dispatchEvent('click'); await page.waitForTimeout(2000); // Wait for new items to load // After clicking 'Load More', scroll down to load more items await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); From 077234212af3e920e78699d25a1acedc789da28b Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Tue, 10 Dec 2024 20:40:59 +0530 Subject: [PATCH 080/500] language support --- public/locales/ar.json | 0 public/locales/de.json | 50 +++++++++++++ public/locales/en.json | 40 +++++++++++ public/locales/ja.json | 55 +++++++++++--- src/components/molecules/NavBar.tsx | 9 +++ src/components/molecules/RecordingsTable.tsx | 76 +++++++++++--------- src/components/organisms/MainMenu.tsx | 16 +++-- src/i18n.ts | 2 +- src/pages/Login.tsx | 8 +-- src/pages/Register.tsx | 20 ++++-- 10 files changed, 215 insertions(+), 61 deletions(-) delete mode 100644 public/locales/ar.json create mode 100644 public/locales/de.json diff --git a/public/locales/ar.json b/public/locales/ar.json deleted file mode 100644 index e69de29b..00000000 diff --git a/public/locales/de.json b/public/locales/de.json new file mode 100644 index 00000000..d12b2489 --- /dev/null +++ b/public/locales/de.json @@ -0,0 +1,50 @@ +{ + "login": { + "title": "Willkommen zurück!", + "email": "E-Mail", + "password": "Passwort", + "button": "Einloggen", + "loading": "Lädt", + "register_prompt": "Noch keinen Account?", + "register_link": "Registrieren", + "welcome_notification": "Willkommen bei Maxun!", + "error_notification": "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut." + }, + "register": { + "title": "Konto registrieren", + "email": "E-Mail", + "password": "Passwort", + "button": "Registrieren", + "loading": "Lädt", + "register_prompt": "Bereits ein Konto?", + "login_link": "Einloggen", + "welcome_notification": "Willkommen bei Maxun!", + "error_notification": "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut." + }, + "recordingtable": { + "run": "Ausführen", + "name": "Name", + "schedule": "Zeitplan", + "integrate": "Integrieren", + "settings": "Einstellungen", + "options": "Optionen", + "heading": "Meine Roboter", + "new": "Roboter erstellen", + "modal": { + "title": "Geben Sie die URL ein", + "label": "URL", + "button": "Aufnahme starten" + }, + "edit": "Bearbeiten", + "delete": "Löschen", + "duplicate": "Duplizieren" + }, + "mainmenu": { + "recordings": "Roboter", + "runs": "Ausführungen", + "proxy": "Proxy", + "apikey": "API-Schlüssel", + "feedback": "Maxun Cloud beitreten", + "apidocs": "API-Dokumentation" + } +} diff --git a/public/locales/en.json b/public/locales/en.json index 23281300..897e1729 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -9,5 +9,45 @@ "register_link": "Register", "welcome_notification": "Welcome to Maxun!", "error_notification": "Login Failed. Please try again." + }, + "register": { + "title": "Register Account", + "email": "Email", + "password": "Password", + "button": "Register", + "loading": "Loading", + "register_prompt": "Already have an account?", + "login_link": "Login", + "welcome_notification": "Welcome to Maxun!", + "error_notification": "Registeration Failed. Please try again." + }, + "recordingtable":{ + "run": "Run", + "name": "Name", + "schedule": "Schedule", + "integrate": "Integrate", + "settings": "Settings", + "options": "Options", + "heading":"My Robots", + "new":"Create Robot", + "modal":{ + "title":"Enter the URL", + "label":"URL", + "button":"Start Recording" + }, + "edit":"Edit", + "delete":"Delete", + "duplicate":"Duplicate", + "search":"Search Robots..." + + }, + "mainmenu":{ + "recordings": "Robots", + "runs": "Runs", + "proxy": "Proxy", + "apikey": "API Key", + "feedback":"Join Maxun Cloud", + "apidocs":"API Docs" + } } \ No newline at end of file diff --git a/public/locales/ja.json b/public/locales/ja.json index 80a594eb..88686e62 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -1,13 +1,50 @@ { - "app": { - "name": "Maxun", - "version": "beta" - }, - "login": { - "title": "おかえりなさい!", + "login": { + "title": "お帰りなさい!", "email": "メールアドレス", "password": "パスワード", "button": "ログイン", - "register_prompt": "アカウントをお持ちでない方は、新規登録" - } - } \ No newline at end of file + "loading": "読み込み中", + "register_prompt": "アカウントをお持ちでないですか?", + "register_link": "登録する", + "welcome_notification": "Maxunへようこそ!", + "error_notification": "ログインに失敗しました。もう一度お試しください。" + }, + "register": { + "title": "アカウントを登録する", + "email": "メールアドレス", + "password": "パスワード", + "button": "登録する", + "loading": "読み込み中", + "register_prompt": "既にアカウントをお持ちですか?", + "login_link": "ログイン", + "welcome_notification": "Maxunへようこそ!", + "error_notification": "登録に失敗しました。もう一度お試しください。" + }, + "recordingtable": { + "run": "実行", + "name": "名前", + "schedule": "スケジュール", + "integrate": "統合", + "settings": "設定", + "options": "オプション", + "heading": "私のロボット", + "new": "ロボットを作成", + "modal": { + "title": "URLを入力してください", + "label": "URL", + "button": "録画を開始" + }, + "edit": "編集", + "delete": "削除", + "duplicate": "複製" + }, + "mainmenu": { + "recordings": "ロボット", + "runs": "実行", + "proxy": "プロキシ", + "apikey": "APIキー", + "feedback": "Maxunクラウドに参加する", + "apidocs": "APIドキュメント" + } +} diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index b068aa3a..958492e6 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -245,6 +245,15 @@ export const NavBar: React.FC = ({ > 中文 + { + changeLanguage("de"); + handleMenuClose(); + }} + > + German + +
) : ( diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index e9f0aebc..e298445f 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import Paper from '@mui/material/Paper'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -19,6 +20,7 @@ import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; import { GenericModal } from '../atoms/GenericModal'; + /** TODO: * 1. allow editing existing robot after persisting browser steps */ @@ -31,30 +33,9 @@ interface Column { format?: (value: string) => string; } -const columns: readonly Column[] = [ - { id: 'interpret', label: 'Run', minWidth: 80 }, - { id: 'name', label: 'Name', minWidth: 80 }, - { - id: 'schedule', - label: 'Schedule', - minWidth: 80, - }, - { - id: 'integrate', - label: 'Integrate', - minWidth: 80, - }, - { - id: 'settings', - label: 'Settings', - minWidth: 80, - }, - { - id: 'options', - label: 'Options', - minWidth: 80, - }, -]; + + + interface Data { id: string; @@ -76,12 +57,38 @@ interface RecordingsTableProps { } export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { + const {t} = useTranslation(); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const [rows, setRows] = React.useState([]); const [isModalOpen, setModalOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); + const columns: readonly Column[] = [ + { id: 'interpret', label: t('recordingtable.run'), minWidth: 80 }, + { id: 'name', label: t('recordingtable.name'), minWidth: 80 }, + { + id: 'schedule', + label: t('recordingtable.schedule'), + minWidth: 80, + }, + { + id: 'integrate', + label: t('recordingtable.integrate'), + minWidth: 80, + }, + { + id: 'settings', + label: t('recordingtable.settings'), + minWidth: 80, + }, + { + id: 'options', + label: t('recordingtable.options'), + minWidth: 80, + }, + ]; + const { notify, setRecordings, browserId, setBrowserId, recordingUrl, setRecordingUrl, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore(); const navigate = useNavigate(); @@ -154,16 +161,17 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl + return ( - My Robots + {t('recordingtable.heading')} - Create Robot + {t('recordingtable.new')} @@ -300,9 +308,9 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl /> setModalOpen(false)} modalStyle={modalStyle}>
- Enter URL To Extract Data + {t('recordingtable.modal.title')} - Start Training Robot + {t('recordingtable.modal.button')}
@@ -400,6 +408,8 @@ const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsBut setAnchorEl(null); }; + const {t} = useTranslation(); + return ( <> - Edit + {t('recordingtable.edit')} { handleDelete(); handleClose(); }}> - Delete + {t('recordingtable.delete')} { handleDuplicate(); handleClose(); }}> - Duplicate + {t('recordingtable.duplicate')} diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index dadb6731..4143ae9f 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -5,6 +5,9 @@ import Box from '@mui/material/Box'; import { Paper, Button } from "@mui/material"; import { AutoAwesome, FormatListBulleted, VpnKey, Usb, CloudQueue, Code } from "@mui/icons-material"; import { apiUrl } from "../../apiConfig"; +import { useTranslation } from 'react-i18next'; +import i18n from '../../i18n'; + interface MainMenuProps { value: string; @@ -12,6 +15,7 @@ interface MainMenuProps { } export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenuProps) => { + const {t} = useTranslation(); const handleChange = (event: React.SyntheticEvent, newValue: string) => { handleChangeContent(newValue); @@ -47,7 +51,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu fontSize: 'medium', }} value="recordings" - label="Robots" + label={t('mainmenu.recordings')} icon={} iconPosition="start" /> @@ -58,7 +62,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu fontSize: 'medium', }} value="runs" - label="Runs" + label={t('mainmenu.runs')} icon={} iconPosition="start" /> @@ -69,7 +73,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu fontSize: 'medium', }} value="proxy" - label="Proxy" + label={t('mainmenu.proxy')} icon={} iconPosition="start" /> @@ -80,7 +84,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu fontSize: 'medium', }} value="apikey" - label="API Key" + label={t('mainmenu.apikey')} icon={} iconPosition="start" /> @@ -88,10 +92,10 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
diff --git a/src/i18n.ts b/src/i18n.ts index f318cd5c..c5e84364 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -10,7 +10,7 @@ i18n .init({ fallbackLng: 'en', debug: import.meta.env.DEV, - supportedLngs: ['en', 'es', 'ja', 'zh', 'ar'], + supportedLngs: ['en', 'es', 'ja', 'zh','de'], interpolation: { escapeValue: false, // React already escapes }, diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 3d70bfc2..3c8e08c4 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -6,11 +6,11 @@ import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; import { useTranslation } from 'react-i18next'; -import i18n from '../i18n'; // Add this import +import i18n from '../i18n'; const Login = () => { const { t } = useTranslation(); - console.log(i18n) // Add translation hook + console.log(i18n) console.log(t) const [form, setForm] = useState({ email: "", @@ -55,9 +55,7 @@ const Login = () => { }; // Language switcher function - const changeLanguage = (lng: string) => { - i18n.changeLanguage(lng); - }; + return ( { + const {t} = useTranslation(); const [form, setForm] = useState({ email: "", password: "", @@ -40,11 +45,11 @@ const Register = () => { password, }); dispatch({ type: "LOGIN", payload: data }); - notify("success", "Registration Successful!"); + notify("success", t('register.welcome_notification')); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); } catch (error:any) { - notify("error", error.response.data || "Registration Failed. Please try again."); + notify("error", error.response.data || t('register.error_notification')); setLoading(false); } }; @@ -82,7 +87,7 @@ const Register = () => { { /> { Loading ) : ( - "Register" + t('register.button') )} - Already have an account?{" "} + {t('register.register_prompt')}{" "} - Login + + {t('register.login_link')} From 5663abe63eaec093a22299a87b007c206617d4aa Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Tue, 10 Dec 2024 21:45:09 +0530 Subject: [PATCH 081/500] little progress and icon changed --- public/locales/en.json | 13 +++++ public/locales/ja.json | 13 ++++- src/components/molecules/NavBar.tsx | 8 ++- src/components/molecules/RunsTable.tsx | 76 ++++++++++++++------------ src/pages/Register.tsx | 2 +- 5 files changed, 72 insertions(+), 40 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 897e1729..b6396893 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -49,5 +49,18 @@ "feedback":"Join Maxun Cloud", "apidocs":"API Docs" + }, + "runstable":{ + "runs":"All Runs", + "runStatus":"Status", + "runName":"Name", + "startedAt":"Started At", + "finishedAt":"Finished At", + "delete":"Delete", + "settings":"Settings", + "search":"Search Runs..." + + + } } \ No newline at end of file diff --git a/public/locales/ja.json b/public/locales/ja.json index 88686e62..881fd131 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -37,7 +37,8 @@ }, "edit": "編集", "delete": "削除", - "duplicate": "複製" + "duplicate": "複製", + "search": "ロボットを検索..." }, "mainmenu": { "recordings": "ロボット", @@ -46,5 +47,15 @@ "apikey": "APIキー", "feedback": "Maxunクラウドに参加する", "apidocs": "APIドキュメント" + }, + "runstable": { + "runs": "すべての実行", + "runStatus": "ステータス", + "runName": "名前", + "startedAt": "開始日時", + "finishedAt": "終了日時", + "delete": "削除", + "settings": "設定", + "search": "実行を検索..." } } diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 958492e6..ba607fe4 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { IconButton, Menu, MenuItem, Typography, Chip } from "@mui/material"; -import { AccountCircle, Logout, Clear } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, Language } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; import { AuthContext } from "../../context/auth"; import { SaveRecording } from "../molecules/SaveRecording"; @@ -159,7 +159,7 @@ export const NavBar: React.FC = ({ {t("logout")} - {/* Language dropdown */} + ) : ( <> @@ -190,7 +190,9 @@ export const NavBar: React.FC = ({ marginRight: "10px", }} > - {t("language")} + + +
string; } -export const columns: readonly Column[] = [ - { id: 'runStatus', label: 'Status', minWidth: 80 }, - { id: 'name', label: 'Robot Name', minWidth: 80 }, - { id: 'startedAt', label: 'Started at', minWidth: 80 }, - { id: 'finishedAt', label: 'Finished at', minWidth: 80 }, - { id: 'settings', label: 'Settings', minWidth: 80 }, - { id: 'delete', label: 'Delete', minWidth: 80 }, -]; - -export interface Data { +interface Data { id: number; status: string; name: string; @@ -58,15 +61,25 @@ interface RunsTableProps { runningRecordingName: string; } -export const RunsTable = ( - { currentInterpretationLog, abortRunHandler, runId, runningRecordingName }: RunsTableProps) => { +export const RunsTable: React.FC = ({ + currentInterpretationLog, + abortRunHandler, + runId, + runningRecordingName +}) => { + const { t } = useTranslation(); + + // Update column labels using translation if needed + const translatedColumns = columns.map(column => ({ + ...column, + label: t(`runstable.${column.id}`, column.label) + })); + const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const [rows, setRows] = useState([]); - const [searchTerm, setSearchTerm] = useState(''); - const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleChangePage = (event: unknown, newPage: number) => { @@ -86,16 +99,13 @@ export const RunsTable = ( const fetchRuns = async () => { const runs = await getStoredRuns(); if (runs) { - const parsedRows: Data[] = []; - runs.map((run: any, index) => { - parsedRows.push({ - id: index, - ...run, - }); - }); + const parsedRows: Data[] = runs.map((run: any, index: number) => ({ + id: index, + ...run, + })); setRows(parsedRows); } else { - notify('error', 'No runs found. Please try again.') + notify('error', 'No runs found. Please try again.'); } }; @@ -104,7 +114,7 @@ export const RunsTable = ( fetchRuns(); setRerenderRuns(false); } - }, [rerenderRuns]); + }, [rerenderRuns, rows.length, setRerenderRuns]); const handleDelete = () => { setRows([]); @@ -112,7 +122,6 @@ export const RunsTable = ( fetchRuns(); }; - // Filter rows based on search term const filteredRows = rows.filter((row) => row.name.toLowerCase().includes(searchTerm.toLowerCase()) @@ -120,7 +129,6 @@ export const RunsTable = ( // Group filtered rows by robot meta id const groupedRows = filteredRows.reduce((acc, row) => { - if (!acc[row.robotMetaId]) { acc[row.robotMetaId] = []; } @@ -132,11 +140,11 @@ export const RunsTable = ( - All Runs + {t('runstable.runs', 'Runs')} ( }> - {data[data.length - 1].name} - - {columns.map((column) => ( + {translatedColumns.map((column) => ( ); -}; +}; \ No newline at end of file diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 57ec676a..b1fb23e2 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -83,7 +83,7 @@ const Register = () => { > logo - Create an Account + {t('register.title')} Date: Tue, 10 Dec 2024 23:05:17 +0530 Subject: [PATCH 082/500] feat: emit setGetList:false socket event in pagination mode --- src/context/browserActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index ef303f82..55ca1b37 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -53,6 +53,7 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const startPaginationMode = () => { setPaginationMode(true); setCaptureStage('pagination'); + socket?.emit('setGetList', { getList: false }); }; const stopPaginationMode = () => setPaginationMode(false); @@ -75,7 +76,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => { const stopGetList = () => { setGetList(false); - socket?.emit('setGetList', { getList: false }); setPaginationType(''); setLimitType(''); setCustomLimit(''); From c994072ef70b25d516a0b9d59f832949418ab6c3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:00:43 +0530 Subject: [PATCH 083/500] feat: pass listSelector getRect and getElementInfo --- server/src/workflow-management/classes/Generator.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index c9dc3385..1e81cd4a 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -541,7 +541,9 @@ export class WorkflowGenerator { * @returns {Promise} */ private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { - const elementInfo = await getElementInformation(page, coordinates); + const elementInfo = await getElementInformation(page, coordinates, this.listSelector); + + console.log(`List selector: ${this.listSelector}`) const selectorBasedOnCustomAction = (this.getList === true) ? await getNonUniqueSelectors(page, coordinates) @@ -570,9 +572,9 @@ export class WorkflowGenerator { * @returns {Promise} */ public generateDataForHighlighter = async (page: Page, coordinates: Coordinates) => { - const rect = await getRect(page, coordinates); + const rect = await getRect(page, coordinates, this.listSelector); const displaySelector = await this.generateSelector(page, coordinates, ActionType.Click); - const elementInfo = await getElementInformation(page, coordinates); + const elementInfo = await getElementInformation(page, coordinates, this.listSelector); if (rect) { if (this.getList === true) { if (this.listSelector !== '') { From 81bbba473fce2e37f031329305e9647c637940a6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:01:55 +0530 Subject: [PATCH 084/500] feat: condtionally handle getRect & getelementInfo --- server/src/workflow-management/selector.ts | 366 ++++++++++++--------- 1 file changed, 217 insertions(+), 149 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 917ac561..b383d653 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -22,100 +22,144 @@ type Workflow = WorkflowFile["workflow"]; */ export const getElementInformation = async ( page: Page, - coordinates: Coordinates + coordinates: Coordinates, + listSelector: string, ) => { try { - const elementInfo = await page.evaluate( - async ({ x, y }) => { - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (originalEl) { - let element = originalEl; - - // if (originalEl.tagName === 'A') { - // element = originalEl; - // } else if (originalEl.parentElement?.tagName === 'A') { - // element = originalEl.parentElement; - // } else { - // Generic parent finding logic based on visual containment - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', - 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', - 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', - 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', - 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' - ]; - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); + if (listSelector !== '') { + // Old implementation + const elementInfo = await page.evaluate( + async ({ x, y }) => { + const el = document.elementFromPoint(x, y) as HTMLElement; + if (el) { + const { parentElement } = el; + const element = parentElement?.tagName === 'A' ? parentElement : el; + let info: { + tagName: string; + hasOnlyText?: boolean; + innerText?: string; + url?: string; + imageUrl?: string; + attributes?: Record; + innerHTML?: string; + outerHTML?: string; + } = { + tagName: element?.tagName ?? '', + }; + if (element) { + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); + } + // Gather specific information based on the tag + if (element?.tagName === 'A') { + info.url = (element as HTMLAnchorElement).href; + info.innerText = element.innerText ?? ''; + } else if (element?.tagName === 'IMG') { + info.imageUrl = (element as HTMLImageElement).src; + } else { + info.hasOnlyText = element?.children?.length === 0 && + element?.innerText?.length > 0; + info.innerText = element?.innerText ?? ''; + } + info.innerHTML = element.innerHTML; + info.outerHTML = element.outerHTML; + return info; + } + return null; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return elementInfo; + } else { + // New implementation + const elementInfo = await page.evaluate( + async ({ x, y }) => { + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (originalEl) { + let element = originalEl; + + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', + 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', + 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', + 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT' + ]; + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); - if (!containerTags.includes(element.parentElement.tagName)) { - break; + if (!containerTags.includes(element.parentElement.tagName)) { + break; + } + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } } - // Check if parent visually contains the child - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; + let info: { + tagName: string; + hasOnlyText?: boolean; + innerText?: string; + url?: string; + imageUrl?: string; + attributes?: Record; + innerHTML?: string; + outerHTML?: string; + } = { + tagName: element?.tagName ?? '', + }; - // Additional checks for more comprehensive containment - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; + if (element) { + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); + } - if (fullyContained && significantOverlap) { - element = element.parentElement; + if (element?.tagName === 'A') { + info.url = (element as HTMLAnchorElement).href; + info.innerText = element.innerText ?? ''; + } else if (element?.tagName === 'IMG') { + info.imageUrl = (element as HTMLImageElement).src; } else { - break; - // } - } } + info.hasOnlyText = element?.children?.length === 0 && + element?.innerText?.length > 0; + info.innerText = element?.innerText ?? ''; + } - let info: { - tagName: string; - hasOnlyText?: boolean; - innerText?: string; - url?: string; - imageUrl?: string; - attributes?: Record; - innerHTML?: string; - outerHTML?: string; - } = { - tagName: element?.tagName ?? '', - }; - - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); + info.innerHTML = element.innerHTML; + info.outerHTML = element.outerHTML; + return info; } - - // Existing tag-specific logic - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; - return info; - } - return null; - }, - { x: coordinates.x, y: coordinates.y }, - ); - return elementInfo; + return null; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return elementInfo; + } } catch (error) { const { message, stack } = error as Error; console.error('Error while retrieving selector:', message); @@ -123,79 +167,103 @@ export const getElementInformation = async ( } }; -export const getRect = async (page: Page, coordinates: Coordinates) => { +export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string) => { try { - const rect = await page.evaluate( - async ({ x, y }) => { - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (originalEl) { - let element = originalEl; - - // if (originalEl.tagName === 'A') { - // element = originalEl; - // } else if (originalEl.parentElement?.tagName === 'A') { - // element = originalEl.parentElement; - // } else { - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', - 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', - 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', - 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', - 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' - ]; - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); - - if (!containerTags.includes(element.parentElement.tagName)) { - break; + if (listSelector !== '') { + // Old implementation + const rect = await page.evaluate( + async ({ x, y }) => { + const el = document.elementFromPoint(x, y) as HTMLElement; + if (el) { + const { parentElement } = el; + // Match the logic in recorder.ts for link clicks + const element = parentElement?.tagName === 'A' ? parentElement : el; + const rectangle = element?.getBoundingClientRect(); + if (rectangle) { + return { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + }; } - - - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; - - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; - - if (fullyContained && significantOverlap) { - element = element.parentElement; - } else { - break; - // } - }} - - //element = element?.parentElement?.tagName === 'A' ? element?.parentElement : element; - const rectangle = element?.getBoundingClientRect(); - - if (rectangle) { - return { - x: rectangle.x, - y: rectangle.y, - width: rectangle.width, - height: rectangle.height, - top: rectangle.top, - right: rectangle.right, - bottom: rectangle.bottom, - left: rectangle.left, - }; } - } - }, - { x: coordinates.x, y: coordinates.y }, - ); - return rect; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return rect; + } else { + // New implementation + const rect = await page.evaluate( + async ({ x, y }) => { + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (originalEl) { + let element = originalEl; + + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', + 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', + 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', + 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT' + ]; + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); + + if (!containerTags.includes(element.parentElement.tagName)) { + break; + } + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } + + const rectangle = element?.getBoundingClientRect(); + + if (rectangle) { + return { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + }; + } + } + return null; + }, + { x: coordinates.x, y: coordinates.y }, + ); + return rect; + } } catch (error) { const { message, stack } = error as Error; logger.log('error', `Error while retrieving selector: ${message}`); logger.log('error', `Stack: ${stack}`); } -} +}; /** From a34e8657735de66fdab71b08346fd1695acd1b21 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:02:09 +0530 Subject: [PATCH 085/500] chore: lint --- server/src/workflow-management/selector.ts | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index b383d653..66186a80 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -82,8 +82,8 @@ export const getElementInformation = async ( const originalEl = document.elementFromPoint(x, y) as HTMLElement; if (originalEl) { let element = originalEl; - - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', @@ -99,14 +99,14 @@ export const getElementInformation = async ( break; } - const fullyContained = + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && parentRect.top <= childRect.top && parentRect.bottom >= childRect.bottom; - const significantOverlap = - (childRect.width * childRect.height) / + const significantOverlap = + (childRect.width * childRect.height) / (parentRect.width * parentRect.height) > 0.5; if (fullyContained && significantOverlap) { @@ -203,8 +203,8 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector const originalEl = document.elementFromPoint(x, y) as HTMLElement; if (originalEl) { let element = originalEl; - - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', @@ -220,14 +220,14 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector break; } - const fullyContained = + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && parentRect.top <= childRect.top && parentRect.bottom >= childRect.bottom; - const significantOverlap = - (childRect.width * childRect.height) / + const significantOverlap = + (childRect.width * childRect.height) / (parentRect.width * parentRect.height) > 0.5; if (fullyContained && significantOverlap) { @@ -238,7 +238,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector } const rectangle = element?.getBoundingClientRect(); - + if (rectangle) { return { x: rectangle.x, @@ -916,7 +916,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', + const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', @@ -924,7 +924,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' ]; - + while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -933,22 +933,22 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates break; } - const fullyContained = + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && parentRect.top <= childRect.top && parentRect.bottom >= childRect.bottom; - const significantOverlap = - (childRect.width * childRect.height) / + const significantOverlap = + (childRect.width * childRect.height) / (parentRect.width * parentRect.height) > 0.5; if (fullyContained && significantOverlap) { element = element.parentElement; } else { break; + } } - } const generalSelector = getSelectorPath(element); return { From 8533ea536291f4294037b5174f0471e6f03b5e0b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:02:37 +0530 Subject: [PATCH 086/500] chore: remove unused imprts --- server/src/workflow-management/selector.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 66186a80..49d56f36 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -1,8 +1,7 @@ import { Page } from "playwright"; -import { Action, ActionType, Coordinates, TagName } from "../types"; +import { Coordinates } from "../types"; import { WhereWhatPair, WorkflowFile } from "maxun-core"; import logger from "../logger"; -import { getBestSelectorForAction } from "./utils"; /*TODO: 1. Handle TS errors (here we definetly know better) From 0b1b2436834bed1c6ed67dcbf8697e7c81fcdce3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:02:52 +0530 Subject: [PATCH 087/500] chore: remove todo --- server/src/workflow-management/selector.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 49d56f36..5fc621ba 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -3,11 +3,6 @@ import { Coordinates } from "../types"; import { WhereWhatPair, WorkflowFile } from "maxun-core"; import logger from "../logger"; -/*TODO: -1. Handle TS errors (here we definetly know better) -2. Add pending function descriptions + thought process (esp. selector generation) -*/ - type Workflow = WorkflowFile["workflow"]; /** From fea0c0331b6d2f347471fd7627b1262402f12611 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:04:16 +0530 Subject: [PATCH 088/500] chore: cleanup --- server/src/workflow-management/selector.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 5fc621ba..4c6d3f87 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -21,7 +21,6 @@ export const getElementInformation = async ( ) => { try { if (listSelector !== '') { - // Old implementation const elementInfo = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -70,7 +69,6 @@ export const getElementInformation = async ( ); return elementInfo; } else { - // New implementation const elementInfo = await page.evaluate( async ({ x, y }) => { const originalEl = document.elementFromPoint(x, y) as HTMLElement; @@ -83,7 +81,7 @@ export const getElementInformation = async ( 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT' + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); @@ -164,7 +162,6 @@ export const getElementInformation = async ( export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string) => { try { if (listSelector !== '') { - // Old implementation const rect = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -191,7 +188,6 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector ); return rect; } else { - // New implementation const rect = await page.evaluate( async ({ x, y }) => { const originalEl = document.elementFromPoint(x, y) as HTMLElement; @@ -204,7 +200,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT' + 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); From b72baca821ffec5ef3b31a231e356c82503fc489 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:08:08 +0530 Subject: [PATCH 089/500] docs: re-add jsdoc --- server/src/workflow-management/selector.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 4c6d3f87..891c0e3b 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -6,13 +6,12 @@ import logger from "../logger"; type Workflow = WorkflowFile["workflow"]; /** - * Returns a {@link Rectangle} object representing - * the coordinates, width, height and corner points of the element. - * If an element is not found, returns null. + * Checks the basic info about an element and returns a {@link BaseActionInfo} object. + * If the element is not found, returns undefined. * @param page The page instance. * @param coordinates Coordinates of an element. * @category WorkflowManagement-Selectors - * @returns {Promise} + * @returns {Promise} */ export const getElementInformation = async ( page: Page, @@ -159,6 +158,15 @@ export const getElementInformation = async ( } }; +/** + * Returns a {@link Rectangle} object representing + * the coordinates, width, height and corner points of the element. + * If an element is not found, returns null. + * @param page The page instance. + * @param coordinates Coordinates of an element. + * @category WorkflowManagement-Selectors + * @returns {Promise} + */ export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string) => { try { if (listSelector !== '') { From d6e4b8860fd05e1abf471b6d85891d4a34e9a6bd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 02:12:43 +0530 Subject: [PATCH 090/500] chore: remove console logs --- server/src/workflow-management/classes/Generator.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 1e81cd4a..57be015e 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -542,9 +542,6 @@ export class WorkflowGenerator { */ private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates, this.listSelector); - - console.log(`List selector: ${this.listSelector}`) - const selectorBasedOnCustomAction = (this.getList === true) ? await getNonUniqueSelectors(page, coordinates) : await getSelectors(page, coordinates); @@ -580,8 +577,6 @@ export class WorkflowGenerator { if (this.listSelector !== '') { const childSelectors = await getChildSelectors(page, this.listSelector || ''); this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childSelectors }) - console.log(`Child Selectors: ${childSelectors}`) - console.log(`Parent Selector: ${this.listSelector}`) } else { this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); } From 2135f2eb6227fcb7fd86fd32155161b6d211e438 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 13:57:09 +0530 Subject: [PATCH 091/500] chore: use node:22-slim as base --- server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Dockerfile b/server/Dockerfile index ae26e8eb..877b781e 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.40.0-jammy +FROM node:22-slim # Set working directory WORKDIR /app From 08f8684b98dc2d1dc56c19c6805a71b4f444600b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:05:14 +0530 Subject: [PATCH 092/500] chore: use BACKEND_PORT --- server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Dockerfile b/server/Dockerfile index 877b781e..f8853fb2 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -50,7 +50,7 @@ RUN apt-get update && apt-get install -y \ # RUN chmod +x ./start.sh # Expose the backend port -EXPOSE 8080 +EXPOSE ${BACKEND_PORT:-8080} # Start the backend using the start script CMD ["npm", "run", "server"] \ No newline at end of file From a2b87762015eff62c439c130ce8144f2eeaf4a7a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:06:08 +0530 Subject: [PATCH 093/500] chore: use BACKEND_PORT --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 719e7814..032dc982 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY vite.config.js ./ COPY tsconfig.json ./ # Expose the frontend port -EXPOSE 5173 +EXPOSE ${FRONTEND_PORT:-5173} # Start the frontend using the client script CMD ["npm", "run", "client", "--", "--host"] \ No newline at end of file From c2ce3f3387c87191d6d940354afff716272b2083 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:13:12 +0530 Subject: [PATCH 094/500] feat: explicitly fetch easylist url --- 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 7e0a7d1a..71fdd933 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -180,7 +180,7 @@ export class RemoteBrowser { // await this.currentPage.setExtraHTTPHeaders({ // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' // }); - const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch); + const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']); await blocker.enableBlockingInPage(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage); await blocker.disableBlockingInPage(this.currentPage); From 44880cfce0ef8030ed9915beaff5b3fb806c3770 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:14:23 +0530 Subject: [PATCH 095/500] feat: explicitly fetch easylist url --- maxun-core/src/interpret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index bd90e40f..1837dbd5 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -102,7 +102,7 @@ export default class Interpreter extends EventEmitter { }; } - PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch).then(blocker => { + PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']).then(blocker => { this.blocker = blocker; }).catch(err => { this.log(`Failed to initialize ad-blocker:`, Level.ERROR); From 53cb428715f468be6e696daa2cee12d781080a41 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:21:26 +0530 Subject: [PATCH 096/500] chore: core v0.0.6 --- 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 90ee01b7..36d06aa9 100644 --- a/maxun-core/package.json +++ b/maxun-core/package.json @@ -1,6 +1,6 @@ { "name": "maxun-core", - "version": "0.0.5", + "version": "0.0.6", "description": "Core package for Maxun, responsible for data extraction", "main": "build/index.js", "typings": "build/index.d.ts", From 63230480f3a42394f2d276ebd4044906da21ae2a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:22:05 +0530 Subject: [PATCH 097/500] chore: use core v0.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7d634f3..6761d7a9 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "jwt-decode": "^4.0.0", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.5", + "maxun-core": "0.0.6", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", From ab2c32c334224236243c65ee451187aaf3fea835 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:34:09 +0530 Subject: [PATCH 098/500] chore: use core v0.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6761d7a9..9fab1c55 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "jwt-decode": "^4.0.0", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "0.0.6", + "maxun-core": "^0.0.6", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", From 337ad21577b5ec90ffcb6bd687f82f4fde6737b4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:53:18 +0530 Subject: [PATCH 099/500] feat: update description --- server/src/swagger/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/swagger/config.ts b/server/src/swagger/config.ts index c9c12210..2c7c588e 100644 --- a/server/src/swagger/config.ts +++ b/server/src/swagger/config.ts @@ -7,7 +7,7 @@ const options = { info: { title: 'Maxun API Documentation', version: '1.0.0', - description: 'API documentation for Maxun (https://github.com/getmaxun/maxun)', + description: 'Maxun lets you get the data your robot extracted and run robots via API. All you need to do is input the Maxun API key by clicking Authorize below.', }, components: { securitySchemes: { From 2025d09e1e8240fd921e4d4dc6a722d630eeb42c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 14:53:54 +0530 Subject: [PATCH 100/500] feat: rename to website to api --- server/src/swagger/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/swagger/config.ts b/server/src/swagger/config.ts index 2c7c588e..5f267053 100644 --- a/server/src/swagger/config.ts +++ b/server/src/swagger/config.ts @@ -5,7 +5,7 @@ const options = { definition: { openapi: '3.0.0', info: { - title: 'Maxun API Documentation', + title: 'Website to API', version: '1.0.0', description: 'Maxun lets you get the data your robot extracted and run robots via API. All you need to do is input the Maxun API key by clicking Authorize below.', }, From 319f9fce24e22576b98639f91a09e7075edf0940 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 15:16:45 +0530 Subject: [PATCH 101/500] feat: ensure swagger is accessible with or without build --- server/src/swagger/config.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/server/src/swagger/config.ts b/server/src/swagger/config.ts index 5f267053..bf115ed1 100644 --- a/server/src/swagger/config.ts +++ b/server/src/swagger/config.ts @@ -1,5 +1,16 @@ import swaggerJSDoc from 'swagger-jsdoc'; import path from 'path'; +import fs from 'fs'; + +// Dynamically determine API file paths +const jsFiles = [path.join(__dirname, '../api/*.js')] +const tsFiles = [path.join(__dirname, '../api/*.ts')] + +let apis = fs.existsSync(jsFiles[0]) ? jsFiles : tsFiles; + +if (!apis) { + throw new Error('No valid API files found! Ensure either .js or .ts files exist in the ../api/ directory.'); +} const options = { definition: { @@ -7,7 +18,8 @@ const options = { info: { title: 'Website to API', version: '1.0.0', - description: 'Maxun lets you get the data your robot extracted and run robots via API. All you need to do is input the Maxun API key by clicking Authorize below.', + description: + 'Maxun lets you get the data your robot extracted and run robots via API. All you need to do is input the Maxun API key by clicking Authorize below.', }, components: { securitySchemes: { @@ -15,7 +27,8 @@ const options = { type: 'apiKey', in: 'header', name: 'x-api-key', - description: 'API key for authorization. You can find your API key in the "API Key" section on Maxun Dashboard.', + description: + 'API key for authorization. You can find your API key in the "API Key" section on Maxun Dashboard.', }, }, }, @@ -25,7 +38,7 @@ const options = { }, ], }, - apis: process.env.NODE_ENV === 'production' ? [path.join(__dirname, '../api/*.js')] : [path.join(__dirname, '../api/*.ts')] + apis, }; const swaggerSpec = swaggerJSDoc(options); From 62198377d67cdb136d9e1695b2aed68154a5f34f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 15:29:05 +0530 Subject: [PATCH 102/500] chore: 0.0.6 BE img, 0.0.3 FE img --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 46cc72c4..63e59187 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.5 + image: getmaxun/maxun-backend:v0.0.6 ports: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env @@ -72,7 +72,7 @@ services: #build: #context: . #dockerfile: Dockerfile - image: getmaxun/maxun-frontend:v0.0.2 + image: getmaxun/maxun-frontend:v0.0.3 ports: - "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}" env_file: .env From 8b107f0685d4c0ba8df3f408ad7901ef3fb88f07 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 16:54:34 +0530 Subject: [PATCH 103/500] feat: use latest docker image --- server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Dockerfile b/server/Dockerfile index f8853fb2..e738f252 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-slim +FROM mcr.microsoft.com/playwright:v1.46.0-noble # Set working directory WORKDIR /app From 8b33157f53ddebe3b0d5c385e14fcc712ac2d22d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 11 Dec 2024 16:55:10 +0530 Subject: [PATCH 104/500] chore: 0.0.7 BE img --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 63e59187..51a9f4eb 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.6 + image: getmaxun/maxun-backend:v0.0.7 ports: - "${BACKEND_PORT:-8080}:${BACKEND_PORT:-8080}" env_file: .env From 18b517c875d00b5856a26118b55a9d7c614cd152 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Thu, 12 Dec 2024 18:41:25 +0530 Subject: [PATCH 105/500] chore: remove note --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ac74d21c..1fc1bfd6 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,7 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web -> Note: Maxun is in its early stages of development and currently does not support self-hosting. However, you can run Maxun locally. Self-hosting capabilities are planned for a future release and will be available soon. - -# Local Installation +# Installation ### Docker Compose ``` git clone https://github.com/getmaxun/maxun From ac13cd81d95f36dfd709c5177a6f857920e50467 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Thu, 12 Dec 2024 18:41:48 +0530 Subject: [PATCH 106/500] chore: remove clone instruction --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1fc1bfd6..26eb72fe 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web # Installation ### Docker Compose ``` -git clone https://github.com/getmaxun/maxun docker-compose up -d ``` You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ From 171d669d2ed69249c1336311493ed8cf906575e2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 13 Dec 2024 21:20:25 +0530 Subject: [PATCH 107/500] feat: pass context script for webdriver --- .../classes/RemoteBrowser.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 04fb59b3..2dab103a 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -225,6 +225,31 @@ export class RemoteBrowser { contextOptions.userAgent = browserUserAgent; this.context = await this.browser.newContext(contextOptions); + await this.context.addInitScript( + `const defaultGetter = Object.getOwnPropertyDescriptor( + Navigator.prototype, + "webdriver" + ).get; + defaultGetter.apply(navigator); + defaultGetter.toString(); + Object.defineProperty(Navigator.prototype, "webdriver", { + set: undefined, + enumerable: true, + configurable: true, + get: new Proxy(defaultGetter, { + apply: (target, thisArg, args) => { + Reflect.apply(target, thisArg, args); + return false; + }, + }), + }); + const patchedGetter = Object.getOwnPropertyDescriptor( + Navigator.prototype, + "webdriver" + ).get; + patchedGetter.apply(navigator); + patchedGetter.toString();` + ); this.currentPage = await this.context.newPage(); await this.setupPageEventListeners(this.currentPage); From cd05ddfb3c780aa26ec1e6108c4f173dafe26291 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 13 Dec 2024 21:21:00 +0530 Subject: [PATCH 108/500] 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 2dab103a..b19b5cbc 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -104,7 +104,7 @@ export class RemoteBrowser { } catch { return url; } - } + } /** * Determines if a URL change is significant enough to emit @@ -130,11 +130,11 @@ export class RemoteBrowser { }); // Handle page load events with retry mechanism - page.on('load', async () => { + page.on('load', async () => { const injectScript = async (): Promise => { try { await page.waitForLoadState('networkidle', { timeout: 5000 }); - + await page.evaluate(getInjectableScript()); return true; } catch (error: any) { @@ -201,7 +201,7 @@ export class RemoteBrowser { const contextOptions: any = { viewport: { height: 400, width: 900 }, // recordVideo: { dir: 'videos/' } - // Force reduced motion to prevent animation issues + // Force reduced motion to prevent animation issues reducedMotion: 'reduce', // Force JavaScript to be enabled javaScriptEnabled: true, @@ -249,7 +249,7 @@ export class RemoteBrowser { ).get; patchedGetter.apply(navigator); patchedGetter.toString();` - ); + ); this.currentPage = await this.context.newPage(); await this.setupPageEventListeners(this.currentPage); @@ -481,7 +481,7 @@ export class RemoteBrowser { this.currentPage = newPage; if (this.currentPage) { await this.setupPageEventListeners(this.currentPage); - + this.client = await this.currentPage.context().newCDPSession(this.currentPage); await this.subscribeToScreencast(); } else { From c49e70a1eeab9ef569f1e41738bea8882915ec22 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 13 Dec 2024 22:04:35 +0530 Subject: [PATCH 109/500] chrome and chromium user agent --- server/src/browser-management/classes/RemoteBrowser.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index b19b5cbc..e5d3217e 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -220,8 +220,7 @@ export class RemoteBrowser { password: proxyOptions.password ? proxyOptions.password : undefined, }; } - const browserUserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.38 Safari/537.36"; - + const browserUserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 Chromium/131.0.6778.140 Safari/537.36"; contextOptions.userAgent = browserUserAgent; this.context = await this.browser.newContext(contextOptions); From b173ce3e98d9bcc2838de7ee257f3b8ee5e95a92 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 13 Dec 2024 22:05:51 +0530 Subject: [PATCH 110/500] chore: remove commented code --- .../classes/RemoteBrowser.ts | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index e5d3217e..0299faf6 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -155,35 +155,6 @@ export class RemoteBrowser { * @returns {Promise} */ public initialize = async (userId: string): Promise => { - // const launchOptions = { - // headless: true, - // proxy: options.launchOptions?.proxy, - // chromiumSandbox: false, - // args: [ - // '--no-sandbox', - // '--disable-setuid-sandbox', - // '--headless=new', - // '--disable-gpu', - // '--disable-dev-shm-usage', - // '--disable-software-rasterizer', - // '--in-process-gpu', - // '--disable-infobars', - // '--single-process', - // '--no-zygote', - // '--disable-notifications', - // '--disable-extensions', - // '--disable-background-timer-throttling', - // ...(options.launchOptions?.args || []) - // ], - // env: { - // ...process.env, - // CHROMIUM_FLAGS: '--disable-gpu --no-sandbox --headless=new' - // } - // }; - // console.log('Launch options before:', options.launchOptions); - // this.browser = (await options.browser.launch(launchOptions)); - - // console.log('Launch options after:', options.launchOptions) this.browser = (await chromium.launch({ headless: true, })); @@ -253,9 +224,6 @@ export class RemoteBrowser { await this.setupPageEventListeners(this.currentPage); - // await this.currentPage.setExtraHTTPHeaders({ - // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' - // }); const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']); await blocker.enableBlockingInPage(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage); From 06184010ae1a789c86bc80d00cd082752fdd1444 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 14 Dec 2024 06:58:29 +0530 Subject: [PATCH 111/500] feat: args --- server/src/browser-management/classes/RemoteBrowser.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 0299faf6..9dc51690 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -157,6 +157,13 @@ export class RemoteBrowser { public initialize = async (userId: string): Promise => { this.browser = (await chromium.launch({ headless: true, + args: [ + "--disable-blink-features=AutomationControlled", + "--disable-web-security", + "--disable-features=IsolateOrigins,site-per-process", + "--disable-site-isolation-trials", + "--disable-extensions" + ], })); const proxyConfig = await getDecryptedProxyConfig(userId); let proxyOptions: { server: string, username?: string, password?: string } = { server: '' }; From e5e46e8e6270ff47387efb4699940d6661e3217e Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Sat, 14 Dec 2024 07:43:19 +0530 Subject: [PATCH 112/500] docs: clearer installation instructions --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 26eb72fe..c8441f1a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,14 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web # Installation +1. First, create a file named `.env` in the root folder of the project +2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. +3. Choose your installation method below + ### Docker Compose +1. Copy the `docker-compose.yml` file from the codebase +2. Ensure you have setup the `.env` file +3. Run the command below ``` docker-compose up -d ``` From eb27cddacd27c4b9bcc73c840d909ef9a2b42bb9 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Sat, 14 Dec 2024 07:47:03 +0530 Subject: [PATCH 113/500] docs: compose file location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8441f1a..38d0eebe 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web 3. Choose your installation method below ### Docker Compose -1. Copy the `docker-compose.yml` file from the codebase +1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) 2. Ensure you have setup the `.env` file 3. Run the command below ``` From 0284f3d001536ec7128c484044fbac102647e707 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 14 Dec 2024 09:27:13 +0530 Subject: [PATCH 114/500] feat: remove fe be mounts --- docker-compose.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 51a9f4eb..3c6e3a0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,8 +64,6 @@ services: - redis - minio volumes: - - ./server:/app/server # Mount server source code for hot reloading - - ./maxun-core:/app/maxun-core # Mount maxun-core for any shared code updates - /var/run/dbus:/var/run/dbus frontend: @@ -79,13 +77,10 @@ services: environment: PUBLIC_URL: ${PUBLIC_URL} BACKEND_URL: ${BACKEND_URL} - volumes: - - ./:/app # Mount entire frontend app directory for hot reloading - - /app/node_modules # Anonymous volume to prevent overwriting node_modules depends_on: - backend volumes: postgres_data: minio_data: - redis_data: + redis_data: \ No newline at end of file From 44693259257497deb6b285f54e64a0ae0ec2b7b1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 14 Dec 2024 10:49:52 +0530 Subject: [PATCH 115/500] chore: sync compose master <-> develop --- docker-compose.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 51a9f4eb..3c6e3a0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,8 +64,6 @@ services: - redis - minio volumes: - - ./server:/app/server # Mount server source code for hot reloading - - ./maxun-core:/app/maxun-core # Mount maxun-core for any shared code updates - /var/run/dbus:/var/run/dbus frontend: @@ -79,13 +77,10 @@ services: environment: PUBLIC_URL: ${PUBLIC_URL} BACKEND_URL: ${BACKEND_URL} - volumes: - - ./:/app # Mount entire frontend app directory for hot reloading - - /app/node_modules # Anonymous volume to prevent overwriting node_modules depends_on: - backend volumes: postgres_data: minio_data: - redis_data: + redis_data: \ No newline at end of file From 7f48464eea993f0d4468942cdeb77c87398191f8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 14 Dec 2024 18:35:38 +0530 Subject: [PATCH 116/500] feat: add page navigation timeout --- maxun-core/src/interpret.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index d1cc8318..848ddd76 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -365,7 +365,7 @@ export default class Interpreter extends EventEmitter { try { const newPage = await context.newPage(); await newPage.goto(link); - await newPage.waitForLoadState('networkidle'); + await newPage.waitForLoadState('domcontentloaded'); await this.runLoop(newPage, this.initializedWorkflow!); } catch (e) { // `runLoop` uses soft mode, so it recovers from it's own exceptions @@ -576,7 +576,7 @@ export default class Interpreter extends EventEmitter { } await Promise.all([ nextButton.dispatchEvent('click'), - page.waitForNavigation({ waitUntil: 'networkidle' }) + page.waitForNavigation({ waitUntil: 'domcontentloaded' }) ]); await page.waitForTimeout(1000); @@ -767,6 +767,8 @@ export default class Interpreter extends EventEmitter { public async run(page: Page, params?: ParamType): Promise { this.log('Starting the workflow.', Level.LOG); const context = page.context(); + + page.setDefaultNavigationTimeout(100000); // Check proxy settings from context options const contextOptions = (context as any)._options; From bdf908e37cdcb2200cb5c653a5149db279ce51aa Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 14 Dec 2024 18:36:59 +0530 Subject: [PATCH 117/500] feat: add domcontentloaded wait load state --- server/src/workflow-management/classes/Generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 57be015e..2cde90e4 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -189,7 +189,7 @@ export class WorkflowGenerator { * * This function also makes sure to add a waitForLoadState and a generated flag * action after every new action or pair added. The [waitForLoadState](https://playwright.dev/docs/api/class-frame#frame-wait-for-load-state) - * action waits for the networkidle event to be fired, + * action waits for the domcontentloaded event to be fired, * and the generated flag action is used for making pausing the interpretation possible. * * @param pair The pair to add to the workflow. @@ -217,7 +217,7 @@ export class WorkflowGenerator { if (pair.what[0].action !== 'waitForLoadState' && pair.what[0].action !== 'press') { pair.what.push({ action: 'waitForLoadState', - args: ['networkidle'], + args: ['domcontentloaded'], }); } this.workflowRecord.workflow[matchedIndex].what = this.workflowRecord.workflow[matchedIndex].what.concat(pair.what); @@ -232,7 +232,7 @@ export class WorkflowGenerator { if (pair.what[0].action !== 'waitForLoadState' && pair.what[0].action !== 'press') { pair.what.push({ action: 'waitForLoadState', - args: ['networkidle'], + args: ['domcontentloaded'], }); } if (this.generatedData.lastIndex === 0) { From f38230d1b4d45d266886679c3228d07d2f52d18d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 14 Dec 2024 20:30:24 +0530 Subject: [PATCH 118/500] feat: revert to networkidle for wait load state --- server/src/workflow-management/classes/Generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 2cde90e4..57be015e 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -189,7 +189,7 @@ export class WorkflowGenerator { * * This function also makes sure to add a waitForLoadState and a generated flag * action after every new action or pair added. The [waitForLoadState](https://playwright.dev/docs/api/class-frame#frame-wait-for-load-state) - * action waits for the domcontentloaded event to be fired, + * action waits for the networkidle event to be fired, * and the generated flag action is used for making pausing the interpretation possible. * * @param pair The pair to add to the workflow. @@ -217,7 +217,7 @@ export class WorkflowGenerator { if (pair.what[0].action !== 'waitForLoadState' && pair.what[0].action !== 'press') { pair.what.push({ action: 'waitForLoadState', - args: ['domcontentloaded'], + args: ['networkidle'], }); } this.workflowRecord.workflow[matchedIndex].what = this.workflowRecord.workflow[matchedIndex].what.concat(pair.what); @@ -232,7 +232,7 @@ export class WorkflowGenerator { if (pair.what[0].action !== 'waitForLoadState' && pair.what[0].action !== 'press') { pair.what.push({ action: 'waitForLoadState', - args: ['domcontentloaded'], + args: ['networkidle'], }); } if (this.generatedData.lastIndex === 0) { From 7ce7a1598c3c394d8107677859991257460755ee Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 14 Dec 2024 20:32:07 +0530 Subject: [PATCH 119/500] feat: check for selector visibility in getState --- maxun-core/src/interpret.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 848ddd76..e11ae255 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -192,8 +192,8 @@ export default class Interpreter extends EventEmitter { // const actionable = async (selector: string): Promise => { // try { // const proms = [ - // page.isEnabled(selector, { timeout: 5000 }), - // page.isVisible(selector, { timeout: 5000 }), + // page.isEnabled(selector, { timeout: 10000 }), + // page.isVisible(selector, { timeout: 10000 }), // ]; // return await Promise.all(proms).then((bools) => bools.every((x) => x)); @@ -214,6 +214,17 @@ export default class Interpreter extends EventEmitter { // return []; // }), // ).then((x) => x.flat()); + + const presentSelectors: SelectorArray = await Promise.all( + selectors.map(async (selector) => { + try { + await page.waitForSelector(selector, { state: 'attached' }); + return [selector]; + } catch (e) { + return []; + } + }), + ).then((x) => x.flat()); const action = workflowCopy[workflowCopy.length - 1]; @@ -233,7 +244,7 @@ export default class Interpreter extends EventEmitter { ...p, [cookie.name]: cookie.value, }), {}), - selectors, + selectors: presentSelectors, }; } @@ -365,7 +376,7 @@ export default class Interpreter extends EventEmitter { try { const newPage = await context.newPage(); await newPage.goto(link); - await newPage.waitForLoadState('domcontentloaded'); + await newPage.waitForLoadState('networkidle'); await this.runLoop(newPage, this.initializedWorkflow!); } catch (e) { // `runLoop` uses soft mode, so it recovers from it's own exceptions @@ -576,7 +587,7 @@ export default class Interpreter extends EventEmitter { } await Promise.all([ nextButton.dispatchEvent('click'), - page.waitForNavigation({ waitUntil: 'domcontentloaded' }) + page.waitForNavigation({ waitUntil: 'networkidle' }) ]); await page.waitForTimeout(1000); From e22c019a0c6feed2b2c3e2ecb2aa4b749dd0f226 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 14 Dec 2024 22:30:50 +0530 Subject: [PATCH 120/500] feat: rotate user agents --- .../browser-management/classes/RemoteBrowser.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 9dc51690..05927b24 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -148,6 +148,19 @@ export class RemoteBrowser { }); } + private getUserAgent() { + const userAgents = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.140 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.1938.81 Safari/537.36 Edg/116.0.1938.81', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.96 Safari/537.36 OPR/101.0.4843.25', + '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)]; + } + /** * An asynchronous constructor for asynchronously initialized properties. * Must be called right after creating an instance of RemoteBrowser class. @@ -198,9 +211,8 @@ export class RemoteBrowser { password: proxyOptions.password ? proxyOptions.password : undefined, }; } - const browserUserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 Chromium/131.0.6778.140 Safari/537.36"; - contextOptions.userAgent = browserUserAgent; + contextOptions.userAgent = this.getUserAgent(); this.context = await this.browser.newContext(contextOptions); await this.context.addInitScript( `const defaultGetter = Object.getOwnPropertyDescriptor( From 6c15df78161969fdc46ea0af98fdcc19e2a5ed4f Mon Sep 17 00:00:00 2001 From: The Hague Centre for Strategic Studies <51345661+HCSS-StratBase@users.noreply.github.com> Date: Sat, 14 Dec 2024 19:46:21 +0100 Subject: [PATCH 121/500] Update README.md small addition to make the installation instructions more 'noob-proof' --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 38d0eebe..b63cf8a7 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,15 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web # Installation -1. First, create a file named `.env` in the root folder of the project -2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. -3. Choose your installation method below +1. Create a root folder for your project (e.g. 'maxun') +2. Create a file named `.env` in the root folder of the project +3. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. +4. Choose your installation method below ### Docker Compose -1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) -2. Ensure you have setup the `.env` file -3. Run the command below +1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) into your root folder +2. Ensure you have setup the `.env` file in that same folder +3. Run the command below from a terminal ``` docker-compose up -d ``` From 320f24ec002256067c418f45efbb48258599c893 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 01:06:23 +0530 Subject: [PATCH 122/500] feat: shm & sandbox args --- server/src/browser-management/classes/RemoteBrowser.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 05927b24..4b059cda 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -175,7 +175,9 @@ export class RemoteBrowser { "--disable-web-security", "--disable-features=IsolateOrigins,site-per-process", "--disable-site-isolation-trials", - "--disable-extensions" + "--disable-extensions", + "--no-sandbox", + "--disable-dev-shm-usage", ], })); const proxyConfig = await getDecryptedProxyConfig(userId); @@ -201,7 +203,7 @@ export class RemoteBrowser { // Disable hardware acceleration forcedColors: 'none', isMobile: false, - hasTouch: false + hasTouch: false, }; if (proxyOptions.server) { @@ -212,7 +214,6 @@ export class RemoteBrowser { }; } - contextOptions.userAgent = this.getUserAgent(); this.context = await this.browser.newContext(contextOptions); await this.context.addInitScript( `const defaultGetter = Object.getOwnPropertyDescriptor( From ffe87b0c7db7b0e6446c3f0fb2a5d67e313f29b8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 01:06:45 +0530 Subject: [PATCH 123/500] feat: user getUserAgent() --- server/src/browser-management/classes/RemoteBrowser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 4b059cda..31aceada 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -204,6 +204,7 @@ export class RemoteBrowser { forcedColors: 'none', isMobile: false, hasTouch: false, + userAgent: this.getUserAgent(), }; if (proxyOptions.server) { From e70145219eca9a092487db350487f4a6bb711906 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 04:58:13 +0530 Subject: [PATCH 124/500] feat: remove container tags --- server/src/workflow-management/selector.ts | 37 ---------------------- 1 file changed, 37 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 891c0e3b..bde38300 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -74,22 +74,10 @@ export const getElementInformation = async ( if (originalEl) { let element = originalEl; - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', - 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', - 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', - 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', - 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' - ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); - if (!containerTags.includes(element.parentElement.tagName)) { - break; - } - const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && @@ -202,22 +190,10 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector if (originalEl) { let element = originalEl; - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', - 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', - 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', - 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', - 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' - ]; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); - if (!containerTags.includes(element.parentElement.tagName)) { - break; - } - const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && @@ -914,23 +890,10 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - const containerTags = ['DIV', 'SECTION', 'ARTICLE', 'MAIN', 'HEADER', 'FOOTER', 'NAV', 'ASIDE', - 'ADDRESS', 'BLOCKQUOTE', 'DETAILS', 'DIALOG', 'FIGURE', 'FIGCAPTION', 'MAIN', 'MARK', 'SUMMARY', 'TIME', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD', 'CAPTION', 'COLGROUP', 'COL', 'FORM', 'FIELDSET', - 'LEGEND', 'LABEL', 'INPUT', 'BUTTON', 'SELECT', 'DATALIST', 'OPTGROUP', 'OPTION', 'TEXTAREA', 'OUTPUT', - 'PROGRESS', 'METER', 'DETAILS', 'SUMMARY', 'MENU', 'MENUITEM', 'MENUITEM', 'APPLET', 'EMBED', 'OBJECT', - 'PARAM', 'VIDEO', 'AUDIO', 'SOURCE', 'TRACK', 'CANVAS', 'MAP', 'AREA', 'SVG', 'IFRAME', 'FRAME', 'FRAMESET', - 'LI', 'UL', 'OL', 'DL', 'DT', 'DD', 'HR', 'P', 'PRE', 'LISTING', 'PLAINTEXT', 'A' - ]; - while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); - if (!containerTags.includes(element.parentElement.tagName)) { - break; - } - const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && From cb0965323e3a8f13483e198bb1e7dbf086a4481d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 05:01:15 +0530 Subject: [PATCH 125/500] feat: accept getList in getRect and getElementInfo --- server/src/workflow-management/classes/Generator.ts | 6 +++--- server/src/workflow-management/selector.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 57be015e..2ab54753 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -541,7 +541,7 @@ export class WorkflowGenerator { * @returns {Promise} */ private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { - const elementInfo = await getElementInformation(page, coordinates, this.listSelector); + const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); const selectorBasedOnCustomAction = (this.getList === true) ? await getNonUniqueSelectors(page, coordinates) : await getSelectors(page, coordinates); @@ -569,9 +569,9 @@ export class WorkflowGenerator { * @returns {Promise} */ public generateDataForHighlighter = async (page: Page, coordinates: Coordinates) => { - const rect = await getRect(page, coordinates, this.listSelector); + const rect = await getRect(page, coordinates, this.listSelector, this.getList); const displaySelector = await this.generateSelector(page, coordinates, ActionType.Click); - const elementInfo = await getElementInformation(page, coordinates, this.listSelector); + const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); if (rect) { if (this.getList === true) { if (this.listSelector !== '') { diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index bde38300..fd25a617 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -17,6 +17,7 @@ export const getElementInformation = async ( page: Page, coordinates: Coordinates, listSelector: string, + getList: boolean ) => { try { if (listSelector !== '') { @@ -155,7 +156,7 @@ export const getElementInformation = async ( * @category WorkflowManagement-Selectors * @returns {Promise} */ -export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string) => { +export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => { try { if (listSelector !== '') { const rect = await page.evaluate( From ddb880df668e84ebce1d7731f6dda6aa2413a486 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 05:07:45 +0530 Subject: [PATCH 126/500] fix: capture text selection --- server/src/workflow-management/selector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index fd25a617..36b592a6 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,7 +20,7 @@ export const getElementInformation = async ( getList: boolean ) => { try { - if (listSelector !== '') { + if (!getList) { const elementInfo = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -158,7 +158,7 @@ export const getElementInformation = async ( */ export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => { try { - if (listSelector !== '') { + if (!getList) { const rect = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; From 97e7c89105132d42864be52386e9971208f88d8f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 05:13:08 +0530 Subject: [PATCH 127/500] feat: re-add listSelector empty check for child selection --- server/src/workflow-management/selector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 36b592a6..699cb669 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,7 +20,7 @@ export const getElementInformation = async ( getList: boolean ) => { try { - if (!getList) { + if (!getList || (getList && listSelector !== '')) { const elementInfo = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -158,7 +158,7 @@ export const getElementInformation = async ( */ export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => { try { - if (!getList) { + if (!getList || (getList && listSelector !== '')) { const rect = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; From 0c3b1e3e53c4e52c7898a2f8637884c0f07e118a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 15 Dec 2024 05:29:30 +0530 Subject: [PATCH 128/500] feat: paass listSelect --- .../workflow-management/classes/Generator.ts | 2 +- server/src/workflow-management/selector.ts | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 2ab54753..31775261 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -543,7 +543,7 @@ export class WorkflowGenerator { private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); const selectorBasedOnCustomAction = (this.getList === true) - ? await getNonUniqueSelectors(page, coordinates) + ? await getNonUniqueSelectors(page, coordinates, this.listSelector) : await getSelectors(page, coordinates); const bestSelector = getBestSelectorForAction( diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 699cb669..527a800f 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -852,7 +852,7 @@ interface SelectorResult { * @returns {Promise} */ -export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates): Promise => { +export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates, listSelector: string): Promise => { try { const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { function getNonUniqueSelector(element: HTMLElement): string { @@ -891,24 +891,26 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); - - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; - - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; - - if (fullyContained && significantOverlap) { - element = element.parentElement; - } else { - break; + if (listSelector === '') { + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } } } From e1476935db354b7030eced9447c280241defb713 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 16 Dec 2024 07:25:13 +0530 Subject: [PATCH 129/500] fix: dont pass listSelector to non unique --- server/src/workflow-management/classes/Generator.ts | 2 +- server/src/workflow-management/selector.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 31775261..2ab54753 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -543,7 +543,7 @@ export class WorkflowGenerator { private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); const selectorBasedOnCustomAction = (this.getList === true) - ? await getNonUniqueSelectors(page, coordinates, this.listSelector) + ? await getNonUniqueSelectors(page, coordinates) : await getSelectors(page, coordinates); const bestSelector = getBestSelectorForAction( diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 527a800f..070a897d 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,7 +20,8 @@ export const getElementInformation = async ( getList: boolean ) => { try { - if (!getList || (getList && listSelector !== '')) { + console.log(`List Selector Value From EL INFO: ->> ${listSelector !== '' ? listSelector: 'It is empty'}`); + if (!getList ||listSelector !== '') { const elementInfo = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -158,7 +159,7 @@ export const getElementInformation = async ( */ export const getRect = async (page: Page, coordinates: Coordinates, listSelector: string, getList: boolean) => { try { - if (!getList || (getList && listSelector !== '')) { + if (!getList || listSelector !== '') { const rect = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -852,7 +853,7 @@ interface SelectorResult { * @returns {Promise} */ -export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates, listSelector: string): Promise => { +export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates): Promise => { try { const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { function getNonUniqueSelector(element: HTMLElement): string { @@ -891,7 +892,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - if (listSelector === '') { + // if (listSelector === '') { while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -912,7 +913,7 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates break; } } - } + // } const generalSelector = getSelectorPath(element); return { From 4a9496053177663e4081850cf9db57742899a578 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 16 Dec 2024 08:22:21 +0530 Subject: [PATCH 130/500] feat: push parentSelector --- server/src/workflow-management/selector.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 070a897d..e326f21b 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -968,6 +968,7 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro const childPath = getSelectorPath(child); if (childPath) { selectors.push(childPath); // Add direct child path + selectors.push(parentSelector) selectors = selectors.concat(getAllDescendantSelectors(child)); // Recursively process descendants } } From 23ac1340840a33d307f3730a1ecf0485b06a80b8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 16 Dec 2024 15:55:55 +0530 Subject: [PATCH 131/500] fix: add pair to workflow before decision socket emission --- .../workflow-management/classes/Generator.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 57be015e..9fab04d3 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -140,19 +140,22 @@ export class WorkflowGenerator { socket.on('decision', async ({ pair, actionType, decision }) => { const id = browserPool.getActiveBrowserId(); if (id) { - const activeBrowser = browserPool.getRemoteBrowser(id); - const currentPage = activeBrowser?.getCurrentPage(); - if (decision) { + // const activeBrowser = browserPool.getRemoteBrowser(id); + // const currentPage = activeBrowser?.getCurrentPage(); + if (!decision) { switch (actionType) { case 'customAction': - pair.where.selectors = [this.generatedData.lastUsedSelector]; + // pair.where.selectors = [this.generatedData.lastUsedSelector]; + pair.where.selectors = pair.where.selectors.filter( + (selector: string) => selector !== this.generatedData.lastUsedSelector + ); break; default: break; } } - if (currentPage) { - await this.addPairToWorkflowAndNotifyClient(pair, currentPage); - } + // if (currentPage) { + // await this.addPairToWorkflowAndNotifyClient(pair, currentPage); + // } } }) socket.on('updatePair', (data) => { @@ -360,6 +363,8 @@ export class WorkflowGenerator { }], } + await this.addPairToWorkflowAndNotifyClient(pair, page); + if (this.generatedData.lastUsedSelector) { const elementInfo = await this.getLastUsedSelectorInfo(page, this.generatedData.lastUsedSelector); @@ -372,9 +377,7 @@ export class WorkflowGenerator { innerText: elementInfo.innerText, } }); - } else { - await this.addPairToWorkflowAndNotifyClient(pair, page); - } + } }; /** From 94df79404011d99cc6cc5d80bb1c77208f9abcc5 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 17 Dec 2024 02:48:14 +0530 Subject: [PATCH 132/500] feat: conditionally compute non unique --- server/src/workflow-management/selector.ts | 55 ++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index e326f21b..60f5bdbd 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -20,8 +20,7 @@ export const getElementInformation = async ( getList: boolean ) => { try { - console.log(`List Selector Value From EL INFO: ->> ${listSelector !== '' ? listSelector: 'It is empty'}`); - if (!getList ||listSelector !== '') { + if (!getList || listSelector !== '') { const elementInfo = await page.evaluate( async ({ x, y }) => { const el = document.elementFromPoint(x, y) as HTMLElement; @@ -853,8 +852,10 @@ interface SelectorResult { * @returns {Promise} */ -export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates): Promise => { +export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates, listSelector: string): Promise => { try { + if (!listSelector) { + console.log(`NON UNIQUE: MODE 1`) const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); @@ -920,8 +921,54 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates generalSelector, }; }, coordinates); - return selectors || { generalSelector: '' }; + } else { + console.log(`NON UNIQUE: MODE 2`) + const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); + + if (element.className) { + const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); + if (classes.length > 0) { + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } + } + } + + return selector; + } + + function getSelectorPath(element: HTMLElement | null): string { + const path: string[] = []; + let depth = 0; + const maxDepth = 2; + + while (element && element !== document.body && depth < maxDepth) { + const selector = getNonUniqueSelector(element); + path.unshift(selector); + element = element.parentElement; + depth++; + } + + return path.join(' > '); + } + + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (!originalEl) return null; + + let element = originalEl; + + const generalSelector = getSelectorPath(element); + return { + generalSelector, + }; + }, coordinates); + return selectors || { generalSelector: '' }; + } + } catch (error) { console.error('Error in getNonUniqueSelectors:', error); return { generalSelector: '' }; From 52b767188eedd3ef3c3053a3e50d054fb9b35e44 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 17 Dec 2024 02:48:38 +0530 Subject: [PATCH 133/500] feat: !push parentSelector --- server/src/workflow-management/selector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 60f5bdbd..9c62139b 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -1015,7 +1015,6 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro const childPath = getSelectorPath(child); if (childPath) { selectors.push(childPath); // Add direct child path - selectors.push(parentSelector) selectors = selectors.concat(getAllDescendantSelectors(child)); // Recursively process descendants } } From 647cd62e32fba8fc55084dbacfef6029f071d076 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 17 Dec 2024 11:37:05 +0530 Subject: [PATCH 134/500] feat: add listSelector param --- server/src/workflow-management/classes/Generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 2ab54753..31775261 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -543,7 +543,7 @@ export class WorkflowGenerator { private generateSelector = async (page: Page, coordinates: Coordinates, action: ActionType) => { const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); const selectorBasedOnCustomAction = (this.getList === true) - ? await getNonUniqueSelectors(page, coordinates) + ? await getNonUniqueSelectors(page, coordinates, this.listSelector) : await getSelectors(page, coordinates); const bestSelector = getBestSelectorForAction( From a9dc4c8f4ceeca8abe45268331b1369d4a1cbbb9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 17 Dec 2024 12:21:56 +0530 Subject: [PATCH 135/500] feat: add conditional check to collect matching classes --- maxun-core/src/browserSide/scraper.js | 93 ++++++++++++++++++--------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index 09b6578b..a2009d78 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -265,41 +265,72 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, const scrapedData = []; while (scrapedData.length < limit) { - // Get all parent elements matching the listSelector - const parentElements = Array.from(document.querySelectorAll(listSelector)); + let parentElements = Array.from(document.querySelectorAll(listSelector)); + + // If we only got one element or none, try a more generic approach + if (limit > 1 && parentElements.length <= 1) { + const [containerSelector, _] = listSelector.split('>').map(s => s.trim()); + const container = document.querySelector(containerSelector); + + if (container) { + const allChildren = Array.from(container.children); + + const firstMatch = document.querySelector(listSelector); + if (firstMatch) { + // Get classes from the first matching element + const firstMatchClasses = Array.from(firstMatch.classList); + + // Find similar elements by matching most of their classes + parentElements = allChildren.filter(element => { + const elementClasses = Array.from(element.classList); - // Iterate through each parent element - for (const parent of parentElements) { - if (scrapedData.length >= limit) break; - const record = {}; - - // For each field, select the corresponding element within the parent - for (const [label, { selector, attribute }] of Object.entries(fields)) { - const fieldElement = parent.querySelector(selector); - - if (fieldElement) { - if (attribute === 'innerText') { - record[label] = fieldElement.innerText.trim(); - } else if (attribute === 'innerHTML') { - record[label] = fieldElement.innerHTML.trim(); - } else if (attribute === 'src') { - // Handle relative 'src' URLs - const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, window.location.origin).href : null; - } else if (attribute === 'href') { - // Handle relative 'href' URLs - const href = fieldElement.getAttribute('href'); - record[label] = href ? new URL(href, window.location.origin).href : null; - } else { - record[label] = fieldElement.getAttribute(attribute); + // Element should share at least 70% of classes with the first match + const commonClasses = firstMatchClasses.filter(cls => + elementClasses.includes(cls)); + return commonClasses.length >= Math.floor(firstMatchClasses.length * 0.7); + }); + } } - } } - scrapedData.push(record); - } + + // Iterate through each parent element + for (const parent of parentElements) { + if (scrapedData.length >= limit) break; + const record = {}; + + // For each field, select the corresponding element within the parent + for (const [label, { selector, attribute }] of Object.entries(fields)) { + const fieldElement = parent.querySelector(selector); + + if (fieldElement) { + if (attribute === 'innerText') { + record[label] = fieldElement.innerText.trim(); + } else if (attribute === 'innerHTML') { + record[label] = fieldElement.innerHTML.trim(); + } else if (attribute === 'src') { + // Handle relative 'src' URLs + const src = fieldElement.getAttribute('src'); + record[label] = src ? new URL(src, window.location.origin).href : null; + } else if (attribute === 'href') { + // Handle relative 'href' URLs + const href = fieldElement.getAttribute('href'); + record[label] = href ? new URL(href, window.location.origin).href : null; + } else { + record[label] = fieldElement.getAttribute(attribute); + } + } + } + scrapedData.push(record); + } + + // If we've processed all available elements and still haven't reached the limit, + // break to avoid infinite loop + if (parentElements.length === 0 || scrapedData.length >= parentElements.length) { + break; + } } - return scrapedData - }; + return scrapedData; +}; /** From e34cfda770d9ff8c0c17cb6418c085ade066e7ec Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 17 Dec 2024 21:55:24 +0530 Subject: [PATCH 136/500] fix: skip click action if selector not visible --- maxun-core/src/interpret.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index d1cc8318..ef06d1ab 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -506,7 +506,11 @@ export default class Interpreter extends EventEmitter { try { await executeAction(invokee, methodName, step.args); } catch (error) { - await executeAction(invokee, methodName, [step.args[0], { force: true }]); + try{ + await executeAction(invokee, methodName, [step.args[0], { force: true }]); + } catch (error) { + continue + } } } else { await executeAction(invokee, methodName, step.args); From 0783cbc1c5679c88ea6973a4a0dd831dcae7b8ba Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:17:50 +0530 Subject: [PATCH 137/500] feat: add date selection handler --- .../workflow-management/classes/Generator.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 213a0e86..06eac494 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -1,4 +1,4 @@ -import { Action, ActionType, Coordinates, TagName } from "../../types"; +import { Action, ActionType, Coordinates, TagName, DatePickerEventData } from "../../types"; import { WhereWhatPair, WorkflowFile } from 'maxun-core'; import logger from "../../logger"; import { Socket } from "socket.io"; @@ -255,6 +255,25 @@ export class WorkflowGenerator { logger.log('info', `Workflow emitted`); }; + public onDateSelection = async (page: Page, data: DatePickerEventData) => { + const { selector, value } = data; + + try { + await page.fill(selector, value); + } catch (error) { + console.error("Failed to fill date value:", error); + } + + const pair: WhereWhatPair = { + where: { url: this.getBestUrl(page.url()) }, + what: [{ + action: 'fill', + args: [selector, value], + }], + }; + + await this.addPairToWorkflowAndNotifyClient(pair, page); + }; /** * Generates a pair for the click event. @@ -266,6 +285,22 @@ export class WorkflowGenerator { let where: WhereWhatPair["where"] = { url: this.getBestUrl(page.url()) }; const selector = await this.generateSelector(page, coordinates, ActionType.Click); logger.log('debug', `Element's selector: ${selector}`); + + // Check if clicked element is a date input + const isDateInput = await page.evaluate(({x, y}) => { + const element = document.elementFromPoint(x, y); + return element instanceof HTMLInputElement && element.type === 'date'; + }, coordinates); + + if (isDateInput) { + // Notify client to show datepicker overlay + this.socket.emit('showDatePicker', { + coordinates, + selector + }); + return; + } + //const element = await getElementMouseIsOver(page, coordinates); //logger.log('debug', `Element: ${JSON.stringify(element, null, 2)}`); if (selector) { From 7ac79dc31c7ffa26d94d4b58f9d50088e238fca7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:19:05 +0530 Subject: [PATCH 138/500] feat: add date selection event handlers --- .../src/browser-management/inputHandlers.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/server/src/browser-management/inputHandlers.ts b/server/src/browser-management/inputHandlers.ts index d6902b3f..2e722e9d 100644 --- a/server/src/browser-management/inputHandlers.ts +++ b/server/src/browser-management/inputHandlers.ts @@ -6,7 +6,7 @@ import { Socket } from 'socket.io'; import logger from "../logger"; -import { Coordinates, ScrollDeltas, KeyboardInput } from '../types'; +import { Coordinates, ScrollDeltas, KeyboardInput, DatePickerEventData } from '../types'; import { browserPool } from "../server"; import { WorkflowGenerator } from "../workflow-management/classes/Generator"; import { Page } from "playwright"; @@ -223,6 +223,23 @@ const handleKeydown = async (generator: WorkflowGenerator, page: Page, { key, co logger.log('debug', `Key ${key} pressed`); }; +/** + * Handles the date selection event. + * @param generator - the workflow generator {@link Generator} + * @param page - the active page of the remote browser + * @param data - the data of the date selection event {@link DatePickerEventData} + * @category BrowserManagement + */ +const handleDateSelection = async (generator: WorkflowGenerator, page: Page, data: DatePickerEventData) => { + await generator.onDateSelection(page, data); + logger.log('debug', `Date ${data.value} selected`); +} + +const onDateSelection = async (data: DatePickerEventData) => { + logger.log('debug', 'Handling date selection event emitted from client'); + await handleWrapper(handleDateSelection, data); +} + /** * A wrapper function for handling the keyup event. * @param keyboardInput - the keyboard input of the keyup event @@ -378,6 +395,7 @@ const registerInputHandlers = (socket: Socket) => { socket.on("input:refresh", onRefresh); socket.on("input:back", onGoBack); socket.on("input:forward", onGoForward); + socket.on("input:date", onDateSelection); socket.on("action", onGenerateAction); }; From 7eea077e70124cc9a24faaa768d7752305158685 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:21:05 +0530 Subject: [PATCH 139/500] feat: add interface to hanle date picker event data --- server/src/types/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/types/index.ts b/server/src/types/index.ts index 4fe761f1..f2e327ef 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -20,6 +20,16 @@ export interface Coordinates { y: number; } +/** + * interface to handle date picker events. + * @category Types + */ +export interface DatePickerEventData { + coordinates: Coordinates; + selector: string; + value: string; +} + /** * Holds the deltas of a wheel/scroll event. * @category Types From ec4d1acfa27c9c0ff5ef1fa6ae5415ecad6ebf15 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:22:51 +0530 Subject: [PATCH 140/500] feat: trigger socket event to display date picker --- src/components/atoms/canvas.tsx | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 1dd88e19..84d6a620 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -3,6 +3,7 @@ import { useSocketStore } from '../../context/socket'; import { getMappedCoordinates } from "../../helpers/inputHelpers"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; +import DatePicker from './DatePicker'; interface CreateRefCallback { (ref: React.RefObject): void; @@ -31,6 +32,11 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { const getTextRef = useRef(getText); const getListRef = useRef(getList); + const [datePickerInfo, setDatePickerInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); + const notifyLastAction = (action: string) => { if (lastAction !== action) { setLastAction(action); @@ -44,6 +50,28 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { getListRef.current = getList; }, [getText, getList]); + useEffect(() => { + if (socket) { + socket.on('showDatePicker', (info: {coordinates: Coordinates, selector: string}) => { + setDatePickerInfo(info); + }); + + return () => { + socket.off('showDatePicker'); + }; + } + }, [socket]); + + const handleDateSelect = (value: string) => { + if (socket && datePickerInfo) { + socket.emit('input:date', { + selector: datePickerInfo.selector, + value + }); + setDatePickerInfo(null); + } + }; + const onMouseEvent = useCallback((event: MouseEvent) => { if (socket && canvasRef.current) { // Get the canvas bounding rectangle @@ -146,6 +174,13 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { width={900} style={{ display: 'block' }} /> + {datePickerInfo && ( + setDatePickerInfo(null)} + /> + )} ); From 9ee54d118b522d87c7e37791e69f3a6496f53489 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:23:56 +0530 Subject: [PATCH 141/500] feat: rm onHandleSelct callback function --- src/components/atoms/canvas.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 84d6a620..13966abd 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -62,16 +62,6 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { } }, [socket]); - const handleDateSelect = (value: string) => { - if (socket && datePickerInfo) { - socket.emit('input:date', { - selector: datePickerInfo.selector, - value - }); - setDatePickerInfo(null); - } - }; - const onMouseEvent = useCallback((event: MouseEvent) => { if (socket && canvasRef.current) { // Get the canvas bounding rectangle From 2e301924226fda12ce8fea82828b3eaa4c2976c6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 18 Dec 2024 18:24:51 +0530 Subject: [PATCH 142/500] feat: add date picker component to input date --- src/components/atoms/DatePicker.tsx | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/components/atoms/DatePicker.tsx diff --git a/src/components/atoms/DatePicker.tsx b/src/components/atoms/DatePicker.tsx new file mode 100644 index 00000000..30d3b869 --- /dev/null +++ b/src/components/atoms/DatePicker.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { useSocketStore } from '../../context/socket'; +import { Coordinates } from './canvas'; + +interface DatePickerProps { + coordinates: Coordinates; + selector: string; + onClose: () => void; +} + +const DatePicker: React.FC = ({ coordinates, selector, onClose }) => { + const { socket } = useSocketStore(); + const [selectedDate, setSelectedDate] = useState(''); + + const handleDateChange = (e: React.ChangeEvent) => { + setSelectedDate(e.target.value); + }; + + const handleConfirm = () => { + if (socket && selectedDate) { + socket.emit('input:date', { + selector, + value: selectedDate + }); + onClose(); + } + }; + + return ( +
+
+ +
+ + +
+
+
+ ); +}; + +export default DatePicker; \ No newline at end of file From 14079fa0f89029502e6c3671aab0d9232e17e5a9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 12:13:17 +0530 Subject: [PATCH 143/500] feat: date input check using element information --- server/src/workflow-management/classes/Generator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 06eac494..96645de0 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -286,11 +286,11 @@ export class WorkflowGenerator { const selector = await this.generateSelector(page, coordinates, ActionType.Click); logger.log('debug', `Element's selector: ${selector}`); + const elementInfo = await getElementInformation(page, coordinates, '', false); + console.log("Element info: ", elementInfo); + // Check if clicked element is a date input - const isDateInput = await page.evaluate(({x, y}) => { - const element = document.elementFromPoint(x, y); - return element instanceof HTMLInputElement && element.type === 'date'; - }, coordinates); + const isDateInput = elementInfo?.tagName === 'INPUT' && elementInfo?.attributes?.type === 'date'; if (isDateInput) { // Notify client to show datepicker overlay From a6ed8c67b8b19e7d684e89f4e2985947a48d4522 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 12:14:08 +0530 Subject: [PATCH 144/500] feat: check for select type and emit dropdown socket event --- .../workflow-management/classes/Generator.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 96645de0..a221a47b 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -289,6 +289,45 @@ export class WorkflowGenerator { const elementInfo = await getElementInformation(page, coordinates, '', false); console.log("Element info: ", elementInfo); + // Check if clicked element is a select dropdown + const isDropdown = elementInfo?.tagName === 'SELECT'; + + if (isDropdown && elementInfo.innerHTML) { + // Parse options from innerHTML + const options = elementInfo.innerHTML + .split(' { + const valueMatch = optionHtml.match(/value="([^"]*)"/); + const disabledMatch = optionHtml.includes('disabled="disabled"'); + const selectedMatch = optionHtml.includes('selected="selected"'); + + // Extract text content between > and + const textMatch = optionHtml.match(/>([^<]*) Date: Thu, 19 Dec 2024 12:23:13 +0530 Subject: [PATCH 145/500] feat: add dropdown selection action pair to workflow --- .../workflow-management/classes/Generator.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index a221a47b..3395e9a1 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -275,6 +275,26 @@ export class WorkflowGenerator { await this.addPairToWorkflowAndNotifyClient(pair, page); }; + public onDropdownSelection = async (page: Page, data: { selector: string, value: string }) => { + const { selector, value } = data; + + try { + await page.selectOption(selector, value); + } catch (error) { + console.error("Failed to fill date value:", error); + } + + const pair: WhereWhatPair = { + where: { url: this.getBestUrl(page.url()) }, + what: [{ + action: 'selectOption', + args: [selector, value], + }], + }; + + await this.addPairToWorkflowAndNotifyClient(pair, page); + }; + /** * Generates a pair for the click event. * @param coordinates The coordinates of the click event. From d8b5ae4113d5a4201680e4e23ff6721708ec0199 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 12:24:19 +0530 Subject: [PATCH 146/500] feat: add dropdown selection handler functions and register socket event --- server/src/browser-management/inputHandlers.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/browser-management/inputHandlers.ts b/server/src/browser-management/inputHandlers.ts index 2e722e9d..4b37480f 100644 --- a/server/src/browser-management/inputHandlers.ts +++ b/server/src/browser-management/inputHandlers.ts @@ -240,6 +240,16 @@ const onDateSelection = async (data: DatePickerEventData) => { await handleWrapper(handleDateSelection, data); } +const handleDropdownSelection = async (generator: WorkflowGenerator, page: Page, data: { selector: string, value: string }) => { + await generator.onDropdownSelection(page, data); + logger.log('debug', `Dropdown value ${data.value} selected`); +} + +const onDropdownSelection = async (data: { selector: string, value: string }) => { + logger.log('debug', 'Handling dropdown selection event emitted from client'); + await handleWrapper(handleDropdownSelection, data); +} + /** * A wrapper function for handling the keyup event. * @param keyboardInput - the keyboard input of the keyup event @@ -396,6 +406,7 @@ const registerInputHandlers = (socket: Socket) => { socket.on("input:back", onGoBack); socket.on("input:forward", onGoForward); socket.on("input:date", onDateSelection); + socket.on("input:dropdown", onDropdownSelection); socket.on("action", onGenerateAction); }; From 7bd7a84173d47be16a0f828a1067e7fca7a2ed8d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 14:04:05 +0530 Subject: [PATCH 147/500] feat: tigger socket event to display dropdown --- src/components/atoms/canvas.tsx | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 13966abd..fc778963 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -4,6 +4,7 @@ import { getMappedCoordinates } from "../../helpers/inputHelpers"; import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; import DatePicker from './DatePicker'; +import Dropdown from './Dropdown'; interface CreateRefCallback { (ref: React.RefObject): void; @@ -37,6 +38,17 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { selector: string; } | null>(null); + const [dropdownInfo, setDropdownInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + options: Array<{ + value: string; + text: string; + disabled: boolean; + selected: boolean; + }>; + } | null>(null); + const notifyLastAction = (action: string) => { if (lastAction !== action) { setLastAction(action); @@ -56,8 +68,22 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { setDatePickerInfo(info); }); + socket.on('showDropdown', (info: { + coordinates: Coordinates, + selector: string, + options: Array<{ + value: string; + text: string; + disabled: boolean; + selected: boolean; + }>; + }) => { + setDropdownInfo(info); + }); + return () => { socket.off('showDatePicker'); + socket.off('showDropdown'); }; } }, [socket]); @@ -171,6 +197,14 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { onClose={() => setDatePickerInfo(null)} /> )} + {dropdownInfo && ( + setDropdownInfo(null)} + /> + )} ); From 13b92ee5dce7d7e33e3e0f60c608e202187f01fb Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 14:05:02 +0530 Subject: [PATCH 148/500] feat: add dropdown component to input dropdown --- src/components/atoms/Dropdown.tsx | 85 +++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/components/atoms/Dropdown.tsx diff --git a/src/components/atoms/Dropdown.tsx b/src/components/atoms/Dropdown.tsx new file mode 100644 index 00000000..c7ead64b --- /dev/null +++ b/src/components/atoms/Dropdown.tsx @@ -0,0 +1,85 @@ +import React, { useState } from 'react'; +import { useSocketStore } from '../../context/socket'; +import { Coordinates } from './canvas'; + +interface DropdownProps { + coordinates: Coordinates; + selector: string; + options: Array<{ + value: string; + text: string; + disabled: boolean; + selected: boolean; + }>; + onClose: () => void; +} + +const Dropdown = ({ coordinates, selector, options, onClose }: DropdownProps) => { + const { socket } = useSocketStore(); + const [hoveredIndex, setHoveredIndex] = useState(null); + + const handleSelect = (value: string) => { + if (socket) { + socket.emit('input:dropdown', { selector, value }); + } + onClose(); + }; + + const containerStyle: React.CSSProperties = { + position: 'absolute', + left: coordinates.x, + top: coordinates.y, + zIndex: 1000, + width: '200px', + backgroundColor: 'white', + border: '1px solid rgb(169, 169, 169)', + boxShadow: '0 2px 4px rgba(0,0,0,0.15)', + }; + + const scrollContainerStyle: React.CSSProperties = { + maxHeight: '180px', + overflowY: 'auto', + overflowX: 'hidden', + }; + + const getOptionStyle = (option: any, index: number): React.CSSProperties => ({ + fontSize: '13.333px', + lineHeight: '18px', + padding: '0 3px', + cursor: option.disabled ? 'default' : 'default', + backgroundColor: hoveredIndex === index ? '#0078D7' : + option.selected ? '#0078D7' : + option.disabled ? '#f8f8f8' : 'white', + color: (hoveredIndex === index || option.selected) ? 'white' : + option.disabled ? '#a0a0a0' : 'black', + userSelect: 'none', + }); + + return ( +
+
e.stopPropagation()} + > +
+ {options.map((option, index) => ( +
!option.disabled && setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + onClick={() => !option.disabled && handleSelect(option.value)} + > + {option.text} +
+ ))} +
+
+
+ ); +}; + +export default Dropdown; \ No newline at end of file From 947a6b75cb9d2d431d23ecfd98ec20cc1a27e855 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 16:16:47 +0530 Subject: [PATCH 149/500] feat: check for time input field and emit socket event --- .../workflow-management/classes/Generator.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 3395e9a1..9ff4922e 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -295,6 +295,26 @@ export class WorkflowGenerator { await this.addPairToWorkflowAndNotifyClient(pair, page); }; + public onTimeSelection = async (page: Page, data: { selector: string, value: string }) => { + const { selector, value } = data; + + try { + await page.fill(selector, value); + } catch (error) { + console.error("Failed to set time value:", error); + } + + const pair: WhereWhatPair = { + where: { url: this.getBestUrl(page.url()) }, + what: [{ + action: 'fill', + args: [selector, value], + }], + }; + + await this.addPairToWorkflowAndNotifyClient(pair, page); + }; + /** * Generates a pair for the click event. * @param coordinates The coordinates of the click event. @@ -360,6 +380,16 @@ export class WorkflowGenerator { return; } + const isTimeInput = elementInfo?.tagName === 'INPUT' && elementInfo?.attributes?.type === 'time'; + + if (isTimeInput) { + this.socket.emit('showTimePicker', { + coordinates, + selector + }); + return; + } + //const element = await getElementMouseIsOver(page, coordinates); //logger.log('debug', `Element: ${JSON.stringify(element, null, 2)}`); if (selector) { From 0b2d099dc0348af577762aa2588250e2c8c6202a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 16:25:48 +0530 Subject: [PATCH 150/500] feat: add time selection event handlers --- server/src/browser-management/inputHandlers.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/browser-management/inputHandlers.ts b/server/src/browser-management/inputHandlers.ts index 4b37480f..982e18de 100644 --- a/server/src/browser-management/inputHandlers.ts +++ b/server/src/browser-management/inputHandlers.ts @@ -250,6 +250,16 @@ const onDropdownSelection = async (data: { selector: string, value: string }) => await handleWrapper(handleDropdownSelection, data); } +const handleTimeSelection = async (generator: WorkflowGenerator, page: Page, data: { selector: string, value: string }) => { + await generator.onTimeSelection(page, data); + logger.log('debug', `Time value ${data.value} selected`); +} + +const onTimeSelection = async (data: { selector: string, value: string }) => { + logger.log('debug', 'Handling time selection event emitted from client'); + await handleWrapper(handleTimeSelection, data); +} + /** * A wrapper function for handling the keyup event. * @param keyboardInput - the keyboard input of the keyup event @@ -407,6 +417,7 @@ const registerInputHandlers = (socket: Socket) => { socket.on("input:forward", onGoForward); socket.on("input:date", onDateSelection); socket.on("input:dropdown", onDropdownSelection); + socket.on("input:time", onTimeSelection); socket.on("action", onGenerateAction); }; From 66f3ccd34fa8c06da8dd35b5277e093b318109aa Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 16:26:23 +0530 Subject: [PATCH 151/500] trigger socket event to display time picker --- src/components/atoms/canvas.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index fc778963..77128a65 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -5,6 +5,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { useActionContext } from '../../context/browserActions'; import DatePicker from './DatePicker'; import Dropdown from './Dropdown'; +import TimePicker from './TimePicker'; interface CreateRefCallback { (ref: React.RefObject): void; @@ -49,6 +50,11 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { }>; } | null>(null); + const [timePickerInfo, setTimePickerInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); + const notifyLastAction = (action: string) => { if (lastAction !== action) { setLastAction(action); @@ -81,6 +87,10 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { setDropdownInfo(info); }); + socket.on('showTimePicker', (info: {coordinates: Coordinates, selector: string}) => { + setTimePickerInfo(info); + }); + return () => { socket.off('showDatePicker'); socket.off('showDropdown'); @@ -205,6 +215,13 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { onClose={() => setDropdownInfo(null)} /> )} + {timePickerInfo && ( + setTimePickerInfo(null)} + /> + )} ); From a97837d8b8cc20fcc943362d2745fff9d21c662d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 19 Dec 2024 16:26:57 +0530 Subject: [PATCH 152/500] feat: add time picker component to input time --- src/components/atoms/TimePicker.tsx | 130 ++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/components/atoms/TimePicker.tsx diff --git a/src/components/atoms/TimePicker.tsx b/src/components/atoms/TimePicker.tsx new file mode 100644 index 00000000..31353c7a --- /dev/null +++ b/src/components/atoms/TimePicker.tsx @@ -0,0 +1,130 @@ +import React, { useState } from 'react'; +import { useSocketStore } from '../../context/socket'; +import { Coordinates } from './canvas'; + +interface TimePickerProps { + coordinates: Coordinates; + selector: string; + onClose: () => void; +} + +const TimePicker = ({ coordinates, selector, onClose }: TimePickerProps) => { + const { socket } = useSocketStore(); + const [hoveredHour, setHoveredHour] = useState(null); + const [hoveredMinute, setHoveredMinute] = useState(null); + const [selectedHour, setSelectedHour] = useState(null); + const [selectedMinute, setSelectedMinute] = useState(null); + + const handleHourSelect = (hour: number) => { + setSelectedHour(hour); + // If minute is already selected, complete the selection + if (selectedMinute !== null) { + const formattedHour = hour.toString().padStart(2, '0'); + const formattedMinute = selectedMinute.toString().padStart(2, '0'); + if (socket) { + socket.emit('input:time', { + selector, + value: `${formattedHour}:${formattedMinute}` + }); + } + onClose(); + } + }; + + const handleMinuteSelect = (minute: number) => { + setSelectedMinute(minute); + // If hour is already selected, complete the selection + if (selectedHour !== null) { + const formattedHour = selectedHour.toString().padStart(2, '0'); + const formattedMinute = minute.toString().padStart(2, '0'); + if (socket) { + socket.emit('input:time', { + selector, + value: `${formattedHour}:${formattedMinute}` + }); + } + onClose(); + } + }; + + const containerStyle: React.CSSProperties = { + position: 'absolute', + left: coordinates.x, + top: coordinates.y, + zIndex: 1000, + display: 'flex', + backgroundColor: 'white', + border: '1px solid rgb(169, 169, 169)', + boxShadow: '0 2px 4px rgba(0,0,0,0.15)', + }; + + const columnStyle: React.CSSProperties = { + width: '60px', + maxHeight: '180px', + overflowY: 'auto', + overflowX: 'hidden', + borderRight: '1px solid rgb(169, 169, 169)', + }; + + const getOptionStyle = (value: number, isHour: boolean): React.CSSProperties => { + const isHovered = isHour ? hoveredHour === value : hoveredMinute === value; + const isSelected = isHour ? selectedHour === value : selectedMinute === value; + + return { + fontSize: '13.333px', + lineHeight: '18px', + padding: '0 3px', + cursor: 'default', + backgroundColor: isSelected ? '#0078D7' : isHovered ? '#0078D7' : 'white', + color: (isSelected || isHovered) ? 'white' : 'black', + userSelect: 'none', + }; + }; + + const hours = Array.from({ length: 24 }, (_, i) => i); + const minutes = Array.from({ length: 60 }, (_, i) => i); + + return ( +
+
e.stopPropagation()} + > + {/* Hours column */} +
+ {hours.map((hour) => ( +
setHoveredHour(hour)} + onMouseLeave={() => setHoveredHour(null)} + onClick={() => handleHourSelect(hour)} + > + {hour.toString().padStart(2, '0')} +
+ ))} +
+ + {/* Minutes column */} +
+ {minutes.map((minute) => ( +
setHoveredMinute(minute)} + onMouseLeave={() => setHoveredMinute(null)} + onClick={() => handleMinuteSelect(minute)} + > + {minute.toString().padStart(2, '0')} +
+ ))} +
+
+
+ ); +}; + +export default TimePicker; \ No newline at end of file From 60b901a1a06c3240040dfefcbd9f861e506d71c8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 13:55:48 +0530 Subject: [PATCH 153/500] feat: handle select tags --- server/src/workflow-management/selector.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 9c62139b..e1dbf4db 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -53,7 +53,14 @@ export const getElementInformation = async ( info.url = (element as HTMLAnchorElement).href; info.innerText = element.innerText ?? ''; } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; + info.imageUrl = (element as HTMLImageElement).src; + } else if (element?.tagName === 'SELECT') { + const selectElement = element as HTMLSelectElement; + info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; + info.attributes = { + ...info.attributes, + selectedValue: selectElement.value, + }; } else { info.hasOnlyText = element?.children?.length === 0 && element?.innerText?.length > 0; From 0ff1f4ca0736d97a4236df88d302367e114541b3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 14:47:39 +0530 Subject: [PATCH 154/500] feat: add lang support for german --- public/locales/de.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index d12b2489..86f1ceb2 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -46,5 +46,15 @@ "apikey": "API-Schlüssel", "feedback": "Maxun Cloud beitreten", "apidocs": "API-Dokumentation" + }, + "runstable": { + "runs": "Alle Ausführungen", + "runStatus": "Status", + "runName": "Name", + "startedAt": "Gestartet am", + "finishedAt": "Beendet am", + "delete": "Löschen", + "settings": "Einstellungen", + "search": "Ausführungen suchen..." } } From 082df9102cd543e9785156a382a74d9b97dbd682 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 14:48:12 +0530 Subject: [PATCH 155/500] feat: add lang support for spanish --- public/locales/es.json | 70 +++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/public/locales/es.json b/public/locales/es.json index 00589622..4e717ce2 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -1,13 +1,61 @@ { - "app": { - "name": "Maxun", - "version": "beta" + "login": { + "title": "¡Bienvenido de nuevo!", + "email": "Correo electrónico", + "password": "Contraseña", + "button": "Iniciar sesión", + "loading": "Cargando", + "register_prompt": "¿No tienes una cuenta?", + "register_link": "Registrarse", + "welcome_notification": "¡Bienvenido a Maxun!", + "error_notification": "Error al iniciar sesión. Por favor, inténtalo de nuevo." + }, + "register": { + "title": "Crear cuenta", + "email": "Correo electrónico", + "password": "Contraseña", + "button": "Registrarse", + "loading": "Cargando", + "register_prompt": "¿Ya tienes una cuenta?", + "login_link": "Iniciar sesión", + "welcome_notification": "¡Bienvenido a Maxun!", + "error_notification": "Error en el registro. Por favor, inténtalo de nuevo." + }, + "recordingtable": { + "run": "Ejecutar", + "name": "Nombre", + "schedule": "Programar", + "integrate": "Integrar", + "settings": "Ajustes", + "options": "Opciones", + "heading": "Mis Robots", + "new": "Crear Robot", + "modal": { + "title": "Ingresa la URL", + "label": "URL", + "button": "Comenzar grabación" }, - "login": { - "title": "¡Bienvenido de nuevo!", - "email": "Correo electrónico", - "password": "Contraseña", - "button": "Iniciar sesión", - "register_prompt": "¿No tienes una cuenta? Regístrate" - } - } \ No newline at end of file + "edit": "Editar", + "delete": "Eliminar", + "duplicate": "Duplicar", + "search": "Buscar robots..." + }, + "mainmenu": { + "recordings": "Robots", + "runs": "Ejecuciones", + "proxy": "Proxy", + "apikey": "Clave API", + "feedback": "Unirse a Maxun Cloud", + "apidocs": "Documentación API" + }, + "runstable": { + "runs": "Todas las ejecuciones", + "runStatus": "Estado", + "runName": "Nombre", + "startedAt": "Iniciado el", + "finishedAt": "Finalizado el", + "delete": "Eliminar", + "settings": "Ajustes", + "search": "Buscar ejecuciones..." + } +} \ No newline at end of file From 65266c2af659346ca5bd63e87d0b2e12cd568b1a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 14:48:33 +0530 Subject: [PATCH 156/500] feat: add lang support for chinese --- public/locales/zh.json | 70 +++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index 7fa8bb60..31bce494 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -1,13 +1,61 @@ { - "app": { - "name": "Maxun", - "version": "beta" + "login": { + "title": "欢迎回来!", + "email": "电子邮箱", + "password": "密码", + "button": "登录", + "loading": "加载中", + "register_prompt": "还没有账号?", + "register_link": "注册", + "welcome_notification": "欢迎使用 Maxun!", + "error_notification": "登录失败。请重试。" + }, + "register": { + "title": "注册账号", + "email": "电子邮箱", + "password": "密码", + "button": "注册", + "loading": "加载中", + "register_prompt": "已有账号?", + "login_link": "登录", + "welcome_notification": "欢迎使用 Maxun!", + "error_notification": "注册失败。请重试。" + }, + "recordingtable": { + "run": "运行", + "name": "名称", + "schedule": "计划", + "integrate": "集成", + "settings": "设置", + "options": "选项", + "heading": "我的机器人", + "new": "创建机器人", + "modal": { + "title": "输入URL", + "label": "URL", + "button": "开始录制" }, - "login": { - "title": "欢迎回来!", - "email": "电子邮件", - "password": "密码", - "button": "登录", - "register_prompt": "没有账号?注册" - } - } \ No newline at end of file + "edit": "编辑", + "delete": "删除", + "duplicate": "复制", + "search": "搜索机器人..." + }, + "mainmenu": { + "recordings": "机器人", + "runs": "运行记录", + "proxy": "代理", + "apikey": "API密钥", + "feedback": "加入 Maxun Cloud", + "apidocs": "API文档" + }, + "runstable": { + "runs": "所有运行记录", + "runStatus": "状态", + "runName": "名称", + "startedAt": "开始时间", + "finishedAt": "结束时间", + "delete": "删除", + "settings": "设置", + "search": "搜索运行记录..." + } +} \ No newline at end of file From 39850df42e096e10dffdf0b4b6acfe4c91c0f40f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 14:49:50 +0530 Subject: [PATCH 157/500] feat: rm arabic lang option --- src/components/molecules/NavBar.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index ead395c5..0658a766 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -296,14 +296,14 @@ export const NavBar: React.FC = ({ > 日本語 - { changeLanguage("ar"); handleMenuClose(); }} > العربية - + */} { changeLanguage("zh"); @@ -318,8 +318,7 @@ export const NavBar: React.FC = ({ handleMenuClose(); }} > - German - + Deutsch @@ -373,14 +372,14 @@ export const NavBar: React.FC = ({ > 日本語 - { changeLanguage("ar"); handleMenuClose(); }} > العربية - + */} { changeLanguage("zh"); @@ -389,6 +388,14 @@ export const NavBar: React.FC = ({ > 中文 + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Deutsch + )} From 7860d404449f88b9c503cdd9b96433789bd167c9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:17:49 +0530 Subject: [PATCH 158/500] feat: handle input type time --- server/src/workflow-management/selector.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index e1dbf4db..0d17c9b4 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -61,6 +61,8 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; + } else if (element?.tagName === 'INPUT' && element?.type === 'time') { + info.innerText = element.value; } else { info.hasOnlyText = element?.children?.length === 0 && element?.innerText?.length > 0; From 4b2c0721392e1679a56e66c8b2e33111bc792e24 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:18:06 +0530 Subject: [PATCH 159/500] feat: assign proper types --- server/src/workflow-management/selector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 0d17c9b4..f1259abc 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -61,8 +61,8 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; - } else if (element?.tagName === 'INPUT' && element?.type === 'time') { - info.innerText = element.value; + } else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time') { + info.innerText = (element as HTMLInputElement).value; } else { info.hasOnlyText = element?.children?.length === 0 && element?.innerText?.length > 0; From 81eb32254c1cdea4f977f36bf362959d6ff611f1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:23:44 +0530 Subject: [PATCH 160/500] feat: handle input type date --- server/src/workflow-management/selector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index f1259abc..6770c18b 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -61,7 +61,7 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; - } else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time') { + } else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date') { info.innerText = (element as HTMLInputElement).value; } else { info.hasOnlyText = element?.children?.length === 0 && From 889107d892dad91d705746d22e425674989f5541 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:23:59 +0530 Subject: [PATCH 161/500] chore: lint --- server/src/workflow-management/selector.ts | 158 ++++++++++----------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 6770c18b..240f8921 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -53,16 +53,16 @@ export const getElementInformation = async ( info.url = (element as HTMLAnchorElement).href; info.innerText = element.innerText ?? ''; } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else if (element?.tagName === 'SELECT') { - const selectElement = element as HTMLSelectElement; - info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; - info.attributes = { + info.imageUrl = (element as HTMLImageElement).src; + } else if (element?.tagName === 'SELECT') { + const selectElement = element as HTMLSelectElement; + info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; + info.attributes = { ...info.attributes, selectedValue: selectElement.value, - }; + }; } else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date') { - info.innerText = (element as HTMLInputElement).value; + info.innerText = (element as HTMLInputElement).value; } else { info.hasOnlyText = element?.children?.length === 0 && element?.innerText?.length > 0; @@ -865,118 +865,118 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates try { if (!listSelector) { console.log(`NON UNIQUE: MODE 1`) - const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { - function getNonUniqueSelector(element: HTMLElement): string { - let selector = element.tagName.toLowerCase(); + const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); - if (element.className) { - const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); - if (classes.length > 0) { - const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); - if (validClasses.length > 0) { - selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + if (element.className) { + const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); + if (classes.length > 0) { + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } } } + + return selector; } - return selector; - } + function getSelectorPath(element: HTMLElement | null): string { + const path: string[] = []; + let depth = 0; + const maxDepth = 2; - function getSelectorPath(element: HTMLElement | null): string { - const path: string[] = []; - let depth = 0; - const maxDepth = 2; + while (element && element !== document.body && depth < maxDepth) { + const selector = getNonUniqueSelector(element); + path.unshift(selector); + element = element.parentElement; + depth++; + } - while (element && element !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(element); - path.unshift(selector); - element = element.parentElement; - depth++; + return path.join(' > '); } - return path.join(' > '); - } + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (!originalEl) return null; - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (!originalEl) return null; + let element = originalEl; - let element = originalEl; - - // if (listSelector === '') { + // if (listSelector === '') { while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); - + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && parentRect.top <= childRect.top && parentRect.bottom >= childRect.bottom; - + const significantOverlap = (childRect.width * childRect.height) / (parentRect.width * parentRect.height) > 0.5; - + if (fullyContained && significantOverlap) { element = element.parentElement; } else { break; } } - // } + // } - const generalSelector = getSelectorPath(element); - return { - generalSelector, - }; - }, coordinates); - return selectors || { generalSelector: '' }; - } else { - console.log(`NON UNIQUE: MODE 2`) - const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { - function getNonUniqueSelector(element: HTMLElement): string { - let selector = element.tagName.toLowerCase(); + const generalSelector = getSelectorPath(element); + return { + generalSelector, + }; + }, coordinates); + return selectors || { generalSelector: '' }; + } else { + console.log(`NON UNIQUE: MODE 2`) + const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); - if (element.className) { - const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); - if (classes.length > 0) { - const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); - if (validClasses.length > 0) { - selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + if (element.className) { + const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); + if (classes.length > 0) { + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } } } + + return selector; } - return selector; - } + function getSelectorPath(element: HTMLElement | null): string { + const path: string[] = []; + let depth = 0; + const maxDepth = 2; - function getSelectorPath(element: HTMLElement | null): string { - const path: string[] = []; - let depth = 0; - const maxDepth = 2; + while (element && element !== document.body && depth < maxDepth) { + const selector = getNonUniqueSelector(element); + path.unshift(selector); + element = element.parentElement; + depth++; + } - while (element && element !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(element); - path.unshift(selector); - element = element.parentElement; - depth++; + return path.join(' > '); } - return path.join(' > '); - } + const originalEl = document.elementFromPoint(x, y) as HTMLElement; + if (!originalEl) return null; - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (!originalEl) return null; + let element = originalEl; - let element = originalEl; - - const generalSelector = getSelectorPath(element); - return { - generalSelector, - }; - }, coordinates); - return selectors || { generalSelector: '' }; - } + const generalSelector = getSelectorPath(element); + return { + generalSelector, + }; + }, coordinates); + return selectors || { generalSelector: '' }; + } } catch (error) { console.error('Error in getNonUniqueSelectors:', error); From a1f31ec05d00dbac4dcd12759d66005e3832031d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:44:10 +0530 Subject: [PATCH 162/500] feat: local setup upgrade cd step --- src/components/molecules/NavBar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index c2f271cf..54805ef7 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -25,7 +25,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => const navigate = useNavigate(); const [anchorEl, setAnchorEl] = useState(null); - const currentVersion = packageJson.version; + const currentVersion = "0.0.3" const [open, setOpen] = useState(false); const [latestVersion, setLatestVersion] = useState(null); @@ -208,6 +208,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>

Run the commands below

+ # cd to project directory (eg: maxun) +
+ cd maxun +
+
# pull latest changes
git pull origin master From b6274cca14b878c9d0c00ef283c1d8ef00c381ec Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:44:39 +0530 Subject: [PATCH 163/500] feat: docker setup upgrade cd step --- 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 54805ef7..0816b0e8 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -233,6 +233,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) =>

Run the commands below

+ # cd to project directory (eg: maxun) +
+ cd maxun +
+
# pull latest docker images
docker-compose pull From 5b3b6d848f9ce33df328cd46bf4b2cb9a9684fcb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:45:49 +0530 Subject: [PATCH 164/500] feat: docker setup container down --- 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 0816b0e8..9acfe57b 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -238,6 +238,11 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => cd maxun

+ # stop the working containers +
+ docker-compose down +
+
# pull latest docker images
docker-compose pull From 6da2f6a130a562c5abf42785a9157161b8fc05d6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 17:47:28 +0530 Subject: [PATCH 165/500] feat: revert to version --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 9acfe57b..24c41b20 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -25,7 +25,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => const navigate = useNavigate(); const [anchorEl, setAnchorEl] = useState(null); - const currentVersion = "0.0.3" + const currentVersion = packageJson.version; const [open, setOpen] = useState(false); const [latestVersion, setLatestVersion] = useState(null); From 0d763f78213671d5809102b9b501920921418ffd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 18:32:08 +0530 Subject: [PATCH 166/500] feat: iframe support for get element info --- server/src/workflow-management/selector.ts | 214 ++++++++++++++++----- 1 file changed, 168 insertions(+), 46 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 240f8921..16979487 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -23,10 +23,8 @@ export const getElementInformation = async ( if (!getList || listSelector !== '') { const elementInfo = await page.evaluate( async ({ x, y }) => { - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - const { parentElement } = el; - const element = parentElement?.tagName === 'A' ? parentElement : el; + // Helper function to get element info + const getElementInfo = (element: HTMLElement) => { let info: { tagName: string; hasOnlyText?: boolean; @@ -36,9 +34,12 @@ export const getElementInformation = async ( attributes?: Record; innerHTML?: string; outerHTML?: string; + fromIframe?: boolean; + iframePath?: string[]; } = { tagName: element?.tagName ?? '', }; + if (element) { info.attributes = Array.from(element.attributes).reduce( (acc, attr) => { @@ -48,7 +49,7 @@ export const getElementInformation = async ( {} as Record ); } - // Gather specific information based on the tag + if (element?.tagName === 'A') { info.url = (element as HTMLAnchorElement).href; info.innerText = element.innerText ?? ''; @@ -61,29 +62,186 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; - } else if (element?.tagName === 'INPUT' && (element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date') { + } else if (element?.tagName === 'INPUT' && + ((element as HTMLInputElement).type === 'time' || + (element as HTMLInputElement).type === 'date')) { info.innerText = (element as HTMLInputElement).value; } else { info.hasOnlyText = element?.children?.length === 0 && element?.innerText?.length > 0; info.innerText = element?.innerText ?? ''; } + info.innerHTML = element.innerHTML; info.outerHTML = element.outerHTML; return info; + }; + + // Helper function to search in iframe + const searchInIframe = ( + iframe: HTMLIFrameElement, + relativeX: number, + relativeY: number, + iframePath: string[] + ) => { + try { + if (!iframe.contentDocument) return null; + + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; + if (!el) return null; + + const { parentElement } = el; + const element = parentElement?.tagName === 'A' ? parentElement : el; + + const info = getElementInfo(element); + info.fromIframe = true; + info.iframePath = iframePath; + + return info; + } catch (e) { + console.warn('Cannot access iframe content:', e); + return null; + } + }; + + const el = document.elementFromPoint(x, y) as HTMLElement; + if (el) { + // Check if the element is an iframe + if (el.tagName === 'IFRAME') { + const iframe = el as HTMLIFrameElement; + const rect = iframe.getBoundingClientRect(); + const relativeX = x - rect.left; + const relativeY = y - rect.top; + + const iframeResult = searchInIframe( + iframe, + relativeX, + relativeY, + [iframe.id || 'unnamed-iframe'] + ); + if (iframeResult) return iframeResult; + } + + const { parentElement } = el; + const element = parentElement?.tagName === 'A' ? parentElement : el; + return getElementInfo(element); } return null; }, - { x: coordinates.x, y: coordinates.y }, + { x: coordinates.x, y: coordinates.y } ); return elementInfo; } else { const elementInfo = await page.evaluate( async ({ x, y }) => { + // Helper function to get element info (same as above) + const getElementInfo = (element: HTMLElement) => { + let info: { + tagName: string; + hasOnlyText?: boolean; + innerText?: string; + url?: string; + imageUrl?: string; + attributes?: Record; + innerHTML?: string; + outerHTML?: string; + fromIframe?: boolean; + iframePath?: string[]; + } = { + tagName: element?.tagName ?? '', + }; + + if (element) { + info.attributes = Array.from(element.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record + ); + } + + if (element?.tagName === 'A') { + info.url = (element as HTMLAnchorElement).href; + info.innerText = element.innerText ?? ''; + } else if (element?.tagName === 'IMG') { + info.imageUrl = (element as HTMLImageElement).src; + } else { + info.hasOnlyText = element?.children?.length === 0 && + element?.innerText?.length > 0; + info.innerText = element?.innerText ?? ''; + } + + info.innerHTML = element.innerHTML; + info.outerHTML = element.outerHTML; + return info; + }; + + // Helper function to search in iframe (same as above) + const searchInIframe = ( + iframe: HTMLIFrameElement, + relativeX: number, + relativeY: number, + iframePath: string[] + ) => { + try { + if (!iframe.contentDocument) return null; + + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; + if (!el) return null; + + let element = el; + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } + + const info = getElementInfo(element); + info.fromIframe = true; + info.iframePath = iframePath; + + return info; + } catch (e) { + console.warn('Cannot access iframe content:', e); + return null; + } + }; + const originalEl = document.elementFromPoint(x, y) as HTMLElement; if (originalEl) { - let element = originalEl; + // Check if the element is an iframe + if (originalEl.tagName === 'IFRAME') { + const iframe = originalEl as HTMLIFrameElement; + const rect = iframe.getBoundingClientRect(); + const relativeX = x - rect.left; + const relativeY = y - rect.top; + + const iframeResult = searchInIframe( + iframe, + relativeX, + relativeY, + [iframe.id || 'unnamed-iframe'] + ); + if (iframeResult) return iframeResult; + } + let element = originalEl; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -105,47 +263,11 @@ export const getElementInformation = async ( } } - let info: { - tagName: string; - hasOnlyText?: boolean; - innerText?: string; - url?: string; - imageUrl?: string; - attributes?: Record; - innerHTML?: string; - outerHTML?: string; - } = { - tagName: element?.tagName ?? '', - }; - - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - } - - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; - return info; + return getElementInfo(element); } return null; }, - { x: coordinates.x, y: coordinates.y }, + { x: coordinates.x, y: coordinates.y } ); return elementInfo; } From 2b96f07b36cd4f1a8ac35a54f49915f669b01220 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 19:52:03 +0530 Subject: [PATCH 167/500] feat: add datetime-local selection pair to workflow --- .../workflow-management/classes/Generator.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 9ff4922e..609541de 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -315,6 +315,26 @@ export class WorkflowGenerator { await this.addPairToWorkflowAndNotifyClient(pair, page); }; + public onDateTimeLocalSelection = async (page: Page, data: { selector: string, value: string }) => { + const { selector, value } = data; + + try { + await page.fill(selector, value); + } catch (error) { + console.error("Failed to fill datetime-local value:", error); + } + + const pair: WhereWhatPair = { + where: { url: this.getBestUrl(page.url()) }, + what: [{ + action: 'fill', + args: [selector, value], + }], + }; + + await this.addPairToWorkflowAndNotifyClient(pair, page); + }; + /** * Generates a pair for the click event. * @param coordinates The coordinates of the click event. @@ -390,6 +410,16 @@ export class WorkflowGenerator { return; } + const isDateTimeLocal = elementInfo?.tagName === 'INPUT' && elementInfo?.attributes?.type === 'datetime-local'; + + if (isDateTimeLocal) { + this.socket.emit('showDateTimePicker', { + coordinates, + selector + }); + return; + } + //const element = await getElementMouseIsOver(page, coordinates); //logger.log('debug', `Element: ${JSON.stringify(element, null, 2)}`); if (selector) { From 6d792f365ec5526431d13b08030c315ff5f6f623 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 19:52:55 +0530 Subject: [PATCH 168/500] feat: add datetime-local selection event handlers --- server/src/browser-management/inputHandlers.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/browser-management/inputHandlers.ts b/server/src/browser-management/inputHandlers.ts index 982e18de..bf365053 100644 --- a/server/src/browser-management/inputHandlers.ts +++ b/server/src/browser-management/inputHandlers.ts @@ -260,6 +260,16 @@ const onTimeSelection = async (data: { selector: string, value: string }) => { await handleWrapper(handleTimeSelection, data); } +const handleDateTimeLocalSelection = async (generator: WorkflowGenerator, page: Page, data: { selector: string, value: string }) => { + await generator.onDateTimeLocalSelection(page, data); + logger.log('debug', `DateTime Local value ${data.value} selected`); +} + +const onDateTimeLocalSelection = async (data: { selector: string, value: string }) => { + logger.log('debug', 'Handling datetime-local selection event emitted from client'); + await handleWrapper(handleDateTimeLocalSelection, data); +} + /** * A wrapper function for handling the keyup event. * @param keyboardInput - the keyboard input of the keyup event @@ -418,6 +428,7 @@ const registerInputHandlers = (socket: Socket) => { socket.on("input:date", onDateSelection); socket.on("input:dropdown", onDropdownSelection); socket.on("input:time", onTimeSelection); + socket.on("input:datetime-local", onDateTimeLocalSelection); socket.on("action", onGenerateAction); }; From b1fbcb506c87783e8a953943df37dc2b56080ae5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 19:54:05 +0530 Subject: [PATCH 169/500] feat: trigger socket event to display datime-local picker --- src/components/atoms/canvas.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/atoms/canvas.tsx b/src/components/atoms/canvas.tsx index 77128a65..e71a4d93 100644 --- a/src/components/atoms/canvas.tsx +++ b/src/components/atoms/canvas.tsx @@ -6,6 +6,7 @@ import { useActionContext } from '../../context/browserActions'; import DatePicker from './DatePicker'; import Dropdown from './Dropdown'; import TimePicker from './TimePicker'; +import DateTimeLocalPicker from './DateTimeLocalPicker'; interface CreateRefCallback { (ref: React.RefObject): void; @@ -55,6 +56,11 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { selector: string; } | null>(null); + const [dateTimeLocalInfo, setDateTimeLocalInfo] = React.useState<{ + coordinates: Coordinates; + selector: string; + } | null>(null); + const notifyLastAction = (action: string) => { if (lastAction !== action) { setLastAction(action); @@ -91,9 +97,15 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { 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]); @@ -222,6 +234,13 @@ const Canvas = ({ width, height, onCreateRef }: CanvasProps) => { onClose={() => setTimePickerInfo(null)} /> )} + {dateTimeLocalInfo && ( + setDateTimeLocalInfo(null)} + /> + )} ); From 15aa85976abd4f3f07f62c6f66b95087f047e31b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 19:55:17 +0530 Subject: [PATCH 170/500] feat: add dateime-local picker component --- src/components/atoms/DateTimeLocalPicker.tsx | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/components/atoms/DateTimeLocalPicker.tsx diff --git a/src/components/atoms/DateTimeLocalPicker.tsx b/src/components/atoms/DateTimeLocalPicker.tsx new file mode 100644 index 00000000..dc62a79b --- /dev/null +++ b/src/components/atoms/DateTimeLocalPicker.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { useSocketStore } from '../../context/socket'; +import { Coordinates } from './canvas'; + +interface DateTimeLocalPickerProps { + coordinates: Coordinates; + selector: string; + onClose: () => void; +} + +const DateTimeLocalPicker: React.FC = ({ coordinates, selector, onClose }) => { + const { socket } = useSocketStore(); + const [selectedDateTime, setSelectedDateTime] = useState(''); + + const handleDateTimeChange = (e: React.ChangeEvent) => { + setSelectedDateTime(e.target.value); + }; + + const handleConfirm = () => { + if (socket && selectedDateTime) { + socket.emit('input:datetime-local', { + selector, + value: selectedDateTime + }); + onClose(); + } + }; + + return ( +
+
+ +
+ + +
+
+
+ ); +}; + +export default DateTimeLocalPicker; \ No newline at end of file From 6904933036bc48bc09fc331479efbfe174181c78 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 20:28:11 +0530 Subject: [PATCH 171/500] feat: iframe support for getRect --- server/src/workflow-management/selector.ts | 189 ++++++++++++++++++--- 1 file changed, 166 insertions(+), 23 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 16979487..83491042 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -292,25 +292,90 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector if (!getList || listSelector !== '') { const rect = await page.evaluate( async ({ x, y }) => { + // Helper function to convert rectangle to plain object + const getRectangleInfo = (rectangle: DOMRect) => { + const info = { + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + fromIframe: false, + iframePath: [] as string[] + }; + return info; + }; + + // Helper function to search in iframe + const searchInIframe = ( + iframe: HTMLIFrameElement, + relativeX: number, + relativeY: number, + iframePath: string[] + ) => { + try { + if (!iframe.contentDocument) return null; + + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; + if (!el) return null; + + const { parentElement } = el; + const element = parentElement?.tagName === 'A' ? parentElement : el; + const rectangle = element?.getBoundingClientRect(); + + if (rectangle) { + const iframeRect = iframe.getBoundingClientRect(); + const rectInfo = getRectangleInfo(rectangle); + + // Adjust coordinates relative to the main document + rectInfo.x += iframeRect.x; + rectInfo.y += iframeRect.y; + rectInfo.top += iframeRect.top; + rectInfo.right += iframeRect.left; + rectInfo.bottom += iframeRect.top; + rectInfo.left += iframeRect.left; + rectInfo.fromIframe = true; + rectInfo.iframePath = iframePath; + + return rectInfo; + } + return null; + } catch (e) { + console.warn('Cannot access iframe content:', e); + return null; + } + }; + const el = document.elementFromPoint(x, y) as HTMLElement; if (el) { + // Check if the element is an iframe + if (el.tagName === 'IFRAME') { + const iframe = el as HTMLIFrameElement; + const rect = iframe.getBoundingClientRect(); + const relativeX = x - rect.left; + const relativeY = y - rect.top; + + const iframeResult = searchInIframe( + iframe, + relativeX, + relativeY, + [iframe.id || 'unnamed-iframe'] + ); + if (iframeResult) return iframeResult; + } + const { parentElement } = el; - // Match the logic in recorder.ts for link clicks const element = parentElement?.tagName === 'A' ? parentElement : el; const rectangle = element?.getBoundingClientRect(); + if (rectangle) { - return { - x: rectangle.x, - y: rectangle.y, - width: rectangle.width, - height: rectangle.height, - top: rectangle.top, - right: rectangle.right, - bottom: rectangle.bottom, - left: rectangle.left, - }; + return getRectangleInfo(rectangle); } } + return null; }, { x: coordinates.x, y: coordinates.y }, ); @@ -318,10 +383,98 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector } else { const rect = await page.evaluate( async ({ x, y }) => { + // Helper function to convert rectangle to plain object (same as above) + const getRectangleInfo = (rectangle: DOMRect) => ({ + x: rectangle.x, + y: rectangle.y, + width: rectangle.width, + height: rectangle.height, + top: rectangle.top, + right: rectangle.right, + bottom: rectangle.bottom, + left: rectangle.left, + fromIframe: false, + iframePath: [] as string[] + }); + + // Helper function to search in iframe (same as above) + const searchInIframe = ( + iframe: HTMLIFrameElement, + relativeX: number, + relativeY: number, + iframePath: string[] + ) => { + try { + if (!iframe.contentDocument) return null; + + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; + if (!el) return null; + + let element = el; + while (element.parentElement) { + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); + + const fullyContained = + parentRect.left <= childRect.left && + parentRect.right >= childRect.right && + parentRect.top <= childRect.top && + parentRect.bottom >= childRect.bottom; + + const significantOverlap = + (childRect.width * childRect.height) / + (parentRect.width * parentRect.height) > 0.5; + + if (fullyContained && significantOverlap) { + element = element.parentElement; + } else { + break; + } + } + + const rectangle = element?.getBoundingClientRect(); + if (rectangle) { + const iframeRect = iframe.getBoundingClientRect(); + const rectInfo = getRectangleInfo(rectangle); + + // Adjust coordinates relative to the main document + rectInfo.x += iframeRect.x; + rectInfo.y += iframeRect.y; + rectInfo.top += iframeRect.top; + rectInfo.right += iframeRect.left; + rectInfo.bottom += iframeRect.top; + rectInfo.left += iframeRect.left; + rectInfo.fromIframe = true; + rectInfo.iframePath = iframePath; + + return rectInfo; + } + return null; + } catch (e) { + console.warn('Cannot access iframe content:', e); + return null; + } + }; + const originalEl = document.elementFromPoint(x, y) as HTMLElement; if (originalEl) { - let element = originalEl; + // Check if the element is an iframe + if (originalEl.tagName === 'IFRAME') { + const iframe = originalEl as HTMLIFrameElement; + const rect = iframe.getBoundingClientRect(); + const relativeX = x - rect.left; + const relativeY = y - rect.top; + + const iframeResult = searchInIframe( + iframe, + relativeX, + relativeY, + [iframe.id || 'unnamed-iframe'] + ); + if (iframeResult) return iframeResult; + } + let element = originalEl; while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -344,18 +497,8 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector } const rectangle = element?.getBoundingClientRect(); - if (rectangle) { - return { - x: rectangle.x, - y: rectangle.y, - width: rectangle.width, - height: rectangle.height, - top: rectangle.top, - right: rectangle.right, - bottom: rectangle.bottom, - left: rectangle.left, - }; + return getRectangleInfo(rectangle); } } return null; From 8ba928dae6cac4d7e5924bcc799e792068e6734d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 20:28:24 +0530 Subject: [PATCH 172/500] chore: fix format --- server/src/workflow-management/selector.ts | 76 +++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 83491042..dd869f3d 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -62,9 +62,9 @@ export const getElementInformation = async ( ...info.attributes, selectedValue: selectElement.value, }; - } else if (element?.tagName === 'INPUT' && - ((element as HTMLInputElement).type === 'time' || - (element as HTMLInputElement).type === 'date')) { + } else if (element?.tagName === 'INPUT' && + ((element as HTMLInputElement).type === 'time' || + (element as HTMLInputElement).type === 'date')) { info.innerText = (element as HTMLInputElement).value; } else { info.hasOnlyText = element?.children?.length === 0 && @@ -79,20 +79,20 @@ export const getElementInformation = async ( // Helper function to search in iframe const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, + iframe: HTMLIFrameElement, + relativeX: number, relativeY: number, iframePath: string[] ) => { try { if (!iframe.contentDocument) return null; - + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; if (!el) return null; const { parentElement } = el; const element = parentElement?.tagName === 'A' ? parentElement : el; - + const info = getElementInfo(element); info.fromIframe = true; info.iframePath = iframePath; @@ -112,11 +112,11 @@ export const getElementInformation = async ( const rect = iframe.getBoundingClientRect(); const relativeX = x - rect.left; const relativeY = y - rect.top; - + const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, + iframe, + relativeX, + relativeY, [iframe.id || 'unnamed-iframe'] ); if (iframeResult) return iframeResult; @@ -179,14 +179,14 @@ export const getElementInformation = async ( // Helper function to search in iframe (same as above) const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, + iframe: HTMLIFrameElement, + relativeX: number, relativeY: number, iframePath: string[] ) => { try { if (!iframe.contentDocument) return null; - + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; if (!el) return null; @@ -231,11 +231,11 @@ export const getElementInformation = async ( const rect = iframe.getBoundingClientRect(); const relativeX = x - rect.left; const relativeY = y - rect.top; - + const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, + iframe, + relativeX, + relativeY, [iframe.id || 'unnamed-iframe'] ); if (iframeResult) return iframeResult; @@ -311,25 +311,25 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector // Helper function to search in iframe const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, + iframe: HTMLIFrameElement, + relativeX: number, relativeY: number, iframePath: string[] ) => { try { if (!iframe.contentDocument) return null; - + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; if (!el) return null; const { parentElement } = el; const element = parentElement?.tagName === 'A' ? parentElement : el; const rectangle = element?.getBoundingClientRect(); - + if (rectangle) { const iframeRect = iframe.getBoundingClientRect(); const rectInfo = getRectangleInfo(rectangle); - + // Adjust coordinates relative to the main document rectInfo.x += iframeRect.x; rectInfo.y += iframeRect.y; @@ -339,7 +339,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector rectInfo.left += iframeRect.left; rectInfo.fromIframe = true; rectInfo.iframePath = iframePath; - + return rectInfo; } return null; @@ -357,11 +357,11 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector const rect = iframe.getBoundingClientRect(); const relativeX = x - rect.left; const relativeY = y - rect.top; - + const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, + iframe, + relativeX, + relativeY, [iframe.id || 'unnamed-iframe'] ); if (iframeResult) return iframeResult; @@ -370,7 +370,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector const { parentElement } = el; const element = parentElement?.tagName === 'A' ? parentElement : el; const rectangle = element?.getBoundingClientRect(); - + if (rectangle) { return getRectangleInfo(rectangle); } @@ -399,14 +399,14 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector // Helper function to search in iframe (same as above) const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, + iframe: HTMLIFrameElement, + relativeX: number, relativeY: number, iframePath: string[] ) => { try { if (!iframe.contentDocument) return null; - + const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; if (!el) return null; @@ -436,7 +436,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector if (rectangle) { const iframeRect = iframe.getBoundingClientRect(); const rectInfo = getRectangleInfo(rectangle); - + // Adjust coordinates relative to the main document rectInfo.x += iframeRect.x; rectInfo.y += iframeRect.y; @@ -446,7 +446,7 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector rectInfo.left += iframeRect.left; rectInfo.fromIframe = true; rectInfo.iframePath = iframePath; - + return rectInfo; } return null; @@ -464,11 +464,11 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector const rect = iframe.getBoundingClientRect(); const relativeX = x - rect.left; const relativeY = y - rect.top; - + const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, + iframe, + relativeX, + relativeY, [iframe.id || 'unnamed-iframe'] ); if (iframeResult) return iframeResult; From 5df00b59304d986687f7b04d2da5703c6dafcaf6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:02:01 +0530 Subject: [PATCH 173/500] feat: add german translation for proxy page --- public/locales/de.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 86f1ceb2..ef0cc31a 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -56,5 +56,41 @@ "delete": "Löschen", "settings": "Einstellungen", "search": "Ausführungen suchen..." + }, + "proxy": { + "title": "Proxy-Konfiguration", + "tab_standard": "Standard-Proxy", + "tab_rotation": "Automatische Proxy-Rotation", + "server_url": "Proxy-Server-URL", + "server_url_helper": "Proxy für alle Roboter. HTTP- und SOCKS-Proxys werden unterstützt. Beispiel http://myproxy.com:3128 oder socks5://myproxy.com:3128. Kurzform myproxy.com:3128 wird als HTTP-Proxy behandelt.", + "requires_auth": "Authentifizierung erforderlich?", + "username": "Benutzername", + "password": "Passwort", + "add_proxy": "Proxy hinzufügen", + "test_proxy": "Proxy testen", + "remove_proxy": "Proxy entfernen", + "table": { + "proxy_url": "Proxy-URL", + "requires_auth": "Authentifizierung erforderlich" + }, + "coming_soon": "Demnächst verfügbar - In Open Source (Basis-Rotation) & Cloud (Erweiterte Rotation). Wenn Sie die Infrastruktur nicht selbst verwalten möchten, tragen Sie sich in unsere Cloud-Warteliste ein.", + "join_waitlist": "Maxun Cloud Warteliste beitreten", + "alert": { + "title": "Wenn Ihr Proxy einen Benutzernamen und ein Passwort erfordert, geben Sie diese immer separat von der Proxy-URL an.", + "right_way": "Der richtige Weg", + "wrong_way": "Der falsche Weg", + "proxy_url": "Proxy-URL:", + "username": "Benutzername:", + "password": "Passwort:" + }, + "notifications": { + "config_success": "Proxy-Konfiguration erfolgreich übermittelt", + "config_error": "Fehler beim Übermitteln der Proxy-Konfiguration. Bitte erneut versuchen.", + "test_success": "Proxy-Konfiguration funktioniert", + "test_error": "Fehler beim Testen der Proxy-Konfiguration. Bitte erneut versuchen.", + "fetch_success": "Proxy-Konfiguration erfolgreich abgerufen", + "remove_success": "Proxy-Konfiguration erfolgreich entfernt", + "remove_error": "Fehler beim Entfernen der Proxy-Konfiguration. Bitte erneut versuchen." + } } } From 6e8815b8fd986afdecee215f8f9bf35629933633 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:02:46 +0530 Subject: [PATCH 174/500] feat: add english translation for proxy page --- public/locales/en.json | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index b6396893..ed5582bf 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -59,8 +59,41 @@ "delete":"Delete", "settings":"Settings", "search":"Search Runs..." - - - + }, + "proxy": { + "title": "Proxy Configuration", + "tab_standard": "Standard Proxy", + "tab_rotation": "Automatic Proxy Rotation", + "server_url": "Proxy Server URL", + "server_url_helper": "Proxy to be used for all robots. HTTP and SOCKS proxies are supported. Example http://myproxy.com:3128 or socks5://myproxy.com:3128. Short form myproxy.com:3128 is considered an HTTP proxy.", + "requires_auth": "Requires Authentication?", + "username": "Username", + "password": "Password", + "add_proxy": "Add Proxy", + "test_proxy": "Test Proxy", + "remove_proxy": "Remove Proxy", + "table": { + "proxy_url": "Proxy URL", + "requires_auth": "Requires Authentication" + }, + "coming_soon": "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.", + "join_waitlist": "Join Maxun Cloud Waitlist", + "alert": { + "title": "If your proxy requires a username and password, always provide them separately from the proxy URL.", + "right_way": "The right way", + "wrong_way": "The wrong way", + "proxy_url": "Proxy URL:", + "username": "Username:", + "password": "Password:" + }, + "notifications": { + "config_success": "Proxy configuration submitted successfully", + "config_error": "Failed to submit proxy configuration. Try again.", + "test_success": "Proxy configuration is working", + "test_error": "Failed to test proxy configuration. Try again.", + "fetch_success": "Proxy configuration fetched successfully", + "remove_success": "Proxy configuration removed successfully", + "remove_error": "Failed to remove proxy configuration. Try again." + } } } \ No newline at end of file From ae037b807ade675b042d9a46625c8eca31440315 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:03:05 +0530 Subject: [PATCH 175/500] feat: add spanish translation for proxy page --- public/locales/es.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 4e717ce2..bc62d2a7 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -57,5 +57,41 @@ "delete": "Eliminar", "settings": "Ajustes", "search": "Buscar ejecuciones..." + }, + "proxy": { + "title": "Configuración del Proxy", + "tab_standard": "Proxy Estándar", + "tab_rotation": "Rotación Automática de Proxy", + "server_url": "URL del Servidor Proxy", + "server_url_helper": "Proxy para usar en todos los robots. Se admiten proxies HTTP y SOCKS. Ejemplo http://myproxy.com:3128 o socks5://myproxy.com:3128. La forma corta myproxy.com:3128 se considera un proxy HTTP.", + "requires_auth": "¿Requiere Autenticación?", + "username": "Usuario", + "password": "Contraseña", + "add_proxy": "Agregar Proxy", + "test_proxy": "Probar Proxy", + "remove_proxy": "Eliminar Proxy", + "table": { + "proxy_url": "URL del Proxy", + "requires_auth": "Requiere Autenticación" + }, + "coming_soon": "Próximamente - En Open Source (Rotación Básica) y Cloud (Rotación Avanzada). Si no desea administrar la infraestructura, únase a nuestra lista de espera en la nube para obtener acceso anticipado.", + "join_waitlist": "Unirse a la Lista de Espera de Maxun Cloud", + "alert": { + "title": "Si su proxy requiere un nombre de usuario y contraseña, proporcione siempre estos datos por separado de la URL del proxy.", + "right_way": "La forma correcta", + "wrong_way": "La forma incorrecta", + "proxy_url": "URL del Proxy:", + "username": "Usuario:", + "password": "Contraseña:" + }, + "notifications": { + "config_success": "Configuración del proxy enviada con éxito", + "config_error": "Error al enviar la configuración del proxy. Inténtelo de nuevo.", + "test_success": "La configuración del proxy funciona correctamente", + "test_error": "Error al probar la configuración del proxy. Inténtelo de nuevo.", + "fetch_success": "Configuración del proxy recuperada con éxito", + "remove_success": "Configuración del proxy eliminada con éxito", + "remove_error": "Error al eliminar la configuración del proxy. Inténtelo de nuevo." + } } } \ No newline at end of file From 9f06d437403fcd844c4e284afee7ff4e513724d4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:03:22 +0530 Subject: [PATCH 176/500] feat: add japanese translation for proxy page --- public/locales/ja.json | 136 ++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 50 deletions(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index 881fd131..2d7adc25 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -1,61 +1,97 @@ { "login": { - "title": "お帰りなさい!", - "email": "メールアドレス", - "password": "パスワード", - "button": "ログイン", - "loading": "読み込み中", - "register_prompt": "アカウントをお持ちでないですか?", - "register_link": "登録する", - "welcome_notification": "Maxunへようこそ!", - "error_notification": "ログインに失敗しました。もう一度お試しください。" + "title": "お帰りなさい!", + "email": "メールアドレス", + "password": "パスワード", + "button": "ログイン", + "loading": "読み込み中", + "register_prompt": "アカウントをお持ちでないですか?", + "register_link": "登録する", + "welcome_notification": "Maxunへようこそ!", + "error_notification": "ログインに失敗しました。もう一度お試しください。" }, "register": { - "title": "アカウントを登録する", - "email": "メールアドレス", - "password": "パスワード", - "button": "登録する", - "loading": "読み込み中", - "register_prompt": "既にアカウントをお持ちですか?", - "login_link": "ログイン", - "welcome_notification": "Maxunへようこそ!", - "error_notification": "登録に失敗しました。もう一度お試しください。" + "title": "アカウントを登録する", + "email": "メールアドレス", + "password": "パスワード", + "button": "登録する", + "loading": "読み込み中", + "register_prompt": "既にアカウントをお持ちですか?", + "login_link": "ログイン", + "welcome_notification": "Maxunへようこそ!", + "error_notification": "登録に失敗しました。もう一度お試しください。" }, "recordingtable": { - "run": "実行", - "name": "名前", - "schedule": "スケジュール", - "integrate": "統合", - "settings": "設定", - "options": "オプション", - "heading": "私のロボット", - "new": "ロボットを作成", - "modal": { - "title": "URLを入力してください", - "label": "URL", - "button": "録画を開始" - }, - "edit": "編集", - "delete": "削除", - "duplicate": "複製", - "search": "ロボットを検索..." + "run": "実行", + "name": "名前", + "schedule": "スケジュール", + "integrate": "統合", + "settings": "設定", + "options": "オプション", + "heading": "私のロボット", + "new": "ロボットを作成", + "modal": { + "title": "URLを入力してください", + "label": "URL", + "button": "録画を開始" + }, + "edit": "編集", + "delete": "削除", + "duplicate": "複製", + "search": "ロボットを検索..." }, "mainmenu": { - "recordings": "ロボット", - "runs": "実行", - "proxy": "プロキシ", - "apikey": "APIキー", - "feedback": "Maxunクラウドに参加する", - "apidocs": "APIドキュメント" + "recordings": "ロボット", + "runs": "実行", + "proxy": "プロキシ", + "apikey": "APIキー", + "feedback": "Maxunクラウドに参加する", + "apidocs": "APIドキュメント" }, "runstable": { - "runs": "すべての実行", - "runStatus": "ステータス", - "runName": "名前", - "startedAt": "開始日時", - "finishedAt": "終了日時", - "delete": "削除", - "settings": "設定", - "search": "実行を検索..." - } + "runs": "すべての実行", + "runStatus": "ステータス", + "runName": "名前", + "startedAt": "開始日時", + "finishedAt": "終了日時", + "delete": "削除", + "settings": "設定", + "search": "実行を検索..." + }, + "proxy": { + "title": "プロキシ設定", + "tab_standard": "標準プロキシ", + "tab_rotation": "自動プロキシローテーション", + "server_url": "プロキシサーバーURL", + "server_url_helper": "すべてのロボットで使用するプロキシ。HTTPとSOCKSプロキシがサポートされています。例:http://myproxy.com:3128 または socks5://myproxy.com:3128。短縮形 myproxy.com:3128 はHTTPプロキシとして扱われます。", + "requires_auth": "認証が必要ですか?", + "username": "ユーザー名", + "password": "パスワード", + "add_proxy": "プロキシを追加", + "test_proxy": "プロキシをテスト", + "remove_proxy": "プロキシを削除", + "table": { + "proxy_url": "プロキシURL", + "requires_auth": "認証が必要" + }, + "coming_soon": "近日公開 - オープンソース(基本ローテーション)とクラウド(高度なローテーション)。インフラストラクチャを管理したくない場合は、クラウドの待機リストに参加して早期アクセスを取得してください。", + "join_waitlist": "Maxun Cloud待機リストに参加", + "alert": { + "title": "プロキシにユーザー名とパスワードが必要な場合は、必ずプロキシURLとは別に指定してください。", + "right_way": "正しい方法", + "wrong_way": "間違った方法", + "proxy_url": "プロキシURL:", + "username": "ユーザー名:", + "password": "パスワード:" + }, + "notifications": { + "config_success": "プロキシ設定が正常に送信されました", + "config_error": "プロキシ設定の送信に失敗しました。もう一度お試しください。", + "test_success": "プロキシ設定は正常に動作しています", + "test_error": "プロキシ設定のテストに失敗しました。もう一度お試しください。", + "fetch_success": "プロキシ設定の取得に成功しました", + "remove_success": "プロキシ設定が正常に削除されました", + "remove_error": "プロキシ設定の削除に失敗しました。もう一度お試しください。" + } + } } From 18f7d75ef3ac6817360e996d5bca47b7139950af Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:03:36 +0530 Subject: [PATCH 177/500] feat: add chinese translation for proxy page --- public/locales/zh.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 31bce494..52b2ce3e 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -57,5 +57,41 @@ "delete": "删除", "settings": "设置", "search": "搜索运行记录..." + }, + "proxy": { + "title": "代理设置", + "tab_standard": "标准代理", + "tab_rotation": "自动代理轮换", + "server_url": "代理服务器URL", + "server_url_helper": "用于所有机器人的代理。支持HTTP和SOCKS代理。示例 http://myproxy.com:3128 或 socks5://myproxy.com:3128。简短形式 myproxy.com:3128 被视为HTTP代理。", + "requires_auth": "需要认证?", + "username": "用户名", + "password": "密码", + "add_proxy": "添加代理", + "test_proxy": "测试代理", + "remove_proxy": "删除代理", + "table": { + "proxy_url": "代理URL", + "requires_auth": "需要认证" + }, + "coming_soon": "即将推出 - 开源版(基础轮换)和云版(高级轮换)。如果您不想管理基础设施,请加入我们的云服务等候名单以获得早期访问权限。", + "join_waitlist": "加入Maxun Cloud等候名单", + "alert": { + "title": "如果您的代理需要用户名和密码,请务必将它们与代理URL分开提供。", + "right_way": "正确方式", + "wrong_way": "错误方式", + "proxy_url": "代理URL:", + "username": "用户名:", + "password": "密码:" + }, + "notifications": { + "config_success": "代理配置提交成功", + "config_error": "提交代理配置失败。请重试。", + "test_success": "代理配置运行正常", + "test_error": "测试代理配置失败。请重试。", + "fetch_success": "成功获取代理配置", + "remove_success": "成功删除代理配置", + "remove_error": "删除代理配置失败。请重试。" + } } } \ No newline at end of file From e404b74d2c37dff7084a59a9f25a7d29107fc2a2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:04:44 +0530 Subject: [PATCH 178/500] feat: add translation for proxy page --- src/components/organisms/ProxyForm.tsx | 64 +++++++++++++------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index a581144b..8fbf730a 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -3,6 +3,7 @@ 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 { sendProxyConfig, getProxyConfig, testProxyConfig, deleteProxyConfig } from '../../api/proxy'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useTranslation } from 'react-i18next'; const FormContainer = styled(Box)({ display: 'flex', @@ -16,6 +17,7 @@ const FormControl = styled(Box)({ }); const ProxyForm: React.FC = () => { + const { t } = useTranslation(); const [proxyConfigForm, setProxyConfigForm] = useState({ server_url: '', username: '', @@ -79,9 +81,9 @@ const ProxyForm: React.FC = () => { try { const response = await sendProxyConfig(proxyConfigForm); if (response) { - notify('success', 'Proxy configuration submitted successfully'); + notify('success', t('proxy.notifications.config_success')); } else { - notify('error', `Failed to submit proxy configuration. Try again. ${response}`); + notify('error', t('proxy.notifications.config_error')); console.log(`Failed to submit proxy configuration. Try again. ${response}`) } } catch (error: any) { @@ -96,9 +98,9 @@ const ProxyForm: React.FC = () => { const testProxy = async () => { await testProxyConfig().then((response) => { if (response.success) { - notify('success', 'Proxy configuration is working'); + notify('success', t('proxy.notifications.test_success')); } else { - notify('error', 'Failed to test proxy configuration. Try again.'); + notify('error', t('proxy.notifications.test_error')); } }); }; @@ -109,7 +111,7 @@ const ProxyForm: React.FC = () => { if (response.proxy_url) { setIsProxyConfigured(true); setProxy(response); - notify('success', 'Proxy configuration fetched successfully'); + notify('success', t('proxy.notifications.fetch_success')); } } catch (error: any) { notify('error', error); @@ -119,11 +121,11 @@ const ProxyForm: React.FC = () => { const removeProxy = async () => { await deleteProxyConfig().then((response) => { if (response) { - notify('success', 'Proxy configuration removed successfully'); + notify('success', t('proxy.notifications.remove_success')); setIsProxyConfigured(false); setProxy({ proxy_url: '', auth: false }); } else { - notify('error', 'Failed to remove proxy configuration. Try again.'); + notify('error', t('proxy.notifications.remove_error')); } }); } @@ -136,11 +138,11 @@ const ProxyForm: React.FC = () => { <> - Proxy Configuration + {t('proxy.title')} - - + + {tabIndex === 0 && ( isProxyConfigured ? ( @@ -149,8 +151,8 @@ const ProxyForm: React.FC = () => {
- Proxy URL - Requires Authentication + {t('proxy.table.proxy_url')} + {t('proxy.table.requires_auth')} @@ -162,39 +164,37 @@ const ProxyForm: React.FC = () => {
) : ( } - label="Requires Authentication?" + label={t('proxy.requires_auth')} /> {requiresAuth && ( <> { { fullWidth disabled={!proxyConfigForm.server_url || (requiresAuth && (!proxyConfigForm.username || !proxyConfigForm.password))} > - Add Proxy + {t('proxy.add_proxy')} ))} @@ -234,33 +234,33 @@ 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. + {t('proxy.coming_soon')} )} - If your proxy requires a username and password, always provide them separately from the proxy URL. + {t('proxy.alert.title')}
- The right way + {t('proxy.alert.right_way')}
- Proxy URL: http://proxy.com:1337 + {t('proxy.alert.proxy_url')} http://proxy.com:1337
- Username: myusername + {t('proxy.alert.username')} myusername
- Password: mypassword + {t('proxy.alert.password')} mypassword

- The wrong way + {t('proxy.alert.wrong_way')}
- Proxy URL: http://myusername:mypassword@proxy.com:1337 + {t('proxy.alert.proxy_url')} http://myusername:mypassword@proxy.com:1337
); }; -export default ProxyForm; +export default ProxyForm; \ No newline at end of file From 1529629d9e81621a9c06c7a1d35597433c875e8f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:10:41 +0530 Subject: [PATCH 179/500] feat: add german translation for apikey page --- public/locales/de.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index ef0cc31a..c0ba6a27 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -92,5 +92,30 @@ "remove_success": "Proxy-Konfiguration erfolgreich entfernt", "remove_error": "Fehler beim Entfernen der Proxy-Konfiguration. Bitte erneut versuchen." } + }, + "apikey": { + "title": "API-Schlüssel verwalten", + "default_name": "Maxun API-Schlüssel", + "table": { + "name": "API-Schlüssel Name", + "key": "API-Schlüssel", + "actions": "Aktionen" + }, + "actions": { + "copy": "Kopieren", + "show": "Anzeigen", + "hide": "Ausblenden", + "delete": "Löschen" + }, + "no_key_message": "Sie haben noch keinen API-Schlüssel generiert.", + "generate_button": "API-Schlüssel generieren", + "notifications": { + "fetch_error": "API-Schlüssel konnte nicht abgerufen werden - ${error}", + "generate_success": "API-Schlüssel erfolgreich generiert", + "generate_error": "API-Schlüssel konnte nicht generiert werden - ${error}", + "delete_success": "API-Schlüssel erfolgreich gelöscht", + "delete_error": "API-Schlüssel konnte nicht gelöscht werden - ${error}", + "copy_success": "API-Schlüssel erfolgreich kopiert" + } } } From 114cbdda6f40425b59a64ca89447e68efadb8f27 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:11:10 +0530 Subject: [PATCH 180/500] feat: add english translation for apikey page --- public/locales/en.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index ed5582bf..5dcce1a6 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -95,5 +95,30 @@ "remove_success": "Proxy configuration removed successfully", "remove_error": "Failed to remove proxy configuration. Try again." } + }, + "apikey": { + "title": "Manage Your API Key", + "default_name": "Maxun API Key", + "table": { + "name": "API Key Name", + "key": "API Key", + "actions": "Actions" + }, + "actions": { + "copy": "Copy", + "show": "Show", + "hide": "Hide", + "delete": "Delete" + }, + "no_key_message": "You haven't generated an API key yet.", + "generate_button": "Generate API Key", + "notifications": { + "fetch_error": "Failed to fetch API Key - ${error}", + "generate_success": "Generated API Key successfully", + "generate_error": "Failed to generate API Key - ${error}", + "delete_success": "API Key deleted successfully", + "delete_error": "Failed to delete API Key - ${error}", + "copy_success": "Copied API Key successfully" + } } } \ No newline at end of file From 03aa00e2f8dbb6bf09de05ee05d231dcd6b823fa Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:11:42 +0530 Subject: [PATCH 181/500] feat: add japanese translation for apikey page --- public/locales/ja.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 2d7adc25..53d0d023 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -93,5 +93,30 @@ "remove_success": "プロキシ設定が正常に削除されました", "remove_error": "プロキシ設定の削除に失敗しました。もう一度お試しください。" } + }, + "apikey": { + "title": "APIキーの管理", + "default_name": "Maxun APIキー", + "table": { + "name": "APIキー名", + "key": "APIキー", + "actions": "アクション" + }, + "actions": { + "copy": "コピー", + "show": "表示", + "hide": "非表示", + "delete": "削除" + }, + "no_key_message": "APIキーはまだ生成されていません。", + "generate_button": "APIキーを生成", + "notifications": { + "fetch_error": "APIキーの取得に失敗しました - ${error}", + "generate_success": "APIキーが正常に生成されました", + "generate_error": "APIキーの生成に失敗しました - ${error}", + "delete_success": "APIキーが正常に削除されました", + "delete_error": "APIキーの削除に失敗しました - ${error}", + "copy_success": "APIキーがコピーされました" + } } } From 33ffc445717dc0fd262b4925467a2f80723f3815 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:12:26 +0530 Subject: [PATCH 182/500] feat: add chinese translation for apikey page --- public/locales/zh.json | 55 ++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index 52b2ce3e..2e9ec2b2 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -71,27 +71,52 @@ "test_proxy": "测试代理", "remove_proxy": "删除代理", "table": { - "proxy_url": "代理URL", - "requires_auth": "需要认证" + "proxy_url": "代理URL", + "requires_auth": "需要认证" }, "coming_soon": "即将推出 - 开源版(基础轮换)和云版(高级轮换)。如果您不想管理基础设施,请加入我们的云服务等候名单以获得早期访问权限。", "join_waitlist": "加入Maxun Cloud等候名单", "alert": { - "title": "如果您的代理需要用户名和密码,请务必将它们与代理URL分开提供。", - "right_way": "正确方式", - "wrong_way": "错误方式", - "proxy_url": "代理URL:", - "username": "用户名:", - "password": "密码:" + "title": "如果您的代理需要用户名和密码,请务必将它们与代理URL分开提供。", + "right_way": "正确方式", + "wrong_way": "错误方式", + "proxy_url": "代理URL:", + "username": "用户名:", + "password": "密码:" }, "notifications": { - "config_success": "代理配置提交成功", - "config_error": "提交代理配置失败。请重试。", - "test_success": "代理配置运行正常", - "test_error": "测试代理配置失败。请重试。", - "fetch_success": "成功获取代理配置", - "remove_success": "成功删除代理配置", - "remove_error": "删除代理配置失败。请重试。" + "config_success": "代理配置提交成功", + "config_error": "提交代理配置失败。请重试。", + "test_success": "代理配置运行正常", + "test_error": "测试代理配置失败。请重试。", + "fetch_success": "成功获取代理配置", + "remove_success": "成功删除代理配置", + "remove_error": "删除代理配置失败。请重试。" + } + }, + "apikey": { + "title": "管理API密钥", + "default_name": "Maxun API密钥", + "table": { + "name": "API密钥名称", + "key": "API密钥", + "actions": "操作" + }, + "actions": { + "copy": "复制", + "show": "显示", + "hide": "隐藏", + "delete": "删除" + }, + "no_key_message": "您还未生成API密钥。", + "generate_button": "生成API密钥", + "notifications": { + "fetch_error": "获取API密钥失败 - ${error}", + "generate_success": "API密钥生成成功", + "generate_error": "生成API密钥失败 - ${error}", + "delete_success": "API密钥删除成功", + "delete_error": "删除API密钥失败 - ${error}", + "copy_success": "API密钥复制成功" } } } \ No newline at end of file From a2e3d0fe26829849c2fd4917d1f7d6c28e237bb1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:13:01 +0530 Subject: [PATCH 183/500] feat: add spanish translation for apikey page --- public/locales/es.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index bc62d2a7..5612fc25 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -93,5 +93,30 @@ "remove_success": "Configuración del proxy eliminada con éxito", "remove_error": "Error al eliminar la configuración del proxy. Inténtelo de nuevo." } - } + }, + "apikey": { + "title": "Gestionar tu Clave API", + "default_name": "Clave API de Maxun", + "table": { + "name": "Nombre de la Clave API", + "key": "Clave API", + "actions": "Acciones" + }, + "actions": { + "copy": "Copiar", + "show": "Mostrar", + "hide": "Ocultar", + "delete": "Eliminar" + }, + "no_key_message": "Aún no has generado una clave API.", + "generate_button": "Generar Clave API", + "notifications": { + "fetch_error": "Error al obtener la clave API - ${error}", + "generate_success": "Clave API generada con éxito", + "generate_error": "Error al generar la clave API - ${error}", + "delete_success": "Clave API eliminada con éxito", + "delete_error": "Error al eliminar la clave API - ${error}", + "copy_success": "Clave API copiada con éxito" + } +} } \ No newline at end of file From 52d024916193a718d9fafd6f3b7213e56df25c93 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:13:44 +0530 Subject: [PATCH 184/500] feat: add translation for apikey page --- src/components/organisms/ApiKey.tsx | 38 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index e6a00a91..37a72764 100644 --- a/src/components/organisms/ApiKey.tsx +++ b/src/components/organisms/ApiKey.tsx @@ -19,6 +19,7 @@ import styled from 'styled-components'; import axios from 'axios'; import { useGlobalInfoStore } from '../../context/globalInfo'; import { apiUrl } from '../../apiConfig'; +import { useTranslation } from 'react-i18next'; const Container = styled(Box)` display: flex; @@ -29,24 +30,21 @@ const Container = styled(Box)` `; const ApiKeyManager = () => { + const { t } = useTranslation(); const [apiKey, setApiKey] = useState(null); - const [apiKeyName, setApiKeyName] = useState('Maxun API Key'); + const [apiKeyName, setApiKeyName] = useState(t('apikey.default_name')); const [loading, setLoading] = useState(true); const [showKey, setShowKey] = useState(false); const [copySuccess, setCopySuccess] = useState(false); const { notify } = useGlobalInfoStore(); - - - - useEffect(() => { const fetchApiKey = async () => { try { const { data } = await axios.get(`${apiUrl}/auth/api-key`); setApiKey(data.api_key); } catch (error: any) { - notify('error', `Failed to fetch API Key - ${error.message}`); + notify('error', t('apikey.notifications.fetch_error', { error: error.message })); } finally { setLoading(false); } @@ -62,9 +60,9 @@ const ApiKeyManager = () => { const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); setApiKey(data.api_key); - notify('success', `Generated API Key successfully`); + notify('success', t('apikey.notifications.generate_success')); } catch (error: any) { - notify('error', `Failed to generate API Key - ${error.message}`); + notify('error', t('apikey.notifications.generate_error', { error: error.message })); } finally { setLoading(false); } @@ -75,9 +73,9 @@ const ApiKeyManager = () => { try { await axios.delete(`${apiUrl}/auth/delete-api-key`); setApiKey(null); - notify('success', 'API Key deleted successfully'); + notify('success', t('apikey.notifications.delete_success')); } catch (error: any) { - notify('error', `Failed to delete API Key - ${error.message}`); + notify('error', t('apikey.notifications.delete_error', { error: error.message })); } finally { setLoading(false); } @@ -88,7 +86,7 @@ const ApiKeyManager = () => { navigator.clipboard.writeText(apiKey); setCopySuccess(true); setTimeout(() => setCopySuccess(false), 2000); - notify('info', 'Copied API Key successfully'); + notify('info', t('apikey.notifications.copy_success')); } }; @@ -111,16 +109,16 @@ const ApiKeyManager = () => { return ( - Manage Your API Key + {t('apikey.title')} {apiKey ? ( - API Key Name - API Key - Actions + {t('apikey.table.name')} + {t('apikey.table.key')} + {t('apikey.table.actions')} @@ -128,17 +126,17 @@ const ApiKeyManager = () => { {apiKeyName} {showKey ? `${apiKey?.substring(0, 10)}...` : '***************'} - + - + setShowKey(!showKey)}> - + @@ -150,9 +148,9 @@ const ApiKeyManager = () => { ) : ( <> - You haven't generated an API key yet. + {t('apikey.no_key_message')} )} From 29ed5638df3caf2610f2b0306c687d42454419c0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:31:41 +0530 Subject: [PATCH 185/500] feat: add spanish translation for action description box --- public/locales/es.json | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index 5612fc25..111f831d 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -118,5 +118,29 @@ "delete_error": "Error al eliminar la clave API - ${error}", "copy_success": "Clave API copiada con éxito" } -} + }, + "action_description": { + "text": { + "title": "Capturar Texto", + "description": "Pase el cursor sobre los textos que desea extraer y haga clic para seleccionarlos" + }, + "screenshot": { + "title": "Capturar Pantalla", + "description": "Capture una captura de pantalla parcial o completa de la página actual." + }, + "list": { + "title": "Capturar Lista", + "description": "Pase el cursor sobre la lista que desea extraer. Una vez seleccionada, puede pasar el cursor sobre todos los textos dentro de la lista seleccionada. Haga clic para seleccionarlos." + }, + "default": { + "title": "¿Qué datos desea extraer?", + "description": "Un robot está diseñado para realizar una acción a la vez. Puede elegir cualquiera de las siguientes opciones." + }, + "list_stages": { + "initial": "Seleccione la lista que desea extraer junto con los textos que contiene", + "pagination": "Seleccione cómo puede el robot capturar el resto de la lista", + "limit": "Elija el número de elementos a extraer", + "complete": "Captura completada" + } + } } \ No newline at end of file From 906b752f89d2a56c769b2259c12b911a176f907d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:32:06 +0530 Subject: [PATCH 186/500] feat: add chinese translation for action description box --- public/locales/zh.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 2e9ec2b2..6cc3d56e 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -118,5 +118,29 @@ "delete_error": "删除API密钥失败 - ${error}", "copy_success": "API密钥复制成功" } + }, + "action_description": { + "text": { + "title": "捕获文本", + "description": "将鼠标悬停在要提取的文本上并点击选择" + }, + "screenshot": { + "title": "捕获截图", + "description": "捕获当前页面的部分或全部截图。" + }, + "list": { + "title": "捕获列表", + "description": "将鼠标悬停在要提取的列表上。选择后,您可以将鼠标悬停在所选列表中的所有文本上。点击选择它们。" + }, + "default": { + "title": "您想提取什么数据?", + "description": "机器人设计为一次执行一个操作。您可以选择以下任何选项。" + }, + "list_stages": { + "initial": "选择要提取的列表及其中的文本", + "pagination": "选择机器人如何捕获列表的其余部分", + "limit": "选择要提取的项目数量", + "complete": "捕获完成" + } } } \ No newline at end of file From 9e80bf3373e19b64b7d0adfbdd50615ce83eaa7e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:32:34 +0530 Subject: [PATCH 187/500] feat: add japanese translation for action description box --- public/locales/ja.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 53d0d023..fc25bec0 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -118,5 +118,29 @@ "delete_error": "APIキーの削除に失敗しました - ${error}", "copy_success": "APIキーがコピーされました" } + }, + "action_description": { + "text": { + "title": "テキストを取得", + "description": "抽出したいテキストにカーソルを合わせ、クリックして選択してください" + }, + "screenshot": { + "title": "スクリーンショットを取得", + "description": "現在のページの部分的または全体のスクリーンショットを取得します。" + }, + "list": { + "title": "リストを取得", + "description": "抽出したいリストにカーソルを合わせてください。選択後、選択したリスト内のすべてのテキストにカーソルを合わせることができます。クリックして選択してください。" + }, + "default": { + "title": "どのデータを抽出しますか?", + "description": "ロボットは一度に1つのアクションを実行するように設計されています。以下のオプションから選択できます。" + }, + "list_stages": { + "initial": "抽出したいリストとその中のテキストを選択してください", + "pagination": "ロボットがリストの残りをどのように取得するか選択してください", + "limit": "抽出するアイテムの数を選択してください", + "complete": "取得が完了しました" + } } } From 4b07690cfe41c287077fef5964a94de41386e69d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:32:57 +0530 Subject: [PATCH 188/500] feat: add english translation for action description box --- public/locales/en.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 5dcce1a6..b44798cf 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -120,5 +120,29 @@ "delete_error": "Failed to delete API Key - ${error}", "copy_success": "Copied API Key successfully" } + }, + "action_description": { + "text": { + "title": "Capture Text", + "description": "Hover over the texts you want to extract and click to select them" + }, + "screenshot": { + "title": "Capture Screenshot", + "description": "Capture a partial or full page screenshot of the current page." + }, + "list": { + "title": "Capture List", + "description": "Hover over the list you want to extract. Once selected, you can hover over all texts inside the list you selected. Click to select them." + }, + "default": { + "title": "What data do you want to extract?", + "description": "A robot is designed to perform one action at a time. You can choose any of the options below." + }, + "list_stages": { + "initial": "Select the list you want to extract along with the texts inside it", + "pagination": "Select how the robot can capture the rest of the list", + "limit": "Choose the number of items to extract", + "complete": "Capture is complete" + } } } \ No newline at end of file From 6091df8d9552fd8823d8a6ddb40967da12819b40 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:33:21 +0530 Subject: [PATCH 189/500] feat: add german translation for action description box --- public/locales/de.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index c0ba6a27..5662134e 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -117,5 +117,29 @@ "delete_error": "API-Schlüssel konnte nicht gelöscht werden - ${error}", "copy_success": "API-Schlüssel erfolgreich kopiert" } + }, + "action_description": { + "text": { + "title": "Text erfassen", + "description": "Fahren Sie über die Texte, die Sie extrahieren möchten, und klicken Sie, um sie auszuwählen" + }, + "screenshot": { + "title": "Screenshot erfassen", + "description": "Erfassen Sie einen Teil- oder Vollbildschirmfoto der aktuellen Seite." + }, + "list": { + "title": "Liste erfassen", + "description": "Fahren Sie über die Liste, die Sie extrahieren möchten. Nach der Auswahl können Sie über alle Texte in der ausgewählten Liste fahren. Klicken Sie zum Auswählen." + }, + "default": { + "title": "Welche Daten möchten Sie extrahieren?", + "description": "Ein Roboter ist darauf ausgelegt, eine Aktion nach der anderen auszuführen. Sie können eine der folgenden Optionen wählen." + }, + "list_stages": { + "initial": "Wählen Sie die Liste aus, die Sie extrahieren möchten, zusammen mit den darin enthaltenen Texten", + "pagination": "Wählen Sie aus, wie der Roboter den Rest der Liste erfassen kann", + "limit": "Wählen Sie die Anzahl der zu extrahierenden Elemente", + "complete": "Erfassung ist abgeschlossen" + } } } From 598609e29db157d82cde8b991d02f8e93c33b75f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 21:33:55 +0530 Subject: [PATCH 190/500] feat: add translation for action description box --- .../molecules/ActionDescriptionBox.tsx | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/molecules/ActionDescriptionBox.tsx b/src/components/molecules/ActionDescriptionBox.tsx index cad962c7..190c5838 100644 --- a/src/components/molecules/ActionDescriptionBox.tsx +++ b/src/components/molecules/ActionDescriptionBox.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { Typography, FormControlLabel, Checkbox, Box } from '@mui/material'; import { useActionContext } from '../../context/browserActions'; import MaxunLogo from "../../assets/maxunlogo.png"; +import { useTranslation } from 'react-i18next'; const CustomBoxContainer = styled.div` position: relative; @@ -44,6 +45,7 @@ const Content = styled.div` `; const ActionDescriptionBox = () => { + const { t } = useTranslation(); const { getText, getScreenshot, getList, captureStage } = useActionContext() as { getText: boolean; getScreenshot: boolean; @@ -52,36 +54,36 @@ const ActionDescriptionBox = () => { }; const messages = [ - { stage: 'initial' as const, text: 'Select the list you want to extract along with the texts inside it' }, - { stage: 'pagination' as const, text: 'Select how the robot can capture the rest of the list' }, - { stage: 'limit' as const, text: 'Choose the number of items to extract' }, - { stage: 'complete' as const, text: 'Capture is complete' }, + { stage: 'initial' as const, text: t('action_description.list_stages.initial') }, + { stage: 'pagination' as const, text: t('action_description.list_stages.pagination') }, + { stage: 'limit' as const, text: t('action_description.list_stages.limit') }, + { stage: 'complete' as const, text: t('action_description.list_stages.complete') }, ]; - const stages = messages.map(({ stage }) => stage); // Create a list of stages - const currentStageIndex = stages.indexOf(captureStage); // Get the index of the current stage + const stages = messages.map(({ stage }) => stage); + const currentStageIndex = stages.indexOf(captureStage); const renderActionDescription = () => { if (getText) { return ( <> - Capture Text - Hover over the texts you want to extract and click to select them + {t('action_description.text.title')} + {t('action_description.text.description')} ); } else if (getScreenshot) { return ( <> - Capture Screenshot - Capture a partial or full page screenshot of the current page. + {t('action_description.screenshot.title')} + {t('action_description.screenshot.description')} ); } else if (getList) { return ( <> - Capture List + {t('action_description.list.title')} - Hover over the list you want to extract. Once selected, you can hover over all texts inside the list you selected. Click to select them. + {t('action_description.list.description')} {messages.map(({ stage, text }, index) => ( @@ -89,7 +91,7 @@ const ActionDescriptionBox = () => { key={stage} control={ } @@ -102,8 +104,8 @@ const ActionDescriptionBox = () => { } else { return ( <> - What data do you want to extract? - A robot is designed to perform one action at a time. You can choose any of the options below. + {t('action_description.default.title')} + {t('action_description.default.description')} ); } @@ -111,7 +113,7 @@ const ActionDescriptionBox = () => { return ( - + {renderActionDescription()} @@ -120,4 +122,4 @@ const ActionDescriptionBox = () => { ); }; -export default ActionDescriptionBox; +export default ActionDescriptionBox; \ No newline at end of file From 7cc9947b2acf2938ef7b799f7d535655b37158bc Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 20 Dec 2024 21:59:34 +0530 Subject: [PATCH 191/500] feat: auto logout after certain hours of inactivity --- src/context/auth.tsx | 88 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 5c46e4de..a02711f3 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -1,4 +1,4 @@ -import { useReducer, createContext, useEffect } from 'react'; +import { useReducer, createContext, useEffect, useCallback } from 'react'; import axios from 'axios'; import { useNavigate } from 'react-router-dom'; import { apiUrl } from "../apiConfig"; @@ -14,12 +14,16 @@ interface ActionType { type InitialStateType = { user: any; + lastActivityTime?: number; }; const initialState = { user: null, + lastActivityTime: Date.now(), }; +const AUTO_LOGOUT_TIME = 4 * 60 * 60 * 1000; // 4 hours in milliseconds + const AuthContext = createContext<{ state: InitialStateType; dispatch: React.Dispatch; @@ -34,11 +38,13 @@ const reducer = (state: InitialStateType, action: ActionType) => { return { ...state, user: action.payload, + lastActivityTime: Date.now(), }; case 'LOGOUT': return { ...state, user: null, + lastActivityTime: undefined, }; default: return state; @@ -50,6 +56,39 @@ const AuthProvider = ({ children }: AuthProviderProps) => { const navigate = useNavigate(); axios.defaults.withCredentials = true; + const handleLogout = useCallback(async () => { + try { + await axios.get(`${apiUrl}/auth/logout`); + dispatch({ type: 'LOGOUT' }); + window.localStorage.removeItem('user'); + navigate('/login'); + } catch (err) { + console.error('Logout error:', err); + } + }, [navigate]); + + const checkAutoLogout = useCallback(() => { + if (state.user && state.lastActivityTime) { + const currentTime = Date.now(); + const timeSinceLastActivity = currentTime - state.lastActivityTime; + + if (timeSinceLastActivity >= AUTO_LOGOUT_TIME) { + handleLogout(); + } + } + }, [state.user, state.lastActivityTime, handleLogout]); + + // Update last activity time on user interactions + const updateActivityTime = useCallback(() => { + if (state.user) { + dispatch({ + type: 'LOGIN', + payload: state.user // Reuse existing user data + }); + } + }, [state.user]); + + // Initialize user from localStorage useEffect(() => { const storedUser = window.localStorage.getItem('user'); if (storedUser) { @@ -57,21 +96,54 @@ const AuthProvider = ({ children }: AuthProviderProps) => { } }, []); + // Set up activity listeners + useEffect(() => { + if (state.user) { + // List of events to track for user activity + const events = ['mousedown', 'keydown', 'scroll', 'touchstart']; + + // Throttled event handler + let timeoutId: NodeJS.Timeout; + const handleActivity = () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(updateActivityTime, 1000); + }; + + // Add event listeners + events.forEach(event => { + window.addEventListener(event, handleActivity); + }); + + // Set up periodic check for auto logout + const checkInterval = setInterval(checkAutoLogout, 60000); // Check every minute + + // Cleanup + return () => { + events.forEach(event => { + window.removeEventListener(event, handleActivity); + }); + clearInterval(checkInterval); + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + } + }, [state.user, updateActivityTime, checkAutoLogout]); + axios.interceptors.response.use( function (response) { return response; }, function (error) { const res = error.response; - if (res.status === 401 && res.config && !res.config.__isRetryRequest) { - return new Promise((resolve, reject) => { - axios - .get(`${apiUrl}/auth/logout`) + if (res?.status === 401 && res.config && !res.config.__isRetryRequest) { + return new Promise((_, reject) => { + handleLogout() .then(() => { console.log('/401 error > logout'); - dispatch({ type: 'LOGOUT' }); - window.localStorage.removeItem('user'); - navigate('/login'); + reject(error); }) .catch((err) => { console.error('AXIOS INTERCEPTORS ERROR:', err); From 25e109e29e095207ee93aad69c144b5fcf07a5b9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:04:10 +0530 Subject: [PATCH 192/500] feat: add german translation for right side panel --- public/locales/de.json | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 5662134e..21b9ccdf 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -141,5 +141,56 @@ "limit": "Wählen Sie die Anzahl der zu extrahierenden Elemente", "complete": "Erfassung ist abgeschlossen" } + }, + "right_panel": { + "buttons": { + "capture_list": "Liste erfassen", + "capture_text": "Text erfassen", + "capture_screenshot": "Screenshot erfassen", + "confirm": "Bestätigen", + "discard": "Verwerfen", + "confirm_capture": "Erfassung bestätigen", + "confirm_pagination": "Paginierung bestätigen", + "confirm_limit": "Limit bestätigen", + "finish_capture": "Erfassung abschließen" + }, + "screenshot": { + "capture_fullpage": "Vollständige Seite erfassen", + "capture_visible": "Sichtbaren Bereich erfassen", + "display_fullpage": "Vollständige Seite Screenshot", + "display_visible": "Sichtbarer Bereich Screenshot" + }, + "pagination": { + "title": "Wie können wir das nächste Listenelement auf der Seite finden?", + "click_next": "Auf 'Weiter' klicken, um zur nächsten Seite zu navigieren", + "click_load_more": "Auf 'Mehr laden' klicken, um weitere Elemente zu laden", + "scroll_down": "Nach unten scrollen, um mehr Elemente zu laden", + "scroll_up": "Nach oben scrollen, um mehr Elemente zu laden", + "none": "Keine weiteren Elemente zu laden" + }, + "limit": { + "title": "Wie viele Zeilen möchten Sie maximal extrahieren?", + "custom": "Benutzerdefiniert", + "enter_number": "Nummer eingeben" + }, + "fields": { + "label": "Bezeichnung", + "data": "Daten", + "field_label": "Feldbezeichnung", + "field_data": "Felddaten" + }, + "messages": { + "list_selected": "Liste erfolgreich ausgewählt" + }, + "errors": { + "select_pagination": "Bitte wählen Sie einen Paginierungstyp aus.", + "select_pagination_element": "Bitte wählen Sie zuerst das Paginierungselement aus.", + "select_limit": "Bitte wählen Sie ein Limit oder geben Sie ein benutzerdefiniertes Limit ein.", + "invalid_limit": "Bitte geben Sie ein gültiges Limit ein.", + "confirm_text_fields": "Bitte bestätigen Sie alle Textfelder", + "unable_create_settings": "Listeneinstellungen können nicht erstellt werden. Stellen Sie sicher, dass Sie ein Feld für die Liste definiert haben.", + "capture_text_discarded": "Texterfassung verworfen", + "capture_list_discarded": "Listenerfassung verworfen" + } } } From 50a2321425b98942490e63c98d2c5bf3dddb6988 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:13:18 +0530 Subject: [PATCH 193/500] feat: add german translation for right side panel --- public/locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index 21b9ccdf..c72edeea 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -152,7 +152,8 @@ "confirm_capture": "Erfassung bestätigen", "confirm_pagination": "Paginierung bestätigen", "confirm_limit": "Limit bestätigen", - "finish_capture": "Erfassung abschließen" + "finish_capture": "Erfassung abschließen", + "finish": "Fertig" }, "screenshot": { "capture_fullpage": "Vollständige Seite erfassen", From e8c8559a78849c5c4e4438a6f4b0f62c3da92efb Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:13:34 +0530 Subject: [PATCH 194/500] feat: add english translation for right side panel --- public/locales/en.json | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index b44798cf..661fe29a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -144,5 +144,57 @@ "limit": "Choose the number of items to extract", "complete": "Capture is complete" } + }, + "right_panel": { + "buttons": { + "capture_list": "Capture List", + "capture_text": "Capture Text", + "capture_screenshot": "Capture Screenshot", + "confirm": "Confirm", + "discard": "Discard", + "confirm_capture": "Confirm Capture", + "confirm_pagination": "Confirm Pagination", + "confirm_limit": "Confirm Limit", + "finish_capture": "Finish Capture", + "finish": "Finish" + }, + "screenshot": { + "capture_fullpage": "Capture Fullpage", + "capture_visible": "Capture Visible Part", + "display_fullpage": "Take Fullpage Screenshot", + "display_visible": "Take Visible Part Screenshot" + }, + "pagination": { + "title": "How can we find the next list item on the page?", + "click_next": "Click on next to navigate to the next page", + "click_load_more": "Click on load more to load more items", + "scroll_down": "Scroll down to load more items", + "scroll_up": "Scroll up to load more items", + "none": "No more items to load" + }, + "limit": { + "title": "What is the maximum number of rows you want to extract?", + "custom": "Custom", + "enter_number": "Enter number" + }, + "fields": { + "label": "Label", + "data": "Data", + "field_label": "Field Label", + "field_data": "Field Data" + }, + "messages": { + "list_selected": "List Selected Successfully" + }, + "errors": { + "select_pagination": "Please select a pagination type.", + "select_pagination_element": "Please select the pagination element first.", + "select_limit": "Please select a limit or enter a custom limit.", + "invalid_limit": "Please enter a valid limit.", + "confirm_text_fields": "Please confirm all text fields", + "unable_create_settings": "Unable to create list settings. Make sure you have defined a field for the list.", + "capture_text_discarded": "Capture Text Discarded", + "capture_list_discarded": "Capture List Discarded" + } } } \ No newline at end of file From 0b31fc261a1efacb83b5e5e8ca8edd53444138e6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:14:01 +0530 Subject: [PATCH 195/500] feat: add japanese translation for right side panel --- public/locales/ja.json | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index fc25bec0..8b7acde2 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -142,5 +142,57 @@ "limit": "抽出するアイテムの数を選択してください", "complete": "取得が完了しました" } + }, + "right_panel": { + "buttons": { + "capture_list": "リストを取得", + "capture_text": "テキストを取得", + "capture_screenshot": "スクリーンショットを取得", + "confirm": "確認", + "discard": "破棄", + "confirm_capture": "取得を確認", + "confirm_pagination": "ページネーションを確認", + "confirm_limit": "制限を確認", + "finish_capture": "取得を完了", + "finish": "完了" + }, + "screenshot": { + "capture_fullpage": "フルページを取得", + "capture_visible": "表示部分を取得", + "display_fullpage": "フルページスクリーンショットを撮影", + "display_visible": "表示部分のスクリーンショットを撮影" + }, + "pagination": { + "title": "次のリスト項目をページ上でどのように見つけますか?", + "click_next": "次へをクリックして次のページへ移動", + "click_load_more": "もっと読み込むをクリックして項目を追加", + "scroll_down": "下にスクロールして項目を追加", + "scroll_up": "上にスクロールして項目を追加", + "none": "これ以上読み込む項目はありません" + }, + "limit": { + "title": "抽出する最大行数はいくつですか?", + "custom": "カスタム", + "enter_number": "数値を入力" + }, + "fields": { + "label": "ラベル", + "data": "データ", + "field_label": "フィールドラベル", + "field_data": "フィールドデータ" + }, + "messages": { + "list_selected": "リストが正常に選択されました" + }, + "errors": { + "select_pagination": "ページネーションタイプを選択してください。", + "select_pagination_element": "まずページネーション要素を選択してください。", + "select_limit": "制限を選択するかカスタム制限を入力してください。", + "invalid_limit": "有効な制限を入力してください。", + "confirm_text_fields": "すべてのテキストフィールドを確認してください", + "unable_create_settings": "リスト設定を作成できません。リストのフィールドを定義したことを確認してください。", + "capture_text_discarded": "テキスト取得が破棄されました", + "capture_list_discarded": "リスト取得が破棄されました" + } } } From 6b560c1a248f2bab3814d022654bb806cc9dc43e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:14:21 +0530 Subject: [PATCH 196/500] feat: add spanish translation for right side panel --- public/locales/es.json | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 111f831d..7a779e14 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -142,5 +142,57 @@ "limit": "Elija el número de elementos a extraer", "complete": "Captura completada" } + }, + "right_panel": { + "buttons": { + "capture_list": "Capturar Lista", + "capture_text": "Capturar Texto", + "capture_screenshot": "Capturar Pantalla", + "confirm": "Confirmar", + "discard": "Descartar", + "confirm_capture": "Confirmar Captura", + "confirm_pagination": "Confirmar Paginación", + "confirm_limit": "Confirmar Límite", + "finish_capture": "Finalizar Captura", + "finish": "Finalizar" + }, + "screenshot": { + "capture_fullpage": "Capturar Página Completa", + "capture_visible": "Capturar Parte Visible", + "display_fullpage": "Capturar Screenshot de Página Completa", + "display_visible": "Capturar Screenshot de Parte Visible" + }, + "pagination": { + "title": "¿Cómo podemos encontrar el siguiente elemento de la lista en la página?", + "click_next": "Hacer clic en siguiente para navegar a la siguiente página", + "click_load_more": "Hacer clic en cargar más para cargar más elementos", + "scroll_down": "Desplazarse hacia abajo para cargar más elementos", + "scroll_up": "Desplazarse hacia arriba para cargar más elementos", + "none": "No hay más elementos para cargar" + }, + "limit": { + "title": "¿Cuál es el número máximo de filas que desea extraer?", + "custom": "Personalizado", + "enter_number": "Ingrese número" + }, + "fields": { + "label": "Etiqueta", + "data": "Datos", + "field_label": "Etiqueta del Campo", + "field_data": "Datos del Campo" + }, + "messages": { + "list_selected": "Lista seleccionada exitosamente" + }, + "errors": { + "select_pagination": "Por favor seleccione un tipo de paginación.", + "select_pagination_element": "Por favor seleccione primero el elemento de paginación.", + "select_limit": "Por favor seleccione un límite o ingrese un límite personalizado.", + "invalid_limit": "Por favor ingrese un límite válido.", + "confirm_text_fields": "Por favor confirme todos los campos de texto", + "unable_create_settings": "No se pueden crear las configuraciones de la lista. Asegúrese de haber definido un campo para la lista.", + "capture_text_discarded": "Captura de texto descartada", + "capture_list_discarded": "Captura de lista descartada" + } } } \ No newline at end of file From f33ce5274c30a65a02df0003bbc02db9250e2a26 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:14:41 +0530 Subject: [PATCH 197/500] feat: add chinese translation for right side panel --- public/locales/zh.json | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 6cc3d56e..b31ac8a5 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -142,5 +142,57 @@ "limit": "选择要提取的项目数量", "complete": "捕获完成" } + }, + "right_panel": { + "buttons": { + "capture_list": "捕获列表", + "capture_text": "捕获文本", + "capture_screenshot": "捕获截图", + "confirm": "确认", + "discard": "放弃", + "confirm_capture": "确认捕获", + "confirm_pagination": "确认分页", + "confirm_limit": "确认限制", + "finish_capture": "完成捕获", + "finish": "完成" + }, + "screenshot": { + "capture_fullpage": "捕获整页", + "capture_visible": "捕获可见部分", + "display_fullpage": "获取整页截图", + "display_visible": "获取可见部分截图" + }, + "pagination": { + "title": "如何在页面上找到下一个列表项?", + "click_next": "点击下一页导航到下一页", + "click_load_more": "点击加载更多来加载更多项目", + "scroll_down": "向下滚动加载更多项目", + "scroll_up": "向上滚动加载更多项目", + "none": "没有更多项目可加载" + }, + "limit": { + "title": "您想要提取的最大行数是多少?", + "custom": "自定义", + "enter_number": "输入数字" + }, + "fields": { + "label": "标签", + "data": "数据", + "field_label": "字段标签", + "field_data": "字段数据" + }, + "messages": { + "list_selected": "列表选择成功" + }, + "errors": { + "select_pagination": "请选择分页类型。", + "select_pagination_element": "请先选择分页元素。", + "select_limit": "请选择限制或输入自定义限制。", + "invalid_limit": "请输入有效的限制。", + "confirm_text_fields": "请确认所有文本字段", + "unable_create_settings": "无法创建列表设置。请确保您已为列表定义了字段。", + "capture_text_discarded": "文本捕获已放弃", + "capture_list_discarded": "列表捕获已放弃" + } } } \ No newline at end of file From fa4d8393675daaf538a51088e6b67546dd669fa0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:16:12 +0530 Subject: [PATCH 198/500] feat: add translation for right side panel --- src/components/organisms/RightSidePanel.tsx | 87 +++++++++++---------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 4aaf7b21..224e0954 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -22,6 +22,7 @@ import { emptyWorkflow } from "../../shared/constants"; import { getActiveWorkflow } from "../../api/workflow"; import DeleteIcon from '@mui/icons-material/Delete'; import ActionDescriptionBox from '../molecules/ActionDescriptionBox'; +import { useTranslation } from 'react-i18next'; const fetchWorkflow = (id: string, callback: (response: WorkflowFile) => void) => { getActiveWorkflow(id).then( @@ -60,6 +61,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps(); const { id, socket } = useSocketStore(); + const { t } = useTranslation(); const workflowHandler = useCallback((data: WorkflowFile) => { setWorkflow(data); @@ -139,7 +141,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setTextLabels(prevLabels => ({ ...prevLabels, [id]: label })); } if (!label.trim()) { - setErrors(prevErrors => ({ ...prevErrors, [id]: 'Label cannot be empty' })); + setErrors(prevErrors => ({ ...prevErrors, [id]: t('right_panel.errors.label_required') })); } else { setErrors(prevErrors => ({ ...prevErrors, [id]: '' })); } @@ -151,7 +153,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture updateBrowserTextStepLabel(id, label); setConfirmedTextSteps(prev => ({ ...prev, [id]: true })); } else { - setErrors(prevErrors => ({ ...prevErrors, [id]: 'Label cannot be empty' })); + setErrors(prevErrors => ({ ...prevErrors, [id]: t('right_panel.errors.label_required') })); } }; @@ -213,7 +215,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const stopCaptureAndEmitGetTextSettings = useCallback(() => { const hasUnconfirmedTextSteps = browserSteps.some(step => step.type === 'text' && !confirmedTextSteps[step.id]); if (hasUnconfirmedTextSteps) { - notify('error', 'Please confirm all text fields'); + notify('error', t('right_panel.errors.confirm_text_fields')); return; } stopGetText(); @@ -278,7 +280,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture if (settings) { socket?.emit('action', { action: 'scrapeList', settings }); } else { - notify('error', 'Unable to create list settings. Make sure you have defined a field for the list.'); + notify('error', t('right_panel.errors.unable_create_settings')); } handleStopGetList(); onFinishCapture(); @@ -296,13 +298,13 @@ export const RightSidePanel: React.FC = ({ onFinishCapture case 'pagination': if (!paginationType) { - notify('error', 'Please select a pagination type.'); + notify('error', t('right_panel.errors.select_pagination')); return; } const settings = getListSettingsObject(); const paginationSelector = settings.pagination?.selector; if (['clickNext', 'clickLoadMore'].includes(paginationType) && !paginationSelector) { - notify('error', 'Please select the pagination element first.'); + notify('error', t('right_panel.errors.select_pagination_element')); return; } stopPaginationMode(); @@ -314,12 +316,12 @@ export const RightSidePanel: React.FC = ({ onFinishCapture case 'limit': if (!limitType || (limitType === 'custom' && !customLimit)) { - notify('error', 'Please select a limit or enter a custom limit.'); + notify('error', t('right_panel.errors.select_limit')); return; } const limit = limitType === 'custom' ? parseInt(customLimit) : parseInt(limitType); if (isNaN(limit) || limit <= 0) { - notify('error', 'Please enter a valid limit.'); + notify('error', t('right_panel.errors.invalid_limit')); return; } stopLimitMode(); @@ -348,7 +350,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setTextLabels({}); setErrors({}); setConfirmedTextSteps({}); - notify('error', 'Capture Text Discarded'); + notify('error', t('right_panel.errors.capture_text_discarded')); }, [browserSteps, stopGetText, deleteBrowserStep]); const discardGetList = useCallback(() => { @@ -363,7 +365,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setShowLimitOptions(false); setCaptureStage('initial'); setConfirmedListTextFields({}); - notify('error', 'Capture List Discarded'); + notify('error', t('right_panel.errors.capture_list_discarded')); }, [browserSteps, stopGetList, deleteBrowserStep, resetListState]); @@ -402,7 +404,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture */} - {!getText && !getScreenshot && !getList && showCaptureList && } + {!getText && !getScreenshot && !getList && showCaptureList && } {getList && ( <> @@ -411,28 +413,29 @@ export const RightSidePanel: React.FC = ({ onFinishCapture onClick={handleConfirmListCapture} disabled={captureStage === 'initial' ? isConfirmCaptureDisabled : hasUnconfirmedListTextFields} > - {captureStage === 'initial' ? 'Confirm Capture' : - captureStage === 'pagination' ? 'Confirm Pagination' : - captureStage === 'limit' ? 'Confirm Limit' : 'Finish Capture'} + {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')} - + )} {showPaginationOptions && ( - How can we find the next list item on the page? - - - - - + {t('right_panel.pagination.title')} + + + + + )} {showLimitOptions && ( -

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

+

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

= ({ onFinishCapture } label="10" /> } label="100" />
- } label="Custom" /> + } label={t('right_panel.limit.custom')} /> {limitType === 'custom' && ( updateCustomLimit(e.target.value)} - placeholder="Enter number" + placeholder={t('right_panel.limit.enter_number')} sx={{ marginLeft: '10px', '& input': { @@ -467,21 +470,21 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> - - + + } - {!getText && !getScreenshot && !getList && showCaptureScreenshot && } + {!getText && !getScreenshot && !getList && showCaptureScreenshot && } {getScreenshot && ( - - - + + + )} @@ -492,7 +495,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture step.type === 'text' && ( <> handleTextLabelChange(step.id, e.target.value)} fullWidth @@ -510,7 +513,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture }} /> = ({ onFinishCapture /> {!confirmedTextSteps[step.id] && ( - - + + )} @@ -535,17 +538,19 @@ export const RightSidePanel: React.FC = ({ onFinishCapture - {`Take ${step.fullPage ? 'Fullpage' : 'Visible Part'} Screenshot`} + {step.fullPage ? + t('right_panel.screenshot.display_fullpage') : + t('right_panel.screenshot.display_visible')} )} {step.type === 'list' && ( <> - List Selected Successfully + {t('right_panel.messages.list_selected')} {Object.entries(step.fields).map(([key, field]) => ( handleTextLabelChange(field.id, e.target.value, step.id, key)} fullWidth @@ -560,7 +565,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture }} /> = ({ onFinishCapture onClick={() => handleListTextFieldConfirm(step.id, key)} disabled={!field.label?.trim()} > - Confirm + {t('right_panel.buttons.confirm')} )} From c0c10c8e80072426876ae695aa8dcbcc55d4ca9b Mon Sep 17 00:00:00 2001 From: Richard <115594235+richardzen@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:18:09 +0530 Subject: [PATCH 199/500] feat: move COMMIT_CONVENTION.md --- .github/COMMIT_CONVENTION.md | 130 +++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/COMMIT_CONVENTION.md diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md new file mode 100644 index 00000000..076ff9cf --- /dev/null +++ b/.github/COMMIT_CONVENTION.md @@ -0,0 +1,130 @@ +## Git Commit Message Convention + +> This is adapted from [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). + +## Summary + +The Conventional Commits specification is a lightweight convention on top of commit messages. +It provides an easy set of rules for creating an explicit commit history; +which makes it easier to write automated tools on top of. +This convention dovetails with [SemVer](http://semver.org), +by describing the features, fixes, and breaking changes made in commit messages. + +The commit message should be structured as follows: + +--- + +``` +[optional scope]: +[optional body] +[optional footer(s)] +``` +--- + +
+The commit contains the following structural elements, to communicate intent to the +consumers of your library: + +1. **fix:** a commit of the _type_ `fix` patches a bug in your codebase (this correlates with [`PATCH`](http://semver.org/#summary) in Semantic Versioning). +1. **feat:** a commit of the _type_ `feat` introduces a new feature to the codebase (this correlates with [`MINOR`](http://semver.org/#summary) in Semantic Versioning). +1. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with [`MAJOR`](http://semver.org/#summary) in Semantic Versioning). +A BREAKING CHANGE can be part of commits of any _type_. +1. _types_ other than `fix:` and `feat:` are allowed, for example [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) (based on the [the Angular convention](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)) recommends `build:`, `chore:`, + `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others. +1. _footers_ other than `BREAKING CHANGE: ` may be provided and follow a convention similar to + [git trailer format](https://git-scm.com/docs/git-interpret-trailers). + +Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). +

+A scope may be provided to a commit's type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`. + +## Examples + +### Commit message with description and breaking change footer +``` +feat: allow provided config object to extend other configs + +BREAKING CHANGE: `extends` key in config file is now used for extending other config files +``` + +### Commit message with `!` to draw attention to breaking change +``` +feat!: send an email to the customer when a product is shipped +``` + +### Commit message with scope and `!` to draw attention to breaking change +``` +feat(api)!: send an email to the customer when a product is shipped +``` + +### Commit message with both `!` and BREAKING CHANGE footer +``` +chore!: drop support for Node 6 + +BREAKING CHANGE: use JavaScript features not available in Node 6. +``` + +### Commit message with no body +``` +docs: correct spelling of CHANGELOG +``` + +### Commit message with scope +``` +feat(lang): add polish language +``` + +### Commit message with multi-paragraph body and multiple footers +``` +fix: prevent racing of requests + +Introduce a request id and a reference to latest request. Dismiss +incoming responses other than from latest request. + +Remove timeouts which were used to mitigate the racing issue but are +obsolete now. + +Reviewed-by: Z +Refs: #123 +``` + +## Specification + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). + +1. Commits MUST be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed + by the OPTIONAL scope, OPTIONAL `!`, and REQUIRED terminal colon and space. +1. The type `feat` MUST be used when a commit adds a new feature to your application or library. +1. The type `fix` MUST be used when a commit represents a bug fix for your application. +1. A scope MAY be provided after a type. A scope MUST consist of a noun describing a + section of the codebase surrounded by parenthesis, e.g., `fix(parser):` +1. A description MUST immediately follow the colon and space after the type/scope prefix. +The description is a short summary of the code changes, e.g., _fix: array parsing issue when multiple spaces were contained in string_. +1. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description. +1. A commit body is free-form and MAY consist of any number of newline separated paragraphs. +1. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of + a word token, followed by either a `:` or `#` separator, followed by a string value (this is inspired by the + [git trailer convention](https://git-scm.com/docs/git-interpret-trailers)). +1. A footer's token MUST use `-` in place of whitespace characters, e.g., `Acked-by` (this helps differentiate + the footer section from a multi-paragraph body). An exception is made for `BREAKING CHANGE`, which MAY also be used as a token. +1. A footer's value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer + token/separator pair is observed. +1. Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the + footer. +1. If included as a footer, a breaking change MUST consist of the uppercase text BREAKING CHANGE, followed by a colon, space, and description, e.g., +_BREAKING CHANGE: environment variables now take precedence over config files_. +1. If included in the type/scope prefix, breaking changes MUST be indicated by a + `!` immediately before the `:`. If `!` is used, `BREAKING CHANGE:` MAY be omitted from the footer section, + and the commit description SHALL be used to describe the breaking change. +1. Types other than `feat` and `fix` MAY be used in your commit messages, e.g., _docs: updated ref docs._ +1. The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase. +1. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer. + +## Why Use Conventional Commits + +* Automatically generating CHANGELOGs. +* Automatically determining a semantic version bump (based on the types of commits landed). +* Communicating the nature of changes to teammates, the public, and other stakeholders. +* Triggering build and publish processes. +* Making it easier for people to contribute to your projects, by allowing them to explore + a more structured commit history. From 3f34a6c720ca05a34aab703eeb389e5fd86fcbd8 Mon Sep 17 00:00:00 2001 From: Richard <115594235+richardzen@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:18:27 +0530 Subject: [PATCH 200/500] feat: delete .github/.github directory --- .github/.github/COMMIT_CONVENTION.md | 130 --------------------------- 1 file changed, 130 deletions(-) delete mode 100644 .github/.github/COMMIT_CONVENTION.md diff --git a/.github/.github/COMMIT_CONVENTION.md b/.github/.github/COMMIT_CONVENTION.md deleted file mode 100644 index 076ff9cf..00000000 --- a/.github/.github/COMMIT_CONVENTION.md +++ /dev/null @@ -1,130 +0,0 @@ -## Git Commit Message Convention - -> This is adapted from [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). - -## Summary - -The Conventional Commits specification is a lightweight convention on top of commit messages. -It provides an easy set of rules for creating an explicit commit history; -which makes it easier to write automated tools on top of. -This convention dovetails with [SemVer](http://semver.org), -by describing the features, fixes, and breaking changes made in commit messages. - -The commit message should be structured as follows: - ---- - -``` -[optional scope]: -[optional body] -[optional footer(s)] -``` ---- - -
-The commit contains the following structural elements, to communicate intent to the -consumers of your library: - -1. **fix:** a commit of the _type_ `fix` patches a bug in your codebase (this correlates with [`PATCH`](http://semver.org/#summary) in Semantic Versioning). -1. **feat:** a commit of the _type_ `feat` introduces a new feature to the codebase (this correlates with [`MINOR`](http://semver.org/#summary) in Semantic Versioning). -1. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with [`MAJOR`](http://semver.org/#summary) in Semantic Versioning). -A BREAKING CHANGE can be part of commits of any _type_. -1. _types_ other than `fix:` and `feat:` are allowed, for example [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) (based on the [the Angular convention](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)) recommends `build:`, `chore:`, - `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others. -1. _footers_ other than `BREAKING CHANGE: ` may be provided and follow a convention similar to - [git trailer format](https://git-scm.com/docs/git-interpret-trailers). - -Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). -

-A scope may be provided to a commit's type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`. - -## Examples - -### Commit message with description and breaking change footer -``` -feat: allow provided config object to extend other configs - -BREAKING CHANGE: `extends` key in config file is now used for extending other config files -``` - -### Commit message with `!` to draw attention to breaking change -``` -feat!: send an email to the customer when a product is shipped -``` - -### Commit message with scope and `!` to draw attention to breaking change -``` -feat(api)!: send an email to the customer when a product is shipped -``` - -### Commit message with both `!` and BREAKING CHANGE footer -``` -chore!: drop support for Node 6 - -BREAKING CHANGE: use JavaScript features not available in Node 6. -``` - -### Commit message with no body -``` -docs: correct spelling of CHANGELOG -``` - -### Commit message with scope -``` -feat(lang): add polish language -``` - -### Commit message with multi-paragraph body and multiple footers -``` -fix: prevent racing of requests - -Introduce a request id and a reference to latest request. Dismiss -incoming responses other than from latest request. - -Remove timeouts which were used to mitigate the racing issue but are -obsolete now. - -Reviewed-by: Z -Refs: #123 -``` - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). - -1. Commits MUST be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed - by the OPTIONAL scope, OPTIONAL `!`, and REQUIRED terminal colon and space. -1. The type `feat` MUST be used when a commit adds a new feature to your application or library. -1. The type `fix` MUST be used when a commit represents a bug fix for your application. -1. A scope MAY be provided after a type. A scope MUST consist of a noun describing a - section of the codebase surrounded by parenthesis, e.g., `fix(parser):` -1. A description MUST immediately follow the colon and space after the type/scope prefix. -The description is a short summary of the code changes, e.g., _fix: array parsing issue when multiple spaces were contained in string_. -1. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description. -1. A commit body is free-form and MAY consist of any number of newline separated paragraphs. -1. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of - a word token, followed by either a `:` or `#` separator, followed by a string value (this is inspired by the - [git trailer convention](https://git-scm.com/docs/git-interpret-trailers)). -1. A footer's token MUST use `-` in place of whitespace characters, e.g., `Acked-by` (this helps differentiate - the footer section from a multi-paragraph body). An exception is made for `BREAKING CHANGE`, which MAY also be used as a token. -1. A footer's value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer - token/separator pair is observed. -1. Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the - footer. -1. If included as a footer, a breaking change MUST consist of the uppercase text BREAKING CHANGE, followed by a colon, space, and description, e.g., -_BREAKING CHANGE: environment variables now take precedence over config files_. -1. If included in the type/scope prefix, breaking changes MUST be indicated by a - `!` immediately before the `:`. If `!` is used, `BREAKING CHANGE:` MAY be omitted from the footer section, - and the commit description SHALL be used to describe the breaking change. -1. Types other than `feat` and `fix` MAY be used in your commit messages, e.g., _docs: updated ref docs._ -1. The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase. -1. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer. - -## Why Use Conventional Commits - -* Automatically generating CHANGELOGs. -* Automatically determining a semantic version bump (based on the types of commits landed). -* Communicating the nature of changes to teammates, the public, and other stakeholders. -* Triggering build and publish processes. -* Making it easier for people to contribute to your projects, by allowing them to explore - a more structured commit history. From b39fca192571fc8db9cafb4d8a7f20d0b2ad69c7 Mon Sep 17 00:00:00 2001 From: Richard <115594235+richardzen@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:22:33 +0530 Subject: [PATCH 201/500] chore: cleanup some code --- src/context/browserActions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/browserActions.tsx b/src/context/browserActions.tsx index 55ca1b37..fd951f92 100644 --- a/src/context/browserActions.tsx +++ b/src/context/browserActions.tsx @@ -14,8 +14,8 @@ interface ActionContextProps { paginationType: PaginationType; limitType: LimitType; customLimit: string; - captureStage: CaptureStage; // New captureStage property - setCaptureStage: (stage: CaptureStage) => void; // Setter for captureStage + captureStage: CaptureStage; + setCaptureStage: (stage: CaptureStage) => void; startPaginationMode: () => void; startGetText: () => void; stopGetText: () => void; From 018b9cb56caadd787fc02b05d9107be8c04fb2c4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:26:42 +0530 Subject: [PATCH 202/500] feat: add chinese translation for save recording component --- public/locales/zh.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index b31ac8a5..01bf8e5d 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -194,5 +194,23 @@ "capture_text_discarded": "文本捕获已放弃", "capture_list_discarded": "列表捕获已放弃" } + }, + "save_recording": { + "title": "保存机器人", + "robot_name": "机器人名称", + "buttons": { + "save": "保存", + "confirm": "确认" + }, + "notifications": { + "save_success": "机器人保存成功" + }, + "errors": { + "user_not_logged": "用户未登录。无法保存录制。", + "exists_warning": "已存在同名机器人,请确认是否覆盖机器人。" + }, + "tooltips": { + "saving": "正在优化并保存工作流程" + } } } \ No newline at end of file From 06f0fe6df1586aff5995f043a380b1d47077f9e8 Mon Sep 17 00:00:00 2001 From: Richard <115594235+richardzen@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:27:04 +0530 Subject: [PATCH 203/500] chore: format `concurreny.ts` in maxun-core --- maxun-core/src/utils/concurrency.ts | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/maxun-core/src/utils/concurrency.ts b/maxun-core/src/utils/concurrency.ts index e1ebb87b..56c15fd9 100644 --- a/maxun-core/src/utils/concurrency.ts +++ b/maxun-core/src/utils/concurrency.ts @@ -3,36 +3,36 @@ */ export default class Concurrency { /** - * Maximum number of workers running in parallel. If set to `null`, there is no limit. - */ + * Maximum number of workers running in parallel. If set to `null`, there is no limit. + */ maxConcurrency: number = 1; /** - * Number of currently active workers. - */ + * Number of currently active workers. + */ activeWorkers: number = 0; /** - * Queue of jobs waiting to be completed. - */ + * Queue of jobs waiting to be completed. + */ private jobQueue: Function[] = []; /** - * "Resolve" callbacks of the waitForCompletion() promises. - */ + * "Resolve" callbacks of the waitForCompletion() promises. + */ private waiting: Function[] = []; /** - * Constructs a new instance of concurrency manager. - * @param {number} maxConcurrency Maximum number of workers running in parallel. - */ + * Constructs a new instance of concurrency manager. + * @param {number} maxConcurrency Maximum number of workers running in parallel. + */ constructor(maxConcurrency: number) { this.maxConcurrency = maxConcurrency; } /** - * Takes a waiting job out of the queue and runs it. - */ + * Takes a waiting job out of the queue and runs it. + */ private runNextJob(): void { const job = this.jobQueue.pop(); @@ -53,12 +53,12 @@ export default class Concurrency { } /** - * Pass a job (a time-demanding async function) to the concurrency manager. \ - * The time of the job's execution depends on the concurrency manager itself - * (given a generous enough `maxConcurrency` value, it might be immediate, - * but this is not guaranteed). - * @param worker Async function to be executed (job to be processed). - */ + * Pass a job (a time-demanding async function) to the concurrency manager. \ + * The time of the job's execution depends on the concurrency manager itself + * (given a generous enough `maxConcurrency` value, it might be immediate, + * but this is not guaranteed). + * @param worker Async function to be executed (job to be processed). + */ addJob(job: () => Promise): void { // console.debug("Adding a worker!"); this.jobQueue.push(job); @@ -72,11 +72,11 @@ export default class Concurrency { } /** - * Waits until there is no running nor waiting job. \ - * If the concurrency manager is idle at the time of calling this function, - * it waits until at least one job is completed (can be "presubscribed"). - * @returns Promise, resolved after there is no running/waiting worker. - */ + * Waits until there is no running nor waiting job. \ + * If the concurrency manager is idle at the time of calling this function, + * it waits until at least one job is completed (can be "presubscribed"). + * @returns Promise, resolved after there is no running/waiting worker. + */ waitForCompletion(): Promise { return new Promise((res) => { this.waiting.push(res); From 81a0a06da6e7d369d0a0b11ade7dcdc6379a3366 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:27:05 +0530 Subject: [PATCH 204/500] feat: add german translation for save recording component --- public/locales/de.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index c72edeea..5148b2d2 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -193,5 +193,23 @@ "capture_text_discarded": "Texterfassung verworfen", "capture_list_discarded": "Listenerfassung verworfen" } + }, + "save_recording": { + "title": "Roboter speichern", + "robot_name": "Roboter Name", + "buttons": { + "save": "Speichern", + "confirm": "Bestätigen" + }, + "notifications": { + "save_success": "Roboter erfolgreich gespeichert" + }, + "errors": { + "user_not_logged": "Benutzer nicht angemeldet. Aufnahme kann nicht gespeichert werden.", + "exists_warning": "Ein Roboter mit diesem Namen existiert bereits, bitte bestätigen Sie das Überschreiben des Roboters." + }, + "tooltips": { + "saving": "Workflow wird optimiert und gespeichert" + } } } From 855a1adca2fd607114a85b00a6294a8eeec153a1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:27:27 +0530 Subject: [PATCH 205/500] feat: add spanish translation for save recording component --- public/locales/es.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 7a779e14..5a42b431 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -194,5 +194,23 @@ "capture_text_discarded": "Captura de texto descartada", "capture_list_discarded": "Captura de lista descartada" } + }, + "save_recording": { + "title": "Guardar Robot", + "robot_name": "Nombre del Robot", + "buttons": { + "save": "Guardar", + "confirm": "Confirmar" + }, + "notifications": { + "save_success": "Robot guardado exitosamente" + }, + "errors": { + "user_not_logged": "Usuario no conectado. No se puede guardar la grabación.", + "exists_warning": "Ya existe un robot con este nombre, por favor confirme la sobrescritura del robot." + }, + "tooltips": { + "saving": "Optimizando y guardando el flujo de trabajo" + } } } \ No newline at end of file From 05de9fcb74c4bd629559cd04993ab9486315e1a2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:27:42 +0530 Subject: [PATCH 206/500] feat: add japanese translation for save recording component --- public/locales/ja.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 8b7acde2..46a75b15 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -194,5 +194,23 @@ "capture_text_discarded": "テキスト取得が破棄されました", "capture_list_discarded": "リスト取得が破棄されました" } + }, + "save_recording": { + "title": "ロボットを保存", + "robot_name": "ロボット名", + "buttons": { + "save": "保存", + "confirm": "確認" + }, + "notifications": { + "save_success": "ロボットが正常に保存されました" + }, + "errors": { + "user_not_logged": "ユーザーがログインしていません。録画を保存できません。", + "exists_warning": "この名前のロボットは既に存在します。ロボットの上書きを確認してください。" + }, + "tooltips": { + "saving": "ワークフローを最適化して保存中" + } } } From 73bdc680d3c15aaf8add117cfb55d477a6b8e76e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:27:59 +0530 Subject: [PATCH 207/500] feat: add english translation for save recording component --- public/locales/en.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 661fe29a..3a65f898 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -196,5 +196,23 @@ "capture_text_discarded": "Capture Text Discarded", "capture_list_discarded": "Capture List Discarded" } + }, + "save_recording": { + "title": "Save Robot", + "robot_name": "Robot Name", + "buttons": { + "save": "Save", + "confirm": "Confirm" + }, + "notifications": { + "save_success": "Robot saved successfully" + }, + "errors": { + "user_not_logged": "User not logged in. Cannot save recording.", + "exists_warning": "Robot with this name already exists, please confirm the Robot's overwrite." + }, + "tooltips": { + "saving": "Optimizing and saving the workflow" + } } } \ No newline at end of file From 00c187167cd72c8fb6ce77f10a7dd4394549d003 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:28:31 +0530 Subject: [PATCH 208/500] feat: add translation for save recording component --- src/components/molecules/SaveRecording.tsx | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index cfebc867..b3037060 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -9,13 +9,14 @@ import { TextField, Typography } from "@mui/material"; import { WarningText } from "../atoms/texts"; import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; interface SaveRecordingProps { fileName: string; } export const SaveRecording = ({ fileName }: SaveRecordingProps) => { - + const { t } = useTranslation(); const [openModal, setOpenModal] = useState(false); const [needConfirm, setNeedConfirm] = useState(false); const [recordingName, setRecordingName] = useState(fileName); @@ -46,7 +47,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { }; const exitRecording = useCallback(async () => { - notify('success', 'Robot saved successfully'); + notify('success', t('save_recording.notifications.save_success')); if (browserId) { await stopRecording(browserId); } @@ -63,7 +64,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { setWaitingForSave(true); console.log(`Saving the recording as ${recordingName} for userId ${user.id}`); } else { - console.error('User not logged in. Cannot save recording.'); + console.error(t('save_recording.notifications.user_not_logged')); } }; @@ -77,34 +78,38 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { return (
setOpenModal(false)} modalStyle={modalStyle}>
- Save Robot + {t('save_recording.title')} {needConfirm ? ( - + - Robot with this name already exists, please confirm the Robot's overwrite. + {t('save_recording.warnings.robot_exists')} ) - : + : } {waitingForSave && - + From 0af6cd60710c03ea016b78de7643f140f2188192 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 20 Dec 2024 22:35:59 +0530 Subject: [PATCH 209/500] feat: add translation for save recording component --- 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 b3037060..8e1eb462 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -97,7 +97,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { ? ( @@ -105,7 +105,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { ) : } {waitingForSave && From eb0b58befcc872569b9ffde3f64210f31cda9025 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:15:25 +0530 Subject: [PATCH 210/500] feat: add chinese translation for browser recording save --- public/locales/zh.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index 01bf8e5d..09840b6f 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -154,7 +154,8 @@ "confirm_pagination": "确认分页", "confirm_limit": "确认限制", "finish_capture": "完成捕获", - "finish": "完成" + "finish": "完成", + "cancel": "取消" }, "screenshot": { "capture_fullpage": "捕获整页", @@ -212,5 +213,13 @@ "tooltips": { "saving": "正在优化并保存工作流程" } + }, + "browser_recording": { + "modal": { + "confirm_discard": "您确定要放弃录制吗?" + }, + "notifications": { + "terminated": "当前录制已终止" + } } } \ No newline at end of file From bda5d6f001337f99b724d873aa5a0bb846499ee8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:16:02 +0530 Subject: [PATCH 211/500] feat: add german translation for browser recording save --- public/locales/de.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index 5148b2d2..1ade8e2a 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -153,7 +153,8 @@ "confirm_pagination": "Paginierung bestätigen", "confirm_limit": "Limit bestätigen", "finish_capture": "Erfassung abschließen", - "finish": "Fertig" + "finish": "Fertig", + "cancel": "Abbrechen" }, "screenshot": { "capture_fullpage": "Vollständige Seite erfassen", @@ -211,5 +212,13 @@ "tooltips": { "saving": "Workflow wird optimiert und gespeichert" } + }, + "browser_recording": { + "modal": { + "confirm_discard": "Sind Sie sicher, dass Sie die Aufnahme verwerfen möchten?" + }, + "notifications": { + "terminated": "Aktuelle Aufnahme wurde beendet" + } } } From f1b028d2d79267ca586faa708707e0bc6ff14e25 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:16:38 +0530 Subject: [PATCH 212/500] feat: add spanish translation for browser recording save --- public/locales/es.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/public/locales/es.json b/public/locales/es.json index 5a42b431..fb68ef01 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -154,7 +154,8 @@ "confirm_pagination": "Confirmar Paginación", "confirm_limit": "Confirmar Límite", "finish_capture": "Finalizar Captura", - "finish": "Finalizar" + "finish": "Finalizar", + "cancel": "Cancelar" }, "screenshot": { "capture_fullpage": "Capturar Página Completa", @@ -212,5 +213,13 @@ "tooltips": { "saving": "Optimizando y guardando el flujo de trabajo" } - } + }, + "browser_recording": { + "modal": { + "confirm_discard": "¿Está seguro de que desea descartar la grabación?" + }, + "notifications": { + "terminated": "La grabación actual fue terminada" + } + } } \ No newline at end of file From 0870e51d27a587a18efaf7684d796a05dec3cf70 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:16:55 +0530 Subject: [PATCH 213/500] feat: add japanese translation for browser recording save --- public/locales/ja.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index 46a75b15..0202160f 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -154,7 +154,8 @@ "confirm_pagination": "ページネーションを確認", "confirm_limit": "制限を確認", "finish_capture": "取得を完了", - "finish": "完了" + "finish": "完了", + "cancel": "キャンセル" }, "screenshot": { "capture_fullpage": "フルページを取得", @@ -212,5 +213,13 @@ "tooltips": { "saving": "ワークフローを最適化して保存中" } + }, + "browser_recording": { + "modal": { + "confirm_discard": "録画を破棄してもよろしいですか?" + }, + "notifications": { + "terminated": "現在の録画は終了しました" + } } } From 586f1c16c2320fe6699d0d2c4b9b1444a4ece49f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:17:14 +0530 Subject: [PATCH 214/500] feat: add english translation for browser recording save --- public/locales/en.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 3a65f898..4b856008 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -156,7 +156,8 @@ "confirm_pagination": "Confirm Pagination", "confirm_limit": "Confirm Limit", "finish_capture": "Finish Capture", - "finish": "Finish" + "finish": "Finish", + "cancel": "Cancel" }, "screenshot": { "capture_fullpage": "Capture Fullpage", @@ -214,5 +215,13 @@ "tooltips": { "saving": "Optimizing and saving the workflow" } + }, + "browser_recording": { + "modal": { + "confirm_discard": "Are you sure you want to discard the recording?" + }, + "notifications": { + "terminated": "Current Recording was terminated" + } } } \ No newline at end of file From a614d067e103ff14e8ff1a738c2b26679ac0efc1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 00:17:48 +0530 Subject: [PATCH 215/500] feat: add translation for browser recording save --- .../molecules/BrowserRecordingSave.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/molecules/BrowserRecordingSave.tsx b/src/components/molecules/BrowserRecordingSave.tsx index 03758717..e1eff20e 100644 --- a/src/components/molecules/BrowserRecordingSave.tsx +++ b/src/components/molecules/BrowserRecordingSave.tsx @@ -5,8 +5,10 @@ import { useGlobalInfoStore } from '../../context/globalInfo'; import { stopRecording } from "../../api/recording"; import { useNavigate } from 'react-router-dom'; import { GenericModal } from "../atoms/GenericModal"; +import { useTranslation } from 'react-i18next'; const BrowserRecordingSave = () => { + const { t } = useTranslation(); const [openModal, setOpenModal] = useState(false); const { recordingName, browserId, setBrowserId, notify } = useGlobalInfoStore(); const navigate = useNavigate(); @@ -14,7 +16,7 @@ const BrowserRecordingSave = () => { const goToMainMenu = async () => { if (browserId) { await stopRecording(browserId); - notify('warning', 'Current Recording was terminated'); + notify('warning', t('browser_recording.notifications.terminated')); setBrowserId(null); } navigate('/'); @@ -25,30 +27,29 @@ const BrowserRecordingSave = () => {
setOpenModal(false)} modalStyle={modalStyle}> - Are you sure you want to discard the recording? + {t('browser_recording.modal.confirm_discard')} @@ -60,7 +61,7 @@ const BrowserRecordingSave = () => { ); } -export default BrowserRecordingSave +export default BrowserRecordingSave; const modalStyle = { top: '25%', From 8b8a4d95e326906e8db1882282ebfe33236b831b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:30:34 +0530 Subject: [PATCH 216/500] feat: add chinese translation for interpretation log --- public/locales/zh.json | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index 09840b6f..ce4adc79 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -216,10 +216,28 @@ }, "browser_recording": { "modal": { - "confirm_discard": "您确定要放弃录制吗?" + "confirm_discard": "您确定要放弃录制吗?" }, "notifications": { - "terminated": "当前录制已终止" + "terminated": "当前录制已终止" + } + }, + "interpretation_log": { + "titles": { + "output_preview": "输出数据预览", + "screenshot": "截图" + }, + "messages": { + "additional_rows": "完成录制后将提取更多数据行。", + "successful_training": "您已成功训练机器人执行操作!点击下方按钮预览机器人将提取的数据。", + "no_selection": "看起来您还没有选择要提取的内容。选择后,机器人将在此处显示您的选择预览。" + }, + "data_sections": { + "binary_received": "---------- 已接收二进制输出数据 ----------", + "serializable_received": "---------- 已接收可序列化输出数据 ----------", + "mimetype": "MIME类型:", + "image_below": "图片显示如下:", + "separator": "--------------------------------------------------" } } } \ No newline at end of file From 2c5f4f1afcaaf47b208d54ecd5b1981966b0a360 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:30:56 +0530 Subject: [PATCH 217/500] feat: add german translation for interpretation log --- public/locales/de.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 1ade8e2a..6ef44428 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -220,5 +220,23 @@ "notifications": { "terminated": "Aktuelle Aufnahme wurde beendet" } + }, + "interpretation_log": { + "titles": { + "output_preview": "Vorschau der Ausgabedaten", + "screenshot": "Bildschirmfoto" + }, + "messages": { + "additional_rows": "Weitere Datenzeilen werden nach Abschluss der Aufnahme extrahiert.", + "successful_training": "Sie haben den Roboter erfolgreich für Aktionen trainiert! Klicken Sie auf die Schaltfläche unten, um eine Vorschau der Daten zu erhalten, die Ihr Roboter extrahieren wird.", + "no_selection": "Sie haben noch nichts zur Extraktion ausgewählt. Sobald Sie dies tun, wird der Roboter hier eine Vorschau Ihrer Auswahl anzeigen." + }, + "data_sections": { + "binary_received": "---------- Binäre Ausgabedaten empfangen ----------", + "serializable_received": "---------- Serialisierbare Ausgabedaten empfangen ----------", + "mimetype": "Medientyp: ", + "image_below": "Bild wird unten angezeigt:", + "separator": "--------------------------------------------------" + } } } From e65b1d57c28a4b3dde0725732be80884e4804b8d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:31:11 +0530 Subject: [PATCH 218/500] feat: add spanish translation for interpretation log --- public/locales/es.json | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/public/locales/es.json b/public/locales/es.json index fb68ef01..79b560dd 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -216,10 +216,28 @@ }, "browser_recording": { "modal": { - "confirm_discard": "¿Está seguro de que desea descartar la grabación?" + "confirm_discard": "¿Está seguro de que desea descartar la grabación?" }, "notifications": { - "terminated": "La grabación actual fue terminada" + "terminated": "La grabación actual fue terminada" } - } + }, + "interpretation_log": { + "titles": { + "output_preview": "Vista Previa de Datos de Salida", + "screenshot": "Captura de pantalla" + }, + "messages": { + "additional_rows": "Se extraerán filas adicionales de datos una vez que termine la grabación.", + "successful_training": "¡Has entrenado exitosamente al robot para realizar acciones! Haz clic en el botón de abajo para obtener una vista previa de los datos que tu robot extraerá.", + "no_selection": "Parece que aún no has seleccionado nada para extraer. Una vez que lo hagas, el robot mostrará una vista previa de tus selecciones aquí." + }, + "data_sections": { + "binary_received": "---------- Datos binarios de salida recibidos ----------", + "serializable_received": "---------- Datos serializables de salida recibidos ----------", + "mimetype": "tipo MIME: ", + "image_below": "La imagen se muestra a continuación:", + "separator": "--------------------------------------------------" + } + } } \ No newline at end of file From 2cf6b2c8c6149d2c18aae3e9d5d049c106e2e615 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:31:33 +0530 Subject: [PATCH 219/500] feat: add japanese translation for interpretation log --- public/locales/ja.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 0202160f..0de2aa7c 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -221,5 +221,23 @@ "notifications": { "terminated": "現在の録画は終了しました" } + }, + "interpretation_log": { + "titles": { + "output_preview": "出力データプレビュー", + "screenshot": "スクリーンショット" + }, + "messages": { + "additional_rows": "記録が完了すると、追加のデータ行が抽出されます。", + "successful_training": "ロボットのアクショントレーニングが成功しました!下のボタンをクリックすると、ロボットが抽出するデータのプレビューが表示されます。", + "no_selection": "まだ抽出対象が選択されていません。選択すると、ロボットがここで選択内容のプレビューを表示します。" + }, + "data_sections": { + "binary_received": "---------- バイナリ出力データを受信 ----------", + "serializable_received": "---------- シリアライズ可能な出力データを受信 ----------", + "mimetype": "MIMEタイプ: ", + "image_below": "画像は以下に表示されます:", + "separator": "--------------------------------------------------" + } } } From 2d35f29aea8d4df7073d513784df0c24145f62e7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:31:49 +0530 Subject: [PATCH 220/500] feat: add english translation for interpretation log --- public/locales/en.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 4b856008..abc06082 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -223,5 +223,23 @@ "notifications": { "terminated": "Current Recording was terminated" } + }, + "interpretation_log": { + "titles": { + "output_preview": "Output Data Preview", + "screenshot": "Screenshot" + }, + "messages": { + "additional_rows": "Additional rows of data will be extracted once you finish recording.", + "successful_training": "You've successfully trained the robot to perform actions! Click on the button below to get a preview of the data your robot will extract.", + "no_selection": "It looks like you have not selected anything for extraction yet. Once you do, the robot will show a preview of your selections here." + }, + "data_sections": { + "binary_received": "---------- Binary output data received ----------", + "serializable_received": "---------- Serializable output data received ----------", + "mimetype": "mimetype: ", + "image_below": "Image is rendered below:", + "separator": "--------------------------------------------------" + } } } \ No newline at end of file From c367af3bc432a22c6b5988e3aa3a03e8ee773d68 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:32:08 +0530 Subject: [PATCH 221/500] feat: add translation for interpretation log --- .../molecules/InterpretationLog.tsx | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 24ba848b..0a771535 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -17,6 +17,7 @@ import StorageIcon from '@mui/icons-material/Storage'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import { SidePanelHeader } from './SidePanelHeader'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useTranslation } from 'react-i18next'; interface InterpretationLogProps { isOpen: boolean; @@ -24,6 +25,7 @@ interface InterpretationLogProps { } export const InterpretationLog: React.FC = ({ isOpen, setIsOpen }) => { + const { t } = useTranslation(); const [log, setLog] = useState(''); const [customValue, setCustomValue] = useState(''); const [tableData, setTableData] = useState([]); @@ -63,28 +65,29 @@ export const InterpretationLog: React.FC = ({ isOpen, se const handleSerializableCallback = useCallback((data: any) => { setLog((prevState) => - prevState + '\n' + '---------- Serializable output data received ----------' + '\n' - + JSON.stringify(data, null, 2) + '\n' + '--------------------------------------------------'); + prevState + '\n' + t('interpretation_log.data_sections.serializable_received') + '\n' + + JSON.stringify(data, null, 2) + '\n' + t('interpretation_log.data_sections.separator')); if (Array.isArray(data)) { setTableData(data); } scrollLogToBottom(); - }, [log, scrollLogToBottom]); + }, [log, scrollLogToBottom, t]); const handleBinaryCallback = useCallback(({ data, mimetype }: any) => { const base64String = Buffer.from(data).toString('base64'); const imageSrc = `data:${mimetype};base64,${base64String}`; setLog((prevState) => - prevState + '\n' + '---------- Binary output data received ----------' + '\n' - + `mimetype: ${mimetype}` + '\n' + 'Image is rendered below:' + '\n' - + '------------------------------------------------'); + prevState + '\n' + t('interpretation_log.data_sections.binary_received') + '\n' + + t('interpretation_log.data_sections.mimetype') + mimetype + '\n' + + t('interpretation_log.data_sections.image_below') + '\n' + + t('interpretation_log.data_sections.separator')); setBinaryData(imageSrc); scrollLogToBottom(); - }, [log, scrollLogToBottom]); + }, [log, scrollLogToBottom, t]); const handleCustomValueChange = (event: React.ChangeEvent) => { @@ -136,7 +139,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se }, }} > - Output Data Preview + + {t('interpretation_log.titles.output_preview')} = ({ isOpen, se }, }} > - - Output Data Preview - + + + {t('interpretation_log.titles.output_preview')} +
= ({ isOpen, se { binaryData ? (
- Screenshot - Binary Output + + {t('interpretation_log.titles.screenshot')} + + {t('interpretation_log.titles.screenshot')}
) : tableData.length > 0 ? ( <> @@ -193,7 +200,9 @@ export const InterpretationLog: React.FC = ({ isOpen, se
- Additional rows of data will be extracted once you finish recording. + + {t('interpretation_log.messages.additional_rows')} + ) : ( @@ -201,13 +210,13 @@ export const InterpretationLog: React.FC = ({ isOpen, se {hasScrapeListAction || hasScrapeSchemaAction || hasScreenshotAction ? ( <> - You've successfully trained the robot to perform actions! Click on the button below to get a preview of the data your robot will extract. + {t('interpretation_log.messages.successful_training')} ) : ( - It looks like you have not selected anything for extraction yet. Once you do, the robot will show a preview of your selections here. + {t('interpretation_log.messages.no_selection')} )} @@ -219,4 +228,4 @@ export const InterpretationLog: React.FC = ({ isOpen, se ); -} \ No newline at end of file +}; \ No newline at end of file From 14d8940ac4430a5e54036f70b37275c064a111c7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:41:20 +0530 Subject: [PATCH 222/500] feat: add english translation for interpretation buttons --- public/locales/en.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index abc06082..b5bffd42 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -241,5 +241,23 @@ "image_below": "Image is rendered below:", "separator": "--------------------------------------------------" } + }, + "interpretation_buttons": { + "buttons": { + "preview": "Get Preview of Output Data", + "yes": "Yes", + "no": "No" + }, + "messages": { + "extracting": "Extracting data...please wait for 10secs to 1min", + "restart_required": "Please restart the interpretation after updating the recording", + "run_finished": "Run finished", + "run_failed": "Run failed to start" + }, + "modal": { + "use_previous": "Do you want to use your previous selection as a condition for performing this action?", + "previous_action": "Your previous action was: ", + "element_text": "on an element with text " + } } } \ No newline at end of file From 395874e50a3036d281c129a3941e17349bf4c7a1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:41:46 +0530 Subject: [PATCH 223/500] feat: add japanese translation for interpretation buttons --- public/locales/ja.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 0de2aa7c..274a8d06 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -239,5 +239,23 @@ "image_below": "画像は以下に表示されます:", "separator": "--------------------------------------------------" } + }, + "interpretation_buttons": { + "buttons": { + "preview": "出力データのプレビューを取得", + "yes": "はい", + "no": "いいえ" + }, + "messages": { + "extracting": "データ抽出中...10秒から1分ほどお待ちください", + "restart_required": "録画を更新した後、解釈を再起動してください", + "run_finished": "実行完了", + "run_failed": "実行の開始に失敗しました" + }, + "modal": { + "use_previous": "この操作の条件として前回の選択を使用しますか?", + "previous_action": "前回の操作: ", + "element_text": "テキスト要素 " + } } } From 6956e42ff0cc7b4507b9bedee65f48ed483364da Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:42:03 +0530 Subject: [PATCH 224/500] feat: add spanish translation for interpretation buttons --- public/locales/es.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 79b560dd..f1f744ed 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -239,5 +239,23 @@ "image_below": "La imagen se muestra a continuación:", "separator": "--------------------------------------------------" } + }, + "interpretation_buttons": { + "buttons": { + "preview": "Obtener Vista Previa de Datos de Salida", + "yes": "Sí", + "no": "No" + }, + "messages": { + "extracting": "Extrayendo datos...espere de 10 segundos a 1 minuto", + "restart_required": "Por favor, reinicie la interpretación después de actualizar la grabación", + "run_finished": "Ejecución finalizada", + "run_failed": "Error al iniciar la ejecución" + }, + "modal": { + "use_previous": "¿Desea usar su selección anterior como condición para realizar esta acción?", + "previous_action": "Su acción anterior fue: ", + "element_text": "en un elemento con texto " + } } } \ No newline at end of file From 3d584f150257ee34b21bd58ddf30623524938c4a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:42:18 +0530 Subject: [PATCH 225/500] feat: add german translation for interpretation buttons --- public/locales/de.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 6ef44428..697473f2 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -238,5 +238,23 @@ "image_below": "Bild wird unten angezeigt:", "separator": "--------------------------------------------------" } + }, + "interpretation_buttons": { + "buttons": { + "preview": "Vorschau der Ausgabedaten anzeigen", + "yes": "Ja", + "no": "Nein" + }, + "messages": { + "extracting": "Daten werden extrahiert...bitte warten Sie 10 Sekunden bis 1 Minute", + "restart_required": "Bitte starten Sie die Interpretation nach der Aktualisierung der Aufnahme neu", + "run_finished": "Durchlauf beendet", + "run_failed": "Start fehlgeschlagen" + }, + "modal": { + "use_previous": "Möchten Sie Ihre vorherige Auswahl als Bedingung für diese Aktion verwenden?", + "previous_action": "Ihre vorherige Aktion war: ", + "element_text": "auf einem Element mit Text " + } } } From aacb5153d48d769a92fa1bb5005a98bd6c4813b9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:42:34 +0530 Subject: [PATCH 226/500] feat: add chinese translation for interpretation buttons --- public/locales/zh.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index ce4adc79..c31d83cc 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -239,5 +239,23 @@ "image_below": "图片显示如下:", "separator": "--------------------------------------------------" } + }, + "interpretation_buttons": { + "buttons": { + "preview": "获取输出数据预览", + "yes": "是", + "no": "否" + }, + "messages": { + "extracting": "正在提取数据...请等待10秒到1分钟", + "restart_required": "更新录制后请重新启动解释", + "run_finished": "运行完成", + "run_failed": "运行启动失败" + }, + "modal": { + "use_previous": "您要将之前的选择用作执行此操作的条件吗?", + "previous_action": "您之前的操作是:", + "element_text": "在文本元素上 " + } } } \ No newline at end of file From 6a4bb3ed209a22306a6b230dc0f4ed60ceee537c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:42:59 +0530 Subject: [PATCH 227/500] feat: add translation for interpretation buttons --- .../molecules/InterpretationButtons.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/molecules/InterpretationButtons.tsx b/src/components/molecules/InterpretationButtons.tsx index 9d983761..624a57b4 100644 --- a/src/components/molecules/InterpretationButtons.tsx +++ b/src/components/molecules/InterpretationButtons.tsx @@ -7,6 +7,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { GenericModal } from "../atoms/GenericModal"; import { WhereWhatPair } from "maxun-core"; import HelpIcon from '@mui/icons-material/Help'; +import { useTranslation } from "react-i18next"; interface InterpretationButtonsProps { enableStepping: (isPaused: boolean) => void; @@ -23,6 +24,7 @@ const interpretationInfo: InterpretationInfo = { }; export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsProps) => { + const { t } = useTranslation(); const [info, setInfo] = useState(interpretationInfo); const [decisionModal, setDecisionModal] = useState<{ pair: WhereWhatPair | null, @@ -44,9 +46,9 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP const breakpointHitHandler = useCallback(() => { setInfo({ running: false, isPaused: true }); - notify('warning', 'Please restart the interpretation after updating the recording'); + notify('warning', t('interpretation_buttons.messages.restart_required')); enableStepping(true); - }, [enableStepping]); + }, [enableStepping, t]); const decisionHandler = useCallback( ({ pair, actionType, lastData }: { pair: WhereWhatPair | null, actionType: string, lastData: { selector: string, action: string, tagName: string, innerText: string } }) => { @@ -73,11 +75,12 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP return ( <> - Do you want to use your previous selection as a condition for performing this action? + {t('interpretation_buttons.modal.use_previous')} - Your previous action was: {decisionModal.action}, on an element with text {decisionModal.innerText} + {t('interpretation_buttons.modal.previous_action')} {decisionModal.action}, + {t('interpretation_buttons.modal.element_text')} {decisionModal.innerText} @@ -105,9 +108,9 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP const finished = await interpretCurrentRecording(); setInfo({ ...info, running: false }); if (finished) { - notify('info', 'Run finished'); + notify('info', t('interpretation_buttons.messages.run_finished')); } else { - notify('error', 'Run failed to start'); + notify('error', t('interpretation_buttons.messages.run_failed')); } } }; @@ -139,9 +142,12 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP disabled={info.running} sx={{ display: 'grid' }} > - {info.running ? - Extracting data...please wait for 10secs to 1min - : 'Get Preview of Output Data'} + {info.running ? ( + + + {t('interpretation_buttons.messages.extracting')} + + ) : t('interpretation_buttons.buttons.preview')} { }} @@ -166,8 +172,12 @@ export const InterpretationButtons = ({ enableStepping }: InterpretationButtonsP {handleDescription()}
- - + +
From a7d415dcda3bc28350e4e63917fcba05002b57fc Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:54:22 +0530 Subject: [PATCH 228/500] feat: add chinese translation for recording page --- public/locales/zh.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index c31d83cc..65aa0856 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -257,5 +257,10 @@ "previous_action": "您之前的操作是:", "element_text": "在文本元素上 " } + }, + "recording_page": { + "loader": { + "browser_startup": "正在启动浏览器...正在导航至{{url}}" + } } } \ No newline at end of file From 9cb85192f1076d914ef52f8a7a8485d867a507a4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:54:41 +0530 Subject: [PATCH 229/500] feat: add german translation for recording page --- public/locales/de.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 697473f2..c796f2dc 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -256,5 +256,10 @@ "previous_action": "Ihre vorherige Aktion war: ", "element_text": "auf einem Element mit Text " } + }, + "recording_page": { + "loader": { + "browser_startup": "Browser wird gestartet...Navigation zu {{url}}" + } } } From e9c4ea0d2eb849ca5b8d1c06290a2c3e240cac8d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:54:56 +0530 Subject: [PATCH 230/500] feat: add spanish translation for recording page --- public/locales/es.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index f1f744ed..8111e104 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -257,5 +257,10 @@ "previous_action": "Su acción anterior fue: ", "element_text": "en un elemento con texto " } + }, + "recording_page": { + "loader": { + "browser_startup": "Iniciando el navegador...Navegando a {{url}}" + } } } \ No newline at end of file From 05c6494b079152e7ec25d3868cf6264e4acb8af1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:55:11 +0530 Subject: [PATCH 231/500] feat: add japanese translation for recording page --- public/locales/ja.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 274a8d06..c9ce883c 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -257,5 +257,10 @@ "previous_action": "前回の操作: ", "element_text": "テキスト要素 " } + }, + "recording_page": { + "loader": { + "browser_startup": "ブラウザを起動中...{{url}}に移動中" + } } } From c77807c1369f3c06e46f311de8f5cf3d0d9b5a45 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:55:25 +0530 Subject: [PATCH 232/500] feat: add english translation for recording page --- public/locales/en.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index b5bffd42..ec51d1f9 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -259,5 +259,10 @@ "previous_action": "Your previous action was: ", "element_text": "on an element with text " } + }, + "recording_page": { + "loader": { + "browser_startup": "Spinning up a browser...Navigating to {{url}}" + } } } \ No newline at end of file From cf2fe717fdd4828330c16f5d521ae6635cb6048b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 12:55:44 +0530 Subject: [PATCH 233/500] feat: add translation for recording page --- src/pages/RecordingPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 18fad49f..82fa7e32 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -15,6 +15,7 @@ import { editRecordingFromStorage } from "../api/storage"; import { WhereWhatPair } from "maxun-core"; import styled from "styled-components"; import BrowserRecordingSave from '../components/molecules/BrowserRecordingSave'; +import { useTranslation } from 'react-i18next'; interface RecordingPageProps { recordingName?: string; @@ -26,7 +27,7 @@ export interface PairForEdit { } export const RecordingPage = ({ recordingName }: RecordingPageProps) => { - + const { t } = useTranslation(); const [isLoaded, setIsLoaded] = React.useState(false); const [hasScrollbar, setHasScrollbar] = React.useState(false); const [pairForEdit, setPairForEdit] = useState({ @@ -145,7 +146,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { ) : ( - + )}
From 1f86a408b7bfc0e6ba4d56f342d60a22b157bff0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:08:44 +0530 Subject: [PATCH 234/500] feat: add english translation for integration settings --- public/locales/en.json | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index ec51d1f9..322d21ca 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -264,5 +264,40 @@ "loader": { "browser_startup": "Spinning up a browser...Navigating to {{url}}" } + }, + "integration_settings": { + "title": "Integrate with Google Sheet", + "descriptions": { + "sync_info": "If you enable this option, every time this robot runs a task successfully, its captured data will be appended to your Google Sheet.", + "authenticated_as": "Authenticated as: {{email}}" + }, + "alerts": { + "success": { + "title": "Google Sheet Integrated Successfully.", + "content": "Every time this robot creates a successful run, its captured data is appended to your {{sheetName}} Google Sheet. You can check the data updates", + "here": "here", + "note": "Note:", + "sync_limitation": "The data extracted before integrating with Google Sheets will not be synced in the Google Sheet. Only the data extracted after the integration will be synced." + } + }, + "buttons": { + "authenticate": "Authenticate with Google", + "fetch_sheets": "Fetch Google Spreadsheets", + "remove_integration": "Remove Integration", + "submit": "Submit" + }, + "fields": { + "select_sheet": "Select Google Sheet", + "selected_sheet": "Selected Sheet: {{name}} (ID: {{id}})" + }, + "errors": { + "auth_error": "Error authenticating with Google", + "fetch_error": "Error fetching spreadsheet files: {{message}}", + "update_error": "Error updating Google Sheet ID: {{message}}", + "remove_error": "Error removing Google Sheets integration: {{message}}" + }, + "notifications": { + "sheet_selected": "Google Sheet selected successfully" + } } } \ No newline at end of file From 7733e62c297972440ac011f1d192234813fced56 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:09:00 +0530 Subject: [PATCH 235/500] feat: add japanese translation for integration settings --- public/locales/ja.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index c9ce883c..a93cae72 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -262,5 +262,31 @@ "loader": { "browser_startup": "ブラウザを起動中...{{url}}に移動中" } + }, + "integration_settings": { + "title": "Google Sheetと連携", + "descriptions": { + "sync_info": "このオプションを有効にすると、このロボットがタスクを正常に実行するたびに、取得したデータがGoogle Sheetに追加されます。", + "authenticated_as": "認証済みユーザー: {{email}}" + }, + "alerts": { + "success": { + "title": "Google Sheetの連携が完了しました。", + "content": "このロボットが正常に実行を完了するたびに、取得したデータはGoogle Sheet {{sheetName}}に追加されます。データの更新は", + "here": "こちら", + "note": "注意:", + "sync_limitation": "Google Sheetsとの連携前に抽出されたデータは同期されません。連携後に抽出されたデータのみが同期されます。" + } + }, + "buttons": { + "authenticate": "Googleで認証", + "fetch_sheets": "Google Sheetsを取得", + "remove_integration": "連携を解除", + "submit": "送信" + }, + "fields": { + "select_sheet": "Google Sheetを選択", + "selected_sheet": "選択したシート: {{name}} (ID: {{id}})" + } } } From 1eb4bc6b032c8afadc03e21f120b3d242027b037 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:09:18 +0530 Subject: [PATCH 236/500] feat: add spanish translation for integration settings --- public/locales/es.json | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/public/locales/es.json b/public/locales/es.json index 8111e104..e38ba1c7 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -242,25 +242,51 @@ }, "interpretation_buttons": { "buttons": { - "preview": "Obtener Vista Previa de Datos de Salida", - "yes": "Sí", - "no": "No" + "preview": "Obtener Vista Previa de Datos de Salida", + "yes": "Sí", + "no": "No" }, "messages": { - "extracting": "Extrayendo datos...espere de 10 segundos a 1 minuto", - "restart_required": "Por favor, reinicie la interpretación después de actualizar la grabación", - "run_finished": "Ejecución finalizada", - "run_failed": "Error al iniciar la ejecución" + "extracting": "Extrayendo datos...espere de 10 segundos a 1 minuto", + "restart_required": "Por favor, reinicie la interpretación después de actualizar la grabación", + "run_finished": "Ejecución finalizada", + "run_failed": "Error al iniciar la ejecución" }, "modal": { - "use_previous": "¿Desea usar su selección anterior como condición para realizar esta acción?", - "previous_action": "Su acción anterior fue: ", - "element_text": "en un elemento con texto " + "use_previous": "¿Desea usar su selección anterior como condición para realizar esta acción?", + "previous_action": "Su acción anterior fue: ", + "element_text": "en un elemento con texto " } }, "recording_page": { "loader": { "browser_startup": "Iniciando el navegador...Navegando a {{url}}" } + }, + "integration_settings": { + "title": "Integrar con Google Sheet", + "descriptions": { + "sync_info": "Si habilitas esta opción, cada vez que este robot ejecute una tarea exitosamente, sus datos capturados se añadirán a tu Google Sheet.", + "authenticated_as": "Autenticado como: {{email}}" + }, + "alerts": { + "success": { + "title": "Google Sheet integrado exitosamente.", + "content": "Cada vez que este robot crea una ejecución exitosa, sus datos capturados se añaden a tu Google Sheet {{sheetName}}. Puedes verificar las actualizaciones de datos", + "here": "aquí", + "note": "Nota:", + "sync_limitation": "Los datos extraídos antes de la integración con Google Sheets no se sincronizarán en el Google Sheet. Solo se sincronizarán los datos extraídos después de la integración." + } + }, + "buttons": { + "authenticate": "Autenticar con Google", + "fetch_sheets": "Obtener Google Sheets", + "remove_integration": "Eliminar integración", + "submit": "Enviar" + }, + "fields": { + "select_sheet": "Seleccionar Google Sheet", + "selected_sheet": "Hoja seleccionada: {{name}} (ID: {{id}})" + } } } \ No newline at end of file From 71b60eea63fe6a16964607e75e425f79efe65998 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:09:38 +0530 Subject: [PATCH 237/500] feat: add german translation for integration settings --- public/locales/de.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index c796f2dc..0e8b5832 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -261,5 +261,31 @@ "loader": { "browser_startup": "Browser wird gestartet...Navigation zu {{url}}" } + }, + "integration_settings": { + "title": "Mit Google Sheet integrieren", + "descriptions": { + "sync_info": "Wenn Sie diese Option aktivieren, werden bei jeder erfolgreichen Ausführung dieses Roboters die erfassten Daten in Ihrem Google Sheet ergänzt.", + "authenticated_as": "Authentifiziert als: {{email}}" + }, + "alerts": { + "success": { + "title": "Google Sheet erfolgreich integriert.", + "content": "Jedes Mal, wenn dieser Roboter eine erfolgreiche Ausführung erstellt, werden die erfassten Daten Ihrem Google Sheet {{sheetName}} hinzugefügt. Sie können die Datenaktualisierungen", + "here": "hier", + "note": "Hinweis:", + "sync_limitation": "Die vor der Integration mit Google Sheets extrahierten Daten werden nicht im Google Sheet synchronisiert. Nur die nach der Integration extrahierten Daten werden synchronisiert." + } + }, + "buttons": { + "authenticate": "Mit Google authentifizieren", + "fetch_sheets": "Google Sheets abrufen", + "remove_integration": "Integration entfernen", + "submit": "Absenden" + }, + "fields": { + "select_sheet": "Google Sheet auswählen", + "selected_sheet": "Ausgewähltes Sheet: {{name}} (ID: {{id}})" + } } } From fbf71028e93934828dcbb34fdc5b6e60391c82c0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:09:57 +0530 Subject: [PATCH 238/500] feat: add chinese translation for integration settings --- public/locales/zh.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 65aa0856..df45c45c 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -262,5 +262,31 @@ "loader": { "browser_startup": "正在启动浏览器...正在导航至{{url}}" } + }, + "integration_settings": { + "title": "与Google Sheet集成", + "descriptions": { + "sync_info": "如果启用此选项,每次机器人成功运行任务时,捕获的数据都会追加到您的Google Sheet中。", + "authenticated_as": "已验证身份: {{email}}" + }, + "alerts": { + "success": { + "title": "Google Sheet集成成功。", + "content": "每次此机器人创建成功运行时,捕获的数据都会追加到您的Google Sheet {{sheetName}}中。您可以查看数据更新", + "here": "在此处", + "note": "注意:", + "sync_limitation": "与Google Sheets集成之前提取的数据将不会同步到Google Sheet中。只有集成后提取的数据才会同步。" + } + }, + "buttons": { + "authenticate": "使用Google验证", + "fetch_sheets": "获取Google Sheets", + "remove_integration": "移除集成", + "submit": "提交" + }, + "fields": { + "select_sheet": "选择Google Sheet", + "selected_sheet": "已选择表格: {{name}} (ID: {{id}})" + } } } \ No newline at end of file From 0a55388e525044c7c7500b63d6b4c27b64e701ee Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:10:38 +0530 Subject: [PATCH 239/500] feat: add translation for integration settings --- .../molecules/IntegrationSettings.tsx | 65 ++++++++----------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index b34bc0e9..97973645 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -15,6 +15,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRecording } from "../../api/storage"; import { apiUrl } from "../../apiConfig.js"; import Cookies from 'js-cookie'; +import { useTranslation } from "react-i18next"; interface IntegrationProps { isOpen: boolean; @@ -33,6 +34,7 @@ export const IntegrationSettingsModal = ({ handleStart, handleClose, }: IntegrationProps) => { + const { t } = useTranslation(); const [settings, setSettings] = useState({ spreadsheetId: "", spreadsheetName: "", @@ -168,38 +170,28 @@ export const IntegrationSettingsModal = ({ return ( -
+ }}> - Integrate with Google Sheet{" "} - {/* */} + {t('integration_settings.title')} {recording && recording.google_sheet_id ? ( <> - Google Sheet Integrated Successfully. - Every time this robot creates a successful run, its captured data - is appended to your {recording.google_sheet_name} Google Sheet. - You can check the data updates{" "} - {t('integration_settings.alerts.success.title')} + {t('integration_settings.alerts.success.content', { sheetName: recording.google_sheet_name })} + - here - - . + rel="noreferrer"> + {t('integration_settings.alerts.success.here')} + .
- Note: The data extracted before integrating with - Google Sheets will not be synced in the Google Sheet. Only the - data extracted after the integration will be synced. + {t('integration_settings.alerts.success.note')} {t('integration_settings.alerts.success.sync_limitation')}
) : ( <> {!recording?.google_sheet_email ? ( <> -

- If you enable this option, every time this robot runs a task - successfully, its captured data will be appended to your - Google Sheet. -

+

{t('integration_settings.descriptions.sync_info')}

) : ( <> {recording.google_sheet_email && ( - Authenticated as: {recording.google_sheet_email} + {t('integration_settings.descriptions.authenticated_as', { + email: recording.google_sheet_email + })} )} @@ -247,14 +237,14 @@ export const IntegrationSettingsModal = ({ color="primary" onClick={fetchSpreadsheetFiles} > - Fetch Google Spreadsheets + {t('integration_settings.buttons.fetch_sheets')}
@@ -263,7 +253,7 @@ export const IntegrationSettingsModal = ({ - Selected Sheet:{" "} - { - spreadsheets.find( - (s) => s.id === settings.spreadsheetId - )?.name - }{" "} - (ID: {settings.spreadsheetId}) + {t('integration_settings.fields.selected_sheet', { + name: spreadsheets.find((s) => s.id === settings.spreadsheetId)?.name, + id: settings.spreadsheetId + })} )} @@ -298,7 +285,7 @@ export const IntegrationSettingsModal = ({ style={{ marginTop: "10px" }} disabled={!settings.spreadsheetId || loading} > - Submit + {t('integration_settings.buttons.submit')} )} From 681abac471ea1015f0c88ec193bbfee0f99085a6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:33:07 +0530 Subject: [PATCH 240/500] feat: add chinese translation for robot duplicate --- public/locales/zh.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index df45c45c..b673b237 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -288,5 +288,27 @@ "select_sheet": "选择Google Sheet", "selected_sheet": "已选择表格: {{name}} (ID: {{id}})" } + }, + "robot_duplication": { + "title": "复制机器人", + "descriptions": { + "purpose": "机器人复制功能用于从具有相同结构的页面提取数据。", + "example": "示例:如果您已经为{{url1}}创建了机器人,您可以复制它来抓取类似的页面(如{{url2}}),而无需从头开始训练机器人。", + "warning": "⚠️ 确保新页面与原始页面具有相同的结构。" + }, + "fields": { + "target_url": "机器人目标URL" + }, + "buttons": { + "duplicate": "复制机器人", + "cancel": "取消" + }, + "notifications": { + "robot_not_found": "找不到机器人详细信息。请重试。", + "url_required": "需要目标URL。", + "duplicate_success": "机器人复制成功。", + "duplicate_error": "更新目标URL失败。请重试。", + "unknown_error": "更新目标URL时发生错误。" + } } } \ No newline at end of file From 0d5cd711951892f92583654798d629b432b91d85 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:33:24 +0530 Subject: [PATCH 241/500] feat: add english translation for robot duplicate --- public/locales/en.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 322d21ca..501c05c0 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -299,5 +299,27 @@ "notifications": { "sheet_selected": "Google Sheet selected successfully" } + }, + "robot_duplication": { + "title": "Duplicate Robot", + "descriptions": { + "purpose": "Robot duplication is useful to extract data from pages with the same structure.", + "example": "Example: If you've created a robot for {{url1}}, you can duplicate it to scrape similar pages like {{url2}} without training a robot from scratch.", + "warning": "⚠️ Ensure the new page has the same structure as the original page." + }, + "fields": { + "target_url": "Robot Target URL" + }, + "buttons": { + "duplicate": "Duplicate Robot", + "cancel": "Cancel" + }, + "notifications": { + "robot_not_found": "Could not find robot details. Please try again.", + "url_required": "Target URL is required.", + "duplicate_success": "Robot duplicated successfully.", + "duplicate_error": "Failed to update the Target URL. Please try again.", + "unknown_error": "An error occurred while updating the Target URL." + } } } \ No newline at end of file From f395f55a96a078f228cd8213231a63be9adfbd8a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:33:41 +0530 Subject: [PATCH 242/500] feat: add japanese translation for robot duplicate --- public/locales/ja.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index a93cae72..3afbf997 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -288,5 +288,27 @@ "select_sheet": "Google Sheetを選択", "selected_sheet": "選択したシート: {{name}} (ID: {{id}})" } + }, + "robot_duplication": { + "title": "ロボットを複製", + "descriptions": { + "purpose": "ロボットの複製は、同じ構造のページからデータを抽出する際に便利です。", + "example": "例:{{url1}}用のロボットを作成した場合、ロボットを一から作り直すことなく、{{url2}}のような類似のページをスクレイピングするために複製できます。", + "warning": "⚠️ 新しいページが元のページと同じ構造であることを確認してください。" + }, + "fields": { + "target_url": "ロボットのターゲットURL" + }, + "buttons": { + "duplicate": "ロボットを複製", + "cancel": "キャンセル" + }, + "notifications": { + "robot_not_found": "ロボットの詳細が見つかりません。もう一度お試しください。", + "url_required": "ターゲットURLが必要です。", + "duplicate_success": "ロボットが正常に複製されました。", + "duplicate_error": "ターゲットURLの更新に失敗しました。もう一度お試しください。", + "unknown_error": "ターゲットURLの更新中にエラーが発生しました。" + } } } From cff34c2aff165aa1c079f7abfb16d54ed8ac21b0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:33:58 +0530 Subject: [PATCH 243/500] feat: add spanish translation for robot duplicate --- public/locales/es.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index e38ba1c7..9ef9ede5 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -288,5 +288,27 @@ "select_sheet": "Seleccionar Google Sheet", "selected_sheet": "Hoja seleccionada: {{name}} (ID: {{id}})" } + }, + "robot_duplication": { + "title": "Duplicar Robot", + "descriptions": { + "purpose": "La duplicación de robots es útil para extraer datos de páginas con la misma estructura.", + "example": "Ejemplo: Si has creado un robot para {{url1}}, puedes duplicarlo para extraer páginas similares como {{url2}} sin tener que entrenar un robot desde cero.", + "warning": "⚠️ Asegúrate de que la nueva página tenga la misma estructura que la página original." + }, + "fields": { + "target_url": "URL Destino del Robot" + }, + "buttons": { + "duplicate": "Duplicar Robot", + "cancel": "Cancelar" + }, + "notifications": { + "robot_not_found": "No se pudieron encontrar los detalles del robot. Por favor, inténtalo de nuevo.", + "url_required": "Se requiere la URL de destino.", + "duplicate_success": "Robot duplicado con éxito.", + "duplicate_error": "Error al actualizar la URL de destino. Por favor, inténtalo de nuevo.", + "unknown_error": "Ocurrió un error al actualizar la URL de destino." + } } } \ No newline at end of file From 553201d0dd3b8542c42daaafe8b5b0be44e4bd5f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:34:16 +0530 Subject: [PATCH 244/500] feat: add german translation for robot duplicate --- public/locales/de.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 0e8b5832..302c16fe 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -287,5 +287,27 @@ "select_sheet": "Google Sheet auswählen", "selected_sheet": "Ausgewähltes Sheet: {{name}} (ID: {{id}})" } + }, + "robot_duplication": { + "title": "Roboter duplizieren", + "descriptions": { + "purpose": "Die Roboter-Duplizierung ist nützlich, um Daten von Seiten mit der gleichen Struktur zu extrahieren.", + "example": "Beispiel: Wenn Sie einen Roboter für {{url1}} erstellt haben, können Sie ihn duplizieren, um ähnliche Seiten wie {{url2}} zu durchsuchen, ohne einen Roboter von Grund auf neu zu trainieren.", + "warning": "⚠️ Stellen Sie sicher, dass die neue Seite die gleiche Struktur wie die Originalseite hat." + }, + "fields": { + "target_url": "Roboter Ziel-URL" + }, + "buttons": { + "duplicate": "Roboter duplizieren", + "cancel": "Abbrechen" + }, + "notifications": { + "robot_not_found": "Roboterdetails konnten nicht gefunden werden. Bitte versuchen Sie es erneut.", + "url_required": "Ziel-URL ist erforderlich.", + "duplicate_success": "Roboter erfolgreich dupliziert.", + "duplicate_error": "Fehler beim Aktualisieren der Ziel-URL. Bitte versuchen Sie es erneut.", + "unknown_error": "Beim Aktualisieren der Ziel-URL ist ein Fehler aufgetreten." + } } } From 9b9a44df40be04a3f9f31d598bffd263cfa0d356 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:35:12 +0530 Subject: [PATCH 245/500] feat: add translation for robot duplicate --- src/components/molecules/RobotDuplicate.tsx | 50 +++++++++++---------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 38b7b422..530aedc6 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -6,6 +6,7 @@ import { useGlobalInfoStore } from '../../context/globalInfo'; import { duplicateRecording, getStoredRecording } from '../../api/storage'; import { WhereWhatPair } from 'maxun-core'; import { getUserById } from "../../api/auth"; +import { useTranslation } from 'react-i18next'; interface RobotMeta { name: string; @@ -54,6 +55,7 @@ interface RobotSettingsProps { } export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + const { t } = useTranslation(); const [robot, setRobot] = useState(null); const [targetUrl, setTargetUrl] = useState(''); const { recordingId, notify } = useGlobalInfoStore(); @@ -65,7 +67,6 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia }, [isOpen]); useEffect(() => { - // Update the targetUrl when the robot data is loaded if (robot) { const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; const url = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; @@ -78,22 +79,17 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia const robot = await getStoredRecording(recordingId); setRobot(robot); } else { - notify('error', 'Could not find robot details. Please try again.'); + notify('error', t('robot_duplication.notifications.robot_not_found')); } } - // const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; - - // // Find the `goto` action in `what` and retrieve its arguments - // const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; - const handleTargetUrlChange = (e: React.ChangeEvent) => { setTargetUrl(e.target.value); }; const handleSave = async () => { if (!robot || !targetUrl) { - notify('error', 'Target URL is required.'); + notify('error', t('robot_duplication.notifications.url_required')); return; } @@ -103,18 +99,18 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia const success = await duplicateRecording(robot.recording_meta.id, targetUrl); if (success) { - notify('success', 'Robot duplicated successfully.'); - handleStart(robot); // Inform parent about the updated robot + notify('success', t('robot_duplication.notifications.duplicate_success')); + handleStart(robot); handleClose(); setTimeout(() => { window.location.reload(); }, 1000); } else { - notify('error', 'Failed to update the Target URL. Please try again.'); + notify('error', t('robot_duplication.notifications.duplicate_error')); } } catch (error) { - notify('error', 'An error occurred while updating the Target URL.'); + notify('error', t('robot_duplication.notifications.unknown_error')); console.error('Error updating Target URL:', error); } }; @@ -126,34 +122,40 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia modalStyle={modalStyle} > <> - Duplicate Robot + + {t('robot_duplication.title')} + { robot && ( <> - Robot duplication is useful to extract data from pages with the same structure. + + {t('robot_duplication.descriptions.purpose')} + +
+ producthunt.com/topics/api
', + url2: 'producthunt.com/topics/database' + }) + }}/>
- Example: If you've created a robot for producthunt.com/topics/api, you can duplicate it to scrape similar pages - like producthunt.com/topics/database without training a robot from scratch. - -
- - ⚠️ Ensure the new page has the same structure as the original page. + {t('robot_duplication.descriptions.warning')} From 277f8a0d2b06c3993aec890843fe65babed38d21 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:38:32 +0530 Subject: [PATCH 246/500] feat: add german translation for runs table --- public/locales/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index 302c16fe..bbf130a7 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -55,7 +55,11 @@ "finishedAt": "Beendet am", "delete": "Löschen", "settings": "Einstellungen", - "search": "Ausführungen suchen..." + "search": "Ausführungen suchen...", + "notifications": { + "no_runs": "Keine Ausführungen gefunden. Bitte versuchen Sie es erneut.", + "delete_success": "Ausführung erfolgreich gelöscht" + } }, "proxy": { "title": "Proxy-Konfiguration", From 4de1f6ff14e2526b2c2de772aae05939f685455b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:38:46 +0530 Subject: [PATCH 247/500] feat: add japanese translation for runs table --- public/locales/ja.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index 3afbf997..88e97a7a 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -56,7 +56,11 @@ "finishedAt": "終了日時", "delete": "削除", "settings": "設定", - "search": "実行を検索..." + "search": "実行を検索...", + "notifications": { + "no_runs": "実行が見つかりません。もう一度お試しください。", + "delete_success": "実行が正常に削除されました" + } }, "proxy": { "title": "プロキシ設定", From 4d5226e236a91cb1d481e67f6f3ffb2e0192e503 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:39:03 +0530 Subject: [PATCH 248/500] feat: add english translation for runs table --- public/locales/en.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 501c05c0..567fdbad 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -58,7 +58,11 @@ "finishedAt":"Finished At", "delete":"Delete", "settings":"Settings", - "search":"Search Runs..." + "search":"Search Runs...", + "notifications": { + "no_runs": "No runs found. Please try again.", + "delete_success": "Run deleted successfully" + } }, "proxy": { "title": "Proxy Configuration", From 55161e662b401414e94c738b9b442d7b2f5616ef Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:39:17 +0530 Subject: [PATCH 249/500] feat: add spanish translation for runs table --- public/locales/es.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index 9ef9ede5..b0653bd7 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -56,7 +56,11 @@ "finishedAt": "Finalizado el", "delete": "Eliminar", "settings": "Ajustes", - "search": "Buscar ejecuciones..." + "search": "Buscar ejecuciones...", + "notifications": { + "no_runs": "No se encontraron ejecuciones. Por favor, inténtelo de nuevo.", + "delete_success": "Ejecución eliminada con éxito" + } }, "proxy": { "title": "Configuración del Proxy", From 413d488cbb35b0f5deda9afa923838e5a04d90d3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:39:32 +0530 Subject: [PATCH 250/500] feat: add chinese translation for runs table --- public/locales/zh.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index b673b237..f1bc8b73 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -56,7 +56,11 @@ "finishedAt": "结束时间", "delete": "删除", "settings": "设置", - "search": "搜索运行记录..." + "search": "搜索运行记录...", + "notifications": { + "no_runs": "未找到运行记录。请重试。", + "delete_success": "运行记录删除成功" + } }, "proxy": { "title": "代理设置", From 85a32b5e39c137b701f6184b8d44be164fb6ccf3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 13:39:52 +0530 Subject: [PATCH 251/500] feat: add translation for runs table --- src/components/molecules/RunsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index 41dd047a..cc64672d 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -105,7 +105,7 @@ export const RunsTable: React.FC = ({ })); setRows(parsedRows); } else { - notify('error', 'No runs found. Please try again.'); + notify('error', t('runstable.notifications.no_runs')); } }; @@ -118,7 +118,7 @@ export const RunsTable: React.FC = ({ const handleDelete = () => { setRows([]); - notify('success', 'Run deleted successfully'); + notify('success', t('runstable.notifications.delete_success')); fetchRuns(); }; From 117d9d16e44c427c7ba022e2208134d593aaa998 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:16:52 +0530 Subject: [PATCH 252/500] feat: add chinese translation for recordings table --- public/locales/zh.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index f1bc8b73..cf24d3f7 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -38,7 +38,11 @@ "edit": "编辑", "delete": "删除", "duplicate": "复制", - "search": "搜索机器人..." + "search": "搜索机器人...", + "notifications": { + "delete_warning": "无法删除机器人,因为它有关联的运行记录", + "delete_success": "机器人删除成功" + } }, "mainmenu": { "recordings": "机器人", From a9515ce42ae60b91015b558a701130bbcec362f4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:17:17 +0530 Subject: [PATCH 253/500] feat: add german translation for recordings table --- public/locales/de.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index bbf130a7..397acaaa 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -37,7 +37,11 @@ }, "edit": "Bearbeiten", "delete": "Löschen", - "duplicate": "Duplizieren" + "duplicate": "Duplizieren", + "notifications": { + "delete_warning": "Roboter kann nicht gelöscht werden, da zugehörige Ausführungen vorhanden sind", + "delete_success": "Roboter erfolgreich gelöscht" + } }, "mainmenu": { "recordings": "Roboter", From 3e1a15a1927b7be919e85b300f90529ede18e948 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:17:31 +0530 Subject: [PATCH 254/500] feat: add spanish translation for recordings table --- public/locales/es.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index b0653bd7..3ea532be 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -38,7 +38,11 @@ "edit": "Editar", "delete": "Eliminar", "duplicate": "Duplicar", - "search": "Buscar robots..." + "search": "Buscar robots...", + "notifications": { + "delete_warning": "No se puede eliminar el robot ya que tiene ejecuciones asociadas", + "delete_success": "Robot eliminado exitosamente" + } }, "mainmenu": { "recordings": "Robots", From fc7d869e8dcaf64a10eaa11c49f4fa052266c7ac Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:17:47 +0530 Subject: [PATCH 255/500] feat: add japanese translation for recordings table --- public/locales/ja.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index 88e97a7a..2f2ebeb6 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -38,7 +38,11 @@ "edit": "編集", "delete": "削除", "duplicate": "複製", - "search": "ロボットを検索..." + "search": "ロボットを検索...", + "notifications": { + "delete_warning": "関連する実行があるため、ロボットを削除できません", + "delete_success": "ロボットが正常に削除されました" + } }, "mainmenu": { "recordings": "ロボット", From 5f9d21b1406922d4bc5c511ec4749d909df4741e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:18:01 +0530 Subject: [PATCH 256/500] feat: add english translation for recordings table --- public/locales/en.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 567fdbad..3e350350 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -38,8 +38,11 @@ "edit":"Edit", "delete":"Delete", "duplicate":"Duplicate", - "search":"Search Robots..." - + "search":"Search Robots...", + "notifications": { + "delete_warning": "Cannot delete robot as it has associated runs", + "delete_success": "Robot deleted successfully" + } }, "mainmenu":{ "recordings": "Robots", From 4a18bfc5fd88bcc75231b2b1867be7534b0026d1 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:18:16 +0530 Subject: [PATCH 257/500] feat: add translation for recordings table --- src/components/molecules/RecordingsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 92bf572a..01bc524b 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -261,14 +261,14 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl checkRunsForRecording(row.id).then((result: boolean) => { if (result) { - notify('warning', 'Cannot delete robot as it has associated runs'); + notify('warning', t('recordingtable.notifications.delete_warning')); } }) deleteRecordingFromStorage(row.id).then((result: boolean) => { if (result) { setRows([]); - notify('success', 'Robot deleted successfully'); + notify('success', t('recordingtable.notifications.delete_success')); fetchRecordings(); } }) From 6b8d966574234915957518795e447cec7154872e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:31:54 +0530 Subject: [PATCH 258/500] feat: add english translation for robot settings --- public/locales/en.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 3e350350..cd3c7f5f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -328,5 +328,16 @@ "duplicate_error": "Failed to update the Target URL. Please try again.", "unknown_error": "An error occurred while updating the Target URL." } + }, + "robot_settings": { + "title": "Robot Settings", + "target_url": "Robot Target URL", + "robot_id": "Robot ID", + "robot_limit": "Robot Limit", + "created_by_user": "Created By User", + "created_at": "Robot Created At", + "errors": { + "robot_not_found": "Could not find robot details. Please try again." + } } } \ No newline at end of file From 9850ff8ee9043390e68ca56310c9e7038bb6ffee Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:33:03 +0530 Subject: [PATCH 259/500] feat: add japanesetranslation for robot settings --- public/locales/ja.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 2f2ebeb6..093d5643 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -318,5 +318,16 @@ "duplicate_error": "ターゲットURLの更新に失敗しました。もう一度お試しください。", "unknown_error": "ターゲットURLの更新中にエラーが発生しました。" } + }, + "robot_settings": { + "title": "ロボット設定", + "target_url": "ロボットのターゲットURL", + "robot_id": "ロボットID", + "robot_limit": "ロボットの制限", + "created_by_user": "作成したユーザー", + "created_at": "作成日時", + "errors": { + "robot_not_found": "ロボットの詳細が見つかりませんでした。もう一度試してください。" + } } } From 5f7195841afda9431056f5414e44e14efa94fa26 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:33:27 +0530 Subject: [PATCH 260/500] feat: add spanish translation for robot settings --- public/locales/es.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 3ea532be..e6609b5d 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -318,5 +318,16 @@ "duplicate_error": "Error al actualizar la URL de destino. Por favor, inténtalo de nuevo.", "unknown_error": "Ocurrió un error al actualizar la URL de destino." } + }, + "robot_settings": { + "title": "Configuración del Robot", + "target_url": "URL de Destino del Robot", + "robot_id": "ID del Robot", + "robot_limit": "Límite del Robot", + "created_by_user": "Creado por Usuario", + "created_at": "Fecha de Creación del Robot", + "errors": { + "robot_not_found": "No se pudieron encontrar los detalles del robot. Inténtelo de nuevo." + } } } \ No newline at end of file From 262590b360b1cced526eb28818d502257f76204e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:33:43 +0530 Subject: [PATCH 261/500] feat: add german translation for robot settings --- public/locales/de.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 397acaaa..d5cfae4f 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -317,5 +317,16 @@ "duplicate_error": "Fehler beim Aktualisieren der Ziel-URL. Bitte versuchen Sie es erneut.", "unknown_error": "Beim Aktualisieren der Ziel-URL ist ein Fehler aufgetreten." } + }, + "robot_settings": { + "title": "Roboter-Einstellungen", + "target_url": "Roboter-Ziel-URL", + "robot_id": "Roboter-ID", + "robot_limit": "Roboter-Limit", + "created_by_user": "Erstellt von Benutzer", + "created_at": "Erstellungsdatum des Roboters", + "errors": { + "robot_not_found": "Roboterdetails konnten nicht gefunden werden. Bitte versuchen Sie es erneut." + } } } From e922a11331aba928e131d148a12d71b3e6eb9c94 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:34:11 +0530 Subject: [PATCH 262/500] feat: add chinese translation for robot settings --- public/locales/zh.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index cf24d3f7..9b5e7fb8 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -318,5 +318,16 @@ "duplicate_error": "更新目标URL失败。请重试。", "unknown_error": "更新目标URL时发生错误。" } + }, + "robot_settings": { + "title": "机器人设置", + "target_url": "机器人目标URL", + "robot_id": "机器人ID", + "robot_limit": "机器人限制", + "created_by_user": "由用户创建", + "created_at": "机器人创建时间", + "errors": { + "robot_not_found": "无法找到机器人详细信息。请重试。" + } } } \ No newline at end of file From 4e375e638071f00581fb26e2ecf76d49e6d4f43c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:34:31 +0530 Subject: [PATCH 263/500] feat: add translation for robot settings --- src/components/molecules/RobotSettings.tsx | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/molecules/RobotSettings.tsx b/src/components/molecules/RobotSettings.tsx index 53d21d7b..d952f43d 100644 --- a/src/components/molecules/RobotSettings.tsx +++ b/src/components/molecules/RobotSettings.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { GenericModal } from "../atoms/GenericModal"; import { TextField, Typography, Box } from "@mui/material"; import { modalStyle } from "./AddWhereCondModal"; @@ -50,10 +51,10 @@ interface RobotSettingsProps { handleStart: (settings: RobotSettings) => void; handleClose: () => void; initialSettings?: RobotSettings | null; - } export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + const { t } = useTranslation(); const [robot, setRobot] = useState(null); const [userEmail, setUserEmail] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); @@ -69,7 +70,7 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe const robot = await getStoredRecording(recordingId); setRobot(robot); } else { - notify('error', 'Could not find robot details. Please try again.'); + notify('error', t('robot_settings.errors.robot_not_found')); } } @@ -97,13 +98,15 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe modalStyle={modalStyle} > <> - Robot Settings + + {t('robot_settings.title')} + { robot && ( <> {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( )} ); -}; +}; \ No newline at end of file From 16496fb66843e353652f97733ea5cca3c33f51c6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:39:02 +0530 Subject: [PATCH 264/500] feat: add chinese translation for robot edit --- public/locales/zh.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 9b5e7fb8..f3954efc 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -329,5 +329,17 @@ "errors": { "robot_not_found": "无法找到机器人详细信息。请重试。" } + }, + "robot_edit": { + "title": "编辑机器人", + "change_name": "更改机器人名称", + "robot_limit": "机器人限制", + "save": "保存更改", + "cancel": "取消", + "notifications": { + "update_success": "机器人更新成功。", + "update_failed": "无法更新机器人。请重试。", + "update_error": "更新机器人时发生错误。" + } } } \ No newline at end of file From d84ff7216eb3b10a0fc47fe37897d4ea0d7ad248 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:39:20 +0530 Subject: [PATCH 265/500] feat: add english translation for robot edit --- public/locales/en.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index cd3c7f5f..f14840a7 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -339,5 +339,17 @@ "errors": { "robot_not_found": "Could not find robot details. Please try again." } + }, + "robot_edit": { + "title": "Edit Robot", + "change_name": "Change Robot Name", + "robot_limit": "Robot Limit", + "save": "Save Changes", + "cancel": "Cancel", + "notifications": { + "update_success": "Robot updated successfully.", + "update_failed": "Failed to update the robot. Please try again.", + "update_error": "An error occurred while updating the robot." + } } } \ No newline at end of file From 6bed79d14e8334729754d579386ec37e77948d2b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:39:36 +0530 Subject: [PATCH 266/500] feat: add japanese translation for robot edit --- public/locales/ja.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 093d5643..fdc0c929 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -329,5 +329,17 @@ "errors": { "robot_not_found": "ロボットの詳細が見つかりませんでした。もう一度試してください。" } + }, + "robot_edit": { + "title": "ロボットを編集", + "change_name": "ロボット名の変更", + "robot_limit": "ロボットの制限", + "save": "変更を保存", + "cancel": "キャンセル", + "notifications": { + "update_success": "ロボットが正常に更新されました。", + "update_failed": "ロボットの更新に失敗しました。もう一度試してください。", + "update_error": "ロボットの更新中にエラーが発生しました。" + } } } From e0f1a095f350584eb1eecf0cbd0bab0514a2af73 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:39:50 +0530 Subject: [PATCH 267/500] feat: add spanish translation for robot edit --- public/locales/es.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index e6609b5d..58ccdef7 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -329,5 +329,17 @@ "errors": { "robot_not_found": "No se pudieron encontrar los detalles del robot. Inténtelo de nuevo." } + }, + "robot_edit": { + "title": "Editar Robot", + "change_name": "Cambiar Nombre del Robot", + "robot_limit": "Límite del Robot", + "save": "Guardar Cambios", + "cancel": "Cancelar", + "notifications": { + "update_success": "Robot actualizado exitosamente.", + "update_failed": "Error al actualizar el robot. Intente de nuevo.", + "update_error": "Ocurrió un error al actualizar el robot." + } } } \ No newline at end of file From 5be29d55cfca55189cf1ad4ee93bcf7414146b70 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:40:01 +0530 Subject: [PATCH 268/500] feat: add german translation for robot edit --- public/locales/de.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index d5cfae4f..49332b76 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -328,5 +328,17 @@ "errors": { "robot_not_found": "Roboterdetails konnten nicht gefunden werden. Bitte versuchen Sie es erneut." } + }, + "robot_edit": { + "title": "Roboter bearbeiten", + "change_name": "Roboternamen ändern", + "robot_limit": "Roboter-Limit", + "save": "Änderungen speichern", + "cancel": "Abbrechen", + "notifications": { + "update_success": "Roboter erfolgreich aktualisiert.", + "update_failed": "Aktualisierung des Roboters fehlgeschlagen. Bitte erneut versuchen.", + "update_error": "Beim Aktualisieren des Roboters ist ein Fehler aufgetreten." + } } } From f4a99fbfc2a087e1bd83780d390a33112e8cc5ff Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 15:42:35 +0530 Subject: [PATCH 269/500] feat: add translation for robot edit --- src/components/molecules/RobotEdit.tsx | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 9441ecef..73397da4 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { GenericModal } from "../atoms/GenericModal"; import { TextField, Typography, Box, Button } from "@mui/material"; import { modalStyle } from "./AddWhereCondModal"; @@ -54,10 +55,10 @@ interface RobotSettingsProps { handleStart: (settings: RobotSettings) => void; handleClose: () => void; initialSettings?: RobotSettings | null; - } export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + const { t } = useTranslation(); const [robot, setRobot] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); @@ -72,7 +73,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const robot = await getStoredRecording(recordingId); setRobot(robot); } else { - notify('error', 'Could not find robot details. Please try again.'); + notify('error', t('robot_edit.notifications.update_failed')); } } @@ -102,6 +103,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin return { ...prev, recording: { ...prev.recording, workflow: updatedWorkflow } }; }); }; + const handleSave = async () => { if (!robot) return; @@ -114,7 +116,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const success = await updateRecording(robot.recording_meta.id, payload); if (success) { - notify('success', 'Robot updated successfully.'); + notify('success', t('robot_edit.notifications.update_success')); handleStart(robot); // Inform parent about the updated robot handleClose(); @@ -122,10 +124,10 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin window.location.reload(); }, 1000); } else { - notify('error', 'Failed to update the robot. Please try again.'); + notify('error', t('robot_edit.notifications.update_failed')); } } catch (error) { - notify('error', 'An error occurred while updating the robot.'); + notify('error', t('robot_edit.notifications.update_error')); console.error('Error updating robot:', error); } }; @@ -137,13 +139,15 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin modalStyle={modalStyle} > <> - Edit Robot + + {t('robot_edit.title')} + { robot && ( <> {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( { @@ -168,10 +172,15 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin - From 5b4a4e710e86f3039b5547acca6d5d8d0dd57083 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:02:25 +0530 Subject: [PATCH 270/500] feat: add german translation for schedule settings --- public/locales/de.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 49332b76..184a918b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -340,5 +340,30 @@ "update_failed": "Aktualisierung des Roboters fehlgeschlagen. Bitte erneut versuchen.", "update_error": "Beim Aktualisieren des Roboters ist ein Fehler aufgetreten." } + }, + "schedule_settings": { + "title": "Zeitplan-Einstellungen", + "run_every": "Ausführen alle", + "start_from": "Beginnen ab", + "on_day": "An Tag", + "at_around": "Um", + "timezone": "Zeitzone", + "buttons": { + "delete_schedule": "Zeitplan löschen", + "save_schedule": "Zeitplan speichern", + "cancel": "Abbrechen" + }, + "labels": { + "in_between": "Zwischen", + "run_once_every": "Ausführen alle", + "start_from_label": "Beginnen ab", + "on_day_of_month": "Tag des Monats", + "on_day": { + "st": ".", + "nd": ".", + "rd": ".", + "th": "." + } + } } } From b15fe34bbc9c0abe47fd96a51a456e6a51e41d0d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:02:46 +0530 Subject: [PATCH 271/500] feat: add japanese translation for schedule settings --- public/locales/ja.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index fdc0c929..9d93ce47 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -341,5 +341,30 @@ "update_failed": "ロボットの更新に失敗しました。もう一度試してください。", "update_error": "ロボットの更新中にエラーが発生しました。" } + }, + "schedule_settings": { + "title": "スケジュール設定", + "run_every": "実行間隔", + "start_from": "開始日", + "on_day": "日付", + "at_around": "時刻", + "timezone": "タイムゾーン", + "buttons": { + "delete_schedule": "スケジュールを削除", + "save_schedule": "スケジュールを保存", + "cancel": "キャンセル" + }, + "labels": { + "in_between": "間隔", + "run_once_every": "実行間隔", + "start_from_label": "開始日", + "on_day_of_month": "月の日付", + "on_day": { + "st": "日", + "nd": "日", + "rd": "日", + "th": "日" + } + } } } From 8a68712f34efd1567e270d428fbbcd0785386a15 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:02:59 +0530 Subject: [PATCH 272/500] feat: add english translation for schedule settings --- public/locales/en.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index f14840a7..580a6520 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -351,5 +351,30 @@ "update_failed": "Failed to update the robot. Please try again.", "update_error": "An error occurred while updating the robot." } + }, + "schedule_settings": { + "title": "Schedule Settings", + "run_every": "Run every", + "start_from": "Start From", + "on_day": "On day", + "at_around": "At around", + "timezone": "Timezone", + "buttons": { + "delete_schedule": "Delete Schedule", + "save_schedule": "Save Schedule", + "cancel": "Cancel" + }, + "labels": { + "in_between": "In Between", + "run_once_every": "Run once every", + "start_from_label": "Start From", + "on_day_of_month": "On Day of the Month", + "on_day": { + "st": "st", + "nd": "nd", + "rd": "rd", + "th": "th" + } + } } } \ No newline at end of file From 9b28e00ae517d0ee0d919f5dc73440e6a6a0c5a0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:03:14 +0530 Subject: [PATCH 273/500] feat: add spanish translation for schedule settings --- public/locales/es.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 58ccdef7..7e429d3b 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -341,5 +341,30 @@ "update_failed": "Error al actualizar el robot. Intente de nuevo.", "update_error": "Ocurrió un error al actualizar el robot." } + }, + "schedule_settings": { + "title": "Configuración de Programación", + "run_every": "Ejecutar cada", + "start_from": "Iniciar desde", + "on_day": "En día", + "at_around": "Alrededor de", + "timezone": "Zona horaria", + "buttons": { + "delete_schedule": "Eliminar Programación", + "save_schedule": "Guardar Programación", + "cancel": "Cancelar" + }, + "labels": { + "in_between": "Entre", + "run_once_every": "Ejecutar cada", + "start_from_label": "Iniciar desde", + "on_day_of_month": "Día del mes", + "on_day": { + "st": "º", + "nd": "º", + "rd": "º", + "th": "º" + } + } } } \ No newline at end of file From 86ef4edf1a8139841e4108db1bdf2d0755f38bd2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:03:31 +0530 Subject: [PATCH 274/500] feat: add chinese translation for schedule settings --- public/locales/zh.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index f3954efc..436e74db 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -341,5 +341,30 @@ "update_failed": "无法更新机器人。请重试。", "update_error": "更新机器人时发生错误。" } + }, + "schedule_settings": { + "title": "计划设置", + "run_every": "每次运行", + "start_from": "开始于", + "on_day": "在日", + "at_around": "大约在", + "timezone": "时区", + "buttons": { + "delete_schedule": "删除计划", + "save_schedule": "保存计划", + "cancel": "取消" + }, + "labels": { + "in_between": "之间", + "run_once_every": "每次运行", + "start_from_label": "开始于", + "on_day_of_month": "月份日期", + "on_day": { + "st": "日", + "nd": "日", + "rd": "日", + "th": "日" + } + } } } \ No newline at end of file From 90a6c54a16311ebe1a78bf3b46a159975b1e02df Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:03:50 +0530 Subject: [PATCH 275/500] feat: add translation for schedule settings --- src/components/molecules/ScheduleSettings.tsx | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 00e842ea..3af0072f 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { GenericModal } from "../atoms/GenericModal"; import { MenuItem, TextField, Typography, Box } from "@mui/material"; import { Dropdown } from "../atoms/DropdownMui"; @@ -25,6 +26,7 @@ export interface ScheduleSettings { } export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: ScheduleSettingsProps) => { + const { t } = useTranslation(); const [schedule, setSchedule] = useState(null); const [settings, setSettings] = useState({ runEvery: 1, @@ -116,6 +118,25 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia } }, [isOpen]); + const getDayOrdinal = (day: string | undefined) => { + if (!day) return ''; + const lastDigit = day.slice(-1); + const lastTwoDigits = day.slice(-2); + + // Special cases for 11, 12, 13 + if (['11', '12', '13'].includes(lastTwoDigits)) { + return t('schedule_settings.labels.on_day.th'); + } + + // Other cases + switch (lastDigit) { + case '1': return t('schedule_settings.labels.on_day.st'); + case '2': return t('schedule_settings.labels.on_day.nd'); + case '3': return t('schedule_settings.labels.on_day.rd'); + default: return t('schedule_settings.labels.on_day.th'); + } + }; + return ( *': { marginBottom: '20px' }, }}> - Schedule Settings + {t('schedule_settings.title')} <> {schedule !== null ? ( <> - Run every: {schedule.runEvery} {schedule.runEveryUnit.toLowerCase()} - {['MONTHS', 'WEEKS'].includes(settings.runEveryUnit) ? "Start From" : "On"} {schedule.startFrom.charAt(0).toUpperCase() + schedule.startFrom.slice(1).toLowerCase()} + {t('schedule_settings.run_every')}: {schedule.runEvery} {schedule.runEveryUnit.toLowerCase()} + {['MONTHS', 'WEEKS'].includes(settings.runEveryUnit) ? t('schedule_settings.start_from') : t('schedule_settings.start_from')}: {schedule.startFrom.charAt(0).toUpperCase() + schedule.startFrom.slice(1).toLowerCase()} {schedule.runEveryUnit === 'MONTHS' && ( - On day: {schedule.dayOfMonth}{['1', '21', '31'].includes(schedule.dayOfMonth || '') ? 'st' : ['2', '22'].includes(schedule.dayOfMonth || '') ? 'nd' : ['3', '23'].includes(schedule.dayOfMonth || '') ? 'rd' : 'th'} of the month + {t('schedule_settings.on_day')}: {schedule.dayOfMonth}{getDayOrdinal(schedule.dayOfMonth)} of the month )} - At around: {schedule.atTimeStart}, {schedule.timezone} Timezone + {t('schedule_settings.at_around')}: {schedule.atTimeStart}, {schedule.timezone} {t('schedule_settings.timezone')} ) : ( <> - Run once every + {t('schedule_settings.labels.run_once_every')} - {['MONTHS', 'WEEKS'].includes(settings.runEveryUnit) ? "Start From" : "On"} + + {['MONTHS', 'WEEKS'].includes(settings.runEveryUnit) ? t('schedule_settings.labels.start_from_label') : t('schedule_settings.labels.start_from_label')} + - On Day of the Month + {t('schedule_settings.labels.on_day_of_month')} - In Between + {t('schedule_settings.labels.in_between')} ) : ( - At Around + {t('schedule_settings.at_around')} - Timezone + {t('schedule_settings.timezone')} @@ -271,4 +294,4 @@ const modalStyle = { height: 'fit-content', display: 'block', padding: '20px', -}; +}; \ No newline at end of file From 3020f96ddd438fd9686369d8f310b40f33283610 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:16:59 +0530 Subject: [PATCH 276/500] feat: add chinese translation for main page notifications --- public/locales/zh.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 436e74db..3032841f 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -366,5 +366,23 @@ "th": "日" } } + }, + "main_page": { + "notifications": { + "interpretation_success": "机器人{{name}}解释成功", + "interpretation_failed": "机器人{{name}}解释失败", + "run_started": "正在运行机器人:{{name}}", + "run_start_failed": "机器人运行失败:{{name}}", + "schedule_success": "机器人{{name}}调度成功", + "schedule_failed": "机器人{{name}}调度失败", + "abort_success": "成功中止机器人{{name}}的解释", + "abort_failed": "中止机器人{{name}}的解释失败" + }, + "menu": { + "recordings": "机器人", + "runs": "运行", + "proxy": "代理", + "apikey": "API密钥" + } } } \ No newline at end of file From eb1c4ec3166a6e47a366dc44dabd35b408baf31c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:17:17 +0530 Subject: [PATCH 277/500] feat: add spanish translation for main page notifications --- public/locales/es.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 7e429d3b..5db7f0c7 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -366,5 +366,23 @@ "th": "º" } } + }, + "main_page": { + "notifications": { + "interpretation_success": "Interpretación del robot {{name}} completada con éxito", + "interpretation_failed": "Error al interpretar el robot {{name}}", + "run_started": "Ejecutando robot: {{name}}", + "run_start_failed": "Error al ejecutar el robot: {{name}}", + "schedule_success": "Robot {{name}} programado exitosamente", + "schedule_failed": "Error al programar el robot {{name}}", + "abort_success": "Interpretación del robot {{name}} abortada exitosamente", + "abort_failed": "Error al abortar la interpretación del robot {{name}}" + }, + "menu": { + "recordings": "Robots", + "runs": "Ejecuciones", + "proxy": "Proxy", + "apikey": "Clave API" + } } } \ No newline at end of file From bc4f5067083668ac188098107a38fe288f62fe64 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:17:31 +0530 Subject: [PATCH 278/500] feat: add japanese translation for main page notifications --- public/locales/ja.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 9d93ce47..7c15d9af 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -366,5 +366,23 @@ "th": "日" } } + }, + "main_page": { + "notifications": { + "interpretation_success": "ロボット{{name}}の解釈に成功しました", + "interpretation_failed": "ロボット{{name}}の解釈に失敗しました", + "run_started": "ロボット{{name}}を実行中", + "run_start_failed": "ロボット{{name}}の実行に失敗しました", + "schedule_success": "ロボット{{name}}のスケジュールが正常に設定されました", + "schedule_failed": "ロボット{{name}}のスケジュール設定に失敗しました", + "abort_success": "ロボット{{name}}の解釈を中止しました", + "abort_failed": "ロボット{{name}}の解釈中止に失敗しました" + }, + "menu": { + "recordings": "ロボット", + "runs": "実行", + "proxy": "プロキシ", + "apikey": "APIキー" + } } } From 80cd1be84318c420b765ad6c5addebe74cad44dd Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:17:48 +0530 Subject: [PATCH 279/500] feat: add english translation for main page notifications --- public/locales/en.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 580a6520..5c40b87f 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -376,5 +376,23 @@ "th": "th" } } + }, + "main_page": { + "notifications": { + "interpretation_success": "Interpretation of robot {{name}} succeeded", + "interpretation_failed": "Failed to interpret robot {{name}}", + "run_started": "Running robot: {{name}}", + "run_start_failed": "Failed to run robot: {{name}}", + "schedule_success": "Robot {{name}} scheduled successfully", + "schedule_failed": "Failed to schedule robot {{name}}", + "abort_success": "Interpretation of robot {{name}} aborted successfully", + "abort_failed": "Failed to abort the interpretation of robot {{name}}" + }, + "menu": { + "recordings": "Robots", + "runs": "Runs", + "proxy": "Proxy", + "apikey": "API Key" + } } } \ No newline at end of file From 5983594faa2715d6796ae0516d065d50e6eb7e53 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:18:05 +0530 Subject: [PATCH 280/500] feat: add german translation for main page notifications --- public/locales/de.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 184a918b..2e99b451 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -365,5 +365,23 @@ "th": "." } } + }, + "main_page": { + "notifications": { + "interpretation_success": "Interpretation des Roboters {{name}} erfolgreich", + "interpretation_failed": "Interpretation des Roboters {{name}} fehlgeschlagen", + "run_started": "Roboter wird ausgeführt: {{name}}", + "run_start_failed": "Fehler beim Ausführen des Roboters: {{name}}", + "schedule_success": "Roboter {{name}} erfolgreich geplant", + "schedule_failed": "Planen des Roboters {{name}} fehlgeschlagen", + "abort_success": "Interpretation des Roboters {{name}} erfolgreich abgebrochen", + "abort_failed": "Abbrechen der Interpretation des Roboters {{name}} fehlgeschlagen" + }, + "menu": { + "recordings": "Roboter", + "runs": "Ausführungen", + "proxy": "Proxy", + "apikey": "API-Schlüssel" + } } } From 3d4ec211e7d71edf3d16d99ff278200f790a6531 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:18:30 +0530 Subject: [PATCH 281/500] feat: add translation for main page notifications --- src/pages/MainPage.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 8af3d3c5..4a82170e 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { MainMenu } from "../components/organisms/MainMenu"; import { Stack } from "@mui/material"; import { Recordings } from "../components/organisms/Recordings"; @@ -30,7 +31,7 @@ export interface ScheduleRunResponse { } export const MainPage = ({ handleEditRecording }: MainPageProps) => { - + const { t } = useTranslation(); const [content, setContent] = React.useState('recordings'); const [sockets, setSockets] = React.useState([]); const [runningRecordingId, setRunningRecordingId] = React.useState(''); @@ -49,10 +50,10 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => { aborted = true; notifyAboutAbort(runId).then(async (response) => { if (response) { - notify('success', `Interpretation of robot ${runningRecordingName} aborted successfully`); + notify('success', t('main_page.notifications.abort_success', { name: runningRecordingName })); await stopRecording(ids.browserId); } else { - notify('error', `Failed to abort the interpretation of ${runningRecordingName} robot`); + notify('error', t('main_page.notifications.abort_failed', { name: runningRecordingName })); } }) } @@ -67,9 +68,9 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => { interpretStoredRecording(runId).then(async (interpretation: boolean) => { if (!aborted) { if (interpretation) { - notify('success', `Interpretation of robot ${runningRecordingName} succeeded`); + notify('success', t('main_page.notifications.interpretation_success', { name: runningRecordingName })); } else { - notify('success', `Failed to interpret ${runningRecordingName} robot`); + notify('success', t('main_page.notifications.interpretation_failed', { name: runningRecordingName })); // destroy the created browser await stopRecording(browserId); } @@ -98,9 +99,9 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => { socket.on('debugMessage', debugMessageHandler); setContent('runs'); if (browserId) { - notify('info', `Running robot: ${runningRecordingName}`); + notify('info', t('main_page.notifications.run_started', { name: runningRecordingName })); } else { - notify('error', `Failed to run robot: ${runningRecordingName}`); + notify('error', t('main_page.notifications.run_start_failed', { name: runningRecordingName })); } }) return (socket: Socket, browserId: string, runId: string) => { @@ -113,9 +114,9 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => { scheduleStoredRecording(runningRecordingId, settings) .then(({ message, runId }: ScheduleRunResponse) => { if (message === 'success') { - notify('success', `Robot ${runningRecordingName} scheduled successfully`); + notify('success', t('main_page.notifications.schedule_success', { name: runningRecordingName })); } else { - notify('error', `Failed to schedule robot ${runningRecordingName}`); + notify('error', t('main_page.notifications.schedule_failed', { name: runningRecordingName })); } }); } @@ -151,4 +152,4 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => { {DisplayContent()} ); -}; +}; \ No newline at end of file From 329aff1ce98b5c377a9ce75d8dbdbe1b08e73cfb Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:19:04 +0530 Subject: [PATCH 282/500] feat: add translation for proxy form notifications --- src/components/organisms/ProxyForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 8fbf730a..46874349 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -84,10 +84,10 @@ const ProxyForm: React.FC = () => { notify('success', t('proxy.notifications.config_success')); } else { notify('error', t('proxy.notifications.config_error')); - console.log(`Failed to submit proxy configuration. Try again. ${response}`) + console.log(`${t('proxy.notifications.config_error')} ${response}`) } } catch (error: any) { - notify('error', `${error} : Failed to submit proxy configuration`); + notify('error', `${error} : ${t('proxy.notifications.config_error')}`); } }; From e0c7acfb1908bd27b50fcaf6f8a0a2599bdb47c2 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:31:44 +0530 Subject: [PATCH 283/500] feat: add german translation for browser window notifications --- public/locales/de.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 2e99b451..685fbd20 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -383,5 +383,27 @@ "proxy": "Proxy", "apikey": "API-Schlüssel" } + }, + "browser_window": { + "attribute_modal": { + "title": "Attribut auswählen", + "notifications": { + "list_select_success": "Liste erfolgreich ausgewählt. Wählen Sie die zu extrahierenden Textdaten.", + "pagination_select_success": "Paginierungselement erfolgreich ausgewählt." + } + }, + "attribute_options": { + "anchor": { + "text": "Text: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "Alt-Text: {{altText}}", + "image_url": "Bild-URL: {{imageUrl}}" + }, + "default": { + "text": "Text: {{text}}" + } + } } } From 6dafc7986c59d6970cfb613ea6abc00d270e7239 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:32:09 +0530 Subject: [PATCH 284/500] feat: add spanish translation for browser window notifications --- public/locales/es.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 5db7f0c7..69c61db6 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -384,5 +384,27 @@ "proxy": "Proxy", "apikey": "Clave API" } + }, + "browser_window": { + "attribute_modal": { + "title": "Seleccionar Atributo", + "notifications": { + "list_select_success": "Lista seleccionada correctamente. Seleccione los datos de texto para extracción.", + "pagination_select_success": "Elemento de paginación seleccionado correctamente." + } + }, + "attribute_options": { + "anchor": { + "text": "Texto: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "Texto Alt: {{altText}}", + "image_url": "URL de Imagen: {{imageUrl}}" + }, + "default": { + "text": "Texto: {{text}}" + } + } } } \ No newline at end of file From f92236fe0986b545e1c1008b63dd4eb68e0cf907 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:32:23 +0530 Subject: [PATCH 285/500] feat: add japanese translation for browser window notifications --- public/locales/ja.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index 7c15d9af..596adebc 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -384,5 +384,27 @@ "proxy": "プロキシ", "apikey": "APIキー" } + }, + "browser_window": { + "attribute_modal": { + "title": "属性を選択", + "notifications": { + "list_select_success": "リストが正常に選択されました。抽出するテキストデータを選択してください。", + "pagination_select_success": "ページネーション要素が正常に選択されました。" + } + }, + "attribute_options": { + "anchor": { + "text": "テキスト: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "代替テキスト: {{altText}}", + "image_url": "画像URL: {{imageUrl}}" + }, + "default": { + "text": "テキスト: {{text}}" + } + } } } From 7101eba88fff47642dc31fb18dfeb9ad9424f449 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:32:41 +0530 Subject: [PATCH 286/500] feat: add english translation for browser window notifications --- public/locales/en.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 5c40b87f..7424872e 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -394,5 +394,27 @@ "proxy": "Proxy", "apikey": "API Key" } + }, + "browser_window": { + "attribute_modal": { + "title": "属性を選択", + "notifications": { + "list_select_success": "リストが正常に選択されました。抽出するテキストデータを選択してください。", + "pagination_select_success": "ページネーション要素が正常に選択されました。" + } + }, + "attribute_options": { + "anchor": { + "text": "テキスト: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "代替テキスト: {{altText}}", + "image_url": "画像URL: {{imageUrl}}" + }, + "default": { + "text": "テキスト: {{text}}" + } + } } } \ No newline at end of file From 95c57591e00ea880a3a3ed4499c86743ac96be1f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:32:56 +0530 Subject: [PATCH 287/500] feat: add chinese translation for browser window notifications --- public/locales/zh.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 3032841f..de9c8594 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -384,5 +384,27 @@ "proxy": "代理", "apikey": "API密钥" } + }, + "browser_window": { + "attribute_modal": { + "title": "选择属性", + "notifications": { + "list_select_success": "列表选择成功。选择要提取的文本数据。", + "pagination_select_success": "分页元素选择成功。" + } + }, + "attribute_options": { + "anchor": { + "text": "文本: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "替代文本: {{altText}}", + "image_url": "图像URL: {{imageUrl}}" + }, + "default": { + "text": "文本: {{text}}" + } + } } } \ No newline at end of file From 33ac9c3a2d69b39436aa058b93c73ec36fdcb55e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:33:21 +0530 Subject: [PATCH 288/500] feat: add translation for browser window notifications --- src/components/organisms/BrowserWindow.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 697b4adb..c7e9fc0f 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -7,6 +7,7 @@ import { GenericModal } from '../atoms/GenericModal'; import { useActionContext } from '../../context/browserActions'; import { useBrowserSteps, TextStep } from '../../context/browserSteps'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useTranslation } from 'react-i18next'; interface ElementInfo { @@ -52,6 +53,7 @@ const getAttributeOptions = (tagName: string, elementInfo: ElementInfo | null): }; export const BrowserWindow = () => { + const { t } = useTranslation(); const [canvasRef, setCanvasReference] = useState | undefined>(undefined); const [screenShot, setScreenShot] = useState(""); const [highlighterData, setHighlighterData] = useState<{ rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] } | null>(null); @@ -200,7 +202,7 @@ export const BrowserWindow = () => { // Only allow selection in pagination mode if type is not empty, 'scrollDown', or 'scrollUp' if (paginationType !== '' && paginationType !== 'scrollDown' && paginationType !== 'scrollUp' && paginationType !== 'none') { setPaginationSelector(highlighterData.selector); - notify(`info`, `Pagination element selected successfully.`); + notify(`info`, t('browser_window.attribute_modal.notifications.pagination_select_success')); addListStep(listSelector!, fields, currentListId || 0, { type: paginationType, selector: highlighterData.selector }); } return; @@ -208,7 +210,7 @@ export const BrowserWindow = () => { if (getList === true && !listSelector) { setListSelector(highlighterData.selector); - notify(`info`, `List selected succesfully. Select the text data for extraction.`) + notify(`info`, t('browser_window.attribute_modal.notifications.list_select_success')); setCurrentListId(Date.now()); setFields({}); } else if (getList === true && listSelector && currentListId) { From 70eed0c36cd578ddcef7034992bdefcb5d0ab8e8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:38:01 +0530 Subject: [PATCH 289/500] feat: add translation for integration settings notifications --- src/components/molecules/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 97973645..15e5dad8 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -109,7 +109,7 @@ export const IntegrationSettingsModal = ({ }, { withCredentials: true } ); - notify(`success`, `Google Sheet selected successfully`) + notify(`success`, t('integration_settings.notifications.sheet_selected')); console.log("Google Sheet ID updated:", response.data); } catch (error: any) { console.error( From 3f0a58e584a465686eb5c7d505412716206b28af Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:42:32 +0530 Subject: [PATCH 290/500] feat: add translation for integration settings notifications --- src/components/molecules/IntegrationSettings.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 15e5dad8..c4b13e8c 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -79,8 +79,9 @@ export const IntegrationSettingsModal = ({ ); notify( "error", - `Error fetching spreadsheet files: ${error.response?.data?.message || error.message - }` + t('integration_settings.errors.fetch_error', { + message: error.response?.data?.message || error.message + }) ); } }; From c8265a806038c8d4e3dbeda35f43f9dece682033 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:43:10 +0530 Subject: [PATCH 291/500] feat: add translation for navbar notifications --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 0658a766..e5b26b96 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -105,7 +105,7 @@ export const NavBar: React.FC = ({ const goToMainMenu = async () => { if (browserId) { await stopRecording(browserId); - notify("warning", "Current Recording was terminated"); + notify("warning", t('browser_recording.notifications.terminated')); setBrowserId(null); } navigate("/"); From 6e3724798796347c57316d9ad590308da34b795f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:48:18 +0530 Subject: [PATCH 292/500] feat: add chinese translation for main menu --- public/locales/zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index de9c8594..0858cdf7 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -50,7 +50,7 @@ "proxy": "代理", "apikey": "API密钥", "feedback": "加入 Maxun Cloud", - "apidocs": "API文档" + "apidocs": "网站转API" }, "runstable": { "runs": "所有运行记录", From 5d8f97179f7dba81acc420749eae567fc3a35ce7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:48:34 +0530 Subject: [PATCH 293/500] feat: add spanish translation for main menu --- public/locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index 69c61db6..1053a6df 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -50,7 +50,7 @@ "proxy": "Proxy", "apikey": "Clave API", "feedback": "Unirse a Maxun Cloud", - "apidocs": "Documentación API" + "apidocs": "Sitio Web a API" }, "runstable": { "runs": "Todas las ejecuciones", From e3629d6007de1b4db94ee85cbdbab34e7fcb90f6 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:48:47 +0530 Subject: [PATCH 294/500] feat: add german translation for main menu --- public/locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index 685fbd20..7d09dc7e 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -49,7 +49,7 @@ "proxy": "Proxy", "apikey": "API-Schlüssel", "feedback": "Maxun Cloud beitreten", - "apidocs": "API-Dokumentation" + "apidocs": "Website zu API" }, "runstable": { "runs": "Alle Ausführungen", From 6ecae3894035503b97118cbd9c70e87ea6d69fa8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:49:04 +0530 Subject: [PATCH 295/500] feat: add japanese translation for main menu --- public/locales/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index 596adebc..f99631eb 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -50,7 +50,7 @@ "proxy": "プロキシ", "apikey": "APIキー", "feedback": "Maxunクラウドに参加する", - "apidocs": "APIドキュメント" + "apidocs": "WebサイトからAPI" }, "runstable": { "runs": "すべての実行", From e1bd600307c22131d77e728d1782cbef0dc4bcfd Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 16:49:19 +0530 Subject: [PATCH 296/500] feat: add english translation for main menu --- public/locales/en.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7424872e..7176a56e 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -50,8 +50,7 @@ "proxy": "Proxy", "apikey": "API Key", "feedback":"Join Maxun Cloud", - "apidocs":"API Docs" - + "apidocs":"Website To API" }, "runstable":{ "runs":"All Runs", From c95b1b8a244c1e44f325c1f67ecae1600eeea373 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:21:52 +0530 Subject: [PATCH 297/500] feat: add export to interface Data --- src/components/molecules/RunsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index cc64672d..61e139ec 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -36,7 +36,7 @@ interface Column { format?: (value: string) => string; } -interface Data { +export interface Data { id: number; status: string; name: string; From 0fd870a1124409860f3a55fa16bac9adde70c620 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:29:20 +0530 Subject: [PATCH 298/500] feat: add chinese translation for collapsible row --- public/locales/zh.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 0858cdf7..7fc99df0 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -406,5 +406,29 @@ "text": "文本: {{text}}" } } + }, + "runs_table": { + "run_type_chips": { + "manual_run": "手动运行", + "scheduled_run": "计划运行", + "api": "API", + "unknown_run_type": "未知运行类型" + }, + "run_status_chips": { + "success": "成功", + "running": "运行中", + "scheduled": "已计划", + "failed": "失败" + }, + "run_settings_modal": { + "title": "运行设置", + "labels": { + "run_id": "运行ID", + "run_by_user": "由用户运行", + "run_by_schedule": "按计划ID运行", + "run_by_api": "由API运行", + "run_type": "运行类型" + } + } } } \ No newline at end of file From f016c1a175e33d7bc75e939b9780d07f3bfbf6bf Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:29:37 +0530 Subject: [PATCH 299/500] feat: add german translation for collapsible row --- public/locales/de.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 7d09dc7e..d33f413b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -405,5 +405,29 @@ "text": "Text: {{text}}" } } + }, + "runs_table": { + "run_type_chips": { + "manual_run": "Manuelle Ausführung", + "scheduled_run": "Geplante Ausführung", + "api": "API", + "unknown_run_type": "Unbekannter Ausführungstyp" + }, + "run_status_chips": { + "success": "Erfolg", + "running": "Läuft", + "scheduled": "Geplant", + "failed": "Fehlgeschlagen" + }, + "run_settings_modal": { + "title": "Ausführungseinstellungen", + "labels": { + "run_id": "Ausführungs-ID", + "run_by_user": "Ausgeführt von Benutzer", + "run_by_schedule": "Ausgeführt nach Zeitplan-ID", + "run_by_api": "Ausgeführt durch API", + "run_type": "Ausführungstyp" + } + } } } From 9765363ba83e0b71e1efd2cb25200b59df442726 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:29:52 +0530 Subject: [PATCH 300/500] feat: add spanish translation for collapsible row --- public/locales/es.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 1053a6df..24dcad09 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -406,5 +406,29 @@ "text": "Texto: {{text}}" } } + }, + "runs_table": { + "run_type_chips": { + "manual_run": "Ejecución Manual", + "scheduled_run": "Ejecución Programada", + "api": "API", + "unknown_run_type": "Tipo de Ejecución Desconocido" + }, + "run_status_chips": { + "success": "Éxito", + "running": "Ejecutando", + "scheduled": "Programado", + "failed": "Fallido" + }, + "run_settings_modal": { + "title": "Configuración de Ejecución", + "labels": { + "run_id": "ID de Ejecución", + "run_by_user": "Ejecutado por Usuario", + "run_by_schedule": "Ejecutado por ID de Programación", + "run_by_api": "Ejecutado por API", + "run_type": "Tipo de Ejecución" + } + } } } \ No newline at end of file From 0f263978dfde409f490eda5d6758efc71d16283a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:30:06 +0530 Subject: [PATCH 301/500] feat: add japanese translation for collapsible row --- public/locales/ja.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index f99631eb..fc31c463 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -406,5 +406,29 @@ "text": "テキスト: {{text}}" } } + }, + "runs_table": { + "run_type_chips": { + "manual_run": "手動実行", + "scheduled_run": "スケジュール実行", + "api": "API", + "unknown_run_type": "不明な実行タイプ" + }, + "run_status_chips": { + "success": "成功", + "running": "実行中", + "scheduled": "スケジュール済み", + "failed": "失敗" + }, + "run_settings_modal": { + "title": "実行設定", + "labels": { + "run_id": "実行ID", + "run_by_user": "ユーザーによる実行", + "run_by_schedule": "スケジュールによる実行", + "run_by_api": "APIによる実行", + "run_type": "実行タイプ" + } + } } } From 0942e5f17f57200c206c5fd8088a040b825d2fe9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:30:22 +0530 Subject: [PATCH 302/500] feat: add english translation for collapsible row --- public/locales/en.json | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7176a56e..111a9391 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -415,5 +415,29 @@ "text": "テキスト: {{text}}" } } + }, + "runs_table": { + "run_type_chips": { + "manual_run": "Manual Run", + "scheduled_run": "Scheduled Run", + "api": "API", + "unknown_run_type": "Unknown Run Type" + }, + "run_status_chips": { + "success": "Success", + "running": "Running", + "scheduled": "Scheduled", + "failed": "Failed" + }, + "run_settings_modal": { + "title": "Run Settings", + "labels": { + "run_id": "Run ID", + "run_by_user": "Run by User", + "run_by_schedule": "Run by Schedule ID", + "run_by_api": "Run by API", + "run_type": "Run Type" + } + } } - } \ No newline at end of file +} \ No newline at end of file From e83737c80673c7eeab94ae8b46ac1356c77b49b9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:30:45 +0530 Subject: [PATCH 303/500] feat: add translation for collapsible row --- src/components/molecules/ColapsibleRow.tsx | 46 +++++++++++++++------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/components/molecules/ColapsibleRow.tsx b/src/components/molecules/ColapsibleRow.tsx index 95b144d7..5e4be9da 100644 --- a/src/components/molecules/ColapsibleRow.tsx +++ b/src/components/molecules/ColapsibleRow.tsx @@ -10,6 +10,7 @@ import { RunContent } from "./RunContent"; import { GenericModal } from "../atoms/GenericModal"; import { modalStyle } from "./AddWhereCondModal"; import { getUserById } from "../../api/auth"; +import { useTranslation } from "react-i18next"; interface RunTypeChipProps { runByUserId?: string; @@ -18,10 +19,12 @@ interface RunTypeChipProps { } const RunTypeChip: React.FC = ({ runByUserId, runByScheduledId, runByAPI }) => { - if (runByUserId) return ; - if (runByScheduledId) return ; - if (runByAPI) return ; - return ; + const { t } = useTranslation(); + + if (runByUserId) return ; + if (runByScheduledId) return ; + if (runByAPI) return ; + return ; }; interface CollapsibleRowProps { @@ -33,6 +36,7 @@ interface CollapsibleRowProps { runningRecordingName: string; } export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRunHandler, runningRecordingName }: CollapsibleRowProps) => { + const { t } = useTranslation(); const [open, setOpen] = useState(isOpen); const [openSettingsModal, setOpenSettingsModal] = useState(false); const [userEmail, setUserEmail] = useState(null); @@ -99,12 +103,12 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun } else { switch (column.id) { case 'runStatus': - return ( + return ( - {row.status === 'success' && } - {row.status === 'running' && } - {row.status === 'scheduled' && } - {row.status === 'failed' && } + {row.status === 'success' && } + {row.status === 'running' && } + {row.status === 'scheduled' && } + {row.status === 'failed' && } ) case 'delete': @@ -133,21 +137,35 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun modalStyle={modalStyle} > <> - Run Settings + + {t('runs_table.run_settings_modal.title')} + - Run Type: - + + {t('runs_table.run_settings_modal.labels.run_type')}: + + From defc426466ac5fcea547ed7bae1e90431828771c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:38:20 +0530 Subject: [PATCH 304/500] feat: add english translation for run content --- public/locales/en.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/public/locales/en.json b/public/locales/en.json index 111a9391..232a8279 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -439,5 +439,25 @@ "run_type": "Run Type" } } + }, + "run_content": { + "tabs": { + "output_data": "Output Data", + "log": "Log" + }, + "empty_output": "The output is empty.", + "captured_data": { + "title": "Captured Data", + "download_json": "Download as JSON", + "download_csv": "Download as CSV" + }, + "captured_screenshot": { + "title": "Captured Screenshot", + "download": "Download Screenshot", + "render_failed": "The image failed to render" + }, + "buttons": { + "stop": "Stop" + } } } \ No newline at end of file From 873a0d6df6955474ed02739c27a6133bbced56c7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:38:33 +0530 Subject: [PATCH 305/500] feat: add japanese translation for run content --- public/locales/ja.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/public/locales/ja.json b/public/locales/ja.json index fc31c463..26563215 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -430,5 +430,25 @@ "run_type": "実行タイプ" } } + }, + "run_content": { + "tabs": { + "output_data": "出力データ", + "log": "ログ" + }, + "empty_output": "出力は空です。", + "captured_data": { + "title": "キャプチャされたデータ", + "download_json": "JSONとしてダウンロード", + "download_csv": "CSVとしてダウンロード" + }, + "captured_screenshot": { + "title": "キャプチャされたスクリーンショット", + "download": "スクリーンショットをダウンロード", + "render_failed": "画像のレンダリングに失敗しました" + }, + "buttons": { + "stop": "停止" + } } } From 4ddf7e8a2970c0b4880e6ab6d673a44429d6ba03 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:39:05 +0530 Subject: [PATCH 306/500] feat: add spanish\ translation for run content --- public/locales/es.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index 24dcad09..f7a40e86 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -430,5 +430,25 @@ "run_type": "Tipo de Ejecución" } } + }, + "run_content": { + "tabs": { + "output_data": "Datos de Salida", + "log": "Registro" + }, + "empty_output": "La salida está vacía.", + "captured_data": { + "title": "Datos Capturados", + "download_json": "Descargar como JSON", + "download_csv": "Descargar como CSV" + }, + "captured_screenshot": { + "title": "Captura de Pantalla", + "download": "Descargar Captura", + "render_failed": "No se pudo renderizar la imagen" + }, + "buttons": { + "stop": "Detener" + } } } \ No newline at end of file From c9414fc1c283d0b1fed7ec742f5f9488f069fd85 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:41:22 +0530 Subject: [PATCH 307/500] feat: add german translation for run content --- public/locales/de.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index d33f413b..5c86d757 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -429,5 +429,25 @@ "run_type": "Ausführungstyp" } } + }, + "run_content": { + "tabs": { + "output_data": "Ausgabedaten", + "log": "Protokoll" + }, + "empty_output": "Die Ausgabe ist leer.", + "captured_data": { + "title": "Erfasste Daten", + "download_json": "Als JSON herunterladen", + "download_csv": "Als CSV herunterladen" + }, + "captured_screenshot": { + "title": "Erfasster Screenshot", + "download": "Screenshot herunterladen", + "render_failed": "Das Bild konnte nicht gerendert werden" + }, + "buttons": { + "stop": "Stoppen" + } } } From e1ff7f273d62852a2117117c792498412e2d7353 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:41:35 +0530 Subject: [PATCH 308/500] feat: add chinese translation for run content --- public/locales/zh.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 7fc99df0..4f56bd2c 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -430,5 +430,25 @@ "run_type": "运行类型" } } + }, + "run_content": { + "tabs": { + "output_data": "输出数据", + "log": "日志" + }, + "empty_output": "输出为空。", + "captured_data": { + "title": "捕获的数据", + "download_json": "下载为JSON", + "download_csv": "下载为CSV" + }, + "captured_screenshot": { + "title": "捕获的截图", + "download": "下载截图", + "render_failed": "图像渲染失败" + }, + "buttons": { + "stop": "停止" + } } } \ No newline at end of file From 2df45325e0c1a58553143a591cd31c718d34c4c0 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 17:41:53 +0530 Subject: [PATCH 309/500] feat: add translation for run content --- src/components/molecules/RunContent.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/molecules/RunContent.tsx b/src/components/molecules/RunContent.tsx index ff414628..dff44c6c 100644 --- a/src/components/molecules/RunContent.tsx +++ b/src/components/molecules/RunContent.tsx @@ -13,6 +13,7 @@ import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import 'highlight.js/styles/github.css'; +import { useTranslation } from "react-i18next"; interface RunContentProps { row: Data, @@ -23,6 +24,7 @@ interface RunContentProps { } export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => { + const { t } = useTranslation(); const [tab, setTab] = React.useState('log'); const [tableData, setTableData] = useState([]); const [columns, setColumns] = useState([]); @@ -76,8 +78,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe setTab(newTab)} aria-label="run-content-tabs"> - - + + @@ -102,32 +104,32 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe color="error" onClick={abortRunHandler} > - Stop + {t('run_content.buttons.stop')} : null} {!row || !row.serializableOutput || !row.binaryOutput || (Object.keys(row.serializableOutput).length === 0 && Object.keys(row.binaryOutput).length === 0) - ? The output is empty. : null} + ? {t('run_content.empty_output')} : null} {row.serializableOutput && Object.keys(row.serializableOutput).length !== 0 &&
- Captured Data + {t('run_content.captured_data.title')} - Download as JSON + {t('run_content.captured_data.download_json')} - Download as CSV + {t('run_content.captured_data.download_csv')} {tableData.length > 0 ? ( @@ -171,7 +173,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
- Captured Screenshot + {t('run_content.captured_screenshot.title')} {Object.keys(row.binaryOutput).map((key) => { try { @@ -181,7 +183,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe width: 'max-content', }}> - Download Screenshot + {t('run_content.captured_screenshot.download')} {key} @@ -189,7 +191,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe } catch (e) { console.log(e) return - {key}: The image failed to render + {key}: {t('run_content.captured_screenshot.render_failed')} } })} From 72d855261f634d7e69c753df3d3113747f28351a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:23:59 +0530 Subject: [PATCH 310/500] fix: missing <> --- src/components/molecules/NavBar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 31355b12..d91bde06 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -116,6 +116,7 @@ export const NavBar: React.FC = ({ }, []); return ( + <>
Date: Sat, 21 Dec 2024 18:34:55 +0530 Subject: [PATCH 311/500] feat: language dropdown --- src/components/molecules/NavBar.tsx | 142 +++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 5 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index d91bde06..4a98f2dd 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -6,7 +6,7 @@ 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 { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -29,7 +29,7 @@ export const NavBar: React.FC = ({ const { state, dispatch } = useContext(AuthContext); const { user } = state; const navigate = useNavigate(); - const { t, i18n } = useTranslation(); // Get translation function and i18n methods + const { t, i18n } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); @@ -297,9 +297,141 @@ export const NavBar: React.FC = ({ )} -
- ) : "" - } + + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + +
+ ) : ( + <> + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + + )} ); From f5f17ba8e26be5366da72d130b0de50ed80b889d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:35:08 +0530 Subject: [PATCH 312/500] chore: lint --- src/components/molecules/NavBar.tsx | 266 ++++++++++++++-------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 4a98f2dd..7f49519d 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -297,141 +297,141 @@ export const NavBar: React.FC = ({ )} - - {t("language")} - - - { - changeLanguage("en"); - handleMenuClose(); + + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + +
+ ) : ( + <> - English - - { - changeLanguage("es"); - handleMenuClose(); - }} - > - Español - - { - changeLanguage("ja"); - handleMenuClose(); - }} - > - 日本語 - - { - changeLanguage("ar"); - handleMenuClose(); - }} - > - العربية - - { - changeLanguage("zh"); - handleMenuClose(); - }} - > - 中文 - - -
- ) : ( - <> - {t("language")} - - - { - changeLanguage("en"); - handleMenuClose(); - }} - > - English - - { - changeLanguage("es"); - handleMenuClose(); - }} - > - Español - - { - changeLanguage("ja"); - handleMenuClose(); - }} - > - 日本語 - - { - changeLanguage("ar"); - handleMenuClose(); - }} - > - العربية - - { - changeLanguage("zh"); - handleMenuClose(); - }} - > - 中文 - - - )} + {t("language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("ar"); + handleMenuClose(); + }} + > + العربية + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + + )} ); From c551cc1c3271ab924c192d7bb6d9d5adc26aff5d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:35:33 +0530 Subject: [PATCH 313/500] feat: remove ar --- src/components/molecules/NavBar.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 7f49519d..7b3da8d5 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -346,14 +346,6 @@ export const NavBar: React.FC = ({ > 日本語 - { - changeLanguage("ar"); - handleMenuClose(); - }} - > - العربية - { changeLanguage("zh"); From af57c196fd7db8d5ec1ea41dff13fef2ca7b2d4c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:35:47 +0530 Subject: [PATCH 314/500] feat: remove ar --- src/components/molecules/NavBar.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 7b3da8d5..271c8e80 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -406,14 +406,6 @@ export const NavBar: React.FC = ({ > 日本語 - { - changeLanguage("ar"); - handleMenuClose(); - }} - > - العربية - { changeLanguage("zh"); From 5c4498ce51201e1f7fb244c1d56a2183cfb294c1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:42:11 +0530 Subject: [PATCH 315/500] feat: add german in dropdown --- src/components/molecules/NavBar.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 271c8e80..7c9604c5 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -354,6 +354,14 @@ export const NavBar: React.FC = ({ > 中文 + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Deutsch +
) : ( @@ -414,6 +422,14 @@ export const NavBar: React.FC = ({ > 中文 + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Deutsch + )} From d17df0eb1776c5d831a6924ee658ff13f8743253 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:42:56 +0530 Subject: [PATCH 316/500] chore: -rm whitespace --- src/components/molecules/NavBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 7c9604c5..33b87f0b 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -1,5 +1,4 @@ import { useTranslation } from "react-i18next"; - import React, { useState, useContext, useEffect } from 'react'; import axios from 'axios'; import styled from "styled-components"; From e3f3dafe83afa8cbd60bc27045fbb294d1033734 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:43:16 +0530 Subject: [PATCH 317/500] chore: -rm Language icon --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 33b87f0b..355e6579 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -5,7 +5,7 @@ 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 { AccountCircle, Logout, Clear, YouTube, X, Update, Close } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; From f102b6cf03b7f659116c9182c45a68adfff79f05 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 21 Dec 2024 18:46:43 +0530 Subject: [PATCH 318/500] feat: bring back update button --- src/components/molecules/NavBar.tsx | 45 ++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 355e6579..d011b85f 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -67,7 +67,6 @@ export const NavBar: React.FC = ({ setTab(newValue); }; - const handleMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -116,6 +115,50 @@ export const NavBar: React.FC = ({ return ( <> + {isUpdateAvailable && ( + setIsUpdateAvailable(false)} + message={ + `New version ${latestVersion} available! Click "Upgrade" to update.` + } + action={ + <> + + setIsUpdateAvailable(false)} + style={{ color: 'black' }} + > + + + + } + ContentProps={{ + sx: { + background: "white", + color: "black", + } + }} + /> + + )}
Date: Sat, 21 Dec 2024 18:47:05 +0530 Subject: [PATCH 319/500] chore: whitespace --- src/components/molecules/NavBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index d011b85f..5d083de1 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -157,7 +157,6 @@ export const NavBar: React.FC = ({ } }} /> - )}
Date: Sat, 21 Dec 2024 18:56:04 +0530 Subject: [PATCH 320/500] feat: move lang dropdown inside account settings --- src/components/molecules/NavBar.tsx | 53 +++++++++++++---------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 5d083de1..6dd5a366 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -5,7 +5,7 @@ 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 } from "@mui/icons-material"; +import { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; @@ -320,36 +320,11 @@ export const NavBar: React.FC = ({ }}> Twiiter (X) - - - ) : ( - <> - - - Discard - - - - )} - - {t("language")} - + {t("Language")} + = ({ Deutsch + + + ) : ( + <> + + + Discard + + + + )}
) : ( <> Date: Sat, 21 Dec 2024 18:56:43 +0530 Subject: [PATCH 321/500] fix: format --- src/components/molecules/NavBar.tsx | 118 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 6dd5a366..8f1d61c7 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -320,65 +320,63 @@ export const NavBar: React.FC = ({ }}> Twiiter (X) - - {t("Language")} - - - { - changeLanguage("en"); - handleMenuClose(); - }} - > - English - - { - changeLanguage("es"); - handleMenuClose(); - }} - > - Español - - { - changeLanguage("ja"); - handleMenuClose(); - }} - > - 日本語 - - { - changeLanguage("zh"); - handleMenuClose(); - }} - > - 中文 - - { - changeLanguage("de"); - handleMenuClose(); - }} - > - Deutsch - - + + {t("Language")} + + + { + changeLanguage("en"); + handleMenuClose(); + }} + > + English + + { + changeLanguage("es"); + handleMenuClose(); + }} + > + Español + + { + changeLanguage("ja"); + handleMenuClose(); + }} + > + 日本語 + + { + changeLanguage("zh"); + handleMenuClose(); + }} + > + 中文 + + { + changeLanguage("de"); + handleMenuClose(); + }} + > + Deutsch + + ) : ( @@ -409,7 +407,7 @@ export const NavBar: React.FC = ({ marginRight: "10px", }} > - {t("language")} + {t("Language")} Date: Sat, 21 Dec 2024 19:02:53 +0530 Subject: [PATCH 322/500] feat: add lang icon --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 8f1d61c7..58f2d72d 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -407,7 +407,7 @@ export const NavBar: React.FC = ({ marginRight: "10px", }} > - {t("Language")} + {t("Language")} Date: Sat, 21 Dec 2024 19:03:05 +0530 Subject: [PATCH 323/500] fix: format --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 58f2d72d..994402ff 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -407,7 +407,7 @@ export const NavBar: React.FC = ({ marginRight: "10px", }} > - {t("Language")} + {t("Language")} Date: Sat, 21 Dec 2024 19:23:43 +0530 Subject: [PATCH 324/500] feat: add chinese translation for navbar --- public/locales/zh.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/public/locales/zh.json b/public/locales/zh.json index 4f56bd2c..600fd821 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -450,5 +450,38 @@ "buttons": { "stop": "停止" } + }, + "navbar": { + "project_name": "Maxun", + "upgrade": { + "button": "升级", + "modal": { + "up_to_date": "🎉 您已是最新版本!", + "new_version_available": "新版本已可用:{{version}}。升级到最新版本以获取错误修复、增强和新功能!", + "view_updates": "查看所有新更新", + "view_updates_link": "此处", + "tabs": { + "manual_setup": "手动设置升级", + "docker_setup": "Docker Compose设置升级" + } + } + }, + "menu_items": { + "logout": "退出登录", + "discord": "Discord", + "youtube": "YouTube", + "twitter": "Twitter (X)", + "language": "语言" + }, + "recording": { + "discard": "丢弃" + } + }, + "language_menu": { + "en": "英语", + "es": "西班牙语", + "ja": "日语", + "zh": "中文", + "de": "德语" } } \ No newline at end of file From 529cedeb68550106d9fa4882eb3694fbc57128f8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 19:23:55 +0530 Subject: [PATCH 325/500] feat: add german translation for navbar --- public/locales/de.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 5c86d757..28855c78 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -449,5 +449,38 @@ "buttons": { "stop": "Stoppen" } + }, + "navbar": { + "project_name": "Maxun", + "upgrade": { + "button": "Upgrade", + "modal": { + "up_to_date": "🎉 Du bist auf dem neuesten Stand!", + "new_version_available": "Eine neue Version ist verfügbar: {{version}}. Aktualisieren Sie auf die neueste Version für Fehlerkorrekturen, Verbesserungen und neue Funktionen!", + "view_updates": "Alle Updates anzeigen", + "view_updates_link": "hier", + "tabs": { + "manual_setup": "Manuelles Setup-Upgrade", + "docker_setup": "Docker Compose Setup-Upgrade" + } + } + }, + "menu_items": { + "logout": "Abmelden", + "discord": "Discord", + "youtube": "YouTube", + "twitter": "Twitter (X)", + "language": "Sprache" + }, + "recording": { + "discard": "Verwerfen" + } + }, + "language_menu": { + "en": "Englisch", + "es": "Spanisch", + "ja": "Japanisch", + "zh": "Chinesisch", + "de": "Deutsch" } } From 17e0afdbf8650df5081b599bf8f3233fbf25b656 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 19:24:11 +0530 Subject: [PATCH 326/500] feat: add spanish translation for navbar --- public/locales/es.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/public/locales/es.json b/public/locales/es.json index f7a40e86..4ef417ea 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -450,5 +450,38 @@ "buttons": { "stop": "Detener" } + }, + "navbar": { + "project_name": "Maxun", + "upgrade": { + "button": "Actualizar", + "modal": { + "up_to_date": "¡Estás actualizado!", + "new_version_available": "Hay una nueva versión disponible: {{version}}. ¡Actualice a la última versión para correcciones de errores, mejoras y nuevas características!", + "view_updates": "Ver todas las actualizaciones", + "view_updates_link": "aquí", + "tabs": { + "manual_setup": "Actualización de Configuración Manual", + "docker_setup": "Actualización de Configuración Docker Compose" + } + } + }, + "menu_items": { + "logout": "Cerrar sesión", + "discord": "Discord", + "youtube": "YouTube", + "twitter": "Twitter (X)", + "language": "Idioma" + }, + "recording": { + "discard": "Descartar" + } + }, + "language_menu": { + "en": "Inglés", + "es": "Español", + "ja": "Japonés", + "zh": "Chino", + "de": "Alemán" } } \ No newline at end of file From 2958453e657b2d19e671bfa13c2e91beb3620888 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 19:24:47 +0530 Subject: [PATCH 327/500] feat: add translation --- public/locales/en.json | 33 +++++++++++++++++++++++++++++ public/locales/ja.json | 33 +++++++++++++++++++++++++++++ src/components/molecules/NavBar.tsx | 24 ++++++++++----------- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 232a8279..8084b5f2 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -459,5 +459,38 @@ "buttons": { "stop": "Stop" } + }, + "navbar": { + "project_name": "Maxun", + "upgrade": { + "button": "Upgrade", + "modal": { + "up_to_date": "🎉 You're up to date!", + "new_version_available": "A new version is available: {{version}}. Upgrade to the latest version for bug fixes, enhancements and new features!", + "view_updates": "View all the new updates", + "view_updates_link": "here", + "tabs": { + "manual_setup": "Manual Setup Upgrade", + "docker_setup": "Docker Compose Setup Upgrade" + } + } + }, + "menu_items": { + "logout": "Logout", + "discord": "Discord", + "youtube": "YouTube", + "twitter": "Twitter (X)", + "language": "Language" + }, + "recording": { + "discard": "Discard" + } + }, + "language_menu": { + "en": "English", + "es": "Spanish", + "ja": "Japanese", + "zh": "Chinese", + "de": "German" } } \ No newline at end of file diff --git a/public/locales/ja.json b/public/locales/ja.json index 26563215..b40cc6e6 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -450,5 +450,38 @@ "buttons": { "stop": "停止" } + }, + "navbar": { + "project_name": "Maxun", + "upgrade": { + "button": "アップグレード", + "modal": { + "up_to_date": "最新版です!", + "new_version_available": "新しいバージョンが利用可能です: {{version}}。バグ修正、機能強化のために最新版にアップグレードしてください。", + "view_updates": "すべての更新を", + "view_updates_link": "こちら", + "tabs": { + "manual_setup": "手動セットアップ", + "docker_setup": "Docker Composeセットアップ" + } + } + }, + "menu_items": { + "logout": "ログアウト", + "discord": "Discord", + "youtube": "YouTube", + "twitter": "Twitter (X)", + "language": "言語" + }, + "recording": { + "discard": "破棄" + } + }, + "language_menu": { + "en": "英語", + "es": "スペイン語", + "ja": "日本語", + "zh": "中国語", + "de": "ドイツ語" } } diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 994402ff..142d45ab 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -120,7 +120,7 @@ export const NavBar: React.FC = ({ open={isUpdateAvailable} onClose={() => setIsUpdateAvailable(false)} message={ - `New version ${latestVersion} available! Click "Upgrade" to update.` + `${t('navbar.upgrade.modal.new_version_available', { version: latestVersion })} ${t('navbar.upgrade.modal.view_updates')}` } action={ <> @@ -137,7 +137,7 @@ export const NavBar: React.FC = ({ borderRadius: '5px', }} > - Upgrade + {t('navbar.upgrade.button')} = ({ justifyContent: 'flex-start', }}> -
Maxun
+
{t('navbar.project_name')}
= ({ border: "#00000099 1px solid", '&:hover': { color: '#ff00c3', border: '#ff00c3 1px solid' } }}> - Upgrade Maxun + {t('navbar.upgrade.button')} Maxun = ({ Checking for updates... ) : currentVersion === latestVersion ? ( - 🎉 You're up to date! + {t('navbar.upgrade.modal.up_to_date')} ) : ( <> - A new version is available: {latestVersion}. Upgrade to the latest version for bug fixes, enhancements and new features! + {t('navbar.upgrade.modal.new_version_available', { version: latestVersion })}
- View all the new updates + {t('navbar.upgrade.modal.view_updates')} {' '}here.
= ({ sx={{ marginTop: 2, marginBottom: 2 }} centered > - - + + {tab === 0 && ( @@ -303,7 +303,7 @@ export const NavBar: React.FC = ({ PaperProps={{ sx: { width: '180px' } }} > { handleMenuClose(); logout(); }}> - Logout + {t('navbar.menu_items.logout')} { window.open('https://discord.gg/5GbPjBUkws', '_blank'); @@ -321,7 +321,7 @@ export const NavBar: React.FC = ({ Twiiter (X) - {t("Language")} + {t('navbar.menu_items.language')} = ({ '&:hover': { color: 'white', backgroundColor: 'red' } }}> - Discard + {t('navbar.recording.discard')} From 2e28f84b12c49880c9cbd831e60870c80abdd2c5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 20:34:35 +0530 Subject: [PATCH 328/500] feat: add reset button for output preview --- .../molecules/InterpretationLog.tsx | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 0a771535..4d14bc51 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -94,6 +94,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se setCustomValue(event.target.value); }; + const handleReset = () => { + setLog(''); + setTableData([]); + setBinaryData(null); + }; + useEffect(() => { socket?.on('log', handleLog); socket?.on('serializableCallback', handleSerializableCallback); @@ -172,12 +178,34 @@ export const InterpretationLog: React.FC = ({ isOpen, se > { binaryData ? ( -
- - {t('interpretation_log.titles.screenshot')} - - {t('interpretation_log.titles.screenshot')} -
+ <> +
+ + {t('interpretation_log.titles.screenshot')} + + {t('interpretation_log.titles.screenshot')} +
+ + ) : tableData.length > 0 ? ( <> @@ -203,6 +231,25 @@ export const InterpretationLog: React.FC = ({ isOpen, se {t('interpretation_log.messages.additional_rows')} + ) : ( From 8cd5efcac301edb93cad100a0ca474e4962e9436 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 20:35:26 +0530 Subject: [PATCH 329/500] feat: add reset button translation --- public/locales/de.json | 1 + public/locales/en.json | 1 + public/locales/es.json | 1 + public/locales/ja.json | 1 + public/locales/zh.json | 1 + 5 files changed, 5 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 28855c78..6ea6e509 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -250,6 +250,7 @@ "interpretation_buttons": { "buttons": { "preview": "Vorschau der Ausgabedaten anzeigen", + "reset": "Zurücksetzen", "yes": "Ja", "no": "Nein" }, diff --git a/public/locales/en.json b/public/locales/en.json index 8084b5f2..0b4f9461 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -251,6 +251,7 @@ "interpretation_buttons": { "buttons": { "preview": "Get Preview of Output Data", + "reset": "Reset", "yes": "Yes", "no": "No" }, diff --git a/public/locales/es.json b/public/locales/es.json index 4ef417ea..40ef1d6b 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -251,6 +251,7 @@ "interpretation_buttons": { "buttons": { "preview": "Obtener Vista Previa de Datos de Salida", + "reset": "Restablecer", "yes": "Sí", "no": "No" }, diff --git a/public/locales/ja.json b/public/locales/ja.json index b40cc6e6..946ae029 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -251,6 +251,7 @@ "interpretation_buttons": { "buttons": { "preview": "出力データのプレビューを取得", + "reset": "リセット", "yes": "はい", "no": "いいえ" }, diff --git a/public/locales/zh.json b/public/locales/zh.json index 600fd821..8ec36ddc 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -251,6 +251,7 @@ "interpretation_buttons": { "buttons": { "preview": "获取输出数据预览", + "reset": "重置", "yes": "是", "no": "否" }, From 74da84a14421e8ecdab6366150e2bd4b26ffc3f7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 21:42:48 +0530 Subject: [PATCH 330/500] feat: add output preview reset notification --- src/components/molecules/InterpretationLog.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 4d14bc51..05e2876c 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -35,7 +35,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se const { width } = useBrowserDimensionsStore(); const { socket } = useSocketStore(); - const { currentWorkflowActionsState } = useGlobalInfoStore(); + const { currentWorkflowActionsState, notify } = useGlobalInfoStore(); const toggleDrawer = (newOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { if ( @@ -98,6 +98,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se setLog(''); setTableData([]); setBinaryData(null); + notify("success", t('interpretation_log.notifications.reset_success')); }; useEffect(() => { From 64f452b4dc1546d4fcaafe041d00c50bfad73ad8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 21:43:35 +0530 Subject: [PATCH 331/500] feat: add reset success translation --- public/locales/de.json | 3 +++ public/locales/en.json | 3 +++ public/locales/es.json | 3 +++ public/locales/ja.json | 3 +++ public/locales/zh.json | 3 +++ 5 files changed, 15 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 6ea6e509..90beaa14 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -245,6 +245,9 @@ "mimetype": "Medientyp: ", "image_below": "Bild wird unten angezeigt:", "separator": "--------------------------------------------------" + }, + "notifications": { + "reset_success": "Vorschau erfolgreich zurückgesetzt" } }, "interpretation_buttons": { diff --git a/public/locales/en.json b/public/locales/en.json index 0b4f9461..7752ad78 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -246,6 +246,9 @@ "mimetype": "mimetype: ", "image_below": "Image is rendered below:", "separator": "--------------------------------------------------" + }, + "notifications": { + "reset_success": "Output Preview reset successfully" } }, "interpretation_buttons": { diff --git a/public/locales/es.json b/public/locales/es.json index 40ef1d6b..00fa379e 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -265,6 +265,9 @@ "use_previous": "¿Desea usar su selección anterior como condición para realizar esta acción?", "previous_action": "Su acción anterior fue: ", "element_text": "en un elemento con texto " + }, + "notifications": { + "reset_success": "Vista previa restablecida correctamente" } }, "recording_page": { diff --git a/public/locales/ja.json b/public/locales/ja.json index 946ae029..b444c81a 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -246,6 +246,9 @@ "mimetype": "MIMEタイプ: ", "image_below": "画像は以下に表示されます:", "separator": "--------------------------------------------------" + }, + "notifications": { + "reset_success": "出力プレビューが正常にリセットされました" } }, "interpretation_buttons": { diff --git a/public/locales/zh.json b/public/locales/zh.json index 8ec36ddc..27455ebe 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -246,6 +246,9 @@ "mimetype": "MIME类型:", "image_below": "图片显示如下:", "separator": "--------------------------------------------------" + }, + "notifications": { + "reset_success": "输出预览已成功重置" } }, "interpretation_buttons": { From c2b7088b180a6804b36ec2c08e54009b634ff608 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 22:58:23 +0530 Subject: [PATCH 332/500] feat: add context handlers to reset output preview log --- src/context/globalInfo.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/context/globalInfo.tsx b/src/context/globalInfo.tsx index 58589c3a..ac281630 100644 --- a/src/context/globalInfo.tsx +++ b/src/context/globalInfo.tsx @@ -32,6 +32,8 @@ interface GlobalInfo { hasScreenshotAction: boolean; hasScrapeSchemaAction: boolean; }) => void; + shouldResetInterpretationLog: boolean; + resetInterpretationLog: () => void; }; class GlobalInfoStore implements Partial { @@ -53,6 +55,7 @@ class GlobalInfoStore implements Partial { hasScreenshotAction: false, hasScrapeSchemaAction: false, }; + shouldResetInterpretationLog = false; }; const globalInfoStore = new GlobalInfoStore(); @@ -71,6 +74,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => { const [recordingName, setRecordingName] = useState(globalInfoStore.recordingName); const [recordingUrl, setRecordingUrl] = useState(globalInfoStore.recordingUrl); const [currentWorkflowActionsState, setCurrentWorkflowActionsState] = useState(globalInfoStore.currentWorkflowActionsState); + const [shouldResetInterpretationLog, setShouldResetInterpretationLog] = useState(globalInfoStore.shouldResetInterpretationLog); const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => { setNotification({ severity, message, isOpen: true }); @@ -87,6 +91,14 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => { } } + const resetInterpretationLog = () => { + setShouldResetInterpretationLog(true); + // Reset the flag after a short delay to allow components to respond + setTimeout(() => { + setShouldResetInterpretationLog(false); + }, 100); + } + return ( { setRecordingUrl, currentWorkflowActionsState, setCurrentWorkflowActionsState, + shouldResetInterpretationLog, + resetInterpretationLog, }} > {children} From e16d1a1c113b8066a882532422b8d9c001cd660d Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 22:59:54 +0530 Subject: [PATCH 333/500] feat: add reset interpretation context action on confirm text data --- src/components/organisms/RightSidePanel.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 224e0954..ba178b7b 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -57,7 +57,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const [hoverStates, setHoverStates] = useState<{ [id: string]: boolean }>({}); const [browserStepIdList, setBrowserStepIdList] = useState([]); - const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState } = useGlobalInfoStore(); + const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog } = useGlobalInfoStore(); const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); const { browserSteps, updateBrowserTextStepLabel, deleteBrowserStep, addScreenshotStep, updateListTextFieldLabel, removeListTextField } = useBrowserSteps(); const { id, socket } = useSocketStore(); @@ -225,8 +225,9 @@ export const RightSidePanel: React.FC = ({ onFinishCapture if (hasTextSteps) { socket?.emit('action', { action: 'scrapeSchema', settings }); } + resetInterpretationLog(); onFinishCapture(); - }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps]); + }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps, resetInterpretationLog]); const getListSettingsObject = useCallback(() => { let settings: { From fb7fafbae6a9cb2742a25f724db60ca5c309051b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 23:02:15 +0530 Subject: [PATCH 334/500] feat: rm reset button for output preview --- .../molecules/InterpretationLog.tsx | 53 +++---------------- 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 05e2876c..99b9fc8b 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -179,34 +179,12 @@ export const InterpretationLog: React.FC = ({ isOpen, se > { binaryData ? ( - <> -
- - {t('interpretation_log.titles.screenshot')} - - {t('interpretation_log.titles.screenshot')} -
- - +
+ + {t('interpretation_log.titles.screenshot')} + + {t('interpretation_log.titles.screenshot')} +
) : tableData.length > 0 ? ( <> @@ -232,25 +210,6 @@ export const InterpretationLog: React.FC = ({ isOpen, se {t('interpretation_log.messages.additional_rows')} - ) : ( From 827e5ecbafbeff21879286a3459217b7eff536b7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 21 Dec 2024 23:04:44 +0530 Subject: [PATCH 335/500] feat: reset data based on current interpretation log state --- src/components/molecules/InterpretationLog.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 99b9fc8b..227e621c 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -35,7 +35,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se const { width } = useBrowserDimensionsStore(); const { socket } = useSocketStore(); - const { currentWorkflowActionsState, notify } = useGlobalInfoStore(); + const { currentWorkflowActionsState, shouldResetInterpretationLog, notify } = useGlobalInfoStore(); const toggleDrawer = (newOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { if ( @@ -94,12 +94,13 @@ export const InterpretationLog: React.FC = ({ isOpen, se setCustomValue(event.target.value); }; - const handleReset = () => { - setLog(''); - setTableData([]); - setBinaryData(null); - notify("success", t('interpretation_log.notifications.reset_success')); - }; + useEffect(() => { + if (shouldResetInterpretationLog) { + setLog(''); + setTableData([]); + setBinaryData(null); + } + }, [shouldResetInterpretationLog]); useEffect(() => { socket?.on('log', handleLog); From fee13e7fefdc91e23a72f7d78cd61305fd722f9f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 22 Dec 2024 22:05:45 +0530 Subject: [PATCH 336/500] feat: gracefully proceed if !adblocker --- .../browser-management/classes/RemoteBrowser.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 31aceada..2c45d146 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -245,10 +245,17 @@ export class RemoteBrowser { await this.setupPageEventListeners(this.currentPage); - const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']); - await blocker.enableBlockingInPage(this.currentPage); - this.client = await this.currentPage.context().newCDPSession(this.currentPage); - await blocker.disableBlockingInPage(this.currentPage); + try { + const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']); + await blocker.enableBlockingInPage(this.currentPage); + this.client = await this.currentPage.context().newCDPSession(this.currentPage); + await blocker.disableBlockingInPage(this.currentPage); + console.log('Adblocker initialized'); + } 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); + } }; /** From 584433e3f775f44c705c6e2a9152d77d006635fb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sun, 22 Dec 2024 22:08:56 +0530 Subject: [PATCH 337/500] feat: try-catch handling for adblocker in core --- maxun-core/src/interpret.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 14d8f46e..bc99c92f 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -111,13 +111,21 @@ export default class Interpreter extends EventEmitter { private async applyAdBlocker(page: Page): Promise { if (this.blocker) { - await this.blocker.enableBlockingInPage(page); + try { + await this.blocker.enableBlockingInPage(page); + } catch (err) { + this.log(`Ad-blocker operation failed:`, Level.ERROR); + } } } private async disableAdBlocker(page: Page): Promise { if (this.blocker) { - await this.blocker.disableBlockingInPage(page); + try { + await this.blocker.disableBlockingInPage(page); + } catch (err) { + this.log(`Ad-blocker operation failed:`, Level.ERROR); + } } } From 58f92a35dc6eace6b58f095678a684c886da91b3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:03:20 +0530 Subject: [PATCH 338/500] feat: try-catch handling for adblocker in core --- maxun-core/src/interpret.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index bc99c92f..c581954d 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -670,7 +670,11 @@ export default class Interpreter extends EventEmitter { const workflowCopy: Workflow = JSON.parse(JSON.stringify(workflow)); // apply ad-blocker to the current page - await this.applyAdBlocker(p); + try { + await this.applyAdBlocker(p); + } catch (error) { + this.log(`Failed to apply ad-blocker: ${error.message}`, Level.ERROR); + } const usedActions: string[] = []; let selectors: string[] = []; let lastAction = null; From d016900237505c0d0180111c9bcd7e671ef3237c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:18:33 +0530 Subject: [PATCH 339/500] chore: 0.0.7 maxun-core --- 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 36d06aa9..7c92d08e 100644 --- a/maxun-core/package.json +++ b/maxun-core/package.json @@ -1,6 +1,6 @@ { "name": "maxun-core", - "version": "0.0.6", + "version": "0.0.7", "description": "Core package for Maxun, responsible for data extraction", "main": "build/index.js", "typings": "build/index.d.ts", From 470eaa80f10d8413db9a2c577d9378ea0ca6733f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:21:48 +0530 Subject: [PATCH 340/500] chore: maxun v0.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 866c3dd2..c0900208 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxun", - "version": "0.0.4", + "version": "0.0.5", "author": "Maxun", "license": "AGPL-3.0-or-later", "dependencies": { From 58ab253382debd8f31585123a228f639bbb77456 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:22:13 +0530 Subject: [PATCH 341/500] chore: lint --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index c0900208..21ea3095 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,10 @@ "fortawesome": "^0.0.1-security", "google-auth-library": "^9.14.1", "googleapis": "^144.0.0", - "i18next": "^24.0.2", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^3.0.1", - "idcac-playwright": "^0.1.3", - "ioredis": "^5.4.1", "joi": "^17.6.0", "jsonwebtoken": "^9.0.2", @@ -118,4 +115,4 @@ "ts-node": "^10.4.0", "vite": "^5.4.10" } -} +} \ No newline at end of file From 13b15db8c29f747b59fae1bf5e4a5da6a9432c96 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:24:12 +0530 Subject: [PATCH 342/500] chore: 0.0.7 maxun-core --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 21ea3095..e89f13de 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "jwt-decode": "^4.0.0", "loglevel": "^1.8.0", "loglevel-plugin-remote": "^0.6.8", - "maxun-core": "^0.0.6", + "maxun-core": "^0.0.7", "minio": "^8.0.1", "moment-timezone": "^0.5.45", "node-cron": "^3.0.3", @@ -115,4 +115,4 @@ "ts-node": "^10.4.0", "vite": "^5.4.10" } -} \ No newline at end of file +} From e6da1350ee395ee3fa031ff6ab62a6d5adee55a0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:25:26 +0530 Subject: [PATCH 343/500] temp: pass --legacy-peer-deps for i18n --- Dockerfile | 2 +- server/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 032dc982..352d4e40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY package*.json ./ COPY maxun-core ./maxun-core # Install dependencies -RUN npm install +RUN npm install --legacy-peer-deps # Copy frontend source code and config COPY src ./src diff --git a/server/Dockerfile b/server/Dockerfile index e738f252..a764058a 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,7 +13,7 @@ COPY server/tsconfig.json ./server/ # COPY server/start.sh ./ # Install dependencies -RUN npm install +RUN npm install --legacy-peer-deps # Install Playwright browsers and dependencies RUN npx playwright install --with-deps chromium From 72e219015ad6bdf6d1e004722a3bec5e039f26a9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 00:26:42 +0530 Subject: [PATCH 344/500] chore: frontend 0.0.4, backend 0.0.8 --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3c6e3a0f..5a5b8eae 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.7 + image: getmaxun/maxun-backend:v0.0.8 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.3 + image: getmaxun/maxun-frontend:v0.0.4 ports: - "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}" env_file: .env From 73006804ba43b2314d6e16559226cfe057a7ea20 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 01:15:00 +0530 Subject: [PATCH 345/500] chore: copy public folder --- server/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/server/Dockerfile b/server/Dockerfile index a764058a..7e197fd3 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -7,6 +7,7 @@ WORKDIR /app COPY package*.json ./ COPY maxun-core ./maxun-core COPY src ./src +COPY public ./public COPY server ./server COPY tsconfig.json ./ COPY server/tsconfig.json ./server/ From 26ca043c23cef908ad8f1c820c9d25076b1f3038 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 01:15:11 +0530 Subject: [PATCH 346/500] chore: copy public folder --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 352d4e40..33df05e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN npm install --legacy-peer-deps # Copy frontend source code and config COPY src ./src +COPY public ./public COPY index.html ./ COPY vite.config.js ./ COPY tsconfig.json ./ From 4ac55f076857efed0807d2079efa0213e09fb04b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 01:15:45 +0530 Subject: [PATCH 347/500] feat: backend 0.0.9, frontend 0.0.5 --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5a5b8eae..874e48d6 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.8 + image: getmaxun/maxun-backend:v0.0.9 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.4 + image: getmaxun/maxun-frontend:v0.0.5 ports: - "${FRONTEND_PORT:-5173}:${FRONTEND_PORT:-5173}" env_file: .env From 422b774ba2e93089c94dc9a884836039abc2c9f5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 23 Dec 2024 13:34:24 +0530 Subject: [PATCH 348/500] fix: browser window english language --- public/locales/en.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/public/locales/en.json b/public/locales/en.json index 7752ad78..66212a87 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -400,24 +400,24 @@ }, "browser_window": { "attribute_modal": { - "title": "属性を選択", - "notifications": { - "list_select_success": "リストが正常に選択されました。抽出するテキストデータを選択してください。", - "pagination_select_success": "ページネーション要素が正常に選択されました。" - } + "title": "Select Attribute", + "notifications": { + "list_select_success": "List has been successfully selected. Please select the text data to extract.", + "pagination_select_success": "Pagination element has been successfully selected." + } }, "attribute_options": { - "anchor": { - "text": "テキスト: {{text}}", - "url": "URL: {{url}}" - }, - "image": { - "alt_text": "代替テキスト: {{altText}}", - "image_url": "画像URL: {{imageUrl}}" - }, - "default": { - "text": "テキスト: {{text}}" - } + "anchor": { + "text": "Text: {{text}}", + "url": "URL: {{url}}" + }, + "image": { + "alt_text": "Alt Text: {{altText}}", + "image_url": "Image URL: {{imageUrl}}" + }, + "default": { + "text": "Text: {{text}}" + } } }, "runs_table": { From e017148487c41f9c3af15d3ea7b025c3615dea33 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 23 Dec 2024 14:46:41 +0530 Subject: [PATCH 349/500] fix: preserve previous labels steps before adding list step --- src/context/browserSteps.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/context/browserSteps.tsx b/src/context/browserSteps.tsx index 7630f559..dd211199 100644 --- a/src/context/browserSteps.tsx +++ b/src/context/browserSteps.tsx @@ -62,26 +62,35 @@ export const BrowserStepsProvider: React.FC<{ children: React.ReactNode }> = ({ const addListStep = (listSelector: string, newFields: { [key: string]: TextStep }, listId: number, pagination?: { type: string; selector: string }, limit?: number) => { setBrowserSteps(prevSteps => { const existingListStepIndex = prevSteps.findIndex(step => step.type === 'list' && step.id === listId); + if (existingListStepIndex !== -1) { const updatedSteps = [...prevSteps]; const existingListStep = updatedSteps[existingListStepIndex] as ListStep; - - const filteredNewFields = Object.entries(newFields).reduce((acc, [key, value]) => { + + // Preserve existing labels for fields + const mergedFields = Object.entries(newFields).reduce((acc, [key, field]) => { if (!discardedFields.has(`${listId}-${key}`)) { - acc[key] = value; + // If field exists, preserve its label + if (existingListStep.fields[key]) { + acc[key] = { + ...field, + label: existingListStep.fields[key].label + }; + } else { + acc[key] = field; + } } return acc; }, {} as { [key: string]: TextStep }); - + updatedSteps[existingListStepIndex] = { ...existingListStep, - fields: { ...existingListStep.fields, ...filteredNewFields }, - pagination: pagination, - limit: limit, + fields: mergedFields, + pagination: pagination || existingListStep.pagination, + limit: limit }; return updatedSteps; } else { - // Create a new ListStep return [ ...prevSteps, { id: listId, type: 'list', listSelector, fields: newFields, pagination, limit } From e2bc45b16328e56de2ddf426427b95342e311c7d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:14:36 +0530 Subject: [PATCH 350/500] chore: -rm unique mode 1 log --- server/src/workflow-management/selector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 240f8921..7c3ead34 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -864,7 +864,6 @@ interface SelectorResult { export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates, listSelector: string): Promise => { try { if (!listSelector) { - console.log(`NON UNIQUE: MODE 1`) const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); From bc88aa82802ec4741a0672401f361807dbe373bb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:15:05 +0530 Subject: [PATCH 351/500] chore: -rm non unique mode 2 log --- server/src/workflow-management/selector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 7c3ead34..c0fa21f1 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -931,7 +931,6 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates }, coordinates); return selectors || { generalSelector: '' }; } else { - console.log(`NON UNIQUE: MODE 2`) const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); From 489b8783a47f8a08723866f78f22d4b849411bd3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:15:53 +0530 Subject: [PATCH 352/500] chore: -rm user.emal --- server/src/routes/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index cc3d879b..084edb4b 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -52,7 +52,7 @@ router.post("/register", async (req, res) => { userId: user.id, registeredAt: new Date().toISOString(), }); - console.log(`User registered - ${user.email}`); + console.log(`User registered`); res.json(user); } catch (error: any) { console.log(`Could not register user - ${error}`); From 2c6f5b7156cf545ffdde70eb6ea1485fbe0204bd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:16:54 +0530 Subject: [PATCH 353/500] feat: better error message --- server/src/workflow-management/scheduler/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/workflow-management/scheduler/index.ts b/server/src/workflow-management/scheduler/index.ts index 169b0061..ade7d969 100644 --- a/server/src/workflow-management/scheduler/index.ts +++ b/server/src/workflow-management/scheduler/index.ts @@ -73,7 +73,7 @@ async function createWorkflowAndStoreMetadata(id: string, userId: string) { } catch (e) { const { message } = e as Error; logger.log('info', `Error while scheduling a run with id: ${id}`); - console.log(message); + console.log(`Error while scheduling a run with id: ${id}:`, message); return { success: false, error: message, From 3776726a0b0e412f0451c8e195b321d637c07810 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:17:33 +0530 Subject: [PATCH 354/500] chore: -rm save log --- src/components/molecules/RobotDuplicate.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 530aedc6..b832ff0b 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -93,8 +93,6 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia return; } - console.log("handle save"); - try { const success = await duplicateRecording(robot.recording_meta.id, targetUrl); From 09aab83851d33ff07bb7fe47c592c8f7ac841435 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:17:54 +0530 Subject: [PATCH 355/500] chore: -rm settings log --- src/components/organisms/RightSidePanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index ba178b7b..12f75028 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -220,7 +220,6 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } stopGetText(); const settings = getTextSettingsObject(); - console.log("SETTINGS", settings); const hasTextSteps = browserSteps.some(step => step.type === 'text'); if (hasTextSteps) { socket?.emit('action', { action: 'scrapeSchema', settings }); From 970a84692083fbd36e3c63c53519e2a0ed7ef752 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:18:20 +0530 Subject: [PATCH 356/500] chore: -rm i18n logs --- src/pages/Login.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 3c8e08c4..86dfa159 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -10,8 +10,6 @@ import i18n from '../i18n'; const Login = () => { const { t } = useTranslation(); - console.log(i18n) - console.log(t) const [form, setForm] = useState({ email: "", password: "", From ecfd1e1e4223ba5958f3e4ff81804fff52d79a79 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:25:37 +0530 Subject: [PATCH 357/500] fix: bring back i18n logs --- src/pages/Login.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 86dfa159..3c8e08c4 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -10,6 +10,8 @@ import i18n from '../i18n'; const Login = () => { const { t } = useTranslation(); + console.log(i18n) + console.log(t) const [form, setForm] = useState({ email: "", password: "", From ad8a48c0c6d9c3eb1c334851e8eb25f1489a0d5a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 23 Dec 2024 23:26:23 +0530 Subject: [PATCH 358/500] chore: add log warning --- src/pages/Login.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 3c8e08c4..b8228799 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -10,6 +10,7 @@ import i18n from '../i18n'; const Login = () => { const { t } = useTranslation(); + // just don't remove these logs - god knows why it's not working without them console.log(i18n) console.log(t) const [form, setForm] = useState({ From feb30b9f9e1ffd047e3054f7c10d90e07ec134d5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 24 Dec 2024 02:30:36 +0530 Subject: [PATCH 359/500] feat: add nth-child selectors for td tag --- server/src/workflow-management/selector.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 240f8921..af9de4af 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -869,6 +869,13 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); + if (selector === 'td' && element.parentElement) { + // Find position among td siblings + const siblings = Array.from(element.parentElement.children); + const position = siblings.indexOf(element) + 1; + return `${selector}:nth-child(${position})`; + } + if (element.className) { const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); if (classes.length > 0) { @@ -937,6 +944,12 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); + if (selector === 'td' && element.parentElement) { + const siblings = Array.from(element.parentElement.children); + const position = siblings.indexOf(element) + 1; + return `${selector}:nth-child(${position})`; + } + if (element.className) { const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); if (classes.length > 0) { @@ -991,6 +1004,12 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro function getNonUniqueSelector(element: HTMLElement): string { let selector = element.tagName.toLowerCase(); + if (selector === 'td' && element.parentElement) { + const siblings = Array.from(element.parentElement.children); + const position = siblings.indexOf(element) + 1; + return `${selector}:nth-child(${position})`; + } + const className = typeof element.className === 'string' ? element.className : ''; if (className) { const classes = className.split(/\s+/).filter((cls: string) => Boolean(cls)); From 5ac88c6eda67f67229ca71e511cb7de25bfefd06 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 24 Dec 2024 02:31:32 +0530 Subject: [PATCH 360/500] feat: add scraping logic for tabular data in scrapeList --- maxun-core/src/browserSide/scraper.js | 116 ++++++++++++++------------ 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index a2009d78..82341fbd 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -262,73 +262,83 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, * @returns {Array.>} Array of arrays of scraped items, one sub-array per list */ window.scrapeList = async function ({ listSelector, fields, limit = 10 }) { + // Separate fields into table and non-table categories + const tableFields = {}; + const nonTableFields = {}; + + for (const [label, field] of Object.entries(fields)) { + if (['TD', 'TH', 'TR'].includes(field.tag)) { + tableFields[label] = field; + } else { + nonTableFields[label] = field; + } + } + + const parentElements = Array.from(document.querySelectorAll(listSelector)); const scrapedData = []; - while (scrapedData.length < limit) { - let parentElements = Array.from(document.querySelectorAll(listSelector)); - - // If we only got one element or none, try a more generic approach - if (limit > 1 && parentElements.length <= 1) { - const [containerSelector, _] = listSelector.split('>').map(s => s.trim()); - const container = document.querySelector(containerSelector); - - if (container) { - const allChildren = Array.from(container.children); - - const firstMatch = document.querySelector(listSelector); - if (firstMatch) { - // Get classes from the first matching element - const firstMatchClasses = Array.from(firstMatch.classList); - - // Find similar elements by matching most of their classes - parentElements = allChildren.filter(element => { - const elementClasses = Array.from(element.classList); + for (const parent of parentElements) { + // First, get the number of rows we'll need by checking the first table field + const firstTableField = Object.values(tableFields)[0]; + const tableRows = firstTableField + ? Array.from(parent.querySelectorAll(firstTableField.selector)).slice(0, limit) + : [null]; - // Element should share at least 70% of classes with the first match - const commonClasses = firstMatchClasses.filter(cls => - elementClasses.includes(cls)); - return commonClasses.length >= Math.floor(firstMatchClasses.length * 0.7); - }); - } - } - } - - // Iterate through each parent element - for (const parent of parentElements) { - if (scrapedData.length >= limit) break; + tableRows.forEach((_, rowIndex) => { const record = {}; - - // For each field, select the corresponding element within the parent - for (const [label, { selector, attribute }] of Object.entries(fields)) { - const fieldElement = parent.querySelector(selector); - - if (fieldElement) { + + // Table fields + for (const [label, { selector, attribute }] of Object.entries(tableFields)) { + const elements = Array.from(parent.querySelectorAll(selector)); + const element = elements[rowIndex]; + + if (element) { + let value; if (attribute === 'innerText') { - record[label] = fieldElement.innerText.trim(); + value = element.innerText.trim(); } else if (attribute === 'innerHTML') { - record[label] = fieldElement.innerHTML.trim(); - } else if (attribute === 'src') { - // Handle relative 'src' URLs - const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, window.location.origin).href : null; - } else if (attribute === 'href') { - // Handle relative 'href' URLs - const href = fieldElement.getAttribute('href'); - record[label] = href ? new URL(href, window.location.origin).href : null; + value = element.innerHTML.trim(); + } else if (attribute === 'src' || attribute === 'href') { + const attrValue = element.getAttribute(attribute); + value = attrValue ? new URL(attrValue, window.location.origin).href : null; } else { - record[label] = fieldElement.getAttribute(attribute); + value = element.getAttribute(attribute); } + record[label] = value; } } - scrapedData.push(record); - } + + // Non table fields + for (const [label, { selector, attribute }] of Object.entries(nonTableFields)) { + const element = parent.querySelector(selector); + + if (element) { + let value; + if (attribute === 'innerText') { + value = element.innerText.trim(); + } else if (attribute === 'innerHTML') { + value = element.innerHTML.trim(); + } else if (attribute === 'src' || attribute === 'href') { + const attrValue = element.getAttribute(attribute); + value = attrValue ? new URL(attrValue, window.location.origin).href : null; + } else { + value = element.getAttribute(attribute); + } + record[label] = value; + } + } + + if (Object.keys(record).length > 0) { + scrapedData.push(record); + } + }); - // If we've processed all available elements and still haven't reached the limit, - // break to avoid infinite loop - if (parentElements.length === 0 || scrapedData.length >= parentElements.length) { + if (scrapedData.length >= limit) { + scrapedData.length = limit; break; } } + return scrapedData; }; From 99ce03f2f1e4caf333df6812fa812d2f37d8df36 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 02:49:17 +0530 Subject: [PATCH 361/500] feat: robot name --- src/components/molecules/RobotEdit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 73397da4..37e4de65 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -148,7 +148,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin <> handleRobotNameChange(e.target.value)} From 155a4c711b9602f7e829e4f214c2ac83c53b1dbb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 02:50:02 +0530 Subject: [PATCH 362/500] feat: robot name --- public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 66212a87..9dcad514 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -345,7 +345,7 @@ }, "robot_edit": { "title": "Edit Robot", - "change_name": "Change Robot Name", + "change_name": "Robot Name", "robot_limit": "Robot Limit", "save": "Save Changes", "cancel": "Cancel", From fed53fd0c609b9460fb5eee8f101b98928f1f9b6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 03:06:50 +0530 Subject: [PATCH 363/500] chore: -rm translated notification --- src/pages/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index b8228799..161cfacd 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -46,7 +46,7 @@ const Login = () => { password, }); dispatch({ type: "LOGIN", payload: data }); - notify("success", t('login.welcome_notification')); // Translated notification + notify("success", t('login.welcome_notification')); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); } catch (err) { From 85381a4b609b175bda07f09cc2097cfe3b34d688 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 03:07:01 +0530 Subject: [PATCH 364/500] chore: -rm translated error --- src/pages/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 161cfacd..85fbb20b 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -50,7 +50,7 @@ const Login = () => { window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); } catch (err) { - notify("error", t('login.error_notification')); // Translated error + notify("error", t('login.error_notification')); setLoading(false); } }; From 7650a21c03bb72a994648dc08d1fe22df9da08ca Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 03:07:17 +0530 Subject: [PATCH 365/500] chore: -rm lang switcher --- src/pages/Login.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 85fbb20b..04fead67 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -55,7 +55,6 @@ const Login = () => { } }; - // Language switcher function return ( From 06d6891f54cd56edebeb34b5bcbe829c28153836 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 03:07:28 +0530 Subject: [PATCH 366/500] chore: -rm lang switcher buttons --- src/pages/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 04fead67..8443a2be 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -68,7 +68,7 @@ const Login = () => { padding: 4, }} > - {/* Language Switcher Buttons */} + Date: Tue, 24 Dec 2024 03:07:42 +0530 Subject: [PATCH 367/500] chore: whitespace cleanup --- src/pages/Login.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 8443a2be..c62eb12d 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -68,8 +68,6 @@ const Login = () => { padding: 4, }} > - - Date: Tue, 24 Dec 2024 03:07:51 +0530 Subject: [PATCH 368/500] chore: whitespace cleanup --- src/pages/Login.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index c62eb12d..d88e1a49 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -54,9 +54,7 @@ const Login = () => { setLoading(false); } }; - - return ( Date: Tue, 24 Dec 2024 03:08:08 +0530 Subject: [PATCH 369/500] fix: format --- src/pages/Login.tsx | 252 ++++++++++++++++++++++---------------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index d88e1a49..ba0f377e 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,138 +1,138 @@ -import axios from "axios"; -import { useState, useContext, useEffect, FormEvent } 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 { useGlobalInfoStore } from "../context/globalInfo"; +import axios from "axios"; +import { useState, useContext, useEffect, FormEvent } 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 { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; import { useTranslation } from 'react-i18next'; -import i18n from '../i18n'; +import i18n from '../i18n'; const Login = () => { - const { t } = useTranslation(); - // just don't remove these logs - god knows why it's not working without them - console.log(i18n) - console.log(t) - const [form, setForm] = useState({ - email: "", - password: "", - }); - const [loading, setLoading] = useState(false); - const { notify } = useGlobalInfoStore(); - const { email, password } = form; + const { t } = useTranslation(); + // just don't remove these logs - god knows why it's not working without them + console.log(i18n) + console.log(t) + 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 { state, dispatch } = useContext(AuthContext); + const { user } = state; - const navigate = useNavigate(); + const navigate = useNavigate(); - useEffect(() => { - if (user) { - navigate("/"); - } - }, [user, navigate]); + useEffect(() => { + if (user) { + navigate("/"); + } + }, [user, navigate]); - const handleChange = (e: any) => { - const { name, value } = e.target; - setForm({ ...form, [name]: value }); - }; + const handleChange = (e: any) => { + const { name, value } = e.target; + setForm({ ...form, [name]: value }); + }; - const submitForm = async (e: any) => { - e.preventDefault(); - setLoading(true); - try { - const { data } = await axios.post(`${apiUrl}/auth/login`, { - email, - password, - }); - dispatch({ type: "LOGIN", payload: data }); - notify("success", t('login.welcome_notification')); - window.localStorage.setItem("user", JSON.stringify(data)); - navigate("/"); - } catch (err) { - notify("error", t('login.error_notification')); - setLoading(false); - } - }; - - return ( - - - logo - - {t('login.title')} - - - - - - {t('login.register_prompt')}{" "} - - {t('login.register_link')} - - - - - ); + const submitForm = async (e: any) => { + e.preventDefault(); + setLoading(true); + try { + const { data } = await axios.post(`${apiUrl}/auth/login`, { + email, + password, + }); + dispatch({ type: "LOGIN", payload: data }); + notify("success", t('login.welcome_notification')); + window.localStorage.setItem("user", JSON.stringify(data)); + navigate("/"); + } catch (err) { + notify("error", t('login.error_notification')); + setLoading(false); + } + }; + + return ( + + + logo + + {t('login.title')} + + + + + + {t('login.register_prompt')}{" "} + + {t('login.register_link')} + + + + + ); }; export default Login; \ No newline at end of file From 5939471762da3dfd90dea7186d53fbab563edad0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 24 Dec 2024 03:18:14 +0530 Subject: [PATCH 370/500] fix: move handleSave onClick to save button --- src/components/molecules/RobotEdit.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 37e4de65..6547d93b 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -170,8 +170,8 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin /> )} - - + ) : ( + + + )} )} @@ -578,7 +620,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} /> - {!confirmedListTextFields[step.id]?.[key] && ( + {!confirmedListTextFields[step.id]?.[key] ? ( + ) : ( + + + )} ))} From 3cf0b858933f0a9aa0541a7b714677600861939f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 28 Dec 2024 17:39:04 +0530 Subject: [PATCH 395/500] feat: add lang translation for delete button --- public/locales/de.json | 3 ++- public/locales/en.json | 3 ++- public/locales/es.json | 3 ++- public/locales/ja.json | 3 ++- public/locales/zh.json | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index 411d8f22..db0ce562 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -162,7 +162,8 @@ "confirm_limit": "Limit bestätigen", "finish_capture": "Erfassung abschließen", "finish": "Fertig", - "cancel": "Abbrechen" + "cancel": "Abbrechen", + "delete": "Löschen" }, "screenshot": { "capture_fullpage": "Vollständige Seite erfassen", diff --git a/public/locales/en.json b/public/locales/en.json index c5a2ff4c..9b4defbc 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -163,7 +163,8 @@ "confirm_limit": "Confirm Limit", "finish_capture": "Finish Capture", "finish": "Finish", - "cancel": "Cancel" + "cancel": "Cancel", + "delete": "Delete" }, "screenshot": { "capture_fullpage": "Capture Fullpage", diff --git a/public/locales/es.json b/public/locales/es.json index 6e52cc6f..e897914e 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -163,7 +163,8 @@ "confirm_limit": "Confirmar Límite", "finish_capture": "Finalizar Captura", "finish": "Finalizar", - "cancel": "Cancelar" + "cancel": "Cancelar", + "delete": "Eliminar" }, "screenshot": { "capture_fullpage": "Capturar Página Completa", diff --git a/public/locales/ja.json b/public/locales/ja.json index 9d2d9a89..9ae226dc 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -163,7 +163,8 @@ "confirm_limit": "制限を確認", "finish_capture": "取得を完了", "finish": "完了", - "cancel": "キャンセル" + "cancel": "キャンセル", + "delete": "削除" }, "screenshot": { "capture_fullpage": "フルページを取得", diff --git a/public/locales/zh.json b/public/locales/zh.json index 69561d5c..344a58a7 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -163,7 +163,8 @@ "confirm_limit": "确认限制", "finish_capture": "完成捕获", "finish": "完成", - "cancel": "取消" + "cancel": "取消", + "delete": "删除" }, "screenshot": { "capture_fullpage": "捕获整页", From fd7e4ab626fe2b862de7fea14819be18c40012d8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 28 Dec 2024 18:11:24 +0530 Subject: [PATCH 396/500] feat: check confirm capture and render delete button --- src/components/organisms/RightSidePanel.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 403f78f6..c6b3479f 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -56,6 +56,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const [showCaptureText, setShowCaptureText] = useState(true); const [hoverStates, setHoverStates] = useState<{ [id: string]: boolean }>({}); const [browserStepIdList, setBrowserStepIdList] = useState([]); + const [isCaptureTextConfirmed, setIsCaptureTextConfirmed] = useState(false); const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog } = useGlobalInfoStore(); const { getText, startGetText, stopGetText, getScreenshot, startGetScreenshot, stopGetScreenshot, getList, startGetList, stopGetList, startPaginationMode, stopPaginationMode, paginationType, updatePaginationType, limitType, customLimit, updateLimitType, updateCustomLimit, stopLimitMode, startLimitMode, captureStage, setCaptureStage } = useActionContext(); @@ -130,6 +131,11 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const handlePairDelete = () => { } + const handleStartGetText = () => { + setIsCaptureTextConfirmed(false); + startGetText(); + } + const handleTextLabelChange = (id: number, label: string, listId?: number, fieldKey?: string) => { if (listId !== undefined && fieldKey !== undefined) { // Prevent editing if the field is confirmed @@ -256,6 +262,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture if (hasTextSteps) { socket?.emit('action', { action: 'scrapeSchema', settings }); } + setIsCaptureTextConfirmed(true); resetInterpretationLog(); onFinishCapture(); }, [stopGetText, getTextSettingsObject, socket, browserSteps, confirmedTextSteps, resetInterpretationLog]); @@ -502,7 +509,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> @@ -563,7 +570,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture - ) : ( + ) : !isCaptureTextConfirmed && ( - ) : ( + ) : !isCaptureListConfirmed && ( + )} - + )} From dead389e480cae29978242047a554e3a3f780f30 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 29 Dec 2024 17:55:01 +0530 Subject: [PATCH 400/500] feat: add translation for back button of capture list action --- public/locales/de.json | 1 + public/locales/en.json | 1 + public/locales/es.json | 1 + public/locales/ja.json | 1 + public/locales/zh.json | 1 + 5 files changed, 5 insertions(+) diff --git a/public/locales/de.json b/public/locales/de.json index 411d8f22..c43b46a2 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -161,6 +161,7 @@ "confirm_pagination": "Paginierung bestätigen", "confirm_limit": "Limit bestätigen", "finish_capture": "Erfassung abschließen", + "back": "Zurück", "finish": "Fertig", "cancel": "Abbrechen" }, diff --git a/public/locales/en.json b/public/locales/en.json index c5a2ff4c..1a68faed 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -162,6 +162,7 @@ "confirm_pagination": "Confirm Pagination", "confirm_limit": "Confirm Limit", "finish_capture": "Finish Capture", + "back": "Back", "finish": "Finish", "cancel": "Cancel" }, diff --git a/public/locales/es.json b/public/locales/es.json index 6e52cc6f..b2fb55a4 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -162,6 +162,7 @@ "confirm_pagination": "Confirmar Paginación", "confirm_limit": "Confirmar Límite", "finish_capture": "Finalizar Captura", + "back": "Atrás", "finish": "Finalizar", "cancel": "Cancelar" }, diff --git a/public/locales/ja.json b/public/locales/ja.json index 9d2d9a89..e4073814 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -162,6 +162,7 @@ "confirm_pagination": "ページネーションを確認", "confirm_limit": "制限を確認", "finish_capture": "取得を完了", + "back": "戻る", "finish": "完了", "cancel": "キャンセル" }, diff --git a/public/locales/zh.json b/public/locales/zh.json index 69561d5c..d171c2c9 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -162,6 +162,7 @@ "confirm_pagination": "确认分页", "confirm_limit": "确认限制", "finish_capture": "完成捕获", + "back": "返回", "finish": "完成", "cancel": "取消" }, From a09b03e4a75627d2adc8189f8fdd361b36b8a82b Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 29 Dec 2024 23:36:06 +0530 Subject: [PATCH 401/500] feat: get deepest shadowDOM element selector --- server/src/workflow-management/selector.ts | 243 +++++++++++++++------ 1 file changed, 179 insertions(+), 64 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 5a7273df..9b3af66e 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -23,31 +23,41 @@ export const getElementInformation = async ( if (!getList || listSelector !== '') { const elementInfo = await page.evaluate( async ({ x, y }) => { - // Helper function to get element from point including shadow DOM + // Enhanced helper function to get element from point including shadow DOM const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { let element = document.elementFromPoint(x, y) as HTMLElement; if (!element) return null; // Traverse through shadow roots let current = element; - while (current) { - // Check if element has shadow root - const shadowRoot = current.shadowRoot; - if (!shadowRoot) break; - - // Try to find deeper element in shadow DOM + let shadowRoot = current.shadowRoot; + + // Keep track of the deepest shadow DOM element found + let deepestElement = current; + + while (shadowRoot) { + // Try to find element at same point in shadow DOM const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; if (!shadowElement || shadowElement === current) break; - + + // Update our tracking of the deepest element + deepestElement = shadowElement; current = shadowElement; + shadowRoot = current.shadowRoot; } - return current; + + return deepestElement; }; const el = getDeepestElementFromPoint(x, y); if (el) { const { parentElement } = el; const element = parentElement?.tagName === 'A' ? parentElement : el; + + // Get the containing shadow root if any + const containingShadowRoot = element.getRootNode() as ShadowRoot; + const isShadowRoot = containingShadowRoot instanceof ShadowRoot; + let info: { tagName: string; hasOnlyText?: boolean; @@ -58,11 +68,20 @@ export const getElementInformation = async ( innerHTML?: string; outerHTML?: string; isShadowRoot?: boolean; + shadowRootMode?: string; + shadowRootContent?: string; } = { tagName: element?.tagName ?? '', - isShadowRoot: !!element?.shadowRoot + isShadowRoot: isShadowRoot }; + + if (isShadowRoot) { + // Include shadow root specific information + info.shadowRootMode = containingShadowRoot.mode; + info.shadowRootContent = containingShadowRoot.innerHTML; + } + // Get attributes including those from shadow DOM context if (element) { info.attributes = Array.from(element.attributes).reduce( (acc, attr) => { @@ -71,84 +90,82 @@ export const getElementInformation = async ( }, {} as Record ); + + // Get text content considering shadow DOM context + info.innerText = element.textContent ?? ''; + info.innerHTML = element.innerHTML; + info.outerHTML = element.outerHTML; + info.hasOnlyText = element.children.length === 0 && + (element.textContent !== null && + element.textContent.trim().length > 0); } - // Gather specific information based on the tag - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else if (element?.tagName === 'SELECT') { - const selectElement = element as HTMLSelectElement; - info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; - info.attributes = { - ...info.attributes, - selectedValue: selectElement.value, - }; - } else if (element?.tagName === 'INPUT' && ((element as HTMLInputElement).type === 'time' || (element as HTMLInputElement).type === 'date')) { - info.innerText = (element as HTMLInputElement).value; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; return info; } return null; }, - { x: coordinates.x, y: coordinates.y }, + { x: coordinates.x, y: coordinates.y } ); return elementInfo; } else { const elementInfo = await page.evaluate( async ({ x, y }) => { - // Helper function to get element from point including shadow DOM + // Enhanced helper function to get element from point including shadow DOM const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { let element = document.elementFromPoint(x, y) as HTMLElement; if (!element) return null; - + // Traverse through shadow roots let current = element; - while (current) { - const shadowRoot = current.shadowRoot; - if (!shadowRoot) break; - + let shadowRoot = current.shadowRoot; + + // Keep track of the deepest shadow DOM element found + let deepestElement = current; + + while (shadowRoot) { + // Try to find element at same point in shadow DOM const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; if (!shadowElement || shadowElement === current) break; - + + // Update our tracking of the deepest element + deepestElement = shadowElement; current = shadowElement; + shadowRoot = current.shadowRoot; } - return current; + + return deepestElement; }; - + const originalEl = getDeepestElementFromPoint(x, y); if (originalEl) { let element = originalEl; - + + // Handle element hierarchy traversal for list items while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); - + const fullyContained = parentRect.left <= childRect.left && parentRect.right >= childRect.right && parentRect.top <= childRect.top && parentRect.bottom >= childRect.bottom; - + const significantOverlap = (childRect.width * childRect.height) / (parentRect.width * parentRect.height) > 0.5; - + if (fullyContained && significantOverlap) { element = element.parentElement; } else { break; } } - + + // Get the containing shadow root if any + const containingShadowRoot = element.getRootNode() as ShadowRoot; + const isShadowRoot = containingShadowRoot instanceof ShadowRoot; + let info: { tagName: string; hasOnlyText?: boolean; @@ -159,12 +176,21 @@ export const getElementInformation = async ( innerHTML?: string; outerHTML?: string; isShadowRoot?: boolean; + shadowRootMode?: string; + shadowRootContent?: string; } = { tagName: element?.tagName ?? '', - isShadowRoot: !!element?.shadowRoot + isShadowRoot: isShadowRoot }; - + + if (isShadowRoot) { + // Include shadow root specific information + info.shadowRootMode = containingShadowRoot.mode; + info.shadowRootContent = containingShadowRoot.innerHTML; + } + if (element) { + // Get attributes including those from shadow DOM context info.attributes = Array.from(element.attributes).reduce( (acc, attr) => { acc[attr.name] = attr.value; @@ -172,21 +198,25 @@ export const getElementInformation = async ( }, {} as Record ); + + // Handle specific element types + if (element.tagName === 'A') { + info.url = (element as HTMLAnchorElement).href; + info.innerText = element.textContent ?? ''; + } else if (element.tagName === 'IMG') { + info.imageUrl = (element as HTMLImageElement).src; + } else { + // Handle text content with proper null checking + info.hasOnlyText = element.children.length === 0 && + (element.textContent !== null && + element.textContent.trim().length > 0); + info.innerText = element.textContent ?? ''; + } + + info.innerHTML = element.innerHTML; + info.outerHTML = element.outerHTML; } - - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; + return info; } return null; @@ -793,6 +823,76 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { return output; } + const MAX_DEPTH = 10; + + const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { + let element = document.elementFromPoint(x, y) as HTMLElement; + if (!element) return null; + + let current = element; + let deepestElement = current; + let depth = 0; + + while (current && depth < MAX_DEPTH) { + const shadowRoot = current.shadowRoot; + if (shadowRoot) { + const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; + if (!shadowElement) break; + + deepestElement = shadowElement; + current = shadowElement; + } else { + break; + } + depth++; + } + + return deepestElement; + }; + + const genSelectorForShadowDOM = (element: HTMLElement) => { + const findShadowContext = (element: HTMLElement): { host: HTMLElement, root: ShadowRoot } | null => { + let current: HTMLElement | null = element; + let depth = 0; + + while (current && depth < MAX_DEPTH) { + // Check if element is inside a shadow root + if (current.parentNode instanceof ShadowRoot) { + return { + host: (current.parentNode as ShadowRoot).host as HTMLElement, + root: current.parentNode as ShadowRoot + }; + } + current = current.parentElement; + depth++; + } + return null; + }; + + const shadowContext = findShadowContext(element); + if (!shadowContext) return null; + + try { + // Generate selector for the shadow host + const hostSelector = finder(shadowContext.host); + + // Generate selector for the element within the shadow DOM + const shadowElementSelector = finder(element, { + root: shadowContext.root as unknown as Element + }); + + return { + fullSelector: `${hostSelector} >>> ${shadowElementSelector}`, + hostSelector, + shadowElementSelector, + mode: shadowContext.root.mode + }; + } catch (e) { + console.warn('Error generating shadow DOM selector:', e); + return null; + } + }; + const genSelectors = (element: HTMLElement | null) => { if (element == null) { return null; @@ -812,6 +912,9 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { } catch (e) { } + // Generate shadow DOM specific selector + const shadowSelector = genSelectorForShadowDOM(element); + const hrefSelector = genSelectorForAttributes(element, ['href']); const formSelector = genSelectorForAttributes(element, [ 'name', @@ -858,9 +961,21 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { hrefSelector, accessibilitySelector, formSelector, + // Shadow DOM selector + shadowSelector: shadowSelector ? { + // Full selector that can traverse shadow DOM + full: shadowSelector.fullSelector, + // Individual parts for more flexible usage + host: shadowSelector.hostSelector, + element: shadowSelector.shadowElementSelector, + // Shadow root mode (open/closed) + mode: shadowSelector.mode + } : null }; } + + function genAttributeSet(element: HTMLElement, attributes: string[]) { return new Set( attributes.filter((attr) => { @@ -900,7 +1015,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { return char.length === 1 && char.match(/[0-9]/); } - const hoveredElement = document.elementFromPoint(x, y) as HTMLElement; + const hoveredElement = getDeepestElementFromPoint(x, y); if ( hoveredElement != null && !hoveredElement.closest('#overlay-controls') != null From 542f4d31fa43359928d052b9edb3caa5f446c1c3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 29 Dec 2024 23:41:19 +0530 Subject: [PATCH 402/500] feat: change shadowDOM full selector path --- server/src/workflow-management/selector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 9b3af66e..690fb0b1 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -882,7 +882,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { }); return { - fullSelector: `${hostSelector} >>> ${shadowElementSelector}`, + fullSelector: `${hostSelector} > ${shadowElementSelector}`, hostSelector, shadowElementSelector, mode: shadowContext.root.mode From b60f4b73b8424151f17bfd3389f5698f72c429df Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 01:24:32 +0530 Subject: [PATCH 403/500] feat: add functionality to scrape shadowDOM elements --- maxun-core/src/browserSide/scraper.js | 126 ++++++++++++++++---------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index a2009d78..ef979828 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -189,68 +189,102 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, * @returns {Array.>} */ window.scrapeSchema = function (lists) { + // These utility functions remain unchanged as they work perfectly function omap(object, f, kf = (x) => x) { - return Object.fromEntries( - Object.entries(object) - .map(([k, v]) => [kf(k), f(v)]), - ); + return Object.fromEntries( + Object.entries(object) + .map(([k, v]) => [kf(k), f(v)]), + ); } function ofilter(object, f) { - return Object.fromEntries( - Object.entries(object) - .filter(([k, v]) => f(k, v)), - ); + return Object.fromEntries( + Object.entries(object) + .filter(([k, v]) => f(k, v)), + ); } - function getSeedKey(listObj) { - const maxLength = Math.max(...Object.values(omap(listObj, (x) => document.querySelectorAll(x.selector).length))); - return Object.keys(ofilter(listObj, (_, v) => document.querySelectorAll(v.selector).length === maxLength))[0]; - } - - function getMBEs(elements) { - return elements.map((element) => { - let candidate = element; - const isUniqueChild = (e) => elements - .filter((elem) => e.parentNode?.contains(elem)) - .length === 1; - - while (candidate && isUniqueChild(candidate)) { - candidate = candidate.parentNode; + function findElement(config) { + // If this is a shadow DOM query + if (config.shadow && config.selector.includes('>>')) { + const [hostSelector, shadowSelector] = config.selector.split('>>').map(s => s.trim()); + const host = document.querySelector(hostSelector); + return host?.shadowRoot?.querySelector(shadowSelector) || null; } + // Otherwise, use regular querySelector + return document.querySelector(config.selector); + } - return candidate; - }); + function findAllElements(config) { + // If this is a shadow DOM query + if (config.shadow && config.selector.includes('>>')) { + const element = findElement(config); + return element ? [element] : []; + } + // Otherwise, use regular querySelectorAll + return Array.from(document.querySelectorAll(config.selector)); + } + + // Modified to use our new element finding functions + function getSeedKey(listObj) { + const maxLength = Math.max(...Object.values( + omap(listObj, (x) => findAllElements(x).length) + )); + return Object.keys( + ofilter(listObj, (_, v) => findAllElements(v).length === maxLength) + )[0]; + } + + // This function remains unchanged as it works with DOM elements + // regardless of how they were found + function getMBEs(elements) { + return elements.map((element) => { + let candidate = element; + const isUniqueChild = (e) => elements + .filter((elem) => e.parentNode?.contains(elem)) + .length === 1; + + while (candidate && isUniqueChild(candidate)) { + candidate = candidate.parentNode; + } + + return candidate; + }); } const seedName = getSeedKey(lists); - const seedElements = Array.from(document.querySelectorAll(lists[seedName].selector)); + const seedElements = findAllElements(lists[seedName]); const MBEs = getMBEs(seedElements); return MBEs.map((mbe) => omap( - lists, - ({ selector, attribute }, key) => { - const elem = Array.from(document.querySelectorAll(selector)).find((elem) => mbe.contains(elem)); - if (!elem) return undefined; + lists, + (config, key) => { + // Use our new findAllElements function + const elem = findAllElements(config) + .find((elem) => mbe.contains(elem)); - switch (attribute) { - case 'href': - const relativeHref = elem.getAttribute('href'); - return relativeHref ? new URL(relativeHref, window.location.origin).href : null; - case 'src': - const relativeSrc = elem.getAttribute('src'); - return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; - case 'innerText': - return elem.innerText; - case 'textContent': - return elem.textContent; - default: - return elem.innerText; - } - }, - (key) => key // Use the original key in the output + if (!elem) return undefined; + + switch (config.attribute) { + case 'href': { + const relativeHref = elem.getAttribute('href'); + return relativeHref ? new URL(relativeHref, window.location.origin).href : null; + } + case 'src': { + const relativeSrc = elem.getAttribute('src'); + return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; + } + case 'innerText': + return elem.innerText; + case 'textContent': + return elem.textContent; + default: + return elem.getAttribute(config.attribute) || elem.innerText; + } + }, + (key) => key )) || []; - } + }; /** * Scrapes multiple lists of similar items based on a template item. From 9f9dc4e1030ca3819355245765ecadcc1e2c8d6f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 01:25:45 +0530 Subject: [PATCH 404/500] feat: add shadow optional field in SelectorObject --- src/context/browserSteps.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context/browserSteps.tsx b/src/context/browserSteps.tsx index dd211199..fd311a35 100644 --- a/src/context/browserSteps.tsx +++ b/src/context/browserSteps.tsx @@ -32,6 +32,7 @@ export interface SelectorObject { selector: string; tag?: string; attribute?: string; + shadow?: boolean; [key: string]: any; } From b696fa568d65a0948edd3b999eb31c4ccf39dad5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 01:28:22 +0530 Subject: [PATCH 405/500] feat: add shadow param for scrapeSchema config --- maxun-core/src/interpret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index c581954d..495ba2db 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -403,7 +403,7 @@ export default class Interpreter extends EventEmitter { await this.options.serializableCallback(scrapeResults); }, - scrapeSchema: async (schema: Record) => { + scrapeSchema: async (schema: Record) => { await this.ensureScriptsLoaded(page); const scrapeResult = await page.evaluate((schemaObj) => window.scrapeSchema(schemaObj), schema); From 415ce02a3d2eb82f7434230239a5c7659b557016 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 02:39:27 +0530 Subject: [PATCH 406/500] feat: add shadow bool field to text step --- src/components/organisms/BrowserWindow.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index c7e9fc0f..11fe8c55 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next'; interface ElementInfo { tagName: string; hasOnlyText?: boolean; + isShadowRoot?: boolean; innerText?: string; url?: string; imageUrl?: string; @@ -185,6 +186,7 @@ export const BrowserWindow = () => { addTextStep('', data, { selector: highlighterData.selector, tag: highlighterData.elementInfo?.tagName, + shadow: highlighterData.elementInfo?.isShadowRoot, attribute }); } else { @@ -192,7 +194,7 @@ export const BrowserWindow = () => { setAttributeOptions(options); setSelectedElement({ selector: highlighterData.selector, - info: highlighterData.elementInfo + info: highlighterData.elementInfo, }); setShowAttributeModal(true); } @@ -229,6 +231,7 @@ export const BrowserWindow = () => { selectorObj: { selector: highlighterData.selector, tag: highlighterData.elementInfo?.tagName, + shadow: highlighterData.elementInfo?.isShadowRoot, attribute } }; @@ -276,6 +279,7 @@ export const BrowserWindow = () => { addTextStep('', data, { selector: selectedElement.selector, tag: selectedElement.info?.tagName, + shadow: selectedElement.info?.isShadowRoot, attribute: attribute }); } @@ -288,6 +292,7 @@ export const BrowserWindow = () => { selectorObj: { selector: selectedElement.selector, tag: selectedElement.info?.tagName, + shadow: selectedElement.info?.isShadowRoot, attribute: attribute } }; From 1a6a481b578a7212743ceb199b934585583b5a0e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 02:46:24 +0530 Subject: [PATCH 407/500] feat: add shadow selectors field type --- server/src/types/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/types/index.ts b/server/src/types/index.ts index f2e327ef..e882f69d 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -129,6 +129,13 @@ export interface BaseActionInfo { hasOnlyText: boolean; } +interface ShadowSelector { + full: string; + host: string; + element: string; + mode: string; +} + /** * Holds all the possible css selectors that has been found for an element. * @category Types @@ -143,6 +150,7 @@ export interface Selectors { hrefSelector: string|null; accessibilitySelector: string|null; formSelector: string|null; + shadowSelector: ShadowSelector | null; } /** @@ -156,7 +164,7 @@ export interface BaseAction extends BaseActionInfo{ associatedActions: ActionType[]; inputType: string | undefined; value: string | undefined; - selectors: { [key: string]: string | null }; + selectors: Selectors; timestamp: number; isPassword: boolean; /** From c3031811a63d21139c306781a6e64ee09d81b1de Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 02:52:58 +0530 Subject: [PATCH 408/500] feat: prioritize returning shadow selector --- server/src/workflow-management/utils.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/workflow-management/utils.ts b/server/src/workflow-management/utils.ts index b3dadd60..4f747127 100644 --- a/server/src/workflow-management/utils.ts +++ b/server/src/workflow-management/utils.ts @@ -12,6 +12,11 @@ export const getBestSelectorForAction = (action: Action) => { case ActionType.Hover: case ActionType.DragAndDrop: { const selectors = action.selectors; + + if (selectors?.shadowSelector?.full) { + return selectors.shadowSelector.full; + } + // less than 25 characters, and element only has text inside const textSelector = selectors?.text?.length != null && @@ -75,6 +80,11 @@ export const getBestSelectorForAction = (action: Action) => { case ActionType.Input: case ActionType.Keydown: { const selectors = action.selectors; + + if (selectors?.shadowSelector?.full) { + return selectors.shadowSelector.full; + } + return ( selectors.testIdSelector ?? selectors?.id ?? From cec2397a58256736b60467e40f1cc2e255667394 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 02:55:21 +0530 Subject: [PATCH 409/500] feat: change shadowDOM full selector path --- server/src/workflow-management/selector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 690fb0b1..164f5220 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -882,7 +882,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { }); return { - fullSelector: `${hostSelector} > ${shadowElementSelector}`, + fullSelector: `${hostSelector} >> ${shadowElementSelector}`, hostSelector, shadowElementSelector, mode: shadowContext.root.mode From 05c7921c9d574d4074b64f56319d6166e84b1dc3 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 03:05:07 +0530 Subject: [PATCH 410/500] feat: add shadowInfo in highlighter data --- .../workflow-management/classes/Generator.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 609541de..d1bccbe4 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -730,15 +730,26 @@ export class WorkflowGenerator { const displaySelector = await this.generateSelector(page, coordinates, ActionType.Click); const elementInfo = await getElementInformation(page, coordinates, this.listSelector, this.getList); if (rect) { + const highlighterData = { + rect, + selector: displaySelector, + elementInfo, + // Include shadow DOM specific information + shadowInfo: elementInfo?.isShadowRoot ? { + mode: elementInfo.shadowRootMode, + content: elementInfo.shadowRootContent + } : null + }; + if (this.getList === true) { if (this.listSelector !== '') { const childSelectors = await getChildSelectors(page, this.listSelector || ''); - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo, childSelectors }) + this.socket.emit('highlighter', { ...highlighterData, childSelectors }) } else { - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); + this.socket.emit('highlighter', { ...highlighterData }); } } else { - this.socket.emit('highlighter', { rect, selector: displaySelector, elementInfo }); + this.socket.emit('highlighter', { ...highlighterData }); } } } From 4031ded27947f7ac72f40c9203cb1498f0ac4460 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:26:53 +0530 Subject: [PATCH 411/500] feat: confirm instead of confirm pagination --- public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index 70ded861..cb3f2789 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -159,7 +159,7 @@ "confirm": "Confirm", "discard": "Discard", "confirm_capture": "Confirm Capture", - "confirm_pagination": "Confirm Pagination", + "confirm_pagination": "Confirm", "confirm_limit": "Confirm Limit", "finish_capture": "Finish Capture", "back": "Back", From 8baad8d1f90b9b36594206e91dc37304c68d0a9b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:27:23 +0530 Subject: [PATCH 412/500] feat: confirm instead of confirm limit --- public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en.json b/public/locales/en.json index cb3f2789..bd8acce3 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -160,7 +160,7 @@ "discard": "Discard", "confirm_capture": "Confirm Capture", "confirm_pagination": "Confirm", - "confirm_limit": "Confirm Limit", + "confirm_limit": "Confirm", "finish_capture": "Finish Capture", "back": "Back", "finish": "Finish", From 663a4fd69c0f13998a61f13b1564cb8565bf942b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:29:24 +0530 Subject: [PATCH 413/500] feat(spanish): confirm instead of confirm pagination --- public/locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index 5cde0c70..089c10cd 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -159,7 +159,7 @@ "confirm": "Confirmar", "discard": "Descartar", "confirm_capture": "Confirmar Captura", - "confirm_pagination": "Confirmar Paginación", + "confirm_pagination": "Confirmar", "confirm_limit": "Confirmar Límite", "finish_capture": "Finalizar Captura", "back": "Atrás", From aded4dfebb42e765f8b22d1b01111c2f82fc73f3 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:29:41 +0530 Subject: [PATCH 414/500] feat(spanish): confirm instead of confirm limit --- public/locales/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/es.json b/public/locales/es.json index 089c10cd..94210880 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -160,7 +160,7 @@ "discard": "Descartar", "confirm_capture": "Confirmar Captura", "confirm_pagination": "Confirmar", - "confirm_limit": "Confirmar Límite", + "confirm_limit": "Confirmar", "finish_capture": "Finalizar Captura", "back": "Atrás", "finish": "Finalizar", From 09b974ca782e574240beab052c1a1e78e0316eac Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:30:38 +0530 Subject: [PATCH 415/500] feat(japanese): confirm instead of confirm pagination --- public/locales/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index a0d18c67..e2204e14 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -159,7 +159,7 @@ "confirm": "確認", "discard": "破棄", "confirm_capture": "取得を確認", - "confirm_pagination": "ページネーションを確認", + "confirm_pagination": "確認", "confirm_limit": "制限を確認", "finish_capture": "取得を完了", "back": "戻る", From 20b31f36d99fe327075e9c104637b79b282edf87 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:30:57 +0530 Subject: [PATCH 416/500] feat(japanese): confirm instead of confirm limit --- public/locales/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/ja.json b/public/locales/ja.json index e2204e14..0bcba967 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -160,7 +160,7 @@ "discard": "破棄", "confirm_capture": "取得を確認", "confirm_pagination": "確認", - "confirm_limit": "制限を確認", + "confirm_limit": "確認", "finish_capture": "取得を完了", "back": "戻る", "finish": "完了", From e78a61139d7fa828cf772ee4bc6c63889f77f3f8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:31:50 +0530 Subject: [PATCH 417/500] feat(german): confirm instead of confirm pagination --- public/locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index debf80f6..e06b784a 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -158,7 +158,7 @@ "confirm": "Bestätigen", "discard": "Verwerfen", "confirm_capture": "Erfassung bestätigen", - "confirm_pagination": "Paginierung bestätigen", + "confirm_pagination": "Bestätigen", "confirm_limit": "Limit bestätigen", "finish_capture": "Erfassung abschließen", "back": "Zurück", From c753ce551200fa4b6431ff034694c6dcd343e516 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:32:16 +0530 Subject: [PATCH 418/500] feat(german): confirm instead of confirm limit --- public/locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index e06b784a..b9b4185b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -159,7 +159,7 @@ "discard": "Verwerfen", "confirm_capture": "Erfassung bestätigen", "confirm_pagination": "Bestätigen", - "confirm_limit": "Limit bestätigen", + "confirm_limit": "Bestätigen", "finish_capture": "Erfassung abschließen", "back": "Zurück", "finish": "Fertig", From 634daeecf595cd2418913d9ae21689e55e5c2b39 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:32:56 +0530 Subject: [PATCH 419/500] feat(chinese): confirm instead of confirm pagination --- public/locales/zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index e55565f8..805396dc 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -159,7 +159,7 @@ "confirm": "确认", "discard": "放弃", "confirm_capture": "确认捕获", - "confirm_pagination": "确认分页", + "confirm_pagination": "确认", "confirm_limit": "确认限制", "finish_capture": "完成捕获", "back": "返回", From cd7f38f561a1ce2e4b596a25adeb48c5b6342f0f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 30 Dec 2024 19:33:12 +0530 Subject: [PATCH 420/500] feat(chinese): confirm instead of confirm limit --- public/locales/zh.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/zh.json b/public/locales/zh.json index 805396dc..a19fe439 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -160,7 +160,7 @@ "discard": "放弃", "confirm_capture": "确认捕获", "confirm_pagination": "确认", - "confirm_limit": "确认限制", + "confirm_limit": "确认", "finish_capture": "完成捕获", "back": "返回", "finish": "完成", From d2ab81e22959acc9fccf65f5845d8962170608d7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 22:59:28 +0530 Subject: [PATCH 421/500] feat: add logic to get deeply nested shadowDOM elements --- server/src/workflow-management/selector.ts | 87 ++++++++++++---------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 164f5220..d957b879 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -823,7 +823,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { return output; } - const MAX_DEPTH = 10; + // const MAX_DEPTH = 10; const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { let element = document.elementFromPoint(x, y) as HTMLElement; @@ -832,60 +832,76 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { let current = element; let deepestElement = current; let depth = 0; + const MAX_DEPTH = 4; // Limit to 2 levels of shadow DOM while (current && depth < MAX_DEPTH) { const shadowRoot = current.shadowRoot; - if (shadowRoot) { - const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; - if (!shadowElement) break; - - deepestElement = shadowElement; - current = shadowElement; - } else { - break; - } + if (!shadowRoot) break; + + const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; + if (!shadowElement || shadowElement === current) break; + + deepestElement = shadowElement; + current = shadowElement; depth++; } return deepestElement; }; + // Helper function to generate selectors for shadow DOM elements const genSelectorForShadowDOM = (element: HTMLElement) => { - const findShadowContext = (element: HTMLElement): { host: HTMLElement, root: ShadowRoot } | null => { - let current: HTMLElement | null = element; + // Get complete path up to document root + const getShadowPath = (el: HTMLElement) => { + const path = []; + let current = el; let depth = 0; + const MAX_DEPTH = 4; while (current && depth < MAX_DEPTH) { - // Check if element is inside a shadow root - if (current.parentNode instanceof ShadowRoot) { - return { - host: (current.parentNode as ShadowRoot).host as HTMLElement, - root: current.parentNode as ShadowRoot - }; + const rootNode = current.getRootNode(); + if (rootNode instanceof ShadowRoot) { + path.unshift({ + host: rootNode.host as HTMLElement, + root: rootNode, + element: current + }); + current = rootNode.host as HTMLElement; + depth++; + } else { + break; } - current = current.parentElement; - depth++; } - return null; + return path; }; - - const shadowContext = findShadowContext(element); - if (!shadowContext) return null; + + const shadowPath = getShadowPath(element); + if (shadowPath.length === 0) return null; try { - // Generate selector for the shadow host - const hostSelector = finder(shadowContext.host); + const selectorParts: string[] = []; - // Generate selector for the element within the shadow DOM - const shadowElementSelector = finder(element, { - root: shadowContext.root as unknown as Element + // Generate selector for each shadow DOM boundary + shadowPath.forEach((context, index) => { + // Get selector for the host element + const hostSelector = finder(context.host, { + root: index === 0 ? document.body : (shadowPath[index - 1].root as unknown as Element) + }); + + // For the last context, get selector for target element + if (index === shadowPath.length - 1) { + const elementSelector = finder(element, { + root: context.root as unknown as Element + }); + selectorParts.push(`${hostSelector} >> ${elementSelector}`); + } else { + selectorParts.push(hostSelector); + } }); return { - fullSelector: `${hostSelector} >> ${shadowElementSelector}`, - hostSelector, - shadowElementSelector, - mode: shadowContext.root.mode + fullSelector: selectorParts.join(' >> '), + mode: shadowPath[shadowPath.length - 1].root.mode }; } catch (e) { console.warn('Error generating shadow DOM selector:', e); @@ -963,12 +979,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { formSelector, // Shadow DOM selector shadowSelector: shadowSelector ? { - // Full selector that can traverse shadow DOM full: shadowSelector.fullSelector, - // Individual parts for more flexible usage - host: shadowSelector.hostSelector, - element: shadowSelector.shadowElementSelector, - // Shadow root mode (open/closed) mode: shadowSelector.mode } : null }; From 9287c296922478b77391d0c4930f4b478de4614e Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 23:02:21 +0530 Subject: [PATCH 422/500] feat: rm host and element info for shadow selector --- server/src/types/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/types/index.ts b/server/src/types/index.ts index e882f69d..151e3dd4 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -131,8 +131,6 @@ export interface BaseActionInfo { interface ShadowSelector { full: string; - host: string; - element: string; mode: string; } From e952d8f202278a67e86350e60542576b09260238 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 23:37:16 +0530 Subject: [PATCH 423/500] feat: add nested shadow-root scraping logic for scrapeSchema --- maxun-core/src/browserSide/scraper.js | 115 ++++++++++++++++---------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index ef979828..ad9295b8 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -188,8 +188,7 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, * @param {Object.} lists The named lists of HTML elements. * @returns {Array.>} */ - window.scrapeSchema = function (lists) { - // These utility functions remain unchanged as they work perfectly + window.scrapeSchema = function(lists) { function omap(object, f, kf = (x) => x) { return Object.fromEntries( Object.entries(object) @@ -203,29 +202,73 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, .filter(([k, v]) => f(k, v)), ); } - - function findElement(config) { - // If this is a shadow DOM query - if (config.shadow && config.selector.includes('>>')) { - const [hostSelector, shadowSelector] = config.selector.split('>>').map(s => s.trim()); - const host = document.querySelector(hostSelector); - return host?.shadowRoot?.querySelector(shadowSelector) || null; - } - // Otherwise, use regular querySelector - return document.querySelector(config.selector); - } - + function findAllElements(config) { - // If this is a shadow DOM query - if (config.shadow && config.selector.includes('>>')) { - const element = findElement(config); - return element ? [element] : []; - } - // Otherwise, use regular querySelectorAll - return Array.from(document.querySelectorAll(config.selector)); + if (!config.shadow || !config.selector.includes('>>')) { + return Array.from(document.querySelectorAll(config.selector)); + } + + // For shadow DOM, we'll get all possible combinations + const parts = config.selector.split('>>').map(s => s.trim()); + let currentElements = [document]; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + const nextElements = []; + + for (const element of currentElements) { + let targets; + if (i === 0) { + // First selector is queried from document + targets = Array.from(element.querySelectorAll(part)) + .filter(el => { + // Only include elements that either: + // 1. Have an open shadow root + // 2. Don't need shadow root (last part of selector) + if (i === parts.length - 1) return true; + const shadowRoot = el.shadowRoot; + return shadowRoot && shadowRoot.mode === 'open'; + }); + } else { + // For subsequent selectors, only use elements with open shadow roots + const shadowRoot = element.shadowRoot; + if (!shadowRoot || shadowRoot.mode !== 'open') continue; + + targets = Array.from(shadowRoot.querySelectorAll(part)); + } + nextElements.push(...targets); + } + + if (nextElements.length === 0) return []; + currentElements = nextElements; + } + + return currentElements; } + + // Helper function to extract value from element based on attribute + function getElementValue(element, attribute) { + if (!element) return null; + + switch (attribute) { + case 'href': { + const relativeHref = element.getAttribute('href'); + return relativeHref ? new URL(relativeHref, window.location.origin).href : null; + } + case 'src': { + const relativeSrc = element.getAttribute('src'); + return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; + } + case 'innerText': + return element.innerText?.trim(); + case 'textContent': + return element.textContent?.trim(); + default: + return element.getAttribute(attribute) || element.innerText?.trim(); + } + } - // Modified to use our new element finding functions + // Get the seed key based on the maximum number of elements found function getSeedKey(listObj) { const maxLength = Math.max(...Object.values( omap(listObj, (x) => findAllElements(x).length) @@ -235,8 +278,7 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, )[0]; } - // This function remains unchanged as it works with DOM elements - // regardless of how they were found + // Find minimal bounding elements function getMBEs(elements) { return elements.map((element) => { let candidate = element; @@ -252,35 +294,18 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, }); } + // Main scraping logic const seedName = getSeedKey(lists); const seedElements = findAllElements(lists[seedName]); const MBEs = getMBEs(seedElements); return MBEs.map((mbe) => omap( lists, - (config, key) => { - // Use our new findAllElements function + (config) => { const elem = findAllElements(config) .find((elem) => mbe.contains(elem)); - - if (!elem) return undefined; - - switch (config.attribute) { - case 'href': { - const relativeHref = elem.getAttribute('href'); - return relativeHref ? new URL(relativeHref, window.location.origin).href : null; - } - case 'src': { - const relativeSrc = elem.getAttribute('src'); - return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; - } - case 'innerText': - return elem.innerText; - case 'textContent': - return elem.textContent; - default: - return elem.getAttribute(config.attribute) || elem.innerText; - } + + return elem ? getElementValue(elem, config.attribute) : undefined; }, (key) => key )) || []; From b757d9c4f8b0ea00d6eb6d6fe6c2e7c37407ed92 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 30 Dec 2024 23:38:38 +0530 Subject: [PATCH 424/500] feat: add func to rm shadow selectors from workflow --- maxun-core/src/interpret.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 495ba2db..3cef8c29 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -663,11 +663,28 @@ export default class Interpreter extends EventEmitter { if (isApplicable) { return actionId; } + } } + + private removeShadowSelectors(workflow: Workflow) { + for (let actionId = workflow.length - 1; actionId >= 0; actionId--) { + const step = workflow[actionId]; + + // Check if step has where and selectors + if (step.where && Array.isArray(step.where.selectors)) { + // Filter out selectors that contain ">>" + step.where.selectors = step.where.selectors.filter(selector => !selector.includes('>>')); + } + } + + return workflow; } private async runLoop(p: Page, workflow: Workflow) { - const workflowCopy: Workflow = JSON.parse(JSON.stringify(workflow)); + let workflowCopy: Workflow = JSON.parse(JSON.stringify(workflow)); + + // remove shadow selectors + workflowCopy = this.removeShadowSelectors(workflowCopy); // apply ad-blocker to the current page try { From 4b4074b70d352401120bd1fe0b37fbee7838bac5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 31 Dec 2024 01:52:38 +0530 Subject: [PATCH 425/500] feat: add logic to scrape multiple nested shadow dom elements --- maxun-core/src/browserSide/scraper.js | 172 +++++++++++++++----------- 1 file changed, 99 insertions(+), 73 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index ad9295b8..00f8cef7 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -204,69 +204,68 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, } function findAllElements(config) { - if (!config.shadow || !config.selector.includes('>>')) { - return Array.from(document.querySelectorAll(config.selector)); - } - - // For shadow DOM, we'll get all possible combinations - const parts = config.selector.split('>>').map(s => s.trim()); - let currentElements = [document]; - - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - const nextElements = []; - - for (const element of currentElements) { - let targets; - if (i === 0) { - // First selector is queried from document - targets = Array.from(element.querySelectorAll(part)) - .filter(el => { - // Only include elements that either: - // 1. Have an open shadow root - // 2. Don't need shadow root (last part of selector) - if (i === parts.length - 1) return true; - const shadowRoot = el.shadowRoot; - return shadowRoot && shadowRoot.mode === 'open'; - }); - } else { - // For subsequent selectors, only use elements with open shadow roots - const shadowRoot = element.shadowRoot; - if (!shadowRoot || shadowRoot.mode !== 'open') continue; - - targets = Array.from(shadowRoot.querySelectorAll(part)); + if (!config.shadow || !config.selector.includes('>>')) { + return Array.from(document.querySelectorAll(config.selector)); + } + + // For shadow DOM, we'll get all possible combinations + const parts = config.selector.split('>>').map(s => s.trim()); + let currentElements = [document]; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + const nextElements = []; + + for (const element of currentElements) { + let targets; + if (i === 0) { + // First selector is queried from document + targets = Array.from(element.querySelectorAll(part)) + .filter(el => { + // Only include elements that either: + // 1. Have an open shadow root + // 2. Don't need shadow root (last part of selector) + if (i === parts.length - 1) return true; + const shadowRoot = el.shadowRoot; + return shadowRoot && shadowRoot.mode === 'open'; + }); + } else { + // For subsequent selectors, only use elements with open shadow roots + const shadowRoot = element.shadowRoot; + if (!shadowRoot || shadowRoot.mode !== 'open') continue; + + targets = Array.from(shadowRoot.querySelectorAll(part)); + } + nextElements.push(...targets); } - nextElements.push(...targets); - } - - if (nextElements.length === 0) return []; - currentElements = nextElements; - } - - return currentElements; + + if (nextElements.length === 0) return []; + currentElements = nextElements; + } + + return currentElements; } - // Helper function to extract value from element based on attribute - function getElementValue(element, attribute) { - if (!element) return null; - - switch (attribute) { - case 'href': { - const relativeHref = element.getAttribute('href'); - return relativeHref ? new URL(relativeHref, window.location.origin).href : null; - } - case 'src': { - const relativeSrc = element.getAttribute('src'); - return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; - } - case 'innerText': - return element.innerText?.trim(); - case 'textContent': - return element.textContent?.trim(); - default: - return element.getAttribute(attribute) || element.innerText?.trim(); - } - } + function getElementValue(element, attribute) { + if (!element) return null; + + switch (attribute) { + case 'href': { + const relativeHref = element.getAttribute('href'); + return relativeHref ? new URL(relativeHref, window.location.origin).href : null; + } + case 'src': { + const relativeSrc = element.getAttribute('src'); + return relativeSrc ? new URL(relativeSrc, window.location.origin).href : null; + } + case 'innerText': + return element.innerText?.trim(); + case 'textContent': + return element.textContent?.trim(); + default: + return element.getAttribute(attribute) || element.innerText?.trim(); + } + } // Get the seed key based on the maximum number of elements found function getSeedKey(listObj) { @@ -280,26 +279,26 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, // Find minimal bounding elements function getMBEs(elements) { - return elements.map((element) => { - let candidate = element; - const isUniqueChild = (e) => elements - .filter((elem) => e.parentNode?.contains(elem)) - .length === 1; + return elements.map((element) => { + let candidate = element; + const isUniqueChild = (e) => elements + .filter((elem) => e.parentNode?.contains(elem)) + .length === 1; - while (candidate && isUniqueChild(candidate)) { - candidate = candidate.parentNode; - } + while (candidate && isUniqueChild(candidate)) { + candidate = candidate.parentNode; + } - return candidate; - }); + return candidate; + }); } - // Main scraping logic + // First try the MBE approach const seedName = getSeedKey(lists); const seedElements = findAllElements(lists[seedName]); const MBEs = getMBEs(seedElements); - - return MBEs.map((mbe) => omap( + + const mbeResults = MBEs.map((mbe) => omap( lists, (config) => { const elem = findAllElements(config) @@ -309,6 +308,33 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, }, (key) => key )) || []; + + // If MBE approach didn't find all elements, try independent scraping + if (mbeResults.some(result => Object.values(result).some(v => v === undefined))) { + // Fall back to independent scraping + const results = []; + const foundElements = new Map(); + + // Find all elements for each selector + Object.entries(lists).forEach(([key, config]) => { + const elements = findAllElements(config); + foundElements.set(key, elements); + }); + + // Create result objects for each found element + foundElements.forEach((elements, key) => { + elements.forEach((element, index) => { + if (!results[index]) { + results[index] = {}; + } + results[index][key] = getElementValue(element, lists[key].attribute); + }); + }); + + return results.filter(result => Object.keys(result).length > 0); + } + + return mbeResults; }; /** From 4a09ea66ff6c3c25c02b7997ed97f0ac4d677cd9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Tue, 31 Dec 2024 12:26:09 +0530 Subject: [PATCH 426/500] feat: get deepest element rect coordinates --- server/src/workflow-management/selector.ts | 53 ++++++++++++++-------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index d957b879..910b3134 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -237,23 +237,30 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector if (!getList || listSelector !== '') { const rect = await page.evaluate( async ({ x, y }) => { - // Helper function to get element from point including shadow DOM + // Enhanced helper function to get element from point including shadow DOM const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { let element = document.elementFromPoint(x, y) as HTMLElement; if (!element) return null; // Traverse through shadow roots let current = element; - while (current) { - const shadowRoot = current.shadowRoot; - if (!shadowRoot) break; - + let shadowRoot = current.shadowRoot; + + // Keep track of the deepest shadow DOM element found + let deepestElement = current; + + while (shadowRoot) { + // Try to find element at same point in shadow DOM const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; if (!shadowElement || shadowElement === current) break; - + + // Update our tracking of the deepest element + deepestElement = shadowElement; current = shadowElement; + shadowRoot = current.shadowRoot; } - return current; + + return deepestElement; }; const el = getDeepestElementFromPoint(x, y); @@ -274,36 +281,45 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector }; } } + return null; }, - { x: coordinates.x, y: coordinates.y }, + { x: coordinates.x, y: coordinates.y } ); return rect; } else { const rect = await page.evaluate( async ({ x, y }) => { - // Helper function to get element from point including shadow DOM + // Enhanced helper function to get element from point including shadow DOM const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { let element = document.elementFromPoint(x, y) as HTMLElement; if (!element) return null; // Traverse through shadow roots let current = element; - while (current) { - const shadowRoot = current.shadowRoot; - if (!shadowRoot) break; - + let shadowRoot = current.shadowRoot; + + // Keep track of the deepest shadow DOM element found + let deepestElement = current; + + while (shadowRoot) { + // Try to find element at same point in shadow DOM const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; if (!shadowElement || shadowElement === current) break; - + + // Update our tracking of the deepest element + deepestElement = shadowElement; current = shadowElement; + shadowRoot = current.shadowRoot; } - return current; + + return deepestElement; }; const originalEl = getDeepestElementFromPoint(x, y); if (originalEl) { let element = originalEl; + // Handle element hierarchy traversal for list items while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -326,7 +342,6 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector } const rectangle = element?.getBoundingClientRect(); - if (rectangle) { return { x: rectangle.x, @@ -342,14 +357,14 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector } return null; }, - { x: coordinates.x, y: coordinates.y }, + { x: coordinates.x, y: coordinates.y } ); return rect; } } catch (error) { const { message, stack } = error as Error; - logger.log('error', `Error while retrieving selector: ${message}`); - logger.log('error', `Stack: ${stack}`); + console.error('Error while retrieving selector:', message); + console.error('Stack:', stack); } }; From 4c0ad3ceed6a8b192d8e672b36908fd4db3871cd Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 31 Dec 2024 21:26:53 +0530 Subject: [PATCH 427/500] fix: avoid ui shift on api key reveal --- src/components/organisms/ApiKey.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index 37a72764..0af27934 100644 --- a/src/components/organisms/ApiKey.tsx +++ b/src/components/organisms/ApiKey.tsx @@ -124,7 +124,11 @@ const ApiKeyManager = () => { {apiKeyName} - {showKey ? `${apiKey?.substring(0, 10)}...` : '***************'} + + + {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} + + From a3337d7fcc08a8436edadcb33b0214401a63e28d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 31 Dec 2024 21:27:12 +0530 Subject: [PATCH 428/500] fix: format --- src/components/organisms/ApiKey.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index 0af27934..9d54fe5c 100644 --- a/src/components/organisms/ApiKey.tsx +++ b/src/components/organisms/ApiKey.tsx @@ -126,7 +126,7 @@ const ApiKeyManager = () => { {apiKeyName} - {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} + {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} From 42e13066bd7800043e6952ddaae06d62985c2ee4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 1 Jan 2025 16:13:38 +0530 Subject: [PATCH 429/500] feat: add shadowDOM support for capture list selector generation --- server/src/workflow-management/selector.ts | 343 +++++++++++++++++---- 1 file changed, 286 insertions(+), 57 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 910b3134..713c05bc 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -1076,46 +1076,133 @@ interface SelectorResult { */ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates, listSelector: string): Promise => { + interface ShadowContext { + host: HTMLElement; + root: ShadowRoot; + element: HTMLElement; + } + try { if (!listSelector) { const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { - function getNonUniqueSelector(element: HTMLElement): string { - let selector = element.tagName.toLowerCase(); + // Helper function to get deepest element, traversing shadow DOM + function getDeepestElementFromPoint(x: number, y: number): HTMLElement | null { + let element = document.elementFromPoint(x, y) as HTMLElement; + if (!element) return null; - if (element.className) { - const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); - if (classes.length > 0) { - const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); - if (validClasses.length > 0) { - selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); - } - } + let current = element; + let deepestElement = current; + let depth = 0; + const MAX_DEPTH = 4; // Limit shadow DOM traversal depth + + while (current && depth < MAX_DEPTH) { + const shadowRoot = current.shadowRoot; + if (!shadowRoot) break; + + const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; + if (!shadowElement || shadowElement === current) break; + + deepestElement = shadowElement; + current = shadowElement; + depth++; } + return deepestElement; + } + + // Generate basic selector from element's tag and classes + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); + + const className = typeof element.className === 'string' ? element.className : ''; + if (className) { + const classes = className.split(/\s+/) + .filter(cls => Boolean(cls) && !cls.startsWith('!') && !cls.includes(':')); + + if (classes.length > 0) { + selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); + } + } + return selector; } - function getSelectorPath(element: HTMLElement | null): string { - const path: string[] = []; + // Get complete shadow DOM path for an element + function getShadowPath(element: HTMLElement): ShadowContext[] { + const path: ShadowContext[] = []; + let current = element; let depth = 0; - const maxDepth = 2; + const MAX_DEPTH = 4; + + while (current && depth < MAX_DEPTH) { + const rootNode = current.getRootNode(); + if (rootNode instanceof ShadowRoot) { + path.unshift({ + host: rootNode.host as HTMLElement, + root: rootNode, + element: current + }); + current = rootNode.host as HTMLElement; + depth++; + } else { + break; + } + } + return path; + } - while (element && element !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(element); + // Generate complete selector path for any element + function getSelectorPath(element: HTMLElement | null): string { + if (!element) return ''; + + // Check for shadow DOM path first + const shadowPath = getShadowPath(element); + if (shadowPath.length > 0) { + const selectorParts: string[] = []; + + // Build complete shadow DOM path + shadowPath.forEach((context, index) => { + const hostSelector = getNonUniqueSelector(context.host); + + if (index === shadowPath.length - 1) { + // For deepest shadow context, include target element + const elementSelector = getNonUniqueSelector(element); + selectorParts.push(`${hostSelector} >> ${elementSelector}`); + } else { + // For intermediate shadow boundaries + selectorParts.push(hostSelector); + } + }); + + return selectorParts.join(' >> '); + } + + // Regular DOM path generation + const path: string[] = []; + let currentElement = element; + let depth = 0; + const MAX_DEPTH = 2; + + while (currentElement && currentElement !== document.body && depth < MAX_DEPTH) { + const selector = getNonUniqueSelector(currentElement); path.unshift(selector); - element = element.parentElement; + + const parentElement = currentElement.parentElement; + if (!parentElement) break; + currentElement = parentElement; depth++; } return path.join(' > '); } - const originalEl = document.elementFromPoint(x, y) as HTMLElement; + // Main logic to get element and generate selector + const originalEl = getDeepestElementFromPoint(x, y); if (!originalEl) return null; let element = originalEl; - // if (listSelector === '') { + // Handle parent traversal for better element targeting while (element.parentElement) { const parentRect = element.parentElement.getBoundingClientRect(); const childRect = element.getBoundingClientRect(); @@ -1136,60 +1223,134 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates break; } } - // } const generalSelector = getSelectorPath(element); - return { - generalSelector, - }; + return { generalSelector }; }, coordinates); + return selectors || { generalSelector: '' }; } else { + // When we have a list selector, we need special handling while maintaining shadow DOM support const selectors = await page.evaluate(({ x, y }: { x: number, y: number }) => { - function getNonUniqueSelector(element: HTMLElement): string { - let selector = element.tagName.toLowerCase(); + // Helper function to get deepest element, traversing shadow DOM + function getDeepestElementFromPoint(x: number, y: number): HTMLElement | null { + let element = document.elementFromPoint(x, y) as HTMLElement; + if (!element) return null; - if (element.className) { - const classes = element.className.split(/\s+/).filter((cls: string) => Boolean(cls)); - if (classes.length > 0) { - const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); - if (validClasses.length > 0) { - selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); - } - } + let current = element; + let deepestElement = current; + let depth = 0; + const MAX_DEPTH = 4; + + while (current && depth < MAX_DEPTH) { + const shadowRoot = current.shadowRoot; + if (!shadowRoot) break; + + const shadowElement = shadowRoot.elementFromPoint(x, y) as HTMLElement; + if (!shadowElement || shadowElement === current) break; + + deepestElement = shadowElement; + current = shadowElement; + depth++; } + return deepestElement; + } + + // Generate basic selector from element's tag and classes + function getNonUniqueSelector(element: HTMLElement): string { + let selector = element.tagName.toLowerCase(); + + const className = typeof element.className === 'string' ? element.className : ''; + if (className) { + const classes = className.split(/\s+/) + .filter(cls => Boolean(cls) && !cls.startsWith('!') && !cls.includes(':')); + + if (classes.length > 0) { + selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); + } + } + return selector; } - function getSelectorPath(element: HTMLElement | null): string { - const path: string[] = []; + // Get complete shadow DOM path for an element + function getShadowPath(element: HTMLElement): ShadowContext[] { + const path: ShadowContext[] = []; + let current = element; let depth = 0; - const maxDepth = 2; + const MAX_DEPTH = 4; + + while (current && depth < MAX_DEPTH) { + const rootNode = current.getRootNode(); + if (rootNode instanceof ShadowRoot) { + path.unshift({ + host: rootNode.host as HTMLElement, + root: rootNode, + element: current + }); + current = rootNode.host as HTMLElement; + depth++; + } else { + break; + } + } + return path; + } - while (element && element !== document.body && depth < maxDepth) { - const selector = getNonUniqueSelector(element); + // Generate selector path specifically for list items + function getListItemSelectorPath(element: HTMLElement | null): string { + if (!element) return ''; + + // Check for shadow DOM path first + const shadowPath = getShadowPath(element); + if (shadowPath.length > 0) { + const selectorParts: string[] = []; + + shadowPath.forEach((context, index) => { + const hostSelector = getNonUniqueSelector(context.host); + + if (index === shadowPath.length - 1) { + const elementSelector = getNonUniqueSelector(element); + selectorParts.push(`${hostSelector} >> ${elementSelector}`); + } else { + selectorParts.push(hostSelector); + } + }); + + return selectorParts.join(' >> '); + } + + // For list items, we want a shallower path to better match list patterns + const path: string[] = []; + let currentElement = element; + let depth = 0; + const MAX_LIST_DEPTH = 2; // Keeping shallow depth for list items + + while (currentElement && currentElement !== document.body && depth < MAX_LIST_DEPTH) { + const selector = getNonUniqueSelector(currentElement); path.unshift(selector); - element = element.parentElement; + + if (!currentElement.parentElement) break; + currentElement = currentElement.parentElement; depth++; } return path.join(' > '); } - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (!originalEl) return null; + // Main logic for list item selection + const originalEl = getDeepestElementFromPoint(x, y); + if (!originalEl) return { generalSelector: '' }; let element = originalEl; - const generalSelector = getSelectorPath(element); - return { - generalSelector, - }; - }, coordinates); - return selectors || { generalSelector: '' }; - } + const generalSelector = getListItemSelectorPath(element); + return { generalSelector }; + }, coordinates); + return selectors || { generalSelector: '' }; + } } catch (error) { console.error('Error in getNonUniqueSelectors:', error); return { generalSelector: '' }; @@ -1218,42 +1379,110 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro } // Function to generate selector path from an element to its parent - function getSelectorPath(element: HTMLElement | null): string { + function getSelectorPath(element: HTMLElement): string { if (!element || !element.parentElement) return ''; const parentSelector = getNonUniqueSelector(element.parentElement); const elementSelector = getNonUniqueSelector(element); + // Check if element is in shadow DOM + const rootNode = element.getRootNode(); + if (rootNode instanceof ShadowRoot) { + const hostSelector = getNonUniqueSelector(rootNode.host as HTMLElement); + return `${hostSelector} >> ${elementSelector}`; + } + return `${parentSelector} > ${elementSelector}`; } - // Function to recursively get all descendant selectors + // Function to get all shadow DOM children of an element + function getShadowChildren(element: HTMLElement): HTMLElement[] { + const children: HTMLElement[] = []; + + // Check if element has shadow root + const shadowRoot = element.shadowRoot; + if (shadowRoot) { + // Get all elements in the shadow DOM + const shadowElements = Array.from(shadowRoot.querySelectorAll('*')) as HTMLElement[]; + children.push(...shadowElements); + } + + return children; + } + + // Function to recursively get all descendant selectors including shadow DOM function getAllDescendantSelectors(element: HTMLElement): string[] { let selectors: string[] = []; + + // Handle regular DOM children const children = Array.from(element.children) as HTMLElement[]; - for (const child of children) { const childPath = getSelectorPath(child); if (childPath) { - selectors.push(childPath); // Add direct child path - selectors = selectors.concat(getAllDescendantSelectors(child)); // Recursively process descendants + selectors.push(childPath); + // Recursively process regular DOM descendants + selectors = selectors.concat(getAllDescendantSelectors(child)); + + // Check for shadow DOM in this child + const shadowChildren = getShadowChildren(child); + for (const shadowChild of shadowChildren) { + const shadowPath = getSelectorPath(shadowChild); + if (shadowPath) { + selectors.push(shadowPath); + // Recursively process shadow DOM descendants + selectors = selectors.concat(getAllDescendantSelectors(shadowChild)); + } + } + } + } + + // Handle direct shadow DOM children of the current element + const shadowChildren = getShadowChildren(element); + for (const shadowChild of shadowChildren) { + const shadowPath = getSelectorPath(shadowChild); + if (shadowPath) { + selectors.push(shadowPath); + selectors = selectors.concat(getAllDescendantSelectors(shadowChild)); } } return selectors; } - // Find all occurrences of the parent selector in the DOM - const parentElements = Array.from(document.querySelectorAll(parentSelector)) as HTMLElement[]; - const allChildSelectors = new Set(); // Use a set to ensure uniqueness + // Split the parent selector if it contains shadow DOM parts + const selectorParts = parentSelector.split('>>').map(part => part.trim()); + let parentElements: HTMLElement[] = []; + + // Handle shadow DOM traversal if needed + if (selectorParts.length > 1) { + // Start with the host elements + parentElements = Array.from(document.querySelectorAll(selectorParts[0])) as HTMLElement[]; + + // Traverse through shadow DOM parts + for (let i = 1; i < selectorParts.length; i++) { + const newParentElements: HTMLElement[] = []; + for (const element of parentElements) { + if (element.shadowRoot) { + const shadowChildren = Array.from(element.shadowRoot.querySelectorAll(selectorParts[i])) as HTMLElement[]; + newParentElements.push(...shadowChildren); + } + } + parentElements = newParentElements; + } + } else { + // Regular DOM selector + parentElements = Array.from(document.querySelectorAll(parentSelector)) as HTMLElement[]; + } + + const allChildSelectors = new Set(); // Process each parent element and its descendants parentElements.forEach((parentElement) => { const descendantSelectors = getAllDescendantSelectors(parentElement); - descendantSelectors.forEach((selector) => allChildSelectors.add(selector)); // Add selectors to the set + descendantSelectors.forEach((selector) => allChildSelectors.add(selector)); }); - return Array.from(allChildSelectors); // Convert the set back to an array + return Array.from(allChildSelectors); }, parentSelector); return childSelectors || []; From c6105b4ee226a562f80b7054fffb3acac23e9d23 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 1 Jan 2025 16:15:13 +0530 Subject: [PATCH 430/500] feat: generate highlighter for shadoDOM and mixedDOM elements --- src/components/organisms/BrowserWindow.tsx | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 11fe8c55..442b7e50 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -120,7 +120,13 @@ export const BrowserWindow = () => { const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] }) => { if (getList === true) { if (listSelector) { + console.log("LIST SELEECTORRRRR: ", listSelector); + console.log("DATA SELEECTORRRRR: ", data.selector); + console.log("CHILDREEENN SELECORRRR: ", data.childSelectors); socket?.emit('listSelector', { selector: listSelector }); + + const hasValidChildSelectors = Array.isArray(data.childSelectors) && data.childSelectors.length > 0; + if (limitMode) { setHighlighterData(null); } else if (paginationMode) { @@ -133,7 +139,29 @@ export const BrowserWindow = () => { } else if (data.childSelectors && data.childSelectors.includes(data.selector)) { // highlight only valid child elements within the listSelector setHighlighterData(data); - } else { + } else if (data.elementInfo?.isShadowRoot && data.childSelectors) { + // New case: Handle pure Shadow DOM elements + // Check if the selector matches any shadow root child selectors + const isShadowChild = data.childSelectors.some(childSelector => + data.selector.includes('>>') && // Shadow DOM uses >> for piercing + childSelector.split('>>').some(part => + data.selector.includes(part.trim()) + ) + ); + setHighlighterData(isShadowChild ? data : null); + } else if (data.selector.includes('>>') && hasValidChildSelectors) { + // New case: Handle mixed DOM cases + // Split the selector into parts and check each against child selectors + const selectorParts = data.selector.split('>>').map(part => part.trim()); + const isValidMixedSelector = selectorParts.some(part => + // Now we know data.childSelectors is defined + data.childSelectors!.some(childSelector => + childSelector.includes(part) + ) + ); + setHighlighterData(isValidMixedSelector ? data : null); + } + else { // if !valid child in normal mode, clear the highlighter setHighlighterData(null); } From 8db6279f05c25e671098f959bfe0b79f5d06cb4f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 1 Jan 2025 16:39:36 +0530 Subject: [PATCH 431/500] feat: add shadowDOM support for scraping list --- maxun-core/src/browserSide/scraper.js | 146 ++++++++++++++++++++------ 1 file changed, 113 insertions(+), 33 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index 00f8cef7..caa783c8 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -349,27 +349,100 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, window.scrapeList = async function ({ listSelector, fields, limit = 10 }) { const scrapedData = []; - while (scrapedData.length < limit) { - let parentElements = Array.from(document.querySelectorAll(listSelector)); + // Helper function to query through Shadow DOM + const queryShadowDOM = (rootElement, selector) => { + // Split the selector by Shadow DOM delimiter + const parts = selector.split('>>').map(part => part.trim()); + let currentElement = rootElement; + + // Traverse through each part of the selector + for (let i = 0; i < parts.length; i++) { + if (!currentElement) return null; + + // If we're at the document level (first part) + if (!currentElement.querySelector && !currentElement.shadowRoot) { + currentElement = document.querySelector(parts[i]); + continue; + } + + // Try to find element in regular DOM first + let nextElement = currentElement.querySelector(parts[i]); + + // If not found, check shadow DOM + if (!nextElement && currentElement.shadowRoot) { + nextElement = currentElement.shadowRoot.querySelector(parts[i]); + } + + // If still not found, try to find in shadow DOM of all child elements + if (!nextElement) { + const allChildren = Array.from(currentElement.children || []); + for (const child of allChildren) { + if (child.shadowRoot) { + nextElement = child.shadowRoot.querySelector(parts[i]); + if (nextElement) break; + } + } + } + + currentElement = nextElement; + } + + return currentElement; + }; + + // Helper function to query all elements through Shadow DOM + const queryShadowDOMAll = (rootElement, selector) => { + const parts = selector.split('>>').map(part => part.trim()); + let currentElements = [rootElement]; - // If we only got one element or none, try a more generic approach + for (const part of parts) { + const nextElements = []; + + for (const element of currentElements) { + // Check regular DOM + if (element.querySelectorAll) { + nextElements.push(...element.querySelectorAll(part)); + } + + // Check shadow DOM + if (element.shadowRoot) { + nextElements.push(...element.shadowRoot.querySelectorAll(part)); + } + + // Check shadow DOM of children + const children = Array.from(element.children || []); + for (const child of children) { + if (child.shadowRoot) { + nextElements.push(...child.shadowRoot.querySelectorAll(part)); + } + } + } + + currentElements = nextElements; + } + + return currentElements; + }; + + while (scrapedData.length < limit) { + // Use our shadow DOM query function to get parent elements + let parentElements = queryShadowDOMAll(document, listSelector); + parentElements = Array.from(parentElements); + + // Handle the case when we don't find enough elements if (limit > 1 && parentElements.length <= 1) { - const [containerSelector, _] = listSelector.split('>').map(s => s.trim()); - const container = document.querySelector(containerSelector); + const [containerSelector, ...rest] = listSelector.split('>>').map(s => s.trim()); + const container = queryShadowDOM(document, containerSelector); if (container) { - const allChildren = Array.from(container.children); + const allChildren = Array.from(container.children || []); + const firstMatch = queryShadowDOM(document, listSelector); - const firstMatch = document.querySelector(listSelector); if (firstMatch) { - // Get classes from the first matching element - const firstMatchClasses = Array.from(firstMatch.classList); + const firstMatchClasses = Array.from(firstMatch.classList || []); - // Find similar elements by matching most of their classes parentElements = allChildren.filter(element => { - const elementClasses = Array.from(element.classList); - - // Element should share at least 70% of classes with the first match + const elementClasses = Array.from(element.classList || []); const commonClasses = firstMatchClasses.filter(cls => elementClasses.includes(cls)); return commonClasses.length >= Math.floor(firstMatchClasses.length * 0.7); @@ -378,42 +451,49 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, } } - // Iterate through each parent element + // Process each parent element for (const parent of parentElements) { if (scrapedData.length >= limit) break; const record = {}; - // For each field, select the corresponding element within the parent + // Process each field using shadow DOM querying for (const [label, { selector, attribute }] of Object.entries(fields)) { - const fieldElement = parent.querySelector(selector); + // Use relative selector from parent + const relativeSelector = selector.split('>>').slice(-1)[0]; + const fieldElement = queryShadowDOM(parent, relativeSelector); if (fieldElement) { - if (attribute === 'innerText') { - record[label] = fieldElement.innerText.trim(); - } else if (attribute === 'innerHTML') { - record[label] = fieldElement.innerHTML.trim(); - } else if (attribute === 'src') { - // Handle relative 'src' URLs - const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, window.location.origin).href : null; - } else if (attribute === 'href') { - // Handle relative 'href' URLs - const href = fieldElement.getAttribute('href'); - record[label] = href ? new URL(href, window.location.origin).href : null; - } else { - record[label] = fieldElement.getAttribute(attribute); + switch (attribute) { + case 'innerText': + record[label] = fieldElement.innerText?.trim() || ''; + break; + case 'innerHTML': + record[label] = fieldElement.innerHTML?.trim() || ''; + break; + case 'src': + const src = fieldElement.getAttribute('src'); + record[label] = src ? new URL(src, window.location.origin).href : null; + break; + case 'href': + const href = fieldElement.getAttribute('href'); + record[label] = href ? new URL(href, window.location.origin).href : null; + break; + default: + record[label] = fieldElement.getAttribute(attribute); } } } - scrapedData.push(record); + + if (Object.keys(record).length > 0) { + scrapedData.push(record); + } } - // If we've processed all available elements and still haven't reached the limit, - // break to avoid infinite loop if (parentElements.length === 0 || scrapedData.length >= parentElements.length) { break; } } + return scrapedData; }; From 24915a93410aa1f309f27a7e18dd1bb0729f1b6f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 22:55:33 +0530 Subject: [PATCH 432/500] feat: get notify from global info store --- package.json | 1 - src/components/molecules/ScheduleSettings.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e89f13de..fc5e9edb 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "jwt-decode": "^4.0.0", "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", diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 3af0072f..ea78720c 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -79,7 +79,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia 'SUNDAY' ]; - const { recordingId } = useGlobalInfoStore(); + const { recordingId, notify } = useGlobalInfoStore(); const deleteRobotSchedule = () => { if (recordingId) { From 8c2b92483b1a86996d970293f2a167ea05157af6 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 22:57:23 +0530 Subject: [PATCH 433/500] feat: notify on schedule delete --- src/components/molecules/ScheduleSettings.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index ea78720c..917696c9 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -85,6 +85,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia if (recordingId) { deleteSchedule(recordingId); setSchedule(null); + notify('success', t('Schedule deleted successfully')); } else { console.error('No recording id provided'); } From e61798855564427223f4c0177f45fedcbf854814 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:02:17 +0530 Subject: [PATCH 434/500] fix: revert local maxun-core changes --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fc5e9edb..e89f13de 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "jwt-decode": "^4.0.0", "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 9b2ea1f5353466f63a92fefc8921bc1271619339 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:29:06 +0530 Subject: [PATCH 435/500] chore: cleanup space --- src/components/molecules/RecordingsTable.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 01bc524b..ecda3c8d 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -33,10 +33,6 @@ interface Column { format?: (value: string) => string; } - - - - interface Data { id: string; name: string; From cc6cc8ff8d03b17e942335bcd3770ad6d12f49aa Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:30:00 +0530 Subject: [PATCH 436/500] fix: format --- src/components/molecules/RecordingsTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index ecda3c8d..f8a0ba37 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -437,7 +437,6 @@ const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsBut {t('recordingtable.duplicate')} -
); From 6d2507982077daceebcba4eba02d44c0c28bab58 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:30:46 +0530 Subject: [PATCH 437/500] fix: format --- src/api/storage.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index 4b2f4e80..9ae3bc47 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -5,11 +5,6 @@ import { ScheduleSettings } from "../components/molecules/ScheduleSettings"; import { CreateRunResponse, ScheduleRunResponse } from "../pages/MainPage"; import { apiUrl } from "../apiConfig"; - - - - - export const getStoredRecordings = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/recordings`); From 3b9e30ddae52efa4e450a948c393132dee883b67 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:31:21 +0530 Subject: [PATCH 438/500] fix: format --- src/api/storage.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index 9ae3bc47..18c793c0 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -77,11 +77,7 @@ export const getStoredRecording = async (id: string) => { } } - - export const checkRunsForRecording = async (id: string): Promise => { - - try { const response = await axios.get(`${apiUrl}/storage/recordings/${id}/runs`); @@ -94,32 +90,26 @@ export const checkRunsForRecording = async (id: string): Promise => { } }; - export const deleteRecordingFromStorage = async (id: string): Promise => { - const hasRuns = await checkRunsForRecording(id); - + if (hasRuns) { - + return false; } try { const response = await axios.delete(`${apiUrl}/storage/recordings/${id}`); if (response.status === 200) { - + return true; } else { throw new Error(`Couldn't delete stored recording ${id}`); } } catch (error: any) { console.log(error); - + return false; } - - - - }; export const deleteRunFromStorage = async (id: string): Promise => { @@ -154,7 +144,7 @@ export const createRunForStoredRecording = async (id: string, settings: RunSetti try { const response = await axios.put( `${apiUrl}/storage/runs/${id}`, - { ...settings }); + { ...settings }); if (response.status === 200) { return response.data; } else { From 52aefd1c0f674cb0cac87e25124dd8c56027712b Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:31:37 +0530 Subject: [PATCH 439/500] fix: format --- src/api/workflow.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/api/workflow.ts b/src/api/workflow.ts index 03b677b1..40ac0d99 100644 --- a/src/api/workflow.ts +++ b/src/api/workflow.ts @@ -3,7 +3,7 @@ import { emptyWorkflow } from "../shared/constants"; import { default as axios, AxiosResponse } from "axios"; import { apiUrl } from "../apiConfig"; -export const getActiveWorkflow = async(id: string) : Promise => { +export const getActiveWorkflow = async (id: string): Promise => { try { const response = await axios.get(`${apiUrl}/workflow/${id}`) if (response.status === 200) { @@ -11,13 +11,13 @@ export const getActiveWorkflow = async(id: string) : Promise => { } else { throw new Error('Something went wrong when fetching a recorded workflow'); } - } catch(error: any) { + } catch (error: any) { console.log(error); return emptyWorkflow; } }; -export const getParamsOfActiveWorkflow = async(id: string) : Promise => { +export const getParamsOfActiveWorkflow = async (id: string): Promise => { try { const response = await axios.get(`${apiUrl}/workflow/params/${id}`) if (response.status === 200) { @@ -25,15 +25,15 @@ export const getParamsOfActiveWorkflow = async(id: string) : Promise => { +export const deletePair = async (index: number): Promise => { try { - const response = await axios.delete(`${apiUrl}/workflow/pair/${index}`); + const response = await axios.delete(`${apiUrl}/workflow/pair/${index}`); if (response.status === 200) { return response.data; } else { @@ -45,11 +45,11 @@ export const deletePair = async(index: number): Promise => { } }; -export const AddPair = async(index: number, pair: WhereWhatPair): Promise => { +export const AddPair = async (index: number, pair: WhereWhatPair): Promise => { try { const response = await axios.post(`${apiUrl}/workflow/pair/${index}`, { pair, - }, {headers: {'Content-Type': 'application/json'}}); + }, { headers: { 'Content-Type': 'application/json' } }); if (response.status === 200) { return response.data; } else { @@ -61,11 +61,11 @@ export const AddPair = async(index: number, pair: WhereWhatPair): Promise => { +export const UpdatePair = async (index: number, pair: WhereWhatPair): Promise => { try { const response = await axios.put(`${apiUrl}/workflow/pair/${index}`, { pair, - }, {headers: {'Content-Type': 'application/json'}}); + }, { headers: { 'Content-Type': 'application/json' } }); if (response.status === 200) { return response.data; } else { From 735b33b84727439c4c354021a06016166661902c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:53:58 +0530 Subject: [PATCH 440/500] fix: typo --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 142d45ab..8577f30e 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -318,7 +318,7 @@ export const NavBar: React.FC = ({ { window.open('https://x.com/maxun_io?ref=app', '_blank'); }}> - Twiiter (X) + Twiter (X) {t('navbar.menu_items.language')} From 22a99ff8b56788635581517bc863a3115b6b79db Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 1 Jan 2025 23:54:28 +0530 Subject: [PATCH 441/500] fix: twitter typo --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 8577f30e..8aeeb05d 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -318,7 +318,7 @@ export const NavBar: React.FC = ({ { window.open('https://x.com/maxun_io?ref=app', '_blank'); }}> - Twiter (X) + Twitter (X) {t('navbar.menu_items.language')} From c287340f845e9429fc5534dc38af6257b4d75826 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 2 Jan 2025 14:17:19 +0530 Subject: [PATCH 442/500] feat: shadowDOM support for table and non table list scraping --- maxun-core/src/browserSide/scraper.js | 347 +++++++++++++++++++++----- 1 file changed, 281 insertions(+), 66 deletions(-) diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index caa783c8..ff5a1938 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -347,33 +347,29 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, * @returns {Array.>} Array of arrays of scraped items, one sub-array per list */ window.scrapeList = async function ({ listSelector, fields, limit = 10 }) { - const scrapedData = []; - - // Helper function to query through Shadow DOM + // Shadow DOM query functions remain unchanged const queryShadowDOM = (rootElement, selector) => { - // Split the selector by Shadow DOM delimiter + if (!selector.includes('>>')) { + return rootElement.querySelector(selector); + } + const parts = selector.split('>>').map(part => part.trim()); let currentElement = rootElement; - // Traverse through each part of the selector for (let i = 0; i < parts.length; i++) { if (!currentElement) return null; - // If we're at the document level (first part) if (!currentElement.querySelector && !currentElement.shadowRoot) { currentElement = document.querySelector(parts[i]); continue; } - // Try to find element in regular DOM first let nextElement = currentElement.querySelector(parts[i]); - // If not found, check shadow DOM if (!nextElement && currentElement.shadowRoot) { nextElement = currentElement.shadowRoot.querySelector(parts[i]); } - // If still not found, try to find in shadow DOM of all child elements if (!nextElement) { const allChildren = Array.from(currentElement.children || []); for (const child of allChildren) { @@ -390,8 +386,11 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, return currentElement; }; - // Helper function to query all elements through Shadow DOM const queryShadowDOMAll = (rootElement, selector) => { + if (!selector.includes('>>')) { + return rootElement.querySelectorAll(selector); + } + const parts = selector.split('>>').map(part => part.trim()); let currentElements = [rootElement]; @@ -399,17 +398,14 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, const nextElements = []; for (const element of currentElements) { - // Check regular DOM if (element.querySelectorAll) { nextElements.push(...element.querySelectorAll(part)); } - // Check shadow DOM if (element.shadowRoot) { nextElements.push(...element.shadowRoot.querySelectorAll(part)); } - // Check shadow DOM of children const children = Array.from(element.children || []); for (const child of children) { if (child.shadowRoot) { @@ -424,76 +420,295 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, return currentElements; }; - while (scrapedData.length < limit) { - // Use our shadow DOM query function to get parent elements - let parentElements = queryShadowDOMAll(document, listSelector); - parentElements = Array.from(parentElements); + // Enhanced table processing helper functions with shadow DOM support + function extractValue(element, attribute) { + if (!element) return null; + + // Check for shadow root first + if (element.shadowRoot) { + const shadowContent = element.shadowRoot.textContent; + if (shadowContent && shadowContent.trim()) { + return shadowContent.trim(); + } + } + + if (attribute === 'innerText') { + return element.innerText.trim(); + } else if (attribute === 'innerHTML') { + return element.innerHTML.trim(); + } else if (attribute === 'src' || attribute === 'href') { + const attrValue = element.getAttribute(attribute); + return attrValue ? new URL(attrValue, window.location.origin).href : null; + } + return element.getAttribute(attribute); + } - // Handle the case when we don't find enough elements - if (limit > 1 && parentElements.length <= 1) { - const [containerSelector, ...rest] = listSelector.split('>>').map(s => s.trim()); - const container = queryShadowDOM(document, containerSelector); + function findTableAncestor(element) { + let currentElement = element; + const MAX_DEPTH = 5; + let depth = 0; + + while (currentElement && depth < MAX_DEPTH) { + // Check if current element is in shadow DOM + if (currentElement.getRootNode() instanceof ShadowRoot) { + currentElement = currentElement.getRootNode().host; + continue; + } - if (container) { - const allChildren = Array.from(container.children || []); - const firstMatch = queryShadowDOM(document, listSelector); - - if (firstMatch) { - const firstMatchClasses = Array.from(firstMatch.classList || []); + if (currentElement.tagName === 'TD') { + return { type: 'TD', element: currentElement }; + } else if (currentElement.tagName === 'TR') { + return { type: 'TR', element: currentElement }; + } + currentElement = currentElement.parentElement; + depth++; + } + return null; + } + + function getCellIndex(td) { + let index = 0; + let sibling = td; + + // Handle shadow DOM case + if (td.getRootNode() instanceof ShadowRoot) { + const shadowRoot = td.getRootNode(); + const allCells = Array.from(shadowRoot.querySelectorAll('td')); + return allCells.indexOf(td); + } + + while (sibling = sibling.previousElementSibling) { + index++; + } + return index; + } + + function hasThElement(row, tableFields) { + for (const [label, { selector }] of Object.entries(tableFields)) { + const element = queryShadowDOM(row, selector); + if (element) { + let current = element; + while (current && current !== row) { + // Check if we're in shadow DOM + if (current.getRootNode() instanceof ShadowRoot) { + current = current.getRootNode().host; + continue; + } - parentElements = allChildren.filter(element => { - const elementClasses = Array.from(element.classList || []); - const commonClasses = firstMatchClasses.filter(cls => - elementClasses.includes(cls)); - return commonClasses.length >= Math.floor(firstMatchClasses.length * 0.7); - }); + if (current.tagName === 'TH') { + return true; + } + current = current.parentElement; } } } + return false; + } - // Process each parent element - for (const parent of parentElements) { - if (scrapedData.length >= limit) break; - const record = {}; + function filterRowsBasedOnTag(rows, tableFields) { + for (const row of rows) { + if (hasThElement(row, tableFields)) { + return rows; + } + } + // Include shadow DOM in TH search + return rows.filter(row => { + const directTH = row.getElementsByTagName('TH').length === 0; + const shadowTH = row.shadowRoot ? + row.shadowRoot.querySelector('th') === null : true; + return directTH && shadowTH; + }); + } - // Process each field using shadow DOM querying - for (const [label, { selector, attribute }] of Object.entries(fields)) { - // Use relative selector from parent - const relativeSelector = selector.split('>>').slice(-1)[0]; - const fieldElement = queryShadowDOM(parent, relativeSelector); + // Class similarity functions remain unchanged + function calculateClassSimilarity(classList1, classList2) { + const set1 = new Set(classList1); + const set2 = new Set(classList2); + const intersection = new Set([...set1].filter(x => set2.has(x))); + const union = new Set([...set1, ...set2]); + return intersection.size / union.size; + } - if (fieldElement) { - switch (attribute) { - case 'innerText': - record[label] = fieldElement.innerText?.trim() || ''; - break; - case 'innerHTML': - record[label] = fieldElement.innerHTML?.trim() || ''; - break; - case 'src': - const src = fieldElement.getAttribute('src'); - record[label] = src ? new URL(src, window.location.origin).href : null; - break; - case 'href': - const href = fieldElement.getAttribute('href'); - record[label] = href ? new URL(href, window.location.origin).href : null; - break; - default: - record[label] = fieldElement.getAttribute(attribute); + function findSimilarElements(baseElement, similarityThreshold = 0.7) { + const baseClasses = Array.from(baseElement.classList); + if (baseClasses.length === 0) return []; + const potentialElements = document.getElementsByTagName(baseElement.tagName); + return Array.from(potentialElements).filter(element => { + if (element === baseElement) return false; + const similarity = calculateClassSimilarity( + baseClasses, + Array.from(element.classList) + ); + return similarity >= similarityThreshold; + }); + } + + // Main scraping logic with shadow DOM support + let containers = queryShadowDOMAll(document, listSelector); + containers = Array.from(containers); + + if (containers.length === 0) return []; + + if (limit > 1 && containers.length === 1) { + const baseContainer = containers[0]; + const similarContainers = findSimilarElements(baseContainer); + + if (similarContainers.length > 0) { + const newContainers = similarContainers.filter(container => + !container.matches(listSelector) + ); + containers = [...containers, ...newContainers]; + } + } + + const containerFields = containers.map(() => ({ + tableFields: {}, + nonTableFields: {} + })); + + // Classify fields + containers.forEach((container, containerIndex) => { + for (const [label, field] of Object.entries(fields)) { + const sampleElement = queryShadowDOM(container, field.selector); + + if (sampleElement) { + const ancestor = findTableAncestor(sampleElement); + if (ancestor) { + containerFields[containerIndex].tableFields[label] = { + ...field, + tableContext: ancestor.type, + cellIndex: ancestor.type === 'TD' ? getCellIndex(ancestor.element) : -1 + }; + } else { + containerFields[containerIndex].nonTableFields[label] = field; + } + } else { + containerFields[containerIndex].nonTableFields[label] = field; + } + } + }); + + const tableData = []; + const nonTableData = []; + + // Process table data with shadow DOM support + for (let containerIndex = 0; containerIndex < containers.length; containerIndex++) { + const container = containers[containerIndex]; + const { tableFields } = containerFields[containerIndex]; + + if (Object.keys(tableFields).length > 0) { + const firstField = Object.values(tableFields)[0]; + const firstElement = queryShadowDOM(container, firstField.selector); + let tableContext = firstElement; + + // Find table context including shadow DOM + while (tableContext && tableContext.tagName !== 'TABLE' && tableContext !== container) { + if (tableContext.getRootNode() instanceof ShadowRoot) { + tableContext = tableContext.getRootNode().host; + } else { + tableContext = tableContext.parentElement; + } + } + + if (tableContext) { + // Get rows from both regular DOM and shadow DOM + const rows = []; + if (tableContext.shadowRoot) { + rows.push(...tableContext.shadowRoot.getElementsByTagName('TR')); + } + rows.push(...tableContext.getElementsByTagName('TR')); + + const processedRows = filterRowsBasedOnTag(rows, tableFields); + + for (let rowIndex = 0; rowIndex < Math.min(processedRows.length, limit); rowIndex++) { + const record = {}; + const currentRow = processedRows[rowIndex]; + + for (const [label, { selector, attribute, cellIndex }] of Object.entries(tableFields)) { + let element = null; + + if (cellIndex >= 0) { + let td = currentRow.children[cellIndex]; + + // Check shadow DOM for td + if (!td && currentRow.shadowRoot) { + const shadowCells = currentRow.shadowRoot.children; + if (shadowCells && shadowCells.length > cellIndex) { + td = shadowCells[cellIndex]; + } + } + + if (td) { + element = queryShadowDOM(td, selector); + + if (!element && selector.split(">").pop().includes('td:nth-child')) { + element = td; + } + + if (!element) { + const tagOnlySelector = selector.split('.')[0]; + element = queryShadowDOM(td, tagOnlySelector); + } + + if (!element) { + let currentElement = td; + while (currentElement && currentElement.children.length > 0) { + let foundContentChild = false; + for (const child of currentElement.children) { + if (extractValue(child, attribute)) { + currentElement = child; + foundContentChild = true; + break; + } + } + if (!foundContentChild) break; + } + element = currentElement; + } + } + } else { + element = queryShadowDOM(currentRow, selector); + } + + if (element) { + record[label] = extractValue(element, attribute); + } + } + + if (Object.keys(record).length > 0) { + tableData.push(record); } } } - - if (Object.keys(record).length > 0) { - scrapedData.push(record); - } } + } - if (parentElements.length === 0 || scrapedData.length >= parentElements.length) { - break; + // Non-table data scraping remains unchanged + for (let containerIndex = 0; containerIndex < containers.length; containerIndex++) { + if (nonTableData.length >= limit) break; + + const container = containers[containerIndex]; + const { nonTableFields } = containerFields[containerIndex]; + + if (Object.keys(nonTableFields).length > 0) { + const record = {}; + + for (const [label, { selector, attribute }] of Object.entries(nonTableFields)) { + const relativeSelector = selector.split('>>').slice(-1)[0]; + const element = queryShadowDOM(container, relativeSelector); + + if (element) { + record[label] = extractValue(element, attribute); + } + } + + if (Object.keys(record).length > 0) { + nonTableData.push(record); + } } } + const scrapedData = [...tableData, ...nonTableData]; return scrapedData; }; From ec0bc75097c287a9ffce1b0fcc47600a96f781c8 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Thu, 2 Jan 2025 18:12:10 +0530 Subject: [PATCH 443/500] docs: update website to maxun.dev --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cebcedd2..47e170b5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web

- Website | + Website | Discord | Twitter | Join Maxun Cloud | From b6faf5cf17736dcc99ffa0b146031f23ccc55f80 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Thu, 2 Jan 2025 19:35:03 +0530 Subject: [PATCH 444/500] feat: add iframeSelector generation logic for capture text --- server/src/workflow-management/selector.ts | 471 +++++++++++---------- 1 file changed, 242 insertions(+), 229 deletions(-) diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index dd869f3d..6ed6a997 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -23,247 +23,110 @@ export const getElementInformation = async ( if (!getList || listSelector !== '') { const elementInfo = await page.evaluate( async ({ x, y }) => { - // Helper function to get element info - const getElementInfo = (element: HTMLElement) => { - let info: { - tagName: string; - hasOnlyText?: boolean; - innerText?: string; - url?: string; - imageUrl?: string; - attributes?: Record; - innerHTML?: string; - outerHTML?: string; - fromIframe?: boolean; - iframePath?: string[]; - } = { - tagName: element?.tagName ?? '', - }; + // Helper function to find elements within iframes, handling nested cases + const getElementFromIframePoint = ( + x: number, + y: number, + context: Document = document, + iframePath: string[] = [] + ): { element: HTMLElement | null; iframePath: string[] } => { + // First try to get element at the given coordinates + let element = context.elementFromPoint(x, y) as HTMLElement; + if (!element) return { element: null, iframePath }; - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - } - - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else if (element?.tagName === 'SELECT') { - const selectElement = element as HTMLSelectElement; - info.innerText = selectElement.options[selectElement.selectedIndex]?.text ?? ''; - info.attributes = { - ...info.attributes, - selectedValue: selectElement.value, - }; - } else if (element?.tagName === 'INPUT' && - ((element as HTMLInputElement).type === 'time' || - (element as HTMLInputElement).type === 'date')) { - info.innerText = (element as HTMLInputElement).value; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; - return info; - }; - - // Helper function to search in iframe - const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, - relativeY: number, - iframePath: string[] - ) => { - try { - if (!iframe.contentDocument) return null; - - const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; - if (!el) return null; - - const { parentElement } = el; - const element = parentElement?.tagName === 'A' ? parentElement : el; - - const info = getElementInfo(element); - info.fromIframe = true; - info.iframePath = iframePath; - - return info; - } catch (e) { - console.warn('Cannot access iframe content:', e); - return null; - } - }; - - const el = document.elementFromPoint(x, y) as HTMLElement; - if (el) { - // Check if the element is an iframe - if (el.tagName === 'IFRAME') { - const iframe = el as HTMLIFrameElement; - const rect = iframe.getBoundingClientRect(); - const relativeX = x - rect.left; - const relativeY = y - rect.top; - - const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, - [iframe.id || 'unnamed-iframe'] - ); - if (iframeResult) return iframeResult; - } - - const { parentElement } = el; - const element = parentElement?.tagName === 'A' ? parentElement : el; - return getElementInfo(element); - } - return null; - }, - { x: coordinates.x, y: coordinates.y } - ); - return elementInfo; - } else { - const elementInfo = await page.evaluate( - async ({ x, y }) => { - // Helper function to get element info (same as above) - const getElementInfo = (element: HTMLElement) => { - let info: { - tagName: string; - hasOnlyText?: boolean; - innerText?: string; - url?: string; - imageUrl?: string; - attributes?: Record; - innerHTML?: string; - outerHTML?: string; - fromIframe?: boolean; - iframePath?: string[]; - } = { - tagName: element?.tagName ?? '', - }; - - if (element) { - info.attributes = Array.from(element.attributes).reduce( - (acc, attr) => { - acc[attr.name] = attr.value; - return acc; - }, - {} as Record - ); - } - - if (element?.tagName === 'A') { - info.url = (element as HTMLAnchorElement).href; - info.innerText = element.innerText ?? ''; - } else if (element?.tagName === 'IMG') { - info.imageUrl = (element as HTMLImageElement).src; - } else { - info.hasOnlyText = element?.children?.length === 0 && - element?.innerText?.length > 0; - info.innerText = element?.innerText ?? ''; - } - - info.innerHTML = element.innerHTML; - info.outerHTML = element.outerHTML; - return info; - }; - - // Helper function to search in iframe (same as above) - const searchInIframe = ( - iframe: HTMLIFrameElement, - relativeX: number, - relativeY: number, - iframePath: string[] - ) => { - try { - if (!iframe.contentDocument) return null; - - const el = iframe.contentDocument.elementFromPoint(relativeX, relativeY) as HTMLElement; - if (!el) return null; - - let element = el; - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); - - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; - - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; - - if (fullyContained && significantOverlap) { - element = element.parentElement; - } else { - break; + // Check if we found an iframe + if (element.tagName === 'IFRAME') { + const iframe = element as HTMLIFrameElement; + try { + // Make sure we can access the iframe's content + if (!iframe.contentDocument) { + return { element, iframePath }; } + + // Transform coordinates to iframe's space + const rect = iframe.getBoundingClientRect(); + const relativeX = x - rect.left; + const relativeY = y - rect.top; + + // Add this iframe to the path + const updatedPath = [...iframePath, iframe.id || 'unnamed-iframe']; + + // Recursively search within the iframe + const iframeResult = getElementFromIframePoint( + relativeX, + relativeY, + iframe.contentDocument, + updatedPath + ); + + // If we found an element in the iframe, return it + if (iframeResult.element) { + return iframeResult; + } + } catch (e) { + console.warn('Cannot access iframe content:', e); } - - const info = getElementInfo(element); - info.fromIframe = true; - info.iframePath = iframePath; - - return info; - } catch (e) { - console.warn('Cannot access iframe content:', e); - return null; } + + // Return the element we found (either in main document or iframe) + return { element, iframePath }; }; - const originalEl = document.elementFromPoint(x, y) as HTMLElement; - if (originalEl) { - // Check if the element is an iframe - if (originalEl.tagName === 'IFRAME') { - const iframe = originalEl as HTMLIFrameElement; - const rect = iframe.getBoundingClientRect(); - const relativeX = x - rect.left; - const relativeY = y - rect.top; + // Get the element and its iframe path + const { element: el, iframePath } = getElementFromIframePoint(x, y); + + if (el) { + // Handle potential anchor parent + const { parentElement } = el; + const targetElement = parentElement?.tagName === 'A' ? parentElement : el; - const iframeResult = searchInIframe( - iframe, - relativeX, - relativeY, - [iframe.id || 'unnamed-iframe'] + // Build the element information object + let info: { + tagName: string; + hasOnlyText?: boolean; + innerText?: string; + url?: string; + imageUrl?: string; + attributes?: Record; + innerHTML?: string; + outerHTML?: string; + fromIframe?: boolean; + iframePath?: string[]; + } = { + tagName: targetElement?.tagName ?? '', + fromIframe: iframePath.length > 0, + iframePath: iframePath.length > 0 ? iframePath : undefined + }; + + // Collect element attributes and properties + if (targetElement) { + // Get all attributes + info.attributes = Array.from(targetElement.attributes).reduce( + (acc, attr) => { + acc[attr.name] = attr.value; + return acc; + }, + {} as Record ); - if (iframeResult) return iframeResult; - } - let element = originalEl; - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); - - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; - - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; - - if (fullyContained && significantOverlap) { - element = element.parentElement; + // Handle specific element types + if (targetElement.tagName === 'A') { + info.url = (targetElement as HTMLAnchorElement).href; + info.innerText = targetElement.textContent ?? ''; + } else if (targetElement.tagName === 'IMG') { + info.imageUrl = (targetElement as HTMLImageElement).src; } else { - break; + info.hasOnlyText = targetElement.children.length === 0 && + (targetElement.textContent !== null && + targetElement.textContent.trim().length > 0); + info.innerText = targetElement.textContent ?? ''; } + + info.innerHTML = targetElement.innerHTML; + info.outerHTML = targetElement.outerHTML; } - return getElementInfo(element); + return info; } return null; }, @@ -271,6 +134,7 @@ export const getElementInformation = async ( ); return elementInfo; } + // ... rest of the code remains same } catch (error) { const { message, stack } = error as Error; console.error('Error while retrieving selector:', message); @@ -984,6 +848,148 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { } return output; } + + const getIframeOffset = (iframe: HTMLIFrameElement): { x: number; y: number } => { + const rect = iframe.getBoundingClientRect(); + return { + x: rect.left, + y: rect.top + }; + }; + + const isAccessibleIframe = (iframe: HTMLIFrameElement): boolean => { + try { + return !!iframe.contentDocument; + } catch (e) { + return false; + } + }; + + const getDeepestElementFromPoint = (x: number, y: number): HTMLElement | null => { + // Get the initial element at the specified coordinates + let currentElement = document.elementFromPoint(x, y) as HTMLElement; + if (!currentElement) return null; + + let deepestElement = currentElement; + let current = currentElement; + let currentX = x; + let currentY = y; + let depth = 0; + const MAX_DEPTH = 20; // Prevent infinite loops with deeply nested iframes + + // Continue traversing while we find nested iframes + while (current && depth < MAX_DEPTH) { + // Check if the current element is an iframe and if we can access it + if (current instanceof HTMLIFrameElement && isAccessibleIframe(current)) { + // Calculate the offset of the iframe + const iframeOffset = getIframeOffset(current); + + // Transform coordinates to be relative to the iframe's content window + const relativeX = currentX - iframeOffset.x; + const relativeY = currentY - iframeOffset.y; + + // Find the element at these coordinates within the iframe + const iframeElement = current.contentDocument?.elementFromPoint(relativeX, relativeY) as HTMLElement; + + // If we don't find an element or we get the same element, stop traversing + if (!iframeElement || iframeElement === current) break; + + // Update our tracking variables + deepestElement = iframeElement; + current = iframeElement; + currentX = relativeX; + currentY = relativeY; + depth++; + } else { + // If the current element is not an iframe, we're done traversing + break; + } + } + + return deepestElement; + }; + + interface IframeContext { + frame: HTMLIFrameElement; + document: Document; + element: HTMLElement; + } + + const genSelectorForIframe = (element: HTMLElement) => { + // Helper function to check if we can access an iframe's content + const isAccessibleIframe = (iframe: HTMLIFrameElement): boolean => { + try { + return !!iframe.contentDocument; + } catch (e) { + return false; + } + }; + + // Get complete path up through nested iframes to document root + const getIframePath = (el: HTMLElement) => { + const path: IframeContext[] = []; + let current = el; + let currentDoc = el.ownerDocument; + let depth = 0; + const MAX_DEPTH = 20; // Limit depth to prevent infinite loops + + while (current && depth < MAX_DEPTH) { + // If we're in an iframe, get its parent document + const frameElement = currentDoc.defaultView?.frameElement as HTMLIFrameElement; + if (frameElement && isAccessibleIframe(frameElement)) { + path.unshift({ + frame: frameElement, + document: currentDoc, + element: current + }); + current = frameElement; + currentDoc = frameElement.ownerDocument; + depth++; + } else { + break; + } + } + return path; + }; + + // Get the iframe path for our target element + const iframePath = getIframePath(element); + if (iframePath.length === 0) return null; + + try { + const selectorParts: string[] = []; + + // Generate selector for each iframe boundary + iframePath.forEach((context, index) => { + // Get selector for the iframe element in its parent document + const frameSelector = finder(context.frame, { + root: index === 0 ? document.body : (iframePath[index - 1].document.body as Element) + }); + + // For the last context, get selector for target element + if (index === iframePath.length - 1) { + const elementSelector = finder(element, { + root: context.document.body as Element + }); + // Use :>> for iframe traversal in the selector + selectorParts.push(`${frameSelector} :>> ${elementSelector}`); + } else { + selectorParts.push(frameSelector); + } + }); + + return { + // Join all parts with :>> to indicate iframe traversal + fullSelector: selectorParts.join(' :>> '), + // Include additional metadata about the frames if needed + frameCount: iframePath.length, + isAccessible: true + }; + } catch (e) { + console.warn('Error generating iframe selector:', e); + return null; + } + }; const genSelectors = (element: HTMLElement | null) => { if (element == null) { @@ -1004,6 +1010,8 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { } catch (e) { } + const iframeSelector = genSelectorForIframe(element); + const hrefSelector = genSelectorForAttributes(element, ['href']); const formSelector = genSelectorForAttributes(element, [ 'name', @@ -1050,6 +1058,11 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { hrefSelector, accessibilitySelector, formSelector, + iframeSelector: iframeSelector ? { + full: iframeSelector.fullSelector, + frame: iframeSelector.frameCount, + accesible: iframeSelector.isAccessible + } : null }; } @@ -1092,7 +1105,7 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { return char.length === 1 && char.match(/[0-9]/); } - const hoveredElement = document.elementFromPoint(x, y) as HTMLElement; + const hoveredElement = getDeepestElementFromPoint(x, y) as HTMLElement; if ( hoveredElement != null && !hoveredElement.closest('#overlay-controls') != null From 8323593bb09d0b9a869afb825aa58be3944199b9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 2 Jan 2025 21:18:49 +0530 Subject: [PATCH 445/500] chore: 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 442b7e50..2a5f7758 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -9,7 +9,6 @@ import { useBrowserSteps, TextStep } from '../../context/browserSteps'; import { useGlobalInfoStore } from '../../context/globalInfo'; import { useTranslation } from 'react-i18next'; - interface ElementInfo { tagName: string; hasOnlyText?: boolean; From e91a3916a0513af15d306adc71fdd68bb9250e7d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 2 Jan 2025 21:19:36 +0530 Subject: [PATCH 446/500] chore: cleanup console logs --- src/components/organisms/BrowserWindow.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 2f66e906..ad58a309 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -119,9 +119,6 @@ export const BrowserWindow = () => { const highlighterHandler = useCallback((data: { rect: DOMRect, selector: string, elementInfo: ElementInfo | null, childSelectors?: string[] }) => { if (getList === true) { if (listSelector) { - console.log("LIST SELEECTORRRRR: ", listSelector); - console.log("DATA SELEECTORRRRR: ", data.selector); - console.log("CHILDREEENN SELECORRRR: ", data.childSelectors); socket?.emit('listSelector', { selector: listSelector }); const hasValidChildSelectors = Array.isArray(data.childSelectors) && data.childSelectors.length > 0; From edfcd8f869f194f7525744d050e30ba81a8bafef Mon Sep 17 00:00:00 2001 From: amhsirak Date: Thu, 2 Jan 2025 23:15:03 +0530 Subject: [PATCH 447/500] 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 af237ba1b0d9973e94f3595fdcc8610c8eac03d5 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 3 Jan 2025 20:06:13 +0530 Subject: [PATCH 448/500] fix: update custom limit if value >= 1 --- src/components/organisms/RightSidePanel.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 8211a64a..d4670d4f 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -529,7 +529,22 @@ export const RightSidePanel: React.FC = ({ onFinishCapture updateCustomLimit(e.target.value)} + onChange={(e: React.ChangeEvent) => { + const value = parseInt(e.target.value); + // Only update if the value is greater than or equal to 1 or if the field is empty + if (e.target.value === '' || value >= 1) { + updateCustomLimit(e.target.value); + } + }} + inputProps={{ + min: 1, + onKeyPress: (e: React.KeyboardEvent) => { + const value = (e.target as HTMLInputElement).value + e.key; + if (parseInt(value) < 1) { + e.preventDefault(); + } + } + }} placeholder={t('right_panel.limit.enter_number')} sx={{ marginLeft: '10px', From 35a44bb39fcff9381417918dd37fc43d43ee3519 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 3 Jan 2025 20:18:03 +0530 Subject: [PATCH 449/500] fix: rm translation for alt maxun_logo --- src/components/molecules/ActionDescriptionBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/ActionDescriptionBox.tsx b/src/components/molecules/ActionDescriptionBox.tsx index 190c5838..45ec1641 100644 --- a/src/components/molecules/ActionDescriptionBox.tsx +++ b/src/components/molecules/ActionDescriptionBox.tsx @@ -113,7 +113,7 @@ const ActionDescriptionBox = () => { return ( - + {renderActionDescription()} From 1874e71e0f5ec86a1ba2cad4891dfdce8f1f19b7 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 3 Jan 2025 20:22:30 +0530 Subject: [PATCH 450/500] fix: add translation for same name robot creation --- src/components/molecules/SaveRecording.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx index 8e1eb462..cc51f238 100644 --- a/src/components/molecules/SaveRecording.tsx +++ b/src/components/molecules/SaveRecording.tsx @@ -101,7 +101,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => { - {t('save_recording.warnings.robot_exists')} + {t('save_recording.errors.exists_warning')} ) :