Skip to content

Commit

Permalink
fix(dashboard): JSON view (#8038)
Browse files Browse the repository at this point in the history
* updated json view

* cleanup

* cleanup
  • Loading branch information
kasperkristensen authored Jul 9, 2024
1 parent 566bbd5 commit 4736d9e
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 454 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { ArrowsPointingOut, XMarkMini } from "@medusajs/icons"
import {
ArrowsPointingOut,
Check,
SquareTwoStack,
TriangleDownMini,
XMarkMini,
} from "@medusajs/icons"
import {
Badge,
Container,
Expand All @@ -8,29 +14,27 @@ import {
Kbd,
} from "@medusajs/ui"
import Primitive from "@uiw/react-json-view"
import { CSSProperties, Suspense } from "react"
import { useTranslation } from "react-i18next"
import { CSSProperties, MouseEvent, Suspense, useState } from "react"
import { Trans, useTranslation } from "react-i18next"

type JsonViewSectionProps = {
data: object
root?: string
title?: string
}

// TODO: Fix the positioning of the copy btn
export const JsonViewSection = ({
data,
root,
title = "JSON",
}: JsonViewSectionProps) => {
export const JsonViewSection = ({ data }: JsonViewSectionProps) => {
const { t } = useTranslation()
const numberOfKeys = Object.keys(data).length

return (
<Container className="flex items-center justify-between px-6 py-4">
<div className="flex items-center gap-x-4">
<Heading level="h2">{title}</Heading>
<Badge size="2xsmall">{numberOfKeys} keys</Badge>
<Heading level="h2">{t("json.header")}</Heading>
<Badge size="2xsmall">
{t("json.numberOfKeys", {
count: numberOfKeys,
})}
</Badge>
</div>
<Drawer>
<Drawer.Trigger asChild>
Expand All @@ -42,86 +46,153 @@ export const JsonViewSection = ({
<ArrowsPointingOut />
</IconButton>
</Drawer.Trigger>
<Drawer.Content className="border-ui-code-border bg-ui-code-bg-base text-ui-code-fg-subtle dark overflow-hidden border shadow-none max-md:inset-x-2 max-md:max-w-[calc(100%-16px)]">
<div className="bg-ui-code-bg-base border-ui-code-border flex items-center justify-between border-b px-6 py-4">
<Drawer.Content className="bg-ui-contrast-bg-base text-ui-code-fg-subtle !shadow-elevation-commandbar overflow-hidden border border-none max-md:inset-x-2 max-md:max-w-[calc(100%-16px)]">
<div className="bg-ui-code-bg-base flex items-center justify-between px-6 py-4">
<div className="flex items-center gap-x-4">
<Heading className="text-ui-code-fg-base">{title}</Heading>
<Badge size="2xsmall">{numberOfKeys} keys</Badge>
<Drawer.Title asChild>
<Heading className="text-ui-contrast-fg-primary">
<Trans
i18nKey="json.drawer.header"
count={numberOfKeys}
components={[
<span key="count-span" className="text-ui-fg-subtle" />,
]}
/>
</Heading>
</Drawer.Title>
<Drawer.Description className="sr-only">
{t("json.drawer.description")}
</Drawer.Description>
</div>
<div className="flex items-center gap-x-2">
<Kbd>esc</Kbd>
<Kbd className="bg-ui-contrast-bg-subtle border-ui-contrast-border-base text-ui-contrast-fg-secondary">
esc
</Kbd>
<Drawer.Close asChild>
<IconButton
size="small"
variant="transparent"
className="text-ui-fg-subtle"
className="text-ui-contrast-fg-secondary hover:text-ui-contrast-fg-primary hover:bg-ui-contrast-bg-base-hover active:bg-ui-contrast-bg-base-pressed focus-visible:bg-ui-contrast-bg-base-hover focus-visible:shadow-borders-interactive-with-active"
>
<XMarkMini />
</IconButton>
</Drawer.Close>
</div>
</div>
<Drawer.Body className="overflow-auto p-4">
<Suspense fallback={<div>Loading...</div>}>
<Primitive
value={data}
displayDataTypes={false}
keyName={root}
style={
{
"--w-rjv-font-family": "Roboto Mono, monospace",
"--w-rjv-line-color": "var(--code-border)",
"--w-rjv-curlybraces-color": "rgb(255,255,255)",
"--w-rjv-key-string": "rgb(247,208,25)",
"--w-rjv-info-color": "var(--code-fg-muted)",
"--w-rjv-type-string-color": "rgb(73,209,110)",
"--w-rjv-quotes-string-color": "rgb(73,209,110)",
"--w-rjv-type-boolean-color": "rgb(187,77,96)",
"--w-rjv-type-int-color": "rgb(247,208,25)",
"--w-rjv-type-float-color": "rgb(247,208,25)",
"--w-rjv-type-bigint-color": "rgb(247,208,25)",
"--w-rjv-key-number": "rgb(247,208,25)",
"--w-rjv-arrow-color": "rgb(255,255,255)",
"--w-rjv-copied-color": "var(--code-fg-subtle)",
"--w-rjv-copied-success-color": "var(--code-fg-base)",
"--w-rjv-colon-color": "rgb(255,255,255)",
} as CSSProperties
}
collapsed={1}
<Drawer.Body className="flex flex-1 flex-col overflow-hidden px-[5px] py-0 pb-[5px]">
<div className="bg-ui-contrast-bg-subtle flex-1 overflow-auto rounded-b-[4px] rounded-t-lg p-3">
<Suspense
fallback={<div className="flex size-full flex-col"></div>}
>
<Primitive.Quote render={() => <span />} />
<Primitive.Null
render={() => (
<span className="text-ui-tag-red-text">null</span>
)}
/>
<Primitive.Undefined
render={() => (
<span className="text-ui-code-fg-muted">undefined</span>
)}
/>
<Primitive.CountInfo
render={(_props, { value }) => {
return (
<span className="text-ui-tag-neutral-text ml-2">
{t("general.items", {
count: Object.keys(value as object).length,
})}
</span>
)
}}
/>
{/* <Primitive.Arrow>
<TriangleDownMini className="text-ui-code-fg-subtle -ml-[3px]" />
</Primitive.Arrow> */}
<Primitive.Colon>
<span className="mr-1">:</span>
</Primitive.Colon>
</Primitive>
</Suspense>
<Primitive
value={data}
displayDataTypes={false}
style={
{
"--w-rjv-font-family": "Roboto Mono, monospace",
"--w-rjv-line-color": "var(--contrast-border-base)",
"--w-rjv-curlybraces-color":
"var(--contrast-fg-secondary)",
"--w-rjv-brackets-color": "var(--contrast-fg-secondary)",
"--w-rjv-key-string": "var(--contrast-fg-primary)",
"--w-rjv-info-color": "var(--contrast-fg-secondary)",
"--w-rjv-type-string-color": "var(--tag-green-icon)",
"--w-rjv-quotes-string-color": "var(--tag-green-icon)",
"--w-rjv-type-boolean-color": "var(--tag-orange-icon)",
"--w-rjv-type-int-color": "var(--tag-orange-icon)",
"--w-rjv-type-float-color": "var(--tag-orange-icon)",
"--w-rjv-type-bigint-color": "var(--tag-orange-icon)",
"--w-rjv-key-number": "var(--contrast-fg-secondary)",
"--w-rjv-arrow-color": "var(--contrast-fg-secondary)",
"--w-rjv-copied-color": "var(--contrast-fg-secondary)",
"--w-rjv-copied-success-color":
"var(--contrast-fg-primary)",
"--w-rjv-colon-color": "var(--contrast-fg-primary)",
"--w-rjv-ellipsis-color": "var(--contrast-fg-secondary)",
} as CSSProperties
}
collapsed={1}
>
<Primitive.Quote render={() => <span />} />
<Primitive.Null
render={() => (
<span className="text-ui-tag-red-icon">null</span>
)}
/>
<Primitive.Undefined
render={() => (
<span className="text-ui-tag-blue-icon">undefined</span>
)}
/>
<Primitive.CountInfo
render={(_props, { value }) => {
return (
<span className="text-ui-contrast-fg-secondary ml-2">
{t("general.items", {
count: Object.keys(value as object).length,
})}
</span>
)
}}
/>
<Primitive.Arrow>
<TriangleDownMini className="text-ui-contrast-fg-secondary -ml-[0.5px]" />
</Primitive.Arrow>
<Primitive.Colon>
<span className="mr-1">:</span>
</Primitive.Colon>
<Primitive.Copied
render={({ style }, { value }) => {
return <Copied style={style} value={value} />
}}
/>
</Primitive>
</Suspense>
</div>
</Drawer.Body>
</Drawer.Content>
</Drawer>
</Container>
)
}

type CopiedProps = {
style?: CSSProperties
value: object | undefined
}

const Copied = ({ style, value }: CopiedProps) => {
const [copied, setCopied] = useState(false)

const handler = (e: MouseEvent<HTMLSpanElement>) => {
e.stopPropagation()
setCopied(true)

if (typeof value === "string") {
navigator.clipboard.writeText(value)
} else {
const json = JSON.stringify(value, null, 2)
navigator.clipboard.writeText(json)
}

setTimeout(() => {
setCopied(false)
}, 2000)
}

const styl = { whiteSpace: "nowrap", width: "20px" }

if (copied) {
return (
<span style={{ ...style, ...styl }}>
<Check className="text-ui-contrast-fg-primary" />
</span>
)
}

return (
<span style={{ ...style, ...styl }} onClick={handler}>
<SquareTwoStack className="text-ui-contrast-fg-secondary" />
</span>
)
}
10 changes: 10 additions & 0 deletions packages/admin-next/dashboard/src/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@
"includesTaxTooltip": "Prices in this column are tax inclusive.",
"excludesTaxTooltip": "Prices in this column are tax exclusive."
},
"json": {
"header": "JSON",
"numberOfKeys_one": "{{count}} key",
"numberOfKeys_other": "{{count}} keys",
"drawer": {
"header_one": "JSON <0>· {{count}} key</0>",
"header_other": "JSON <0>· {{count}} keys</0>",
"description": "View the JSON data for this object."
}
},
"validation": {
"mustBeInt": "The value must be a whole number.",
"mustBePositive": "The value must be a positive number."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const ProductDetail = () => {
)
})}
<div className="hidden xl:block">
<JsonViewSection data={product} root="product" />
<JsonViewSection data={product} />
</div>
</div>
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[400px]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export const WorkflowExecutionPayloadSection = ({
payload = { input: payload }
}

return <JsonViewSection title="Payload" data={payload as object} />
return <JsonViewSection data={payload as object} />
}
Loading

0 comments on commit 4736d9e

Please sign in to comment.