file-view-text.jsx 2.8 KB

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