style: scroll to field
This commit is contained in:
@@ -129,6 +129,12 @@ export default function useTableFetch<T>(
|
|||||||
..._.pickBy(query || queryParams, (val: any) => !!val)
|
..._.pickBy(query || queryParams, (val: any) => !!val)
|
||||||
};
|
};
|
||||||
const res = await fetchAPI(params);
|
const res = await fetchAPI(params);
|
||||||
|
if (!dataSource.loadend) {
|
||||||
|
// add a delay to avoid flash
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 200);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!res.items.length &&
|
!res.items.length &&
|
||||||
params.page > res.pagination.totalPage &&
|
params.page > res.pagination.totalPage &&
|
||||||
@@ -139,6 +145,7 @@ export default function useTableFetch<T>(
|
|||||||
page: res.pagination.totalPage
|
page: res.pagination.totalPage
|
||||||
};
|
};
|
||||||
const newRes = await fetchAPI(newParams);
|
const newRes = await fetchAPI(newParams);
|
||||||
|
|
||||||
setDataSource({
|
setDataSource({
|
||||||
dataList: loadmore
|
dataList: loadmore
|
||||||
? [...dataSource.dataList, ...(newRes.items || [])]
|
? [...dataSource.dataList, ...(newRes.items || [])]
|
||||||
|
|||||||
@@ -81,7 +81,13 @@ const AdvanceConfig = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData> name="categories">
|
<Form.Item<FormData>
|
||||||
|
name="categories"
|
||||||
|
data-field="categories"
|
||||||
|
style={{
|
||||||
|
scrollMarginTop: 200
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
allowNull
|
allowNull
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
|
data-field="name"
|
||||||
name="name"
|
name="name"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form, Segmented } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { excludeFields, gpusCountTypeMap, ScheduleValueMap } from '../config';
|
import { excludeFields, gpusCountTypeMap, ScheduleValueMap } from '../config';
|
||||||
import { backendOptionsMap } from '../config/backend-parameters';
|
import { backendOptionsMap } from '../config/backend-parameters';
|
||||||
import { FormContext } from '../config/form-context';
|
import { FormContext } from '../config/form-context';
|
||||||
@@ -14,13 +15,35 @@ import {
|
|||||||
SourceType
|
SourceType
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
import { generateGPUIds } from '../config/utils';
|
import { generateGPUIds } from '../config/utils';
|
||||||
|
import useFieldScroll from '../hooks/use-field-scroll';
|
||||||
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
|
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
|
||||||
import useQueryBackends from '../hooks/use-query-backends';
|
import useQueryBackends from '../hooks/use-query-backends';
|
||||||
import AdvanceConfig from './advance-config';
|
import AdvanceConfig from './advance-config';
|
||||||
import BasicForm from './basic';
|
import BasicForm from './basic';
|
||||||
import Performance from './performance';
|
import Performance from './performance';
|
||||||
|
|
||||||
const requiredFields = ['gpu_selector', 'backend'];
|
const advancedRequiredFields = [
|
||||||
|
'gpu_selector',
|
||||||
|
'backend',
|
||||||
|
'image_name',
|
||||||
|
'run_command'
|
||||||
|
];
|
||||||
|
const performanceRequiredFields = ['speculative_config'];
|
||||||
|
|
||||||
|
const SegmentedInner = styled(Segmented)`
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
.ant-segmented-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SegmentedHeader = styled.div`
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
interface DataFormProps {
|
interface DataFormProps {
|
||||||
initialValues?: any;
|
initialValues?: any;
|
||||||
@@ -64,6 +87,31 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
const performanceRef = React.useRef<HTMLDivElement>(null);
|
const performanceRef = React.useRef<HTMLDivElement>(null);
|
||||||
const advanceRef = React.useRef<HTMLDivElement>(null);
|
const advanceRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const segmentOptions = [
|
||||||
|
{
|
||||||
|
value: 'basic',
|
||||||
|
label: intl.formatMessage({ id: 'common.title.basicInfo' }),
|
||||||
|
field: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'performance',
|
||||||
|
label: intl.formatMessage({ id: 'models.form.performance' }),
|
||||||
|
field: 'extended_kv_cache.enabled'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'advanced',
|
||||||
|
label: intl.formatMessage({ id: 'resources.form.advanced' }),
|
||||||
|
field: 'categories'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const { scrollToSegment } = useFieldScroll({
|
||||||
|
form,
|
||||||
|
activeKey,
|
||||||
|
setActiveKey,
|
||||||
|
segmentOptions
|
||||||
|
});
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
};
|
};
|
||||||
@@ -155,21 +203,34 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
const handleOnFinishFailed = (errorInfo: any) => {
|
const handleOnFinishFailed = (errorInfo: any) => {
|
||||||
const { errorFields } = errorInfo;
|
const { errorFields } = errorInfo;
|
||||||
if (errorFields && errorFields.length > 0) {
|
if (errorFields && errorFields.length > 0) {
|
||||||
|
const collapseKeys: string[] = [];
|
||||||
const names = errorFields.map((item: any) => item.name[0]);
|
const names = errorFields.map((item: any) => item.name[0]);
|
||||||
const isRequired = names.some((name: string) =>
|
const isAdvancedRequired = names.some((name: string) =>
|
||||||
requiredFields.includes(name)
|
advancedRequiredFields.includes(name)
|
||||||
);
|
);
|
||||||
if (isRequired) {
|
|
||||||
setActiveKey(['advanced']);
|
const isPerformanceRequired = names.some((name: string) =>
|
||||||
|
performanceRequiredFields.includes(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isPerformanceRequired) {
|
||||||
|
collapseKeys.push('performance');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdvancedRequired) {
|
||||||
|
collapseKeys.push('advanced');
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveKey((prev: string[]) => [
|
||||||
|
...new Set([...prev, ...collapseKeys])
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTargetChange = (val: string) => {
|
const handleTargetChange = async (val: any) => {
|
||||||
form.scrollToField(val, {
|
setTarget(val);
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center'
|
await scrollToSegment(val, { offsetTop: 96 });
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
@@ -214,30 +275,14 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
onBackendChange: handleBackendChange
|
onBackendChange: handleBackendChange
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <div
|
<SegmentedHeader>
|
||||||
className="m-b-8"
|
<SegmentedInner
|
||||||
style={{ position: 'sticky', top: 0, zIndex: 100 }}
|
defaultValue="basic"
|
||||||
>
|
|
||||||
<Segmented
|
|
||||||
value={target}
|
value={target}
|
||||||
defaultValue="Basic"
|
|
||||||
onChange={handleTargetChange}
|
onChange={handleTargetChange}
|
||||||
options={[
|
options={segmentOptions}
|
||||||
{
|
|
||||||
value: 'name',
|
|
||||||
label: 'Basic'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'categories',
|
|
||||||
label: 'Performance'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'categories',
|
|
||||||
label: 'Advanced'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div> */}
|
</SegmentedHeader>
|
||||||
<Form
|
<Form
|
||||||
name="deployModel"
|
name="deployModel"
|
||||||
form={form}
|
form={form}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ const KVCacheForm = () => {
|
|||||||
<>
|
<>
|
||||||
<div style={{ paddingBottom: 22 }}>
|
<div style={{ paddingBottom: 22 }}>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
|
data-field="extended_kv_cache.enabled"
|
||||||
name={['extended_kv_cache', 'enabled']}
|
name={['extended_kv_cache', 'enabled']}
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||||
noStyle
|
|
||||||
>
|
>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
interface ScrollOptions {
|
||||||
|
wait?: number;
|
||||||
|
behavior?: 'smooth' | 'auto';
|
||||||
|
block?: 'start' | 'end' | 'center';
|
||||||
|
offsetTop?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useScrollAfterExpand({
|
||||||
|
form,
|
||||||
|
activeKey,
|
||||||
|
setActiveKey,
|
||||||
|
segmentOptions,
|
||||||
|
defaultWait = 300
|
||||||
|
}: {
|
||||||
|
form: any;
|
||||||
|
activeKey: string[];
|
||||||
|
setActiveKey: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
|
segmentOptions: { value: string; field: string }[];
|
||||||
|
defaultWait?: number;
|
||||||
|
}) {
|
||||||
|
const scrollToElement = useCallback(
|
||||||
|
(
|
||||||
|
el: HTMLElement,
|
||||||
|
{ behavior = 'smooth', offsetTop = 0 }: ScrollOptions = {}
|
||||||
|
) => {
|
||||||
|
// find the nearest scrollable parent
|
||||||
|
const scrollParent = (() => {
|
||||||
|
let node: HTMLElement | null = el;
|
||||||
|
while (node) {
|
||||||
|
const { overflowY } = getComputedStyle(node);
|
||||||
|
if (overflowY === 'auto' || overflowY === 'scroll') return node;
|
||||||
|
node = node.parentElement;
|
||||||
|
}
|
||||||
|
return document.scrollingElement || document.documentElement;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const parentRect = scrollParent.getBoundingClientRect();
|
||||||
|
const elRect = el.getBoundingClientRect();
|
||||||
|
const top =
|
||||||
|
elRect.top - parentRect.top + scrollParent.scrollTop - offsetTop;
|
||||||
|
|
||||||
|
scrollParent.scrollTo({ top, behavior });
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollToSegment = useCallback(
|
||||||
|
async (val: string, options?: ScrollOptions) => {
|
||||||
|
if (!activeKey.includes(val)) {
|
||||||
|
setActiveKey((prev) => [...prev, val]);
|
||||||
|
await new Promise((r) => {
|
||||||
|
setTimeout(r, options?.wait ?? defaultWait);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(requestAnimationFrame);
|
||||||
|
|
||||||
|
const current = segmentOptions.find((item) => item.value === val);
|
||||||
|
if (!current?.field) return;
|
||||||
|
|
||||||
|
const el = document.querySelector(
|
||||||
|
`[data-field="${current.field}"]`
|
||||||
|
) as HTMLElement | null;
|
||||||
|
|
||||||
|
if (el) scrollToElement(el, options);
|
||||||
|
},
|
||||||
|
[activeKey, setActiveKey, segmentOptions, defaultWait, scrollToElement]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { scrollToSegment };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user