Converted to Material UI v7 - bare.

This commit is contained in:
2026-01-31 11:52:15 +01:00
parent 57371feeb0
commit 3dd352df92
12 changed files with 1451 additions and 777 deletions

View File

@@ -1,4 +1,31 @@
import React, { useState, useEffect } from "react";
import {
Box,
Typography,
Paper,
TextField,
Button,
Grid,
Tooltip,
IconButton,
Alert,
Stack,
Divider,
} from "@mui/material";
import {
Search as SearchIcon,
DataObject as DataObjectIcon,
Output as OutputIcon,
UploadFile as UploadFileIcon,
FileOpen as FileOpenIcon,
Restore as RestoreIcon,
FormatAlignLeft as FormatAlignLeftIcon,
Clear as ClearIcon,
ContentCopy as ContentCopyIcon,
Download as DownloadIcon,
Check as CheckIcon,
Refresh as RefreshIcon,
} from "@mui/icons-material";
import jmespath from "jmespath";
function MainPage({
@@ -160,173 +187,275 @@ function MainPage({
};
return (
<>
{/* Description paragraph */}
<div className="row mb-2">
<div className="col-12">
<p className="text-muted text-center mb-2 small">
Validate and test JMESPath expressions against JSON data in
real-time. Enter your JMESPath query and JSON data below to see the
results instantly.
</p>
</div>
</div>
<Box component="main" sx={{ flexGrow: 1, py: 3, px: { xs: 2, md: 4 } }}>
<Box sx={{ mb: 4, maxWidth: 800, mx: "auto" }}>
<Typography
variant="body1"
color="text.secondary"
align="center"
sx={{ mb: 3 }}
>
Validate and test JMESPath expressions against JSON data in real-time.
Enter your JMESPath query and JSON data below to see the results
instantly.
</Typography>
</Box>
{/* Middle Section: JMESPath Expression Input */}
<div className="row mb-2">
<div className="col-12">
<div className="card">
<div className="card-header py-2">
<h6 className="mb-0">
<i className="bi bi-search me-2"></i>
JMESPath Expression
</h6>
</div>
<div className="card-body">
<input
type="text"
className={`form-control jmespath-input ${error ? "error" : "success"}`}
value={jmespathExpression}
onChange={handleJmespathChange}
placeholder="Enter JMESPath expression (e.g., people[*].name)"
/>
<div
className={`alert mt-2 mb-0 d-flex justify-content-between align-items-center ${error ? "alert-danger" : "alert-success"}`}
<Paper
sx={{
mb: 4,
p: 0,
borderRadius: 2,
overflow: "hidden",
bgcolor: "background.paper",
}}
>
<Box
sx={{
px: 3,
py: 1.5,
display: "flex",
alignItems: "center",
gap: 1.5,
bgcolor: "action.hover",
}}
>
<SearchIcon sx={{ fontSize: 24 }} color="primary" />
<Typography variant="subtitle1" fontWeight="600">
JMESPath Expression
</Typography>
</Box>
<Box sx={{ px: 3, pb: 2 }}>
<TextField
fullWidth
placeholder="Enter JMESPath expression (e.g., people[*].name)"
value={jmespathExpression}
onChange={handleJmespathChange}
error={!!error}
helperText={error || " "}
FormHelperTextProps={{
sx: { color: error ? "error.main" : "success.main", fontWeight: 500 }
}}
slotProps={{
input: {
style: { fontFamily: "'JetBrains Mono', 'Fira Code', monospace", fontSize: "1rem" },
},
}}
sx={{
"& .MuiOutlinedInput-root": {
borderRadius: 3,
bgcolor: "background.paper",
}
}}
/>
{showReloadButton && (
<Box sx={{ display: "flex", justifyContent: "center", mt: -1, mb: 2 }}>
<Button
variant="contained"
color="secondary"
onClick={onReloadSampleData}
startIcon={<RefreshIcon />}
size="small"
>
<small className="mb-0">
{error || "Expression is correct"}
</small>
{showReloadButton && (
<button
className="btn btn-light btn-sm ms-2 border"
onClick={() => {
onReloadSampleData();
}}
title="New sample data is available"
>
<i className="bi bi-arrow-clockwise me-1"></i>
Reload Sample Data
</button>
)}
</div>
</div>
</div>
</div>
</div>
New data available - Reload
</Button>
</Box>
)}
</Box>
</Paper>
{/* Lower Middle Section: Input and Output Areas */}
<div className="row flex-grow-1" style={{ minHeight: 0 }}>
{/* Left Panel: JSON Data Input */}
<div className="col-md-6">
<div className="card h-100 d-flex flex-column">
<div className="card-header d-flex justify-content-between align-items-center py-2">
<h6 className="mb-0">
<i className="bi bi-file-earmark-code me-2"></i>
JSON Data
</h6>
<div>
<button
className="btn btn-outline-success btn-sm me-2"
onClick={loadFromDisk}
title="Load JSON object from file"
>
📄 Load an Object
</button>
<button
className="btn btn-outline-info btn-sm me-2"
onClick={loadLogFile}
title="Load JSON Lines log file"
>
📋 Load a Log File
</button>
<button
className="btn btn-outline-primary btn-sm me-2"
onClick={loadSample}
title="Load sample data"
>
Load Sample
</button>
<button
className="btn btn-outline-secondary btn-sm me-2"
onClick={formatJson}
title="Format JSON"
>
Format JSON
</button>
<button
className="btn btn-outline-danger btn-sm"
onClick={clearAll}
title="Clear all inputs"
>
Clear All
</button>
</div>
</div>
<div
className="card-body flex-grow-1 d-flex flex-column"
style={{ minHeight: 0 }}
<Grid container spacing={3} sx={{ flexGrow: 1, minHeight: 0 }}>
<Grid size={{ xs: 12, md: 6 }} sx={{ display: "flex", flexDirection: "column" }}>
<Paper
sx={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
borderRadius: 4,
overflow: "hidden",
bgcolor: "background.paper",
}}
>
<Box
sx={{
px: 2,
py: 2,
bgcolor: "action.hover",
borderBottom: 1,
borderColor: "divider",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
flexWrap: "wrap",
gap: 1,
}}
>
<textarea
className={`form-control json-input flex-grow-1 ${jsonError ? "error" : "success"}`}
<Box sx={{ display: "flex", alignItems: "center" }}>
<DataObjectIcon sx={{ mr: 1, fontSize: 20 }} color="primary" />
<Typography variant="subtitle2" color="text.primary">
JSON Input
</Typography>
</Box>
<Stack direction="row" spacing={1} flexWrap="wrap">
<Tooltip title="Load from Disk">
<span>
<IconButton size="medium" onClick={loadFromDisk} color="primary" aria-label="Load from Disk">
<FileOpenIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Load Logs">
<span>
<IconButton size="medium" onClick={loadLogFile} color="primary" aria-label="Load Logs">
<UploadFileIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Load Sample">
<span>
<IconButton size="medium" onClick={loadSample} color="primary" aria-label="Load Sample">
<RestoreIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Format">
<span>
<IconButton size="medium" onClick={formatJson} color="primary" aria-label="Format">
<FormatAlignLeftIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
<Tooltip title="Clear">
<span>
<IconButton size="medium" onClick={clearAll} color="secondary" aria-label="Clear all inputs">
<ClearIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</Stack>
</Box>
<Box sx={{ p: 2, flexGrow: 1, display: "flex", flexDirection: "column" }}>
<TextField
multiline
fullWidth
value={jsonData}
onChange={handleJsonChange}
placeholder="Enter JSON data here..."
style={{ minHeight: 0, resize: "none" }}
error={!!jsonError}
variant="standard"
slotProps={{
input: {
disableUnderline: true,
style: {
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
height: "100%",
alignItems: "flex-start",
fontSize: "0.85rem",
lineHeight: 1.5,
},
},
}}
sx={{
flexGrow: 1,
"& .MuiInputBase-root": { height: "100%", overflow: "auto" },
}}
/>
{jsonError && (
<div className="alert alert-danger mt-2 mb-0">
<small>{jsonError}</small>
</div>
<Alert severity="error" sx={{ mt: 1, borderRadius: 2 }} variant="filled">
{jsonError}
</Alert>
)}
</div>
</div>
</div>
</Box>
</Paper>
</Grid>
{/* Right Panel: Results */}
<div className="col-md-6">
<div className="card h-100 d-flex flex-column">
<div className="card-header py-2 d-flex justify-content-between align-items-center">
<h6 className="mb-0">
<i className="bi bi-output me-2"></i>
Results
</h6>
<div>
<button
className={`btn btn-sm me-2 ${copySuccess ? "btn-success" : "btn-outline-secondary"}`}
onClick={copyToClipboard}
disabled={!result || result === "null"}
title="Copy result to clipboard"
>
<i className={`bi ${copySuccess ? "bi-check-lg" : "bi-clipboard"} me-1`}></i>
{copySuccess ? "Copied!" : "Copy"}
</button>
<button
className="btn btn-outline-secondary btn-sm"
onClick={downloadResult}
disabled={!result || result === "null"}
title="Download result as JSON file"
>
<i className="bi bi-download me-1"></i>
Download
</button>
</div>
</div>
<div
className="card-body flex-grow-1 d-flex flex-column"
style={{ minHeight: 0 }}
<Grid size={{ xs: 12, md: 6 }} sx={{ display: "flex", flexDirection: "column" }}>
<Paper
sx={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
borderRadius: 4,
overflow: "hidden",
bgcolor: "background.paper",
}}
>
<Box
sx={{
px: 2,
py: 2,
bgcolor: "action.hover",
borderBottom: 1,
borderColor: "divider",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<textarea
className="form-control result-output flex-grow-1"
<Box sx={{ display: "flex", alignItems: "center" }}>
<OutputIcon sx={{ mr: 1, fontSize: 20 }} color="primary" />
<Typography variant="subtitle2" color="text.primary">
Query Result
</Typography>
</Box>
<Stack direction="row" spacing={1}>
<Tooltip title="Copy to Clipboard">
<span>
<IconButton
size="medium"
onClick={copyToClipboard}
disabled={!result || result === "null"}
color={copySuccess ? "success" : "primary"}
>
{copySuccess ? <CheckIcon fontSize="small" /> : <ContentCopyIcon fontSize="small" />}
</IconButton>
</span>
</Tooltip>
<Tooltip title="Download Result">
<span>
<IconButton
size="medium"
onClick={downloadResult}
disabled={!result || result === "null"}
color="primary"
>
<DownloadIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
</Stack>
</Box>
<Box sx={{ p: 2, flexGrow: 1, display: "flex", flexDirection: "column" }}>
<TextField
multiline
fullWidth
value={result}
readOnly
slotProps={{
input: {
readOnly: true,
disableUnderline: true,
style: {
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
height: "100%",
alignItems: "flex-start",
fontSize: "0.85rem",
lineHeight: 1.5,
},
},
}}
variant="standard"
placeholder="Results will appear here..."
style={{ minHeight: 0, resize: "none" }}
sx={{
flexGrow: 1,
"& .MuiInputBase-root": { height: "100%", overflow: "auto" },
}}
/>
</div>
</div>
</div>
</div>
</>
</Box>
</Paper>
</Grid>
</Grid>
</Box>
);
}