Skip to content

Commit 751d109

Browse files
committed
feat: add InteractiveJsonViewer component and integrate it into DatasetDetails, enhance DatasetTypes with constrainWidth prop
1 parent 4724a62 commit 751d109

File tree

6 files changed

+125
-7
lines changed

6 files changed

+125
-7
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@tanstack/react-router-devtools": "^1.116.0",
4040
"@tanstack/react-table": "^8.21.3",
4141
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
42+
"@uiw/react-json-view": "^2.0.0-alpha.33",
4243
"big.js": "^7.0.1",
4344
"buffer": "^6.0.3",
4445
"class-variance-authority": "^0.7.1",

src/components/DatasetTypes.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface DatasetTypesProps {
77
layout?: 'horizontal' | 'vertical';
88
maxDisplay?: number;
99
showMoreCount?: boolean;
10+
constrainWidth?: boolean; // Whether to constrain the width of each type card
1011
}
1112

1213
export function DatasetTypes({
@@ -15,6 +16,7 @@ export function DatasetTypes({
1516
layout = 'horizontal',
1617
maxDisplay,
1718
showMoreCount = false,
19+
constrainWidth = true, // Default to true for table views
1820
}: DatasetTypesProps) {
1921
const displayTypes = processSchemaPathsToTypes(schemaPaths);
2022

@@ -59,14 +61,24 @@ export function DatasetTypes({
5961
{typesToShow.map((type) => (
6062
<div
6163
key={type.name}
62-
className="inline-block w-fit cursor-pointer rounded-lg border px-2 py-1 text-sm font-medium whitespace-nowrap transition-transform duration-200 hover:scale-105"
64+
className={`inline-block w-fit cursor-pointer rounded-lg border px-2 py-1 text-sm font-medium transition-transform duration-200 hover:scale-105 ${
65+
constrainWidth ? 'max-w-40' : ''
66+
}`}
6367
style={type.style}
64-
title={type.type ? `${type.name} (${type.type})` : type.name}
68+
title={type.type ? `${type.name} (${type.type})\nFull path: ${type.fullPath}` : `${type.name}\nFull path: ${type.fullPath}`}
6569
>
6670
<div className="flex flex-col items-center gap-0.5">
67-
<span>{type.name}</span>
71+
<span
72+
className={`w-full text-center ${constrainWidth ? 'truncate' : ''}`}
73+
>
74+
{type.name}
75+
</span>
6876
{type.type && (
69-
<span className="rounded bg-black/10 px-1 py-0.5 text-xs font-normal opacity-70">
77+
<span
78+
className={`w-full rounded bg-black/10 px-1 py-0.5 text-center text-xs font-normal opacity-70 ${
79+
constrainWidth ? 'truncate' : ''
80+
}`}
81+
>
7082
{type.type}
7183
</span>
7284
)}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import JsonView from '@uiw/react-json-view';
2+
3+
interface JsonViewerProps {
4+
schemaPaths?: Array<{ path?: string | null; type?: string | null }>;
5+
className?: string;
6+
}
7+
8+
interface JsonNode {
9+
[key: string]: JsonNode | string;
10+
}
11+
12+
function buildJsonStructure(
13+
schemaPaths: Array<{ path?: string | null; type?: string | null }>
14+
): JsonNode {
15+
const result: JsonNode = {};
16+
17+
schemaPaths.forEach(({ path, type }) => {
18+
if (!path || !type) return;
19+
20+
const parts = path.split('.');
21+
let current = result;
22+
23+
// Navigate through the path, creating nested objects
24+
for (let i = 0; i < parts.length - 1; i++) {
25+
const part = parts[i];
26+
if (typeof current[part] !== 'object') {
27+
current[part] = {};
28+
}
29+
current = current[part] as JsonNode;
30+
}
31+
32+
// Set the final value with its type
33+
const lastPart = parts[parts.length - 1];
34+
current[lastPart] = type;
35+
});
36+
37+
return result;
38+
}
39+
40+
export function InteractiveJsonViewer({
41+
schemaPaths,
42+
className,
43+
}: JsonViewerProps) {
44+
if (!schemaPaths || schemaPaths.length === 0) {
45+
return (
46+
<div className={`${className} text-muted-foreground text-sm`}>
47+
No schema data available
48+
</div>
49+
);
50+
}
51+
52+
const jsonStructure = buildJsonStructure(schemaPaths);
53+
54+
return (
55+
<div className={`${className} max-h-96 overflow-y-auto`}>
56+
<JsonView
57+
value={jsonStructure}
58+
displayDataTypes={false}
59+
displayObjectSize={false}
60+
enableClipboard={false}
61+
collapsed={2} // Collapse after 2 levels for compact view
62+
style={
63+
{
64+
backgroundColor: 'transparent',
65+
fontFamily: "'Space Mono', monospace", // Use project's monospace font
66+
color: 'rgb(229 231 235)', // text-gray-200 for better contrast
67+
'--w-rjv-key-string': 'rgb(229 231 235)', // Override key color to light gray
68+
'--w-rjv-type-string-color': 'rgb(251 146 60)', // Keep orange for values
69+
'--w-rjv-type-boolean-color': 'rgb(34 197 94)', // Keep green for booleans
70+
'--w-rjv-indent-width': '24px', // Increase indentation for nested keys
71+
} as React.CSSProperties
72+
}
73+
/>
74+
</div>
75+
);
76+
}

src/modules/datasets/dataset/buildDatasetDetails.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DatasetQuery } from '@/graphql/graphql';
22
import CopyButton from '@/components/CopyButton';
33
import { DatasetTypes } from '@/components/DatasetTypes';
4+
import { InteractiveJsonViewer } from '@/components/InteractiveJsonViewer';
45
import SmartLinkGroup from '@/components/SmartLinkGroup';
56
import TransferEvent from '@/modules/events/TransferEvent';
67
import { multiaddrHexToHuman } from '@/utils/format';
@@ -15,7 +16,10 @@ export function buildDatasetDetails({
1516
isSchemaLoading,
1617
}: {
1718
dataset: DatasetQuery['dataset'];
18-
schemaPaths?: Array<{ path?: string | null }>;
19+
schemaPaths?: Array<{
20+
path?: string | null;
21+
type?: string | null;
22+
}>;
1923
isSchemaLoading?: boolean;
2024
}) {
2125
if (!dataset) {
@@ -51,8 +55,12 @@ export function buildDatasetDetails({
5155
schemaPaths={schemaPaths}
5256
isLoading={isSchemaLoading}
5357
layout="horizontal"
58+
constrainWidth={false}
5459
/>
5560
),
61+
'Data Structure': (
62+
<InteractiveJsonViewer schemaPaths={schemaPaths} className="mt-2" />
63+
),
5664
...(firstTimestamp && {
5765
Created: (
5866
<p>

src/modules/datasets/utils/datasetTypeUtils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface DatasetTypeInfo {
22
name: string;
3+
fullPath: string; // Full path for tooltip
34
type?: string; // Data type like 'string', 'boolean', etc.
45
style: {
56
backgroundColor: string;
@@ -57,8 +58,12 @@ export function createDynamicType(
5758
const hueVariation = (hash % 20) - 10; // -10 to +10 degrees variation
5859
const finalHue = (selectedHue + hueVariation + 360) % 360;
5960

60-
// Convert path to a readable name (e.g., "targetPrivacyPassV2_prod" -> "Target Privacy Pass V2 Prod")
61-
const name = path
61+
// Extract only the last part of the path after the last dot
62+
const pathParts = path.split('.');
63+
const displayName = pathParts[pathParts.length - 1];
64+
65+
// Convert to readable name if needed (e.g., "accessToken" -> "Access Token")
66+
const name = displayName
6267
.replace(/([A-Z])/g, ' $1') // Add space before capital letters
6368
.replace(/_/g, ' ') // Replace underscores with spaces
6469
.replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter
@@ -67,6 +72,7 @@ export function createDynamicType(
6772

6873
return {
6974
name,
75+
fullPath: path,
7076
type,
7177
style: {
7278
backgroundColor: `hsla(${finalHue}, 80%, 60%, 0.15)`,

0 commit comments

Comments
 (0)