| import React, { useCallback, useEffect, useState } from 'react'; | 
| import { Upload, Tooltip, Popover, Modal, Progress, Spin, Result } from 'antd'; | 
| import * as AntdIcons from '@ant-design/icons'; | 
| import { useIntl } from '@umijs/max'; | 
| import './style.less'; | 
|   | 
| const allIcons: { [key: string]: any } = AntdIcons; | 
|   | 
| const { Dragger } = Upload; | 
| interface AntdIconClassifier { | 
|   load: () => void; | 
|   predict: (imgEl: HTMLImageElement) => void; | 
| } | 
| declare global { | 
|   interface Window { | 
|     antdIconClassifier: AntdIconClassifier; | 
|   } | 
| } | 
|   | 
| interface PicSearcherState { | 
|   loading: boolean; | 
|   modalOpen: boolean; | 
|   popoverVisible: boolean; | 
|   icons: iconObject[]; | 
|   fileList: any[]; | 
|   error: boolean; | 
|   modelLoaded: boolean; | 
| } | 
|   | 
| interface iconObject { | 
|   type: string; | 
|   score: number; | 
| } | 
|   | 
| const PicSearcher: React.FC = () => { | 
|   const intl = useIntl(); | 
|   const {formatMessage} = intl; | 
|   const [state, setState] = useState<PicSearcherState>({ | 
|     loading: false, | 
|     modalOpen: false, | 
|     popoverVisible: false, | 
|     icons: [], | 
|     fileList: [], | 
|     error: false, | 
|     modelLoaded: false, | 
|   }); | 
|   const predict = (imgEl: HTMLImageElement) => { | 
|     try { | 
|       let icons: any[] = window.antdIconClassifier.predict(imgEl); | 
|       if (gtag && icons.length) { | 
|         gtag('event', 'icon', { | 
|           event_category: 'search-by-image', | 
|           event_label: icons[0].className, | 
|         }); | 
|       } | 
|       icons = icons.map(i => ({ score: i.score, type: i.className.replace(/\s/g, '-') })); | 
|       setState(prev => ({ ...prev, loading: false, error: false, icons })); | 
|     } catch { | 
|       setState(prev => ({ ...prev, loading: false, error: true })); | 
|     } | 
|   }; | 
|   // eslint-disable-next-line class-methods-use-this | 
|   const toImage = (url: string) => | 
|     new Promise(resolve => { | 
|       const img = new Image(); | 
|       img.setAttribute('crossOrigin', 'anonymous'); | 
|       img.src = url; | 
|       img.onload = () => { | 
|         resolve(img); | 
|       }; | 
|     }); | 
|   | 
|   const uploadFile = useCallback((file: File) => { | 
|     setState(prev => ({ ...prev, loading: true })); | 
|     const reader = new FileReader(); | 
|     reader.onload = () => { | 
|       toImage(reader.result as string).then(predict); | 
|       setState(prev => ({ | 
|         ...prev, | 
|         fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }], | 
|       })); | 
|     }; | 
|     reader.readAsDataURL(file); | 
|   }, []); | 
|   | 
|   const onPaste = useCallback((event: ClipboardEvent) => { | 
|     const items = event.clipboardData && event.clipboardData.items; | 
|     let file = null; | 
|     if (items && items.length) { | 
|       for (let i = 0; i < items.length; i++) { | 
|         if (items[i].type.includes('image')) { | 
|           file = items[i].getAsFile(); | 
|           break; | 
|         } | 
|       } | 
|     } | 
|     if (file) { | 
|       uploadFile(file); | 
|     } | 
|   }, []); | 
|   const toggleModal = useCallback(() => { | 
|     setState(prev => ({ | 
|       ...prev, | 
|       modalOpen: !prev.modalOpen, | 
|       popoverVisible: false, | 
|       fileList: [], | 
|       icons: [], | 
|     })); | 
|     if (!localStorage.getItem('disableIconTip')) { | 
|       localStorage.setItem('disableIconTip', 'true'); | 
|     } | 
|   }, []); | 
|   | 
|   useEffect(() => { | 
|     const script = document.createElement('script'); | 
|     script.onload = async () => { | 
|       await window.antdIconClassifier.load(); | 
|       setState(prev => ({ ...prev, modelLoaded: true })); | 
|       document.addEventListener('paste', onPaste); | 
|     }; | 
|     script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js'; | 
|     document.head.appendChild(script); | 
|     setState(prev => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') })); | 
|     return () => { | 
|       document.removeEventListener('paste', onPaste); | 
|     }; | 
|   }, []); | 
|   | 
|   return ( | 
|     <div className="iconPicSearcher"> | 
|       <Popover | 
|         content={formatMessage({id: 'app.docs.components.icon.pic-searcher.intro'})} | 
|         open={state.popoverVisible} | 
|       > | 
|         <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} /> | 
|       </Popover> | 
|       <Modal | 
|         title={intl.formatMessage({ | 
|           id: 'app.docs.components.icon.pic-searcher.title', | 
|           defaultMessage: '信息', | 
|         })} | 
|         open={state.modalOpen} | 
|         onCancel={toggleModal} | 
|         footer={null} | 
|       > | 
|         {state.modelLoaded || ( | 
|           <Spin | 
|             spinning={!state.modelLoaded} | 
|             tip={formatMessage({ | 
|               id: 'app.docs.components.icon.pic-searcher.modelloading', | 
|   | 
|             })} | 
|           > | 
|             <div style={{ height: 100 }} /> | 
|           </Spin> | 
|         )} | 
|         {state.modelLoaded && ( | 
|           <Dragger | 
|             accept="image/jpeg, image/png" | 
|             listType="picture" | 
|             customRequest={o => uploadFile(o.file as File)} | 
|             fileList={state.fileList} | 
|             showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} | 
|           > | 
|             <p className="ant-upload-drag-icon"> | 
|               <AntdIcons.InboxOutlined /> | 
|             </p> | 
|             <p className="ant-upload-text"> | 
|               {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-text'})} | 
|             </p> | 
|             <p className="ant-upload-hint"> | 
|               {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-hint'})} | 
|             </p> | 
|           </Dragger> | 
|         )} | 
|         <Spin | 
|           spinning={state.loading} | 
|           tip={formatMessage({id: 'app.docs.components.icon.pic-searcher.matching'})} | 
|         > | 
|           <div className="icon-pic-search-result"> | 
|             {state.icons.length > 0 && ( | 
|               <div className="result-tip"> | 
|                 {formatMessage({id: 'app.docs.components.icon.pic-searcher.result-tip'})} | 
|               </div> | 
|             )} | 
|             <table> | 
|               {state.icons.length > 0 && ( | 
|                 <thead> | 
|                   <tr> | 
|                     <th className="col-icon"> | 
|                       {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-icon'})} | 
|                     </th> | 
|                     <th>{formatMessage({id: 'app.docs.components.icon.pic-searcher.th-score'})}</th> | 
|                   </tr> | 
|                 </thead> | 
|               )} | 
|               <tbody> | 
|                 {state.icons.map(icon => { | 
|                   const { type } = icon; | 
|                   const iconName = `${type | 
|                     .split('-') | 
|                     .map(str => `${str[0].toUpperCase()}${str.slice(1)}`) | 
|                     .join('')}Outlined`; | 
|                   return ( | 
|                     <tr key={iconName}> | 
|                       <td className="col-icon"> | 
|                           <Tooltip title={icon.type} placement="right"> | 
|                             {React.createElement(allIcons[iconName])} | 
|                           </Tooltip> | 
|                       </td> | 
|                       <td> | 
|                         <Progress percent={Math.ceil(icon.score * 100)} /> | 
|                       </td> | 
|                     </tr> | 
|                   ); | 
|                 })} | 
|               </tbody> | 
|             </table> | 
|             {state.error && ( | 
|               <Result | 
|                 status="500" | 
|                 title="503" | 
|                 subTitle={formatMessage({id: 'app.docs.components.icon.pic-searcher.server-error'})} | 
|               /> | 
|             )} | 
|           </div> | 
|         </Spin> | 
|       </Modal> | 
|     </div> | 
|   ); | 
| }; | 
|   | 
| export default PicSearcher; |