index.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. 'use client'
  2. import type { FC } from 'react'
  3. import { createPortal } from 'react-dom'
  4. import { RiCloseLine } from '@remixicon/react'
  5. import { useTranslation } from 'react-i18next'
  6. import Loading from '@/app/components/base/loading'
  7. import { useEffect, useState } from 'react'
  8. import { KKFILEVIEW_URL } from '@/config'
  9. type KKFileViewPreviewProps = {
  10. url: string
  11. fileName: string
  12. onCancel: () => void
  13. }
  14. const KKFileViewPreview: FC<KKFileViewPreviewProps> = ({
  15. url,
  16. fileName,
  17. onCancel,
  18. }) => {
  19. const { t } = useTranslation()
  20. const [isLoading, setIsLoading] = useState(true)
  21. const [previewUrl, setPreviewUrl] = useState('')
  22. useEffect(() => {
  23. if (!KKFILEVIEW_URL) {
  24. console.error('KKFILEVIEW_URL is not configured')
  25. return
  26. }
  27. if (url) {
  28. // kkFileView official doc: first Base64 encode, then URL encode
  29. // Reference: https://file.kkview.cn/integrated
  30. const base64Url = btoa(url)
  31. const kkFileViewUrl = `${KKFILEVIEW_URL}/onlinePreview?url=${encodeURIComponent(base64Url)}`
  32. setPreviewUrl(kkFileViewUrl)
  33. }
  34. }, [url])
  35. const handleIframeLoad = () => {
  36. setIsLoading(false)
  37. }
  38. const handleIframeError = () => {
  39. setIsLoading(false)
  40. console.error('Failed to load document preview')
  41. }
  42. return createPortal(
  43. <div
  44. className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/80"
  45. onClick={onCancel}
  46. >
  47. <div
  48. className="relative h-[95vh] w-[95vw] max-w-[1400px] overflow-hidden rounded-lg bg-white shadow-2xl"
  49. onClick={e => e.stopPropagation()}
  50. >
  51. {/* Header */}
  52. <div className="flex h-14 items-center justify-between border-b border-divider-subtle bg-background-section-burn px-6">
  53. <div className="flex items-center gap-2">
  54. <span className="system-md-semibold text-text-primary">
  55. {t('datasetDocuments.list.preview.title')}
  56. </span>
  57. <span className="system-sm-regular text-text-tertiary">
  58. {fileName}
  59. </span>
  60. </div>
  61. <button
  62. onClick={onCancel}
  63. className="flex h-8 w-8 items-center justify-center rounded-lg hover:bg-state-base-hover"
  64. >
  65. <RiCloseLine className="h-5 w-5 text-text-tertiary" />
  66. </button>
  67. </div>
  68. {/* Preview Content */}
  69. <div className="relative h-[calc(100%-3.5rem)] w-full">
  70. {isLoading && (
  71. <div className="absolute inset-0 z-10 flex items-center justify-center bg-white">
  72. <Loading type="app" />
  73. </div>
  74. )}
  75. {previewUrl
  76. ? (
  77. <iframe
  78. src={previewUrl}
  79. className="h-full w-full border-0"
  80. title={fileName}
  81. onLoad={handleIframeLoad}
  82. onError={handleIframeError}
  83. sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
  84. />
  85. )
  86. : (
  87. <div className="flex h-full items-center justify-center">
  88. <div className="text-center">
  89. <p className="text-text-tertiary">
  90. {t('datasetDocuments.list.preview.error')}
  91. </p>
  92. </div>
  93. </div>
  94. )}
  95. </div>
  96. </div>
  97. </div>,
  98. document.body,
  99. )
  100. }
  101. export default KKFileViewPreview