Skip to content

Commit a17192e

Browse files
chore: hide json view under a button (#93)
* feat: view json in a button * feat: add syntax highlights --------- Co-authored-by: Neil Campbell <neil.campbell@makerx.com.au>
1 parent 420772f commit a17192e

25 files changed

+245
-191
lines changed

src/features/accounts/components/account-details.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { Account } from '../models'
33
import { cn } from '@/features/common/utils'
44
import { AccountActivityTabs } from './account-activity-tabs'
55
import { AccountInfo } from './account-info'
6-
import { accountActivityLabel, accountApplicationLabel, accountAssetLabel, accountJsonLabel } from './labels'
7-
import { JsonView } from '@/features/common/components/json-view'
6+
import { accountActivityLabel, accountApplicationLabel, accountAssetLabel } from './labels'
87
import { AccountAssetTabs } from './account-asset-tabs'
98
import { AccountApplicationTabs } from './account-application-tabs'
109

@@ -19,7 +18,7 @@ export function AccountDetails({ account }: Props) {
1918
<Card className={cn('p-4')}>
2019
<CardContent className={cn('text-sm space-y-2')}>
2120
<h1 className={cn('text-2xl text-primary font-bold')}>{accountActivityLabel}</h1>
22-
<div className={cn('border-solid border-2 border-border grid')}>
21+
<div className={cn('border-solid border-2 border-border grid grid-cols-[1fr_max-content]')}>
2322
<AccountActivityTabs account={account} />
2423
</div>
2524
</CardContent>
@@ -41,15 +40,6 @@ export function AccountDetails({ account }: Props) {
4140
</div>
4241
</CardContent>
4342
</Card>
44-
45-
<Card className={cn('p-4')}>
46-
<CardContent aria-label={accountJsonLabel} className={cn('text-sm space-y-2')}>
47-
<h1 className={cn('text-2xl text-primary font-bold')}>{accountJsonLabel}</h1>
48-
<div className={cn('border-solid border-2 border-border h-96 grid')}>
49-
<JsonView json={account.json} />
50-
</div>
51-
</CardContent>
52-
</Card>
5343
</div>
5444
)
5545
}

src/features/accounts/components/account-info.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
accountMinBalanceLabel,
1818
accountRekeyedToLabel,
1919
} from './labels'
20+
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'
2021

