chore: infinite scroller
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
@@ -74,6 +76,12 @@ const CardList: React.FC<CatalogListProps> = (props) => {
|
||||
renderItem
|
||||
} = props;
|
||||
const [span, setSpan] = React.useState(defaultSpan);
|
||||
const {
|
||||
total,
|
||||
current,
|
||||
loading: contextLoading,
|
||||
refresh
|
||||
} = useScrollerContext();
|
||||
|
||||
const getSpanByWidth = (width: number) => {
|
||||
if (width < breakpoints.md) return 24;
|
||||
@@ -91,7 +99,12 @@ const CardList: React.FC<CatalogListProps> = (props) => {
|
||||
return (
|
||||
<div className="relative" style={{ width: '100%' }}>
|
||||
<ResizeObserver onResize={handleResize} disabled={!resizable}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<InfiniteScroller
|
||||
total={total}
|
||||
current={current}
|
||||
loading={contextLoading}
|
||||
refresh={refresh}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
{dataList.map((item: any, index) => {
|
||||
return (
|
||||
@@ -107,7 +120,7 @@ const CardList: React.FC<CatalogListProps> = (props) => {
|
||||
isFirst={isFirst}
|
||||
Skeleton={Skeleton}
|
||||
/>
|
||||
</div>
|
||||
</InfiniteScroller>
|
||||
</ResizeObserver>
|
||||
<FloatButton.BackTop visibilityHeight={1000} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
useInfiniteScroll,
|
||||
UseInfiniteScrollOptions
|
||||
} from './use-infinite-scroll';
|
||||
|
||||
const InfiniteScroller: React.FC<
|
||||
UseInfiniteScrollOptions & { children: React.ReactNode }
|
||||
> = (props) => {
|
||||
const { children, ...restProps } = props;
|
||||
const { observerRef } = useInfiniteScroll(restProps);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<div ref={observerRef} style={{ height: 1 }}>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfiniteScroller;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { throttle } from 'lodash';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
|
||||
export interface UseInfiniteScrollOptions {
|
||||
total: number;
|
||||
current: number;
|
||||
loading: boolean;
|
||||
refresh: (nextPage: number) => void;
|
||||
onBottom?: () => void;
|
||||
throttleDelay?: number;
|
||||
}
|
||||
|
||||
export function useInfiniteScroll({
|
||||
total,
|
||||
current,
|
||||
loading,
|
||||
refresh,
|
||||
onBottom,
|
||||
throttleDelay = 300
|
||||
}: UseInfiniteScrollOptions) {
|
||||
const { ref: observerRef, inView } = useInView({
|
||||
threshold: 0.2
|
||||
});
|
||||
const isInitialLoad = useRef(true);
|
||||
|
||||
const throttledLoadMore = useMemoizedFn(
|
||||
throttle(() => {
|
||||
if (loading) return;
|
||||
if (current >= total) return;
|
||||
|
||||
onBottom?.();
|
||||
refresh(current + 1);
|
||||
}, throttleDelay)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
if (isInitialLoad.current) {
|
||||
isInitialLoad.current = false;
|
||||
return;
|
||||
}
|
||||
throttledLoadMore();
|
||||
}
|
||||
}, [inView, throttledLoadMore]);
|
||||
|
||||
return { observerRef };
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface ScrollerContextProps {
|
||||
total: number;
|
||||
current: number;
|
||||
loading: boolean;
|
||||
refresh: (nextPage: number) => void;
|
||||
onBottom?: () => void;
|
||||
throttleDelay?: number;
|
||||
}
|
||||
|
||||
export const ScrollerContext = createContext<ScrollerContextProps>(
|
||||
{} as ScrollerContextProps
|
||||
);
|
||||
|
||||
export const useScrollerContext = () => {
|
||||
const context = useContext(ScrollerContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useScrollerContext must be used within a ScrollerProvider'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import CardSkeleton from '@/components/templates/card-skelton';
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
@@ -72,6 +74,13 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
||||
onSelect
|
||||
} = props;
|
||||
const [span, setSpan] = React.useState(defaultSpan);
|
||||
const {
|
||||
total,
|
||||
current,
|
||||
loading: contextLoading,
|
||||
refresh,
|
||||
throttleDelay
|
||||
} = useScrollerContext();
|
||||
|
||||
const getSpanByWidth = (width: number) => {
|
||||
if (width < breakpoints.md) return 24;
|
||||
@@ -89,7 +98,13 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
||||
return (
|
||||
<div className="relative" style={{ width: '100%' }}>
|
||||
<ResizeObserver onResize={handleResize} disabled={!resizable}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<InfiniteScroller
|
||||
total={total}
|
||||
current={current}
|
||||
loading={contextLoading}
|
||||
refresh={refresh}
|
||||
throttleDelay={throttleDelay}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
{dataList.map((item: any) => {
|
||||
return (
|
||||
@@ -100,7 +115,7 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
||||
})}
|
||||
</Row>
|
||||
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
||||
</div>
|
||||
</InfiniteScroller>
|
||||
</ResizeObserver>
|
||||
<FloatButton.BackTop visibilityHeight={1000} />
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,10 @@ import { PageActionType } from '@/config/types';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import _ from 'lodash';
|
||||
import { useState } from 'react';
|
||||
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
||||
import {
|
||||
createBackend,
|
||||
createBackendFromYAML,
|
||||
@@ -141,6 +143,13 @@ const BackendList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||
fetchData({
|
||||
...queryParams,
|
||||
page: nextPage
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
@@ -170,13 +179,23 @@ const BackendList = () => {
|
||||
showSelect={false}
|
||||
></FilterBar>
|
||||
|
||||
<BackendCardList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
onSelect={handleOnSelect}
|
||||
></BackendCardList>
|
||||
<ScrollerContext.Provider
|
||||
value={{
|
||||
total: dataSource.totalPage,
|
||||
current: queryParams.page!,
|
||||
loading: dataSource.loading,
|
||||
refresh: loadMore,
|
||||
throttleDelay: 300
|
||||
}}
|
||||
>
|
||||
<BackendCardList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
onSelect={handleOnSelect}
|
||||
></BackendCardList>
|
||||
</ScrollerContext.Provider>
|
||||
<AddModal
|
||||
action={openModalStatus.action}
|
||||
onClose={() => setOpenModalStatus({ open: false, action: 'create' })}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { modelsExpandKeysAtom } from '@/atoms/models';
|
||||
import MoreButton from '@/components/buttons/more';
|
||||
import PageTools from '@/components/page-tools';
|
||||
import BaseSelect from '@/components/seal-form/base/select';
|
||||
import { PageAction } from '@/config';
|
||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||
import { ScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||
import { IS_FIRST_LOGIN, writeState } from '@/utils/localstore/index';
|
||||
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, Input, Pagination, Space, message } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import _ from 'lodash';
|
||||
@@ -213,12 +214,12 @@ const Catalog: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||
fetchData({
|
||||
...queryParams,
|
||||
page: queryParams.page + 1
|
||||
page: nextPage
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
@@ -303,18 +304,23 @@ const Catalog: React.FC = () => {
|
||||
</Space>
|
||||
}
|
||||
></PageTools>
|
||||
<CatalogList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
onDeploy={handleOnDeploy}
|
||||
activeId={-1}
|
||||
isFirst={isFirst}
|
||||
></CatalogList>
|
||||
<MoreButton
|
||||
show={queryParams.page < dataSource.totalPage}
|
||||
loading={dataSource.loading}
|
||||
loadMore={loadMore}
|
||||
></MoreButton>
|
||||
<ScrollerContext.Provider
|
||||
value={{
|
||||
total: dataSource.totalPage,
|
||||
current: queryParams.page,
|
||||
loading: dataSource.loading,
|
||||
refresh: loadMore,
|
||||
throttleDelay: 300
|
||||
}}
|
||||
>
|
||||
<CatalogList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
onDeploy={handleOnDeploy}
|
||||
activeId={-1}
|
||||
isFirst={isFirst}
|
||||
></CatalogList>
|
||||
</ScrollerContext.Provider>
|
||||
<PageWrapper>
|
||||
<Pagination
|
||||
hideOnSinglePage={queryParams.perPage === 100}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import CatalogSkelton from '@/components/templates/card-skelton';
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
||||
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
||||
import { Col, FloatButton, Row, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
@@ -54,6 +56,13 @@ const ListSkeleton: React.FC<{
|
||||
|
||||
const CatalogList: React.FC<CatalogListProps> = (props) => {
|
||||
const { dataList, loading, activeId, isFirst, onDeploy } = props;
|
||||
const {
|
||||
total,
|
||||
current,
|
||||
loading: contextLoading,
|
||||
refresh,
|
||||
throttleDelay
|
||||
} = useScrollerContext();
|
||||
const [span, setSpan] = React.useState(8);
|
||||
|
||||
const getSpanByWidth = (width: number) => {
|
||||
@@ -72,7 +81,13 @@ const CatalogList: React.FC<CatalogListProps> = (props) => {
|
||||
return (
|
||||
<div className="relative" style={{ width: '100%' }}>
|
||||
<ResizeObserver onResize={handleResize}>
|
||||
<div>
|
||||
<InfiniteScroller
|
||||
total={total}
|
||||
current={current}
|
||||
loading={contextLoading}
|
||||
refresh={refresh}
|
||||
throttleDelay={throttleDelay}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
{dataList.map((item: CatalogItemType, index) => {
|
||||
return (
|
||||
@@ -87,11 +102,11 @@ const CatalogList: React.FC<CatalogListProps> = (props) => {
|
||||
})}
|
||||
</Row>
|
||||
<ListSkeleton span={span} loading={loading} isFirst={isFirst} />
|
||||
</div>
|
||||
</InfiniteScroller>
|
||||
</ResizeObserver>
|
||||
<FloatButton.BackTop visibilityHeight={1000} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CatalogList);
|
||||
export default CatalogList;
|
||||
|
||||
@@ -7,7 +7,7 @@ export const backendOptionsMap = {
|
||||
vllm: 'vLLM',
|
||||
voxBox: 'vox-box',
|
||||
ascendMindie: 'MindIE',
|
||||
custom: 'custom'
|
||||
custom: 'Custom'
|
||||
};
|
||||
|
||||
export interface BackendParameter {
|
||||
|
||||
@@ -114,7 +114,7 @@ const BackendFields: React.FC = () => {
|
||||
required
|
||||
allowClear
|
||||
scaleSize={true}
|
||||
label="Image Name"
|
||||
label={intl.formatMessage({ id: 'backend.imageName' })}
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="run_command" rules={[{ required: true }]}>
|
||||
@@ -122,7 +122,7 @@ const BackendFields: React.FC = () => {
|
||||
required
|
||||
scaleSize={true}
|
||||
allowClear
|
||||
label="Execution Command"
|
||||
label={intl.formatMessage({ id: 'backend.runCommand' })}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@@ -264,7 +264,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
items={[
|
||||
{
|
||||
key: 'performance',
|
||||
label: 'Performance',
|
||||
label: intl.formatMessage({ id: 'models.form.performance' }),
|
||||
forceRender: true,
|
||||
children: <Performance></Performance>
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import MoreButton from '@/components/buttons/more';
|
||||
import PageTools from '@/components/page-tools';
|
||||
import BaseSelect from '@/components/seal-form/base/select';
|
||||
import CardList from '@/components/templates/card-list';
|
||||
@@ -7,8 +6,10 @@ import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
||||
import { MY_MODELS_API, queryMyModels } from './apis';
|
||||
import ModelItem from './components/model-item';
|
||||
import { categoryOptions, modelCategoriesMap } from './config';
|
||||
@@ -30,13 +31,6 @@ const UserModels: React.FC = () => {
|
||||
});
|
||||
const intl = useIntl();
|
||||
|
||||
const loadMore = () => {
|
||||
fetchData({
|
||||
...queryParams,
|
||||
page: queryParams.page + 1
|
||||
});
|
||||
};
|
||||
|
||||
const handleCategoryChange = (value: string) => {
|
||||
handleQueryChange({
|
||||
categories: value
|
||||
@@ -67,6 +61,13 @@ const UserModels: React.FC = () => {
|
||||
return <ModelItem model={data} onClick={handleOnClick} />;
|
||||
};
|
||||
|
||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||
fetchData({
|
||||
...queryParams,
|
||||
page: nextPage
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
@@ -116,19 +117,23 @@ const UserModels: React.FC = () => {
|
||||
</Space>
|
||||
}
|
||||
></PageTools>
|
||||
<CardList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
Skeleton={CardSkeleton}
|
||||
renderItem={renderCard}
|
||||
></CardList>
|
||||
<MoreButton
|
||||
show={queryParams.page < dataSource.totalPage}
|
||||
loading={dataSource.loading}
|
||||
loadMore={loadMore}
|
||||
></MoreButton>
|
||||
<ScrollerContext.Provider
|
||||
value={{
|
||||
total: dataSource.totalPage,
|
||||
current: queryParams.page,
|
||||
loading: dataSource.loading,
|
||||
refresh: loadMore
|
||||
}}
|
||||
>
|
||||
<CardList
|
||||
dataList={dataSource.dataList}
|
||||
loading={dataSource.loading}
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
Skeleton={CardSkeleton}
|
||||
renderItem={renderCard}
|
||||
></CardList>
|
||||
</ScrollerContext.Provider>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user