DownloadResultsButton.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { IS_PLATFORM, useParams } from 'common'
  2. import saveAs from 'file-saver'
  3. import { ChevronDown, Copy, Download, Settings } from 'lucide-react'
  4. import Link from 'next/link'
  5. import { usePathname } from 'next/navigation'
  6. import { useMemo } from 'react'
  7. import { toast } from 'sonner'
  8. import {
  9. Button,
  10. copyToClipboard,
  11. DropdownMenu,
  12. DropdownMenuContent,
  13. DropdownMenuItem,
  14. DropdownMenuTrigger,
  15. KeyboardShortcut,
  16. } from 'ui'
  17. import {
  18. convertResultsToCSV,
  19. convertResultsToJSON,
  20. convertResultsToMarkdown,
  21. } from '@/components/interfaces/SQLEditor/UtilityPanel/Results.utils'
  22. import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
  23. import { useShortcut } from '@/state/shortcuts/useShortcut'
  24. interface DownloadResultsButtonProps {
  25. iconOnly?: boolean
  26. type?: 'text' | 'default'
  27. text?: string
  28. align?: 'start' | 'center' | 'end'
  29. results: any[]
  30. fileName: string
  31. enableCopyShortcuts?: boolean
  32. onDownloadAsCSV?: () => void
  33. onCopyAsMarkdown?: () => void
  34. onCopyAsJSON?: () => void
  35. onCopyAsCSV?: () => void
  36. }
  37. export const DownloadResultsButton = ({
  38. iconOnly = false,
  39. type = 'default',
  40. text = 'Export',
  41. align = 'start',
  42. results,
  43. fileName,
  44. enableCopyShortcuts = true,
  45. onDownloadAsCSV,
  46. onCopyAsMarkdown,
  47. onCopyAsJSON,
  48. onCopyAsCSV,
  49. }: DownloadResultsButtonProps) => {
  50. const { ref } = useParams()
  51. const pathname = usePathname()
  52. const isLogs = pathname?.includes?.('/logs') ?? false
  53. const isEmpty = useMemo(() => results.length === 0, [results])
  54. const downloadAsCSV = () => {
  55. const csv = convertResultsToCSV(results)
  56. if (!csv) {
  57. toast('Results are empty')
  58. return
  59. }
  60. const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
  61. saveAs(blob, `${fileName}.csv`)
  62. toast.success('Downloading results as CSV')
  63. onDownloadAsCSV?.()
  64. }
  65. const copyAsMarkdown = () => {
  66. const markdownData = convertResultsToMarkdown(results)
  67. if (!markdownData) {
  68. toast('Results are empty')
  69. return
  70. }
  71. copyToClipboard(markdownData, () => {
  72. toast.success('Copied Markdown to clipboard')
  73. onCopyAsMarkdown?.()
  74. })
  75. }
  76. const copyAsJSON = () => {
  77. const jsonData = convertResultsToJSON(results)
  78. if (!jsonData) {
  79. toast('Results are empty')
  80. return
  81. }
  82. copyToClipboard(jsonData, () => {
  83. toast.success('Copied JSON to clipboard')
  84. onCopyAsJSON?.()
  85. })
  86. }
  87. const copyAsCSV = () => {
  88. const csv = convertResultsToCSV(results)
  89. if (!csv) {
  90. toast('Results are empty')
  91. return
  92. }
  93. copyToClipboard(csv, () => {
  94. toast.success('Copied CSV to clipboard')
  95. onCopyAsCSV?.()
  96. })
  97. }
  98. useShortcut(SHORTCUT_IDS.RESULTS_COPY_MARKDOWN, copyAsMarkdown, {
  99. enabled: !isEmpty && enableCopyShortcuts,
  100. conflictBehavior: 'allow',
  101. registerInCommandMenu: true,
  102. })
  103. useShortcut(SHORTCUT_IDS.RESULTS_COPY_JSON, copyAsJSON, {
  104. enabled: !isEmpty && enableCopyShortcuts,
  105. conflictBehavior: 'allow',
  106. registerInCommandMenu: true,
  107. })
  108. useShortcut(SHORTCUT_IDS.RESULTS_COPY_CSV, copyAsCSV, {
  109. enabled: !isEmpty && enableCopyShortcuts,
  110. conflictBehavior: 'allow',
  111. registerInCommandMenu: true,
  112. })
  113. useShortcut(SHORTCUT_IDS.RESULTS_DOWNLOAD_CSV, downloadAsCSV, {
  114. enabled: !isEmpty,
  115. registerInCommandMenu: true,
  116. })
  117. return (
  118. <DropdownMenu>
  119. <DropdownMenuTrigger asChild>
  120. <Button
  121. type={type}
  122. icon={iconOnly ? <Download /> : undefined}
  123. iconRight={iconOnly ? undefined : <ChevronDown />}
  124. disabled={results.length === 0}
  125. className={iconOnly ? 'w-7' : ''}
  126. >
  127. {!iconOnly && text}
  128. </Button>
  129. </DropdownMenuTrigger>
  130. <DropdownMenuContent align={align} className="w-60">
  131. {isLogs && IS_PLATFORM && (
  132. <DropdownMenuItem asChild className="gap-x-2">
  133. <Link href={`/project/${ref}/settings/log-drains`}>
  134. <Settings size={14} />
  135. <p>Add a Log Drain</p>
  136. </Link>
  137. </DropdownMenuItem>
  138. )}
  139. <DropdownMenuItem onClick={copyAsMarkdown} className="gap-x-2">
  140. <Copy size={14} />
  141. <p>Copy as Markdown</p>
  142. <span className="ml-auto">
  143. <KeyboardShortcut keys={['Shift', 'Meta', 'm']} />
  144. </span>
  145. </DropdownMenuItem>
  146. <DropdownMenuItem onClick={copyAsJSON} className="gap-x-2">
  147. <Copy size={14} />
  148. <p>Copy as JSON</p>
  149. <span className="ml-auto">
  150. <KeyboardShortcut keys={['Shift', 'Meta', 'j']} />
  151. </span>
  152. </DropdownMenuItem>
  153. <DropdownMenuItem onClick={copyAsCSV} className="gap-x-2">
  154. <Copy size={14} />
  155. <p>Copy as CSV</p>
  156. <span className="ml-auto">
  157. <KeyboardShortcut keys={['Shift', 'Meta', 'c']} />
  158. </span>
  159. </DropdownMenuItem>
  160. <DropdownMenuItem className="gap-x-2" onClick={() => downloadAsCSV()}>
  161. <Download size={14} />
  162. <p>Download CSV</p>
  163. <span className="ml-auto">
  164. <KeyboardShortcut keys={['Shift', 'Meta', 'd']} />
  165. </span>
  166. </DropdownMenuItem>
  167. </DropdownMenuContent>
  168. </DropdownMenu>
  169. )
  170. }