ContentModal.jsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useState, useEffect } from 'react';
  16. import { Modal, Button, Typography, Spin } from '@douyinfe/semi-ui';
  17. import { IconExternalOpen, IconCopy } from '@douyinfe/semi-icons';
  18. import { useTranslation } from 'react-i18next';
  19. const { Text } = Typography;
  20. const ContentModal = ({
  21. isModalOpen,
  22. setIsModalOpen,
  23. modalContent,
  24. isVideo,
  25. }) => {
  26. const { t } = useTranslation();
  27. const [videoError, setVideoError] = useState(false);
  28. const [isLoading, setIsLoading] = useState(false);
  29. useEffect(() => {
  30. if (isModalOpen && isVideo) {
  31. setVideoError(false);
  32. setIsLoading(true);
  33. }
  34. }, [isModalOpen, isVideo]);
  35. const handleVideoError = () => {
  36. setVideoError(true);
  37. setIsLoading(false);
  38. };
  39. const handleVideoLoaded = () => {
  40. setIsLoading(false);
  41. };
  42. const handleCopyUrl = () => {
  43. navigator.clipboard.writeText(modalContent);
  44. };
  45. const handleOpenInNewTab = () => {
  46. window.open(modalContent, '_blank');
  47. };
  48. const renderVideoContent = () => {
  49. if (videoError) {
  50. return (
  51. <div style={{ textAlign: 'center', padding: '40px' }}>
  52. <Text
  53. type='tertiary'
  54. style={{ display: 'block', marginBottom: '16px' }}
  55. >
  56. {t('视频无法在当前浏览器中播放,这可能是由于:')}
  57. </Text>
  58. <Text
  59. type='tertiary'
  60. style={{ display: 'block', marginBottom: '8px', fontSize: '12px' }}
  61. >
  62. {t('• 视频服务商的跨域限制')}
  63. </Text>
  64. <Text
  65. type='tertiary'
  66. style={{ display: 'block', marginBottom: '8px', fontSize: '12px' }}
  67. >
  68. {t('• 需要特定的请求头或认证')}
  69. </Text>
  70. <Text
  71. type='tertiary'
  72. style={{ display: 'block', marginBottom: '16px', fontSize: '12px' }}
  73. >
  74. {t('• 防盗链保护机制')}
  75. </Text>
  76. <div style={{ marginTop: '20px' }}>
  77. <Button
  78. icon={<IconExternalOpen />}
  79. onClick={handleOpenInNewTab}
  80. style={{ marginRight: '8px' }}
  81. >
  82. {t('在新标签页中打开')}
  83. </Button>
  84. <Button icon={<IconCopy />} onClick={handleCopyUrl}>
  85. {t('复制链接')}
  86. </Button>
  87. </div>
  88. <div
  89. style={{
  90. marginTop: '16px',
  91. padding: '8px',
  92. backgroundColor: '#f8f9fa',
  93. borderRadius: '4px',
  94. }}
  95. >
  96. <Text
  97. type='tertiary'
  98. style={{ fontSize: '10px', wordBreak: 'break-all' }}
  99. >
  100. {modalContent}
  101. </Text>
  102. </div>
  103. </div>
  104. );
  105. }
  106. return (
  107. <div style={{ position: 'relative', height: '100%' }}>
  108. {isLoading && (
  109. <div
  110. style={{
  111. position: 'absolute',
  112. top: '50%',
  113. left: '50%',
  114. transform: 'translate(-50%, -50%)',
  115. zIndex: 10,
  116. }}
  117. >
  118. <Spin size='large' />
  119. </div>
  120. )}
  121. <video
  122. src={modalContent}
  123. controls
  124. style={{
  125. width: '100%',
  126. height: '100%',
  127. maxWidth: '100%',
  128. maxHeight: '100%',
  129. objectFit: 'contain',
  130. }}
  131. onError={handleVideoError}
  132. onLoadedData={handleVideoLoaded}
  133. onLoadStart={() => setIsLoading(true)}
  134. />
  135. </div>
  136. );
  137. };
  138. return (
  139. <Modal
  140. visible={isModalOpen}
  141. onOk={() => setIsModalOpen(false)}
  142. onCancel={() => setIsModalOpen(false)}
  143. closable={null}
  144. bodyStyle={{
  145. height: isVideo ? '70vh' : '400px',
  146. maxHeight: '80vh',
  147. overflow: 'auto',
  148. padding: isVideo && videoError ? '0' : '24px',
  149. }}
  150. width={isVideo ? '90vw' : 800}
  151. style={isVideo ? { maxWidth: 960 } : undefined}
  152. >
  153. {isVideo ? (
  154. renderVideoContent()
  155. ) : (
  156. <p style={{ whiteSpace: 'pre-line' }}>{modalContent}</p>
  157. )}
  158. </Modal>
  159. );
  160. };
  161. export default ContentModal;