2122
type Props = {
2223
account: Account
@@ -87,7 +88,10 @@ export function AccountInfo({ account }: Props) {
8788
return (
8889
<Card aria-label={accountInformationLabel} className={cn('p-4')}>
8990
<CardContent className={cn('text-sm space-y-2')}>
90-
<DescriptionList items={accountInfoItems} />
91+
<div className={cn('grid grid-cols-[1fr_max-content]')}>
92+
<DescriptionList items={accountInfoItems} />
93+
<OpenJsonViewDialogButton json={account.json} />
94+
</div>
9195
</CardContent>
9296
</Card>
9397
)

src/features/accounts/components/labels.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export const accountJsonLabel = 'Account JSON'
21
export const accountActivityLabel = 'Activity'
32
export const accountAssetLabel = 'Assets'
43
export const accountApplicationLabel = 'Applications'

src/features/applications/components/application-details.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
applicationLocalStateByteLabel,
2424
applicationLocalStateUintLabel,
2525
applicationTransactionsLabel,
26-
applicationJsonLabel,
2726
applicationNameLabel,
2827
} from './labels'
2928
import { isDefined } from '@/utils/is-defined'
@@ -33,8 +32,8 @@ import { ApplicationBoxes } from './application-boxes'
3332
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
3433
import { ApplicationLiveTransactions } from './application-live-transactions'
3534
import { ApplicationTransactionHistory } from './application-transaction-history'
36-
import { JsonView } from '@/features/common/components/json-view'
3735
import { AccountLink } from '@/features/accounts/components/account-link'
36+
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'
3837

3938
type Props = {
4039
application: Application
@@ -100,7 +99,10 @@ export function ApplicationDetails({ application }: Props) {
10099
<div className={cn('space-y-6 pt-7')}>
101100
<Card aria-label={applicationDetailsLabel} className={cn('p-4')}>
102101
<CardContent className={cn('text-sm space-y-2')}>
103-
<DescriptionList items={applicationItems} />
102+
<div className={cn('grid grid-cols-[1fr_max-content]')}>
103+
<DescriptionList items={applicationItems} />
104+
<OpenJsonViewDialogButton json={application.json} />
105+
</div>
104106
</CardContent>
105107
</Card>
106108
<Card aria-label={applicationApprovalProgramLabel} className={cn('p-4')}>
@@ -157,14 +159,6 @@ export function ApplicationDetails({ application }: Props) {
157159
</Tabs>
158160
</CardContent>
159161
</Card>
160-
<Card className={cn('p-4')}>
161-
<CardContent className={cn('text-sm space-y-2')}>
162-
<h1 className={cn('text-2xl text-primary font-bold')}>{applicationJsonLabel}</h1>
163-
<div className={cn('border-solid border-2 border-border h-96 grid')}>
164-
<JsonView json={application.json} />
165-
</div>
166-
</CardContent>
167-
</Card>
168162
</div>
169163
)
170164
}

src/features/applications/components/labels.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,3 @@ export const applicationLiveTransactionsTabId = 'live-transactions'
2626
export const applicationLiveTransactionsTabLabel = 'Live Transactions'
2727
export const applicationHistoricalTransactionsTabId = 'historical-transactions'
2828
export const applicationHistoricalTransactionsTabLabel = 'Historical Transactions'
29-
30-
export const applicationJsonLabel = 'Application JSON'

src/features/assets/components/asset-details.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
assetHistoricalTransactionsTabId,
1919
assetHistoricalTransactionsTabLabel,
2020
assetIdLabel,
21-
assetJsonLabel,
2221
assetLiveTransactionsTabId,
2322
assetLiveTransactionsTabLabel,
2423
assetManagerLabel,
@@ -36,12 +35,16 @@ import { AssetMetadata } from './asset-metadata'
3635
import { AssetTransactionHistory } from './asset-transaction-history'
3736
import { AssetLiveTransactions } from './asset-live-transactions'
3837
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
39-
import { JsonView } from '@/features/common/components/json-view'
38+
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'
4039

4140
type Props = {
4241
asset: Asset
4342
}
4443

44+
const expandAssetJsonLevel = (level: number) => {
45+
return level < 2
46+
}
47+
4548
export function AssetDetails({ asset }: Props) {
4649
const assetItems = useMemo(
4750
() => [
@@ -137,7 +140,10 @@ export function AssetDetails({ asset }: Props) {
137140
<CardContent className={cn('text-sm space-y-2')}>
138141
<div className={cn('grid grid-cols-[1fr_max-content]')}>
139142
<DescriptionList items={assetItems} />
140-
<AssetMedia asset={asset} />
143+
<div className="ml-2 grid gap-2">
144+
<OpenJsonViewDialogButton json={asset.json} expandJsonLevel={expandAssetJsonLevel} />
145+
<AssetMedia asset={asset} />
146+
</div>
141147
</div>
142148
</CardContent>
143149
</Card>
@@ -152,14 +158,6 @@ export function AssetDetails({ asset }: Props) {
152158

153159
<AssetMetadata metadata={asset.metadata} />
154160
<AssetTraits traits={asset.traits} />
155-
<Card className={cn('p-4')}>
156-
<CardContent className={cn('text-sm space-y-2')}>
157-
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
158-
<div className={cn('border-solid border-2 border-border h-96 grid')}>
159-
<JsonView json={asset.json} />
160-
</div>
161-
</CardContent>
162-
</Card>
163161

164162
<Card className={cn('p-4')}>
165163
<CardContent className={cn('text-sm space-y-2')}>

src/features/assets/components/asset-media.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ type Props = {
77

88
export function AssetMedia({ asset }: Props) {
99
return asset.media ? (
10-
<div className={cn('pl-2 w-32 h-auto')}>
10+
<div className={cn('w-32 h-auto')}>
1111
{asset.media.type === AssetMediaType.Image && <img src={asset.media.url} alt={asset.name} />}
1212
{asset.media.type === AssetMediaType.Video && (
1313
<video title={asset.name} autoPlay playsInline loop controls muted>
1414
<source src={asset.media.url} type="video/mp4" />
1515
</video>
1616
)}
1717
</div>
18-
) : null
18+
) : undefined
1919
}

src/features/assets/components/labels.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ export const assetTraitsLabel = 'Asset Traits'
2020

2121
export const assetMetadataLabel = 'Asset Metadata'
2222

23-
export const assetJsonLabel = 'Asset JSON'
24-
2523
export const assetActivityLabel = 'Activity'
2624
export const assetLiveTransactionsTabId = 'live-transactions'
2725
export const assetLiveTransactionsTabLabel = 'Live Transactions'
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { JsonView as ReactJsonView } from 'react-json-view-lite'
2+
import 'react-json-view-lite/dist/index.css'
3+
import styles from './json-view-dialog.module.css'
4+
import { cn } from '../utils'
5+
import { Button } from './button'
6+
import { useCallback, useState } from 'react'
7+
import { asJson } from '@/utils/as-json'
8+
import { toast } from 'react-toastify'
9+
import { Dialog, DialogContent, DialogHeader } from '@/features/common/components/dialog'
10+
import { useResolvedTheme } from '@/features/settings/data/theme'
11+
12+
type Props = {
13+
json: object
14+
expandJsonLevel?: (level: number) => boolean
15+
}
16+
17+
export function OpenJsonViewDialogButton({ json, expandJsonLevel: exapandJsonLevel = defaultExpandLevel }: Props) {
18+
const [dialogOpen, setDialogOpen] = useState(false)
19+
20+
const openJsonViewDialog = useCallback(() => {
21+
setDialogOpen(true)
22+
}, [setDialogOpen])
23+
24+
const styleDark: StyleProps = {
25+
container: styles['key-dark'],
26+
basicChildStyle: styles['basic-element-style'],
27+
collapseIcon: styles['collapse-icon'],
28+
expandIcon: styles['expand-icon'],
29+
collapsedContent: styles['collapsed-content'],
30+
label: styles['label'],
31+
clickableLabel: styles['clickable-label'],
32+
nullValue: '',
33+
undefinedValue: '',
34+
stringValue: styles['value-string-dark'],
35+
booleanValue: styles['value-boolean-dark'],
36+
numberValue: styles['value-number-dark'],
37+
otherValue: '',
38+
punctuation: styles['punctuation-dark'],
39+
noQuotesForStringValues: false,
40+
}
41+
const styleLight: StyleProps = {
42+
container: styles['key-light'],
43+
basicChildStyle: styles['basic-element-style'],
44+
collapseIcon: styles['collapse-icon'],
45+
expandIcon: styles['expand-icon'],
46+
collapsedContent: styles['collapsed-content'],
47+
label: styles['label'],
48+
clickableLabel: styles['clickable-label'],
49+
nullValue: '',
50+
undefinedValue: '',
51+
stringValue: styles['value-string-light'],
52+
booleanValue: styles['value-boolean-light'],
53+
numberValue: styles['value-number-light'],
54+
otherValue: '',
55+
punctuation: styles['punctuation-light'],
56+
noQuotesForStringValues: false,
57+
}
58+
59+
const theme = useResolvedTheme()
60+
const currentStyle = theme === 'dark' ? styleDark : styleLight
61+
62+
const copyJsonToClipboard = useCallback(() => {
63+
const jsonString = asJson(json)
64+
navigator.clipboard.writeText(jsonString)
65+
66+
toast.success('JSON copied to clipboard')
67+
}, [json])
68+
69+
return (
70+
<>
71+
<Button variant="outline" onClick={openJsonViewDialog}>
72+
View JSON
73+
</Button>
74+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen} modal={true}>
75+
<DialogContent className="bg-card">
76+
<DialogHeader>
77+
<h4 className={cn('text-xl text-primary font-bold')}>JSON</h4>
78+
</DialogHeader>
79+
<div className={cn('border-solid border-2 border-border grid w-[900px] min-h-[200px] max-h-[500px] overflow-auto relative')}>
80+
<Button variant="default" className={cn('absolute top-2 right-2')} onClick={copyJsonToClipboard}>
81+
Copy
82+
</Button>
83+
<ReactJsonView data={json} shouldExpandNode={exapandJsonLevel} style={currentStyle} />
84+
</div>
85+
</DialogContent>
86+
</Dialog>
87+
</>
88+
)
89+
}
90+
// By default only render the top level because sometimes the object has too many children, which result in the UI thread being blocked on mount.
91+
const defaultExpandLevel = (level: number) => {
92+
return level < 1
93+
}
94+
95+
export interface StyleProps {
96+
container: string
97+
basicChildStyle: string
98+
label: string
99+
clickableLabel: string
100+
nullValue: string
101+
undefinedValue: string
102+
numberValue: string
103+
stringValue: string
104+
booleanValue: string
105+
otherValue: string
106+
punctuation: string
107+
expandIcon: string
108+
collapseIcon: string
109+
collapsedContent: string
110+
noQuotesForStringValues: boolean
111+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
.collapse-icon::after {
2+
cursor: pointer;
3+
margin-right: 5px;
4+
content: '\25BE';
5+
}
6+
7+
.expand-icon::after {
8+
cursor: pointer;
9+
margin-right: 5px;
10+
content: '\25B8';
11+
}
12+
13+
.basic-element-style {
14+
margin: 0 10px;
15+
padding: 0;
16+
}
17+
18+
.label {
19+
margin-right: 5px;
20+
}
21+
22+
.clickable-label {
23+
cursor: pointer;
24+
}
25+
26+
.collapsed-content {
27+
cursor: pointer;
28+
margin-right: 5px;
29+
}
30+
31+
.collapsed-content::after {
32+
content: '...';
33+
font-size: 0.8em;
34+
}
35+
36+
.punctuation-base {
37+
margin-right: 5px;
38+
font-weight: normal;
39+
}
40+
41+
.punctuation-light {
42+
composes: punctuation-base;
43+
color: rgb(15 23 42);
44+
}
45+
46+
.key-light {
47+
color: rgb(27 108 135);
48+
padding: 10px 0;
49+
}
50+
.value-string-light {
51+
color: rgb(169 48 59);
52+
}
53+
54+
.value-number-light {
55+
color: rgb(25 109 11);
56+
}
57+
58+
.value-boolean-light {
59+
color: rgb(67 100 99);
60+
}
61+
62+
.punctuation-dark {
63+
composes: punctuation-base;
64+
color: rgb(255 255 255);
65+
}
66+
67+
.key-dark {
68+
color: rgb(43 157 195);
69+
padding: 10px 0;
70+
}
71+
.value-string-dark {
72+
color: rgb(234 100 104);
73+
}
74+
75+
.value-number-dark {
76+
color: rgb(43 167 22);
77+
}
78+
79+
.value-boolean-dark {
80+
color: rgb(105 154 152);
81+
}

0 commit comments

Comments
 (0)