'init_again'

Change-Id: Ib7ecdb9f5baeab1e4681152a57b936edf7475b35
diff --git a/src/components/IconSelector/IconPicSearcher.tsx b/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/src/components/IconSelector/IconPicSearcher.tsx
@@ -0,0 +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;