mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 19:30:17 +08:00
fix: improve multi-select functionality in Messages and SelectionBox components
This commit is contained in:
parent
1b6cba454d
commit
c4e0744806
@ -80,7 +80,11 @@ export const useChatContext = (activeTopic: Topic) => {
|
||||
(messageId: string, selected: boolean) => {
|
||||
dispatch(
|
||||
setSelectedMessageIds(
|
||||
selected ? [...selectedMessageIds, messageId] : selectedMessageIds.filter((id) => id !== messageId)
|
||||
selected
|
||||
? selectedMessageIds.includes(messageId)
|
||||
? selectedMessageIds
|
||||
: [...selectedMessageIds, messageId]
|
||||
: selectedMessageIds.filter((id) => id !== messageId)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
@ -2,11 +2,13 @@ import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
||||
import SelectionBox from '@renderer/pages/home/Messages/SelectionBox'
|
||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService'
|
||||
@ -64,7 +66,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
||||
const { displayCount, clearTopicMessages, deleteMessage, createTopicBranch } = useMessageOperations(topic)
|
||||
const messagesRef = useRef<Message[]>(messages)
|
||||
|
||||
// const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
||||
const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
||||
|
||||
useEffect(() => {
|
||||
messagesRef.current = messages
|
||||
@ -313,13 +315,13 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
||||
</NarrowLayout>
|
||||
{messageNavigation === 'anchor' && <MessageAnchorLine messages={displayMessages} />}
|
||||
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
||||
{/* TODO: 多选功能实现有问题,需要重新改改 */}
|
||||
{/* <SelectionBox
|
||||
|
||||
<SelectionBox
|
||||
isMultiSelectMode={isMultiSelectMode}
|
||||
scrollContainerRef={scrollContainerRef}
|
||||
messageElements={messageElements.current}
|
||||
handleSelectMessage={handleSelectMessage}
|
||||
/> */}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface SelectionBoxProps {
|
||||
@ -18,6 +18,8 @@ const SelectionBox: React.FC<SelectionBoxProps> = ({
|
||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
|
||||
const [dragCurrent, setDragCurrent] = useState({ x: 0, y: 0 })
|
||||
|
||||
const dragSelectedIds = useRef<Set<string>>(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMultiSelectMode) return
|
||||
|
||||
@ -25,96 +27,90 @@ const SelectionBox: React.FC<SelectionBoxProps> = ({
|
||||
const container = scrollContainerRef.current!
|
||||
if (!container) return { x: 0, y: 0 }
|
||||
const rect = container.getBoundingClientRect()
|
||||
const x = e.clientX - rect.left + container.scrollLeft
|
||||
const y = e.clientY - rect.top + container.scrollTop
|
||||
return { x, y }
|
||||
return {
|
||||
x: e.clientX - rect.left + container.scrollLeft,
|
||||
y: e.clientY - rect.top + container.scrollTop
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest('.ant-checkbox-wrapper')) return
|
||||
if ((e.target as HTMLElement).closest('.MessageFooter')) return
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
setIsDragging(true)
|
||||
const pos = updateDragPos(e)
|
||||
setDragStart(pos)
|
||||
setDragCurrent(pos)
|
||||
dragSelectedIds.current.clear()
|
||||
document.body.classList.add('no-select')
|
||||
}
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging) return
|
||||
setDragCurrent(updateDragPos(e))
|
||||
const container = scrollContainerRef.current!
|
||||
if (container) {
|
||||
const { top, bottom } = container.getBoundingClientRect()
|
||||
const scrollSpeed = 15
|
||||
if (e.clientY < top + 50) {
|
||||
container.scrollBy(0, -scrollSpeed)
|
||||
} else if (e.clientY > bottom - 50) {
|
||||
container.scrollBy(0, scrollSpeed)
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
const pos = updateDragPos(e)
|
||||
setDragCurrent(pos)
|
||||
|
||||
// 计算当前框选矩形
|
||||
const left = Math.min(dragStart.x, pos.x)
|
||||
const right = Math.max(dragStart.x, pos.x)
|
||||
const top = Math.min(dragStart.y, pos.y)
|
||||
const bottom = Math.max(dragStart.y, pos.y)
|
||||
|
||||
// 创建新选中的消息ID集合
|
||||
const newSelectedIds = new Set<string>()
|
||||
|
||||
messageElements.forEach((el, id) => {
|
||||
// 检查消息是否已被选中(不管是拖动选中还是手动选中)
|
||||
const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement | null
|
||||
const isAlreadySelected = checkbox?.checked || false
|
||||
|
||||
// 如果已经被记录为拖动选中,跳过
|
||||
if (dragSelectedIds.current.has(id)) return
|
||||
|
||||
const rect = el.getBoundingClientRect()
|
||||
const container = scrollContainerRef.current!
|
||||
const eTop = rect.top - container.getBoundingClientRect().top + container.scrollTop
|
||||
const eLeft = rect.left - container.getBoundingClientRect().left + container.scrollLeft
|
||||
const eBottom = eTop + rect.height
|
||||
const eRight = eLeft + rect.width
|
||||
|
||||
// 检查消息是否在当前选择框内
|
||||
const isInSelectionBox = !(eRight < left || eLeft > right || eBottom < top || eTop > bottom)
|
||||
|
||||
// 只有在选择框内且未被选中的消息才需要处理
|
||||
if (isInSelectionBox && !isAlreadySelected) {
|
||||
handleSelectMessage(id, true)
|
||||
dragSelectedIds.current.add(id)
|
||||
newSelectedIds.add(id)
|
||||
el.classList.add('selection-highlight')
|
||||
setTimeout(() => el.classList.remove('selection-highlight'), 300)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!isDragging) return
|
||||
|
||||
const left = Math.min(dragStart.x, dragCurrent.x)
|
||||
const right = Math.max(dragStart.x, dragCurrent.x)
|
||||
const top = Math.min(dragStart.y, dragCurrent.y)
|
||||
const bottom = Math.max(dragStart.y, dragCurrent.y)
|
||||
|
||||
const MIN_SELECTION_SIZE = 5
|
||||
const isValidSelection =
|
||||
Math.abs(right - left) > MIN_SELECTION_SIZE && Math.abs(bottom - top) > MIN_SELECTION_SIZE
|
||||
|
||||
if (isValidSelection) {
|
||||
messageElements.forEach((element, messageId) => {
|
||||
try {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const container = scrollContainerRef.current!
|
||||
|
||||
const elementTop = rect.top - container.getBoundingClientRect().top + container.scrollTop
|
||||
const elementLeft = rect.left - container.getBoundingClientRect().left + container.scrollLeft
|
||||
const elementBottom = elementTop + rect.height
|
||||
const elementRight = elementLeft + rect.width
|
||||
|
||||
const isIntersecting = !(
|
||||
elementRight < left ||
|
||||
elementLeft > right ||
|
||||
elementBottom < top ||
|
||||
elementTop > bottom
|
||||
)
|
||||
|
||||
if (isIntersecting) {
|
||||
handleSelectMessage(messageId, true)
|
||||
element.classList.add('selection-highlight')
|
||||
setTimeout(() => element.classList.remove('selection-highlight'), 300)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error calculating element intersection:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
setIsDragging(false)
|
||||
document.body.classList.remove('no-select')
|
||||
}
|
||||
|
||||
const container = scrollContainerRef.current!
|
||||
if (container) {
|
||||
container.addEventListener('mousedown', handleMouseDown)
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
container?.addEventListener('mousedown', handleMouseDown)
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener('mousedown', handleMouseDown)
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
document.body.classList.remove('no-select')
|
||||
}
|
||||
container?.removeEventListener('mousedown', handleMouseDown)
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
document.body.classList.remove('no-select')
|
||||
}
|
||||
}, [isMultiSelectMode, isDragging, dragStart, dragCurrent, handleSelectMessage, scrollContainerRef, messageElements])
|
||||
}, [isMultiSelectMode, isDragging, dragStart, scrollContainerRef, messageElements, handleSelectMessage])
|
||||
|
||||
if (!isDragging || !isMultiSelectMode) return null
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user