rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
172 lines (162 loc) • 5.31 kB
text/typescript
import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";
import { copyToClipboard } from "~/utils/common-utils";
import FontStyles from "~/styles/font-styles";
import BorderStyles from "~/styles/border-styles";
import InputStyles from "~/styles/input-styles";
import CustomStyles from "~/styles/custom-styles";
("json-tree")
export default class JsonTree extends LitElement {
({ type: Object }) data = null;
({ type: String, attribute: "render-style" }) renderStyle = "";
static get styles() {
return [
FontStyles,
BorderStyles,
InputStyles,
css`
:host {
display: flex;
}
:where(button, input[type='checkbox'], [tabindex='0']):focus-visible {
box-shadow: var(--focus-shadow);
}
:where(input[type='text'], input[type='password'], select, textarea):focus-visible {
border-color: var(--primary);
}
.json-tree {
position: relative;
font-family: var(--font-mono);
font-size: var(--font-size-small);
display: inline-block;
overflow: hidden;
word-break: break-all;
flex: 1;
line-height: calc(var(--font-size-small) + 6px);
min-height: 40px;
direction: ltr;
text-align: left;
}
.open-bracket {
display: inline-block;
padding: 0 20px 0 0;
cursor: pointer;
border: 1px solid transparent;
border-radius: min(var(--radius), 0.5rem);
&:hover {
color: var(--primary);
border: 1px solid var(--border);
}
&.expanded:hover ~ .inside-bracket {
border-left: 1px solid var(--muted-foreground);
}
&.expanded:hover ~ .close-bracket {
color: var(--primary);
}
&.collapsed + .inside-bracket,
&.collapsed + .inside-bracket + .close-bracket {
display: none;
}
&.expanded .open-bracket-collapsed {
display: none;
}
&.collapsed .open-bracket-expanded {
display: none;
}
}
.close-bracket {
border: 1px solid transparent;
border-radius: min(var(--radius), 0.5rem);
display: inline-block;
}
.inside-bracket {
padding-left: 12px;
overflow: hidden;
border-left: 1px dotted var(--border);
}
.string {
color: var(--green);
}
.number {
color: var(--blue);
}
.null {
color: var(--destructive);
}
.boolean {
color: var(--purple);
}
.object {
color: var(--foreground);
}
.toolbar {
position: absolute;
top: 5px;
right: 6px;
display: flex;
padding: 2px;
align-items: center;
}
`,
CustomStyles,
];
}
render() {
return html`
<div
class="json-tree"
@click="${(e) => {
if (e.target.classList.contains("btn-copy")) {
copyToClipboard(JSON.stringify(this.data, null, 2), e);
} else {
this.toggleExpand(e);
}
}}"
>
<div class="toolbar">
<button class="btn btn-sm btn-outline toolbar-btn btn-copy" part="btn btn-outline btn-copy">Copy</button>
</div>
${this.generateTree(this.data, true)}
</div>
`;
}
generateTree(data, isLast = false) {
if (data === null) {
return html`<span class="null">null</span>${isLast ? "" : ","}`;
}
if (typeof data === "object" && data instanceof Date === false) {
const detailType = Array.isArray(data) ? "array" : "pure_object";
if (Object.keys(data).length === 0) {
return html`${Array.isArray(data) ? "[ ]," : "{ },"}`;
}
return html`
<div class="open-bracket expanded ${detailType === "array" ? "array" : "object"}">
<span class="open-bracket-expanded">${detailType === "array" ? "[" : "{"}</span>
<span class="open-bracket-collapsed">${detailType === "array" ? "[...]" : "{...}"}</span>
</div>
<div class="inside-bracket">
${Object.keys(data).map(
(key, i, a) =>
html`<div class="item">
${detailType === "pure_object" ? html`"${key}":` : ""} ${this.generateTree(data[key], i === a.length - 1)}
</div>`,
)}
</div>
<div class="close-bracket">${detailType === "array" ? "]" : "}"}${isLast ? "" : ","}</div>
`;
}
return typeof data === "string" || data instanceof Date
? html`<span class="${typeof data}">"${data}"</span>${isLast ? "" : ","}`
: html`<span class="${typeof data}">${data}</span>${isLast ? "" : ","}`;
}
toggleExpand(e) {
const openBracketEl = e.target.closest(".open-bracket");
if (openBracketEl) {
if (openBracketEl.classList.contains("expanded")) {
openBracketEl.classList.replace("expanded", "collapsed");
} else {
openBracketEl.classList.replace("collapsed", "expanded");
}
}
}
}