file-view-text.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  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 { useSnapshotContext } from '@/features/ide-react/context/snapshot-context'
  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 { _id: projectId } = useProjectContext()
  18. const { fileTreeFromHistory } = useSnapshotContext()
  19. const [textPreview, setTextPreview] = useState('')
  20. const [shouldShowDots, setShouldShowDots] = useState(false)
  21. const [inFlight, setInFlight] = useState(false)
  22. const fetchContentLengthController = useAbortController()
  23. const fetchDataController = useAbortController()
  24. useEffect(() => {
  25. if (inFlight) {
  26. return
  27. }
  28. let path = fileTreeFromHistory
  29. ? `/project/${projectId}/blob/${file.hash}`
  30. : `/project/${projectId}/file/${file.id}`
  31. const fetchContentLengthTimeout = setTimeout(
  32. () => fetchContentLengthController.abort(),
  33. 10000
  34. )
  35. let fetchDataTimeout: number | undefined
  36. fetch(path, { method: 'HEAD', signal: fetchContentLengthController.signal })
  37. .then(response => {
  38. if (!response.ok) throw new Error('HTTP Error Code: ' + response.status)
  39. return response.headers.get('Content-Length')
  40. })
  41. .then(fileSize => {
  42. let truncated = false
  43. let maxSize = null
  44. if (fileSize && Number(fileSize) > MAX_FILE_SIZE) {
  45. truncated = true
  46. maxSize = MAX_FILE_SIZE
  47. }
  48. if (maxSize != null) {
  49. path += `?range=0-${maxSize}`
  50. }
  51. fetchDataTimeout = window.setTimeout(
  52. () => fetchDataController.abort(),
  53. 60000
  54. )
  55. return fetch(path, { signal: fetchDataController.signal }).then(
  56. response => {
  57. return response.text().then(text => {
  58. if (truncated) {
  59. text = text.replace(/\n.*$/, '')
  60. }
  61. setTextPreview(text)
  62. onLoad()
  63. setShouldShowDots(truncated)
  64. })
  65. }
  66. )
  67. })
  68. .catch(err => {
  69. debugConsole.error('Error fetching file contents', err)
  70. onError()
  71. })
  72. .finally(() => {
  73. setInFlight(false)
  74. clearTimeout(fetchContentLengthTimeout)
  75. clearTimeout(fetchDataTimeout)
  76. })
  77. }, [
  78. fileTreeFromHistory,
  79. projectId,
  80. file.id,
  81. file.hash,
  82. onError,
  83. onLoad,
  84. inFlight,
  85. fetchContentLengthController,
  86. fetchDataController,
  87. ])
  88. return (
  89. Boolean(textPreview) && (
  90. <div className="text-preview">
  91. <div className="scroll-container">
  92. <p>{textPreview}</p>
  93. {shouldShowDots && <p>...</p>}
  94. </div>
  95. </div>
  96. )
  97. )
  98. }