mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-11 16:39:15 +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) => {
|
(messageId: string, selected: boolean) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setSelectedMessageIds(
|
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 Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
import { LOAD_MORE_COUNT } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||||
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
import useScrollPosition from '@renderer/hooks/useScrollPosition'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic'
|
||||||
|
import SelectionBox from '@renderer/pages/home/Messages/SelectionBox'
|
||||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import { getContextCount, getGroupedMessages, getUserMessage } from '@renderer/services/MessagesService'
|
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 { displayCount, clearTopicMessages, deleteMessage, createTopicBranch } = useMessageOperations(topic)
|
||||||
const messagesRef = useRef<Message[]>(messages)
|
const messagesRef = useRef<Message[]>(messages)
|
||||||
|
|
||||||
// const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
const { isMultiSelectMode, handleSelectMessage } = useChatContext(topic)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesRef.current = messages
|
messagesRef.current = messages
|
||||||
@ -313,13 +315,13 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
|||||||
</NarrowLayout>
|
</NarrowLayout>
|
||||||
{messageNavigation === 'anchor' && <MessageAnchorLine messages={displayMessages} />}
|
{messageNavigation === 'anchor' && <MessageAnchorLine messages={displayMessages} />}
|
||||||
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
||||||
{/* TODO: 多选功能实现有问题,需要重新改改 */}
|
|
||||||
{/* <SelectionBox
|
<SelectionBox
|
||||||
isMultiSelectMode={isMultiSelectMode}
|
isMultiSelectMode={isMultiSelectMode}
|
||||||
scrollContainerRef={scrollContainerRef}
|
scrollContainerRef={scrollContainerRef}
|
||||||
messageElements={messageElements.current}
|
messageElements={messageElements.current}
|
||||||
handleSelectMessage={handleSelectMessage}
|
handleSelectMessage={handleSelectMessage}
|
||||||
/> */}
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface SelectionBoxProps {
|
interface SelectionBoxProps {
|
||||||
@ -18,6 +18,8 @@ const SelectionBox: React.FC<SelectionBoxProps> = ({
|
|||||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
|
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
|
||||||
const [dragCurrent, setDragCurrent] = useState({ x: 0, y: 0 })
|
const [dragCurrent, setDragCurrent] = useState({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
const dragSelectedIds = useRef<Set<string>>(new Set())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMultiSelectMode) return
|
if (!isMultiSelectMode) return
|
||||||
|
|
||||||
@ -25,96 +27,90 @@ const SelectionBox: React.FC<SelectionBoxProps> = ({
|
|||||||
const container = scrollContainerRef.current!
|
const container = scrollContainerRef.current!
|
||||||
if (!container) return { x: 0, y: 0 }
|
if (!container) return { x: 0, y: 0 }
|
||||||
const rect = container.getBoundingClientRect()
|
const rect = container.getBoundingClientRect()
|
||||||
const x = e.clientX - rect.left + container.scrollLeft
|
return {
|
||||||
const y = e.clientY - rect.top + container.scrollTop
|
x: e.clientX - rect.left + container.scrollLeft,
|
||||||
return { x, y }
|
y: e.clientY - rect.top + container.scrollTop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
if ((e.target as HTMLElement).closest('.ant-checkbox-wrapper')) return
|
if ((e.target as HTMLElement).closest('.ant-checkbox-wrapper')) return
|
||||||
if ((e.target as HTMLElement).closest('.MessageFooter')) return
|
if ((e.target as HTMLElement).closest('.MessageFooter')) return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
setIsDragging(true)
|
setIsDragging(true)
|
||||||
const pos = updateDragPos(e)
|
const pos = updateDragPos(e)
|
||||||
setDragStart(pos)
|
setDragStart(pos)
|
||||||
setDragCurrent(pos)
|
setDragCurrent(pos)
|
||||||
|
dragSelectedIds.current.clear()
|
||||||
document.body.classList.add('no-select')
|
document.body.classList.add('no-select')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!isDragging) return
|
if (!isDragging) return
|
||||||
setDragCurrent(updateDragPos(e))
|
|
||||||
const container = scrollContainerRef.current!
|
e.preventDefault()
|
||||||
if (container) {
|
|
||||||
const { top, bottom } = container.getBoundingClientRect()
|
const pos = updateDragPos(e)
|
||||||
const scrollSpeed = 15
|
setDragCurrent(pos)
|
||||||
if (e.clientY < top + 50) {
|
|
||||||
container.scrollBy(0, -scrollSpeed)
|
// 计算当前框选矩形
|
||||||
} else if (e.clientY > bottom - 50) {
|
const left = Math.min(dragStart.x, pos.x)
|
||||||
container.scrollBy(0, scrollSpeed)
|
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 = () => {
|
const handleMouseUp = () => {
|
||||||
if (!isDragging) return
|
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)
|
setIsDragging(false)
|
||||||
document.body.classList.remove('no-select')
|
document.body.classList.remove('no-select')
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = scrollContainerRef.current!
|
const container = scrollContainerRef.current!
|
||||||
if (container) {
|
container?.addEventListener('mousedown', handleMouseDown)
|
||||||
container.addEventListener('mousedown', handleMouseDown)
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
window.addEventListener('mousemove', handleMouseMove)
|
window.addEventListener('mouseup', handleMouseUp)
|
||||||
window.addEventListener('mouseup', handleMouseUp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (container) {
|
container?.removeEventListener('mousedown', handleMouseDown)
|
||||||
container.removeEventListener('mousedown', handleMouseDown)
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
window.removeEventListener('mousemove', handleMouseMove)
|
window.removeEventListener('mouseup', handleMouseUp)
|
||||||
window.removeEventListener('mouseup', handleMouseUp)
|
document.body.classList.remove('no-select')
|
||||||
document.body.classList.remove('no-select')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [isMultiSelectMode, isDragging, dragStart, dragCurrent, handleSelectMessage, scrollContainerRef, messageElements])
|
}, [isMultiSelectMode, isDragging, dragStart, scrollContainerRef, messageElements, handleSelectMessage])
|
||||||
|
|
||||||
if (!isDragging || !isMultiSelectMode) return null
|
if (!isDragging || !isMultiSelectMode) return null
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user