file-view-text.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { useState, useEffect } from 'react'
  2. import { useProjectContext } from '../../../shared/context/project-context'
  3. import { debugConsole } from '@/utils/debugging'
  4. import useAbortController from '../../../shared/hooks/use-abort-controller'
  5. import { BinaryFile } from '@/features/file-view/types/binary-file'
  6. import { fileUrl } from '../../utils/fileUrl'
  7. const MAX_FILE_SIZE = 2 * 1024 * 1024
  8. export default function FileViewText({
  9. file,
  10. onLoad,
  11. onError,
  12. }: {
  13. file: BinaryFile
  14. onLoad: () => void
  15. onError: () => void
  16. }) {
  17. const { projectId } = useProjectContext()
  18. const [textPreview, setTextPreview] = useState('')
  19. const [shouldShowDots, setShouldShowDots] = useState(false)
  20. const [inFlight, setInFlight] = useState(false)
  21. const fetchContentLengthController = useAbortController()
  22. const fetchDataController = useAbortController()
  23. useEffect(() => {
  24. if (inFlight) {
  25. return
  26. }
  27. const path = fileUrl(projectId, file.id, file.hash)
  28. const fetchContentLengthTimeout = setTimeout(
  29. () => fetchContentLengthController.abort(),
  30. 10000
  31. )
  32. let fetchDataTimeout: number | undefined
  33. fetch(path, { method: 'HEAD', signal: fetchContentLengthController.signal })
  34. .then(response => {
  35. if (!response.ok) throw new Error('HTTP Error Code: ' + response.status)
  36. return response.headers.get('Content-Length')
  37. })
  38. .then(fileSize => {
  39. let truncated = false
  40. const headers = new Headers()
  41. if (fileSize && Number(fileSize) > MAX_FILE_SIZE) {
  42. truncated = true
  43. headers.set('Range', `bytes=0-${MAX_FILE_SIZE}`)
  44. }
  45. fetchDataTimeout = window.setTimeout(
  46. () => fetchDataController.abort(),
  47. 60000
  48. )
  49. const signal = fetchDataController.signal
  50. return fetch(path, { signal, headers }).then(response => {
  51. return response.text().then(text => {
  52. if (truncated) {
  53. text = text.replace(/\n.*$/, '')
  54. }
  55. setTextPreview(text)
  56. onLoad()
  57. setShouldShowDots(truncated)
  58. })
  59. })
  60. })
  61. .catch(err => {
  62. debugConsole.error('Error fetching file contents', err)
  63. onError()
  64. })
  65. .finally(() => {
  66. setInFlight(false)
  67. clearTimeout(fetchContentLengthTimeout)
  68. clearTimeout(fetchDataTimeout)
  69. })
  70. }, [
  71. projectId,
  72. file.id,
  73. file.hash,
  74. onError,
  75. onLoad,
  76. inFlight,
  77. fetchContentLengthController,
  78. fetchDataController,
  79. ])
  80. return (
  81. Boolean(textPreview) && (
  82. <div className="text-preview">
  83. <div className="scroll-container">
  84. <p>{textPreview}</p>
  85. {shouldShowDots && <p>...</p>}
  86. </div>
  87. </div>
  88. )
  89. )
  90. }