feat: add JsonCodeEditor component and integrate it into JsonGrid and JsonViewer
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"pnpm": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useMemo, useRef } from "react"
|
||||
|
||||
type JsonCodeEditorProps = {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const tokenRegex = /"(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"\s*:?|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g
|
||||
|
||||
function escapeHtml(input: string): string {
|
||||
return input
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
}
|
||||
|
||||
function getTokenClass(token: string): string {
|
||||
if (token.startsWith('"')) {
|
||||
return token.endsWith(":") ? "text-indigo-600" : "text-emerald-600"
|
||||
}
|
||||
if (token === "true" || token === "false") {
|
||||
return "text-violet-600"
|
||||
}
|
||||
if (token === "null") {
|
||||
return "text-slate-500 italic"
|
||||
}
|
||||
return "text-amber-600"
|
||||
}
|
||||
|
||||
function highlightJson(input: string): string {
|
||||
let result = ""
|
||||
let lastIndex = 0
|
||||
|
||||
for (const match of input.matchAll(tokenRegex)) {
|
||||
const token = match[0]
|
||||
const index = match.index ?? 0
|
||||
|
||||
result += escapeHtml(input.slice(lastIndex, index))
|
||||
result += `<span class="${getTokenClass(token)}">${escapeHtml(token)}</span>`
|
||||
lastIndex = index + token.length
|
||||
}
|
||||
|
||||
result += escapeHtml(input.slice(lastIndex))
|
||||
return result || " "
|
||||
}
|
||||
|
||||
export default function JsonCodeEditor({ value, onChange }: JsonCodeEditorProps) {
|
||||
const highlighted = useMemo(() => highlightJson(value), [value])
|
||||
const preRef = useRef<HTMLPreElement>(null)
|
||||
|
||||
const syncScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
|
||||
if (!preRef.current) return
|
||||
preRef.current.scrollTop = event.currentTarget.scrollTop
|
||||
preRef.current.scrollLeft = event.currentTarget.scrollLeft
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex-1 min-h-0">
|
||||
<pre
|
||||
ref={preRef}
|
||||
aria-hidden
|
||||
className="absolute inset-0 m-0 p-4 font-mono text-sm leading-6 overflow-auto whitespace-pre-wrap wrap-break-word"
|
||||
>
|
||||
<code dangerouslySetInnerHTML={{ __html: highlighted }} />
|
||||
</pre>
|
||||
<textarea
|
||||
className="absolute inset-0 w-full h-full p-4 font-mono text-sm leading-6 resize-none overflow-auto bg-transparent text-transparent caret-slate-900 outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onScroll={syncScroll}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import JsonCodeEditor from "@/components/json-code-editor"
|
||||
|
||||
type RowRecord = Record<string, unknown>
|
||||
|
||||
@@ -84,12 +85,7 @@ export default function JsonGrid() {
|
||||
{t("jsonGrid.jsonInput")}
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="flex-1 w-full p-4 font-mono text-sm outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all resize-none overflow-auto"
|
||||
value={jsonInput}
|
||||
onChange={(e) => setJsonInput(e.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<JsonCodeEditor value={jsonInput} onChange={setJsonInput} />
|
||||
</div>
|
||||
|
||||
<div className="w-[65%] bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex flex-col min-h-0">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import jp from "jsonpath"
|
||||
import JsonTreeNode from "@/components/json-tree-node"
|
||||
import JsonCodeEditor from "@/components/json-code-editor"
|
||||
|
||||
/**
|
||||
* JSON Viewer page component that displays the JSON visualisation tool in DevLab.
|
||||
@@ -83,12 +84,7 @@ export default function JsonViewer() {
|
||||
{t("jsonViewer.jsonSource")}
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="flex-1 w-full p-4 font-mono text-sm outline-none focus:ring-2 focus:ring-indigo-500/20 transition-all resize-none overflow-auto"
|
||||
value={jsonInput}
|
||||
onChange={(e) => setJsonInput(e.target.value)}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<JsonCodeEditor value={jsonInput} onChange={setJsonInput} />
|
||||
</div>
|
||||
|
||||
{/* JSONPath Expression - fixed height */}
|
||||
|
||||