From c635d78b479510ebe2556a420948effcd30a0731 Mon Sep 17 00:00:00 2001 From: skyouc Date: 星期六, 21 十二月 2024 18:40:43 +0800 Subject: [PATCH] 新建德森项目分支 --- zy-asrs-flow/src/components/IconSelector/IconPicSearcher.tsx | 466 +++++++++++++++++++++++++++++----------------------------- 1 files changed, 233 insertions(+), 233 deletions(-) diff --git a/zy-asrs-flow/src/components/IconSelector/IconPicSearcher.tsx b/zy-asrs-flow/src/components/IconSelector/IconPicSearcher.tsx index 3a4cf01..9057cec 100644 --- a/zy-asrs-flow/src/components/IconSelector/IconPicSearcher.tsx +++ b/zy-asrs-flow/src/components/IconSelector/IconPicSearcher.tsx @@ -1,233 +1,233 @@ -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; +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; -- Gitblit v1.9.1