@ant-design/icons#ReloadOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#ReloadOutlined. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
export default function RefreshIcon(props: Props) {
  const { t } = useTranslation();
  const { onClick, className } = props;
  const [refreshing, setRefreshing] = useState<boolean>(false);

  const handleRefresh = (e) => {
    if (refreshing) return;
    setRefreshing(true);
    onClick();
    setTimeout(() => {
      setRefreshing(false);
    }, 1000);
  };

  return (
    <Button
      className={className ? className + ' reload-icon' : 'reload-icon'}
      loading={refreshing}
      onClick={handleRefresh}
      icon={<ReloadOutlined className='refresh' spin={refreshing} />}
    ></Button>
  );
}
Example #2
Source File: index.tsx    From memex with MIT License 6 votes vote down vote up
renderInboxOptions() {
    return (
      <div className="inbox-drawer-header">
        <div>
          <Checkbox />
        </div>
        <div>
          <Tooltip placement="left" title="Commit">
            <Button
              style={{ border: 'none' }}
              onClick={() => {
                this.confirmCommitDocs();
              }}
              shape="circle"
              icon={<FileAddOutlined style={{ fontSize: 14 }} />}
            />
          </Tooltip>
          <Tooltip placement="left" title="Refresh">
            <Button
              style={{ border: 'none' }}
              onClick={() => {
                this.getInbox();
              }}
              shape="circle"
              icon={<ReloadOutlined style={{ fontSize: 14 }} />}
            />
          </Tooltip>
        </div>
      </div>
    );
  }
Example #3
Source File: out.tsx    From web-pdm with Apache License 2.0 6 votes vote down vote up
IconRenders = {

    undo: <RollbackOutlined />,
    redo: <RollbackOutlined style={{ transform: 'scaleX(-1)' }} />,
    min: <ZoomOutOutlined />,
    max: <ZoomInOutlined />,
    full: <BorderOutlined />,
    miniMap: <PictureFilled />,
    miniMapNo: <PictureOutlined />,
    dagreLayout: <PartitionOutlined />,
    relationLayout: <UngroupOutlined />,
    reload: <ReloadOutlined />,
    image: <DownloadOutlined />,
    darkness: <SnippetsFilled />,
    light: <SnippetsOutlined />,
    colorClose: <BgColorsOutlined />,
    colorOpen: <BgColorsOutlined />
}
Example #4
Source File: ErrorNetwork.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function ErrorNetwork(): JSX.Element {
    return (
        <div>
            <h1 className="page-title">Network Error</h1>
            <p>There was an issue loading the requested resource.</p>
            <p>
                <Button onClick={() => window.location.reload()}>
                    <ReloadOutlined /> Reload the page!
                </Button>
            </p>
        </div>
    )
}
Example #5
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
Peers: React.FC = () => {
  const { t } = useTranslation();
  const [isLoading, setIsLoading] = React.useState<boolean>(false);
  const { peers, getPeers, count, isLoading: isPeersLoading } = usePeers();

  const refreshPeers = async () => {
    setIsLoading(true);
    await refreshActionDelay(getPeers);
    setIsLoading(false);
  };

  const opacity = isLoading ? 0.5 : 1;

  React.useEffect(() => {
    if (isEmpty(peers)) return;

    protocolVersions = {};
    Object.values(peers).forEach(({ protocol_version: protocolVersion }) => {
      if (!protocolVersions[protocolVersion]) {
        protocolVersions[protocolVersion] = 1;
      } else {
        protocolVersions[protocolVersion] += 1;
      }
    });

    protocolVersion = Object.keys(protocolVersions).reduce(function (a, b) {
      return protocolVersions[a] > protocolVersions[b] ? a : b;
    });

    percentProtocolVersion = new BigNumber(protocolVersions[protocolVersion])
      .times(100)
      .dividedBy(count)
      .toFormat(2);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [peers]);

  return (
    <Card
      size="small"
      title={t("pages.status.peers")}
      extra={
        <Tooltip title={t("pages.status.reload")}>
          <Button
            type="primary"
            icon={<ReloadOutlined />}
            size="small"
            onClick={refreshPeers}
            loading={isLoading}
          />
        </Tooltip>
      }
    >
      <LoadingStatistic
        title={t("pages.status.connectedPeers")}
        value={count}
        isLoading={isPeersLoading}
        style={{ opacity }}
      />
      <LoadingStatistic
        title={t("pages.status.protocolVersion", {
          protocolVersion,
        })}
        value={percentProtocolVersion}
        suffix="%"
        isLoading={isPeersLoading}
        style={{ opacity }}
      />
    </Card>
  );
}
Example #6
Source File: HTTPFuzzerHistory.tsx    From yakit with GNU Affero General Public License v3.0 5 votes vote down vote up
HTTPFuzzerHistorySelector: React.FC<HTTPFuzzerHistorySelectorProp> = React.memo((props) => {
    const [tasks, setTasks] = useState<HTTPFuzzerTask[]>([]);
    const [loading, setLoading] = useState(false);

    const deleteAll = useMemoizedFn(()=>{
        setLoading(true)
        ipcRenderer.invoke("DeleteHistoryHTTPFuzzerTask", {}).then(()=>{
            info("Delete History")
            reload()
        }).finally(()=>setTimeout(()=>setLoading(false), 300))
    })

    const reload = useMemoizedFn(() => {
        setLoading(true)
        ipcRenderer.invoke("QueryHistoryHTTPFuzzerTask", {}).then((data: { Tasks: HTTPFuzzerTask[] }) => {
            setTasks(data.Tasks)
        }).finally(() => setTimeout(() => setLoading(false), 300))
    })
    useEffect(() => {
        reload()
    }, [])

    return <Card size={"small"} bordered={false} title={<Space>
        Web Fuzzer History
        <Button type={"link"} size={"small"} icon={<ReloadOutlined/>} onClick={e => {
            reload()
        }}/>
        <Popconfirm title={"确定删除吗?"} onConfirm={()=>{
            deleteAll()
        }}>
            <Button type={"link"} size={"small"} danger={true}
                    icon={<DeleteOutlined />}
            />
        </Popconfirm>
    </Space>}>
        <List<HTTPFuzzerTask>
            loading={loading}
            dataSource={tasks} pagination={{total: tasks.length, size: "small", pageSize: 10}}
            renderItem={(i: HTTPFuzzerTask) => {
                return <Card size={"small"} style={{marginBottom: 8}} hoverable={true} onClick={e => {
                    e.preventDefault()

                    props.onSelect(i.Id)
                }}>
                    <Space>
                        <div>
                            {`ID:${i.Id} ${formatTimestamp(i.CreatedAt)}`}
                        </div>
                        <Tag>共{i.HTTPFlowTotal}个</Tag>
                        {i.HTTPFlowSuccessCount != i.HTTPFlowTotal && <Tag>成功:{i.HTTPFlowSuccessCount}个</Tag>}
                    </Space>
                </Card>
            }}
        />
    </Card>
})
Example #7
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
BlockCount: React.FC = () => {
  const { t } = useTranslation();
  const [isLoading, setIsLoading] = React.useState(false);
  const [isInitialLoading, setIsInitialLoading] = React.useState(true);
  const {
    count,
    unchecked,
    cemented,
    getBlockCount,
    isLoading: isBlockCountLoading,
  } = React.useContext(BlockCountContext);

  const refreshBlockCount = async () => {
    setIsLoading(true);
    await refreshActionDelay(getBlockCount);
    setIsLoading(false);
  };

  React.useEffect(() => {
    let interval: number = window.setInterval(() => {
      try {
        getBlockCount();
      } catch (_e) {
        clearInterval(interval);
      }
    }, POLL_INTERVAL);

    return () => clearInterval(interval);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  React.useEffect(() => {
    if (!isBlockCountLoading) {
      setIsInitialLoading(false);
    }
  }, [isBlockCountLoading]);

  const opacity = isLoading ? 0.5 : 1;

  return (
    <Card
      size="small"
      title={t("common.blocks")}
      extra={
        <Tooltip title={t("pages.status.reload")}>
          <Button
            type="primary"
            icon={<ReloadOutlined />}
            size="small"
            onClick={refreshBlockCount}
            loading={isLoading}
          />
        </Tooltip>
      }
    >
      <Skeleton active loading={!count}>
        <LoadingStatistic
          title={t("pages.status.count")}
          value={count}
          isLoading={isInitialLoading}
          style={{ opacity }}
        />
        <LoadingStatistic
          isLoading={isInitialLoading}
          title={t("pages.status.unchecked")}
          tooltip={t("tooltips.unchecked")}
          value={unchecked}
          style={{ opacity }}
        />
        <LoadingStatistic
          title={t("pages.status.cemented")}
          tooltip={t("tooltips.cemented")}
          value={cemented}
          isLoading={isInitialLoading}
          style={{ opacity }}
        />
      </Skeleton>
    </Card>
  );
}
Example #8
Source File: Background.tsx    From datart with Apache License 2.0 5 votes vote down vote up
export function Background() {
  const [formVisible, setFormVisible] = useState(false);
  const userSettings = useSelector(selectUserSettings);
  const organizations = useSelector(selectOrganizations);
  const userSettingLoading = useSelector(selectUserSettingLoading);
  const error = useSelector(selectInitializationError);
  const t = useI18NPrefix('main.background');

  const showForm = useCallback(() => {
    setFormVisible(true);
  }, []);

  const hideForm = useCallback(() => {
    setFormVisible(false);
  }, []);

  let visible = true;
  let content;

  if (userSettingLoading) {
    content = (
      <Hint>
        <SettingOutlined className="img loading" />
        <p>{t('loading')}</p>
      </Hint>
    );
  } else if (error) {
    content = (
      <Hint>
        <ReloadOutlined className="img" />
        <p>{t('initError')}</p>
      </Hint>
    );
  } else if (
    !userSettingLoading &&
    !(userSettings && userSettings.length) &&
    !organizations.length
  ) {
    content = (
      <>
        <Hint className="add" onClick={showForm}>
          <AppstoreAddOutlined className="img" />
          <p>{t('createOrg')}</p>
        </Hint>
        <OrganizationForm visible={formVisible} onCancel={hideForm} />
      </>
    );
  } else {
    visible = false;
  }

  return <Container visible={visible}>{content}</Container>;
}
Example #9
Source File: ErrorCard.tsx    From jitsu with MIT License 5 votes vote down vote up
ErrorCard: FC<ErrorCardProps> = ({
  title,
  icon,
  error,
  description,
  descriptionWithContacts,
  stackTrace,
  className,
  onReload,
}) => {
  if (description === undefined && error !== undefined) {
    description = error.message
  }
  if (stackTrace === undefined && error !== undefined) {
    stackTrace = error.stack
  }
  return (
    <Card bordered={false} className={cn(className, "max-h-full")}>
      <Card.Meta
        avatar={icon || <ExclamationCircleOutlined className={styles.icon} />}
        title={title || "An Error Occured"}
        description={
          <>
            <Fragment key="description">
              {description !== undefined ? (
                description
              ) : (
                <span>
                  {descriptionWithContacts !== undefined ? (
                    <>
                      {descriptionWithContacts}
                      {descriptionWithContacts && <br />}
                    </>
                  ) : (
                    <>
                      {"The application component crashed because of an internal error."}
                      <br />
                    </>
                  )}
                  {"Please, try to reload the page first and if the problem is still present contact us at"}{" "}
                  <Typography.Paragraph copyable={{ tooltips: false }} className="inline">
                    {"[email protected]"}
                  </Typography.Paragraph>{" "}
                  {"and our engineers will fix the problem asap."}
                </span>
              )}
            </Fragment>
            {stackTrace && (
              <Collapse key="stack-trace" bordered={false} className={`mt-2 ${styles.stackTraceCard}`}>
                <Collapse.Panel key={1} header="Error Stack Trace">
                  <div className="overflow-y-auto">
                    <Typography.Paragraph
                      copyable={{
                        text: stackTrace,
                        icon: [<CopyOutlined />, <CheckOutlined />],
                      }}
                      className={`flex flex-row ${styles.errorStackContainer}`}
                    >
                      <pre className="text-xs">{stackTrace}</pre>
                    </Typography.Paragraph>
                  </div>
                </Collapse.Panel>
              </Collapse>
            )}
            {onReload && (
              <div key="reload-button" className="flex justify-center items-center mt-2">
                <Button type="default" onClick={onReload} icon={<ReloadOutlined />}>{`Reload`}</Button>
              </div>
            )}
          </>
        }
      />
    </Card>
  )
}
Example #10
Source File: index.tsx    From memex with MIT License 5 votes vote down vote up
render() {
    const { handleClose, layout, selectedDoc } = this.props;
    const { data, loading } = this.state;
    const { visible, width, title, closeMask = false } = layout.drawer;

    return (
      <div>
        <Drawer
          width={width}
          title={title}
          style={{ left: 48 }}
          placement="left"
          closable
          mask={!closeMask}
          onClose={() => {
            handleClose();
            this.resetSelectedDoc();
          }}
          visible={visible}
        >
          {loading ? (
            <div className="loading-screen">
              <Spin />
            </div>
          ) : null}
          <div className="inbox-drawer-header">
            <div>
              <Checkbox />
            </div>
            <div>
              <Tooltip placement="left" title="Commit">
                <Button
                  style={{ border: 'none' }}
                  onClick={() => {
                    this.confirmCommitDocs();
                  }}
                  shape="circle"
                  icon={<FileAddOutlined style={{ fontSize: 14 }} />}
                />
              </Tooltip>
              <Tooltip placement="left" title="Refresh">
                <Button
                  style={{ border: 'none' }}
                  onClick={() => {
                    this.getInbox();
                  }}
                  shape="circle"
                  icon={<ReloadOutlined style={{ fontSize: 14 }} />}
                />
              </Tooltip>
            </div>
          </div>
          <div style={{ padding: '0px 16px' }}>
            <Tabs defaultActiveKey="1">
              <TabPane tab="Uncommitted" key="uncommitted">
                {data.map((item: any) => {
                  if (!item.doc.committed) {
                    return this.renderInboxItem(item);
                  }
                })}
              </TabPane>
              <TabPane tab="Committed" key="committed">
                {data.map((item: any) => {
                  if (item.doc.committed) {
                    return this.renderInboxItem(item);
                  }
                })}
              </TabPane>
            </Tabs>
          </div>
        </Drawer>
      </div>
    );
  }
Example #11
Source File: InternalMetricsTab.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function InternalMetricsTab(): JSX.Element {
    const { openSections, systemStatus, queries, queriesLoading, showAnalyzeQueryButton } = useValues(systemStatusLogic)
    const { setOpenSections, loadQueries } = useActions(systemStatusLogic)

    const [showIdle, setShowIdle] = useState(false)
    const postgresQueries = useMemo(
        () => queries?.postgres_running?.filter(({ state }) => showIdle || state !== 'idle'),
        [showIdle, queries]
    )

    const dashboard = systemStatus?.internal_metrics.clickhouse

    const reloadQueries = (e: React.MouseEvent): void => {
        e.stopPropagation()
        loadQueries()
    }

    return (
        <Card>
            <Collapse activeKey={openSections} onChange={(keys) => setOpenSections(keys as string[])}>
                {dashboard ? (
                    <Collapse.Panel header="Dashboards" key="0">
                        <Dashboard id={dashboard.id.toString()} shareToken={dashboard.share_token} internal />
                    </Collapse.Panel>
                ) : null}
                <Collapse.Panel header="PostgreSQL - currently running queries" key="1">
                    <div className="mb float-right">
                        <Checkbox
                            checked={showIdle}
                            onChange={(e) => {
                                setShowIdle(e.target.checked)
                            }}
                        >
                            Show idle queries
                        </Checkbox>
                        <Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
                            <ReloadOutlined /> Reload Queries
                        </Button>
                    </div>
                    <QueryTable queries={postgresQueries} loading={queriesLoading} />
                </Collapse.Panel>
                {queries?.clickhouse_running != undefined ? (
                    <Collapse.Panel header="Clickhouse - currently running queries" key="2">
                        <div className="mb float-right">
                            <Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
                                <ReloadOutlined /> Reload Queries
                            </Button>
                        </div>
                        <QueryTable
                            queries={queries?.clickhouse_running}
                            loading={queriesLoading}
                            showAnalyze={showAnalyzeQueryButton}
                        />
                    </Collapse.Panel>
                ) : null}
                {queries?.clickhouse_slow_log != undefined ? (
                    <Collapse.Panel header="Clickhouse - slow query log (past 6 hours)" key="3">
                        <div className="mb float-right">
                            <Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
                                <ReloadOutlined /> Reload Queries
                            </Button>
                        </div>
                        <QueryTable
                            queries={queries?.clickhouse_slow_log}
                            loading={queriesLoading}
                            showAnalyze={showAnalyzeQueryButton}
                        />
                    </Collapse.Panel>
                ) : null}
            </Collapse>

            <AnalyzeQueryModal />
        </Card>
    )
}
Example #12
Source File: DomainAssetPage.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
DomainAssetPage: React.FC<DomainAssetPageProps> = (props: DomainAssetPageProps) => {
    const [params, setParams] = useState<QueryDomainsRequest>({
        Pagination: genDefaultPagination(20),
    });
    const [response, setResponse] = useState<QueryGeneralResponse<Domain>>({
        Data: [],
        Pagination: genDefaultPagination(20),
        Total: 0
    });
    const [loading, setLoading] = useState(false);
    const {Data, Total, Pagination} = response;
    const [outputDomainKeyword, setOutputDomainKeyword] = useState("*");

    const [checkedURL, setCheckedURL] = useState<string[]>([])
    const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([])

    const update = useMemoizedFn((page?: number, limit?: number,) => {
        const newParams = {
            ...params,
        }
        if (page) newParams.Pagination.Page = page;
        if (limit) newParams.Pagination.Limit = limit;

        setLoading(true)
        ipcRenderer.invoke("QueryDomains", newParams).then(data => {
            setResponse(data)
        }).catch((e: any) => {
            failed("QueryExecHistory failed: " + `${e}`)
        }).finally(() => {
            setTimeout(() => setLoading(false), 200)
        })
    })

    const delDomain = useMemoizedFn((host?: string) => {
        const params = !!host ? {DomainKeyword: host} : {DeleteAll: true}

        setLoading(true)
        ipcRenderer
            .invoke("DeleteDomains", params)
            .then(() => {
                update(1)
            })
            .catch((e) => {
                failed(`DelDomain failed: ${e}`)
            })
            .finally(() => setTimeout(() => setLoading(false), 300))
    })

    useEffect(() => {
        update(1, 20)
    }, [])

    return <Table<Domain>
        loading={loading}
        pagination={{
            size: "small", current: +Pagination.Page,
            pageSize: Pagination?.Limit || 10, showSizeChanger: true,
            total: Total, showTotal: (i) => <Tag>共{i}条历史记录</Tag>,
            onChange: (page: number, limit?: number) => {
                update(page, limit)
            }
        }}
        title={e => {
            return <div style={{display: "flex", justifyContent: "space-between"}}>
                <Space>
                    <div>域名资产</div>
                    <Button
                        type={"link"} onClick={() => {
                            update(1)
                            setSelectedRowKeys([])
                            setCheckedURL([])
                        }}
                        size={"small"} icon={<ReloadOutlined/>}
                    />
                </Space>
                <Space>
                    <Popover title={"输入想要导出的域名关键字"}
                             trigger={["click"]}
                             content={<div>
                                 <Form layout={"inline"} size={"small"} onSubmitCapture={e => {
                                     e.preventDefault()

                                     startExecYakCode("Output Domains", {
                                         Script: OutputAsset.outputDomainByKeyword, Params: [
                                             {Key: "keyword", Value: outputDomainKeyword}
                                         ]
                                     })
                                 }}>
                                     <InputItem
                                         label={"域名关键字"} value={outputDomainKeyword}
                                         setValue={setOutputDomainKeyword}
                                     />
                                     <Form.Item colon={false} label={" "}>
                                         <Button size={"small"} type="primary" htmlType="submit"> 导出 </Button>
                                     </Form.Item>
                                 </Form>
                             </div>}
                    >
                        <Button
                            type={"primary"}
                            size={"small"}
                        >导出域名</Button>
                    </Popover>
                    <Popconfirm title="确定删除所有域名资产吗? 不可恢复" onConfirm={e => {
                        delDomain()
                        setSelectedRowKeys([])
                        setCheckedURL([])
                    }}>
                        <Button
                            type="link"
                            danger
                            size="small"
                        >删除全部</Button>
                    </Popconfirm>
                    <DropdownMenu 
                            menu={{data: [
                                {key:'bug-test',title:"发送到漏洞检测"},
                                {key:'scan-port',title:"发送到端口扫描"},
                                {key:'brute',title:"发送到爆破"}
                            ]}}
                            dropdown={{placement: "bottomRight"}}
                            onClick={(key) => {
                                if(checkedURL.length === 0){
                                    failed("请最少选择一个选项再进行操作")
                                    return
                                }

                                ipcRenderer.invoke("send-to-tab", {
                                    type: key,
                                    data:{URL: JSON.stringify(checkedURL)}
                                })
                            }}
                        >
                            <Button type="link" icon={<LineMenunIcon />}></Button>
                        </DropdownMenu>
                </Space>
            </div>
        }}
        size={"small"} bordered={true}
        dataSource={Data}
        rowKey={e => `${e.ID}`}
        rowSelection={{
            onChange: (selectedRowKeys, selectedRows) => {
                setSelectedRowKeys(selectedRowKeys as string[])
                setCheckedURL(selectedRows.map(item => item.DomainName))
            },
            selectedRowKeys
        }}
        columns={[
            {
                title: "域名",
                render: (i: Domain) => <Text style={{maxWidth: 470}} ellipsis={{tooltip: true}}>{i.DomainName}</Text>,
                filterIcon: (filtered) => {
                    return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
                },
                filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
                    return (
                        params &&
                        setParams && (
                            <TableFilterDropdownString
                                label={"搜索关键字"}
                                params={params}
                                setParams={setParams}
                                filterName={"DomainKeyword"}
                                confirm={confirm}
                                setSelectedKeys={setSelectedKeys}
                                update={update}
                            />
                        )
                    )
                }
            },
            {
                title: "IP",
                dataIndex: "IPAddr",
                width: 160,
                filterIcon: (filtered) => {
                    return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
                },
                filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
                    return (
                        params &&
                        setParams && (
                            <TableFilterDropdownString
                                label={"搜索IP"}
                                params={params}
                                setParams={setParams}
                                filterName={"Network"}
                                confirm={confirm}
                                setSelectedKeys={setSelectedKeys}
                                update={update}
                            />
                        )
                    )
                }
            },
            {
                title: "HTMLTitle",
                dataIndex: "HTTPTitle",
                filterIcon: (filtered) => {
                    return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
                },
                filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
                    return (
                        params &&
                        setParams && (
                            <TableFilterDropdownString
                                label={"搜索关键字"}
                                params={params}
                                setParams={setParams}
                                filterName={"Title"}
                                confirm={confirm}
                                setSelectedKeys={setSelectedKeys}
                                update={update}
                            />
                        )
                    )
                }
            },
            {
                title: "操作",
                render: (i: Domain) => (
                    <Space>
                        <Button
                            size="small"
                            type={"link"}
                            danger
                            onClick={() => {
                                delDomain(i.DomainName)
                                setSelectedRowKeys([])
                                setCheckedURL([])
                            }}
                        >
                            删除
                        </Button>
                    </Space>
                )
            }
        ]}
    >

    </Table>
}
Example #13
Source File: Recycle.tsx    From datart with Apache License 2.0 4 votes vote down vote up
Recycle = memo(
  ({ type, orgId, selectedId, list, listLoading, onInit }: RecycleProps) => {
    const dispatch = useDispatch();
    const history = useHistory();
    const { showSaveForm } = useContext(SaveFormContext);
    const vizs = useSelector(selectVizs);
    const isOwner = useSelector(selectIsOrgOwner);
    const permissionMap = useSelector(selectPermissionMap);
    const tg = useI18NPrefix('global');

    useEffect(() => {
      onInit();
    }, [onInit]);

    const redirect = useCallback(
      vizId => {
        if (vizId) {
          history.push(`/organizations/${orgId}/vizs/${vizId}`);
        } else {
          history.push(`/organizations/${orgId}/vizs`);
        }
      },
      [history, orgId],
    );

    const del = useCallback(
      (id, type) => e => {
        e.stopPropagation();
        dispatch(
          deleteViz({
            params: { id, archive: false },
            type,
            resolve: () => {
              message.success(tg('operation.deleteSuccess'));
              dispatch(removeTab({ id, resolve: redirect }));
            },
          }),
        );
      },
      [dispatch, redirect, tg],
    );

    const moreMenuClick = useCallback(
      (id, name, vizType) =>
        ({ key, domEvent }) => {
          domEvent.stopPropagation();
          switch (key) {
            case 'reset':
              showSaveForm({
                vizType,
                type: CommonFormTypes.Edit,
                visible: true,
                initialValues: { id, name, parentId: void 0 },
                onSave: (values, onClose) => {
                  let index = getInsertedNodeIndex(values, vizs);

                  dispatch(
                    unarchiveViz({
                      params: {
                        id,
                        vizType,
                        ...values,
                        parentId: values.parentId || null,
                        index,
                      },
                      resolve: () => {
                        message.success(tg('operation.restoreSuccess'));
                        dispatch(removeTab({ id, resolve: redirect }));
                        onClose();
                      },
                    }),
                  );
                },
              });
              break;
            default:
              break;
          }
        },
      [dispatch, showSaveForm, redirect, vizs, tg],
    );

    const toDetail = useCallback(
      id => () => {
        history.push(`/organizations/${orgId}/vizs/${id}`);
      },
      [history, orgId],
    );

    return (
      <Wrapper>
        <List
          dataSource={list}
          loading={listLoading && { indicator: <LoadingOutlined /> }}
          renderItem={({ id, name, vizType, loading }) => {
            let allowManage = false;
            if (type === 'viz') {
              const viz = vizs.find(v => v.id === id);
              const path = viz
                ? getPath(
                    vizs as Array<{ id: string; parentId: string }>,
                    { id, parentId: viz.parentId },
                    VizResourceSubTypes.Folder,
                  )
                : [id];
              allowManage = getCascadeAccess(
                isOwner,
                permissionMap,
                ResourceTypes.View,
                path,
                PermissionLevels.Manage,
              );
            } else {
              allowManage = !!calcAc(
                isOwner,
                permissionMap,
                ResourceTypes.Viz,
                PermissionLevels.Manage,
                id,
              );
            }
            return (
              <ListItem
                selected={selectedId === id}
                className={classnames({
                  recycle: true,
                  disabled: loading,
                })}
                onClick={toDetail(id)}
                actions={[
                  allowManage && (
                    <Popup
                      trigger={['click']}
                      placement="bottomRight"
                      content={
                        <Menu
                          prefixCls="ant-dropdown-menu"
                          selectable={false}
                          onClick={moreMenuClick(id, name, vizType)}
                        >
                          <MenuListItem
                            key="reset"
                            prefix={<ReloadOutlined className="icon" />}
                          >
                            {tg('button.restore')}
                          </MenuListItem>
                          <MenuListItem
                            key="delelte"
                            prefix={<DeleteOutlined className="icon" />}
                          >
                            <Popconfirm
                              title={tg('operation.deleteConfirm')}
                              onConfirm={del(id, vizType)}
                            >
                              {tg('button.delete')}
                            </Popconfirm>
                          </MenuListItem>
                        </Menu>
                      }
                    >
                      <Button
                        type="link"
                        icon={<MoreOutlined />}
                        className="btn-hover"
                        onClick={stopPPG}
                      />
                    </Popup>
                  ),
                ]}
              >
                <List.Item.Meta title={name} />
              </ListItem>
            );
          }}
        />
      </Wrapper>
    );
  },
)
Example #14
Source File: YakScriptManager.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
YakScriptManagerPage: React.FC<YakScriptManagerPageProp> = (props) => {
    const [response, setResponse] = useState<QueryYakScriptsResponse>({
        Data: [], Pagination: {
            Limit: props.limit || 15, Page: 1,
            Order: "desc", OrderBy: "updated_at"
        },
        Total: 0
    });
    const [selectedScript, setSelectedScript] = useState<YakScript>();
    const {Data, Pagination, Total} = response;
    const [params, setParams] = useState<QueryYakScriptRequest>({
        Pagination: {
            Limit: props.limit || 15, Page: 1,
            Order: "desc", OrderBy: "updated_at"
        }, Type: props.type || undefined,
        Keyword: props.keyword || "", IsHistory: false
    });
    const [loading, setLoading] = useState(false);

    const isMainPage = !props.onLoadYakScript

    const update = (page?: number, limit?: number) => {
        const newParams = {
            ...params
        }
        if (page) newParams.Pagination.Page = page;
        if (limit) newParams.Pagination.Limit = limit;
        setLoading(true)

        ipcRenderer.invoke("QueryYakScript", newParams).then((data: QueryYakScriptsResponse) => {
            setResponse(data)
        }).finally(() => setTimeout(() => setLoading(false), 300))
    };

    useEffect(() => {
        update(1)
    }, [params.Type])

    const renderTable = () => {
        return <Space direction={"vertical"} style={{width: "100%"}}>
            {!props.onlyViewer && <Form onSubmitCapture={e => {
                e.preventDefault()
                update(1)
            }} layout={"inline"}>
                <InputItem
                    label={"搜索关键字"}
                    setValue={Keyword => setParams({...params, Keyword})}
                    value={params.Keyword}
                />
                <Form.Item colon={false}>
                    <Button.Group>
                        <Button type="primary" htmlType="submit">搜索</Button>
                        <Button onClick={e => {
                            if (!params.Keyword) {
                                Modal.error({title: "关键字为空无法生成批量扫描能力"});
                                return
                            }
                            showDrawer({
                                title: "", width: "93%", mask: false, keyboard: false,
                                content: <>
                                    <YakBatchExecutorLegacy
                                        keyword={params.Keyword || ""}
                                        verbose={`自定义搜索关键字: ${params.Keyword}`}
                                    />
                                </>,
                            })
                        }}>批量</Button>
                    </Button.Group>
                </Form.Item>
            </Form>}
            <Table<YakScript>
                size={"small"}
                dataSource={Data}
                rowKey={"Id"}
                loading={loading} bordered={true}
                scroll={{y: 750}}
                expandable={{
                    expandedRowRender: (i: YakScript) => {
                        return <div style={{height: 400}}>
                            <YakEditor
                                type={"yak"} readOnly={true} value={i.Content}
                            />
                        </div>
                    },
                }}
                onRow={isMainPage ? r => {
                    return {
                        onClick: () => {
                            setSelectedScript(r)
                        }
                    }
                } : undefined}
                pagination={{
                    size: "small",
                    pageSize: Pagination?.Limit || 10,
                    total: Total,
                    showTotal: (i) => <Tag>共{i}条历史记录</Tag>,
                    // onChange(page: number, limit?: number): any {
                    //     update(page, limit)
                    // },
                }}
                onChange={(p) => {
                    update(p.current, p.pageSize)
                }}
                columns={isMainPage ? [
                    {
                        title: "模块名称", width: 300,
                        render: (i: YakScript) => <Tag><Text
                            style={{maxWidth: 260}} copyable={true}
                            ellipsis={{tooltip: true}}>
                            {i.ScriptName}
                        </Text></Tag>
                    },
                    // {
                    //     title: "描述", render: (i: YakScript) => <Text
                    //         style={{maxWidth: 300}}
                    //         ellipsis={{tooltip: true}}
                    //     >{i.Help}</Text>, width: 330,
                    // },
                    {
                        title: "操作", fixed: "right", width: 135, render: (i: YakScript) => <Space>
                            <Button size={"small"} onClick={e => {
                                let m = showDrawer({
                                    title: "修改当前 Yak 模块", width: "90%", keyboard: false,
                                    content: <>
                                        <YakScriptCreatorForm
                                            modified={i} onChanged={i => update()}
                                            onCreated={(created) => {
                                                m.destroy()
                                            }}
                                        />
                                    </>
                                })
                            }}>修改</Button>
                            <Popconfirm
                                title={"确认想要删除该模块?"}
                                onConfirm={e => {
                                    ipcRenderer.invoke("delete-yak-script", i.Id)
                                    setLoading(true)
                                    setTimeout(() => update(1), 1000)
                                }}
                            >
                                <Button size={"small"} danger={true}>删除</Button>
                            </Popconfirm>
                        </Space>
                    },
                ] : [
                    {
                        title: "模块名称", fixed: "left",
                        render: (i: YakScript) => <Tag><Text style={{maxWidth: 200}} copyable={true}
                                                             ellipsis={{tooltip: true}}>
                            {i.ScriptName}
                        </Text></Tag>
                    },
                    {
                        title: "描述", render: (i: YakScript) => <Text
                            style={{maxWidth: 200}}
                            ellipsis={{tooltip: true}}
                        >{i.Help}</Text>
                    },
                    {
                        title: "操作", fixed: "right", render: (i: YakScript) => <Space>
                            {props.onLoadYakScript && <Button size={"small"} onClick={e => {
                                props.onLoadYakScript && props.onLoadYakScript(i)
                            }} type={"primary"}>加载</Button>}
                        </Space>
                    },
                ]}
            />
        </Space>
    }

    return <div>
        {!props.onlyViewer && <PageHeader
            title={"Yak 模块管理器"}
            subTitle={<Space>
                <Button
                    icon={<ReloadOutlined/>}
                    type={"link"}
                    onClick={() => {
                        update()
                    }}
                />
                {props.type ? undefined : <Form layout={"inline"}>
                    <ManySelectOne
                        formItemStyle={{marginBottom: 0, width: 200}}
                        label={"模块类型"}
                        data={[
                            {value: "yak", text: "Yak 原生模块"},
                            {value: "nuclei", text: "nuclei Yaml模块"},
                            {value: undefined, text: "全部"},
                        ]}
                        setValue={Type => setParams({...params, Type})} value={params.Type}
                    />
                </Form>}
                <div>
                    你可以在这里管理 / 添加你的 Yak 模块
                </div>
            </Space>}
            extra={[
                isMainPage ? <Popconfirm
                    title={<>
                        确定要加载本地 yaml(nuclei) poc 吗?<br/>
                        可通过 <Text mark={true} copyable={true}>yak update-nuclei-poc</Text> 一键更新已知 PoC
                    </>}
                    onConfirm={() => {
                        ipcRenderer.invoke("update-nuclei-poc")
                    }}
                >
                    <Button>加载 PoC(nuclei)</Button>
                </Popconfirm> : undefined,
                <Button type={"primary"} onClick={e => {
                    let m = showDrawer({
                        title: "创建新的 Yakit 模块",
                        keyboard: false,
                        width: "95%",
                        content: <>
                            <YakScriptCreatorForm onCreated={() => {
                                m.destroy()
                            }} onChanged={e => update(1)}/>
                        </>
                    })
                }}>创建新脚本</Button>
            ]}
        />}
        {(isMainPage && !props.onlyViewer) ? <Row gutter={12}>
            <Col span={8}>
                {renderTable()}
            </Col>
            <Col span={16}>
                {selectedScript ? <YakScriptOperator script={selectedScript}/> : <Empty/>}
            </Col>
        </Row> : <Row>
            <Col span={24}>
                {renderTable()}
            </Col>
        </Row>}
    </div>
}
Example #15
Source File: App.tsx    From pcap2socks-gui with MIT License 4 votes vote down vote up
render() {
    return (
      <Layout className="layout">
        <Content className="content-wrapper">
          <div className="content">
            {(() => {
              switch (this.state.stage) {
                case STAGE_WELCOME:
                  return this.renderWelcome();
                case STAGE_INTERFACE:
                  return this.renderInterface();
                case STAGE_DEVICE:
                  return this.renderDevice();
                case STAGE_PROXY:
                  return this.renderProxy();
                case STAGE_RUNNING:
                  return this.renderRunning();
                default:
                  return;
              }
            })()}
          </div>
          <div className="footer">
            {(() => {
              if (this.state.stage > STAGE_WELCOME && this.state.stage <= STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<LeftOutlined />}
                    onClick={() => this.setState({ stage: this.state.stage - 1 })}
                  >
                    上一步
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_INTERFACE) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0 && this.state.loading !== 1}
                    icon={<ReloadOutlined />}
                    onClick={this.updateInterfaces}
                  >
                    刷新网卡列表
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<FolderOpenOutlined />}
                    onClick={() => {
                      const node = document.getElementById("open");
                      if (node) {
                        node.click();
                      }
                    }}
                  >
                    导入代理配置
                    <input id="open" type="file" onChange={this.import} style={{ display: "none" }} />
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button className="button" icon={<ExportOutlined />} onClick={this.export}>
                    导出代理配置
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0 && this.state.loading !== 2}
                    loading={this.state.loading === 2}
                    icon={<ExperimentOutlined />}
                    onClick={this.test}
                  >
                    测试代理服务器
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_WELCOME && this.state.ready) {
                return (
                  <Tooltip title={this.state.destination}>
                    <Button
                      className="button"
                      type="primary"
                      disabled={this.state.loading > 0 && this.state.loading !== 3}
                      loading={this.state.loading === 3}
                      icon={<PlayCircleOutlined />}
                      onClick={this.run}
                    >
                      以上次的配置运行
                    </Button>
                  </Tooltip>
                );
              }
            })()}
            {(() => {
              if (this.state.stage >= STAGE_WELCOME && this.state.stage < STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<RightOutlined />}
                    type="primary"
                    onClick={() => this.setState({ stage: this.state.stage + 1 })}
                  >
                    下一步
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    type="primary"
                    disabled={this.state.loading > 0 && this.state.loading !== 3}
                    loading={this.state.loading === 3}
                    icon={<PoweroffOutlined />}
                    onClick={this.run}
                  >
                    运行
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_RUNNING) {
                return (
                  <Button className="button" icon={<GlobalOutlined />} onClick={this.notifyNetwork}>
                    显示网络设置
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_RUNNING) {
                return (
                  <Button
                    className="button"
                    type="primary"
                    danger
                    disabled={this.state.loading > 0 && this.state.loading !== 4}
                    loading={this.state.loading === 4}
                    icon={<PoweroffOutlined />}
                    onClick={this.stopConfirm}
                  >
                    停止
                  </Button>
                );
              }
            })()}
          </div>
        </Content>
      </Layout>
    );
  }
