Merge pull request #574 from getmaxun/all-record-ui

feat: runs ui v2
This commit is contained in:
Rohit
2025-04-30 22:01:28 +05:30
committed by GitHub
7 changed files with 220 additions and 712 deletions

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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"
}
},

View File

@@ -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": {

View File

@@ -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": "スクリーンショットのレンダリングに失敗しました"
}
},

View File

@@ -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": "渲染截图失败"
}
},

View File

@@ -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'
}}
>
&lt;
</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'
}}
>
&gt;
</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>