forked from Mirrors/gomuks
web/viewsource: add fancy JSON rendering
This commit is contained in:
parent
6b4b12435a
commit
fa004a639e
4 changed files with 158 additions and 9 deletions
36
web/src/ui/JSONView.css
Normal file
36
web/src/ui/JSONView.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
pre.json-view {
|
||||
white-space: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
margin: 0;
|
||||
|
||||
ul, ol {
|
||||
margin: 0;
|
||||
|
||||
> li {
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
span.json-collapsed {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 .25rem;
|
||||
}
|
||||
|
||||
/* If the screen is wide enough, make line-wrapped strings aligned after the object key */
|
||||
@media screen and (min-width: 800px) {
|
||||
li.json-object-entry:has(> span.json-comma-container > span.json-string) {
|
||||
display: flex;
|
||||
|
||||
span.json-object-key {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span.json-object-entry-colon {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
web/src/ui/JSONView.tsx
Normal file
120
web/src/ui/JSONView.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
// gomuks - A Matrix client written in Go.
|
||||
// Copyright (C) 2024 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { useReducer } from "react"
|
||||
import "./JSONView.css"
|
||||
|
||||
interface JSONViewProps {
|
||||
data: unknown
|
||||
}
|
||||
|
||||
interface JSONViewPropsWithKey extends JSONViewProps {
|
||||
objectKey?: string
|
||||
trailingComma?: boolean
|
||||
noCollapse?: boolean
|
||||
}
|
||||
|
||||
function renderJSONString(data: string, styleClass: string = "s2") {
|
||||
return <span className={`json-string ${styleClass}`}>{JSON.stringify(data)}</span>
|
||||
}
|
||||
|
||||
function renderJSONValue(data: unknown, collapsed: boolean) {
|
||||
switch (typeof data) {
|
||||
case "object":
|
||||
if (data === null) {
|
||||
return <span className="json-null kc">null</span>
|
||||
} else if (Array.isArray(data)) {
|
||||
if (data.length === 0) {
|
||||
return null
|
||||
} else if (collapsed) {
|
||||
return <span className="json-collapsed">…</span>
|
||||
}
|
||||
return <ol className="json-array-children">
|
||||
{data.map((item, i) =>
|
||||
<li key={i} className="json-array-entry"><JSONValueWithKey data={item}/></li>)}
|
||||
</ol>
|
||||
} else {
|
||||
const entries = Object.entries(data)
|
||||
if (entries.length === 0) {
|
||||
return null
|
||||
} else if (collapsed) {
|
||||
return <span className="json-collapsed">…</span>
|
||||
}
|
||||
return <ul className="json-object-children">
|
||||
{entries.map(([key, value], index, arr) =>
|
||||
<li key={key} className="json-object-entry">
|
||||
<JSONValueWithKey data={value} objectKey={key} trailingComma={index < arr.length - 1}/>
|
||||
</li>)}
|
||||
</ul>
|
||||
}
|
||||
case "string":
|
||||
return renderJSONString(data)
|
||||
case "number":
|
||||
return <span className="json-number mf">{data}</span>
|
||||
case "boolean":
|
||||
return <span className="json-boolean kc">{data ? "true" : "false"}</span>
|
||||
default:
|
||||
return <span className="json-unknown">undefined</span>
|
||||
}
|
||||
}
|
||||
|
||||
function JSONValueWithKey({ data, objectKey, trailingComma, noCollapse }: JSONViewPropsWithKey) {
|
||||
const [collapsed, toggleCollapsed] = useReducer(collapsed => !collapsed, false)
|
||||
const renderedKey = objectKey
|
||||
? <span className="json-object-key">
|
||||
{renderJSONString(objectKey, "nt")}
|
||||
<span className="json-object-entry-colon p">: </span>
|
||||
</span>
|
||||
: null
|
||||
const renderedSuffix = trailingComma
|
||||
? <span className="json-object-comma p">,</span>
|
||||
: null
|
||||
const collapseButton = noCollapse ? null :
|
||||
<button onClick={toggleCollapsed} title={collapsed ? "Expand" : "Collapse"}>
|
||||
{collapsed ? "+" : "-"}
|
||||
</button>
|
||||
if (Array.isArray(data)) {
|
||||
return <>
|
||||
{renderedKey}
|
||||
{collapseButton}
|
||||
<span className="json-array-bracket p">[</span>
|
||||
{renderJSONValue(data, collapsed)}
|
||||
<span className="json-array-bracket p">]</span>
|
||||
{renderedSuffix}
|
||||
</>
|
||||
} else if (data !== null && typeof data === "object") {
|
||||
return <>
|
||||
{renderedKey}
|
||||
{collapseButton}
|
||||
<span className="json-object-brace p">{"{"}</span>
|
||||
{renderJSONValue(data, collapsed)}
|
||||
<span className="json-object-brace p">{"}"}</span>
|
||||
{renderedSuffix}
|
||||
</>
|
||||
}
|
||||
return <>
|
||||
{renderedKey}
|
||||
<span className="json-comma-container">
|
||||
{renderJSONValue(data, collapsed)}
|
||||
{renderedSuffix}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
|
||||
export default function JSONView({ data }: JSONViewProps) {
|
||||
return <pre className="json-view chroma">
|
||||
<JSONValueWithKey data={data} noCollapse={true} />
|
||||
</pre>
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { MemDBEvent } from "@/api/types"
|
||||
import JSONView from "../../JSONView.tsx"
|
||||
|
||||
interface ViewSourceModalProps {
|
||||
evt: MemDBEvent
|
||||
|
@ -21,9 +22,7 @@ interface ViewSourceModalProps {
|
|||
|
||||
const ViewSourceModal = ({ evt }: ViewSourceModalProps) => {
|
||||
return <div className="view-source-modal">
|
||||
<pre>
|
||||
{JSON.stringify(evt, null, " ")}
|
||||
</pre>
|
||||
<JSONView data={evt} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -56,12 +56,6 @@ div.view-source-modal {
|
|||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
|
||||
> pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
div.confirm-message-modal {
|
||||
|
|
Loading…
Add table
Reference in a new issue