diff --git a/src/hooks/use-overlay-scroller.ts b/src/hooks/use-overlay-scroller.ts index c0b4f273..afc28b2a 100644 --- a/src/hooks/use-overlay-scroller.ts +++ b/src/hooks/use-overlay-scroller.ts @@ -1,3 +1,4 @@ +import { useMemoizedFn } from 'ahooks'; import { throttle } from 'lodash'; import { UseOverlayScrollbarsParams, @@ -108,48 +109,44 @@ export default function useOverlayScroller(data?: { } }; - const throttledScroll = React.useCallback( + const throttledScroll = useMemoizedFn( throttle(() => { scrollEventElement.current?.scrollTo?.({ top: scrollEventElement.current?.scrollHeight, behavior: 'smooth' }); instanceRef.current?.update?.(); - }, 100), - [(scrollEventElement.current, instanceRef.current)] + }, 100) ); - const scrollauto = React.useCallback(() => { + const scrollauto = useMemoizedFn(() => { scrollEventElement.current?.scrollTo?.({ top: scrollEventElement.current.scrollHeight, behavior: 'auto' }); instanceRef.current?.update?.(); - }, [scrollEventElement.current, instanceRef.current]); + }); // scroll to bottom - const throttledUpdateScrollerPosition = React.useCallback( - (delay?: number) => { - if (stopUpdatePosition.current) { - return; - } - if (delay === 0) { - scrollauto(); - } else { - throttledScroll(); - } - }, - [throttledScroll, scrollauto] - ); + const throttledUpdateScrollerPosition = useMemoizedFn((delay?: number) => { + if (stopUpdatePosition.current) { + return; + } + if (delay === 0) { + scrollauto(); + } else { + throttledScroll(); + } + }); // scroll to top - const updateScrollerPositionToTop = React.useCallback(() => { + const updateScrollerPositionToTop = useMemoizedFn(() => { scrollEventElement.current?.scrollTo?.({ top: 0, behavior: 'auto' }); instanceRef.current?.update?.(); - }, [scrollEventElement.current, instanceRef.current]); + }); const generateInstance = () => { instanceRef.current = instance?.(); @@ -157,7 +154,7 @@ export default function useOverlayScroller(data?: { instanceRef.current?.elements()?.scrollEventElement; }; - const handleWheelCallback = React.useCallback((e: any) => { + const handleWheelCallback = useMemoizedFn((e: any) => { handleOnScroll(); if (timerRef.current) { clearTimeout(timerRef.current); @@ -165,7 +162,7 @@ export default function useOverlayScroller(data?: { timerRef.current = setTimeout(() => { stopUpdatePosition.current = false; }, RESETSCROLLDELAY); - }, []); + }); // add wheel event const handleWheelEvent = () => { @@ -180,24 +177,21 @@ export default function useOverlayScroller(data?: { ); }; - const createInstance = React.useCallback( - (el: any) => { - if (instanceRef.current) { - return instanceRef.current; - } - if (el) { - initialize(el); - scrollElementRef.current = el; - initialized.current = true; - instanceRef.current = instance?.(); - scrollEventElement.current = - instanceRef.current?.elements()?.scrollEventElement; - handleWheelEvent(); - } + const createInstance = useMemoizedFn((el: any) => { + if (instanceRef.current) { return instanceRef.current; - }, - [initialize, instance] - ); + } + if (el) { + initialize(el); + scrollElementRef.current = el; + initialized.current = true; + instanceRef.current = instance?.(); + scrollEventElement.current = + instanceRef.current?.elements()?.scrollEventElement; + handleWheelEvent(); + } + return instanceRef.current; + }); const destroyInstance = () => { instanceRef.current?.destroy?.(); @@ -205,6 +199,38 @@ export default function useOverlayScroller(data?: { instanceRef.current = null; }; + const scrollToTarget = (target: any, offset = 100) => { + if (!target) return; + if (!instanceRef.current || !scrollEventElement.current) { + instanceRef.current = instance?.(); + scrollEventElement.current = + instanceRef.current?.elements()?.scrollEventElement; + } + + const viewport = instanceRef.current?.elements().viewport; + const containerRect = viewport.getBoundingClientRect(); + const targetRect = target.getBoundingClientRect(); + + const scrollerState = instanceRef.current?.state(); + + const currentScroll = scrollerState.current?.overflowAmount?.y; + + // const currentScroll = instanceRef.current?.scroll().position.y; + const targetPos = targetRect.top - containerRect.top + currentScroll; + console.log( + 'target=======', + currentScroll, + targetPos, + instanceRef.current?.options() + ); + + scrollEventElement.current.scroll({ + y: targetPos - offset, + behavior: 'smooth' + }); + instanceRef.current?.update?.(); + }; + useEffect(() => { return () => { instanceRef.current?.destroy?.(); @@ -220,6 +246,9 @@ export default function useOverlayScroller(data?: { generateInstance, destroyInstance: destroyInstance, updateScrollerPosition: throttledUpdateScrollerPosition, - updateScrollerPositionToTop: updateScrollerPositionToTop + updateScrollerPositionToTop: updateScrollerPositionToTop, + scrollToBottom: scrollauto, + scrollToTop: updateScrollerPositionToTop, + scrollToTarget }; } diff --git a/src/pages/_components/column-wrapper/index.tsx b/src/pages/_components/column-wrapper/index.tsx index 71e3b14f..adc8fe77 100644 --- a/src/pages/_components/column-wrapper/index.tsx +++ b/src/pages/_components/column-wrapper/index.tsx @@ -1,6 +1,7 @@ import useOverlayScroller from '@/hooks/use-overlay-scroller'; import React from 'react'; import './style.less'; +import { WrapperContext } from './use-wrapper-context'; interface ColumnWrapperProps { children: React.ReactNode; @@ -21,13 +22,14 @@ const ColumnWrapper: React.FC = ({ styles = {} }) => { const scroller = React.useRef(null); - const { initialize } = useOverlayScroller({ - options: { - scrollbars: { - autoHide: 'move' + const { initialize, instance, scrollToBottom, scrollToTarget } = + useOverlayScroller({ + options: { + scrollbars: { + autoHide: 'move' + } } - } - }); + }); React.useEffect(() => { if (scroller.current) { @@ -36,7 +38,9 @@ const ColumnWrapper: React.FC = ({ }, []); return ( - <> +
= ({
{footer &&
{footer}
} - +
); }; diff --git a/src/pages/_components/column-wrapper/use-wrapper-context.ts b/src/pages/_components/column-wrapper/use-wrapper-context.ts new file mode 100644 index 00000000..2e6686c6 --- /dev/null +++ b/src/pages/_components/column-wrapper/use-wrapper-context.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +interface WrapperContextProps { + osInstance?: any; + scrollToBottom?: () => void; + scrollToTop?: () => void; + scrollToTarget?: (target: any, offset?: number) => void; +} + +export const WrapperContext = createContext( + {} as WrapperContextProps +); + +export const useWrapperContext = () => { + const context = useContext(WrapperContext); + if (!context) { + throw new Error('useWrapperContext must be used within a WrapperProvider'); + } + return context; +}; diff --git a/src/pages/llmodels/components/update-modal.tsx b/src/pages/llmodels/components/update-modal.tsx index 4d25e8a8..9dbe7a2d 100644 --- a/src/pages/llmodels/components/update-modal.tsx +++ b/src/pages/llmodels/components/update-modal.tsx @@ -1,7 +1,8 @@ import ModalFooter from '@/components/modal-footer'; +import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import { useIntl } from '@umijs/max'; -import { Button, Modal } from 'antd'; +import { Button } from 'antd'; import _ from 'lodash'; import React, { useEffect, useMemo, useRef } from 'react'; import ColumnWrapper from '../../_components/column-wrapper'; @@ -34,6 +35,12 @@ type AddModalProps = { onCancel: () => void; }; +const ModalFooterStyle = { + padding: '16px 24px', + display: 'flex', + justifyContent: 'flex-end' +}; + const UpdateModal: React.FC = (props) => { const { title, @@ -249,57 +256,34 @@ const UpdateModal: React.FC = (props) => { }, [open, formData]); return ( - - - {intl.formatMessage({ - id: 'models.form.submit.anyway' - })} - - ) - } - > - - } + footer={false} > = (props) => { warningStatus={warningStatus} contentStyle={{ paddingInline: 0 }} > + + {intl.formatMessage({ + id: 'models.form.submit.anyway' + })} + + ) + } + > } > @@ -329,7 +328,7 @@ const UpdateModal: React.FC = (props) => { onValuesChange={handleManulOnValuesChange} > - + ); }; diff --git a/src/pages/llmodels/forms/basic.tsx b/src/pages/llmodels/forms/basic.tsx new file mode 100644 index 00000000..3e5590a5 --- /dev/null +++ b/src/pages/llmodels/forms/basic.tsx @@ -0,0 +1,135 @@ +import SealInput from '@/components/seal-form/seal-input'; +import SealSelect from '@/components/seal-form/seal-select'; +import useAppUtils from '@/hooks/use-app-utils'; +import { useIntl } from '@umijs/max'; +import { Form } from 'antd'; +import { sourceOptions } from '../config'; +import { FormData } from '../config/types'; +import CatalogFrom from './catalog'; +import LocalPathSource from './local-path-source'; +import OnlineSource from './online-source'; + +interface BasicFormProps { + fields?: string[]; + sourceDisable?: boolean; + sourceList?: Global.BaseOption[]; + clusterList: Global.BaseOption[]; + handleClusterChange: (value: number) => void; + onSourceChange?: (value: string) => void; +} + +const BasicForm: React.FC = (props) => { + const { + fields = [], + sourceList, + clusterList, + sourceDisable, + handleClusterChange, + onSourceChange + } = props; + const intl = useIntl(); + const { getRuleMessage } = useAppUtils(); + const form = Form.useFormInstance(); + + const handleOnSourceChange = (val: string) => { + onSourceChange?.(val); + }; + + return ( + <> + + name="name" + rules={[ + { + required: true, + message: getRuleMessage('input', 'common.table.name') + } + ]} + > + + + {fields.includes('source') && ( + + name="source" + rules={[ + { + required: true, + message: getRuleMessage('select', 'models.form.source') + } + ]} + > + { + + } + + )} + + + + + name="cluster_id" + rules={[ + { + required: true, + message: getRuleMessage('select', 'clusters.title') + } + ]} + > + { + + } + + + name="replicas" + rules={[ + { + required: true, + message: getRuleMessage('input', 'models.form.replicas') + } + ]} + > + + + + name="description"> + + + + ); +}; + +export default BasicForm; diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 23a045f5..488579cf 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -1,13 +1,11 @@ -import SealInput from '@/components/seal-form/seal-input'; -import SealSelect from '@/components/seal-form/seal-select'; import { PageActionType } from '@/config/types'; -import useAppUtils from '@/hooks/use-app-utils'; import CollapsePanel from '@/pages/_components/collapse-panel'; +import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle } from 'react'; -import { excludeFields, ScheduleValueMap, sourceOptions } from '../config'; +import { excludeFields, ScheduleValueMap } from '../config'; import { backendOptionsMap } from '../config/backend-parameters'; import { FormContext } from '../config/form-context'; import { @@ -18,13 +16,9 @@ import { } from '../config/types'; import { generateGPUIds } from '../config/utils'; import { useGenerateGPUOptions } from '../hooks/use-form-initial-values'; -import CatalogFrom from './catalog'; -import LocalPathSource from './local-path-source'; -import OnlineSource from './online-source'; -// import AdvanceConfig from './advance-config'; import useQueryBackends from '../hooks/use-query-backends'; import AdvanceConfig from './advance-config'; -import Performance from './performance'; +import BasicForm from './basic'; interface DataFormProps { initialValues?: any; @@ -57,13 +51,16 @@ const DataForm: React.FC = forwardRef((props, ref) => { onValuesChange, onOk } = props; + const { scrollToTarget, scrollToBottom } = useWrapperContext(); const { backendOptions, getBackendOptions } = useQueryBackends(); const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions(); - const { getRuleMessage } = useAppUtils(); const [form] = Form.useForm(); const intl = useIntl(); const [activeKey, setActiveKey] = React.useState([]); const scheduleType = Form.useWatch('scheduleType', form); + const [target, setTarget] = React.useState('basic'); + const performanceRef = React.useRef(null); + const advanceRef = React.useRef(null); const handleSumit = () => { form.submit(); @@ -106,10 +103,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { onOk(allValues); }; - const handleOnSourceChange = (val: string) => { - onSourceChange?.(val); - }; - const handleClusterChange = (value: number) => { getGPUOptionList({ clusterId: value }); getBackendOptions({ cluster_id: value }); @@ -133,6 +126,20 @@ const DataForm: React.FC = forwardRef((props, ref) => { setActiveKey(Array.isArray(keys) ? keys : [keys]); }; + const handleTargetChange = (val: string) => { + console.log('val', val); + // if (val === 'performance' && performanceRef.current) { + // scrollToTarget?.(performanceRef.current); + // } + // if (val === 'advanced' && advanceRef.current) { + // scrollToTarget?.(advanceRef.current); + // } + // scrollToBottom?.(); + // setTimeout(() => { + // setTarget(val); + // }, 100); + }; + useImperativeHandle(ref, () => { return { form: form, @@ -174,6 +181,27 @@ const DataForm: React.FC = forwardRef((props, ref) => { onBackendChange: handleBackendChange }} > + {/*
+ +
*/}
= forwardRef((props, ref) => { ...initialValues }} > - - name="name" - rules={[ - { - required: true, - message: getRuleMessage('input', 'common.table.name') - } - ]} - > - - - {fields.includes('source') && ( - - name="source" - rules={[ - { - required: true, - message: getRuleMessage('select', 'models.form.source') - } - ]} - > - { - - } - - )} - - - - - name="cluster_id" - rules={[ - { - required: true, - message: getRuleMessage('select', 'clusters.title') - } - ]} - > - { - - } - - - name="replicas" - rules={[ - { - required: true, - message: getRuleMessage('input', 'models.form.replicas') - } - ]} - > - - - - name="description"> - - + , + // children: + // }, { - key: 'performance', - label: intl.formatMessage({ id: 'models.form.performance' }), - forceRender: true, - children: - }, - { - key: 'advance_config', + key: 'advanced', label: intl.formatMessage({ id: 'resources.form.advanced' }), forceRender: true, + extra:
, children: } ]}