Example #16
Source File: BasicTable.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
// 泛型函数组件 https://juejin.im/post/5cd7f2c4e51d453a7d63b715#heading-7
function BasicTable<T>(props: IBasicTableProps<T>) {
  const prefixCls = `${props.prefixCls || 'dantd'}-table`;
  const filterType = props.filterType || 'half';
  const tmpDataSource = props.dataSource || ([] as T[]);
  const t = intlZhMap;
  const [dataSource, setDataSource] = useState(tmpDataSource);
  const [queryFormValues, setQueryFormValues] = useState<any>({});
  const [isQueryInit, setQueryInit] = useState(false);
  const {
    showFilter = true,
    showSearch = true,
    showReloadBtn = true,
    showQueryOptionBtns = true,
    showQueryCollapseButton = true,
    queryFormProps = {},
    isQuerySearchOnChange = true,
    loading = false,
    showBodyBg = false,
    queryFormColumns,
    queryMode = 'default',
    searchPos = 'full',
    reloadBtnPos = 'right',
    reloadBtnType = 'icon',
    antProps = {
      rowKey: 'id',
    },
  } = props;
  // 整理搜索区的展示状态
  // 是否展示长条搜索区
  const showFullSearch = showSearch && searchPos === 'full';
  // 搜索按钮展示的位置
  const showReloadBtn2SearchRight =
    searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
  const showReloadBtn2FilterRight =
    (!showSearch || searchPos !== 'full') &&
    showReloadBtn &&
    reloadBtnPos === 'right';
  const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';

  const searchPlaceholder =
    props.searchPlaceholder || t('table.search.placeholder');
  const tableClassName = classNames(prefixCls, props.className);
  const tableContentClassName = classNames({
    [`${prefixCls}-table-content`]: true,
    [`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
  });
  const tableBodyCls = classNames({
    [`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
    [`${prefixCls}-body-compact`]: queryMode === 'compact',
  });

  const tableQueryCls = classNames({
    [`${prefixCls}-query`]: !!queryFormColumns,
    [`${prefixCls}-query-compact`]: queryMode === 'compact',
  });
  const filterSearchInputRef = useRef({}) as any;
  const clearFiltersRef = useRef({}) as any;
  const [searchQuery, setSearchQuery] = useState('');
  const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
  const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
  const rowSelection = props.rowSelection;

  const selectRowNum = rowSelection
    ? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
    : -1;
  const sorterNames = {
    ascend: t('table.sort.ascend'),
    descend: t('table.sort.descend'),
  };

  const showTotal = (total: number) => {
    return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
  };
  // 生成列的 dataIndex 数组
  const columnsDataIndexArr =
    props.columns.map((columnItem) => {
      let dataIndex = columnItem.dataIndex;
      if (typeof dataIndex === 'object' && dataIndex !== null) {
        // metadata name
        dataIndex = dataIndex.join(' ');
      }
      return dataIndex;
    }) || [];

  const renderCurTarget = (
    data: Object,
    parentKey: Array<String>,
    curTarget = '',
  ) => {
    let returnItem = '';
    Object.entries(data).forEach(([curKey, curItem]) => {
      // 如果属性的值是对象,继续递归
      if (typeof curItem === 'object' && curItem !== null) {
        returnItem += ' ' + renderCurTarget(curItem, parentKey.concat(curKey));
      }

      columnsDataIndexArr.some((dataIndex) => {
        // 如果data对象的属性匹配到了columnsDataIndexArr中的某一项
        if (dataIndex === parentKey.concat(curKey).join(' ')) {
          // 当值为String|Number|Array类型时,将值加到curTarget中
          if (
            typeof curItem === 'string' ||
            typeof curItem === 'number' ||
            Array.isArray(curItem)
          ) {
            returnItem += ' ' + curItem;
          }
        }
      });
    });
    return `${curTarget} ${returnItem}`;
  };

  const dataSourceMap = useMemo(() => {
    if (!props.dataSource || !Array.isArray(props.dataSource)) {
      return [];
    }
    return props.dataSource.reduce((acc, curVal, curIdx) => {
      // let curTarget = ''
      // Object.entries(curVal).forEach(([curKey, curItem]) => {
      //   if (columnsDataIndexArr.indexOf(curKey) >= 0 && (typeof curItem === 'string' || typeof curItem === 'number')) {
      //     curTarget = `${curTarget} ${curItem}`
      //   }
      // })

      return {
        ...acc,
        [curIdx]: renderCurTarget(curVal, []).toLowerCase(),
      };
    }, {});
  }, [columnsDataIndexArr, props.dataSource]);
  // console.log('basicTable dataSourceMap', dataSourceMap)
  const columnsMap = useMemo(() => {
    const result = {} as any;
    if (props.columns && props.columns.length > 0) {
      props.columns.forEach((columnItem) => {
        if (columnItem.dataIndex) {
          result[columnItem.dataIndex as string] = {
            title: columnItem.title,
            dataIndex: columnItem.dataIndex,
            value: null,
            commonSearch: columnItem.commonSearch,
            commonFilter: columnItem.commonFilter,
            commonSorter: columnItem.commonSorter,
          };
        }
      });
    }
    return result;
  }, [props.columns]);
  // console.log('basicTable columnsMap', columnsMap)
  function columnInit(initColumnState) {
    return initColumnState;
  }

  function columnsReducer(
    state: IColumnsReducerState,
    action: TColumnsReducerAction,
  ) {
    switch (action.type) {
      case 'update':
        return {
          ...state,
          [action.dataIndex]: {
            ...state[action.dataIndex],
            dataIndex: action.dataIndex,
            type: action.updateType,
            value: action.value,
          },
        };
      case 'clear':
        return columnInit(action.initialState);
      default:
        return state;
    }
  }

  const [columnsState, columnsDispatch] = useReducer(
    columnsReducer,
    columnsMap,
    columnInit,
  );
  const debouncedSearchQuery = useDebounce(searchQuery, 300);

  useDeepCompareEffect(() => {
    // 模糊匹配
    let newDataSource = [...tmpDataSource];
    if (showSearch && debouncedSearchQuery && debouncedSearchQuery.trim()) {
      if (props.onSearch) {
        // 使用用户自定义的search回调
        props.onSearch(debouncedSearchQuery.trim());
        return;
      } else {
        // 使用组件默认的search回调

        const debouncedSearchArr = debouncedSearchQuery
          .trim()
          .toLowerCase()
          .split(' ');
        newDataSource = newDataSource.filter((fiterItem, filterIdx) => {
          const filterStr = dataSourceMap[filterIdx];
          return debouncedSearchArr.every(
            (someItem) => filterStr.indexOf(someItem) >= 0,
          );
        });
      }
    }

    if (queryFormColumns && Object.keys(queryFormValues).length > 0) {
      newDataSource = _.filter(newDataSource, (sourceItem) => {
        return Object.entries(queryFormValues).every(
          ([queryKey, queryValue]) => {
            if (
              (!queryValue && queryValue !== 0) ||
              (Array.isArray(queryValue) && queryValue.length === 0)
            ) {
              return true;
            }
            if (typeof queryValue === 'string') {
              return (
                sourceItem[queryKey] &&
                sourceItem[queryKey].indexOf(queryValue) >= 0
              );
            }
            if (typeof queryValue === 'number') {
              return sourceItem[queryKey] === queryValue;
            }
            if (Array.isArray(queryValue) && queryValue.length > 0) {
              return queryValue.indexOf(sourceItem[queryKey]) >= 0;
            }
          },
        );
      });
    }

    setDataSource(newDataSource);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchQuery, queryFormValues, props.dataSource]);

  const handleChange = (
    pagination: any,
    filters: any,
    sorter: any,
    extra: any,
  ) => {
    sorterDispatch({
      type: 'update',
      value: sorter,
    });

    Object.entries(filters).forEach(([filterKey, filterValue]) => {
      if (columnsMap[filterKey].commonFilter) {
        columnsDispatch({
          type: 'update',
          dataIndex: filterKey,
          updateType: 'filter',
          value: filterValue,
        });
      }
    });

    checkRightHeader(filters, sorter);

    if (props.onChange) {
      props.onChange(pagination, filters, sorter, extra);
    }
  };

  const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
    const checkSorter = sorter || sorterState;
    const checkFilters = filters || columnsState;
    const checkSearch = search || columnsState;
    const isSearch = Object.values(checkSearch).some((columnItem: any) => {
      return !!(columnItem.type === 'search' && columnItem.value);
    });
    let isFilter = false;
    if (filters) {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return columnItem && columnItem.length > 0;
      });
    } else {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return !!(
          columnItem.type === 'filter' &&
          columnItem.value &&
          columnItem.value.length > 0
        );
      });
    }
    const isSorter = !!checkSorter.column;

    const res = isSearch || isFilter || isSorter;
    setShowRightHeader(res);
  };

  const handleFilterSearch = useCallback(
    (
      selectedKeys: React.ReactText[] | undefined,
      confirm: (() => void) | undefined,
      dataIndex: string | number,
    ) => {
      if (confirm) {
        confirm();
      }
      if (selectedKeys && dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
          updateType: 'search',
          value: selectedKeys[0],
        });
      }
    },
    [],
  );

  const handleFilterSearchReset = useCallback(
    (
      clearFilters: ((selectedKeys: string[]) => void) | undefined,
      dataIndex: string | number | undefined,
    ) => {
      if (clearFilters) {
        clearFilters([]);
      }
      if (dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
        });
      }
    },
    [],
  );

  const handleClearAll = () => {
    sorterDispatch({ type: 'clear' });
    columnsDispatch({ type: 'clear', initialState: columnsMap });
    setShowRightHeader(false);
    setTimeout(() => {
      Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
        if (filterItem && filterItem.clearFilters) {
          filterItem.clearFilters([]);
        }
      });
    });
  };

  const handleSortClear = () => {
    sorterDispatch({ type: 'clear' });
    checkRightHeader(null, {});
  };

  const handleFilterClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
      value: [],
    });

    const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
      if (filterItem.dataIndex === columnValue.dataIndex) {
        return {
          [filterItem.dataIndex]: [],
        };
      }
      return {
        [filterItem.dataIndex]: filterItem.value || [],
      };
    });

    checkRightHeader(tmpFilters, sorterState, columnsState);
  };

  const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
    });

    if (
      clearFiltersRef.current[columnValue.dataIndex] &&
      clearFiltersRef.current[columnValue.dataIndex].clearFilters
    ) {
      clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
    }

    checkRightHeader(null, sorterState, {
      ...columnsState,
      [columnValue.dataIndex]: {
        title: columnsState[columnValue.dataIndex].title,
        dataIndex: columnsState[columnValue.dataIndex].dataIndex,
        value: null,
      },
    });
  };

  const renderColumns = () => {
    // if (!dataSource || (dataSource && dataSource.length === 0)) {
    //   return props.columns;
    // }

    const handledColumns = props.columns.map((columnItem) => {
      const currentItem = _.cloneDeep(columnItem);

      // filter
      if (currentItem.commonFilter && !currentItem.filters) {
        const filters = _.uniq(_.map(dataSource, columnItem.dataIndex));
        currentItem.filters = filters.map((value: string) => {
          return {
            text: value,
            value,
          };
        });

        currentItem.filterIcon = () => <FilterOutlined />;
        currentItem.filteredValue =
          columnsState[columnItem.dataIndex as string].value;
        currentItem.onFilter = (value, record: any) => {
          if (currentItem.dataIndex && record[currentItem.dataIndex]) {
            return record[currentItem.dataIndex] === value;
          }

          return false;
        };
      }

      // sort
      if (currentItem.commonSorter) {
        currentItem.sorter = (aItem: any, bItem: any) => {
          const a = aItem[currentItem.dataIndex as string];
          const b = bItem[currentItem.dataIndex as string];
          // number
          const numA = Number(a);
          const numB = Number(b);
          if (!isNaN(numA) && !isNaN(numB)) {
            return numA - numB;
          }

          // date
          const dateA = +new Date(a);
          const dateB = +new Date(b);
          if (!isNaN(dateA) && !isNaN(dateB)) {
            return dateA - dateB;
          }

          // string
          if (typeof a === 'string' && typeof b === 'string') {
            return a > b ? 1 : -1;
          }

          return 0;
        };
        currentItem.sortOrder =
          sorterState.columnKey === currentItem.dataIndex && sorterState.order;
      }

      // Search
      if (currentItem.commonSearch) {
        currentItem.filterIcon = () => <SearchOutlined />;

        currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
          if (
            visible &&
            filterSearchInputRef.current &&
            filterSearchInputRef.current.select
          ) {
            setTimeout(() => {
              if (
                filterSearchInputRef.current &&
                filterSearchInputRef.current.select
              ) {
                filterSearchInputRef.current.select();
              }
              return null;
            });
          }
        };

        currentItem.filterDropdown = ({
          setSelectedKeys,
          selectedKeys,
          confirm,
          clearFilters,
        }) => {
          clearFiltersRef.current[currentItem.dataIndex as string] = {
            clearFilters,
          };

          return (
            <div style={{ padding: 8 }}>
              <Input
                data-testid='filter-search-input'
                autoFocus={true}
                ref={(node) => {
                  filterSearchInputRef.current = node;
                }}
                placeholder={t('table.filter.search.placeholder')}
                value={selectedKeys && selectedKeys[0]}
                onChange={(e) => {
                  if (setSelectedKeys) {
                    return setSelectedKeys(
                      e.target.value ? [e.target.value] : [],
                    );
                  }
                  return [];
                }}
                onPressEnter={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                style={{ width: 188, marginBottom: 8, display: 'block' }}
              />
              <Button
                type='primary'
                onClick={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                icon='search'
                size='small'
                data-testid='search-btn-ok'
                style={{ width: 90, marginRight: 8 }}
              >
                {t('table.filter.search.btn.ok')}
              </Button>
              <Button
                onClick={() =>
                  handleFilterSearchReset(clearFilters, currentItem.dataIndex)
                }
                size='small'
                style={{ width: 90 }}
              >
                {t('table.filter.search.btn.cancel')}
              </Button>
            </div>
          );
        };

        let searchWords: any[] = [];

        const tmpStateValue =
          columnsState[currentItem.dataIndex as string].value;
        if (typeof tmpStateValue === 'string') {
          searchWords = [tmpStateValue];
        }

        if (
          Array.isArray(tmpStateValue) &&
          typeof tmpStateValue[0] === 'string'
        ) {
          searchWords = [tmpStateValue[0]];
        }

        currentItem.onFilter = (value, record: any) => {
          return record[currentItem.dataIndex as string]
            .toString()
            .toLowerCase()
            .includes(value.toLowerCase());
        };

        if (!currentItem.render) {
          currentItem.render = (value, row, index) => {
            if (currentItem.searchRender) {
              return currentItem.searchRender(
                value,
                row,
                index,
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />,
              );
            } else {
              return (
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />
              );
            }
          };
        }
      }
      return currentItem;
    });
    // console.log('basicTable handledColumns', handledColumns)
    return handledColumns;
  };

  const isSearch = Object.values(columnsState).some((columnItem: any) => {
    return !!(columnItem.type === 'search' && columnItem.value);
  });

  const isFilter = Object.values(columnsState).some((columnItem: any) => {
    return !!(
      columnItem.type === 'filter' &&
      columnItem.value &&
      columnItem.value.length > 0
    );
  });

  const handleReload = () => {
    if (props.onReload) {
      props.onReload();
    }
  };

  const handleSearchChange = (e) => {
    const query = e.target.value;
    setSearchQuery(query);
  };

  const handleQueryFormInit = (data) => {
    if (!isQueryInit) {
      setQueryFormValues(data);
      setQueryInit(true);
    }
  };

  const handleQueryFormChange = (data) => {
    setQueryFormValues(data);
  };

  const handleQueryFormReset = (data) => {
    setQueryFormValues(data);
  };

  const renderRightHeader = (params) => {
    if (!showFilter) {
      return null;
    }
    return (
      <>
        <div>
          <b
            style={{
              display: 'inline-block',
              marginTop: 3,
            }}
          >
            {t('table.filter.header.title')}
          </b>
        </div>
        {(isSearch || isFilter) &&
          Object.values(columnsState as IColumnsReducerState).map(
            (columnValue) => {
              if (columnValue.type === 'search' && columnValue.value) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterSearchClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }

              if (
                columnValue.type === 'filter' &&
                columnValue.value &&
                columnValue.value.length > 0
              ) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value.join(',')}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value.join(',')}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }
              return null;
            },
          )}
        {sorterState.columnKey && sorterState.column && (
          <div className={`${params.headerClsPrefix}-item`}>
            <Tooltip
              title={
                <span>
                  {sorterState.column.title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
              }
            >
              <Button
                size='small'
                className='table-header-item-btn'
                onClick={handleSortClear}
              >
                <span className='table-header-item-btn-content'>
                  {sorterState.column.title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
                <CloseOutlined />
              </Button>
            </Tooltip>
          </div>
        )}
        <div className={`${params.headerClsPrefix}-item`}>
          <Button
            size='small'
            type='link'
            data-testid='btn-clearall'
            onClick={handleClearAll}
          >
            {t('table.filter.header.btn.clear')}
          </Button>
        </div>
      </>
    );
  };

  const renderSearch = () => {
    return (
      <Tooltip placement='topLeft' title={searchPlaceholder}>
        <Input
          data-testid='search-input'
          prefix={
            reloadBtnPos === 'right' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          suffix={
            reloadBtnPos === 'left' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          allowClear={true}
          value={searchQuery}
          onChange={handleSearchChange}
          placeholder={searchPlaceholder}
        />
      </Tooltip>
    );
  };

  const renderReloadBtn = () => {
    if (reloadBtnType === 'icon') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-icon`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <ReloadOutlined
          onClick={handleReload}
          spin={loading}
          className={reloadBtnCls}
        />
      );
    }

    if (reloadBtnType === 'btn') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-btn`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <Button
          className={reloadBtnCls}
          loading={loading}
          icon={<ReloadOutlined />}
          data-testid='reload-btn'
          onClick={handleReload}
        />
      );
    }
  };

  return (
    <ConfigProvider {...props.antConfig}>
      <div className={tableClassName} style={props.style}>
        {queryFormColumns && (
          <div className={tableQueryCls}>
            <QueryForm
              onChange={
                isQuerySearchOnChange
                  ? handleQueryFormChange
                  : handleQueryFormInit
              }
              onReset={handleQueryFormReset}
              onSearch={handleQueryFormChange}
              columns={queryFormColumns}
              showOptionBtns={showQueryOptionBtns}
              showCollapseButton={showQueryCollapseButton}
              {...queryFormProps}
            />
          </div>
        )}
        <div className={tableBodyCls}>
          {!!props.tableTitle && <h3> {props.tableTitle} </h3>}

          {showFullSearch && (
            <Row className={`${prefixCls}-header-search`}>
              {showSearch ? renderSearch() : <div></div>}
              {showReloadBtn2SearchRight && renderReloadBtn()}
            </Row>
          )}
          {/* 自定义格式 */}
          {filterType === 'flex' && (
            <div
              style={{
                display: 'flex',
                justifyContent: 'flex-start',
                marginBottom: '10px',
              }}
            >
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              {props.leftHeader}
            </div>
          )}
          {filterType === 'none' && searchPos !== 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
              >
                {props.leftHeader}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'none' && showSearch && searchPos === 'right' && (
            <Row className={`${prefixCls}-header-filter`} align='middle'>
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={13}
              >
                {showReloadBtn2FilterLeft && renderReloadBtn()}
                {props.leftHeader}
              </Col>
              <Col
                span={6}
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
              >
                {props.customHeader && (
                  <div data-testid='custom-header'>{props.customHeader}</div>
                )}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={5}
              >
                {renderSearch()}
              </Col>

              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'line' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-line`}
                span={24}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-line`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'half' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={12}
              >
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={12}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-right`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          <div className={`${prefixCls}-table-wrapper`}>
            <div className={tableContentClassName}>
              <Table
                loading={loading}
                columns={renderColumns()}
                dataSource={dataSource}
                bordered={false}
                rowSelection={rowSelection}
                onChange={handleChange}
                pagination={{
                  showTotal: showTotal,
                  showSizeChanger: true,
                  showQuickJumper: true,
                  pageSizeOptions: pageSizeOptions,
                  ...props.pagination,
                }}
                {...antProps}
              />
              {rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
                <div className={`${prefixCls}-table-content-select-num`}>
                  {t('table.select.num')}
                  {selectRowNum}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </ConfigProvider>
  );
}
Example #17
Source File: DashboardReloadAction.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function DashboardReloadAction(): JSX.Element {
    const { itemsLoading, autoRefresh, refreshMetrics } = useValues(dashboardLogic)
    const { refreshAllDashboardItemsManual, setAutoRefresh } = useActions(dashboardLogic)
    const [open, setOpen] = useState(false)

    return (
        <>
            <Dropdown.Button
                overlay={
                    <Menu data-attr="auto-refresh-picker" id="auto-refresh-picker">
                        <div
                            id="auto-refresh-check"
                            key="auto-refresh-check"
                            onClick={(e) => {
                                e.stopPropagation()
                                setOpen(true)
                                setAutoRefresh(!autoRefresh.enabled, autoRefresh.interval)
                            }}
                        >
                            <Tooltip title={`Refresh dashboard automatically`} placement="bottomLeft">
                                <Checkbox
                                    onChange={(e) => {
                                        e.stopPropagation()
                                        e.preventDefault()
                                    }}
                                    checked={autoRefresh.enabled}
                                />
                                <label
                                    style={{
                                        marginLeft: 10,
                                        cursor: 'pointer',
                                    }}
                                >
                                    Auto refresh
                                </label>
                            </Tooltip>
                        </div>
                        <Menu.Divider />
                        <Menu.ItemGroup title="Refresh interval">
                            <Radio.Group
                                onChange={(e) => {
                                    setAutoRefresh(true, parseInt(e.target.value))
                                }}
                                value={autoRefresh.interval}
                                style={{ width: '100%' }}
                            >
                                <Space direction="vertical" style={{ width: '100%' }}>
                                    {intervalOptions.map(({ label, value }) => (
                                        <Radio key={value} value={value} style={{ width: '100%' }}>
                                            {label}
                                        </Radio>
                                    ))}
                                </Space>
                            </Radio.Group>
                        </Menu.ItemGroup>
                    </Menu>
                }
                trigger={['click']}
                onClick={() => refreshAllDashboardItemsManual()}
                icon={<DownOutlined />}
                disabled={itemsLoading}
                buttonsRender={([leftButton, rightButton]) => [
                    React.cloneElement(leftButton as React.ReactElement, { style: { paddingLeft: 10 } }),
                    rightButton,
                ]}
                visible={open}
                onVisibleChange={(toOpen) => setOpen(toOpen)}
            >
                <span className="dashboard-items-action-icon">
                    {itemsLoading ? <LoadingOutlined /> : <ReloadOutlined />}
                </span>
                <span className={clsx('dashboard-items-action-refresh-text', { hidden: itemsLoading })}>
                    <LastRefreshText />
                </span>
                <span className={clsx('dashboard-items-action-refresh-text', 'completed', { hidden: !itemsLoading })}>
                    Refreshed {refreshMetrics.completed} out of {refreshMetrics.total}
                </span>
            </Dropdown.Button>
        </>
    )
}
Example #18
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

      {/*</Col>*/}
    </Row>
  );
}
Example #19
Source File: index.tsx    From S2 with MIT License 4 votes vote down vote up
SwitcherContent: React.FC<SwitcherContentProps> = React.memo(
  (props) => {
    const {
      innerContentClassName,
      contentTitleText = i18n('行列切换'),
      resetText = i18n('恢复默认'),
      onToggleVisible,
      onSubmit,
      sheetType,
      ...defaultFields
    } = props;

    const defaultState = getSwitcherState(defaultFields);

    const [state, setState] = React.useState<SwitcherState>(defaultState);
    const [draggingItemId, setDraggingItemId] = React.useState<string>(null);
    const SWITCHER_CONFIG = React.useMemo(getSwitcherConfig, []);

    const onBeforeDragStart = (initial: BeforeCapture) => {
      setDraggingItemId(initial.draggableId);
    };

    const onDragEnd = ({ destination, source }: DropResult) => {
      // reset dragging item id
      setDraggingItemId(null);

      // cancelled or drop to where can't drop
      if (!destination) {
        return;
      }
      // don't change position
      if (
        destination.droppableId === source.droppableId &&
        destination.index === source.index
      ) {
        return;
      }

      const updatedState = moveItem(
        state[source.droppableId],
        state[destination.droppableId],
        source,
        destination,
      );
      setState({ ...state, ...updatedState });
    };

    const onReset = () => {
      setState(defaultState);
    };

    const onConfirm = () => {
      onToggleVisible();
      onSubmit?.(generateSwitchResult(state));
    };

    const onVisibleItemChange = (
      fieldType: FieldType,
      checked: boolean,
      id: string,
      parentId?: string,
    ) => {
      const updatedState = checkItem(state[fieldType], checked, id, parentId);
      setState({
        ...state,
        [fieldType]: updatedState,
      });
    };

    const isNothingChanged = isEqual(defaultState, state);

    const displayFieldItems = SWITCHER_FIELDS.filter(
      (filed) => sheetType !== 'table' || filed === FieldType.Cols,
    );
    return (
      <DragDropContext
        onBeforeCapture={onBeforeDragStart}
        onDragEnd={onDragEnd}
      >
        <div
          className={cx(
            innerContentClassName,
            getSwitcherClassName(CLASS_NAME_PREFIX),
          )}
        >
          <header className={getSwitcherClassName(CLASS_NAME_PREFIX, 'header')}>
            {contentTitleText}
          </header>
          <main
            className={cx(
              getSwitcherClassName(CLASS_NAME_PREFIX, 'main'),
              getMainLayoutClassName(sheetType),
            )}
          >
            {displayFieldItems.map((type) => (
              <Dimension
                {...defaultFields[type]}
                key={type}
                fieldType={type}
                items={state[type]}
                crossRows={shouldCrossRows(sheetType, type)}
                droppableType={SWITCHER_CONFIG[type].droppableType}
                draggingItemId={draggingItemId}
                onVisibleItemChange={onVisibleItemChange}
              />
            ))}
          </main>
          <footer className={getSwitcherClassName(CLASS_NAME_PREFIX, 'footer')}>
            <Button
              type={'text'}
              icon={<ReloadOutlined />}
              className={getSwitcherClassName(
                CLASS_NAME_PREFIX,
                'footer',
                'reset-button',
              )}
              disabled={isNothingChanged}
              onClick={onReset}
            >
              {resetText}
            </Button>
            <div
              className={getSwitcherClassName(
                CLASS_NAME_PREFIX,
                'footer',
                'actions',
              )}
            >
              <Button className="action-button" onClick={onToggleVisible}>
                {i18n('取消')}
              </Button>
              <Button
                className="action-button"
                type="primary"
                disabled={isNothingChanged}
                onClick={onConfirm}
              >
                {i18n('确定')}
              </Button>
            </div>
          </footer>
        </div>
      </DragDropContext>
    );
  },
)
Example #20
Source File: DynamicDashboardTitle.tsx    From iot-center-v2 with MIT License 4 votes vote down vote up
DynamicDashboardTitle: React.FC<TDynamicDashboardTitleProps> = (
  props
) => {
  const {
    dashboardKey,
    isEditing,
    setIsEditing,
    onDeleteDashboard,
    onEditAccept,
    onEditCancel,
    onOpenSettings,
    onReloadDashboard,
    // newName,
    // setNewName,
  } = props

  const editable = (
    <div style={{width: '100%'}}>
      {dashboardKey}{' '}
      {/*     
      <Input
        value={newName}
        onChange={(e) => setNewName(e.target.value)}
        style={{width: 'auto'}}
      />
*/}
      <Tooltip title={'Cancel editing'}>
        <Button
          size="small"
          type="text"
          icon={<CloseOutlined />}
          onClick={onEditCancel}
        ></Button>
      </Tooltip>
      <Tooltip title={'Save changes'} color="green">
        <Button
          size="small"
          type="text"
          style={{color: 'green'}}
          icon={<CheckOutlined />}
          onClick={onEditAccept}
        ></Button>
      </Tooltip>
      <Tooltip title={'Delete dashboard'} color="red">
        <Button
          size="small"
          type="text"
          icon={<DeleteOutlined />}
          onClick={onDeleteDashboard}
          danger
        ></Button>
      </Tooltip>
      <Tooltip title="Dashboard settings" color="#4040ad">
        <Button
          size="small"
          type="text"
          style={{color: '#4040ad'}}
          icon={<SettingOutlined />}
          onClick={onOpenSettings}
        ></Button>
      </Tooltip>
    </div>
  )

  const fixed = (
    <>
      {dashboardKey}{' '}
      <Tooltip title={'Edit dashboard'}>
        <Button
          size="small"
          type="text"
          icon={<EditOutlined />}
          onClick={() => setIsEditing(true)}
        ></Button>
      </Tooltip>
      <Tooltip title={'Reload dashboard'}>
        <Button
          size="small"
          type="text"
          icon={<ReloadOutlined />}
          onClick={onReloadDashboard}
        ></Button>
      </Tooltip>
    </>
  )

  return <>{isEditing ? editable : fixed}</>
}
Example #21
Source File: EventsList.tsx    From jitsu with MIT License 4 votes vote down vote up
EventsList: React.FC<{
  type: EventType
  filterOptions: FilterOption[]
}> = ({ type, filterOptions }) => {
  const statusOptions = [
    { label: "All", value: null },
    { label: "Error", value: "error" },
  ]

  const listInnerRef = useRef()
  const location = useLocation()
  const params = new URLSearchParams(location.search)
  const [autoReload, setAutoReload] = useState(true)
  const [selectedEvent, setSelectedEvent] = useState(null)
  const [events, setEvents] = useState<Event[]>([])
  const [filteredEvents, setFilteredEvents] = useState<Event[]>([])
  const [term, setTerm] = useState(params.get("q"))
  const [idFilter, setIdFilter] = useState(
    filterOptions.find(f => f.value === params.get("id"))?.value ?? filterOptions[0]?.value
  )
  const [statusFilter, setStatusFilter] = useState(
    statusOptions.find(f => f.value === params.get("status"))?.value ?? statusOptions[0]?.value
  )
  const [reloadCount, setReloadCount] = useState(0)
  const services = useServices()
  const history = useHistory()

  const destinationsMap: Record<string, DestinationData> = destinationsStore.listIncludeHidden.reduce((index, dst) => {
    index[dst._uid] = dst
    return index
  }, {})

  useEffect(() => {
    if (!idFilter) {
      history.push({ search: null })
      return
    }
    let queryParams = omitBy({ type, id: idFilter, status: statusFilter }, isNull)
    if (term) {
      queryParams["q"] = term
    }
    history.push({ search: new URLSearchParams(queryParams).toString() })
  }, [idFilter, statusFilter, term])

  const { data, error } = useLoaderAsObject(() => {
    if (!idFilter) {
      return null
    }

    const ids = type === EventType.Destination ? `${services.activeProject.id}.${idFilter}` : idFilter
    setSelectedEvent(null)
    return services.backendApiClient
      .get(
        `/events/cache?project_id=${services.activeProject.id}&limit=500&namespace=${type}&ids=${ids}&status=${
          statusFilter ?? ""
        }`,
        { proxy: true }
      )
      .then(events => {
        return { events, id: idFilter }
      })
  }, [idFilter, statusFilter, reloadCount])

  useEffect(() => {
    const interval = setInterval(() => {
      if (!autoReload || selectedEvent) {
        return
      }
      setReloadCount(reloadCount + 1)
    }, 15000)
    return () => clearInterval(interval)
  }, [autoReload, selectedEvent, reloadCount])

  const filterByTerm = (events, term) => {
    return term ? events.filter(i => JSON.stringify(i.rawJson).indexOf(term) !== -1) : events
  }

  const search = term => {
    setTerm(term)
    setFilteredEvents(filterByTerm(events, term))
  }

  useEffect(() => {
    const initialEvents = error || !data ? [] : processEvents(type, data)
    setEvents(initialEvents)
    setFilteredEvents(filterByTerm(initialEvents, term))
  }, [error, data])

  if (!filterOptions.length) {
    return <NoDataFlowing showHint={true} />
  }

  const filters = (
    <>
      <div className={`mb-6 flex ${styles.filters}`}>
        <SelectFilter
          className="mr-5"
          label={type === EventType.Token ? "API Key" : "Destination"}
          initialValue={idFilter}
          options={filterOptions}
          onChange={option => {
            setIdFilter(option.value)
          }}
        />
        <SelectFilter
          className="mr-5"
          label="Status"
          initialValue={statusFilter}
          options={statusOptions}
          onChange={option => {
            setStatusFilter(option.value)
          }}
        />
        <Button
          size="large"
          type="primary"
          className={styles.reloadBtn}
          onClick={() => {
            setReloadCount(count => count + 1)
          }}
        >
          <ReloadOutlined /> Reload
        </Button>
      </div>
      <Input className="w-full" placeholder="Filter" value={term} onChange={e => search(e.target.value)} />
    </>
  )

  const eventStatusMessage = event => {
    const error = event.status === EventStatus.Error
    const skip = event.status === EventStatus.Skip

    if (type === EventType.Token) {
      if (skip) {
        return `Skip`
      }
      return error ? event.rawJson.error ?? "Error" : "Success"
    }

    return error
      ? "Failed - at least one destination load is failed"
      : skip
      ? "Skipped - event was not sent to destination"
      : "Success - successfully sent to destination"
  }

  if (error) {
    return (
      <div className="w-full">
        {filters}
        <CenteredError error={error} />
      </div>
    )
  } else if (!data) {
    return (
      <div className="w-full">
        {filters}
        <CenteredSpin />
      </div>
    )
  }

  const { last_minute_limited, cache_capacity_per_interval, interval_seconds } = data?.events
  const alert =
    last_minute_limited > 0 ? (
      <div className="mt-4">
        <Alert
          message={`This isn't a full list of all events. Jitsu doesn't cache all events, but the only ${cache_capacity_per_interval} event per ${interval_seconds} seconds. Other ${last_minute_limited} events from the last minute have been being processed and stored to the destinations but haven't been saved into the cache.`}
          type="warning"
        />
      </div>
    ) : null

  const onScroll = () => {
    if (!listInnerRef.current) {
      return
    }
    const { scrollTop } = listInnerRef.current
    const startAutoReload = scrollTop === 0
    if (startAutoReload === autoReload) {
      return
    }
    setAutoReload(startAutoReload)
  }

  return (
    <>
      {filters}
      {alert}
      <div
        className={`mt-3 transition-all duration-300 ${styles.autoReloadInfo} ${
          autoReload && !selectedEvent ? "" : "opacity-0"
        }`}
      >
        <ReloadOutlined spin={true} /> Auto reload is enabled. <a onClick={() => setAutoReload(false)}>Disable</a>
      </div>
      <div className={styles.eventsList} ref={listInnerRef} onScroll={onScroll}>
        {!filteredEvents.length ? <NoDataFlowing showHint={false} /> : null}
        {filteredEvents.map(event => {
          const active = event.eventId === selectedEvent
          return (
            <div key={event.eventId}>
              <div
                className={`overflow-hidden w-full flex flex-row border-b border-secondaryText border-opacity-50 items-center cursor-pointer h-12 ${
                  selectedEvent === event.eventId ? "bg-bgSecondary" : "hover:bg-bgComponent"
                }`}
                key="header"
                onClick={() => setSelectedEvent(active ? null : event.eventId)}
              >
                <div className="w-6 flex items-center justify-center px-3 text-lg" key="icon">
                  <Tooltip title={eventStatusMessage(event)}>
                    {event.status === EventStatus.Error ? (
                      <ExclamationCircleOutlined className="text-error" />
                    ) : event.status === EventStatus.Pending || event.status === EventStatus.Skip ? (
                      <MinusCircleOutlined className="text-warning" />
                    ) : (
                      <CheckCircleOutlined className="text-success" />
                    )}
                  </Tooltip>
                </div>
                <div
                  className={`text-xxs whitespace-nowrap text-secondaryText px-1 ${styles.timestampColumn}`}
                  key="time"
                >
                  <div>{event.timestamp.format("YYYY-MM-DD HH:mm:ss")} UTC</div>
                  <div className="text-xxs">{event.timestamp.fromNow()}</div>
                </div>
                <div
                  className="pl-4 text-3xs text-secondaryText font-monospace overflow-hidden overflow-ellipsis h-12 leading-4 flex-shrink"
                  key="json"
                >
                  {event.rawJson.malformed ? event.rawJson.malformed : JSON.stringify(event.rawJson, null, 2)}
                </div>
                <div
                  className={cn(
                    "w-12 text-testPale flex items-center justify-center px-2 text-xl transition-transform duration-500",
                    styles.expandBtn,
                    active && "transform rotate-90"
                  )}
                  key="expand"
                >
                  <RightCircleOutlined />
                </div>
              </div>
              <div key="details">
                {active && <EventsView event={event} allDestinations={destinationsMap} className="pb-6" />}
              </div>
            </div>
          )
        })}
      </div>
    </>
  )
}
Example #22
Source File: ShapeComponent.tsx    From ant-simple-draw with MIT License 4 votes vote down vote up
Shape: FC<ShapeType> = memo(function Shape({ children, style, element, defaultStyle }) {
  const currentElement = useRef<HTMLDivElement | null>(null);
  const [cursors, setCursors] = useState<Record<pointType, string>>();
  const [curComponent, active] = useSelector(
    createSelector([(state: storeType) => state.component], (component) => {
      const active = element?.componentId === component.curComponent?.componentId ? true : false;
      return [component.curComponent, active] as const;
    }),
  );
  const dispatch = useDispatch();
  /**
  @description 拖拽图形
   */
  const handleMouseDownOnShape: React.MouseEventHandler<HTMLDivElement> = async (e) => {
    e.stopPropagation();
    dispatch(isClickComponentAction(true));
    dispatch(curComponentAction(element));
    setCursors(getCursor(element));
    const pos = { ...defaultStyle };
    const startY = e.clientY;
    const startX = e.clientX;
    // 如果直接修改属性,值的类型会变为字符串,所以要转为数值型
    const startTop = Number(pos.top);
    const startLeft = Number(pos.left);

    // 如果元素没有移动,则不保存快照
    let hasMove = false;
    const move = async (moveEvent: MouseEvent) => {
      hasMove = true;
      const curX = moveEvent.clientX;
      const curY = moveEvent.clientY;
      pos.top = curY - startY + startTop;
      pos.left = curX - startX + startLeft;
      await dispatch(setShapeStyleAction(pos));
      // 触发元素移动事件,用于显示标线、吸附功能
      // curY - startY > 0 true 表示向下移动 false 表示向上移动
      // curX - startX > 0 true 表示向右移动 false 表示向左移动
      await dispatch(
        showMarkLineAction({
          isDownward: curY - startY > 0,
          isRightward: curX - startX > 0,
        }),
      );
    };

    const up = () => {
      hasMove && dispatch(recordSnapshot());
      // 触发元素停止移动事件,用于隐藏标线
      dispatch(hideMarkLineAction());
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };

  /**
   * @description 动态的布局连接桩
   */
  const getPointStyle = (point: string) => {
    const { width, height } = defaultStyle as { width: number; height: number };
    const hasT = /t/.test(point);
    const hasB = /b/.test(point);
    const hasL = /l/.test(point);
    const hasR = /r/.test(point);
    let newLeft = 0;
    let newTop = 0;
    // 四个角的点
    if (point.length === 2) {
      newLeft = hasL ? 0 : width;
      newTop = hasT ? 0 : height;
    } else {
      // 上下两点的点,宽度居中
      if (hasT || hasB) {
        newLeft = width / 2;
        newTop = hasT ? 0 : height;
      }
      // 左右两边的点,高度居中
      if (hasL || hasR) {
        newLeft = hasL ? 0 : width;
        newTop = Math.floor(height / 2);
      }
    }
    const eleStyle = {
      marginLeft: hasR ? '-4px' : '-4px',
      marginTop: '-4px',
      left: `${newLeft}px`,
      top: `${newTop}px`,
      cursor: cursors && (cursors as any)[point],
    };
    return eleStyle;
  };
  /**
   * @description 更具不同的八个点,显示不同的cursor属性值
   */
  const getCursor = (curComponent: templateDataType) => {
    const rotate = mod360(Number(curComponent?.style?.rotate)); // 取余 360
    const result = Object.create({});
    let lastMatchIndex = -1; // 从上一个命中的角度的索引开始匹配下一个,降低时间复杂度
    pointList.forEach((point) => {
      const angle = mod360(initialAngle[point] + rotate);
      const len = angleToCursor.length;
      while (true) {
        lastMatchIndex = (lastMatchIndex + 1) % len;
        const angleLimit = angleToCursor[lastMatchIndex];
        if (angle < 23 || angle >= 338) {
          result[point] = 'nw-resize';
          return;
        }
        if (angleLimit.start <= angle && angle < angleLimit.end) {
          result[point] = angleLimit.cursor + '-resize';
          return;
        }
      }
    });
    return result;
  };
  /**
   * @description 八个点,每个点按下的事件
   */
  const handleMouseDownOnPoint = (point: pointType, e: MergeEvent) => {
    e.stopPropagation();
    e.preventDefault();
    dispatch(isClickComponentAction(true));
    const style = { ...defaultStyle } as Required<MergeCSSProperties>;

    // 组件宽高比
    const proportion = Number(style.width) / Number(style.height);

    // 组件中心点
    const center = {
      x: Number(style.left) + Number(style.width) / 2,
      y: Number(style.top) + Number(style.height) / 2,
    };

    // 获取画布位移信息
    const editorRectInfo = $('#editor').getBoundingClientRect();

    // 获取 point 与实际拖动基准点的差值
    const pointRect = e.target.getBoundingClientRect();
    // 当前点击圆点相对于画布的中心坐标
    const curPoint = {
      x: Math.round(pointRect.left - editorRectInfo.left + e.target.offsetWidth / 2),
      y: Math.round(pointRect.top - editorRectInfo.top + e.target.offsetHeight / 2),
    };

    // 获取对称点的坐标
    const symmetricPoint = {
      x: center.x - (curPoint.x - center.x),
      y: center.y - (curPoint.y - center.y),
    };

    // 是否需要保存快照
    let needSave = false;
    let isFirst = true;

    const move = (moveEvent: MouseEvent) => {
      // 第一次点击时也会触发 move,所以会有“刚点击组件但未移动,组件的大小却改变了”的情况发生
      // 因此第一次点击时不触发 move 事件
      if (isFirst) {
        isFirst = false;
        return;
      }

      needSave = true;
      const curPositon = {
        x: moveEvent.clientX - editorRectInfo.left,
        y: moveEvent.clientY - editorRectInfo.top,
      };

      calculateComponentPositonAndSize(point, style, curPositon, proportion, false, {
        center,
        curPoint,
        symmetricPoint,
      });
      dispatch(setShapeStyleAction(style));
    };

    const up = () => {
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      needSave && dispatch(recordSnapshot());
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };
  /**
   * @description 图形旋转
   */
  const handleRotate: React.MouseEventHandler<HTMLDivElement> = (e) => {
    e.preventDefault();
    e.stopPropagation();
    dispatch(isClickComponentAction(true));
    const pos = { ...defaultStyle };
    const startY = e.clientY;
    const startX = e.clientX;
    const startRotate = Number(pos.rotate);
    // 获取元素中心点位置
    const rect = currentElement.current!.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    // 旋转前的角度
    const rotateDegreeBefore = Math.atan2(startY - centerY, startX - centerX) / (Math.PI / 180);

    // 如果元素没有移动,则不保存快照
    let hasMove = false;
    const move = (moveEvent: MouseEvent) => {
      hasMove = true;
      const curX = moveEvent.clientX;
      const curY = moveEvent.clientY;
      // 旋转后的角度
      const rotateDegreeAfter = Math.atan2(curY - centerY, curX - centerX) / (Math.PI / 180);
      // 获取旋转的角度值
      pos.rotate = startRotate + rotateDegreeAfter - rotateDegreeBefore;
      // 修改当前组件样式
      dispatch(setShapeStyleAction(pos));
    };

    const up = () => {
      hasMove && dispatch(recordSnapshot());
      document.removeEventListener('mousemove', move);
      document.removeEventListener('mouseup', up);
      // 根据旋转角度重新获取光标位置,cursor
      setCursors(getCursor(element));
    };

    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', up);
  };
  const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {
    // 阻止向父组件冒泡
    e.stopPropagation();
    e.preventDefault();
    dispatch(hideContextMenuAction());
  };
  return (
    <div
      className={`${styles.shape} ${active ? styles.shapeActive : null}`}
      style={style}
      onMouseDown={handleMouseDownOnShape}
      onClick={handleClick}
      ref={currentElement}
    >
      {active && (
        <>
          <ReloadOutlined className={styles.shapeRotate} onMouseDown={handleRotate} />
          {pointList.map((item, index) => (
            <div
              key={index}
              className={styles.shapePoint}
              style={getPointStyle(item)}
              onMouseDown={(e: any) => handleMouseDownOnPoint(item, e)}
            ></div>
          ))}
        </>
      )}
      {children}
    </div>
  );
})
Example #23
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function Chart(props: Props) {
  const { t } = useTranslation();
  const { options, barControl, rightBar, title } = props;
  const [refreshing, setRefreshing] = useState(false);
  const chartRef = useRef(null);
  const location = useLocation();
  const { metric, description = '', tags, range, limit, idents, classpath_id, classpath_prefix, prome_ql, yplotline, xplotline, step } = options;

  const [privateTags, setPrivateTags] = useState<TagType[]>([]);
  const [multi, setMulti] = useState(false);
  const [sort, setSort] = useState<'desc' | 'asc'>('desc');
  const [tooltipFormat, setTooltipFormat] = useState<'origin' | 'short'>('origin');
  const [instance, setInstance] =
    useState<{
      destroy: Function;
      update: Function;
      options: {
        yAxis: object;
        xAxis: object;
      };
    } | null>(null); // transfer Param and RangeItem into timestamp

  const formatDate = (r?: Range) => {
    let newR = r || range;

    if (newR) {
      if (isAbsoluteRange(newR)) {
        const { start, end } = newR;
        return {
          start,
          end,
        };
      } else {
        return generateTimeStampRange(newR);
      }
    }

    return {
      start: 0,
      end: 0,
    };
  };

  const { start, end } = formatDate(range);

  const initChart = (privateTags: TagType[] = []) => {
    let newTags = privateTags;

    if (tags && tags.length > 0) {
      newTags = tags.concat(newTags);
    }

    let params = Array.isArray(metric)
      ? metric.map((item) => {
          return {
            metric: item,
            classpath_id,
            classpath_prefix: classpath_prefix === undefined ? undefined : classpath_prefix ? 1 : 0,
            prome_ql,
            tags: newTags && newTags.length > 0 ? newTags : undefined,
            idents,
          };
        })
      : Array.isArray(prome_ql)
      ? prome_ql.map((item) => {
          return {
            metric,
            classpath_id,
            classpath_prefix: classpath_prefix === undefined ? undefined : classpath_prefix ? 1 : 0,
            prome_ql: item,
            tags: newTags && newTags.length > 0 ? newTags : undefined,
            idents,
          };
        })
      : [
          {
            metric,
            classpath_id,
            classpath_prefix: classpath_prefix ? 1 : 0,
            prome_ql,
            tags: newTags && newTags.length > 0 ? newTags : undefined,
            idents,
          },
        ];
    // GetData({
    //   params,
    //   start,
    //   end,
    //   limit,
    //   step,
    // }).then((data) => {
    //   const dataY: DataSource[] = [];
    //   data.dat.forEach((dataItem) => {
    //     dataY.push({
    //       name: dataItem.metric,
    //       data: dataItem.values.map((item) => item.v),
    //     });
    //   });
    //   const series: Array<any> = [];
    //   data.dat.forEach((dataItem) => {
    //     const { metric, values, tags } = dataItem;
    //     const seriesData = values.map((item) => {
    //       return {
    //         timestamp: item.t * 1000,
    //         value: item.v,
    //       };
    //     });
    //     series.push({
    //       name: (metric ? `【${metric}】` : '') + tags,
    //       data: seriesData,
    //     });
    //   });
    //   // if (chartRef.current) {
    //   //   // @ts-ignore
    //   //   chartRef.current.innerHTML = '';
    //   // }

    //   let graphOption = instance
    //     ? {
    //         series: series,
    //         tooltip: {
    //           precision: tooltipFormat,
    //           shared: multi,
    //           sharedSortDirection: sort,
    //         },
    //         // 必须xAxis和yAxis必须将属性返回
    //         yAxis: {
    //           ...instance.options.yAxis,
    //           plotLines: yplotline
    //             ? [
    //                 {
    //                   value: yplotline,
    //                   color: 'red',
    //                 },
    //               ]
    //             : undefined,
    //         },
    //         xAxis: {
    //           ...instance.options.xAxis,
    //           plotLines: xplotline
    //             ? [
    //                 {
    //                   value: xplotline * 1000,
    //                   color: 'red',
    //                 },
    //               ]
    //             : undefined,
    //         },
    //       }
    //     : {
    //         timestamp: 'x',
    //         xkey: 'timestamp',
    //         ykey: 'value',
    //         chart: {
    //           renderTo: chartRef.current,
    //         },
    //         yAxis: {
    //           plotLines: yplotline
    //             ? [
    //                 {
    //                   value: yplotline,
    //                   color: 'red',
    //                 },
    //               ]
    //             : undefined,
    //         },
    //         xAxis: {
    //           plotLines: xplotline
    //             ? [
    //                 {
    //                   value: xplotline * 1000,
    //                   color: 'red',
    //                 },
    //               ]
    //             : undefined,
    //         },
    //         series: series,
    //         tooltip: {
    //           precision: tooltipFormat,
    //           shared: multi,
    //           sharedSortDirection: sort,
    //         },
    //       };

    //   if (instance) {
    //     instance.update(graphOption);
    //   } else {
    //     setInstance(new TsGraph(graphOption));
    //   }
    // });
  };

  useEffect(() => {
    initChart(privateTags);
  }, [options, multi, sort, tooltipFormat]);

  // each chart is mounted once, when props and state change, the instance will update.
  // so below hook only run once.
  useEffect(() => {
    return () => {
      instance && instance.destroy();
    };
  }, [instance]);

  const handleRefresh = (e) => {
    if (refreshing) return;
    setRefreshing(true);
    initChart(privateTags); //需要将选择的过滤器传进去

    setTimeout(() => {
      setRefreshing(false);
    }, 1000);
  };

  const handleChartTagsChange = (e: TagType[]) => {
    setPrivateTags(e);
    initChart(e);
  };

  const handleMultiChange = (e) => {
    setMulti(e.target.checked);
  };

  const handleOrderSortChange = (bool) => {
    setSort(bool ? 'desc' : 'asc');
  };

  const handleTooltipFormat = (e) => {
    setTooltipFormat(e.target.checked ? 'short' : 'origin');
  };

  const renderMultiOrSort = (
    <>
      <Tooltip title={t('tooltip中展示所有曲线的值')}>
        <Checkbox onChange={handleMultiChange}>Multi</Checkbox>
      </Tooltip>
      <Tooltip
        title={
          <>
            <span>{t('SI格式化:')}</span>
            <a type='link' href='https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes' target='_blank'>
              {t('文档')}
            </a>
          </>
        }
      >
        <Checkbox onChange={handleTooltipFormat}>Format</Checkbox>
      </Tooltip>
      <OrderSort onChange={handleOrderSortChange} />
    </>
  );
  return (
    <div className='chart-wrapper'>
      {(title || rightBar || description || metric) && (
        <div className='chart-title'>
          {title ? (
            <div className='chart-title-label'>{title}</div>
          ) : (
            <div className='chart-title-label'>
              {metric} {description}
            </div>
          )}

          <div className='chart-title-right-bar'>
            {rightBar}
            {!location.pathname.startsWith('/chart/') && (
              <Button
                type='link'
                size='small'
                onClick={async (e) => {
                  e.preventDefault();
                  let { dat: ids } = await SetTmpChartData([{ configs: JSON.stringify({ title, options, barControl }) }]);
                  window.open('/chart/' + ids);
                }}
              >
                <ShareAltOutlined />
              </Button>
            )}
          </div>
        </div>
      )}
      {!barControl && (
        <div className='chart-filter'>
          <ReloadOutlined className='refresh' spin={refreshing} onClick={handleRefresh} />
          {renderMultiOrSort}
          {!prome_ql && (
            <TagFilterForChart
              options={{
                ...options,
                start,
                end,
                idents,
              }}
              onChange={handleChartTagsChange}
            />
          )}
        </div>
      )}

      {barControl === 'multiOrSort' && <div className='chart-filter'>{renderMultiOrSort}</div>}

      <div ref={chartRef} className='chart-content'></div>
    </div>
  );
}
Example #24
Source File: HTTPFlowTable.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
HTTPFlowTable: React.FC<HTTPFlowTableProp> = (props) => {
    const [data, setData, getData] = useGetState<HTTPFlow[]>([])
    const [params, setParams] = useState<YakQueryHTTPFlowRequest>(
        props.params || {SourceType: "mitm"}
    )
    const [pagination, setPagination] = useState<PaginationSchema>({
        Limit: OFFSET_LIMIT,
        Order: "desc",
        OrderBy: "created_at",
        Page: 1
    });

    // const [autoReload, setAutoReload, getAutoReload] = useGetState(false);
    const autoReloadRef = useRef<boolean>(false);
    const autoReload = autoReloadRef.current;
    const setAutoReload = (b: boolean) => {
        autoReloadRef.current = b
    };
    const getAutoReload = () => autoReloadRef.current;

    const [total, setTotal] = useState<number>(0)
    const [loading, setLoading] = useState(false)
    const [selected, setSelected, getSelected] = useGetState<HTTPFlow>()
    const [_lastSelected, setLastSelected, getLastSelected] = useGetState<HTTPFlow>()

    const [compareLeft, setCompareLeft] = useState<CompateData>({content: '', language: 'http'})
    const [compareRight, setCompareRight] = useState<CompateData>({content: '', language: 'http'})
    const [compareState, setCompareState] = useState(0)
    const [tableContentHeight, setTableContentHeight, getTableContentHeight] = useGetState<number>(0);
    // 用于记录适合
    const [_scrollY, setScrollYRaw, getScrollY] = useGetState(0)
    const setScrollY = useThrottleFn(setScrollYRaw, {wait: 300}).run

    // 如果这个大于等于 0 ,就 Lock 住,否则忽略
    const [_trigger, setLockedScroll, getLockedScroll] = useGetState(-1);
    const lockScrollTimeout = (size: number, timeout: number) => {
        setLockedScroll(size)
        setTimeout(() => setLockedScroll(-1), timeout)
    }

    const tableRef = useRef(null)

    const ref = useHotkeys('ctrl+r, enter', e => {
        const selected = getSelected()
        if (selected) {
            ipcRenderer.invoke("send-to-tab", {
                type: "fuzzer",
                data: {
                    isHttps: selected?.IsHTTPS,
                    request: new Buffer(selected.Request).toString()
                }
            })
        }
    })

    // 使用上下箭头
    useHotkeys("up", () => {
        setLastSelected(getSelected())
        const data = getData();
        if (data.length <= 0) {
            return
        }
        if (!getSelected()) {
            setSelected(data[0])
            return
        }
        const expected = parseInt(`${parseInt(`${(getSelected()?.Id as number)}`) + 1}`);
        // 如果上点的话,应该是选择更新的内容
        for (let i = 0; i < data.length; i++) {
            let current = parseInt(`${data[i]?.Id}`);
            if (current === expected) {
                setSelected(data[i])
                return
            }
        }
        setSelected(undefined)
    })
    useHotkeys("down", () => {
        setLastSelected(getSelected())
        const data = getData();

        if (data.length <= 0) {
            return
        }
        if (!getSelected()) {
            setSelected(data[0])
            return
        }
        // 如果上点的话,应该是选择更新的内容
        for (let i = 0; i < data.length; i++) {
            if (data[i]?.Id == (getSelected()?.Id as number) - 1) {
                setSelected(data[i])
                return
            }
        }
        setSelected(undefined)
    })

    // 向主页发送对比数据
    useEffect(() => {
        if (compareLeft.content) {
            const params = {info: compareLeft, type: 1}
            setCompareState(compareState === 0 ? 1 : 0)

            ipcRenderer.invoke("add-data-compare", params)
        }
    }, [compareLeft])

    useEffect(() => {
        if (compareRight.content) {
            const params = {info: compareRight, type: 2}
            setCompareState(compareState === 0 ? 2 : 0)

            ipcRenderer.invoke("add-data-compare", params)
        }
    }, [compareRight])

    const update = useMemoizedFn((
        page?: number,
        limit?: number,
        order?: string,
        orderBy?: string,
        sourceType?: string,
        noLoading?: boolean
    ) => {
        const paginationProps = {
            Page: page || 1,
            Limit: limit || pagination.Limit,
            Order: order || "desc",
            OrderBy: orderBy || "id"
        }
        if (!noLoading) {
            setLoading(true)
            // setAutoReload(false)
        }
        // yakQueryHTTPFlow({
        //     SourceType: sourceType, ...params,
        //     Pagination: {...paginationProps},
        // })
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: sourceType,
                ...params,
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                setData((rsp?.Data || []))
                setPagination(rsp.Pagination)
                setTotal(rsp.Total)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            })
            .finally(() => setTimeout(() => setLoading(false), 300))
    })

    const getNewestId = useMemoizedFn(() => {
        let max = 0;
        (getData() || []).forEach(e => {
            const id = parseInt(`${e.Id}`)
            if (id >= max) {
                max = id
            }
        })
        return max
    })

    const getOldestId = useMemoizedFn(() => {
        if (getData().length <= 0) {
            return 0
        }
        let min = parseInt(`${getData()[0].Id}`);
        (getData() || []).forEach(e => {
            const id = parseInt(`${e.Id}`)
            if (id <= min) {
                min = id
            }
        })
        return min
    })

    // 第一次启动的时候加载一下
    useEffect(() => {
        update(1)
    }, [])

    const scrollTableTo = useMemoizedFn((size: number) => {
        if (!tableRef || !tableRef.current) return
        const table = tableRef.current as unknown as {
            scrollTop: (number) => any,
            scrollLeft: (number) => any,
        }
        table.scrollTop(size)
    })

    const scrollUpdateTop = useDebounceFn(useMemoizedFn(() => {
        const paginationProps = {
            Page: 1,
            Limit: OFFSET_STEP,
            Order: "desc",
            OrderBy: "id"
        }

        const offsetId = getNewestId()
        console.info("触顶:", offsetId)
        // 查询数据
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: "mitm",
                ...params,
                AfterId: offsetId,  // 用于计算增量的
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                const offsetDeltaData = (rsp?.Data || [])
                if (offsetDeltaData.length <= 0) {
                    // 没有增量数据
                    return
                }
                setLoading(true)
                let offsetData = offsetDeltaData.concat(data);
                if (offsetData.length > MAX_ROW_COUNT) {
                    offsetData = offsetData.splice(0, MAX_ROW_COUNT)
                }
                setData(offsetData);
                scrollTableTo((offsetDeltaData.length + 1) * ROW_HEIGHT)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            })
            .finally(() => setTimeout(() => setLoading(false), 200))
    }), {wait: 600, leading: true, trailing: false}).run
    const scrollUpdateButt = useDebounceFn(useMemoizedFn((tableClientHeight: number) => {
        const paginationProps = {
            Page: 1,
            Limit: OFFSET_STEP,
            Order: "desc",
            OrderBy: "id"
        }

        const offsetId = getOldestId();
        console.info("触底:", offsetId)

        // 查询数据
        ipcRenderer
            .invoke("QueryHTTPFlows", {
                SourceType: "mitm",
                ...params,
                BeforeId: offsetId,  // 用于计算增量的
                Pagination: {...paginationProps}
            })
            .then((rsp: YakQueryHTTPFlowResponse) => {
                const offsetDeltaData = (rsp?.Data || [])
                if (offsetDeltaData.length <= 0) {
                    // 没有增量数据
                    return
                }
                setLoading(true)
                const originDataLength = data.length;
                let offsetData = data.concat(offsetDeltaData);
                let metMax = false
                const originOffsetLength = offsetData.length;
                if (originOffsetLength > MAX_ROW_COUNT) {
                    metMax = true
                    offsetData = offsetData.splice(originOffsetLength - MAX_ROW_COUNT, MAX_ROW_COUNT)
                }
                setData(offsetData);
                setTimeout(() => {
                    if (!metMax) {
                        // 没有丢结果的裁剪问题
                        scrollTableTo((originDataLength + 1) * ROW_HEIGHT - tableClientHeight)
                    } else {
                        // 丢了结果之后的裁剪计算
                        const a = originOffsetLength - offsetDeltaData.length;
                        scrollTableTo((originDataLength + 1 + MAX_ROW_COUNT - originOffsetLength) * ROW_HEIGHT - tableClientHeight)
                    }
                }, 50)
            })
            .catch((e: any) => {
                failed(`query HTTP Flow failed: ${e}`)
            }).finally(() => setTimeout(() => setLoading(false), 60))
    }), {wait: 600, leading: true, trailing: false}).run

    const sortFilter = useMemoizedFn((column: string, type: any) => {
        const keyRelation: any = {
            UpdatedAt: "updated_at",
            BodyLength: "body_length",
            StatusCode: "status_code"
        }

        if (column && type) {
            update(1, OFFSET_LIMIT, type, keyRelation[column])
        } else {
            update(1, OFFSET_LIMIT)
        }
    })

    // 这是用来设置选中坐标的,不需要做防抖
    useEffect(() => {
        if (!getLastSelected() || !getSelected()) {
            return
        }

        const lastSelected = getLastSelected() as HTTPFlow;
        const up = parseInt(`${lastSelected?.Id}`) < parseInt(`${selected?.Id}`)
        // if (up) {
        //     console.info("up")
        // } else {
        //     console.info("down")
        // }
        // console.info(lastSelected.Id, selected?.Id)
        const screenRowCount = Math.floor(getTableContentHeight() / ROW_HEIGHT) - 1

        if (!autoReload) {
            let count = 0;
            const data = getData();
            for (let i = 0; i < data.length; i++) {
                if (data[i].Id != getSelected()?.Id) {
                    count++
                } else {
                    break
                }
            }

            let minCount = count
            if (minCount < 0) {
                minCount = 0
            }
            const viewHeightMin = getScrollY() + tableContentHeight
            const viewHeightMax = getScrollY() + tableContentHeight * 2
            const minHeight = minCount * ROW_HEIGHT;
            const maxHeight = minHeight + tableContentHeight
            const maxHeightBottom = minHeight + tableContentHeight + 3 * ROW_HEIGHT
            // console.info("top: ", minHeight, "maxHeight: ", maxHeight, "maxHeightBottom: ", maxHeightBottom)
            // console.info("viewTop: ", viewHeightMin, "viewButtom: ", viewHeightMax)
            if (maxHeight < viewHeightMin) {
                // 往下滚动
                scrollTableTo(minHeight)
                return
            }
            if (maxHeightBottom > viewHeightMax) {
                // 上滚动
                const offset = minHeight - (screenRowCount - 2) * ROW_HEIGHT;
                // console.info(screenRowCount, minHeight, minHeight - (screenRowCount - 1) * ROW_HEIGHT)
                if (offset > 0) {
                    scrollTableTo(offset)
                }
                return
            }
        }
    }, [selected])

    // 给设置做防抖
    useDebounceEffect(() => {
        props.onSelected && props.onSelected(selected)
    }, [selected], {wait: 400, trailing: true, leading: true})

    useEffect(() => {
        if (autoReload) {
            const id = setInterval(() => {
                update(1, undefined, "desc", undefined, undefined, true)
            }, 1000)
            return () => {
                clearInterval(id)
            }
        }
    }, [autoReload])

    return (
        // <AutoCard bodyStyle={{padding: 0, margin: 0}} bordered={false}>
        <div ref={ref as Ref<any>} tabIndex={-1}
             style={{width: "100%", height: "100%", overflow: "hidden"}}
        >
            <ReactResizeDetector
                onResize={(width, height) => {
                    if (!width || !height) {
                        return
                    }
                    setTableContentHeight(height - 38)
                }}
                handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
            {!props.noHeader && (
                <PageHeader
                    title={"HTTP History"}
                    subTitle={
                        <Space>
                            {"所有相关请求都在这里"}
                            <Button
                                icon={<ReloadOutlined/>}
                                type={"link"}
                                onClick={(e) => {
                                    update(1)
                                }}
                            />
                        </Space>
                    }
                    extra={[
                        <Space>
                            <Form.Item label={"选择 HTTP History 类型"} style={{marginBottom: 0}}>
                                <Select
                                    mode={"multiple"}
                                    value={params.SourceType}
                                    style={{minWidth: 200}}
                                    onChange={(e) => {
                                        setParams({...params, SourceType: e})
                                        setLoading(true)
                                        setTimeout(() => {
                                            update(1, undefined, undefined, undefined, e)
                                        }, 200)
                                    }}
                                >
                                    <Select.Option value={"mitm"}>mitm: 中间人劫持</Select.Option>
                                    <Select.Option value={"fuzzer"}>
                                        fuzzer: 模糊测试分析
                                    </Select.Option>
                                </Select>
                            </Form.Item>
                            <Popconfirm
                                title={"确定想要删除所有记录吗?不可恢复"}
                                onConfirm={(e) => {
                                    ipcRenderer.invoke("delete-http-flows-all")
                                    setLoading(true)
                                    info("正在删除...如自动刷新失败请手动刷新")
                                    setTimeout(() => {
                                        update(1)
                                        if (props.onSelected) props.onSelected(undefined)
                                    }, 400)
                                }}
                            >
                                <Button danger={true}>清除全部历史记录?</Button>
                            </Popconfirm>
                        </Space>
                    ]}
                />
            )}
            <Row style={{margin: "5px 0 5px 5px"}}>
                <Col span={12}>
                    <Space>
                        <span>HTTP History</span>
                        <Button
                            icon={<ReloadOutlined/>}
                            type={"link"}
                            size={"small"}
                            onClick={(e) => {
                                update(1, undefined, "desc")
                            }}
                        />
                        {/* <Space>
                            自动刷新:
                            <Switch size={"small"} checked={autoReload} onChange={setAutoReload}/>
                        </Space> */}
                        <Input.Search
                            placeholder={"URL关键字"}
                            enterButton={true}
                            size={"small"}
                            style={{width: 170}}
                            value={params.SearchURL}
                            onChange={(e) => {
                                setParams({...params, SearchURL: e.target.value})
                            }}
                            onSearch={(v) => {
                                update(1)
                            }}
                        />
                        {props.noHeader && (
                            <Popconfirm
                                title={"确定想要删除所有记录吗?不可恢复"}
                                onConfirm={(e) => {
                                    ipcRenderer.invoke("delete-http-flows-all")
                                    setLoading(true)
                                    info("正在删除...如自动刷新失败请手动刷新")
                                    setCompareLeft({content: '', language: 'http'})
                                    setCompareRight({content: '', language: 'http'})
                                    setCompareState(0)
                                    setTimeout(() => {
                                        update(1)
                                        if (props.onSelected) props.onSelected(undefined)
                                    }, 400)
                                }}
                            >
                                <Button danger={true} size={"small"}>
                                    删除历史记录
                                </Button>
                            </Popconfirm>
                        )}
                        {/*{autoReload && <Tag color={"green"}>自动刷新中...</Tag>}*/}
                    </Space>
                </Col>
                <Col span={12} style={{textAlign: "right"}}>
                    <Tag>{total} Records</Tag>
                </Col>
            </Row>
            <TableResizableColumn
                tableRef={tableRef}
                virtualized={true}
                className={"httpFlowTable"}
                loading={loading}
                columns={[
                    {
                        dataKey: "Id",
                        width: 80,
                        headRender: () => "序号",
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return `${rowData[dataKey] <= 0 ? "..." : rowData[dataKey]}`
                        }
                    },
                    {
                        dataKey: "Method",
                        width: 70,
                        headRender: (params1: any) => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    方法
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索方法"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"Methods"}
                                                    autoCompletions={["GET", "POST", "HEAD"]}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.Methods ? undefined : "gray",
                                            }}
                                            type={!!params.Methods ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            // return (
                            //     <Tag color={"geekblue"} style={{marginRight: 20}}>
                            //         {rowData[dataKey]}
                            //     </Tag>
                            // )
                            return rowData[dataKey]
                        }
                    },
                    {
                        dataKey: "StatusCode",
                        width: 100,
                        sortable: true,
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "inline-flex"}}
                                >
                                    状态码
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索状态码"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"StatusCode"}
                                                    autoCompletions={[
                                                        "200",
                                                        "300-305",
                                                        "400-404",
                                                        "500-502",
                                                        "200-299",
                                                        "300-399",
                                                        "400-499"
                                                    ]}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.StatusCode ? undefined : "gray",
                                            }}
                                            type={!!params.StatusCode ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <div style={{color: StatusCodeToColor(rowData[dataKey])}}>
                                    {rowData[dataKey] === 0 ? "" : rowData[dataKey]}
                                </div>
                            )
                        }
                    },
                    {
                        dataKey: "Url",
                        resizable: true,
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    URL
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"搜索URL关键字"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"SearchURL"}
                                                    pureString={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.SearchURL ? undefined : "gray",
                                            }}
                                            type={!!params.SearchURL ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            if (rowData.IsPlaceholder) {
                                return <div style={{color: "#888585"}}>{"滚轮上滑刷新..."}</div>
                            }
                            return (
                                <div style={{width: "100%", display: "flex"}}>
                                    <div className='resize-ellipsis' title={rowData.Url}>
                                        {!params.SearchURL ? (
                                            rowData.Url
                                        ) : (
                                            rowData.Url
                                        )}
                                    </div>
                                </div>
                            )
                        },
                        width: 600
                    },
                    {
                        dataKey: "HtmlTitle",
                        width: 120,
                        resizable: true,
                        headRender: () => {
                            return "Title"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? rowData[dataKey] : ""
                        }
                    },
                    {
                        dataKey: "Tags",
                        width: 120,
                        resizable: true,
                        headRender: () => {
                            return "Tags"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? (
                                `${rowData[dataKey]}`.split("|").filter(i => !i.startsWith("YAKIT_COLOR_")).join(", ")
                            ) : ""
                        }
                    },
                    {
                        dataKey: "IPAddress",
                        width: 140, resizable: true,
                        headRender: () => {
                            return "IP"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return rowData[dataKey] ? rowData[dataKey] : ""
                        }
                    },
                    {
                        dataKey: "BodyLength",
                        width: 120,
                        sortable: true,
                        headRender: () => {
                            return (
                                <div style={{display: "inline-block", position: "relative"}}>
                                    响应长度
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"是否存在Body?"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"HaveBody"}
                                                    pureBool={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.HaveBody ? undefined : "gray",
                                            }}
                                            type={!!params.HaveBody ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <div style={{width: 100}}>
                                    {/* 1M 以上的话,是红色*/}
                                    {rowData.BodyLength !== -1 ?
                                        (<div style={{color: rowData.BodyLength > 1000000 ? "red" : undefined}}>
                                            {rowData.BodySizeVerbose
                                                ? rowData.BodySizeVerbose
                                                : rowData.BodyLength}
                                        </div>)
                                        :
                                        (<div></div>)
                                    }
                                </div>
                            )
                        }
                    },
                    // {
                    //     dataKey: "UrlLength",
                    //     width: 90,
                    //     headRender: () => {
                    //         return "URL 长度"
                    //     },
                    //     cellRender: ({rowData, dataKey, ...props}: any) => {
                    //         const len = (rowData.Url || "").length
                    //         return len > 0 ? <div>{len}</div> : "-"
                    //     }
                    // },
                    {
                        dataKey: "GetParamsTotal",
                        width: 65,
                        align: "center",
                        headRender: () => {
                            return (
                                <div
                                    style={{display: "flex", justifyContent: "space-between"}}
                                >
                                    参数
                                    <Popover
                                        placement='bottom'
                                        trigger='click'
                                        content={
                                            params &&
                                            setParams && (
                                                <HTTLFlowFilterDropdownForms
                                                    label={"过滤是否存在基础参数"}
                                                    params={params}
                                                    setParams={setParams}
                                                    filterName={"HaveCommonParams"}
                                                    pureBool={true}
                                                    submitFilter={() => update(1)}
                                                />
                                            )
                                        }
                                    >
                                        <Button
                                            style={{
                                                paddingLeft: 4, paddingRight: 4, marginLeft: 4,
                                                color: !!params.HaveCommonParams ? undefined : "gray",
                                            }}
                                            type={!!params.HaveCommonParams ? "primary" : "link"} size={"small"}
                                            icon={<SearchOutlined/>}
                                        />
                                    </Popover>
                                </div>
                            )
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return (
                                <Space>
                                    {(rowData.GetParamsTotal > 0 ||
                                        rowData.PostParamsTotal > 0) && <CheckOutlined/>}
                                </Space>
                            )
                        }
                    },
                    {
                        dataKey: "ContentType",
                        resizable: true, width: 80,
                        headRender: () => {
                            return "响应类型"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            let contentTypeFixed = rowData.ContentType.split(";")
                                .map((el: any) => el.trim())
                                .filter((i: any) => !i.startsWith("charset"))
                                .join(",") || "-"
                            if (contentTypeFixed.includes("/")) {
                                const contentTypeFixedNew = contentTypeFixed.split("/").pop()
                                if (!!contentTypeFixedNew) {
                                    contentTypeFixed = contentTypeFixedNew
                                }
                            }
                            return (
                                <div>
                                    {contentTypeFixed === "null" ? "" : contentTypeFixed}
                                </div>
                            )
                        }
                    },
                    {
                        dataKey: "UpdatedAt",
                        sortable: true,
                        width: 110,
                        headRender: () => {
                            return "请求时间"
                        },
                        cellRender: ({rowData, dataKey, ...props}: any) => {
                            return <Tooltip
                                title={rowData[dataKey] === 0 ? "" : formatTimestamp(rowData[dataKey])}
                            >
                                {rowData[dataKey] === 0 ? "" : formatTime(rowData[dataKey])}
                            </Tooltip>
                        }
                    },
                    {
                        dataKey: "operate",
                        width: 90,
                        headRender: () => "操作",
                        cellRender: ({rowData}: any) => {
                            if (!rowData.Hash) return <></>
                            return (
                                <a
                                    onClick={(e) => {
                                        let m = showDrawer({
                                            width: "80%",
                                            content: onExpandHTTPFlow(
                                                rowData,
                                                () => m.destroy()
                                            )
                                        })
                                    }}
                                >
                                    详情
                                </a>
                            )
                        }
                    }
                ]}
                data={autoReload ? data : [TableFirstLinePlaceholder].concat(data)}
                autoHeight={tableContentHeight <= 0}
                height={tableContentHeight}
                sortFilter={sortFilter}
                renderRow={(children: ReactNode, rowData: any) => {
                    if (rowData)
                        return (
                            <div
                                id='http-flow-row'
                                ref={(node) => {
                                    const color =
                                        rowData.Hash === selected?.Hash ?
                                            "rgba(78, 164, 255, 0.4)" :
                                            rowData.Tags.indexOf("YAKIT_COLOR") > -1 ?
                                                TableRowColor(rowData.Tags.split("|").pop().split('_').pop().toUpperCase()) :
                                                "#ffffff"
                                    if (node) {
                                        if (color) node.style.setProperty("background-color", color, "important")
                                        else node.style.setProperty("background-color", "#ffffff")
                                    }
                                }}
                                style={{height: "100%"}}
                            >
                                {children}
                            </div>
                        )
                    return children
                }}
                onRowContextMenu={(rowData: HTTPFlow | any, event: React.MouseEvent) => {
                    if (rowData) {
                        setSelected(rowData);
                    }
                    showByCursorMenu(
                        {
                            content: [
                                {
                                    title: '发送到 Web Fuzzer',
                                    onClick: () => {
                                        ipcRenderer.invoke("send-to-tab", {
                                            type: "fuzzer",
                                            data: {
                                                isHttps: rowData.IsHTTPS,
                                                request: new Buffer(rowData.Request).toString("utf8")
                                            }
                                        })
                                    }
                                },
                                {
                                    title: '发送到 数据包扫描',
                                    onClick: () => {
                                        ipcRenderer
                                            .invoke("GetHTTPFlowByHash", {Hash: rowData.Hash})
                                            .then((i: HTTPFlow) => {
                                                ipcRenderer.invoke("send-to-packet-hack", {
                                                    request: i.Request,
                                                    ishttps: i.IsHTTPS,
                                                    response: i.Response
                                                })
                                            })
                                            .catch((e: any) => {
                                                failed(`Query Response failed: ${e}`)
                                            })
                                    }
                                },
                                {
                                    title: '复制 URL',
                                    onClick: () => {
                                        callCopyToClipboard(rowData.Url)
                                    },
                                },
                                {
                                    title: '复制为 Yak PoC 模版', onClick: () => {
                                    },
                                    subMenuItems: [
                                        {
                                            title: "数据包 PoC 模版", onClick: () => {
                                                const flow = rowData as HTTPFlow;
                                                if (!flow) return;
                                                generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
                                                    callCopyToClipboard(code)
                                                }, RequestToYakCodeTemplate.Ordinary)
                                            }
                                        },
                                        {
                                            title: "批量检测 PoC 模版", onClick: () => {
                                                const flow = rowData as HTTPFlow;
                                                if (!flow) return;
                                                generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
                                                    callCopyToClipboard(code)
                                                }, RequestToYakCodeTemplate.Batch)
                                            }
                                        },
                                    ]
                                },
                                {
                                    title: '标注颜色',
                                    subMenuItems: availableColors.map(i => {
                                        return {
                                            title: i.title,
                                            render: i.render,
                                            onClick: () => {
                                                const flow = rowData as HTTPFlow
                                                if (!flow) {
                                                    return
                                                }

                                                const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
                                                existedTags.push(`YAKIT_COLOR_${i.color.toUpperCase()}`)
                                                ipcRenderer.invoke("SetTagForHTTPFlow", {
                                                    Id: flow.Id, Hash: flow.Hash,
                                                    Tags: existedTags,
                                                }).then(() => {
                                                    info(`设置 HTTPFlow 颜色成功`)
                                                    if (!autoReload) {
                                                        setData(data.map(item => {
                                                            if (item.Hash === flow.Hash) {
                                                                item.Tags = `YAKIT_COLOR_${i.color.toUpperCase()}`
                                                                return item
                                                            }
                                                            return item
                                                        }))
                                                    }
                                                })
                                            }
                                        }
                                    }),
                                    onClick: () => {
                                    }
                                },
                                {
                                    title: '移除颜色',
                                    onClick: () => {
                                        const flow = rowData as HTTPFlow
                                        if (!flow) return

                                        const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
                                        existedTags.pop()
                                        ipcRenderer.invoke("SetTagForHTTPFlow", {
                                            Id: flow.Id, Hash: flow.Hash,
                                            Tags: existedTags,
                                        }).then(() => {
                                            info(`清除 HTTPFlow 颜色成功`)
                                            if (!autoReload) {
                                                setData(data.map(item => {
                                                    if (item.Hash === flow.Hash) {
                                                        item.Tags = ""
                                                        return item
                                                    }
                                                    return item
                                                }))
                                            }
                                        })
                                        return
                                    },
                                },
                                {
                                    title: "发送到对比器", onClick: () => {
                                    },
                                    subMenuItems: [
                                        {
                                            title: '发送到对比器左侧',
                                            onClick: () => {
                                                setCompareLeft({
                                                    content: new Buffer(rowData.Request).toString("utf8"),
                                                    language: 'http'
                                                })
                                            },
                                            disabled: [false, true, false][compareState]
                                        },
                                        {
                                            title: '发送到对比器右侧',
                                            onClick: () => {
                                                setCompareRight({
                                                    content: new Buffer(rowData.Request).toString("utf8"),
                                                    language: 'http'
                                                })
                                            },
                                            disabled: [false, false, true][compareState]
                                        }
                                    ]
                                },
                            ]
                        },
                        event.clientX,
                        event.clientY
                    )
                }}
                onRowClick={(rowDate: any) => {
                    if (!rowDate.Hash) return
                    if (rowDate.Hash !== selected?.Hash) {
                        setSelected(rowDate)
                    } else {
                        // setSelected(undefined)
                    }
                }}
                onScroll={(scrollX, scrollY) => {
                    setScrollY(scrollY)
                    // 防止无数据触发加载
                    if (data.length === 0 && !getAutoReload()) {
                        setAutoReload(true)
                        return
                    }

                    // 根据页面展示内容决定是否自动刷新
                    let contextHeight = (data.length + 1) * ROW_HEIGHT // +1 是要把表 title 算进去
                    let offsetY = scrollY + tableContentHeight;
                    if (contextHeight < tableContentHeight) {
                        setAutoReload(true)
                        return
                    }
                    setAutoReload(false)

                    // 向下刷新数据
                    if (contextHeight <= offsetY) {
                        setAutoReload(false)
                        scrollUpdateButt(tableContentHeight)
                        return
                    }

                    // 锁住滚轮
                    if (getLockedScroll() > 0 && getLockedScroll() >= scrollY) {
                        if (scrollY === getLockedScroll()) {
                            return
                        }
                        // scrollTableTo(getLockedScroll())
                        return
                    }
                    const toTop = scrollY <= 0;
                    if (toTop) {
                        lockScrollTimeout(ROW_HEIGHT, 600)
                        scrollUpdateTop()
                    }
                }}
            />
        </div>
        // </AutoCard>
    )
}
Example #25
Source File: DataTable.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function DataTable<T>(props: IDataTableProps<T>, ref: any) {
  const prefixCls = `${props.prefixCls || 'dantd'}-data-table`;
  const filterType = props.filterType || 'half';
  const isUrlLoad = useRef<string>();
  const t = intlZhMap;
  const {
    fetchOptions = {},
    searchParams,
    showFilter = true,
    showReloadBtn = true,
    showQueryOptionBtns = true,
    showQueryCollapseButton = true,
    isQuerySearchOnChange = true,
    showBodyBg = false,
    queryFormProps = {},
    url,
    queryMode = 'default',
    reloadBtnPos = 'right',
    searchPos = 'full',
    reloadBtnType = 'icon',
    queryFormColumns,
    pageParams,
    antProps = {
      rowKey: 'id',
    },
  } = props;

  const tableClassName = classNames(prefixCls, props.className);
  const tableContentClassName = classNames({
    [`${prefixCls}-table-content`]: true,
    [`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
  });
  const tableBodyCls = classNames({
    [`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
    [`${prefixCls}-body-compact`]: queryMode === 'compact',
  });

  const tableQueryCls = classNames({
    [`${prefixCls}-query`]: !!queryFormColumns,
    [`${prefixCls}-query-compact`]: queryMode === 'compact',
  });

  // hooks
  const columnsMap = useMemo(() => {
    const result = {} as any;
    if (props.columns && props.columns.length > 0) {
      props.columns.forEach((columnItem) => {
        if (columnItem.dataIndex) {
          result[columnItem.dataIndex as string] = {
            ...columnItem,
            value: null,
          };
        }
      });
    }
    return result;
  }, [props.columns]);

  const [queryFormValues, setQueryFormValues] = useState<any>({});
  const [isQueryInit, setQueryInit] = useState(false);
  const isPageChangeNoSearch = useRef<boolean>(false);
  const filterSearchInputRef = useRef({}) as any;
  const clearFiltersRef = useRef({}) as any;
  const [dataSource, setDataSource] = useState([]);
  const [loading, setLoading] = useState(false);
  const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
  const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
  const [searchQuery, setSearchQuery] = useState();
  const rowSelection = props.rowSelection;

  const selectRowNum = rowSelection
    ? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
    : -1;

  const sorterNames = {
    ascend: t('table.sort.ascend'),
    descend: t('table.sort.descend'),
  };

  const showTotal = (total: number) => {
    return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
  };
  const showSearch = !!searchParams;
  // 是否展示长条搜索区
  const showFullSearch = showSearch && searchPos === 'full';
  // 搜索按钮展示的位置
  const showReloadBtn2SearchRight =
    searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
  const showReloadBtn2FilterRight =
    (!showSearch || searchPos !== 'full') &&
    showReloadBtn &&
    reloadBtnPos === 'right';
  const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';
  const searchPlaceholder = searchParams
    ? searchParams.placeholder
    : t('table.search.placeholder');
  const [paginationState, setPagination] = useState({
    current: 1,
    pageSize: 10,
    total: 0,
    ...pageParams,
  });
  const [columnsState, columnsDispatch] = useReducer(
    columnsReducer,
    columnsMap,
  );
  const debouncedSearchQuery = useDebounce(searchQuery, 300);
  const debouncedQueryFormValues = useDebounce(
    JSON.stringify(queryFormValues),
    300,
  );
  const pageStr = JSON.stringify({
    pageSize: paginationState.pageSize,
    current: paginationState.current,
  });
  const tableStateStr = JSON.stringify({
    ...columnsState,
    ...sorterState,
  });
  // TODO 支持监听 customQuery
  useEffect(() => {
    if (url && isUrlLoad.current !== url) {
      let fetchParams = getAllFetchParams();
      fetchData(fetchParams);

      setTimeout(() => {
        isUrlLoad.current = url;
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url]);

  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchParams[paginationState.curPageName] = 1;
    if (searchParams && debouncedSearchQuery && debouncedSearchQuery.trim()) {
      fetchParams[searchParams.apiName] = debouncedSearchQuery.trim();
    }

    if (props.onSearch) {
      // 使用用户自定义的search回调
      props.onSearch(
        debouncedSearchQuery
          ? debouncedSearchQuery.trim()
          : debouncedSearchQuery,
      );
    }

    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchQuery]);

  // 监听queryform
  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }

    if (
      !queryFormColumns ||
      (queryFormValues && Object.keys(queryFormValues).length === 0)
    ) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchParams[paginationState.curPageName] = 1;

    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedQueryFormValues]);

  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tableStateStr]);

  useEffect(() => {
    if (!isUrlLoad.current || !paginationState.pageSize) {
      return;
    }

    if (isPageChangeNoSearch.current) {
      isPageChangeNoSearch.current = false;
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pageStr]);

  async function fetchData(params?: any) {
    let fetchUrl = url;
    let customFetchParams = null as any;
    if (fetchUrl.indexOf('?') !== -1) {
      const fetchArr = fetchUrl.split('?');
      fetchUrl = fetchArr[0];
      customFetchParams = queryString.parse(fetchArr[1], {
        arrayFormat: 'comma',
      });
    }

    let fetchParams = {
      [pageParams.curPageName]: params.current,
      [pageParams.pageSizeName]: params.pageSize,
    };

    if (customFetchParams) {
      fetchParams = {
        ...customFetchParams,
        ...fetchParams,
      };
    }

    // api起始页从0开始,参数需要减1
    if (pageParams.startPage === 0) {
      fetchParams[pageParams.curPageName] -= 1;
    }

    props.columns.forEach((columnItem) => {
      if (columnItem.dataIndex && params[columnItem.dataIndex]) {
        fetchParams[columnItem.dataIndex] = params[columnItem.dataIndex];
      }
      if (columnItem.apiSearch && params[columnItem.apiSearch.name]) {
        fetchParams[columnItem.apiSearch.name] =
          params[columnItem.apiSearch.name];
      }
      if (columnItem.apiFilter && params[columnItem.apiFilter.name]) {
        fetchParams[columnItem.apiFilter.name] =
          params[columnItem.apiFilter.name];
      }
      if (columnItem.apiSorter && params[columnItem.apiSorter.name]) {
        fetchParams[columnItem.apiSorter.name] =
          params[columnItem.apiSorter.name];
      }
    });

    if (searchParams && params[searchParams.apiName]) {
      fetchParams[searchParams.apiName] = params[searchParams.apiName];
    }

    if (
      !!queryFormColumns &&
      queryFormValues &&
      Object.keys(queryFormValues).length > 0
    ) {
      fetchParams = {
        ...queryFormValues,
        ...fetchParams,
      };
    }

    if (props.customQueryCallback) {
      fetchParams = props.customQueryCallback({
        ...fetchParams,
      });
    }

    fetchUrl = `${fetchUrl}?${queryString.stringify(fetchParams, {
      arrayFormat: 'comma',
    })}`;

    if (props.customFetchUrlCallback) {
      fetchUrl = props.customFetchUrlCallback(fetchUrl);
    }

    setLoading(true);
    let transportOptions = {
      ...fetchOptions,
    };
    if (fetchOptions.method && fetchOptions.method.toLowerCase() === 'post') {
      transportOptions.data = fetchParams;
    }
    const res = request(fetchUrl, transportOptions);
    props.rowSelection &&
      props.rowSelection.onChange &&
      props.rowSelection.onChange([]);
    if ((res.status < 200 || res.status >= 300) && props.apiErrorCallback) {
      props.apiErrorCallback(res);
      setLoading(false);
    } else {
      res
        .then(async (res) => {
          let callbackData = res;
          if (props.apiCallback) {
            callbackData = props.apiCallback(res);
          }
          if (props.apiAsyncCallback) {
            callbackData = await props.apiAsyncCallback(res);
          }
          setDataSource(callbackData.data);
          const tmpPagination = {
            ...paginationState,
            total: callbackData.total,
          };
          setPagination(tmpPagination);
          setLoading(false);
        })
        .catch(() => {
          if (props.apiErrorCallback) {
            props.apiErrorCallback(res);
          }
          setLoading(false);
        });
    }
  }

  const handleTableChange = (pagination: any, filters: any, sorter: any) => {
    if (!isUrlLoad.current) {
      return;
    }

    if (Object.keys(sorter).length > 0 && sorter.column) {
      sorterDispatch({
        type: 'update',
        value: {
          ...sorter,
          column: {
            apiSorter: sorter.column.apiSorter,
          },
        },
      });
    } else {
      sorterDispatch({
        type: 'update',
        value: {},
      });
    }

    Object.entries(filters).forEach(([filterKey, filterValue]) => {
      if (columnsMap[filterKey].filters) {
        columnsDispatch({
          type: 'update',
          dataIndex: filterKey,
          updateType: 'filter',
          value: filterValue,
        });
      }
    });

    isPageChangeNoSearch.current = false;

    setPagination(pagination);

    checkRightHeader(filters, sorter);
  };

  const handleFilterSearch = useCallback(
    (
      selectedKeys: React.ReactText[] | undefined,
      confirm: (() => void) | undefined,
      dataIndex: string | number,
    ) => {
      if (confirm) {
        confirm();
      }
      if (selectedKeys && dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
          updateType: 'search',
          value: selectedKeys[0],
        });
      }
    },
    [],
  );

  const handleFilterSearchReset = useCallback(
    (
      clearFilters: ((selectedKeys: string[]) => void) | undefined,
      dataIndex: string | number | undefined,
    ) => {
      if (clearFilters) {
        clearFilters([]);
      }
      if (dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
        });
      }
    },
    [],
  );

  const handleFilterClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
      value: [],
    });

    const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
      if (filterItem.dataIndex === columnValue.dataIndex) {
        return {
          [filterItem.dataIndex]: [],
        };
      }
      return {
        [filterItem.dataIndex]: filterItem.value || [],
      };
    });

    checkRightHeader(tmpFilters, sorterState, columnsState);
  };

  const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
    });

    if (
      clearFiltersRef.current[columnValue.dataIndex] &&
      clearFiltersRef.current[columnValue.dataIndex].clearFilters
    ) {
      clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
    }

    checkRightHeader(null, sorterState, {
      ...columnsState,
      [columnValue.dataIndex]: {
        title: columnsState[columnValue.dataIndex].title,
        dataIndex: columnsState[columnValue.dataIndex].dataIndex,
        value: null,
      },
    });
  };

  const handleClearAll = () => {
    sorterDispatch({ type: 'clear' });
    columnsDispatch({ type: 'clear', initialState: columnsMap });
    setShowRightHeader(false);

    setTimeout(() => {
      Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
        if (filterItem && filterItem.clearFilters) {
          filterItem.clearFilters([]);
        }
      });
    });
  };

  const handleSortClear = () => {
    sorterDispatch({ type: 'clear' });
    checkRightHeader(null, {});
  };

  const handleQueryFormInit = (data) => {
    if (!isQueryInit) {
      setQueryFormValues(data);
      setQueryInit(true);
    }
  };

  const handleQueryFormChange = (data) => {
    isPageChangeNoSearch.current = true;
    setPagination({
      ...paginationState,
      current: 1,
    });
    setQueryFormValues(data);
  };

  const handleQueryFormReset = (data) => {
    isPageChangeNoSearch.current = true;
    setPagination({
      ...paginationState,
      current: 1,
    });
    setQueryFormValues(data);
  };

  const handleQuerySearch = (data) => {
    if (paginationState.current > 1) {
      // 这次修改分页参数
      isPageChangeNoSearch.current = true;

      setPagination({
        ...paginationState,
        current: 1,
      });
      setQueryFormValues(data);
    } else {
      setQueryFormValues(data);
      // let fetchParams = getAllFetchParams();
      // fetchData(fetchParams);
    }
  };

  const getAllFetchParams = () => {
    let fetchParams = {
      ...paginationState,
    };

    // columns sort
    if (sorterState && sorterState.order) {
      fetchParams[sorterState.column.apiSorter.name] =
        sorterState.column.apiSorter[sorterState.order];
    }

    Object.values(columnsState).forEach((column: any) => {
      const filterKey = column.dataIndex;
      const filterVal = column.value;
      // columns filter
      if (column.apiFilter && filterVal) {
        let filterName = columnsMap[filterKey].apiFilter
          ? columnsMap[filterKey].apiFilter.name
          : filterKey;
        fetchParams[filterName] = filterVal;
        if (column.apiFilter.callback) {
          fetchParams[filterName] =
            columnsMap[filterKey].apiFilter.callback(filterVal);
        }
      }
      // columns search
      if (column.apiSearch && filterVal) {
        const filterName = columnsMap[filterKey].apiSearch
          ? columnsMap[filterKey].apiSearch.name
          : filterKey;
        fetchParams[filterName] = Array.isArray(filterVal)
          ? filterVal[0]
          : filterVal;
      }
    });

    // query search
    if (searchParams && searchQuery) {
      fetchParams[searchParams.apiName] = searchQuery;
    }

    // queryform
    if (
      queryFormColumns ||
      (queryFormValues && Object.keys(queryFormValues).length > 0)
    ) {
      fetchParams = {
        ...queryFormValues,
        ...fetchParams,
      };
    }

    return fetchParams;
  };

  const handleReload = () => {
    // pages
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
  };

  const handleSearchChange = (e) => {
    const query = e.target.value;
    setSearchQuery(query);
    // 使用组件默认的search回调
    setPagination({
      ...paginationState,
      current: 1,
    });
  };

  const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
    const checkSorter = sorter || sorterState;
    const checkFilters = filters || columnsState;
    const checkSearch = search || columnsState;
    const isSearch = Object.values(checkSearch).some((columnItem: any) => {
      return !!(columnItem.type === 'search' && columnItem.value);
    });
    let isFilter = false;
    if (filters) {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return columnItem && columnItem.length > 0;
      });
    } else {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return !!(
          columnItem.type === 'filter' &&
          columnItem.value &&
          columnItem.value.length > 0
        );
      });
    }
    const isSorter = !!checkSorter.column;

    const res = isSearch || isFilter || isSorter;
    setShowRightHeader(res);
  };

  const isSearch = Object.values(columnsState).some((columnItem: any) => {
    return !!(columnItem.type === 'search' && columnItem.value);
  });

  const isFilter = Object.values(columnsState).some((columnItem: any) => {
    return !!(
      columnItem.type === 'filter' &&
      columnItem.value &&
      columnItem.value.length > 0
    );
  });

  useImperativeHandle(ref, () => ({
    handleReload,
    goToFirstPage: () => {
      setPagination({
        ...paginationState,
        current: 1,
      });
    },
  }));

  const renderColumns = () => {
    return props.columns.map((columnItem) => {
      const currentItem = _.cloneDeep(columnItem);

      // filter
      if (currentItem.apiFilter) {
        currentItem.filterIcon = () => <FilterOutlined />;
        currentItem.filteredValue =
          columnsState[columnItem.dataIndex as string].value;
      }

      // // sort
      if (currentItem.apiSorter) {
        currentItem.sortOrder =
          sorterState.columnKey === currentItem.dataIndex && sorterState.order;
      }

      // Search
      if (currentItem.apiSearch) {
        currentItem.filterIcon = () => <SearchOutlined />;

        currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
          if (
            visible &&
            filterSearchInputRef.current &&
            filterSearchInputRef.current.select
          ) {
            setTimeout(() => {
              if (
                filterSearchInputRef.current &&
                filterSearchInputRef.current.select
              ) {
                filterSearchInputRef.current.select();
              }
              return null;
            });
          }
        };

        currentItem.filterDropdown = ({
          setSelectedKeys,
          selectedKeys,
          confirm,
          clearFilters,
        }) => {
          clearFiltersRef.current[currentItem.dataIndex as string] = {
            clearFilters,
          };

          return (
            <div style={{ padding: 8 }}>
              <Input
                data-testid='filter-search-input'
                autoFocus={true}
                ref={(node) => {
                  filterSearchInputRef.current = node;
                }}
                placeholder={t('table.filter.search.placeholder')}
                value={selectedKeys && selectedKeys[0]}
                onChange={(e) => {
                  if (setSelectedKeys) {
                    return setSelectedKeys(
                      e.target.value ? [e.target.value] : [],
                    );
                  }
                  return [];
                }}
                onPressEnter={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                style={{ width: 188, marginBottom: 8, display: 'block' }}
              />
              <Button
                type='primary'
                onClick={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                icon='search'
                size='small'
                data-testid='search-btn-ok'
                style={{ width: 90, marginRight: 8 }}
              >
                {t('table.filter.search.btn.ok')}
              </Button>
              <Button
                onClick={() =>
                  handleFilterSearchReset(clearFilters, currentItem.dataIndex)
                }
                size='small'
                style={{ width: 90 }}
              >
                {t('table.filter.search.btn.cancel')}
              </Button>
            </div>
          );
        };

        let searchWords: any[] = [];

        const tmpStateValue =
          columnsState[currentItem.dataIndex as string].value;
        if (typeof tmpStateValue === 'string') {
          searchWords = [tmpStateValue];
        }

        if (
          Array.isArray(tmpStateValue) &&
          typeof tmpStateValue[0] === 'string'
        ) {
          searchWords = [tmpStateValue[0]];
        }

        currentItem.onFilter = (value, record: any) => {
          return record[currentItem.dataIndex as string]
            .toString()
            .toLowerCase()
            .includes(value.toLowerCase());
        };

        if (!currentItem.render) {
          currentItem.render = (value, row, index) => {
            if (currentItem.searchRender) {
              return currentItem.searchRender(
                value,
                row,
                index,
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />,
              );
            } else {
              return (
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />
              );
            }
          };
        }
      }

      return currentItem;
    });
  };

  const renderRightHeader = (params) => {
    if (!showFilter) {
      return null;
    }
    return (
      <>
        <div>
          <b
            style={{
              marginTop: 4,
              display: 'inline-block',
            }}
          >
            {t('table.filter.header.title')}
          </b>
        </div>
        {(isSearch || isFilter) &&
          Object.values(columnsState as IColumnsReducerState).map(
            (columnValue) => {
              if (columnValue.type === 'search' && columnValue.value) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterSearchClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }

              if (
                columnValue.type === 'filter' &&
                columnValue.value &&
                columnValue.value.length > 0
              ) {
                const mappings = columnsMap[columnValue.dataIndex]
                  ? columnsMap[columnValue.dataIndex].filters
                  : [];
                const mapping = mappings.reduce((acc, cur) => {
                  return {
                    ...acc,
                    [cur.value]: [cur.text],
                  };
                }, {});
                const newValues = columnValue.value.map((valItem) => {
                  return mapping && mapping[valItem]
                    ? mapping[valItem]
                    : valItem;
                });
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {newValues.join(',')}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {newValues.join(',')}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }
              return null;
            },
          )}
        {sorterState.columnKey && sorterState.column && (
          <div className={`${params.headerClsPrefix}-item`}>
            <Tooltip
              title={
                <span>
                  {columnsMap[sorterState.columnKey].title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
              }
            >
              <Button
                size='small'
                className='table-header-item-btn'
                onClick={handleSortClear}
              >
                <span className='table-header-item-btn-content'>
                  {columnsMap[sorterState.columnKey].title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
                <CloseOutlined />
              </Button>
            </Tooltip>
          </div>
        )}
        <div className={`${params.headerClsPrefix}-item`}>
          <Button
            size='small'
            type='link'
            data-testid='btn-clearall'
            onClick={handleClearAll}
          >
            {t('table.filter.header.btn.clear')}
          </Button>
        </div>
      </>
    );
  };

  const renderSearch = () => {
    return (
      <Tooltip placement='topLeft' title={searchPlaceholder}>
        <Input
          data-testid='search-input'
          prefix={
            reloadBtnPos === 'right' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          suffix={
            reloadBtnPos === 'left' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          allowClear={true}
          value={searchQuery}
          onChange={handleSearchChange}
          placeholder={searchPlaceholder}
          // suffix={<Icon onClick={handleSearchClick} type="search" />}
        />
      </Tooltip>
    );
  };

  const renderReloadBtn = () => {
    if (reloadBtnType === 'icon') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-icon`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <ReloadOutlined
          onClick={handleReload}
          spin={loading}
          className={reloadBtnCls}
          type='reload'
        />
      );
    }

    if (reloadBtnType === 'btn') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-btn`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <Button
          className={reloadBtnCls}
          loading={loading}
          icon={<ReloadOutlined />}
          data-testid='reload-btn'
          onClick={handleReload}
        />
      );
    }
  };

  return (
    <ConfigProvider {...props.antConfig}>
      <div className={tableClassName} style={props.style}>
        {queryFormColumns && (
          <div className={tableQueryCls}>
            <QueryForm
              onChange={
                isQuerySearchOnChange
                  ? handleQueryFormChange
                  : handleQueryFormInit
              }
              onReset={handleQueryFormReset}
              onSearch={handleQuerySearch}
              columns={queryFormColumns}
              showOptionBtns={showQueryOptionBtns}
              showCollapseButton={showQueryCollapseButton}
              {...queryFormProps}
            />
          </div>
        )}
        <div className={tableBodyCls}>
          {!!props.tableTitle && <h3> {props.tableTitle} </h3>}
          {props.customHeader && (
            <div
              data-testid='custom-header'
              className={`${prefixCls}-header-custom`}
            >
              {props.customHeader}
            </div>
          )}
          {showFullSearch && (
            <Row type='flex' className={`${prefixCls}-header-search`}>
              {showSearch ? renderSearch() : <div></div>}
              {showReloadBtn2SearchRight && renderReloadBtn()}
            </Row>
          )}
          {/* 自定义格式 */}
          {filterType === 'flex' && (
            <div
              style={{
                display: 'flex',
                justifyContent: 'flex-start',
                marginBottom: '10px',
              }}
            >
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              {props.leftHeader}
            </div>
          )}
          {filterType === 'none' && searchPos !== 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
              >
                {props.leftHeader}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'none' && showSearch && searchPos === 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={14}
              >
                {showReloadBtn2FilterLeft && renderReloadBtn()}
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={10}
              >
                {renderSearch()}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'line' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-line`}
                span={24}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-line`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'half' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={12}
              >
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={12}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-right`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          <div className={`${prefixCls}-table-wrapper`}>
            <div className={tableContentClassName}>
              <Table
                loading={loading}
                columns={renderColumns()}
                dataSource={dataSource}
                bordered={false}
                rowSelection={rowSelection}
                onChange={handleTableChange}
                pagination={{
                  showTotal: showTotal,
                  showSizeChanger: true,
                  showQuickJumper: true,
                  pageSizeOptions: pageSizeOptions,
                  ...props.pagination,
                  ...paginationState,
                }}
                {...antProps}
              />
              {rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
                <div className={`${prefixCls}-table-content-select-num`}>
                  {t('table.select.num')}
                  {selectRowNum}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </ConfigProvider>
  );
}
Example #26
Source File: index.tsx    From Aragorn with MIT License 4 votes vote down vote up
FileManage = () => {
  const {
    state: {
      uploaderProfiles,
      configuration: { defaultUploaderProfileId }
    }
  } = useAppContext();

  const [windowHeight, setWindowHeight] = useState(window.innerHeight);

  useEffect(() => {
    function handleResize() {
      setWindowHeight(window.innerHeight);
    }
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const { id } = useParams<{ id: string }>();

  const [hasFileManageFeature, setHasFileManageFeature] = useState(false);

  const [uploaderProfile, setUploaderProfile] = useState({} as UploaderProfile);

  useEffect(() => {
    const currentId = id || defaultUploaderProfileId;
    setCurrentProfile(currentId as string);
  }, []);

  useEffect(() => {
    if (uploaderProfile?.id) {
      getList();
    }
  }, [uploaderProfile]);

  const [list, setList] = useState([] as ListFile[]);
  const [listLoading, setListLoading] = useState(false);

  const getList = (directoryPath?: string) => {
    setListLoading(true);
    ipcRenderer.send('file-list-get', uploaderProfile.id, directoryPath);
  };

  const [dirPath, setDirPath] = useState([] as string[]);

  useEffect(() => {
    function handleListGetReply(_, res?: FileListResponse) {
      setListLoading(false);
      if (res === undefined) {
        setHasFileManageFeature(false);
        setList([]);
        message.info(`${uploaderProfile.uploaderName}暂不支持文件管理功能`);
        return;
      }
      setHasFileManageFeature(true);
      if (res.success) {
        setList(res.data);
      } else {
        message.error(`文件列表获取失败 ${res.desc || ''}`);
      }
    }

    function handleFileDeleteReply(_, res?: DeleteFileResponse) {
      if (res === undefined) {
        return;
      }
      if (res.success) {
        message.success({ content: '文件删除成功', key: 'file-manage-delete' });
        getList(dirPath.join('/'));
      } else {
        message.error({ content: `文件删除失败 ${res.desc || ''}`, key: 'file-manage-delete' });
      }
    }

    function handleFileUploadReply() {
      getList(dirPath.join('/'));
    }

    function handleDirectoryCreateReply(_, res?: CreateDirectoryResponse) {
      if (res === undefined) {
        return;
      }
      if (res.success) {
        message.success('目录创建成功');
        setModalVisible(false);
        getList(dirPath.join('/'));
      } else {
        message.error(`目录创建失败 ${res.desc || ''}`);
      }
    }

    function handleExportReplay(_, res) {
      setExportLoading(false);
      if (res) {
        shell.showItemInFolder(res);
        setRowKeys([]);
        setSelectRows([]);
      }
    }

    ipcRenderer.on('file-list-get-reply', handleListGetReply);
    ipcRenderer.on('file-delete-reply', handleFileDeleteReply);
    ipcRenderer.on('file-upload-reply', handleFileUploadReply);
    ipcRenderer.on('directory-create-reply', handleDirectoryCreateReply);
    ipcRenderer.on('export-reply', handleExportReplay);

    return () => {
      ipcRenderer.removeListener('file-list-get-reply', handleListGetReply);
      ipcRenderer.removeListener('file-delete-reply', handleFileDeleteReply);
      ipcRenderer.removeListener('file-upload-reply', handleFileUploadReply);
      ipcRenderer.removeListener('directory-create-reply', handleDirectoryCreateReply);
      ipcRenderer.removeListener('export-reply', handleExportReplay);
    };
  }, [uploaderProfile, dirPath]);

  const handleNameClick = (record: ListFile) => {
    if (record.type === 'directory') {
      const newPath = [...dirPath, formatFileName(record.name)];
      setDirPath(newPath);
      getList(newPath.join('/'));
    } else {
      clipboard.writeText(record.url as string);
      message.success('链接已复制到粘贴板');
    }
  };

  const handlePathClick = (index: number) => {
    if (index === -1) {
      setDirPath([]);
      getList();
    } else {
      const newPath = dirPath.slice(0, index + 1);
      setDirPath(newPath);
      getList(newPath.join('/'));
    }
  };

  const setCurrentProfile = (uploaderProfileId: string) => {
    setDirPath([]);
    const uploaderProfile = uploaderProfiles.find(item => item.id === uploaderProfileId);
    setUploaderProfile(uploaderProfile as UploaderProfile);
  };

  const formatFileName = (name: string) => {
    if (dirPath.length > 0) {
      const pathPrefix = dirPath.join('/') + '/';
      return name.split(pathPrefix).pop() || '';
    } else {
      return name;
    }
  };

  const [selectRowKeys, setRowKeys] = useState([] as string[]);
  const [selectRows, setSelectRows] = useState([] as ListFile[]);

  const handleTableRowChange = (selectedRowKeys, selectedRows: ListFile[]) => {
    setRowKeys(selectedRowKeys);
    setSelectRows(selectedRows);
  };

  const handleRefresh = () => {
    getList(dirPath.join('/'));
  };

  const handleBatchDelete = () => {
    Modal.confirm({
      title: '确认删除',
      onOk: () => {
        const names = selectRows.map(item => [...dirPath, formatFileName(item.name)].join('/'));
        message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
        ipcRenderer.send('file-delete', uploaderProfile.id, names);
      }
    });
  };

  const handleDelete = (record: ListFile) => {
    let name = record.name;
    Modal.confirm({
      title: '确认删除',
      content: name,
      onOk: () => {
        let name = record.name;
        if (record.type === 'directory') {
          name = `${[...dirPath, record.name].join('/')}/`;
        } else {
          name = [...dirPath, formatFileName(record.name)].join('/');
        }
        message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
        ipcRenderer.send('file-delete', uploaderProfile.id, [name]);
      }
    });
  };

  const uploadRef = useRef<HTMLInputElement>(null);

  const handleFileUpload = (event: React.FormEvent<HTMLInputElement>) => {
    const fileList = event.currentTarget.files || [];
    const filesPath = Array.from(fileList).map(file => file.path);
    const pathPrefix = dirPath.join('/');
    ipcRenderer.send('file-upload', uploaderProfile.id, filesPath, pathPrefix);
    event.currentTarget.value = '';
  };

  const [modalVisible, setModalVisible] = useState(false);

  const [form] = Form.useForm();

  const handleCreateDirectory = () => {
    form.validateFields().then(values => {
      ipcRenderer.send('directory-create', uploaderProfile.id, values?.directoryPath || '');
    });
  };

  const handleDownload = (record: ListFile) => {
    ipcRenderer.send('file-download', record.name, record.url);
  };

  const [exportLoading, setExportLoading] = useState(false);

  const handleExport = () => {
    const data = selectRows.map(item => {
      const fileNameArr = item.name.split('.');
      fileNameArr.pop();
      return {
        name: fileNameArr.join('.'),
        url: item.url
      };
    });
    setExportLoading(true);
    ipcRenderer.send('export', data);
  };

  const columns: ColumnsType<ListFile> = [
    {
      title: '文件名',
      dataIndex: 'name',
      ellipsis: true,
      render: (val: string, record: ListFile) => (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          {record.type === 'directory' ? (
            <FolderFilled style={{ fontSize: 16 }} />
          ) : (
            <FileOutlined style={{ fontSize: 16 }} />
          )}
          {record.type === 'directory' ? (
            <a
              title={val}
              onClick={() => handleNameClick(record)}
              className="table-filename"
              style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
            >
              {formatFileName(val)}
            </a>
          ) : (
            <Popover
              placement="topLeft"
              content={() =>
                /(jpg|png|gif|jpeg)$/.test(val) ? (
                  <Image
                    style={{ maxWidth: 500 }}
                    src={record.url}
                    fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
                  />
                ) : (
                  val
                )
              }
              trigger="hover"
            >
              <a
                title={val}
                onClick={() => handleNameClick(record)}
                className="table-filename"
                style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
              >
                {formatFileName(val)}
              </a>
            </Popover>
          )}
        </div>
      )
    },
    {
      title: '文件大小',
      dataIndex: 'size',
      ellipsis: true,
      width: 120,
      render: val => (val ? filesize(val) : '-')
    },
    {
      title: '更新时间',
      dataIndex: 'lastModified',
      ellipsis: true,
      width: 200,
      render: val => (val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-')
    },
    {
      title: '操作',
      width: 120,
      render: (_, record) => (
        <Space>
          {record.type !== 'directory' && (
            <>
              <DownloadOutlined onClick={() => handleDownload(record)} />
              <CopyOutlined onClick={() => handleNameClick(record)} />
            </>
          )}
          <DeleteOutlined onClick={() => handleDelete(record)} />
        </Space>
      )
    }
  ];

  return (
    <div className="storage-page">
      <header>
        <span>文件管理</span>
        <Divider />
      </header>
      <Space style={{ marginBottom: 10 }}>
        <Select style={{ minWidth: 120 }} value={uploaderProfile?.id} onChange={setCurrentProfile}>
          {uploaderProfiles.map(item => (
            <Select.Option key={item.name} value={item.id}>
              {item.name}
            </Select.Option>
          ))}
        </Select>
        <Button
          title="上传"
          icon={<UploadOutlined />}
          disabled={!hasFileManageFeature}
          type="primary"
          onClick={() => {
            uploadRef.current?.click();
          }}
        />
        <Button title="刷新" icon={<ReloadOutlined />} disabled={!hasFileManageFeature} onClick={handleRefresh} />
        <Button
          title="创建文件夹"
          icon={<FolderAddOutlined />}
          disabled={!hasFileManageFeature}
          onClick={() => {
            setModalVisible(true);
          }}
        />
        <Button
          title="导出"
          icon={<ExportOutlined />}
          disabled={selectRows.length === 0}
          onClick={handleExport}
          loading={exportLoading}
        />
        <Button title="删除" icon={<DeleteOutlined />} disabled={selectRows.length === 0} onClick={handleBatchDelete} />
      </Space>
      <Breadcrumb style={{ marginBottom: 10 }}>
        <Breadcrumb.Item>
          <a onClick={() => handlePathClick(-1)}>全部文件</a>
        </Breadcrumb.Item>
        {dirPath.map((item, index) => (
          <Breadcrumb.Item key={item}>
            <a onClick={() => handlePathClick(index)}>{item}</a>
          </Breadcrumb.Item>
        ))}
      </Breadcrumb>
      <div className="table-wrapper">
        <Table
          size="small"
          rowKey="name"
          scroll={{ y: windowHeight - 270 }}
          dataSource={list}
          columns={columns}
          pagination={{
            size: 'small',
            defaultPageSize: 100,
            pageSizeOptions: ['50', '100', '200'],
            hideOnSinglePage: true
          }}
          loading={listLoading}
          rowSelection={{
            onChange: handleTableRowChange,
            selectedRowKeys: selectRowKeys,
            getCheckboxProps: record => ({ disabled: record?.type === 'directory' })
          }}
        />
      </div>
      <input ref={uploadRef} type="file" multiple hidden onChange={handleFileUpload} />
      <Modal
        title="创建目录"
        visible={modalVisible}
        onCancel={() => setModalVisible(false)}
        onOk={handleCreateDirectory}
        destroyOnClose={true}
      >
        <Form form={form} preserve={false}>
          <Form.Item
            label="目录名称"
            name="directoryPath"
            rules={[{ required: true }, { pattern: domainPathRegExp, message: '目录名不能以 / 开头或结尾' }]}
          >
            <Input autoFocus />
          </Form.Item>
        </Form>
      </Modal>
    </div>
  );
}
Example #27
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
Node: React.FC = () => {
  const { t } = useTranslation();
  const [isLoading, setIsLoading] = React.useState<boolean>(false);
  const {
    uptime: { seconds },
  } = useUptime();
  const {
    version: { node_vendor },
  } = useVersion();
  const {
    nodeStatus: {
      memory: { total = 0 } = {},
      nodeStats: { cpu = 0, memory = 0 } = {},
    },
    getNodeStatus,
    isLoading: isNodeStatusLoading,
  } = React.useContext(NodeStatusContext);
  const [formattedMemory, setFormattedMemory] = React.useState(formatBytes(0));
  const [formattedTotal, setFormattedTotal] = React.useState(formatBytes(0));

  const refreshNode = async () => {
    setIsLoading(true);
    await refreshActionDelay(getNodeStatus);
    setIsLoading(false);
  };

  const opacity = isLoading ? 0.5 : 1;

  React.useEffect(() => {
    setFormattedMemory(formatBytes(memory));
  }, [memory]);

  React.useEffect(() => {
    setFormattedTotal(formatBytes(total));
  }, [total]);

  return (
    <Card
      size="small"
      title={t("common.node")}
      extra={
        <Tooltip title={t("pages.status.reload")}>
          <Button
            type="primary"
            icon={<ReloadOutlined />}
            size="small"
            onClick={refreshNode}
            loading={isLoading}
          />
        </Tooltip>
      }
    >
      <LoadingStatistic
        title={t("pages.status.version")}
        value={node_vendor}
        isLoading={!node_vendor}
        style={{ opacity }}
      />
      <LoadingStatistic
        title={t("pages.status.uptime")}
        tooltip={t("tooltips.uptime")}
        value={secondsToTime(seconds || 0)}
        isLoading={!node_vendor}
        style={{ opacity }}
      />
      <LoadingStatistic
        title={t("pages.status.cpuUsage")}
        value={new BigNumber(cpu).decimalPlaces(2).toNumber()}
        suffix="%"
        isLoading={isNodeStatusLoading}
        style={{ opacity }}
      />
      <LoadingStatistic
        title={t("pages.status.memory")}
        value={`${new BigNumber(formattedMemory.value).toFormat(
          2,
        )} / ${new BigNumber(formattedTotal.value).toFormat(2)}`}
        suffix={formattedTotal.suffix}
        isLoading={isNodeStatusLoading}
        style={{ opacity }}
      />
    </Card>
  );
}
Example #28
Source File: VizOperationMenu.tsx    From datart with Apache License 2.0 4 votes vote down vote up
VizOperationMenu: FC<{
  onShareLinkClick?;
  onDownloadDataLinkClick?;
  onSaveAsVizs?;
  onReloadData?;
  onAddToDashBoard?;
  onPublish?;
  onRecycleViz?;
  allowDownload?: boolean;
  allowShare?: boolean;
  allowManage?: boolean;
  isArchived?: boolean;
}> = memo(
  ({
    onShareLinkClick,
    onDownloadDataLinkClick,
    onSaveAsVizs,
    onReloadData,
    onAddToDashBoard,
    onPublish,
    allowDownload,
    allowShare,
    allowManage,
    isArchived,
    onRecycleViz,
  }) => {
    const t = useI18NPrefix(`viz.action`);
    const tg = useI18NPrefix(`global`);

    const moreActionMenu = () => {
      const menus: any[] = [];

      if (onReloadData) {
        menus.push(
          <Menu.Item
            key="reloadData"
            icon={<ReloadOutlined />}
            onClick={onReloadData}
          >
            {t('syncData')}
          </Menu.Item>,
          <Menu.Divider key={'reloadDataLine'} />,
        );
      }

      if (allowManage && onSaveAsVizs) {
        menus.push(
          <Menu.Item key="saveAs" icon={<CopyFilled />} onClick={onSaveAsVizs}>
            {tg('button.saveAs')}
          </Menu.Item>,
        );
      }

      if (allowManage && onSaveAsVizs) {
        menus.push(
          <Menu.Item
            key="addToDash"
            icon={<FileAddOutlined />}
            onClick={() => onAddToDashBoard(true)}
          >
            {t('addToDash')}
          </Menu.Item>,
          <Menu.Divider key="addToDashLine" />,
        );
      }

      if (allowShare && onShareLinkClick) {
        menus.push(
          <Menu.Item
            key="shareLink"
            icon={<ShareAltOutlined />}
            onClick={onShareLinkClick}
          >
            {t('share.shareLink')}
          </Menu.Item>,
        );
      }

      if (allowDownload && onDownloadDataLinkClick) {
        menus.push(
          <Menu.Item key="downloadData" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Excel);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadData')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Item key="downloadPDF" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Pdf);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadPDF')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Item key="downloadPicture" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Image);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadPicture')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Divider />,
          <Menu.Divider key="downloadDataLine" />,
        );
      }

      if (allowManage && !isArchived && onPublish) {
        menus.push(
          <Menu.Item
            key="publish"
            icon={<VerticalAlignBottomOutlined />}
            onClick={onPublish}
          >
            {t('unpublish')}
          </Menu.Item>,
        );
      }

      if (allowManage && onRecycleViz) {
        menus.push(
          <Menu.Item key="delete" icon={<DeleteOutlined />}>
            <Popconfirm
              title={tg('operation.archiveConfirm')}
              onConfirm={onRecycleViz}
            >
              {tg('button.archive')}
            </Popconfirm>
          </Menu.Item>,
        );
      }

      return <Menu>{menus}</Menu>;
    };

    return <StyleVizOperationMenu>{moreActionMenu()}</StyleVizOperationMenu>;
  },
)
Example #29
Source File: ExtrinsicRecords.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
/* -----------------------------------extrinsic tabs------------------------------------ */

// eslint-disable-next-line complexity
export function ExtrinsicRecords() {
  const { networkConfig } = useApi();
  const { t } = useTranslation();
  const { account: multiAddress } = useParams<{ account: string }>();
  const { multisigAccount, inProgress, confirmedAccount, cancelledAccount, queryInProgress, loadingInProgress } =
    useMultisigContext();
  const [tabKey, setTabKey] = useState('inProgress');
  const [confirmedPage, setConfirmedPage] = useState(1);
  const [cancelledPage, setCancelledPage] = useState(1);
  const [first, setFirst] = useState(true);

  const [fetchConfimed, { data: confirmedData, loading: loadingConfirmed }] = useManualQuery<MultisigRecordsQueryRes>(
    MULTISIG_RECORD_QUERY,
    {
      variables: {
        account: multiAddress,
        status: 'confirmed',
        offset: (confirmedPage - 1) * 10,
        limit: 10,
      },
    }
  );

  const [fetchCancelled, { data: cancelledData, loading: loadingCancelled }] = useManualQuery<MultisigRecordsQueryRes>(
    MULTISIG_RECORD_QUERY,
    {
      variables: {
        account: multiAddress,
        status: 'cancelled',
        offset: (cancelledPage - 1) * 10,
        limit: 10,
      },
    }
  );

  // eslint-disable-next-line complexity
  useEffect(() => {
    if (!loadingInProgress && confirmedAccount !== undefined && first) {
      setFirst(false);
      if (inProgress.length === 0 && confirmedAccount > 0) {
        setTabKey('confirmed');
      }
    }
  }, [loadingInProgress, confirmedAccount, first, inProgress]);

  useEffect(() => {
    if (networkConfig?.api?.subql) {
      fetchConfimed();
      fetchCancelled();
    }
  }, [networkConfig, fetchCancelled, fetchConfimed]);

  useEffect(() => {
    if (networkConfig?.api?.subql) {
      fetchConfimed();
    }
  }, [confirmedPage, fetchConfimed, networkConfig]);

  useEffect(() => {
    if (networkConfig?.api?.subql) {
      fetchCancelled();
    }
  }, [cancelledPage, fetchCancelled, networkConfig]);

  // eslint-disable-next-line complexity
  const handleChangeTab = (key: string) => {
    setTabKey(key);
    if (key === 'inProgress') {
      queryInProgress();
    } else if (key === 'confirmed') {
      if (networkConfig?.api?.subql) {
        fetchConfimed();
      }
    } else if (key === 'cancelled') {
      if (networkConfig?.api?.subql) {
        fetchCancelled();
      }
    }
  };

  const refreshData = () => {
    queryInProgress();
    if (networkConfig?.api?.subql) {
      fetchConfimed();
      fetchCancelled();
    }
  };

  return (
    <div className="relative">
      <div className="lg:absolute lg:right-2 lg:top-2 cursor-pointer z-50" onClick={refreshData}>
        <ReloadOutlined />
      </div>
      <Tabs activeKey={tabKey} onChange={handleChangeTab}>
        <TabPane
          tab={
            <Space>
              <span>{t('multisig.In Progress')}</span>
              <span>{inProgress.length}</span>
            </Space>
          }
          key="inProgress"
        >
          {multisigAccount?.address ? (
            <Entries
              source={inProgress}
              account={multisigAccount}
              loading={loadingInProgress}
              totalCount={inProgress.length}
            />
          ) : (
            <Spin className="w-full mt-4" />
          )}
        </TabPane>
        {networkConfig?.api?.subql && (
          <>
            <TabPane
              tab={
                <Space>
                  <span>{t('multisig.Confirmed Extrinsic')}</span>
                  <span>{confirmedAccount}</span>
                </Space>
              }
              key="confirmed"
            >
              <ConfirmedOrCancelled
                nodes={confirmedData?.multisigRecords?.nodes || []}
                loading={loadingConfirmed}
                account={multisigAccount}
                multiAddress={multiAddress}
                isConfirmed
                totalCount={confirmedData?.multisigRecords?.totalCount || 0}
                currentPage={confirmedPage}
                onChangePage={setConfirmedPage}
              />
            </TabPane>
            <TabPane
              tab={
                <Space>
                  <span>{t('multisig.Cancelled Extrinsic')}</span>
                  <span>{cancelledAccount}</span>
                </Space>
              }
              key="cancelled"
            >
              <ConfirmedOrCancelled
                nodes={cancelledData?.multisigRecords?.nodes || []}
                loading={loadingCancelled}
                account={multisigAccount}
                multiAddress={multiAddress}
                isConfirmed={false}
                totalCount={cancelledData?.multisigRecords?.totalCount || 0}
                currentPage={cancelledPage}
                onChangePage={setCancelledPage}
              />
            </TabPane>
          </>
        )}
      </Tabs>
    </div>
  );
}