code2024/bingo
0
1import {2 useEffect,3 useState,4 useCallback,5 ChangeEvent,6 ClipboardEvent,7 MouseEventHandler,8 FormEvent,9 useRef10} from "react"11import Image from 'next/image'12import PasteIcon from '@/assets/images/paste.svg'13import UploadIcon from '@/assets/images/upload.svg'14import CameraIcon from '@/assets/images/camera.svg'15import { useBing } from '@/lib/hooks/use-bing'16import { cn } from '@/lib/utils'17 18interface ChatImageProps extends Pick<ReturnType<typeof useBing>, 'uploadImage'> {}19 20const preventDefault: MouseEventHandler<HTMLDivElement> = (event) => {21 event.nativeEvent.stopImmediatePropagation()22}23 24const toBase64 = (file: File): Promise<string> => new Promise((resolve, reject) => {25 const reader = new FileReader()26 reader.readAsDataURL(file)27 reader.onload = () => resolve(reader.result as string)28 reader.onerror = reject29})30 31export function ChatImage({ children, uploadImage }: React.PropsWithChildren<ChatImageProps>) {32 const videoRef = useRef<HTMLVideoElement>(null)33 const canvasRef = useRef<HTMLCanvasElement>(null)34 const mediaStream = useRef<MediaStream>()35 const [panel, setPanel] = useState('none')36 37 const upload = useCallback((url: string) => {38 if (url) {39 uploadImage(url)40 }41 setPanel('none')42 }, [panel])43 44 const onUpload = useCallback(async (event: ChangeEvent<HTMLInputElement>) => {45 const file = event.target.files?.[0]46 if (file) {47 const fileDataUrl = await toBase64(file)48 if (fileDataUrl) {49 upload(fileDataUrl)50 }51 }52 }, [])53 54 const onPaste = useCallback((event: ClipboardEvent<HTMLInputElement>) => {55 const pasteUrl = event.clipboardData.getData('text') ?? ''56 upload(pasteUrl)57 }, [])58 59 const onEnter = useCallback((event: FormEvent<HTMLFormElement>) => {60 event.preventDefault()61 event.stopPropagation()62 // @ts-ignore63 const inputUrl = event.target.elements.image.value64 if (inputUrl) {65 upload(inputUrl)66 }67 }, [])68 69 const openVideo: MouseEventHandler<HTMLButtonElement> = async (event) => {70 event.stopPropagation()71 setPanel('camera-mode')72 }73 74 const onCapture = () => {75 if (canvasRef.current && videoRef.current) {76 const canvas = canvasRef.current77 canvas.width = videoRef.current!.videoWidth78 canvas.height = videoRef.current!.videoHeight79 canvas.getContext('2d')?.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height)80 const cameraUrl = canvas.toDataURL('image/jpeg')81 upload(cameraUrl)82 }83 }84 85 useEffect(() => {86 const handleBlur = () => {87 if (panel !== 'none') {88 setPanel('none')89 }90 }91 document.addEventListener('click', handleBlur)92 return () => {93 document.removeEventListener('click', handleBlur)94 }95 }, [panel])96 97 useEffect(() => {98 if (panel === 'camera-mode') {99 navigator.mediaDevices.getUserMedia({ video: true, audio: false })100 .then(videoStream => {101 mediaStream.current = videoStream102 if (videoRef.current) {103 videoRef.current.srcObject = videoStream104 }105 })106 } else {107 if (mediaStream.current) {108 mediaStream.current.getTracks().forEach(function(track) {109 track.stop()110 })111 mediaStream.current = undefined112 }113 }114 }, [panel])115 116 return (117 <div className="visual-search-container">118 <div onClick={() => panel === 'none' ? setPanel('normal') : setPanel('none')}>{children}</div>119 <div className={cn('visual-search', panel)} onClick={preventDefault}>120 <div className="normal-content">121 <div className="header">122 <h4>添加图像</h4>123 </div>124 <div className="paste">125 <Image alt="paste" src={PasteIcon} width={24} />126 <form onSubmitCapture={onEnter}>127 <input128 className="paste-input"129 id="sb_imgpst"130 type="text"131 name="image"132 placeholder="粘贴图像 URL"133 aria-label="粘贴图像 URL"134 onPaste={onPaste}135 onClickCapture={(e) => e.stopPropagation()}136 />137 </form>138 </div>139 <div className="buttons">140 <button type="button" aria-label="从此设备上传">141 <input142 id="vs_fileinput"143 className="fileinput"144 type="file"145 accept="image/gif, image/jpeg, image/png, image/webp"146 onChange={onUpload}147 />148 <Image alt="uplaod" src={UploadIcon} width={20} />149 从此设备上传150 </button>151 <button type="button" aria-label="拍照" onClick={openVideo}>152 <Image alt="camera" src={CameraIcon} width={20} />153 拍照154 </button>155 </div>156 </div>157 {panel === 'camera-mode' && <div className="cam-content">158 <div className="webvideo-container">159 <video className="webvideo" autoPlay muted playsInline ref={videoRef} />160 <canvas className="webcanvas" ref={canvasRef} />161 </div>162 <div className="cambtn" role="button" aria-label="拍照" onClick={onCapture}>163 <div className="cam-btn-circle-large"></div>164 <div className="cam-btn-circle-small"></div>165 </div>166 </div>}167 </div>168 </div>169 )170}171 