Merge pull request #772 from getmaxun/record-fix
feat: prevent robot saving without capture actions
This commit is contained in:
@@ -252,7 +252,15 @@
|
||||
"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",
|
||||
"label_required": "Beschriftung darf nicht leer sein"
|
||||
"label_required": "Beschriftung darf nicht leer sein",
|
||||
"duplicate_label": "Diese Beschriftung existiert bereits. Bitte verwenden Sie eine eindeutige Beschriftung.",
|
||||
"no_text_captured": "Bitte markieren und wählen Sie Textelemente aus, bevor Sie bestätigen.",
|
||||
"capture_list_first": "Bitte bewegen Sie die Maus über eine Liste und wählen Sie Textfelder darin aus",
|
||||
"confirm_all_list_fields": "Bitte bestätigen Sie alle erfassten Listenfelder, bevor Sie fortfahren"
|
||||
},
|
||||
"tooltips": {
|
||||
"capture_list_first": "Bewegen Sie die Maus über eine Liste und wählen Sie Textfelder darin aus",
|
||||
"confirm_all_list_fields": "Bitte bestätigen Sie alle erfassten Listenfelder, bevor Sie fortfahren"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +277,8 @@
|
||||
},
|
||||
"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."
|
||||
"exists_warning": "Ein Roboter mit diesem Namen existiert bereits, bitte bestätigen Sie das Überschreiben des Roboters.",
|
||||
"no_actions_performed": "Roboter kann nicht gespeichert werden. Bitte führen Sie mindestens eine Erfassungsaktion durch, bevor Sie speichern."
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "Workflow wird optimiert und gespeichert"
|
||||
|
||||
@@ -252,7 +252,15 @@
|
||||
"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",
|
||||
"label_required": "Label cannot be empty"
|
||||
"label_required": "Label cannot be empty",
|
||||
"duplicate_label": "This label already exists. Please use a unique label.",
|
||||
"no_text_captured": "Please highlight and select text elements before confirming.",
|
||||
"capture_list_first": "Please hover over a list and select text fields inside it first",
|
||||
"confirm_all_list_fields": "Please confirm all captured list fields before proceeding"
|
||||
},
|
||||
"tooltips": {
|
||||
"capture_list_first": "Hover over a list and select text fields inside it first",
|
||||
"confirm_all_list_fields": "Please confirm all captured list fields before proceeding"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +277,8 @@
|
||||
},
|
||||
"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."
|
||||
"exists_warning": "Robot with this name already exists, please confirm the Robot's overwrite.",
|
||||
"no_actions_performed": "Cannot save robot. Please perform at least one capture action before saving."
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "Optimizing and saving the workflow"
|
||||
|
||||
@@ -252,7 +252,15 @@
|
||||
"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",
|
||||
"label_required": "La etiqueta no puede estar vacía"
|
||||
"label_required": "La etiqueta no puede estar vacía",
|
||||
"duplicate_label": "Esta etiqueta ya existe. Por favor use una etiqueta única.",
|
||||
"no_text_captured": "Por favor resalte y seleccione elementos de texto antes de confirmar.",
|
||||
"capture_list_first": "Por favor posicione el cursor sobre una lista y seleccione campos de texto dentro de ella primero",
|
||||
"confirm_all_list_fields": "Por favor confirme todos los campos de lista capturados antes de continuar"
|
||||
},
|
||||
"tooltips": {
|
||||
"capture_list_first": "Posicione el cursor sobre una lista y seleccione campos de texto dentro de ella primero",
|
||||
"confirm_all_list_fields": "Por favor confirme todos los campos de lista capturados antes de continuar"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +277,8 @@
|
||||
},
|
||||
"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."
|
||||
"exists_warning": "Ya existe un robot con este nombre, por favor confirme la sobrescritura del robot.",
|
||||
"no_actions_performed": "No se puede guardar el robot. Por favor realice al menos una acción de captura antes de guardar."
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "Optimizando y guardando el flujo de trabajo"
|
||||
|
||||
@@ -252,7 +252,15 @@
|
||||
"unable_create_settings": "リスト設定を作成できません。リストのフィールドを定義したことを確認してください。",
|
||||
"capture_text_discarded": "テキスト取得が破棄されました",
|
||||
"capture_list_discarded": "リスト取得が破棄されました",
|
||||
"label_required": "ラベルは空にできません"
|
||||
"label_required": "ラベルは空にできません",
|
||||
"duplicate_label": "このラベルは既に存在します。固有のラベルを使用してください。",
|
||||
"no_text_captured": "確認する前にテキスト要素をハイライトして選択してください。",
|
||||
"capture_list_first": "まずリストの上にカーソルを置き、その中のテキストフィールドを選択してください",
|
||||
"confirm_all_list_fields": "続行する前にすべてのキャプチャされたリストフィールドを確認してください"
|
||||
},
|
||||
"tooltips": {
|
||||
"capture_list_first": "リストの上にカーソルを置き、その中のテキストフィールドを選択してください",
|
||||
"confirm_all_list_fields": "すべてのキャプチャされたリストフィールドを確認してください"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +277,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"user_not_logged": "ユーザーがログインしていません。録画を保存できません。",
|
||||
"exists_warning": "この名前のロボットは既に存在します。ロボットの上書きを確認してください。"
|
||||
"exists_warning": "この名前のロボットは既に存在します。ロボットの上書きを確認してください。",
|
||||
"no_actions_performed": "ロボットを保存できません。保存する前に少なくとも1つのキャプチャアクションを実行してください。"
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "ワークフローを最適化して保存中"
|
||||
|
||||
@@ -252,7 +252,14 @@
|
||||
"unable_create_settings": "Liste ayarları oluşturulamadı. Bir alan tanımladığınızdan emin olun.",
|
||||
"capture_text_discarded": "Metin Yakalama İptal Edildi",
|
||||
"capture_list_discarded": "Liste Yakalama İptal Edildi",
|
||||
"label_required": "Etiket boş olamaz"
|
||||
"label_required": "Etiket boş olamaz",
|
||||
"no_text_captured": "Henüz metin yakalanmadı. Lütfen önce metin öğeleri seçin.",
|
||||
"duplicate_label": "Bu etiket zaten mevcut. Lütfen benzersiz bir etiket kullanın.",
|
||||
"capture_list_first": "Lütfen onaylamadan önce bir liste yakalayın ve alanlar seçin.",
|
||||
"confirm_all_list_fields": "Lütfen devam etmeden önce tüm liste alanlarını onaylayın."
|
||||
},
|
||||
"tooltips": {
|
||||
"confirm_all_list_fields": "Lütfen bir sonraki adıma geçmeden önce tüm liste alanlarını onaylayın"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +276,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"user_not_logged": "Kullanıcı girişi yok. Kaydedilemedi.",
|
||||
"exists_warning": "Bu isimde robot zaten var; üzerine yazmayı onaylayın."
|
||||
"exists_warning": "Bu isimde robot zaten var; üzerine yazmayı onaylayın.",
|
||||
"no_actions_performed": "Robot kaydedilemez. Lütfen kaydetmeden önce en az bir yakalama eylemi gerçekleştirin."
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "Akış optimize ediliyor ve kaydediliyor"
|
||||
|
||||
@@ -252,7 +252,15 @@
|
||||
"unable_create_settings": "无法创建列表设置。请确保您已为列表定义了字段。",
|
||||
"capture_text_discarded": "文本捕获已放弃",
|
||||
"capture_list_discarded": "列表捕获已放弃",
|
||||
"label_required": "标签不能为空"
|
||||
"label_required": "标签不能为空",
|
||||
"duplicate_label": "此标签已存在。请使用唯一的标签。",
|
||||
"no_text_captured": "请在确认之前先高亮并选择文本元素。",
|
||||
"capture_list_first": "请先将鼠标悬停在列表上并选择其中的文本字段",
|
||||
"confirm_all_list_fields": "请在继续之前确认所有已捕获的列表字段"
|
||||
},
|
||||
"tooltips": {
|
||||
"capture_list_first": "将鼠标悬停在列表上并选择其中的文本字段",
|
||||
"confirm_all_list_fields": "请确认所有已捕获的列表字段"
|
||||
}
|
||||
},
|
||||
"save_recording": {
|
||||
@@ -269,7 +277,8 @@
|
||||
},
|
||||
"errors": {
|
||||
"user_not_logged": "用户未登录。无法保存录制。",
|
||||
"exists_warning": "已存在同名机器人,请确认是否覆盖机器人。"
|
||||
"exists_warning": "已存在同名机器人,请确认是否覆盖机器人。",
|
||||
"no_actions_performed": "无法保存机器人。请在保存之前至少执行一次捕获操作。"
|
||||
},
|
||||
"tooltips": {
|
||||
"saving": "正在优化并保存工作流程"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Button, Paper, Box, TextField, IconButton } from "@mui/material";
|
||||
import { Button, Paper, Box, TextField, IconButton, Tooltip } from "@mui/material";
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import TextFieldsIcon from '@mui/icons-material/TextFields';
|
||||
import DocumentScannerIcon from '@mui/icons-material/DocumentScanner';
|
||||
@@ -21,6 +21,7 @@ import ActionDescriptionBox from '../action/ActionDescriptionBox';
|
||||
import { useThemeMode } from '../../context/theme-provider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBrowserDimensionsStore } from '../../context/browserDimensions';
|
||||
import { emptyWorkflow } from '../../shared/constants';
|
||||
import { clientListExtractor } from '../../helpers/clientListExtractor';
|
||||
import { clientSelectorGenerator } from '../../helpers/clientSelectorGenerator';
|
||||
|
||||
@@ -54,7 +55,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
const [isCaptureListConfirmed, setIsCaptureListConfirmed] = useState(false);
|
||||
const { panelHeight } = useBrowserDimensionsStore();
|
||||
|
||||
const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog, currentListActionId, setCurrentListActionId, currentTextActionId, setCurrentTextActionId, currentScreenshotActionId, setCurrentScreenshotActionId, updateDOMMode, currentSnapshot, isDOMMode } = useGlobalInfoStore();
|
||||
const { lastAction, notify, currentWorkflowActionsState, setCurrentWorkflowActionsState, resetInterpretationLog, currentListActionId, setCurrentListActionId, currentTextActionId, setCurrentTextActionId, currentScreenshotActionId, setCurrentScreenshotActionId, isDOMMode, setIsDOMMode, currentSnapshot, setCurrentSnapshot, updateDOMMode, initialUrl, setRecordingUrl } = useGlobalInfoStore();
|
||||
const {
|
||||
getText, startGetText, stopGetText,
|
||||
getList, startGetList, stopGetList,
|
||||
@@ -89,12 +90,6 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
}
|
||||
};
|
||||
|
||||
const screenshotModeHandler = (data: any) => {
|
||||
if (!data.userId || data.userId === id) {
|
||||
updateDOMMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const domcastHandler = (data: any) => {
|
||||
if (!data.userId || data.userId === id) {
|
||||
if (data.snapshotData && data.snapshotData.snapshot) {
|
||||
@@ -104,12 +99,10 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
};
|
||||
|
||||
socket.on("dom-mode-enabled", domModeHandler);
|
||||
socket.on("screenshot-mode-enabled", screenshotModeHandler);
|
||||
socket.on("domcast", domcastHandler);
|
||||
|
||||
return () => {
|
||||
socket.off("dom-mode-enabled", domModeHandler);
|
||||
socket.off("screenshot-mode-enabled", screenshotModeHandler);
|
||||
socket.off("domcast", domcastHandler);
|
||||
};
|
||||
}
|
||||
@@ -243,36 +236,18 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
return;
|
||||
}
|
||||
|
||||
Object.entries(fields).forEach(([key, field]) => {
|
||||
if (field.selectorObj?.selector) {
|
||||
const isFieldXPath =
|
||||
field.selectorObj.selector.startsWith("//") ||
|
||||
field.selectorObj.selector.startsWith("/");
|
||||
console.log(
|
||||
`Field "${key}" selector:`,
|
||||
field.selectorObj.selector,
|
||||
`(XPath: ${isFieldXPath})`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const extractedData = clientListExtractor.extractListData(
|
||||
iframeDoc,
|
||||
listSelector,
|
||||
fields,
|
||||
5
|
||||
5
|
||||
);
|
||||
|
||||
updateListStepData(currentListId, extractedData);
|
||||
|
||||
if (extractedData.length === 0) {
|
||||
console.warn(
|
||||
"⚠️ No data extracted - this might indicate selector issues"
|
||||
);
|
||||
notify(
|
||||
"warning",
|
||||
"No data was extracted. Please verify your selections."
|
||||
);
|
||||
console.warn("⚠️ No data extracted - this might indicate selector issues");
|
||||
notify("warning", "No data was extracted. Please verify your selections.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in client-side data extraction:", error);
|
||||
@@ -346,12 +321,32 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
|
||||
const handleTextStepConfirm = (id: number) => {
|
||||
const label = textLabels[id]?.trim();
|
||||
if (label) {
|
||||
updateBrowserTextStepLabel(id, label);
|
||||
setConfirmedTextSteps(prev => ({ ...prev, [id]: true }));
|
||||
} else {
|
||||
if (!label) {
|
||||
setErrors(prevErrors => ({ ...prevErrors, [id]: t('right_panel.errors.label_required') }));
|
||||
return;
|
||||
}
|
||||
|
||||
const existingLabels = browserSteps
|
||||
.filter(step =>
|
||||
step.type === 'text' &&
|
||||
step.id !== id &&
|
||||
confirmedTextSteps[step.id] &&
|
||||
'label' in step &&
|
||||
step.label
|
||||
)
|
||||
.map(step => (step as any).label);
|
||||
|
||||
if (existingLabels.includes(label)) {
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[id]: t('right_panel.errors.duplicate_label') || `Label "${label}" already exists. Please use a unique label.`
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
updateBrowserTextStepLabel(id, label);
|
||||
setConfirmedTextSteps(prev => ({ ...prev, [id]: true }));
|
||||
setErrors(prevErrors => ({ ...prevErrors, [id]: '' }));
|
||||
};
|
||||
|
||||
const handleTextStepDiscard = (id: number) => {
|
||||
@@ -425,14 +420,22 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
};
|
||||
|
||||
const getTextSettingsObject = useCallback(() => {
|
||||
const settings: Record<string, { selector: string; tag?: string;[key: string]: any }> = {};
|
||||
const settings: Record<string, {
|
||||
selector: string;
|
||||
tag?: string;
|
||||
[key: string]: any
|
||||
}> = {};
|
||||
|
||||
browserSteps.forEach(step => {
|
||||
if (browserStepIdList.includes(step.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.type === 'text' && step.label && step.selectorObj?.selector) {
|
||||
settings[step.label] = step.selectorObj;
|
||||
settings[step.label] = {
|
||||
...step.selectorObj,
|
||||
selector: step.selectorObj.selector
|
||||
};
|
||||
}
|
||||
setBrowserStepIdList(prevList => [...prevList, step.id]);
|
||||
});
|
||||
@@ -441,15 +444,24 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
}, [browserSteps, browserStepIdList]);
|
||||
|
||||
const stopCaptureAndEmitGetTextSettings = useCallback(() => {
|
||||
const hasUnconfirmedTextSteps = browserSteps.some(step => step.type === 'text' && !confirmedTextSteps[step.id]);
|
||||
if (hasUnconfirmedTextSteps) {
|
||||
const hasTextStepsForCurrentAction = browserSteps.some(step => step.type === 'text' && step.actionId === currentTextActionId);
|
||||
if (!hasTextStepsForCurrentAction) {
|
||||
notify('error', t('right_panel.errors.no_text_captured'));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasUnconfirmedTextStepsForCurrentAction = browserSteps.some(step =>
|
||||
step.type === 'text' &&
|
||||
step.actionId === currentTextActionId &&
|
||||
!confirmedTextSteps[step.id]
|
||||
);
|
||||
if (hasUnconfirmedTextStepsForCurrentAction) {
|
||||
notify('error', t('right_panel.errors.confirm_text_fields'));
|
||||
return;
|
||||
}
|
||||
stopGetText();
|
||||
const settings = getTextSettingsObject();
|
||||
const hasTextSteps = browserSteps.some(step => step.type === 'text');
|
||||
if (hasTextSteps) {
|
||||
if (hasTextStepsForCurrentAction) {
|
||||
socket?.emit('action', { action: 'scrapeSchema', settings });
|
||||
}
|
||||
setIsCaptureTextConfirmed(true);
|
||||
@@ -463,15 +475,29 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
const getListSettingsObject = useCallback(() => {
|
||||
let settings: {
|
||||
listSelector?: string;
|
||||
fields?: Record<string, { selector: string; tag?: string; [key: string]: any; isShadow?: boolean }>;
|
||||
pagination?: { type: string; selector?: string; isShadow?: boolean };
|
||||
fields?: Record<string, {
|
||||
selector: string;
|
||||
tag?: string;
|
||||
[key: string]: any;
|
||||
isShadow?: boolean;
|
||||
}>;
|
||||
pagination?: {
|
||||
type: string;
|
||||
selector?: string;
|
||||
isShadow?: boolean;
|
||||
};
|
||||
limit?: number;
|
||||
isShadow?: boolean
|
||||
isShadow?: boolean;
|
||||
} = {};
|
||||
|
||||
browserSteps.forEach(step => {
|
||||
if (step.type === 'list' && step.listSelector && Object.keys(step.fields).length > 0) {
|
||||
const fields: Record<string, { selector: string; tag?: string;[key: string]: any; isShadow?: boolean }> = {};
|
||||
const fields: Record<string, {
|
||||
selector: string;
|
||||
tag?: string;
|
||||
[key: string]: any;
|
||||
isShadow?: boolean;
|
||||
}> = {};
|
||||
|
||||
Object.entries(step.fields).forEach(([id, field]) => {
|
||||
if (field.selectorObj?.selector) {
|
||||
@@ -487,7 +513,11 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
settings = {
|
||||
listSelector: step.listSelector,
|
||||
fields: fields,
|
||||
pagination: { type: paginationType, selector: step.pagination?.selector, isShadow: step.isShadow },
|
||||
pagination: {
|
||||
type: paginationType,
|
||||
selector: step.pagination?.selector,
|
||||
isShadow: step.isShadow
|
||||
},
|
||||
limit: parseInt(limitType === 'custom' ? customLimit : limitType),
|
||||
isShadow: step.isShadow
|
||||
};
|
||||
@@ -503,7 +533,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
setShowLimitOptions(false);
|
||||
updateLimitType('');
|
||||
updateCustomLimit('');
|
||||
}, [setShowPaginationOptions, updatePaginationType, setShowLimitOptions, updateLimitType, updateCustomLimit]);
|
||||
}, [updatePaginationType, updateLimitType, updateCustomLimit]);
|
||||
|
||||
const handleStopGetList = useCallback(() => {
|
||||
stopGetList();
|
||||
@@ -512,8 +542,6 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
|
||||
const stopCaptureAndEmitGetListSettings = useCallback(() => {
|
||||
const settings = getListSettingsObject();
|
||||
|
||||
console.log("rrwebSnapshotHandler", settings);
|
||||
|
||||
const latestListStep = getLatestListStep(browserSteps);
|
||||
if (latestListStep && settings) {
|
||||
@@ -534,6 +562,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
|
||||
const hasUnconfirmedListTextFields = browserSteps.some(step =>
|
||||
step.type === 'list' &&
|
||||
step.actionId === currentListActionId &&
|
||||
Object.entries(step.fields).some(([fieldKey]) =>
|
||||
!confirmedListTextFields[step.id]?.[fieldKey]
|
||||
)
|
||||
@@ -549,6 +578,31 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
const handleConfirmListCapture = useCallback(() => {
|
||||
switch (captureStage) {
|
||||
case 'initial':
|
||||
const hasValidListSelectorForCurrentAction = browserSteps.some(step =>
|
||||
step.type === 'list' &&
|
||||
step.actionId === currentListActionId &&
|
||||
step.listSelector &&
|
||||
Object.keys(step.fields).length > 0
|
||||
);
|
||||
|
||||
if (!hasValidListSelectorForCurrentAction) {
|
||||
notify('error', t('right_panel.errors.capture_list_first'));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasUnconfirmedListTextFieldsForCurrentAction = browserSteps.some(step =>
|
||||
step.type === 'list' &&
|
||||
step.actionId === currentListActionId &&
|
||||
Object.entries(step.fields).some(([fieldKey]) =>
|
||||
!confirmedListTextFields[step.id]?.[fieldKey]
|
||||
)
|
||||
);
|
||||
|
||||
if (hasUnconfirmedListTextFieldsForCurrentAction) {
|
||||
notify('error', t('right_panel.errors.confirm_all_list_fields'));
|
||||
return;
|
||||
}
|
||||
|
||||
startPaginationMode();
|
||||
setShowPaginationOptions(true);
|
||||
setCaptureStage('pagination');
|
||||
@@ -599,7 +653,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
setCaptureStage('initial');
|
||||
break;
|
||||
}
|
||||
}, [captureStage, paginationType, limitType, customLimit, startPaginationMode, setShowPaginationOptions, setCaptureStage, getListSettingsObject, notify, stopPaginationMode, startLimitMode, setShowLimitOptions, stopLimitMode, setIsCaptureListConfirmed, stopCaptureAndEmitGetListSettings, t]);
|
||||
}, [captureStage, paginationType, limitType, customLimit, startPaginationMode, setShowPaginationOptions, setCaptureStage, getListSettingsObject, notify, stopPaginationMode, startLimitMode, setShowLimitOptions, stopLimitMode, setIsCaptureListConfirmed, stopCaptureAndEmitGetListSettings, t, browserSteps, currentListActionId, confirmedListTextFields]);
|
||||
|
||||
const handleBackCaptureList = useCallback(() => {
|
||||
switch (captureStage) {
|
||||
@@ -616,7 +670,7 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
setCaptureStage('initial');
|
||||
break;
|
||||
}
|
||||
}, [captureStage, stopLimitMode, setShowLimitOptions, startPaginationMode, setShowPaginationOptions, setCaptureStage, stopPaginationMode]);
|
||||
}, [captureStage, stopLimitMode, startPaginationMode, stopPaginationMode]);
|
||||
|
||||
const handlePaginationSettingSelect = (option: PaginationType) => {
|
||||
updatePaginationType(option);
|
||||
@@ -716,14 +770,23 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
const isConfirmCaptureDisabled = useMemo(() => {
|
||||
if (captureStage !== 'initial') return false;
|
||||
|
||||
const hasValidListSelector = browserSteps.some(step =>
|
||||
const hasValidListSelectorForCurrentAction = browserSteps.some(step =>
|
||||
step.type === 'list' &&
|
||||
step.actionId === currentListActionId &&
|
||||
step.listSelector &&
|
||||
Object.keys(step.fields).length > 0
|
||||
);
|
||||
|
||||
return !hasValidListSelector || hasUnconfirmedListTextFields;
|
||||
}, [captureStage, browserSteps, hasUnconfirmedListTextFields]);
|
||||
const hasUnconfirmedListTextFieldsForCurrentAction = browserSteps.some(step =>
|
||||
step.type === 'list' &&
|
||||
step.actionId === currentListActionId &&
|
||||
Object.entries(step.fields).some(([fieldKey]) =>
|
||||
!confirmedListTextFields[step.id]?.[fieldKey]
|
||||
)
|
||||
);
|
||||
|
||||
return !hasValidListSelectorForCurrentAction || hasUnconfirmedListTextFieldsForCurrentAction;
|
||||
}, [captureStage, browserSteps, currentListActionId, confirmedListTextFields]);
|
||||
|
||||
const theme = useThemeMode();
|
||||
const isDarkMode = theme.darkMode;
|
||||
@@ -779,21 +842,33 @@ export const RightSidePanel: React.FC<RightSidePanelProps> = ({ onFinishCapture
|
||||
{t('right_panel.buttons.back')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleConfirmListCapture}
|
||||
disabled={captureStage === 'initial' ? isConfirmCaptureDisabled : hasUnconfirmedListTextFields}
|
||||
sx={{
|
||||
color: '#ff00c3 !important',
|
||||
borderColor: '#ff00c3 !important',
|
||||
backgroundColor: 'whitesmoke !important',
|
||||
}}
|
||||
<Tooltip
|
||||
title={
|
||||
captureStage !== 'initial' && hasUnconfirmedListTextFields
|
||||
? t('right_panel.tooltips.confirm_all_list_fields')
|
||||
: ''
|
||||
}
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleConfirmListCapture}
|
||||
disabled={captureStage !== 'initial' && hasUnconfirmedListTextFields}
|
||||
sx={{
|
||||
color: '#ff00c3 !important',
|
||||
borderColor: '#ff00c3 !important',
|
||||
backgroundColor: 'whitesmoke !important',
|
||||
}}
|
||||
>
|
||||
{captureStage === 'initial' ? t('right_panel.buttons.confirm_capture') :
|
||||
captureStage === 'pagination' ? t('right_panel.buttons.confirm_pagination') :
|
||||
captureStage === 'limit' ? t('right_panel.buttons.confirm_limit') :
|
||||
t('right_panel.buttons.finish_capture')}
|
||||
</Button>
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
|
||||
@@ -22,7 +22,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
|
||||
const [saveRecordingName, setSaveRecordingName] = useState<string>(fileName);
|
||||
const [waitingForSave, setWaitingForSave] = useState<boolean>(false);
|
||||
|
||||
const { browserId, setBrowserId, notify, recordings, isLogin, recordingName, retrainRobotId } = useGlobalInfoStore();
|
||||
const { browserId, setBrowserId, notify, recordings, isLogin, recordingName, retrainRobotId, currentWorkflowActionsState } = useGlobalInfoStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { state, dispatch } = useContext(AuthContext);
|
||||
const { user } = state;
|
||||
@@ -53,6 +53,14 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
|
||||
};
|
||||
|
||||
const handleFinishClick = () => {
|
||||
const { hasScrapeListAction, hasScreenshotAction, hasScrapeSchemaAction } = currentWorkflowActionsState;
|
||||
const hasAnyAction = hasScrapeListAction || hasScreenshotAction || hasScrapeSchemaAction;
|
||||
|
||||
if (!hasAnyAction) {
|
||||
notify('warning', t('save_recording.errors.no_actions_performed'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordingName && !recordings.includes(recordingName)) {
|
||||
saveRecording();
|
||||
} else {
|
||||
@@ -74,10 +82,11 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
|
||||
}
|
||||
|
||||
const notificationData = {
|
||||
type: 'success',
|
||||
type: data?.actionType === 'error' ? 'error' : 'success',
|
||||
message: successMessage,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
window.sessionStorage.setItem('pendingNotification', JSON.stringify(notificationData));
|
||||
|
||||
if (window.opener) {
|
||||
window.opener.postMessage({
|
||||
@@ -103,6 +112,14 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
|
||||
// releases resources and changes the view for main page by clearing the global browserId
|
||||
const saveRecording = async () => {
|
||||
if (user) {
|
||||
const { hasScrapeListAction, hasScreenshotAction, hasScrapeSchemaAction } = currentWorkflowActionsState;
|
||||
const hasAnyAction = hasScrapeListAction || hasScreenshotAction || hasScrapeSchemaAction;
|
||||
|
||||
if (!hasAnyAction) {
|
||||
notify('warning', t('save_recording.errors.no_actions_performed'));
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
fileName: saveRecordingName || recordingName,
|
||||
userId: user.id,
|
||||
|
||||
Reference in New Issue
Block a user