| @@ -1,37 +0,0 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-04-28 14:18:11 | |||||
| * @Description: 自定义 Table 数组类单元格 | |||||
| */ | |||||
| import { Tooltip } from 'antd'; | |||||
| function ArrayTableCell(ellipsis: boolean = false, property?: string) { | |||||
| return (value?: any | null) => { | |||||
| if ( | |||||
| value === undefined || | |||||
| value === null || | |||||
| Array.isArray(value) === false || | |||||
| value.length === 0 | |||||
| ) { | |||||
| return <span>--</span>; | |||||
| } | |||||
| let list = value; | |||||
| if (property && typeof value[0] === 'object') { | |||||
| list = value.map((item) => item[property]); | |||||
| } | |||||
| const text = list.join(','); | |||||
| if (ellipsis) { | |||||
| return ( | |||||
| <Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}> | |||||
| <span>{text}</span>; | |||||
| </Tooltip> | |||||
| ); | |||||
| } else { | |||||
| return <span>{text}</span>; | |||||
| } | |||||
| }; | |||||
| } | |||||
| export default ArrayTableCell; | |||||
| @@ -1,45 +0,0 @@ | |||||
| import { GithubOutlined } from '@ant-design/icons'; | |||||
| import { DefaultFooter } from '@ant-design/pro-components'; | |||||
| import { useIntl } from '@umijs/max'; | |||||
| import React from 'react'; | |||||
| const Footer: React.FC = () => { | |||||
| const intl = useIntl(); | |||||
| const defaultMessage = intl.formatMessage({ | |||||
| id: 'app.copyright.produced', | |||||
| defaultMessage: '蚂蚁集团体验技术部出品', | |||||
| }); | |||||
| const currentYear = new Date().getFullYear(); | |||||
| return ( | |||||
| <DefaultFooter | |||||
| style={{ | |||||
| background: 'none', | |||||
| }} | |||||
| copyright={`${currentYear} ${defaultMessage}`} | |||||
| links={[ | |||||
| { | |||||
| key: 'Ant Design Pro', | |||||
| title: 'Ant Design Pro', | |||||
| href: 'https://pro.ant.design', | |||||
| blankTarget: true, | |||||
| }, | |||||
| { | |||||
| key: 'github', | |||||
| title: <GithubOutlined />, | |||||
| href: 'https://github.com/ant-design/ant-design-pro', | |||||
| blankTarget: true, | |||||
| }, | |||||
| { | |||||
| key: 'Ant Design', | |||||
| title: 'Ant Design', | |||||
| href: 'https://ant.design', | |||||
| blankTarget: true, | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| export default Footer; | |||||
| @@ -1,64 +0,0 @@ | |||||
| import { useIntl } from '@umijs/max'; | |||||
| import * as React from 'react'; | |||||
| import CopyableIcon from './CopyableIcon'; | |||||
| import type { CategoriesKeys } from './fields'; | |||||
| import type { ThemeType } from './index'; | |||||
| import styles from './style.less'; | |||||
| interface CategoryProps { | |||||
| title: CategoriesKeys; | |||||
| icons: string[]; | |||||
| theme: ThemeType; | |||||
| newIcons: string[]; | |||||
| onSelect: (type: string, name: string) => any; | |||||
| } | |||||
| const Category: React.FC<CategoryProps> = (props) => { | |||||
| const { icons, title, newIcons, theme } = props; | |||||
| const intl = useIntl(); | |||||
| const [justCopied, setJustCopied] = React.useState<string | null>(null); | |||||
| const copyId = React.useRef<NodeJS.Timeout | null>(null); | |||||
| const onSelect = React.useCallback((type: string, text: string) => { | |||||
| const { onSelect } = props; | |||||
| if (onSelect) { | |||||
| onSelect(type, text); | |||||
| } | |||||
| setJustCopied(type); | |||||
| copyId.current = setTimeout(() => { | |||||
| setJustCopied(null); | |||||
| }, 2000); | |||||
| }, []); | |||||
| React.useEffect( | |||||
| () => () => { | |||||
| if (copyId.current) { | |||||
| clearTimeout(copyId.current); | |||||
| } | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| return ( | |||||
| <div> | |||||
| <h4> | |||||
| {intl.formatMessage({ | |||||
| id: `app.docs.components.icon.category.${title}`, | |||||
| defaultMessage: '信息', | |||||
| })} | |||||
| </h4> | |||||
| <ul className={styles.anticonsList}> | |||||
| {icons.map((name) => ( | |||||
| <CopyableIcon | |||||
| key={name} | |||||
| name={name} | |||||
| theme={theme} | |||||
| isNew={newIcons.includes(name)} | |||||
| justCopied={justCopied} | |||||
| onSelect={onSelect} | |||||
| /> | |||||
| ))} | |||||
| </ul> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Category; | |||||
| @@ -1,44 +0,0 @@ | |||||
| import * as AntdIcons from '@ant-design/icons'; | |||||
| import { Tooltip } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import * as React from 'react'; | |||||
| import type { ThemeType } from './index'; | |||||
| import styles from './style.less'; | |||||
| const allIcons: { | |||||
| [key: string]: any; | |||||
| } = AntdIcons; | |||||
| export interface CopyableIconProps { | |||||
| name: string; | |||||
| isNew: boolean; | |||||
| theme: ThemeType; | |||||
| justCopied: string | null; | |||||
| onSelect: (type: string, text: string) => any; | |||||
| } | |||||
| const CopyableIcon: React.FC<CopyableIconProps> = ({ name, justCopied, onSelect, theme }) => { | |||||
| const className = classNames({ | |||||
| copied: justCopied === name, | |||||
| [theme]: !!theme, | |||||
| }); | |||||
| return ( | |||||
| <li | |||||
| className={className} | |||||
| onClick={() => { | |||||
| if (onSelect) { | |||||
| onSelect(theme, name); | |||||
| } | |||||
| }} | |||||
| > | |||||
| <Tooltip title={name}> | |||||
| {React.createElement(allIcons[name], { className: styles.anticon })} | |||||
| </Tooltip> | |||||
| {/* <span className={styles.anticonClass}> | |||||
| <Badge dot={isNew}>{name}</Badge> | |||||
| </span> */} | |||||
| </li> | |||||
| ); | |||||
| }; | |||||
| export default CopyableIcon; | |||||
| @@ -1,237 +0,0 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import * as AntdIcons from '@ant-design/icons'; | |||||
| import { useIntl } from '@umijs/max'; | |||||
| import { Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd'; | |||||
| import React, { useCallback, useEffect, useState } from 'react'; | |||||
| 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> | |||||
| <KFModal | |||||
| 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> | |||||
| </KFModal> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default PicSearcher; | |||||
| @@ -1,223 +0,0 @@ | |||||
| import * as AntdIcons from '@ant-design/icons/lib/icons'; | |||||
| const all = Object.keys(AntdIcons) | |||||
| .map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, '')) | |||||
| .filter((n, i, arr) => arr.indexOf(n) === i); | |||||
| const direction = [ | |||||
| 'StepBackward', | |||||
| 'StepForward', | |||||
| 'FastBackward', | |||||
| 'FastForward', | |||||
| 'Shrink', | |||||
| 'ArrowsAlt', | |||||
| 'Down', | |||||
| 'Up', | |||||
| 'Left', | |||||
| 'Right', | |||||
| 'CaretUp', | |||||
| 'CaretDown', | |||||
| 'CaretLeft', | |||||
| 'CaretRight', | |||||
| 'UpCircle', | |||||
| 'DownCircle', | |||||
| 'LeftCircle', | |||||
| 'RightCircle', | |||||
| 'DoubleRight', | |||||
| 'DoubleLeft', | |||||
| 'VerticalLeft', | |||||
| 'VerticalRight', | |||||
| 'VerticalAlignTop', | |||||
| 'VerticalAlignMiddle', | |||||
| 'VerticalAlignBottom', | |||||
| 'Forward', | |||||
| 'Backward', | |||||
| 'Rollback', | |||||
| 'Enter', | |||||
| 'Retweet', | |||||
| 'Swap', | |||||
| 'SwapLeft', | |||||
| 'SwapRight', | |||||
| 'ArrowUp', | |||||
| 'ArrowDown', | |||||
| 'ArrowLeft', | |||||
| 'ArrowRight', | |||||
| 'PlayCircle', | |||||
| 'UpSquare', | |||||
| 'DownSquare', | |||||
| 'LeftSquare', | |||||
| 'RightSquare', | |||||
| 'Login', | |||||
| 'Logout', | |||||
| 'MenuFold', | |||||
| 'MenuUnfold', | |||||
| 'BorderBottom', | |||||
| 'BorderHorizontal', | |||||
| 'BorderInner', | |||||
| 'BorderOuter', | |||||
| 'BorderLeft', | |||||
| 'BorderRight', | |||||
| 'BorderTop', | |||||
| 'BorderVerticle', | |||||
| 'PicCenter', | |||||
| 'PicLeft', | |||||
| 'PicRight', | |||||
| 'RadiusBottomleft', | |||||
| 'RadiusBottomright', | |||||
| 'RadiusUpleft', | |||||
| 'RadiusUpright', | |||||
| 'Fullscreen', | |||||
| 'FullscreenExit', | |||||
| ]; | |||||
| const suggestion = [ | |||||
| 'Question', | |||||
| 'QuestionCircle', | |||||
| 'Plus', | |||||
| 'PlusCircle', | |||||
| 'Pause', | |||||
| 'PauseCircle', | |||||
| 'Minus', | |||||
| 'MinusCircle', | |||||
| 'PlusSquare', | |||||
| 'MinusSquare', | |||||
| 'Info', | |||||
| 'InfoCircle', | |||||
| 'Exclamation', | |||||
| 'ExclamationCircle', | |||||
| 'Close', | |||||
| 'CloseCircle', | |||||
| 'CloseSquare', | |||||
| 'Check', | |||||
| 'CheckCircle', | |||||
| 'CheckSquare', | |||||
| 'ClockCircle', | |||||
| 'Warning', | |||||
| 'IssuesClose', | |||||
| 'Stop', | |||||
| ]; | |||||
| const editor = [ | |||||
| 'Edit', | |||||
| 'Form', | |||||
| 'Copy', | |||||
| 'Scissor', | |||||
| 'Delete', | |||||
| 'Snippets', | |||||
| 'Diff', | |||||
| 'Highlight', | |||||
| 'AlignCenter', | |||||
| 'AlignLeft', | |||||
| 'AlignRight', | |||||
| 'BgColors', | |||||
| 'Bold', | |||||
| 'Italic', | |||||
| 'Underline', | |||||
| 'Strikethrough', | |||||
| 'Redo', | |||||
| 'Undo', | |||||
| 'ZoomIn', | |||||
| 'ZoomOut', | |||||
| 'FontColors', | |||||
| 'FontSize', | |||||
| 'LineHeight', | |||||
| 'Dash', | |||||
| 'SmallDash', | |||||
| 'SortAscending', | |||||
| 'SortDescending', | |||||
| 'Drag', | |||||
| 'OrderedList', | |||||
| 'UnorderedList', | |||||
| 'RadiusSetting', | |||||
| 'ColumnWidth', | |||||
| 'ColumnHeight', | |||||
| ]; | |||||
| const data = [ | |||||
| 'AreaChart', | |||||
| 'PieChart', | |||||
| 'BarChart', | |||||
| 'DotChart', | |||||
| 'LineChart', | |||||
| 'RadarChart', | |||||
| 'HeatMap', | |||||
| 'Fall', | |||||
| 'Rise', | |||||
| 'Stock', | |||||
| 'BoxPlot', | |||||
| 'Fund', | |||||
| 'Sliders', | |||||
| ]; | |||||
| const logo = [ | |||||
| 'Android', | |||||
| 'Apple', | |||||
| 'Windows', | |||||
| 'Ie', | |||||
| 'Chrome', | |||||
| 'Github', | |||||
| 'Aliwangwang', | |||||
| 'Dingding', | |||||
| 'WeiboSquare', | |||||
| 'WeiboCircle', | |||||
| 'TaobaoCircle', | |||||
| 'Html5', | |||||
| 'Weibo', | |||||
| 'Twitter', | |||||
| 'Wechat', | |||||
| 'Youtube', | |||||
| 'AlipayCircle', | |||||
| 'Taobao', | |||||
| 'Skype', | |||||
| 'Qq', | |||||
| 'MediumWorkmark', | |||||
| 'Gitlab', | |||||
| 'Medium', | |||||
| 'Linkedin', | |||||
| 'GooglePlus', | |||||
| 'Dropbox', | |||||
| 'Facebook', | |||||
| 'Codepen', | |||||
| 'CodeSandbox', | |||||
| 'CodeSandboxCircle', | |||||
| 'Amazon', | |||||
| 'Google', | |||||
| 'CodepenCircle', | |||||
| 'Alipay', | |||||
| 'AntDesign', | |||||
| 'AntCloud', | |||||
| 'Aliyun', | |||||
| 'Zhihu', | |||||
| 'Slack', | |||||
| 'SlackSquare', | |||||
| 'Behance', | |||||
| 'BehanceSquare', | |||||
| 'Dribbble', | |||||
| 'DribbbleSquare', | |||||
| 'Instagram', | |||||
| 'Yuque', | |||||
| 'Alibaba', | |||||
| 'Yahoo', | |||||
| 'Reddit', | |||||
| 'Sketch', | |||||
| 'WhatsApp', | |||||
| 'Dingtalk', | |||||
| ]; | |||||
| const datum = [...direction, ...suggestion, ...editor, ...data, ...logo]; | |||||
| const other = all.filter((n) => !datum.includes(n)); | |||||
| export const categories = { | |||||
| direction, | |||||
| suggestion, | |||||
| editor, | |||||
| data, | |||||
| logo, | |||||
| other, | |||||
| }; | |||||
| export default categories; | |||||
| export type Categories = typeof categories; | |||||
| export type CategoriesKeys = keyof Categories; | |||||
| @@ -1,146 +0,0 @@ | |||||
| import Icon, * as AntdIcons from '@ant-design/icons'; | |||||
| import { Empty, Input, Radio } from 'antd'; | |||||
| import type { RadioChangeEvent } from 'antd/es/radio/interface'; | |||||
| import debounce from 'lodash/debounce'; | |||||
| import * as React from 'react'; | |||||
| import Category from './Category'; | |||||
| import IconPicSearcher from './IconPicSearcher'; | |||||
| import type { CategoriesKeys } from './fields'; | |||||
| import { categories } from './fields'; | |||||
| import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; | |||||
| // import { useIntl } from '@umijs/max'; | |||||
| export enum ThemeType { | |||||
| Filled = 'Filled', | |||||
| Outlined = 'Outlined', | |||||
| TwoTone = 'TwoTone', | |||||
| } | |||||
| const allIcons: { [key: string]: any } = AntdIcons; | |||||
| interface IconSelectorProps { | |||||
| //intl: any; | |||||
| onSelect: any; | |||||
| } | |||||
| interface IconSelectorState { | |||||
| theme: ThemeType; | |||||
| searchKey: string; | |||||
| } | |||||
| const IconSelector: React.FC<IconSelectorProps> = (props) => { | |||||
| // const intl = useIntl(); | |||||
| // const { messages } = intl; | |||||
| const { onSelect } = props; | |||||
| const [displayState, setDisplayState] = React.useState<IconSelectorState>({ | |||||
| theme: ThemeType.Outlined, | |||||
| searchKey: '', | |||||
| }); | |||||
| const newIconNames: string[] = []; | |||||
| const handleSearchIcon = React.useCallback( | |||||
| debounce((searchKey: string) => { | |||||
| setDisplayState((prevState) => ({ ...prevState, searchKey })); | |||||
| }), | |||||
| [], | |||||
| ); | |||||
| const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => { | |||||
| setDisplayState((prevState) => ({ ...prevState, theme: e.target.value as ThemeType })); | |||||
| }, []); | |||||
| const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => { | |||||
| const { searchKey = '', theme } = displayState; | |||||
| const categoriesResult = Object.keys(categories) | |||||
| .map((key: CategoriesKeys) => { | |||||
| let iconList = categories[key]; | |||||
| if (searchKey) { | |||||
| const matchKey = searchKey | |||||
| // eslint-disable-next-line prefer-regex-literals | |||||
| .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name) | |||||
| .replace(/(Filled|Outlined|TwoTone)$/, '') | |||||
| .toLowerCase(); | |||||
| iconList = iconList.filter((iconName: string) => | |||||
| iconName.toLowerCase().includes(matchKey), | |||||
| ); | |||||
| } | |||||
| // CopyrightCircle is same as Copyright, don't show it | |||||
| iconList = iconList.filter((icon: string) => icon !== 'CopyrightCircle'); | |||||
| return { | |||||
| category: key, | |||||
| icons: iconList | |||||
| .map((iconName: string) => iconName + theme) | |||||
| .filter((iconName: string) => allIcons[iconName]), | |||||
| }; | |||||
| }) | |||||
| .filter(({ icons }) => !!icons.length) | |||||
| .map(({ category, icons }) => ( | |||||
| <Category | |||||
| key={category} | |||||
| title={category as CategoriesKeys} | |||||
| theme={theme} | |||||
| icons={icons} | |||||
| newIcons={newIconNames} | |||||
| onSelect={(type, name) => { | |||||
| if (onSelect) { | |||||
| onSelect(name, allIcons[name]); | |||||
| } | |||||
| }} | |||||
| /> | |||||
| )); | |||||
| return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult; | |||||
| }, [displayState.searchKey, displayState.theme]); | |||||
| return ( | |||||
| <> | |||||
| <div style={{ display: 'flex', justifyContent: 'space-between' }}> | |||||
| <Radio.Group | |||||
| value={displayState.theme} | |||||
| onChange={handleChangeTheme} | |||||
| size="large" | |||||
| optionType="button" | |||||
| buttonStyle="solid" | |||||
| options={[ | |||||
| { | |||||
| label: <Icon component={OutlinedIcon} />, | |||||
| value: ThemeType.Outlined, | |||||
| }, | |||||
| { | |||||
| label: <Icon component={FilledIcon} />, | |||||
| value: ThemeType.Filled, | |||||
| }, | |||||
| { | |||||
| label: <Icon component={TwoToneIcon} />, | |||||
| value: ThemeType.TwoTone, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| {/* <Radio.Button value={ThemeType.Outlined}> | |||||
| <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']} | |||||
| </Radio.Button> | |||||
| <Radio.Button value={ThemeType.Filled}> | |||||
| <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']} | |||||
| </Radio.Button> | |||||
| <Radio.Button value={ThemeType.TwoTone}> | |||||
| <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']} | |||||
| </Radio.Button> */} | |||||
| </Radio.Group> | |||||
| <Input.Search | |||||
| // placeholder={messages['app.docs.components.icon.search.placeholder']} | |||||
| style={{ margin: '0 10px', flex: 1 }} | |||||
| allowClear | |||||
| onChange={(e) => handleSearchIcon(e.currentTarget.value)} | |||||
| size="large" | |||||
| autoFocus | |||||
| suffix={<IconPicSearcher />} | |||||
| /> | |||||
| </div> | |||||
| {renderCategories} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default IconSelector; | |||||
| @@ -1,137 +0,0 @@ | |||||
| .iconPicSearcher { | |||||
| display: inline-block; | |||||
| margin: 0 8px; | |||||
| .icon-pic-btn { | |||||
| color: @text-color-secondary; | |||||
| cursor: pointer; | |||||
| transition: all 0.3s; | |||||
| &:hover { | |||||
| color: @input-icon-hover-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| .icon-pic-preview { | |||||
| width: 30px; | |||||
| height: 30px; | |||||
| margin-top: 10px; | |||||
| padding: 8px; | |||||
| text-align: center; | |||||
| border: 1px solid @border-color-base; | |||||
| border-radius: 4px; | |||||
| > img { | |||||
| max-width: 50px; | |||||
| max-height: 50px; | |||||
| } | |||||
| } | |||||
| .icon-pic-search-result { | |||||
| min-height: 50px; | |||||
| padding: 0 10px; | |||||
| > .result-tip { | |||||
| padding: 10px 0; | |||||
| color: @text-color-secondary; | |||||
| } | |||||
| > table { | |||||
| width: 100%; | |||||
| .col-icon { | |||||
| width: 80px; | |||||
| padding: 10px 0; | |||||
| > .anticon { | |||||
| font-size: 30px; | |||||
| :hover { | |||||
| color: @link-hover-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| ul.anticonsList { | |||||
| margin: 2px 0; | |||||
| overflow: hidden; | |||||
| direction: ltr; | |||||
| list-style: none; | |||||
| li { | |||||
| position: relative; | |||||
| float: left; | |||||
| width: 48px; | |||||
| height: 48px; | |||||
| margin: 3px 0; | |||||
| padding: 2px 0 0; | |||||
| overflow: hidden; | |||||
| color: #555; | |||||
| text-align: center; | |||||
| list-style: none; | |||||
| background-color: inherit; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; | |||||
| .rtl & { | |||||
| margin: 3px 0; | |||||
| padding: 2px 0 0; | |||||
| } | |||||
| .anticon { | |||||
| margin: 4px 0 2px; | |||||
| font-size: 24px; | |||||
| transition: transform 0.3s ease-in-out; | |||||
| will-change: transform; | |||||
| } | |||||
| .anticonClass { | |||||
| display: block; | |||||
| font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; | |||||
| white-space: nowrap; | |||||
| text-align: center; | |||||
| transform: scale(0.83); | |||||
| .ant-badge { | |||||
| transition: color 0.3s ease-in-out; | |||||
| } | |||||
| } | |||||
| &:hover { | |||||
| color: #fff; | |||||
| background-color: @primary-color; | |||||
| .anticon { | |||||
| transform: scale(1.4); | |||||
| } | |||||
| .ant-badge { | |||||
| color: #fff; | |||||
| } | |||||
| } | |||||
| &.TwoTone:hover { | |||||
| background-color: #8ecafe; | |||||
| } | |||||
| &.copied:hover { | |||||
| color: rgba(255, 255, 255, 0.2); | |||||
| } | |||||
| &.copied::after { | |||||
| top: -2px; | |||||
| opacity: 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| .copied-code { | |||||
| padding: 2px 4px; | |||||
| font-size: 12px; | |||||
| background: #f5f5f5; | |||||
| border-radius: 2px; | |||||
| } | |||||
| @@ -1,40 +0,0 @@ | |||||
| import * as React from 'react'; | |||||
| export const FilledIcon: React.FC = (props) => { | |||||
| const path = | |||||
| 'M864 64H160C107 64 64 107 64 160v' + | |||||
| '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' + | |||||
| '0c0-53-43-96-96-96z'; | |||||
| return ( | |||||
| <svg {...props} viewBox="0 0 1024 1024"> | |||||
| <path d={path} /> | |||||
| </svg> | |||||
| ); | |||||
| }; | |||||
| export const OutlinedIcon: React.FC = (props) => { | |||||
| const path = | |||||
| 'M864 64H160C107 64 64 107 64 160v7' + | |||||
| '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' + | |||||
| '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' + | |||||
| '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' + | |||||
| ' 12 12v680c0 6.6-5.4 12-12 12z'; | |||||
| return ( | |||||
| <svg {...props} viewBox="0 0 1024 1024"> | |||||
| <path d={path} /> | |||||
| </svg> | |||||
| ); | |||||
| }; | |||||
| export const TwoToneIcon: React.FC = (props) => { | |||||
| const path = | |||||
| 'M16 512c0 273.932 222.066 496 496 49' + | |||||
| '6s496-222.068 496-496S785.932 16 512 16 16 238.' + | |||||
| '066 16 512z m496 368V144c203.41 0 368 164.622 3' + | |||||
| '68 368 0 203.41-164.622 368-368 368z'; | |||||
| return ( | |||||
| <svg {...props} viewBox="0 0 1024 1024"> | |||||
| <path d={path} /> | |||||
| </svg> | |||||
| ); | |||||
| }; | |||||
| @@ -1,7 +0,0 @@ | |||||
| .kf-confirm-modal { | |||||
| &__content { | |||||
| width: 100%; | |||||
| font-size: 18px; | |||||
| text-align: center; | |||||
| } | |||||
| } | |||||
| @@ -1,37 +0,0 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-10-10 10:54:25 | |||||
| * @Description: 自定义 Confirm Modal | |||||
| */ | |||||
| import classNames from 'classnames'; | |||||
| import KFModal, { KFModalProps } from '../KFModal'; | |||||
| import './index.less'; | |||||
| export interface KFConfirmModalProps extends KFModalProps { | |||||
| content: string; | |||||
| } | |||||
| function KFConfirmModal({ | |||||
| title, | |||||
| image, | |||||
| className = '', | |||||
| centered, | |||||
| maskClosable, | |||||
| content, | |||||
| ...rest | |||||
| }: KFConfirmModalProps) { | |||||
| return ( | |||||
| <KFModal | |||||
| className={classNames(['kf-confirm-modal', className])} | |||||
| {...rest} | |||||
| centered={centered ?? true} | |||||
| maskClosable={maskClosable ?? false} | |||||
| title={title} | |||||
| image={image ?? require('@/assets/img/edit-experiment.png')} | |||||
| > | |||||
| <div className="kf-confirm-modal__content">{content}</div> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default KFConfirmModal; | |||||