Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dashboard): JSON view #8038

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't really need this check, JSON.stringify("string", null, 2) will just give you "string" anyway

Copy link
Contributor Author

@kasperkristensen kasperkristensen Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seb wants strings to be copied without quotation marks, and as you say it gives you "string" 😄

} 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
Loading