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
|
// 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/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { MemDBEvent } from "@/api/types"
|
import { MemDBEvent } from "@/api/types"
|
||||||
|
import JSONView from "../../JSONView.tsx"
|
||||||
|
|
||||||
interface ViewSourceModalProps {
|
interface ViewSourceModalProps {
|
||||||
evt: MemDBEvent
|
evt: MemDBEvent
|
||||||
|
@ -21,9 +22,7 @@ interface ViewSourceModalProps {
|
||||||
|
|
||||||
const ViewSourceModal = ({ evt }: ViewSourceModalProps) => {
|
const ViewSourceModal = ({ evt }: ViewSourceModalProps) => {
|
||||||
return <div className="view-source-modal">
|
return <div className="view-source-modal">
|
||||||
<pre>
|
<JSONView data={evt} />
|
||||||
{JSON.stringify(evt, null, " ")}
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,6 @@ div.view-source-modal {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
> pre {
|
|
||||||
margin: 0;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.confirm-message-modal {
|
div.confirm-message-modal {
|
||||||
|
|
Loading…
Add table
Reference in a new issue