mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Chore/refactor download results button to use papaparse (#34693)
* refactor LogTable and PreviewFilterPanel to use DownloadResultsButton * Remove CSVButton file and remove react-csv dependency * Update * Change the download button to use file-saver. --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
1 parent
98c7cbff4d
commit
fa8cf46d50
6 files changed
+73
-227
No files matched your search
@@ -1,32 +1,20 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { isEqual } from 'lodash'
|
||||
import { ChevronDown, Clipboard, Download, Eye, EyeOff, Play } from 'lucide-react'
|
||||
import Papa from 'papaparse'
|
||||
import { Key, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Clipboard, Eye, EyeOff, Play } from 'lucide-react'
|
||||
import { Key, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Item, Menu, useContextMenu } from 'react-contexify'
|
||||
import DataGrid, { Column, RenderRowProps, Row } from 'react-data-grid'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { IS_PLATFORM, useParams } from 'common'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import CSVButton from 'components/ui/CSVButton'
|
||||
import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
|
||||
import { useSelectedLog } from 'hooks/analytics/useSelectedLog'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { ResponseError } from 'types'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { Button, ResizableHandle, ResizablePanel, ResizablePanelGroup, cn } from 'ui'
|
||||
import AuthColumnRenderer from './LogColumnRenderers/AuthColumnRenderer'
|
||||
import DatabaseApiColumnRender from './LogColumnRenderers/DatabaseApiColumnRender'
|
||||
import DatabasePostgresColumnRender from './LogColumnRenderers/DatabasePostgresColumnRender'
|
||||
@@ -93,11 +81,11 @@ const LogTable = ({
|
||||
selectedLogError,
|
||||
onSelectedLogChange,
|
||||
}: Props) => {
|
||||
const { ref } = useParams()
|
||||
const { profile } = useProfile()
|
||||
const [selectedLogId] = useSelectedLog()
|
||||
const { show: showContextMenu } = useContextMenu()
|
||||
|
||||
const downloadCsvRef = useRef<HTMLDivElement>(null)
|
||||
const [cellPosition, setCellPosition] = useState<any>()
|
||||
const [selectionOpen, setSelectionOpen] = useState(false)
|
||||
const [selectedRow, setSelectedRow] = useState<LogData | null>(null)
|
||||
@@ -188,7 +176,6 @@ const LogTable = ({
|
||||
}
|
||||
}
|
||||
|
||||
const stringData = useMemo(() => JSON.stringify(data), [data])
|
||||
const [dedupedData, logMap] = useMemo<[LogData[], LogMap]>(() => {
|
||||
const deduped = [...new Set(data)] as LogData[]
|
||||
|
||||
@@ -245,54 +232,12 @@ const LogTable = ({
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="text" iconRight={<ChevronDown size={14} />}>
|
||||
Results {data && data.length ? `(${data.length})` : ''}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
downloadCsvRef.current?.click()
|
||||
}}
|
||||
className="space-x-2"
|
||||
>
|
||||
<Download size={14} />
|
||||
<div>Download CSV</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
const csvData = Papa.unparse(data)
|
||||
copyToClipboard(csvData, () => {
|
||||
toast.success('Results copied to clipboard')
|
||||
})
|
||||
}}
|
||||
className="space-x-2"
|
||||
>
|
||||
<Clipboard size={14} />
|
||||
<div>Copy as CSV</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
copyToClipboard(stringData, () => {
|
||||
toast.success('Results copied to clipboard')
|
||||
})
|
||||
}}
|
||||
className="space-x-2"
|
||||
>
|
||||
<Clipboard size={14} />
|
||||
<div>Copy as JSON</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{/* Using .hidden with a ref so I don't have to duplicate the code to download the CSV - Jordi */}
|
||||
<div className="hidden">
|
||||
<CSVButton buttonType={'text'} data={data}>
|
||||
<div ref={downloadCsvRef}>Download CSV</div>
|
||||
</CSVButton>
|
||||
<DownloadResultsButton
|
||||
type="text"
|
||||
text={`Results ${data && data.length ? `(${data.length})` : ''}`}
|
||||
results={data}
|
||||
fileName={`supabase-logs-${ref}.csv`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showHistogramToggle && (
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useEffect, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import CSVButton from 'components/ui/CSVButton'
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
|
||||
import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { Button, Input, Tooltip, TooltipContent, TooltipTrigger, cn } from 'ui'
|
||||
@@ -70,6 +70,8 @@ const PreviewFilterPanel = ({
|
||||
const { ref } = useParams()
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const logName = router.pathname.split('/').pop()
|
||||
|
||||
const { data: loadBalancers } = useLoadBalancersQuery({ projectRef: ref })
|
||||
|
||||
// [Joshen] These are the routes tested that can show replica logs
|
||||
@@ -233,7 +235,15 @@ const PreviewFilterPanel = ({
|
||||
Chart
|
||||
</Button>
|
||||
</div>
|
||||
<CSVButton data={csvData} disabled={!Boolean(csvData)} title="Download data" />
|
||||
{Boolean(csvData) && (
|
||||
<DownloadResultsButton
|
||||
iconOnly
|
||||
type="default"
|
||||
align="center"
|
||||
results={csvData ?? []}
|
||||
fileName={`supabase-${logName}-${ref}.csv`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showDatabaseSelector ? (
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Download } from 'lucide-react'
|
||||
import React, { PropsWithChildren, useMemo, useRef } from 'react'
|
||||
import { CSVLink } from 'react-csv'
|
||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { ButtonProps } from 'ui/src/components/Button/Button'
|
||||
|
||||
interface CSVButtonProps {
|
||||
buttonType?: ButtonProps['type']
|
||||
onClick?: ButtonProps['onClick']
|
||||
disabled?: ButtonProps['disabled']
|
||||
icon?: React.ReactNode
|
||||
data?: unknown[]
|
||||
title?: string
|
||||
}
|
||||
|
||||
const CSVButton = ({
|
||||
onClick,
|
||||
buttonType = 'default',
|
||||
icon,
|
||||
children,
|
||||
disabled,
|
||||
data,
|
||||
title,
|
||||
}: PropsWithChildren<CSVButtonProps>) => {
|
||||
const csvRef = useRef(null)
|
||||
const handleDownload = () => {
|
||||
;(csvRef.current as any)?.link.click()
|
||||
}
|
||||
const formattedData = useMemo(() => {
|
||||
const first = data?.[0]
|
||||
if (!first || !data) return
|
||||
const keys = Object.keys(first as any)
|
||||
return data.map((datum: any) => {
|
||||
return keys.reduce((acc: any, key) => {
|
||||
if (typeof datum[key] === 'object') {
|
||||
acc[key] = JSON.stringify(datum[key]) as string
|
||||
} else {
|
||||
acc[key] = String(datum[key])
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
// retrieve dot notation of all keys
|
||||
}, [JSON.stringify(data)])
|
||||
|
||||
return (
|
||||
<>
|
||||
<CSVLink
|
||||
ref={csvRef}
|
||||
className="hidden"
|
||||
data={formattedData || ([] as any)}
|
||||
filename={`supabase_logs.csv`}
|
||||
title={title}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type={buttonType}
|
||||
icon={icon || <Download />}
|
||||
disabled={disabled}
|
||||
className="px-1.5"
|
||||
onClick={(e) => {
|
||||
if (onClick) onClick(e)
|
||||
handleDownload()
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="text-xs">
|
||||
Download logs as CSV
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSVButton
|
||||
@@ -1,7 +1,8 @@
|
||||
import saveAs from 'file-saver'
|
||||
import { ChevronDown, Clipboard, Download } from 'lucide-react'
|
||||
import { markdownTable } from 'markdown-table'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { CSVLink } from 'react-csv'
|
||||
import Papa from 'papaparse'
|
||||
import { useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
@@ -14,7 +15,9 @@ import {
|
||||
} from 'ui'
|
||||
|
||||
interface DownloadResultsButtonProps {
|
||||
iconOnly?: boolean
|
||||
type?: 'text' | 'default'
|
||||
text?: string
|
||||
align?: 'start' | 'center' | 'end'
|
||||
results: any[]
|
||||
fileName: string
|
||||
@@ -24,7 +27,9 @@ interface DownloadResultsButtonProps {
|
||||
}
|
||||
|
||||
export const DownloadResultsButton = ({
|
||||
iconOnly = false,
|
||||
type = 'default',
|
||||
text = 'Export',
|
||||
align = 'start',
|
||||
results,
|
||||
fileName,
|
||||
@@ -32,8 +37,6 @@ export const DownloadResultsButton = ({
|
||||
onCopyAsMarkdown,
|
||||
onCopyAsJSON,
|
||||
}: DownloadResultsButtonProps) => {
|
||||
const csvRef = useRef<CSVLink & HTMLAnchorElement & { link: HTMLAnchorElement }>(null)
|
||||
|
||||
// [Joshen] Ensure JSON values are stringified for CSV and Markdown
|
||||
const formattedResults = results.map((row) => {
|
||||
const r = { ...row }
|
||||
@@ -51,6 +54,14 @@ export const DownloadResultsButton = ({
|
||||
return undefined
|
||||
}, [results])
|
||||
|
||||
const downloadAsCSV = () => {
|
||||
const csv = Papa.unparse(formattedResults, { columns: headers })
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
|
||||
saveAs(blob, `${fileName}.csv`)
|
||||
toast.success('Downloading results as CSV')
|
||||
onDownloadAsCSV?.()
|
||||
}
|
||||
|
||||
const copyAsMarkdown = () => {
|
||||
if (navigator) {
|
||||
if (formattedResults.length == 0) toast('Results are empty')
|
||||
@@ -82,42 +93,32 @@ export const DownloadResultsButton = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type={type} iconRight={<ChevronDown />} disabled={results.length === 0}>
|
||||
Export
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align={align} className="w-44">
|
||||
<DropdownMenuItem
|
||||
className="gap-x-2"
|
||||
onClick={() => {
|
||||
csvRef.current?.link.click()
|
||||
toast.success('Downloading results as CSV')
|
||||
onDownloadAsCSV?.()
|
||||
}}
|
||||
>
|
||||
<Download size={14} />
|
||||
<p>Download CSV</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={copyAsMarkdown} className="gap-x-2">
|
||||
<Clipboard size={14} />
|
||||
<p>Copy as markdown</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={copyAsJSON} className="gap-x-2">
|
||||
<Clipboard size={14} />
|
||||
<p>Copy as JSON</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<CSVLink
|
||||
ref={csvRef}
|
||||
className="hidden"
|
||||
headers={headers}
|
||||
data={formattedResults}
|
||||
filename={`${fileName}.csv`}
|
||||
/>
|
||||
</>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type={type}
|
||||
icon={iconOnly ? <Download /> : undefined}
|
||||
iconRight={iconOnly ? undefined : <ChevronDown />}
|
||||
disabled={results.length === 0}
|
||||
className={iconOnly ? 'w-7' : ''}
|
||||
>
|
||||
{!iconOnly && text}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align={align} className="w-44">
|
||||
<DropdownMenuItem className="gap-x-2" onClick={() => downloadAsCSV()}>
|
||||
<Download size={14} />
|
||||
<p>Download CSV</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={copyAsMarkdown} className="gap-x-2">
|
||||
<Clipboard size={14} />
|
||||
<p>Copy as markdown</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={copyAsJSON} className="gap-x-2">
|
||||
<Clipboard size={14} />
|
||||
<p>Copy as JSON</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -95,7 +95,6 @@
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-contexify": "^5.0.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-csv": "^2.0.3",
|
||||
"react-data-grid": "7.0.0-beta.41",
|
||||
"react-datepicker": "^4.18.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
@@ -150,7 +149,6 @@
|
||||
"@types/react": "^18.2.24",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-csv": "^1.1.2",
|
||||
"@types/react-datepicker": "^4.3.4",
|
||||
"@types/react-dom": "^18.2.8",
|
||||
"@types/react-grid-layout": "^1.3.0",
|
||||
|
||||
Generated
+7
-37
@@ -745,9 +745,6 @@ importers:
|
||||
react-copy-to-clipboard:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(react@18.2.0)
|
||||
react-csv:
|
||||
specifier: ^2.0.3
|
||||
version: 2.2.2
|
||||
react-data-grid:
|
||||
specifier: 7.0.0-beta.41
|
||||
version: 7.0.0-beta.41(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -905,9 +902,6 @@ importers:
|
||||
'@types/react-copy-to-clipboard':
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.5
|
||||
'@types/react-csv':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.6
|
||||
'@types/react-datepicker':
|
||||
specifier: ^4.3.4
|
||||
version: 4.15.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -6504,9 +6498,6 @@ packages:
|
||||
'@types/react-copy-to-clipboard@5.0.5':
|
||||
resolution: {integrity: sha512-en3JGqPA4RX4aUlo6q6uUbnqLp31Dhm2E/thiMvFTIvU+dUDG249jBG2MJ0rPMXE/MbKVrpmi/1r1G4QLhIHKQ==}
|
||||
|
||||
'@types/react-csv@1.1.6':
|
||||
resolution: {integrity: sha512-D8MmuwIrqVr1ilgCEhB5U7uyMeojPC+Ffr/Zg/galVEFMDy/dbmegj38Bs5hWwgC3VVjCyo9GR+YHrC1fnFm1w==}
|
||||
|
||||
'@types/react-datepicker@4.15.1':
|
||||
resolution: {integrity: sha512-6/LthK0pTDBKjjVJqA2ygY3jJsHH7uZXIk8WPQcGHUiFOQLOKIv3krOxFZ2mt3BB3guMB6jVTc6ansQAd0r7xQ==}
|
||||
|
||||
@@ -9004,9 +8995,6 @@ packages:
|
||||
resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
flatted@3.3.1:
|
||||
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
||||
|
||||
flatted@3.3.2:
|
||||
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
|
||||
|
||||
@@ -9901,9 +9889,6 @@ packages:
|
||||
is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
|
||||
is-reference@3.0.2:
|
||||
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
||||
|
||||
is-reference@3.0.3:
|
||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||
|
||||
@@ -12394,9 +12379,6 @@ packages:
|
||||
react: '>= 15'
|
||||
react-dom: '>= 15'
|
||||
|
||||
react-csv@2.2.2:
|
||||
resolution: {integrity: sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==}
|
||||
|
||||
react-data-grid@7.0.0-beta.41:
|
||||
resolution: {integrity: sha512-WmTP/PV+vtVjIaGVLgyG6WAhqvuPBM8I54bsR7oJZl6w43+mIasZM9rEBWjQ52XHJEy41/tjcMBIMNiWqoEbrQ==}
|
||||
peerDependencies:
|
||||
@@ -20914,10 +20896,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@types/react-csv@1.1.6':
|
||||
dependencies:
|
||||
'@types/react': 18.3.3
|
||||
|
||||
'@types/react-datepicker@4.15.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
@@ -23556,8 +23534,8 @@ snapshots:
|
||||
|
||||
espree@9.6.1:
|
||||
dependencies:
|
||||
acorn: 8.11.3
|
||||
acorn-jsx: 5.3.2(acorn@8.11.3)
|
||||
acorn: 8.14.1
|
||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
esprima@4.0.1: {}
|
||||
@@ -23904,12 +23882,10 @@ snapshots:
|
||||
|
||||
flat-cache@3.1.0:
|
||||
dependencies:
|
||||
flatted: 3.3.1
|
||||
flatted: 3.3.2
|
||||
keyv: 4.5.3
|
||||
rimraf: 3.0.2
|
||||
|
||||
flatted@3.3.1: {}
|
||||
|
||||
flatted@3.3.2: {}
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
@@ -25011,10 +24987,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
is-reference@3.0.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
is-reference@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.7
|
||||
@@ -28084,9 +28056,9 @@ snapshots:
|
||||
|
||||
periscopic@3.1.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.7
|
||||
estree-walker: 3.0.3
|
||||
is-reference: 3.0.2
|
||||
is-reference: 3.0.3
|
||||
|
||||
pg-cloudflare@1.1.1:
|
||||
optional: true
|
||||
@@ -28588,8 +28560,6 @@ snapshots:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
react-csv@2.2.2: {}
|
||||
|
||||
react-data-grid@7.0.0-beta.41(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
@@ -29491,9 +29461,9 @@ snapshots:
|
||||
|
||||
sass@1.72.0:
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
chokidar: 3.6.0
|
||||
immutable: 4.3.4
|
||||
source-map-js: 1.2.0
|
||||
source-map-js: 1.2.1
|
||||
|
||||
satori@0.10.9:
|
||||
dependencies:
|
||||
|
||||
Reference in new issue
Block a user