@@ -50,7 +50,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.8.0",
|
||||
"loglevel-plugin-remote": "^0.6.8",
|
||||
"maxun-core": "^0.0.15",
|
||||
"minio": "^8.0.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"node-cron": "^3.0.3",
|
||||
|
||||
@@ -543,16 +543,15 @@
|
||||
"captured_data": {
|
||||
"title": "Erfasste Daten",
|
||||
"download_csv": "CSV herunterladen",
|
||||
"download_all_json": "Gesamte JSON herunterladen",
|
||||
"view_full": "Vollständige Daten anzeigen",
|
||||
"items": "Elemente",
|
||||
"schema_title": "Text erfassen",
|
||||
"list_title": "Liste erfassen"
|
||||
"schema_title": "Erfasste Texte",
|
||||
"list_title": "Erfasste Listen"
|
||||
},
|
||||
"captured_screenshot": {
|
||||
"title": "Erfasste Screenshots",
|
||||
"download": "Screenshot herunterladen",
|
||||
"render_failed": "Screenshot konnte nicht gerendert werden"
|
||||
"download": "Herunterladen",
|
||||
"render_failed": "Fehler beim Rendern des Screenshots"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
||||
@@ -556,15 +556,14 @@
|
||||
"captured_data": {
|
||||
"title": "Captured Data",
|
||||
"download_csv": "Download CSV",
|
||||
"download_all_json": "Download All JSON",
|
||||
"view_full": "View Full Data",
|
||||
"items": "items",
|
||||
"schema_title": "Capture Text",
|
||||
"list_title": "Capture List"
|
||||
"schema_title": "Captured Texts",
|
||||
"list_title": "Captured Lists"
|
||||
},
|
||||
"captured_screenshot": {
|
||||
"title": "Captured Screenshots",
|
||||
"download": "Download Screenshot",
|
||||
"download": "Download",
|
||||
"render_failed": "Failed to render screenshot"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -542,18 +542,17 @@
|
||||
"loading": "Cargando datos...",
|
||||
"empty_output": "No hay datos de salida disponibles",
|
||||
"captured_data": {
|
||||
"title": "Datos Capturados",
|
||||
"title": "Datos capturados",
|
||||
"download_csv": "Descargar CSV",
|
||||
"download_all_json": "Descargar Todo JSON",
|
||||
"view_full": "Ver Datos Completos",
|
||||
"view_full": "Ver datos completos",
|
||||
"items": "elementos",
|
||||
"schema_title": "Capturar Texto",
|
||||
"list_title": "Capturar Lista"
|
||||
"schema_title": "Textos capturados",
|
||||
"list_title": "Listas capturadas"
|
||||
},
|
||||
"captured_screenshot": {
|
||||
"title": "Capturas de Pantalla",
|
||||
"download": "Descargar Captura",
|
||||
"render_failed": "Error al renderizar la captura"
|
||||
"title": "Capturas de pantalla",
|
||||
"download": "Descargar",
|
||||
"render_failed": "Error al renderizar la captura de pantalla"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
||||
@@ -542,17 +542,16 @@
|
||||
"loading": "データを読み込み中...",
|
||||
"empty_output": "出力データがありません",
|
||||
"captured_data": {
|
||||
"title": "キャプチャされたデータ",
|
||||
"title": "キャプチャしたデータ",
|
||||
"download_csv": "CSVをダウンロード",
|
||||
"download_all_json": "すべてのJSONをダウンロード",
|
||||
"view_full": "すべてのデータを表示",
|
||||
"view_full": "完全なデータを表示",
|
||||
"items": "アイテム",
|
||||
"schema_title": "テキストをキャプチャ",
|
||||
"list_title": "リストをキャプチャ"
|
||||
"schema_title": "キャプチャしたテキスト",
|
||||
"list_title": "キャプチャしたリスト"
|
||||
},
|
||||
"captured_screenshot": {
|
||||
"title": "キャプチャされたスクリーンショット",
|
||||
"download": "スクリーンショットをダウンロード",
|
||||
"title": "キャプチャしたスクリーンショット",
|
||||
"download": "ダウンロード",
|
||||
"render_failed": "スクリーンショットのレンダリングに失敗しました"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -544,15 +544,14 @@
|
||||
"captured_data": {
|
||||
"title": "已捕获的数据",
|
||||
"download_csv": "下载CSV",
|
||||
"download_all_json": "下载所有JSON",
|
||||
"view_full": "查看完整数据",
|
||||
"items": "项目",
|
||||
"schema_title": "捕获文本",
|
||||
"list_title": "捕获列表"
|
||||
"schema_title": "已捕获的文本",
|
||||
"list_title": "已捕获的列表"
|
||||
},
|
||||
"captured_screenshot": {
|
||||
"title": "已捕获的截图",
|
||||
"download": "下载截图",
|
||||
"download": "下载",
|
||||
"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 * as React from "react";
|
||||
import { Data } from "./RunsTable";
|
||||
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 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 ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -46,9 +47,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
const [listColumns, setListColumns] = useState<string[][]>([]);
|
||||
const [currentListIndex, setCurrentListIndex] = useState<number>(0);
|
||||
|
||||
const [expandedView, setExpandedView] = useState<string | null>(null);
|
||||
|
||||
const [viewMode, setViewMode] = useState<'horizontal' | 'vertical'>('vertical');
|
||||
const [screenshotKeys, setScreenshotKeys] = useState<string[]>([]);
|
||||
const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState<number>(0);
|
||||
|
||||
const [legacyData, setLegacyData] = useState<any[]>([]);
|
||||
const [legacyColumns, setLegacyColumns] = useState<string[]>([]);
|
||||
@@ -81,6 +81,13 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
}
|
||||
}, [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>) => {
|
||||
let allData: any[] = [];
|
||||
|
||||
@@ -223,29 +230,6 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
}, 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') => {
|
||||
if (direction === 'next' && currentListIndex < listData.length - 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 = (
|
||||
data: any[],
|
||||
columns: string[],
|
||||
title: string,
|
||||
icon: React.ReactNode,
|
||||
csvFilename: string,
|
||||
jsonFilename: string,
|
||||
isPaginatedList: boolean = false
|
||||
@@ -279,58 +270,51 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
id={`${title.toLowerCase()}-header`}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{icon}
|
||||
<Typography variant='h6' sx={{ ml: 2 }}>
|
||||
<Typography variant='h6'>
|
||||
{title}
|
||||
</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>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<ButtonGroup size="small" variant="outlined">
|
||||
<Box>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
onClick={() => {
|
||||
if (isPaginatedList) {
|
||||
downloadCSV(currentData, currentColumns, `list_table_${currentListIndex+1}.csv`);
|
||||
} else {
|
||||
downloadCSV(data, columns, csvFilename);
|
||||
component="a"
|
||||
onClick={() => downloadJSON(data, jsonFilename)}
|
||||
sx={{
|
||||
color: '#FF00C3',
|
||||
textTransform: 'none',
|
||||
mr: 2,
|
||||
p: 0,
|
||||
minWidth: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
textDecoration: 'underline'
|
||||
}
|
||||
}}
|
||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
||||
>
|
||||
CSV
|
||||
Download as JSON
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
startIcon={<DataObjectIcon />}
|
||||
onClick={() => {
|
||||
if (isPaginatedList) {
|
||||
downloadJSON(currentData, `list_table_${currentListIndex+1}.json`);
|
||||
} else {
|
||||
downloadJSON(data, jsonFilename);
|
||||
component="a"
|
||||
onClick={() => downloadCSV(data, columns, csvFilename)}
|
||||
sx={{
|
||||
color: '#FF00C3',
|
||||
textTransform: 'none',
|
||||
p: 0,
|
||||
minWidth: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
textDecoration: 'underline'
|
||||
}
|
||||
}}
|
||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
||||
>
|
||||
JSON
|
||||
Download as CSV
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
|
||||
{isPaginatedList && listData.length > 1 && (
|
||||
<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 hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0;
|
||||
|
||||
@@ -785,73 +460,22 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
|
||||
{hasData && (
|
||||
<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 && (
|
||||
viewMode === 'vertical' ? (
|
||||
renderDataTable(
|
||||
legacyData,
|
||||
legacyColumns,
|
||||
t('run_content.captured_data.title'),
|
||||
<ArticleIcon sx={{ color: '#FF00C3' }} />,
|
||||
'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 && (
|
||||
viewMode === 'vertical' ? (
|
||||
<>
|
||||
{renderDataTable(
|
||||
schemaData,
|
||||
schemaColumns,
|
||||
t('run_content.captured_data.schema_title'),
|
||||
<SchemaIcon sx={{ color: '#FF00C3' }} />,
|
||||
'schema_data.csv',
|
||||
'schema_data.json'
|
||||
)}
|
||||
@@ -860,134 +484,87 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
[],
|
||||
[],
|
||||
t('run_content.captured_data.list_title'),
|
||||
<ListIcon sx={{ color: '#FF00C3' }} />,
|
||||
'list_data.csv',
|
||||
'list_data.json',
|
||||
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>
|
||||
)}
|
||||
|
||||
{hasScreenshots && (
|
||||
<>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<ImageIcon sx={{ marginRight: '15px' }} />
|
||||
{t('run_content.captured_screenshot.title')}
|
||||
<Chip
|
||||
label={`${Object.keys(row.binaryOutput).length} ${Object.keys(row.binaryOutput).length === 1 ? 'item' : 'items'}`}
|
||||
size="small"
|
||||
sx={{ ml: 2, backgroundColor: '#FF00C3', color: 'white' }}
|
||||
/>
|
||||
</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>
|
||||
|
||||
{viewMode === 'vertical' ? (
|
||||
<>
|
||||
{Object.keys(row.binaryOutput).map((key, index) => {
|
||||
try {
|
||||
const imageUrl = row.binaryOutput[key];
|
||||
return (
|
||||
<Accordion defaultExpanded sx={{ mb: 2 }} key={`screenshot-${key}`}>
|
||||
<Accordion defaultExpanded sx={{ mb: 2 }}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`screenshot-${key}-content`}
|
||||
id={`screenshot-${key}-header`}
|
||||
aria-controls="screenshot-content"
|
||||
id="screenshot-header"
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<ImageIcon sx={{ color: '#FF00C3' }} />
|
||||
<Typography variant='h6' sx={{ ml: 2 }}>
|
||||
Screenshot {index+1}
|
||||
<Typography variant='h6'>
|
||||
{t('run_content.captured_screenshot.title', 'Screenshots')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
||||
<ButtonGroup size="small" variant="outlined">
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Button
|
||||
startIcon={<DownloadIcon />}
|
||||
href={imageUrl}
|
||||
download={key}
|
||||
sx={{ borderColor: '#FF00C3', color: '#FF00C3' }}
|
||||
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')}
|
||||
{t('run_content.captured_screenshot.download', 'Download')}
|
||||
</Button>
|
||||
|
||||
{screenshotKeys.length > 1 && (
|
||||
<ButtonGroup size="small">
|
||||
<Button
|
||||
onClick={() => navigateScreenshots('prev')}
|
||||
disabled={currentScreenshotIndex === 0}
|
||||
sx={{
|
||||
borderColor: '#FF00C3',
|
||||
color: currentScreenshotIndex === 0 ? 'gray' : '#FF00C3',
|
||||
'&.Mui-disabled': {
|
||||
borderColor: 'rgba(0, 0, 0, 0.12)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigateScreenshots('next')}
|
||||
disabled={currentScreenshotIndex === screenshotKeys.length - 1}
|
||||
sx={{
|
||||
borderColor: '#FF00C3',
|
||||
color: currentScreenshotIndex === screenshotKeys.length - 1 ? 'gray' : '#FF00C3',
|
||||
'&.Mui-disabled': {
|
||||
borderColor: 'rgba(0, 0, 0, 0.12)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Box>
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={`Screenshot ${key}`}
|
||||
src={row.binaryOutput[screenshotKeys[currentScreenshotIndex]]}
|
||||
alt={`Screenshot ${screenshotKeys[currentScreenshotIndex]}`}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
@@ -996,72 +573,9 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
} catch (e) {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user