@@ -50,7 +50,6 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"loglevel": "^1.8.0",
|
"loglevel": "^1.8.0",
|
||||||
"loglevel-plugin-remote": "^0.6.8",
|
"loglevel-plugin-remote": "^0.6.8",
|
||||||
"maxun-core": "^0.0.15",
|
|
||||||
"minio": "^8.0.1",
|
"minio": "^8.0.1",
|
||||||
"moment-timezone": "^0.5.45",
|
"moment-timezone": "^0.5.45",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
|
|||||||
@@ -543,16 +543,15 @@
|
|||||||
"captured_data": {
|
"captured_data": {
|
||||||
"title": "Erfasste Daten",
|
"title": "Erfasste Daten",
|
||||||
"download_csv": "CSV herunterladen",
|
"download_csv": "CSV herunterladen",
|
||||||
"download_all_json": "Gesamte JSON herunterladen",
|
|
||||||
"view_full": "Vollständige Daten anzeigen",
|
"view_full": "Vollständige Daten anzeigen",
|
||||||
"items": "Elemente",
|
"items": "Elemente",
|
||||||
"schema_title": "Text erfassen",
|
"schema_title": "Erfasste Texte",
|
||||||
"list_title": "Liste erfassen"
|
"list_title": "Erfasste Listen"
|
||||||
},
|
},
|
||||||
"captured_screenshot": {
|
"captured_screenshot": {
|
||||||
"title": "Erfasste Screenshots",
|
"title": "Erfasste Screenshots",
|
||||||
"download": "Screenshot herunterladen",
|
"download": "Herunterladen",
|
||||||
"render_failed": "Screenshot konnte nicht gerendert werden"
|
"render_failed": "Fehler beim Rendern des Screenshots"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
|
|||||||
@@ -556,15 +556,14 @@
|
|||||||
"captured_data": {
|
"captured_data": {
|
||||||
"title": "Captured Data",
|
"title": "Captured Data",
|
||||||
"download_csv": "Download CSV",
|
"download_csv": "Download CSV",
|
||||||
"download_all_json": "Download All JSON",
|
|
||||||
"view_full": "View Full Data",
|
"view_full": "View Full Data",
|
||||||
"items": "items",
|
"items": "items",
|
||||||
"schema_title": "Capture Text",
|
"schema_title": "Captured Texts",
|
||||||
"list_title": "Capture List"
|
"list_title": "Captured Lists"
|
||||||
},
|
},
|
||||||
"captured_screenshot": {
|
"captured_screenshot": {
|
||||||
"title": "Captured Screenshots",
|
"title": "Captured Screenshots",
|
||||||
"download": "Download Screenshot",
|
"download": "Download",
|
||||||
"render_failed": "Failed to render screenshot"
|
"render_failed": "Failed to render screenshot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -542,18 +542,17 @@
|
|||||||
"loading": "Cargando datos...",
|
"loading": "Cargando datos...",
|
||||||
"empty_output": "No hay datos de salida disponibles",
|
"empty_output": "No hay datos de salida disponibles",
|
||||||
"captured_data": {
|
"captured_data": {
|
||||||
"title": "Datos Capturados",
|
"title": "Datos capturados",
|
||||||
"download_csv": "Descargar CSV",
|
"download_csv": "Descargar CSV",
|
||||||
"download_all_json": "Descargar Todo JSON",
|
"view_full": "Ver datos completos",
|
||||||
"view_full": "Ver Datos Completos",
|
|
||||||
"items": "elementos",
|
"items": "elementos",
|
||||||
"schema_title": "Capturar Texto",
|
"schema_title": "Textos capturados",
|
||||||
"list_title": "Capturar Lista"
|
"list_title": "Listas capturadas"
|
||||||
},
|
},
|
||||||
"captured_screenshot": {
|
"captured_screenshot": {
|
||||||
"title": "Capturas de Pantalla",
|
"title": "Capturas de pantalla",
|
||||||
"download": "Descargar Captura",
|
"download": "Descargar",
|
||||||
"render_failed": "Error al renderizar la captura"
|
"render_failed": "Error al renderizar la captura de pantalla"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
|
|||||||
@@ -542,17 +542,16 @@
|
|||||||
"loading": "データを読み込み中...",
|
"loading": "データを読み込み中...",
|
||||||
"empty_output": "出力データがありません",
|
"empty_output": "出力データがありません",
|
||||||
"captured_data": {
|
"captured_data": {
|
||||||
"title": "キャプチャされたデータ",
|
"title": "キャプチャしたデータ",
|
||||||
"download_csv": "CSVをダウンロード",
|
"download_csv": "CSVをダウンロード",
|
||||||
"download_all_json": "すべてのJSONをダウンロード",
|
"view_full": "完全なデータを表示",
|
||||||
"view_full": "すべてのデータを表示",
|
|
||||||
"items": "アイテム",
|
"items": "アイテム",
|
||||||
"schema_title": "テキストをキャプチャ",
|
"schema_title": "キャプチャしたテキスト",
|
||||||
"list_title": "リストをキャプチャ"
|
"list_title": "キャプチャしたリスト"
|
||||||
},
|
},
|
||||||
"captured_screenshot": {
|
"captured_screenshot": {
|
||||||
"title": "キャプチャされたスクリーンショット",
|
"title": "キャプチャしたスクリーンショット",
|
||||||
"download": "スクリーンショットをダウンロード",
|
"download": "ダウンロード",
|
||||||
"render_failed": "スクリーンショットのレンダリングに失敗しました"
|
"render_failed": "スクリーンショットのレンダリングに失敗しました"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -544,15 +544,14 @@
|
|||||||
"captured_data": {
|
"captured_data": {
|
||||||
"title": "已捕获的数据",
|
"title": "已捕获的数据",
|
||||||
"download_csv": "下载CSV",
|
"download_csv": "下载CSV",
|
||||||
"download_all_json": "下载所有JSON",
|
|
||||||
"view_full": "查看完整数据",
|
"view_full": "查看完整数据",
|
||||||
"items": "项目",
|
"items": "项目",
|
||||||
"schema_title": "捕获文本",
|
"schema_title": "已捕获的文本",
|
||||||
"list_title": "捕获列表"
|
"list_title": "已捕获的列表"
|
||||||
},
|
},
|
||||||
"captured_screenshot": {
|
"captured_screenshot": {
|
||||||
"title": "已捕获的截图",
|
"title": "已捕获的截图",
|
||||||
"download": "下载截图",
|
"download": "下载",
|
||||||
"render_failed": "渲染截图失败"
|
"render_failed": "渲染截图失败"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress, Accordion, AccordionSummary, AccordionDetails, Divider, Card, CardHeader, CardContent, Grid, IconButton, Chip, ButtonGroup } from "@mui/material";
|
import {
|
||||||
|
Box,
|
||||||
|
Tabs,
|
||||||
|
Typography,
|
||||||
|
Tab,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
ButtonGroup
|
||||||
|
} from "@mui/material";
|
||||||
import Highlight from "react-highlight";
|
import Highlight from "react-highlight";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Data } from "./RunsTable";
|
import { Data } from "./RunsTable";
|
||||||
import { TabPanel, TabContext } from "@mui/lab";
|
import { TabPanel, TabContext } from "@mui/lab";
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
|
||||||
import ImageIcon from '@mui/icons-material/Image';
|
|
||||||
import ListIcon from '@mui/icons-material/List';
|
|
||||||
import SchemaIcon from '@mui/icons-material/Schema';
|
|
||||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
|
|
||||||
import DownloadIcon from '@mui/icons-material/Download';
|
|
||||||
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
|
||||||
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
|
||||||
import ViewListIcon from '@mui/icons-material/ViewList';
|
|
||||||
import DataObjectIcon from '@mui/icons-material/DataObject';
|
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -46,9 +47,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
const [listColumns, setListColumns] = useState<string[][]>([]);
|
const [listColumns, setListColumns] = useState<string[][]>([]);
|
||||||
const [currentListIndex, setCurrentListIndex] = useState<number>(0);
|
const [currentListIndex, setCurrentListIndex] = useState<number>(0);
|
||||||
|
|
||||||
const [expandedView, setExpandedView] = useState<string | null>(null);
|
const [screenshotKeys, setScreenshotKeys] = useState<string[]>([]);
|
||||||
|
const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState<number>(0);
|
||||||
const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical');
|
|
||||||
|
|
||||||
const [legacyData, setLegacyData] = useState<any[]>([]);
|
const [legacyData, setLegacyData] = useState<any[]>([]);
|
||||||
const [legacyColumns, setLegacyColumns] = useState<string[]>([]);
|
const [legacyColumns, setLegacyColumns] = useState<string[]>([]);
|
||||||
@@ -62,8 +62,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
if (!row.serializableOutput) return;
|
if (!row.serializableOutput) return;
|
||||||
|
|
||||||
if (!row.serializableOutput.scrapeSchema &&
|
if (!row.serializableOutput.scrapeSchema &&
|
||||||
!row.serializableOutput.scrapeList &&
|
!row.serializableOutput.scrapeList &&
|
||||||
Object.keys(row.serializableOutput).length > 0) {
|
Object.keys(row.serializableOutput).length > 0) {
|
||||||
|
|
||||||
setIsLegacyData(true);
|
setIsLegacyData(true);
|
||||||
processLegacyData(row.serializableOutput);
|
processLegacyData(row.serializableOutput);
|
||||||
@@ -81,6 +81,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
}
|
}
|
||||||
}, [row.serializableOutput]);
|
}, [row.serializableOutput]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (row.binaryOutput && Object.keys(row.binaryOutput).length > 0) {
|
||||||
|
setScreenshotKeys(Object.keys(row.binaryOutput));
|
||||||
|
setCurrentScreenshotIndex(0);
|
||||||
|
}
|
||||||
|
}, [row.binaryOutput]);
|
||||||
|
|
||||||
const processLegacyData = (legacyOutput: Record<string, any>) => {
|
const processLegacyData = (legacyOutput: Record<string, any>) => {
|
||||||
let allData: any[] = [];
|
let allData: any[] = [];
|
||||||
|
|
||||||
@@ -223,29 +230,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadAllJSON = () => {
|
|
||||||
let allData;
|
|
||||||
|
|
||||||
if (isLegacyData) {
|
|
||||||
allData = { data: legacyData };
|
|
||||||
} else {
|
|
||||||
allData = {
|
|
||||||
schema: schemaData,
|
|
||||||
list: listData.flat(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(allData, null, 2)], { type: 'application/json;charset=utf-8;' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = url;
|
|
||||||
link.setAttribute("download", "all_data.json");
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateListTable = (direction: 'next' | 'prev') => {
|
const navigateListTable = (direction: 'next' | 'prev') => {
|
||||||
if (direction === 'next' && currentListIndex < listData.length - 1) {
|
if (direction === 'next' && currentListIndex < listData.length - 1) {
|
||||||
setCurrentListIndex(currentListIndex + 1);
|
setCurrentListIndex(currentListIndex + 1);
|
||||||
@@ -254,11 +238,18 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const navigateScreenshots = (direction: 'next' | 'prev') => {
|
||||||
|
if (direction === 'next' && currentScreenshotIndex < screenshotKeys.length - 1) {
|
||||||
|
setCurrentScreenshotIndex(currentScreenshotIndex + 1);
|
||||||
|
} else if (direction === 'prev' && currentScreenshotIndex > 0) {
|
||||||
|
setCurrentScreenshotIndex(currentScreenshotIndex - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderDataTable = (
|
const renderDataTable = (
|
||||||
data: any[],
|
data: any[],
|
||||||
columns: string[],
|
columns: string[],
|
||||||
title: string,
|
title: string,
|
||||||
icon: React.ReactNode,
|
|
||||||
csvFilename: string,
|
csvFilename: string,
|
||||||
jsonFilename: string,
|
jsonFilename: string,
|
||||||
isPaginatedList: boolean = false
|
isPaginatedList: boolean = false
|
||||||
@@ -279,58 +270,51 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
id={`${title.toLowerCase()}-header`}
|
id={`${title.toLowerCase()}-header`}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
{icon}
|
<Typography variant='h6'>
|
||||||
<Typography variant='h6' sx={{ ml: 2 }}>
|
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isPaginatedList ? (
|
|
||||||
<Chip
|
|
||||||
label={listData.length > 1
|
|
||||||
? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})`
|
|
||||||
: `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}`
|
|
||||||
}
|
|
||||||
size="small"
|
|
||||||
sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Chip
|
|
||||||
label={`${data.length} ${data.length === 1 ? 'item' : 'items'}`}
|
|
||||||
size="small"
|
|
||||||
sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
<ButtonGroup size="small" variant="outlined">
|
<Box>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<DownloadIcon />}
|
component="a"
|
||||||
onClick={() => {
|
onClick={() => downloadJSON(data, jsonFilename)}
|
||||||
if (isPaginatedList) {
|
sx={{
|
||||||
downloadCSV(currentData, currentColumns, `list_table_${currentListIndex+1}.csv`);
|
color: '#FF00C3',
|
||||||
} else {
|
textTransform: 'none',
|
||||||
downloadCSV(data, columns, csvFilename);
|
mr: 2,
|
||||||
|
p: 0,
|
||||||
|
minWidth: 'auto',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
textDecoration: 'underline'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
|
||||||
>
|
>
|
||||||
CSV
|
Download as JSON
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
startIcon={<DataObjectIcon />}
|
component="a"
|
||||||
onClick={() => {
|
onClick={() => downloadCSV(data, columns, csvFilename)}
|
||||||
if (isPaginatedList) {
|
sx={{
|
||||||
downloadJSON(currentData, `list_table_${currentListIndex+1}.json`);
|
color: '#FF00C3',
|
||||||
} else {
|
textTransform: 'none',
|
||||||
downloadJSON(data, jsonFilename);
|
p: 0,
|
||||||
|
minWidth: 'auto',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
textDecoration: 'underline'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
|
||||||
>
|
>
|
||||||
JSON
|
Download as CSV
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</Box>
|
||||||
|
|
||||||
{isPaginatedList && listData.length > 1 && (
|
{isPaginatedList && listData.length > 1 && (
|
||||||
<ButtonGroup size="small">
|
<ButtonGroup size="small">
|
||||||
@@ -389,315 +373,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDataCard = (
|
|
||||||
data: any[],
|
|
||||||
columns: string[],
|
|
||||||
title: string,
|
|
||||||
icon: React.ReactNode,
|
|
||||||
dataType: string,
|
|
||||||
csvFilename: string,
|
|
||||||
jsonFilename: string,
|
|
||||||
isPaginatedList: boolean = false
|
|
||||||
) => {
|
|
||||||
if (!isPaginatedList && data.length === 0) return null;
|
|
||||||
if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null;
|
|
||||||
|
|
||||||
const currentData = isPaginatedList ? listData[currentListIndex] : data;
|
|
||||||
const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns;
|
|
||||||
|
|
||||||
if (!currentData || currentData.length === 0) return null;
|
|
||||||
|
|
||||||
const previewData = currentData.slice(0, 1);
|
|
||||||
const previewColumns = currentColumns.slice(0, 3);
|
|
||||||
|
|
||||||
const showMoreColumns = currentColumns.length > 3;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{
|
|
||||||
width: '100%',
|
|
||||||
mb: 3,
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
boxShadow: 3
|
|
||||||
}}>
|
|
||||||
<CardHeader
|
|
||||||
avatar={icon}
|
|
||||||
title={title}
|
|
||||||
action={
|
|
||||||
<Box>
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
if (isPaginatedList) {
|
|
||||||
downloadCSV(currentData, currentColumns, `list_table_${currentListIndex+1}.csv`);
|
|
||||||
} else {
|
|
||||||
downloadCSV(data, columns, csvFilename);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title={t('run_content.captured_data.download_csv')}
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
if (isPaginatedList) {
|
|
||||||
downloadJSON(currentData, `list_table_${currentListIndex+1}.json`);
|
|
||||||
} else {
|
|
||||||
downloadJSON(data, jsonFilename);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title="Download JSON"
|
|
||||||
sx={{ mx: 0.5 }}
|
|
||||||
>
|
|
||||||
<DataObjectIcon />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
if (isPaginatedList) {
|
|
||||||
setExpandedView(`list-${currentListIndex}`);
|
|
||||||
} else {
|
|
||||||
setExpandedView(dataType);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title={t('run_content.captured_data.view_full')}
|
|
||||||
>
|
|
||||||
<FullscreenIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
sx={{ pb: 1 }}
|
|
||||||
/>
|
|
||||||
<CardContent sx={{ pt: 0, pb: 1, flexGrow: 1 }}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
|
||||||
{isPaginatedList ? (
|
|
||||||
<Chip
|
|
||||||
label={listData.length > 1
|
|
||||||
? `Table ${currentListIndex + 1} of ${listData.length} (${currentData.length} ${currentData.length === 1 ? 'item' : 'items'})`
|
|
||||||
: `${currentData.length} ${currentData.length === 1 ? 'item' : 'items'}`
|
|
||||||
}
|
|
||||||
size="small"
|
|
||||||
sx={{ backgroundColor: '#FF00C3', color: 'white' }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Chip
|
|
||||||
label={`${data.length} ${data.length === 1 ? 'item' : 'items'}`}
|
|
||||||
size="small"
|
|
||||||
sx={{ backgroundColor: '#FF00C3', color: 'white' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isPaginatedList && listData.length > 1 && (
|
|
||||||
<ButtonGroup size="small">
|
|
||||||
<Button
|
|
||||||
onClick={() => navigateListTable('prev')}
|
|
||||||
disabled={currentListIndex === 0}
|
|
||||||
sx={{
|
|
||||||
borderColor: '#FF00C3',
|
|
||||||
color: currentListIndex === 0 ? 'gray' : '#FF00C3',
|
|
||||||
'&.Mui-disabled': {
|
|
||||||
borderColor: 'rgba(0, 0, 0, 0.12)'
|
|
||||||
},
|
|
||||||
padding: '0 8px',
|
|
||||||
minWidth: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => navigateListTable('next')}
|
|
||||||
disabled={currentListIndex === listData.length - 1}
|
|
||||||
sx={{
|
|
||||||
borderColor: '#FF00C3',
|
|
||||||
color: currentListIndex === listData.length - 1 ? 'gray' : '#FF00C3',
|
|
||||||
'&.Mui-disabled': {
|
|
||||||
borderColor: 'rgba(0, 0, 0, 0.12)'
|
|
||||||
},
|
|
||||||
padding: '0 8px',
|
|
||||||
minWidth: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
>
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<TableContainer component={Paper} sx={{ maxHeight: 180 }}>
|
|
||||||
<Table size="small" aria-label="preview table">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{previewColumns.map((column) => (
|
|
||||||
<TableCell key={column}>{column}</TableCell>
|
|
||||||
))}
|
|
||||||
{showMoreColumns && <TableCell>...</TableCell>}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{previewData.map((row, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
{previewColumns.map((column) => (
|
|
||||||
<TableCell key={column}>
|
|
||||||
{row[column] === undefined || row[column] === "" ? "-" : row[column]}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
{showMoreColumns && <TableCell>...</TableCell>}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
{currentData.length > 1 && (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={previewColumns.length + (showMoreColumns ? 1 : 0)} align="center">
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
if (isPaginatedList) {
|
|
||||||
setExpandedView(`list-${currentListIndex}`);
|
|
||||||
} else {
|
|
||||||
setExpandedView(dataType);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
sx={{ color: '#FF00C3', mt: 1 }}
|
|
||||||
>
|
|
||||||
View all {currentData.length} items
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderExpandedView = (dataTypeWithIndex: string) => {
|
|
||||||
if (expandedView !== dataTypeWithIndex) return null;
|
|
||||||
|
|
||||||
let data: any[] = [];
|
|
||||||
let columns: string[] = [];
|
|
||||||
let title = "";
|
|
||||||
let csvFilename = "";
|
|
||||||
let jsonFilename = "";
|
|
||||||
|
|
||||||
if (dataTypeWithIndex.startsWith('list-')) {
|
|
||||||
const indexStr = dataTypeWithIndex.split('-')[1];
|
|
||||||
const index = parseInt(indexStr, 10);
|
|
||||||
|
|
||||||
if (index >= 0 && index < listData.length) {
|
|
||||||
data = listData[index];
|
|
||||||
columns = listColumns[index];
|
|
||||||
title = `${t('run_content.captured_data.list_title')} - Table ${index+1}`;
|
|
||||||
csvFilename = `list_table_${index+1}.csv`;
|
|
||||||
jsonFilename = `list_table_${index+1}.json`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (dataTypeWithIndex) {
|
|
||||||
case 'schema':
|
|
||||||
data = schemaData;
|
|
||||||
columns = schemaColumns;
|
|
||||||
title = t('run_content.captured_data.schema_title');
|
|
||||||
csvFilename = 'schema_data.csv';
|
|
||||||
jsonFilename = 'schema_data.json';
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
if (listData.length > 0 && listColumns.length > 0) {
|
|
||||||
data = listData[currentListIndex];
|
|
||||||
columns = listColumns[currentListIndex];
|
|
||||||
}
|
|
||||||
title = t('run_content.captured_data.list_title');
|
|
||||||
csvFilename = 'list_data.csv';
|
|
||||||
jsonFilename = 'list_data.json';
|
|
||||||
break;
|
|
||||||
case 'legacy':
|
|
||||||
data = legacyData;
|
|
||||||
columns = legacyColumns;
|
|
||||||
title = t('run_content.captured_data.title');
|
|
||||||
csvFilename = 'data.csv';
|
|
||||||
jsonFilename = 'data.json';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
||||||
zIndex: 9999,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
p: 4
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 1,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
width: '90%',
|
|
||||||
maxWidth: '1200px',
|
|
||||||
maxHeight: '90vh',
|
|
||||||
overflow: 'auto'
|
|
||||||
}}>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
|
|
||||||
<Typography variant="h5">{title}</Typography>
|
|
||||||
<Box>
|
|
||||||
<ButtonGroup variant="outlined" size="small" sx={{ mr: 2 }}>
|
|
||||||
<Button
|
|
||||||
onClick={() => downloadCSV(data, columns, csvFilename)}
|
|
||||||
startIcon={<DownloadIcon />}
|
|
||||||
>
|
|
||||||
CSV
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => downloadJSON(data, jsonFilename)}
|
|
||||||
startIcon={<DataObjectIcon />}
|
|
||||||
>
|
|
||||||
JSON
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
onClick={() => setExpandedView(null)}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TableContainer component={Paper} sx={{ maxHeight: 'calc(90vh - 150px)' }}>
|
|
||||||
<Table stickyHeader aria-label="expanded data table">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{columns.map((column) => (
|
|
||||||
<TableCell key={column}>{column}</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{data.map((row, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
{columns.map((column) => (
|
|
||||||
<TableCell key={column}>
|
|
||||||
{row[column] === undefined || row[column] === "" ? "-" : row[column]}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0;
|
const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0;
|
||||||
const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0;
|
const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0;
|
||||||
|
|
||||||
@@ -785,283 +460,122 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
|||||||
|
|
||||||
{hasData && (
|
{hasData && (
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
|
||||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<ArticleIcon sx={{ marginRight: '15px' }} />
|
|
||||||
{t('run_content.captured_data.title')}
|
|
||||||
</Typography>
|
|
||||||
<Box>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setViewMode('horizontal')}
|
|
||||||
color={viewMode === 'horizontal' ? 'primary' : 'default'}
|
|
||||||
sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }}
|
|
||||||
>
|
|
||||||
<ViewModuleIcon />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setViewMode('vertical')}
|
|
||||||
color={viewMode === 'vertical' ? 'primary' : 'default'}
|
|
||||||
sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }}
|
|
||||||
>
|
|
||||||
<ViewListIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
onClick={downloadAllJSON}
|
|
||||||
startIcon={<CloudDownloadIcon />}
|
|
||||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3', ml: 1 }}
|
|
||||||
>
|
|
||||||
{t('run_content.captured_data.download_all_json')}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{isLegacyData && (
|
{isLegacyData && (
|
||||||
viewMode === 'vertical' ? (
|
renderDataTable(
|
||||||
renderDataTable(
|
legacyData,
|
||||||
legacyData,
|
legacyColumns,
|
||||||
legacyColumns,
|
t('run_content.captured_data.title'),
|
||||||
t('run_content.captured_data.title'),
|
'data.csv',
|
||||||
<ArticleIcon sx={{ color: '#FF00C3' }} />,
|
'data.json'
|
||||||
'data.csv',
|
|
||||||
'data.json'
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12} md={12}>
|
|
||||||
{renderDataCard(
|
|
||||||
legacyData,
|
|
||||||
legacyColumns,
|
|
||||||
t('run_content.captured_data.title'),
|
|
||||||
<ArticleIcon sx={{ color: '#FF00C3' }} />,
|
|
||||||
'legacy',
|
|
||||||
'data.csv',
|
|
||||||
'data.json'
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLegacyData && (
|
{!isLegacyData && (
|
||||||
viewMode === 'vertical' ? (
|
<>
|
||||||
<>
|
{renderDataTable(
|
||||||
{renderDataTable(
|
schemaData,
|
||||||
schemaData,
|
schemaColumns,
|
||||||
schemaColumns,
|
t('run_content.captured_data.schema_title'),
|
||||||
t('run_content.captured_data.schema_title'),
|
'schema_data.csv',
|
||||||
<SchemaIcon sx={{ color: '#FF00C3' }} />,
|
'schema_data.json'
|
||||||
'schema_data.csv',
|
)}
|
||||||
'schema_data.json'
|
|
||||||
)}
|
|
||||||
|
|
||||||
{listData.length > 0 && renderDataTable(
|
{listData.length > 0 && renderDataTable(
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
t('run_content.captured_data.list_title'),
|
t('run_content.captured_data.list_title'),
|
||||||
<ListIcon sx={{ color: '#FF00C3' }} />,
|
'list_data.csv',
|
||||||
'list_data.csv',
|
'list_data.json',
|
||||||
'list_data.json',
|
true
|
||||||
true
|
)}
|
||||||
)}
|
</>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{(() => {
|
|
||||||
const dataCategoriesCount = [
|
|
||||||
schemaData.length > 0,
|
|
||||||
listData.length > 0,
|
|
||||||
].filter(Boolean).length;
|
|
||||||
|
|
||||||
const columnWidth = dataCategoriesCount === 1 ? 12 : dataCategoriesCount === 2 ? 6 : 4;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{schemaData.length > 0 && (
|
|
||||||
<Grid item xs={12} md={columnWidth} sx={{ display: 'flex' }}>
|
|
||||||
{renderDataCard(
|
|
||||||
schemaData,
|
|
||||||
schemaColumns,
|
|
||||||
t('run_content.captured_data.schema_title'),
|
|
||||||
<SchemaIcon sx={{ color: '#FF00C3' }} />,
|
|
||||||
'schema',
|
|
||||||
'schema_data.csv',
|
|
||||||
'schema_data.json'
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{listData.length > 0 && (
|
|
||||||
<Grid item xs={12} md={columnWidth} sx={{ display: 'flex' }}>
|
|
||||||
{renderDataCard(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
t('run_content.captured_data.list_title'),
|
|
||||||
<ListIcon sx={{ color: '#FF00C3' }} />,
|
|
||||||
'list',
|
|
||||||
'list_data.csv',
|
|
||||||
'list_data.json',
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{renderExpandedView('schema')}
|
|
||||||
{renderExpandedView('legacy')}
|
|
||||||
|
|
||||||
{listData.map((_, index) => renderExpandedView(`list-${index}`))}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasScreenshots && (
|
{hasScreenshots && (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ mb: 3 }}>
|
<Accordion defaultExpanded sx={{ mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<AccordionSummary
|
||||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
expandIcon={<ExpandMoreIcon />}
|
||||||
<ImageIcon sx={{ marginRight: '15px' }} />
|
aria-controls="screenshot-content"
|
||||||
{t('run_content.captured_screenshot.title')}
|
id="screenshot-header"
|
||||||
<Chip
|
>
|
||||||
label={`${Object.keys(row.binaryOutput).length} ${Object.keys(row.binaryOutput).length === 1 ? 'item' : 'items'}`}
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
size="small"
|
<Typography variant='h6'>
|
||||||
sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }}
|
{t('run_content.captured_screenshot.title', 'Screenshots')}
|
||||||
/>
|
</Typography>
|
||||||
</Typography>
|
|
||||||
<Box>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setViewMode('horizontal')}
|
|
||||||
color={viewMode === 'horizontal' ? 'primary' : 'default'}
|
|
||||||
sx={{ color: viewMode === 'horizontal' ? '#FF00C3' : 'inherit' }}
|
|
||||||
>
|
|
||||||
<ViewModuleIcon />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setViewMode('vertical')}
|
|
||||||
color={viewMode === 'vertical' ? 'primary' : 'default'}
|
|
||||||
sx={{ color: viewMode === 'vertical' ? '#FF00C3' : 'inherit' }}
|
|
||||||
>
|
|
||||||
<ViewListIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</AccordionSummary>
|
||||||
</Box>
|
<AccordionDetails>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
href={row.binaryOutput[screenshotKeys[currentScreenshotIndex]]}
|
||||||
|
download={screenshotKeys[currentScreenshotIndex]}
|
||||||
|
sx={{
|
||||||
|
color: '#FF00C3',
|
||||||
|
textTransform: 'none',
|
||||||
|
p: 0,
|
||||||
|
minWidth: 'auto',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('run_content.captured_screenshot.download', 'Download')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
{viewMode === 'vertical' ? (
|
{screenshotKeys.length > 1 && (
|
||||||
<>
|
<ButtonGroup size="small">
|
||||||
{Object.keys(row.binaryOutput).map((key, index) => {
|
<Button
|
||||||
try {
|
onClick={() => navigateScreenshots('prev')}
|
||||||
const imageUrl = row.binaryOutput[key];
|
disabled={currentScreenshotIndex === 0}
|
||||||
return (
|
sx={{
|
||||||
<Accordion defaultExpanded sx={{ mb: 2 }} key={`screenshot-${key}`}>
|
borderColor: '#FF00C3',
|
||||||
<AccordionSummary
|
color: currentScreenshotIndex === 0 ? 'gray' : '#FF00C3',
|
||||||
expandIcon={<ExpandMoreIcon />}
|
'&.Mui-disabled': {
|
||||||
aria-controls={`screenshot-${key}-content`}
|
borderColor: 'rgba(0, 0, 0, 0.12)'
|
||||||
id={`screenshot-${key}-header`}
|
}
|
||||||
>
|
}}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
>
|
||||||
<ImageIcon sx={{ color: '#FF00C3' }} />
|
<ArrowBackIcon />
|
||||||
<Typography variant='h6' sx={{ ml: 2 }}>
|
</Button>
|
||||||
Screenshot {index+1}
|
<Button
|
||||||
</Typography>
|
onClick={() => navigateScreenshots('next')}
|
||||||
</Box>
|
disabled={currentScreenshotIndex === screenshotKeys.length - 1}
|
||||||
</AccordionSummary>
|
sx={{
|
||||||
<AccordionDetails>
|
borderColor: '#FF00C3',
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
color: currentScreenshotIndex === screenshotKeys.length - 1 ? 'gray' : '#FF00C3',
|
||||||
<ButtonGroup size="small" variant="outlined">
|
'&.Mui-disabled': {
|
||||||
<Button
|
borderColor: 'rgba(0, 0, 0, 0.12)'
|
||||||
startIcon={<DownloadIcon />}
|
}
|
||||||
href={imageUrl}
|
}}
|
||||||
download={key}
|
>
|
||||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
<ArrowForwardIcon />
|
||||||
>
|
</Button>
|
||||||
{t('run_content.captured_screenshot.download')}
|
</ButtonGroup>
|
||||||
</Button>
|
)}
|
||||||
</ButtonGroup>
|
</Box>
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box sx={{ mt: 1 }}>
|
||||||
<img
|
<Box>
|
||||||
src={imageUrl}
|
<img
|
||||||
alt={`Screenshot ${key}`}
|
src={row.binaryOutput[screenshotKeys[currentScreenshotIndex]]}
|
||||||
style={{
|
alt={`Screenshot ${screenshotKeys[currentScreenshotIndex]}`}
|
||||||
maxWidth: '100%',
|
style={{
|
||||||
height: 'auto',
|
maxWidth: '100%',
|
||||||
border: '1px solid #e0e0e0',
|
height: 'auto',
|
||||||
borderRadius: '4px'
|
border: '1px solid #e0e0e0',
|
||||||
}}
|
borderRadius: '4px'
|
||||||
/>
|
}}
|
||||||
</Box>
|
/>
|
||||||
</AccordionDetails>
|
</Box>
|
||||||
</Accordion>
|
</Box>
|
||||||
);
|
</AccordionDetails>
|
||||||
} catch (e) {
|
</Accordion>
|
||||||
console.log(e);
|
|
||||||
return (
|
|
||||||
<Typography key={`screenshot-error-${key}`} color="error">
|
|
||||||
{key}: {t('run_content.captured_screenshot.render_failed')}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{Object.keys(row.binaryOutput).map((key) => {
|
|
||||||
try {
|
|
||||||
const imageUrl = row.binaryOutput[key];
|
|
||||||
return (
|
|
||||||
<Grid item xs={12} md={6} key={`screenshot-${key}`}>
|
|
||||||
<Card sx={{ height: '100%', boxShadow: 3 }}>
|
|
||||||
<CardHeader
|
|
||||||
avatar={<ImageIcon sx={{ color: '#FF00C3' }} />}
|
|
||||||
title={`Screenshot ${key}`}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
href={imageUrl}
|
|
||||||
download={key}
|
|
||||||
title={t('run_content.captured_screenshot.download')}
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CardContent sx={{ p: 1 }}>
|
|
||||||
<Box sx={{ position: 'relative', width: '100%', height: 'auto', overflow: 'hidden' }}>
|
|
||||||
<img
|
|
||||||
src={imageUrl}
|
|
||||||
alt={`Screenshot ${key}`}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: 'auto',
|
|
||||||
objectFit: 'contain',
|
|
||||||
border: '1px solid #e0e0e0',
|
|
||||||
borderRadius: '4px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return (
|
|
||||||
<Box key={`screenshot-error-${key}`}>
|
|
||||||
<Typography color="error">
|
|
||||||
{key}: {t('run_content.captured_screenshot.render_failed')}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
Reference in New Issue
Block a user