Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
<script lang="ts">
import { ref, defineComponent, h, type VNode } from "vue";

const JsonNode = defineComponent({
  name: "JsonNode",
  props: {
    data: { required: true },
    indent: { type: Number, default: 0 },
    keyName: { type: String, default: "" },
    startCollapsed: { type: Boolean, default: true },
    expandAll: { type: Boolean, default: false },
    onCopy: { type: Function, default: null },
  },
  setup(props) {
    const collapsed = ref(props.startCollapsed);
    const wasManuallyExpanded = ref(false);

    const handleToggle = (e: Event) => {
      e.stopPropagation();
      if (collapsed.value) {
        wasManuallyExpanded.value = true;
      }
      collapsed.value = !collapsed.value;
    };

    const copyValue = (value: unknown) => {
      let text: string;
      if (value === null) {
        text = "null";
      } else if (typeof value === "string") {
        text = value;
      } else if (typeof value === "object") {
        text = JSON.stringify(value);
      } else {
        text = String(value);
      }
      navigator.clipboard.writeText(text);
      if (props.onCopy) {
        props.onCopy(text);
      }
    };

    return (): VNode => {
      const childStartCollapsed = props.expandAll ? props.startCollapsed : (wasManuallyExpanded.value ? false : true);
      const childExpandAll = props.expandAll;
      const data = props.data;
      const indent = props.indent;
      const pad = "  ".repeat(indent);
      const keyPrefix = props.keyName ? `"${props.keyName}": ` : "";

      if (data === null) {
        return h("span", { class: "json-null json-clickable", onClick: () => copyValue(data) }, keyPrefix + "null");
      }

      if (typeof data === "boolean") {
        return h("span", { class: "json-bool json-clickable", onClick: () => copyValue(data) }, keyPrefix + String(data));
      }

      if (typeof data === "number") {
        return h("span", { class: "json-num json-clickable", onClick: () => copyValue(data) }, keyPrefix + String(data));
      }

      if (typeof data === "string") {
        return h("span", { class: "json-str json-clickable", onClick: () => copyValue(data) }, keyPrefix + `"${data}"`);
      }

      if (Array.isArray(data)) {
        if (data.length === 0) {
          return h("span", { class: "json-clickable", onClick: () => copyValue(data) }, keyPrefix + "[]");
        }

        const toggle = h(
          "span",
          {
            class: "json-toggle",
            onClick: handleToggle,
          },
          collapsed.value ? "+" : "-"
        );

        if (collapsed.value) {
          return h("span", { class: "json-clickable", onClick: () => copyValue(data) }, [
            keyPrefix,
            toggle,
            ` [${data.length} items]`,
          ]);
        }

        const children: VNode[] = data.map((item: unknown, i: number) =>
          h("div", { key: i }, [
            pad + "  ",
            h(JsonNode, { data: item, indent: indent + 1, startCollapsed: childStartCollapsed, expandAll: childExpandAll, onCopy: props.onCopy }),
            i < data.length - 1 ? "," : "",
          ])
        );

        return h("span", {}, [
          keyPrefix,
          toggle,
          " [\n",
          ...children,
          "\n" + pad + "]",
        ]);
      }

      if (typeof data === "object") {
        const keys = Object.keys(data);
        if (keys.length === 0) {
          return h("span", { class: "json-clickable", onClick: () => copyValue(data) }, keyPrefix + "{}");
        }

        const toggle = h(
          "span",
          {
            class: "json-toggle",
            onClick: handleToggle,
          },
          collapsed.value ? "+" : "-"
        );

        if (collapsed.value) {
          return h("span", { class: "json-clickable", onClick: () => copyValue(data) }, [
            keyPrefix,
            toggle,
            ` {${keys.length} keys}`,
          ]);
        }

        const children: VNode[] = keys.map((key: string, i: number) =>
          h("div", { key }, [
            pad + "  ",
            h(JsonNode, { data: (data as Record<string, unknown>)[key], indent: indent + 1, keyName: key, startCollapsed: childStartCollapsed, expandAll: childExpandAll, onCopy: props.onCopy }),
            i < keys.length - 1 ? "," : "",
          ])
        );

        return h("span", {}, [
          keyPrefix,
          toggle,
          " {\n",
          ...children,
          "\n" + pad + "}",
        ]);
      }

      return h("span", {}, String(data));
    };
  },
});

export default JsonNode;
</script>

<style>
.json-clickable {
  cursor: pointer;
  color: #000;
}

.json-clickable:hover {
  background: #e8e8e8;
}

.json-toggle {
  cursor: pointer;
  color: #666;
  font-weight: bold;
  user-select: none;
  display: inline-block;
  width: 1em;
  text-align: center;
}

.json-toggle:hover {
  color: #000;
}

.json-str {
  color: #000;
}

.json-num {
  color: #005cc5;
}

.json-bool {
  color: #d73a49;
}

.json-null {
  color: #6a737d;
}
</style>