diff --git a/README.md b/README.md index b63cf8a7..47e170b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- +
Maxun @@ -15,11 +15,11 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web

- Website | + Website | Discord | - Twitter | + Twitter | Join Maxun Cloud | - Watch Tutorials + Watch Tutorials

getmaxun%2Fmaxun | Trendshift diff --git a/maxun-core/src/browserSide/scraper.js b/maxun-core/src/browserSide/scraper.js index ff5a1938..d6489e6b 100644 --- a/maxun-core/src/browserSide/scraper.js +++ b/maxun-core/src/browserSide/scraper.js @@ -705,14 +705,14 @@ function scrapableHeuristics(maxCountPerPage = 50, minArea = 20000, scrolls = 3, if (Object.keys(record).length > 0) { nonTableData.push(record); } - } + } } - + + // Merge and limit the results const scrapedData = [...tableData, ...nonTableData]; return scrapedData; }; - /** * Gets all children of the elements matching the listSelector, * returning their CSS selectors and innerText. diff --git a/public/locales/de.json b/public/locales/de.json index 90beaa14..b9b4185b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -1,7 +1,7 @@ { "login": { "title": "Willkommen zurück!", - "email": "E-Mail", + "email": "Geben Sie Ihre geschäftliche E-Mail-Adresse ein", "password": "Passwort", "button": "Einloggen", "loading": "Lädt", @@ -12,7 +12,7 @@ }, "register": { "title": "Konto registrieren", - "email": "E-Mail", + "email": "Geben Sie Ihre geschäftliche E-Mail-Adresse ein", "password": "Passwort", "button": "Registrieren", "loading": "Lädt", @@ -158,11 +158,13 @@ "confirm": "Bestätigen", "discard": "Verwerfen", "confirm_capture": "Erfassung bestätigen", - "confirm_pagination": "Paginierung bestätigen", - "confirm_limit": "Limit bestätigen", + "confirm_pagination": "Bestätigen", + "confirm_limit": "Bestätigen", "finish_capture": "Erfassung abschließen", + "back": "Zurück", "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 9dcad514..bd8acce3 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -1,7 +1,7 @@ { "login": { "title": "Welcome Back!", - "email": "Email", + "email": "Enter Work Email", "password": "Password", "button": "Login", "loading": "Loading", @@ -12,7 +12,7 @@ }, "register": { "title": "Register Account", - "email": "Email", + "email": "Enter Work Email", "password": "Password", "button": "Register", "loading": "Loading", @@ -159,11 +159,13 @@ "confirm": "Confirm", "discard": "Discard", "confirm_capture": "Confirm Capture", - "confirm_pagination": "Confirm Pagination", - "confirm_limit": "Confirm Limit", + "confirm_pagination": "Confirm", + "confirm_limit": "Confirm", "finish_capture": "Finish Capture", + "back": "Back", "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 00fa379e..94210880 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -1,7 +1,7 @@ { "login": { "title": "¡Bienvenido de nuevo!", - "email": "Correo electrónico", + "email": "Introducir correo electrónico de trabajo", "password": "Contraseña", "button": "Iniciar sesión", "loading": "Cargando", @@ -12,7 +12,7 @@ }, "register": { "title": "Crear cuenta", - "email": "Correo electrónico", + "email": "Introducir correo electrónico de trabajo", "password": "Contraseña", "button": "Registrarse", "loading": "Cargando", @@ -159,11 +159,13 @@ "confirm": "Confirmar", "discard": "Descartar", "confirm_capture": "Confirmar Captura", - "confirm_pagination": "Confirmar Paginación", - "confirm_limit": "Confirmar Límite", + "confirm_pagination": "Confirmar", + "confirm_limit": "Confirmar", "finish_capture": "Finalizar Captura", + "back": "Atrás", "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 b444c81a..0bcba967 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -1,7 +1,7 @@ { "login": { "title": "お帰りなさい!", - "email": "メールアドレス", + "email": "勤務先メールアドレスを入力", "password": "パスワード", "button": "ログイン", "loading": "読み込み中", @@ -12,7 +12,7 @@ }, "register": { "title": "アカウントを登録する", - "email": "メールアドレス", + "email": "勤務先メールアドレスを入力", "password": "パスワード", "button": "登録する", "loading": "読み込み中", @@ -159,11 +159,13 @@ "confirm": "確認", "discard": "破棄", "confirm_capture": "取得を確認", - "confirm_pagination": "ページネーションを確認", - "confirm_limit": "制限を確認", + "confirm_pagination": "確認", + "confirm_limit": "確認", "finish_capture": "取得を完了", + "back": "戻る", "finish": "完了", - "cancel": "キャンセル" + "cancel": "キャンセル", + "delete": "削除" }, "screenshot": { "capture_fullpage": "フルページを取得", diff --git a/public/locales/zh.json b/public/locales/zh.json index 27455ebe..a19fe439 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -1,7 +1,7 @@ { "login": { "title": "欢迎回来!", - "email": "电子邮箱", + "email": "输入工作电子邮箱", "password": "密码", "button": "登录", "loading": "加载中", @@ -12,7 +12,7 @@ }, "register": { "title": "注册账号", - "email": "电子邮箱", + "email": "输入工作电子邮箱", "password": "密码", "button": "注册", "loading": "加载中", @@ -159,11 +159,13 @@ "confirm": "确认", "discard": "放弃", "confirm_capture": "确认捕获", - "confirm_pagination": "确认分页", - "confirm_limit": "确认限制", + "confirm_pagination": "确认", + "confirm_limit": "确认", "finish_capture": "完成捕获", + "back": "返回", "finish": "完成", - "cancel": "取消" + "cancel": "取消", + "delete": "删除" }, "screenshot": { "capture_fullpage": "捕获整页", diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 713c05bc..82e50d9f 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -139,26 +139,45 @@ export const getElementInformation = async ( 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; + + if (element.tagName === 'TD' || element.tagName === 'TH') { + const tableParent = element.closest('table'); + if (tableParent) { + element = tableParent; + } + } + + if (element.tagName !== 'TABLE') { + while (element.parentElement) { + if (element.tagName.toLowerCase() === 'body' || + element.tagName.toLowerCase() === 'html') { + break; + } + + 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) { + const nextParent = element.parentElement; + if (nextParent.tagName.toLowerCase() !== 'body' && + nextParent.tagName.toLowerCase() !== 'html') { + element = nextParent; + } else { + break; + } + } else { + break; + } } } @@ -319,25 +338,44 @@ export const getRect = async (page: Page, coordinates: Coordinates, listSelector if (originalEl) { let element = originalEl; - // Handle element hierarchy traversal for list items - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); + if (element.tagName === 'TD' || element.tagName === 'TH') { + const tableParent = element.closest('table'); + if (tableParent) { + element = tableParent; + } + } - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; + if (element.tagName !== 'TABLE') { + while (element.parentElement) { + if (element.tagName.toLowerCase() === 'body' || + element.tagName.toLowerCase() === 'html') { + break; + } - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); - if (fullyContained && significantOverlap) { - element = element.parentElement; - } else { - 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) { + const nextParent = element.parentElement; + if (nextParent.tagName.toLowerCase() !== 'body' && + nextParent.tagName.toLowerCase() !== 'html') { + element = nextParent; + } else { + break; + } + } else { + break; + } } } @@ -1113,17 +1151,24 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates // 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 (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) { - selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } } } - + return selector; } @@ -1202,25 +1247,45 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates let element = originalEl; - // Handle parent traversal for better element targeting - while (element.parentElement) { - const parentRect = element.parentElement.getBoundingClientRect(); - const childRect = element.getBoundingClientRect(); + if (element.tagName === 'TD' || element.tagName === 'TH') { + const tableParent = element.closest('table'); + if (tableParent) { + element = tableParent; + } + } - const fullyContained = - parentRect.left <= childRect.left && - parentRect.right >= childRect.right && - parentRect.top <= childRect.top && - parentRect.bottom >= childRect.bottom; + // if (listSelector === '') { + if (element.tagName !== 'TABLE') { + while (element.parentElement) { + if (element.tagName.toLowerCase() === 'body' || + element.tagName.toLowerCase() === 'html') { + break; + } - const significantOverlap = - (childRect.width * childRect.height) / - (parentRect.width * parentRect.height) > 0.5; + const parentRect = element.parentElement.getBoundingClientRect(); + const childRect = element.getBoundingClientRect(); - if (fullyContained && significantOverlap) { - element = element.parentElement; - } else { - 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) { + const nextParent = element.parentElement; + if (nextParent.tagName.toLowerCase() !== 'body' && + nextParent.tagName.toLowerCase() !== 'html') { + element = nextParent; + } else { + break; + } + } else { + break; + } } } @@ -1260,17 +1325,23 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates // 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 (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) { - selector += '.' + classes.map(cls => CSS.escape(cls)).join('.'); + const validClasses = classes.filter((cls: string) => !cls.startsWith('!') && !cls.includes(':')); + if (validClasses.length > 0) { + selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.'); + } } } - + return selector; } @@ -1364,6 +1435,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)); diff --git a/src/api/storage.ts b/src/api/storage.ts index 4b2f4e80..18c793c0 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`); @@ -82,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`); @@ -99,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 => { @@ -159,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 { 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 { diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 142d45ab..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'); }}> - Twiiter (X) + Twitter (X) {t('navbar.menu_items.language')} diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 01bc524b..f8a0ba37 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; @@ -441,7 +437,6 @@ const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsBut {t('recordingtable.duplicate')} - ); diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 3af0072f..917696c9 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -79,12 +79,13 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia 'SUNDAY' ]; - const { recordingId } = useGlobalInfoStore(); + const { recordingId, notify } = useGlobalInfoStore(); const deleteRobotSchedule = () => { if (recordingId) { deleteSchedule(recordingId); setSchedule(null); + notify('success', t('Schedule deleted successfully')); } else { console.error('No recording id provided'); } diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index 37a72764..9d54fe5c 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)}...` : '**********'} + + diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index 442b7e50..dcc2cf0d 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -69,7 +69,7 @@ export const BrowserWindow = () => { const { socket } = useSocketStore(); const { notify } = useGlobalInfoStore(); - const { getText, getList, paginationMode, paginationType, limitMode } = useActionContext(); + const { getText, getList, paginationMode, paginationType, limitMode, captureStage } = useActionContext(); const { addTextStep, addListStep } = useBrowserSteps(); const onMouseMove = (e: MouseEvent) => { @@ -173,7 +173,7 @@ export const BrowserWindow = () => { // for non-list steps setHighlighterData(data); } - }, [highlighterData, getList, socket, listSelector, paginationMode, paginationType]); + }, [highlighterData, getList, socket, listSelector, paginationMode, paginationType, captureStage]); useEffect(() => { @@ -187,6 +187,13 @@ export const BrowserWindow = () => { }; }, [socket, onMouseMove]); + useEffect(() => { + if (captureStage === 'initial' && listSelector) { + socket?.emit('setGetList', { getList: true }); + socket?.emit('listSelector', { selector: listSelector }); + } + }, [captureStage, listSelector, socket]); + const handleClick = (e: React.MouseEvent) => { if (highlighterData && canvasRef?.current) { const canvasRect = canvasRef.current.getBoundingClientRect(); diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index 12f75028..8211a64a 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -56,6 +56,8 @@ 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 [isCaptureListConfirmed, setIsCaptureListConfirmed] = 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 +132,16 @@ export const RightSidePanel: React.FC = ({ onFinishCapture const handlePairDelete = () => { } + const handleStartGetText = () => { + setIsCaptureTextConfirmed(false); + startGetText(); + } + + const handleStartGetList = () => { + setIsCaptureListConfirmed(false); + startGetList(); + } + const handleTextLabelChange = (id: number, label: string, listId?: number, fieldKey?: string) => { if (listId !== undefined && fieldKey !== undefined) { // Prevent editing if the field is confirmed @@ -169,6 +181,22 @@ export const RightSidePanel: React.FC = ({ onFinishCapture }); }; + const handleTextStepDelete = (id: number) => { + deleteBrowserStep(id); + setTextLabels(prevLabels => { + const { [id]: _, ...rest } = prevLabels; + return rest; + }); + setConfirmedTextSteps(prev => { + const { [id]: _, ...rest } = prev; + return rest; + }); + setErrors(prevErrors => { + const { [id]: _, ...rest } = prevErrors; + return rest; + }); + }; + const handleListTextFieldConfirm = (listId: number, fieldKey: string) => { setConfirmedListTextFields(prev => ({ ...prev, @@ -195,6 +223,22 @@ export const RightSidePanel: React.FC = ({ onFinishCapture }); }; + const handleListTextFieldDelete = (listId: number, fieldKey: string) => { + removeListTextField(listId, fieldKey); + setConfirmedListTextFields(prev => { + const updatedListFields = { ...(prev[listId] || {}) }; + delete updatedListFields[fieldKey]; + return { + ...prev, + [listId]: updatedListFields + }; + }); + setErrors(prev => { + const { [fieldKey]: _, ...rest } = prev; + return rest; + }); + }; + const getTextSettingsObject = useCallback(() => { const settings: Record = {}; browserSteps.forEach(step => { @@ -224,6 +268,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]); @@ -326,6 +371,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } stopLimitMode(); setShowLimitOptions(false); + setIsCaptureListConfirmed(true); stopCaptureAndEmitGetListSettings(); setCaptureStage('complete'); break; @@ -336,6 +382,23 @@ export const RightSidePanel: React.FC = ({ onFinishCapture } }, [captureStage, paginationType, limitType, customLimit, startPaginationMode, stopPaginationMode, startLimitMode, stopLimitMode, notify, stopCaptureAndEmitGetListSettings, getListSettingsObject]); + const handleBackCaptureList = useCallback(() => { + switch (captureStage) { + case 'limit': + stopLimitMode(); + setShowLimitOptions(false); + startPaginationMode(); + setShowPaginationOptions(true); + setCaptureStage('pagination'); + break; + case 'pagination': + stopPaginationMode(); + setShowPaginationOptions(false); + setCaptureStage('initial'); + break; + } + }, [captureStage, stopLimitMode, startPaginationMode, stopPaginationMode]); + const handlePaginationSettingSelect = (option: PaginationType) => { updatePaginationType(option); }; @@ -350,6 +413,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setTextLabels({}); setErrors({}); setConfirmedTextSteps({}); + setIsCaptureTextConfirmed(false); notify('error', t('right_panel.errors.capture_text_discarded')); }, [browserSteps, stopGetText, deleteBrowserStep]); @@ -365,6 +429,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture setShowLimitOptions(false); setCaptureStage('initial'); setConfirmedListTextFields({}); + setIsCaptureListConfirmed(false); notify('error', t('right_panel.errors.capture_list_discarded')); }, [browserSteps, stopGetList, deleteBrowserStep, resetListState]); @@ -408,6 +473,14 @@ export const RightSidePanel: React.FC = ({ onFinishCapture {getList && ( <> + {(captureStage === 'pagination' || captureStage === 'limit') && ( + + )} - + )} @@ -470,7 +545,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} - {!getText && !getScreenshot && !getList && showCaptureText && } + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> @@ -526,11 +601,21 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} /> - {!confirmedTextSteps[step.id] && ( + {!confirmedTextSteps[step.id] ? ( + ) : !isCaptureTextConfirmed && ( + + + )} )} @@ -578,7 +663,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} /> - {!confirmedListTextFields[step.id]?.[key] && ( + {!confirmedListTextFields[step.id]?.[key] ? ( + ) : !isCaptureListConfirmed && ( + + + )} ))}