file-view-text.tsx 2.7 KB

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