From 8b139087e4a0b0a1c03e5b8833aa00b3bd3721e9 Mon Sep 17 00:00:00 2001 From: twwu Date: Tue, 16 Sep 2025 10:45:00 +0800 Subject: [PATCH] feat: Enhance drawer components with modal and overlay options for improved user experience --- .../detail/completed/child-segment-list.tsx | 14 ++- .../detail/completed/common/drawer.tsx | 104 ++++++++++++++++++ .../completed/common/full-screen-drawer.tsx | 35 +++--- .../documents/detail/completed/index.tsx | 19 +++- .../detail/completed/segment-card/index.tsx | 2 +- 5 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 web/app/components/datasets/documents/detail/completed/common/drawer.tsx diff --git a/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx b/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx index 48f63ae994..b19356cac7 100644 --- a/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx +++ b/web/app/components/datasets/documents/detail/completed/child-segment-list.tsx @@ -95,12 +95,13 @@ const ChildSegmentList: FC = ({ )}> {isFullDocMode ? : null}
-
{ event.stopPropagation() toggleCollapse() @@ -162,6 +163,7 @@ const ChildSegmentList: FC = ({ label={`C-${childChunk.position}${edited ? ` ยท ${t('datasetDocuments.segment.edited')}` : ''}`} text={childChunk.content} onDelete={() => onDelete?.(childChunk.segment_id, childChunk.id)} + className='child-chunk' labelClassName={focused ? 'bg-state-accent-solid text-text-primary-on-surface' : ''} labelInnerClassName={'text-[10px] font-semibold align-bottom leading-6'} contentClassName={cn('!leading-6', focused ? 'bg-state-accent-hover-alt text-text-primary' : 'text-text-secondary')} diff --git a/web/app/components/datasets/documents/detail/completed/common/drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx new file mode 100644 index 0000000000..59ac9d76f1 --- /dev/null +++ b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx @@ -0,0 +1,104 @@ +import React, { useCallback, useEffect, useRef } from 'react' +import { createPortal } from 'react-dom' +import cn from '@/utils/classnames' +import { useKeyPress } from 'ahooks' +import { useSegmentListContext } from '..' + +type DrawerProps = { + open: boolean + onClose: () => void + side?: 'right' | 'left' | 'bottom' | 'top' + showOverlay?: boolean + modal?: boolean // click outside event can pass through if modal is false + closeOnOutsideClick?: boolean + panelClassName?: string + panelContentClassName?: string + needCheckChunks?: boolean +} + +const Drawer = ({ + open, + onClose, + side = 'right', + showOverlay = true, + modal = false, + needCheckChunks = false, + children, + panelClassName, + panelContentClassName, +}: React.PropsWithChildren) => { + const panelContentRef = useRef(null) + const currSegment = useSegmentListContext(s => s.currSegment) + const currChildChunk = useSegmentListContext(s => s.currChildChunk) + + useKeyPress('esc', (e) => { + e.preventDefault() + onClose() + }, { exactMatch: true, useCapture: true }) + + const onDownCapture = useCallback((e: PointerEvent) => { + if (!open || modal) return + const panelContent = panelContentRef.current + if (!panelContent) return + const target = e.target as Node | null + const chunks = document.querySelectorAll('.chunk-card') + const childChunks = document.querySelectorAll('.child-chunk') + const isClickOnChunk = Array.from(chunks).some((chunk) => { + return chunk && chunk.contains(target) + }) + const isClickOnChildChunk = Array.from(childChunks).some((chunk) => { + return chunk && chunk.contains(target) + }) + const reopenChunkDetail = (currSegment.showModal && isClickOnChildChunk) + || (currChildChunk.showModal && isClickOnChunk && !isClickOnChildChunk) + if (target && !panelContent.contains(target) && (!needCheckChunks || reopenChunkDetail)) + queueMicrotask(onClose) + }, [currSegment, currChildChunk, needCheckChunks, onClose, open]) + + useEffect(() => { + window.addEventListener('pointerdown', onDownCapture, { capture: true }) + return () => + window.removeEventListener('pointerdown', onDownCapture, { capture: true }) + }, [onDownCapture]) + + const isHorizontal = side === 'left' || side === 'right' + + const content = ( +
+ {showOverlay ? ( + + ) + + return open && createPortal(content, document.body) +} + +export default Drawer diff --git a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx index cb19aff534..58a9539110 100644 --- a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx @@ -1,33 +1,42 @@ -import React, { type FC } from 'react' -import Drawer from '@/app/components/base/drawer' -import classNames from '@/utils/classnames' +import React from 'react' +import Drawer from './drawer' +import cn from '@/utils/classnames' import { noop } from 'lodash-es' type IFullScreenDrawerProps = { isOpen: boolean onClose?: () => void fullScreen: boolean - children: React.ReactNode + showOverlay?: boolean + needCheckChunks?: boolean + modal?: boolean } -const FullScreenDrawer: FC = ({ +const FullScreenDrawer = ({ isOpen, onClose = noop, fullScreen, children, -}) => { + showOverlay = true, + needCheckChunks = false, + modal = false, +}: React.PropsWithChildren) => { return ( {children} ) diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 894671f79e..726be7519a 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -153,7 +153,7 @@ const Completed: FC = ({ return docForm === ChunkingMode.parentChild && parentMode === 'full-doc' }, [docForm, parentMode]) - const { isFetching: isLoadingSegmentList, data: segmentListData } = useSegmentList( + const { isLoading: isLoadingSegmentList, data: segmentListData } = useSegmentList( { datasetId, documentId, @@ -183,7 +183,7 @@ const Completed: FC = ({ } }, [segments]) - const { isFetching: isLoadingChildSegmentList, data: childChunkListData } = useChildSegmentList( + const { isLoading: isLoadingChildSegmentList, data: childChunkListData } = useChildSegmentList( { datasetId, documentId, @@ -664,8 +664,11 @@ const Completed: FC = ({ isOpen={currSegment.showModal} fullScreen={fullScreen} onClose={onCloseSegmentDetail} + showOverlay={false} + needCheckChunks > = ({ isOpen={showNewSegmentModal} fullScreen={fullScreen} onClose={onCloseNewSegmentModal} + modal > = ({ isOpen={currChildChunk.showModal} fullScreen={fullScreen} onClose={onCloseChildSegmentDetail} + showOverlay={false} + needCheckChunks > = ({ isOpen={showNewChildSegmentModal} fullScreen={fullScreen} onClose={onCloseNewChildChunkModal} + modal > = ({ /> {/* Batch Action Buttons */} - {selectedSegmentIds.length > 0 - && 0 && ( + } + /> + )} ) } diff --git a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx index 80dced041e..f15f3dbd11 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx @@ -118,7 +118,7 @@ const SegmentCard: FC = ({ return (