feat: complete a simple json visualiser
@@ -16,6 +16,8 @@
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"axios": "^1.13.2",
|
||||
"dayjs": "^1.11.19",
|
||||
"jsonpath": "^1.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-redux": "^9.2.0",
|
||||
@@ -25,6 +27,8 @@
|
||||
"tailwindcss": "^4.1.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonpath": "^0.2.4",
|
||||
"@types/lodash": "^4.17.23",
|
||||
"@types/node": "^22.19.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
||||
@@ -20,6 +20,12 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^1.11.19
|
||||
version: 1.11.19
|
||||
jsonpath:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
react:
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3
|
||||
@@ -42,6 +48,12 @@ importers:
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
devDependencies:
|
||||
'@types/jsonpath':
|
||||
specifier: ^0.2.4
|
||||
version: 0.2.4
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.23
|
||||
version: 4.17.23
|
||||
'@types/node':
|
||||
specifier: ^22.19.2
|
||||
version: 22.19.2
|
||||
@@ -559,6 +571,12 @@ packages:
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/jsonpath@0.2.4':
|
||||
resolution: {integrity: sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==}
|
||||
|
||||
'@types/lodash@4.17.23':
|
||||
resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
|
||||
|
||||
'@types/node@22.19.2':
|
||||
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
|
||||
|
||||
@@ -627,6 +645,9 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -671,6 +692,32 @@ packages:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escodegen@1.14.3:
|
||||
resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==}
|
||||
engines: {node: '>=4.0'}
|
||||
hasBin: true
|
||||
|
||||
esprima@1.2.2:
|
||||
resolution: {integrity: sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
estraverse@4.3.0:
|
||||
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
fast-levenshtein@2.0.6:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -756,6 +803,13 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
jsonpath@1.1.1:
|
||||
resolution: {integrity: sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==}
|
||||
|
||||
levn@0.3.0:
|
||||
resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -826,6 +880,9 @@ packages:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
@@ -855,6 +912,10 @@ packages:
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
|
||||
optionator@0.8.3:
|
||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -866,6 +927,10 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prelude-ls@1.1.2:
|
||||
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier@3.7.4:
|
||||
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -955,6 +1020,13 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
static-eval@2.0.2:
|
||||
resolution: {integrity: sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==}
|
||||
|
||||
tailwindcss@4.1.18:
|
||||
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
|
||||
|
||||
@@ -966,11 +1038,18 @@ packages:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
type-check@0.3.2:
|
||||
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
underscore@1.12.1:
|
||||
resolution: {integrity: sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
@@ -1025,6 +1104,10 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
word-wrap@1.2.5:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
@@ -1414,6 +1497,10 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/jsonpath@0.2.4': {}
|
||||
|
||||
'@types/lodash@4.17.23': {}
|
||||
|
||||
'@types/node@22.19.2':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -1483,6 +1570,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
@@ -1546,6 +1635,25 @@ snapshots:
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escodegen@1.14.3:
|
||||
dependencies:
|
||||
esprima: 4.0.1
|
||||
estraverse: 4.3.0
|
||||
esutils: 2.0.3
|
||||
optionator: 0.8.3
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
esprima@1.2.2: {}
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
estraverse@4.3.0: {}
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
fast-levenshtein@2.0.6: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
@@ -1611,6 +1719,17 @@ snapshots:
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
jsonpath@1.1.1:
|
||||
dependencies:
|
||||
esprima: 1.2.2
|
||||
static-eval: 2.0.2
|
||||
underscore: 1.12.1
|
||||
|
||||
levn@0.3.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.1.2
|
||||
type-check: 0.3.2
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
@@ -1660,6 +1779,8 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -1682,6 +1803,15 @@ snapshots:
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
optionator@0.8.3:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
fast-levenshtein: 2.0.6
|
||||
levn: 0.3.0
|
||||
prelude-ls: 1.1.2
|
||||
type-check: 0.3.2
|
||||
word-wrap: 1.2.5
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
@@ -1692,6 +1822,8 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prelude-ls@1.1.2: {}
|
||||
|
||||
prettier@3.7.4: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
@@ -1778,6 +1910,13 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
|
||||
static-eval@2.0.2:
|
||||
dependencies:
|
||||
escodegen: 1.14.3
|
||||
|
||||
tailwindcss@4.1.18: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
@@ -1787,8 +1926,14 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
type-check@0.3.2:
|
||||
dependencies:
|
||||
prelude-ls: 1.1.2
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
underscore@1.12.1: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
update-browserslist-db@1.2.2(browserslist@4.28.1):
|
||||
@@ -1815,4 +1960,6 @@ snapshots:
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useMemo } from "react"
|
||||
import jp, { PathComponent } from "jsonpath"
|
||||
import _ from "lodash"
|
||||
|
||||
export interface JsonTreeNodeProps {
|
||||
data: unknown
|
||||
path: PathComponent[]
|
||||
matchedPaths: string[]
|
||||
}
|
||||
|
||||
export default function JsonTreeNode({ data, path, matchedPaths }: JsonTreeNodeProps) {
|
||||
const currentPathString = useMemo(() => jp.stringify(path), [path])
|
||||
const isMatched = matchedPaths.includes(currentPathString)
|
||||
|
||||
const highlightClass = isMatched ? "bg-yellow-200 ring-2 ring-yellow-400 rounded-sm" : ""
|
||||
|
||||
if (_.isObject(data) && data != null) {
|
||||
const isArray = Array.isArray(data)
|
||||
const entries = Object.entries(data)
|
||||
|
||||
return (
|
||||
<div className="ml-6 border-l border-slate-200 pl-3 transition-all">
|
||||
<span className="text-slate-500">{isArray ? "[" : "{"}</span>
|
||||
{entries.map(([key, value], index) => {
|
||||
const nextPath = [...path, isArray ? Number(key) : key]
|
||||
|
||||
return (
|
||||
<div key={key} className="my-1">
|
||||
{!isArray && <span className="text-indigo-600 font-medium">"{key}": </span>}
|
||||
<JsonTreeNode data={value} path={nextPath} matchedPaths={matchedPaths} />
|
||||
{index < entries.length - 1 && <span className="text-slate-400">,</span>}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<span className="text-slate-500">{isArray ? "]" : "}"}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderValue = () => {
|
||||
if (typeof data === "string") return <span className="text-emerald-600">"{data}"</span>
|
||||
if (typeof data === "number") return <span className="text-blue-600">{data}</span>
|
||||
if (typeof data === "boolean") return <span className="text-orange-600">{String(data)}</span>
|
||||
return <span className="text-gray-400">null</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`inline-block px-1 transition-colors duration-200 ${highlightClass}`}>
|
||||
{renderValue()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -1,138 +1,111 @@
|
||||
import { Link } from "react-router-dom"
|
||||
import { useMemo, useState } from "react"
|
||||
import jp from "jsonpath"
|
||||
import JsonTreeNode from "@/components/json-tree-node"
|
||||
|
||||
/**
|
||||
* Home page component that displays the main landing content.
|
||||
*/
|
||||
export default function Home() {
|
||||
const initialData = {
|
||||
centre_id: "LON-01",
|
||||
location: "London",
|
||||
is_active: true,
|
||||
staff_members: [
|
||||
{ id: 101, name: "Alice", roles: ["Admin", "Manager"] },
|
||||
{ id: 102, name: "Bob", roles: ["Developer"] },
|
||||
],
|
||||
config: {
|
||||
colour_scheme: "Dark Mode",
|
||||
retention_days: 30,
|
||||
},
|
||||
}
|
||||
|
||||
const [jsonInput, setJsonInput] = useState<string>(JSON.stringify(initialData, null, 2))
|
||||
const [query, setQuery] = useState<string>("$.staff_members[*].name")
|
||||
|
||||
// 计算匹配结果
|
||||
const result = useMemo(() => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonInput)
|
||||
const nodes = jp.nodes(parsed, query)
|
||||
return {
|
||||
parsed,
|
||||
matchedPaths: nodes.map((n) => jp.stringify(n.path)),
|
||||
error: null,
|
||||
}
|
||||
} catch (e) {
|
||||
return { parsed: null, matchedPaths: [], error: (e as Error).message }
|
||||
}
|
||||
}, [jsonInput, query])
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Hero Section */}
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-gray-900 sm:text-5xl md:text-6xl">
|
||||
Welcome to OnixByte React Template
|
||||
<div className="min-h-screen bg-slate-50 p-6 font-sans text-slate-900">
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<header className="flex flex-col gap-2">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-slate-800">
|
||||
TypeScript JSONPath Explorer
|
||||
</h1>
|
||||
<p className="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
||||
A modern React application template with <b>TypeScript</b>, <b>Tailwind CSS</b>,{" "}
|
||||
<b>Redux</b>, and <b>React Router</b> for seamless navigation.
|
||||
<p className="text-slate-500 text-sm">
|
||||
Debug and visualises your JSONPath queries with real-time highlighting.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="mt-12">
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{/* Feature 1 */}
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="h-8 w-8 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* 控制面板 */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div className="bg-slate-50 px-4 py-2 border-b border-slate-200">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
JSON Source
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="w-full h-100 p-4 font-mono text-sm outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all resize-none"
|
||||
value={jsonInput}
|
||||
onChange={(e) => setJsonInput(e.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Fast Development</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
Built with Vite for lightning-fast development experience and hot module
|
||||
replacement.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature 2 */}
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="h-8 w-8 text-green-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
|
||||
<label className="block text-xs font-semibold uppercase tracking-wider text-slate-500 mb-2">
|
||||
JSONPath Expression
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full p-3 font-mono text-sm border border-slate-200 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="e.g. $..roles"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Type Safety</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
Full TypeScript support with strict type checking for better code quality and
|
||||
developer experience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="h-8 w-8 text-purple-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zM21 5a2 2 0 00-2-2h-4a2 2 0 00-2 2v12a4 4 0 004 4h4a2 2 0 002-2V5z"
|
||||
{/* 可视化面板 */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
|
||||
<div className="bg-slate-50 px-4 py-2 border-b border-slate-200 flex justify-between items-center">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
Visualised Result
|
||||
</span>
|
||||
<span className="text-xs font-medium px-2 py-0.5 bg-indigo-100 text-indigo-700 rounded-full">
|
||||
{result.matchedPaths.length} matches
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="p-6 overflow-auto max-h-[580px] font-mono text-sm leading-relaxed">
|
||||
{result.error ? (
|
||||
<div className="bg-red-50 text-red-600 p-4 rounded-lg border border-red-100 text-xs">
|
||||
<strong>Error:</strong> {result.error}
|
||||
</div>
|
||||
) : (
|
||||
<JsonTreeNode
|
||||
data={result.parsed}
|
||||
path={["$"]}
|
||||
matchedPaths={result.matchedPaths}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Modern Styling</h3>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
Tailwind CSS for utility-first styling with responsive design and modern UI
|
||||
components.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Call to Action */}
|
||||
<div className="bg-blue-50 rounded-lg p-6 text-center">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Ready to start building?</h2>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Explore the navigation above to see React Router in action, or check out the source code
|
||||
to understand the implementation.
|
||||
</p>
|
||||
<div className="flex justify-center space-x-4">
|
||||
<Link
|
||||
to="/about"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Learn More
|
||||
</Link>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Get in Touch
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||