Skip to content

Commit

Permalink
too fancy json view (#813)
Browse files Browse the repository at this point in the history
  • Loading branch information
turbocrime authored Mar 25, 2024
1 parent 3974f45 commit 6798f17
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 18 deletions.
122 changes: 110 additions & 12 deletions packages/ui/components/ui/json-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,128 @@
import type { JsonObject, JsonValue } from '@bufbuild/protobuf';
import { JsonView } from 'react-json-view-lite';
import { cn } from '../../lib/utils';
import { CopyToClipboardIconButton } from './copy-to-clipboard-icon-button';
import { DoubleArrowDownIcon, DoubleArrowUpIcon } from '@radix-ui/react-icons';
import { Button } from './button';
import { useCallback, useState } from 'react';

const objectDepth = (o: JsonValue): number =>
o && typeof o === 'object' ? 1 + Math.max(-1, ...Object.values(o).map(objectDepth)) : 0;

const objectLength = (o: JsonValue): number =>
o && typeof o === 'object' ? Object.entries(o).length : 0;

export const JsonViewer = ({ jsonObj }: { jsonObj: JsonObject | JsonValue[] }) => {
const [expandAll, setExpandAll] = useState(false);

const shouldExpandNode = useCallback(
(level: number, value: JsonValue, field?: string) => {
const collapseLevel = 3;
if (expandAll) return true;
if (
// expand all empty, they render small
objectLength(value) === 0 ||
objectDepth(value) === 0 ||
// expand arrays
Array.isArray(value) ||
// always expand below minimum
level < collapseLevel
)
return true;
// begin to collapse, small objects stay open
if (level === collapseLevel) return !field && objectLength(value) < 2;
if (level === collapseLevel + 1) return !field && objectLength(value) < 2;
// close all objects
if (level === collapseLevel + 2) return false;

// nested hidden objects stay open
return true;
},
[expandAll],
);

return (
<div className='mt-5 rounded bg-black p-5'>
<div className='relative w-full'>
<div className='absolute right-0 top-0 w-full'>
<div className='relative flex items-end justify-end gap-2 text-muted-foreground'>
<ExpandAllIconButton expandAll={expandAll} setExpandAll={setExpandAll} />
<CopyToClipboardIconButton text={JSON.stringify(jsonObj)} />
</div>
</div>
</div>
<JsonView
data={jsonObj}
shouldExpandNode={level => level < 2}
shouldExpandNode={shouldExpandNode}
style={{
container: 'bg-black whitespace-pre-wrap break-words font-mono -mx-4',
basicChildStyle: 'mx-4 py-[2px]',
label: 'font-semibold mr-1.5 text-gray-200',
container: cn(
'font-mono',
'mr-[3em]', // pad on the right side, for balance
'text-ellipsis', // visually truncate long strings on the right side
'[&>div>span:first-child]:hidden', // hide first collapse arrow
),
basicChildStyle: cn(
'text-gray-500',
'break-keep truncate', // don't break strings, visually truncate
'pl-[1em]', // indent each level
// compact display if no children
'[&:not(:has(div))]:block',
'only-of-type:[&:not(:has(div))]:p-0',
'only-of-type:[&:not(:has(div>div))]:inline',
'only-of-type:[&:not(:has(div>div))]:*:inline',
'[&>span:last-child]:mr-2', // space after last item, for compact display
),
label: cn(
'text-gray-200',
'inline',
'mr-2', // space after label
),
nullValue: 'text-red-600',
undefinedValue: 'text-red-600',
stringValue: 'text-amber-600',
noQuotesForStringValues: true, // quotes will be styled
stringValue: cn(
'text-amber-600',
'select-all', // entire string selected on click
// quotes from style, so they don't get selected
"before:content-['“'] before:text-gray-500",
"after:content-['”'] after:text-gray-500",
),
booleanValue: 'text-purple-600',
numberValue: 'text-teal-600',
numberValue: 'text-teal-200',
otherValue: 'text-blue-600',
punctuation: 'text-gray-500 mr-1.5',
collapseIcon:
'text-teal-200 text-[16px] p-1 mr-1.5 select-none cursor-pointer after:content-["▼"]',
expandIcon:
'text-teal-200 text-[16px] p-1 mr-1.5 select-none cursor-pointer after:content-["▶"]',
collapsedContent: 'text-amber-600 mr-1.5 after:content-["..."] after:text-xs',
punctuation: cn(
'text-gray-500',
'inline text-sm',
'has-[~div]:mr-2', // space if next item is a label
),
collapseIcon: cn(
'text-teal-600 text-lg',
"has-[~div>div:nth-of-type(2)]:after:content-['▼']",
'leading-[0] w-0 inline-block relative -left-3',
),
expandIcon: 'hidden', // use collapsedContent ellipsis to expand
collapsedContent: "text-teal-600 text-xs leading-[0] after:content-['•••'] after:mx-2",
}}
/>
</div>
);
};

const ExpandAllIconButton = ({
expandAll,
setExpandAll,
}: {
expandAll?: boolean;
setExpandAll: (e: boolean) => void;
}) => (
<Button
className={cn('block', 'size-4')}
variant='link'
onClick={() => setExpandAll(!expandAll)}
size='sm'
>
<div className='size-4 hover:opacity-50'>
{!expandAll ? <DoubleArrowDownIcon /> : <DoubleArrowUpIcon />}
</div>
</Button>
);
4 changes: 2 additions & 2 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@penumbra-zone/getters": "workspace:*",
"@penumbra-zone/bech32": "workspace:*",
"@penumbra-zone/getters": "workspace:*",
"@penumbra-zone/perspective": "workspace:*",
"@penumbra-zone/types": "workspace:*",
"@radix-ui/react-checkbox": "^1.0.4",
Expand All @@ -37,7 +37,7 @@
"humanize-duration": "^3.31.0",
"lucide-react": "^0.354.0",
"react-dom": "^18.2.0",
"react-json-view-lite": "^1.2.1",
"react-json-view-lite": "^1.3.0",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.22.3",
"sonner": "1.4.3",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6798f17

Please sign in to comment.