Skip to content

Commit

Permalink
chore: hide json view under a button (#93)
Browse files Browse the repository at this point in the history
* feat: view json in a button

* feat: add syntax highlights

---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
negar-abbasi and neilcampbell authored Jun 14, 2024
1 parent 420772f commit a17192e
Show file tree
Hide file tree
Showing 25 changed files with 245 additions and 191 deletions.
14 changes: 2 additions & 12 deletions src/features/accounts/components/account-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { Account } from '../models'
import { cn } from '@/features/common/utils'
import { AccountActivityTabs } from './account-activity-tabs'
import { AccountInfo } from './account-info'
import { accountActivityLabel, accountApplicationLabel, accountAssetLabel, accountJsonLabel } from './labels'
import { JsonView } from '@/features/common/components/json-view'
import { accountActivityLabel, accountApplicationLabel, accountAssetLabel } from './labels'
import { AccountAssetTabs } from './account-asset-tabs'
import { AccountApplicationTabs } from './account-application-tabs'

Expand All @@ -19,7 +18,7 @@ export function AccountDetails({ account }: Props) {
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{accountActivityLabel}</h1>
<div className={cn('border-solid border-2 border-border grid')}>
<div className={cn('border-solid border-2 border-border grid grid-cols-[1fr_max-content]')}>
<AccountActivityTabs account={account} />
</div>
</CardContent>
Expand All @@ -41,15 +40,6 @@ export function AccountDetails({ account }: Props) {
</div>
</CardContent>
</Card>

<Card className={cn('p-4')}>
<CardContent aria-label={accountJsonLabel} className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{accountJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<JsonView json={account.json} />
</div>
</CardContent>
</Card>
</div>
)
}
6 changes: 5 additions & 1 deletion src/features/accounts/components/account-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
accountMinBalanceLabel,
accountRekeyedToLabel,
} from './labels'
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'

type Props = {
account: Account
Expand Down Expand Up @@ -87,7 +88,10 @@ export function AccountInfo({ account }: Props) {
return (
<Card aria-label={accountInformationLabel} className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<DescriptionList items={accountInfoItems} />
<div className={cn('grid grid-cols-[1fr_max-content]')}>
<DescriptionList items={accountInfoItems} />
<OpenJsonViewDialogButton json={account.json} />
</div>
</CardContent>
</Card>
)
Expand Down
1 change: 0 additions & 1 deletion src/features/accounts/components/labels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const accountJsonLabel = 'Account JSON'
export const accountActivityLabel = 'Activity'
export const accountAssetLabel = 'Assets'
export const accountApplicationLabel = 'Applications'
Expand Down
16 changes: 5 additions & 11 deletions src/features/applications/components/application-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
applicationLocalStateByteLabel,
applicationLocalStateUintLabel,
applicationTransactionsLabel,
applicationJsonLabel,
applicationNameLabel,
} from './labels'
import { isDefined } from '@/utils/is-defined'
Expand All @@ -33,8 +32,8 @@ import { ApplicationBoxes } from './application-boxes'
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
import { ApplicationLiveTransactions } from './application-live-transactions'
import { ApplicationTransactionHistory } from './application-transaction-history'
import { JsonView } from '@/features/common/components/json-view'
import { AccountLink } from '@/features/accounts/components/account-link'
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'

type Props = {
application: Application
Expand Down Expand Up @@ -100,7 +99,10 @@ export function ApplicationDetails({ application }: Props) {
<div className={cn('space-y-6 pt-7')}>
<Card aria-label={applicationDetailsLabel} className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<DescriptionList items={applicationItems} />
<div className={cn('grid grid-cols-[1fr_max-content]')}>
<DescriptionList items={applicationItems} />
<OpenJsonViewDialogButton json={application.json} />
</div>
</CardContent>
</Card>
<Card aria-label={applicationApprovalProgramLabel} className={cn('p-4')}>
Expand Down Expand Up @@ -157,14 +159,6 @@ export function ApplicationDetails({ application }: Props) {
</Tabs>
</CardContent>
</Card>
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{applicationJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<JsonView json={application.json} />
</div>
</CardContent>
</Card>
</div>
)
}
2 changes: 0 additions & 2 deletions src/features/applications/components/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,3 @@ export const applicationLiveTransactionsTabId = 'live-transactions'
export const applicationLiveTransactionsTabLabel = 'Live Transactions'
export const applicationHistoricalTransactionsTabId = 'historical-transactions'
export const applicationHistoricalTransactionsTabLabel = 'Historical Transactions'

export const applicationJsonLabel = 'Application JSON'
20 changes: 9 additions & 11 deletions src/features/assets/components/asset-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
assetHistoricalTransactionsTabId,
assetHistoricalTransactionsTabLabel,
assetIdLabel,
assetJsonLabel,
assetLiveTransactionsTabId,
assetLiveTransactionsTabLabel,
assetManagerLabel,
Expand All @@ -36,12 +35,16 @@ import { AssetMetadata } from './asset-metadata'
import { AssetTransactionHistory } from './asset-transaction-history'
import { AssetLiveTransactions } from './asset-live-transactions'
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
import { JsonView } from '@/features/common/components/json-view'
import { OpenJsonViewDialogButton } from '@/features/common/components/json-view-dialog-button'

type Props = {
asset: Asset
}

const expandAssetJsonLevel = (level: number) => {
return level < 2
}

export function AssetDetails({ asset }: Props) {
const assetItems = useMemo(
() => [
Expand Down Expand Up @@ -137,7 +140,10 @@ export function AssetDetails({ asset }: Props) {
<CardContent className={cn('text-sm space-y-2')}>
<div className={cn('grid grid-cols-[1fr_max-content]')}>
<DescriptionList items={assetItems} />
<AssetMedia asset={asset} />
<div className="ml-2 grid gap-2">
<OpenJsonViewDialogButton json={asset.json} expandJsonLevel={expandAssetJsonLevel} />
<AssetMedia asset={asset} />
</div>
</div>
</CardContent>
</Card>
Expand All @@ -152,14 +158,6 @@ export function AssetDetails({ asset }: Props) {

<AssetMetadata metadata={asset.metadata} />
<AssetTraits traits={asset.traits} />
<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
<h1 className={cn('text-2xl text-primary font-bold')}>{assetJsonLabel}</h1>
<div className={cn('border-solid border-2 border-border h-96 grid')}>
<JsonView json={asset.json} />
</div>
</CardContent>
</Card>

<Card className={cn('p-4')}>
<CardContent className={cn('text-sm space-y-2')}>
Expand Down
4 changes: 2 additions & 2 deletions src/features/assets/components/asset-media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ type Props = {

export function AssetMedia({ asset }: Props) {
return asset.media ? (
<div className={cn('pl-2 w-32 h-auto')}>
<div className={cn('w-32 h-auto')}>
{asset.media.type === AssetMediaType.Image && <img src={asset.media.url} alt={asset.name} />}
{asset.media.type === AssetMediaType.Video && (
<video title={asset.name} autoPlay playsInline loop controls muted>
<source src={asset.media.url} type="video/mp4" />
</video>
)}
</div>
) : null
) : undefined
}
2 changes: 0 additions & 2 deletions src/features/assets/components/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export const assetTraitsLabel = 'Asset Traits'

export const assetMetadataLabel = 'Asset Metadata'

export const assetJsonLabel = 'Asset JSON'

export const assetActivityLabel = 'Activity'
export const assetLiveTransactionsTabId = 'live-transactions'
export const assetLiveTransactionsTabLabel = 'Live Transactions'
Expand Down
111 changes: 111 additions & 0 deletions src/features/common/components/json-view-dialog-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { JsonView as ReactJsonView } from 'react-json-view-lite'
import 'react-json-view-lite/dist/index.css'
import styles from './json-view-dialog.module.css'
import { cn } from '../utils'
import { Button } from './button'
import { useCallback, useState } from 'react'
import { asJson } from '@/utils/as-json'
import { toast } from 'react-toastify'
import { Dialog, DialogContent, DialogHeader } from '@/features/common/components/dialog'
import { useResolvedTheme } from '@/features/settings/data/theme'

type Props = {
json: object
expandJsonLevel?: (level: number) => boolean
}

export function OpenJsonViewDialogButton({ json, expandJsonLevel: exapandJsonLevel = defaultExpandLevel }: Props) {
const [dialogOpen, setDialogOpen] = useState(false)

const openJsonViewDialog = useCallback(() => {
setDialogOpen(true)
}, [setDialogOpen])

const styleDark: StyleProps = {
container: styles['key-dark'],
basicChildStyle: styles['basic-element-style'],
collapseIcon: styles['collapse-icon'],
expandIcon: styles['expand-icon'],
collapsedContent: styles['collapsed-content'],
label: styles['label'],
clickableLabel: styles['clickable-label'],
nullValue: '',
undefinedValue: '',
stringValue: styles['value-string-dark'],
booleanValue: styles['value-boolean-dark'],
numberValue: styles['value-number-dark'],
otherValue: '',
punctuation: styles['punctuation-dark'],
noQuotesForStringValues: false,
}
const styleLight: StyleProps = {
container: styles['key-light'],
basicChildStyle: styles['basic-element-style'],
collapseIcon: styles['collapse-icon'],
expandIcon: styles['expand-icon'],
collapsedContent: styles['collapsed-content'],
label: styles['label'],
clickableLabel: styles['clickable-label'],
nullValue: '',
undefinedValue: '',
stringValue: styles['value-string-light'],
booleanValue: styles['value-boolean-light'],
numberValue: styles['value-number-light'],
otherValue: '',
punctuation: styles['punctuation-light'],
noQuotesForStringValues: false,
}

const theme = useResolvedTheme()
const currentStyle = theme === 'dark' ? styleDark : styleLight

const copyJsonToClipboard = useCallback(() => {
const jsonString = asJson(json)
navigator.clipboard.writeText(jsonString)

toast.success('JSON copied to clipboard')
}, [json])

return (
<>
<Button variant="outline" onClick={openJsonViewDialog}>
View JSON
</Button>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen} modal={true}>
<DialogContent className="bg-card">
<DialogHeader>
<h4 className={cn('text-xl text-primary font-bold')}>JSON</h4>
</DialogHeader>
<div className={cn('border-solid border-2 border-border grid w-[900px] min-h-[200px] max-h-[500px] overflow-auto relative')}>
<Button variant="default" className={cn('absolute top-2 right-2')} onClick={copyJsonToClipboard}>
Copy
</Button>
<ReactJsonView data={json} shouldExpandNode={exapandJsonLevel} style={currentStyle} />
</div>
</DialogContent>
</Dialog>
</>
)
}
// 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.
const defaultExpandLevel = (level: number) => {
return level < 1
}

export interface StyleProps {
container: string
basicChildStyle: string
label: string
clickableLabel: string
nullValue: string
undefinedValue: string
numberValue: string
stringValue: string
booleanValue: string
otherValue: string
punctuation: string
expandIcon: string
collapseIcon: string
collapsedContent: string
noQuotesForStringValues: boolean
}
81 changes: 81 additions & 0 deletions src/features/common/components/json-view-dialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
.collapse-icon::after {
cursor: pointer;
margin-right: 5px;
content: '\25BE';
}

.expand-icon::after {
cursor: pointer;
margin-right: 5px;
content: '\25B8';
}

.basic-element-style {
margin: 0 10px;
padding: 0;
}

.label {
margin-right: 5px;
}

.clickable-label {
cursor: pointer;
}

.collapsed-content {
cursor: pointer;
margin-right: 5px;
}

.collapsed-content::after {
content: '...';
font-size: 0.8em;
}

.punctuation-base {
margin-right: 5px;
font-weight: normal;
}

.punctuation-light {
composes: punctuation-base;
color: rgb(15 23 42);
}

.key-light {
color: rgb(27 108 135);
padding: 10px 0;
}
.value-string-light {
color: rgb(169 48 59);
}

.value-number-light {
color: rgb(25 109 11);
}

.value-boolean-light {
color: rgb(67 100 99);
}

.punctuation-dark {
composes: punctuation-base;
color: rgb(255 255 255);
}

.key-dark {
color: rgb(43 157 195);
padding: 10px 0;
}
.value-string-dark {
color: rgb(234 100 104);
}

.value-number-dark {
color: rgb(43 167 22);
}

.value-boolean-dark {
color: rgb(105 154 152);
}
Loading

0 comments on commit a17192e

Please sign in to comment.