mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 14:59:27 +08:00
feat: Optimize assistant drag-and-drop effect (#7115)
* feat: 优化助手拖拽效果 增加组内,组件,跨组拖拽效果 * feat: 提交注释 --------- Co-authored-by: linshuhao <nmnm1996>
This commit is contained in:
parent
38eb206a8a
commit
793c641e1c
@ -51,23 +51,27 @@ const DragableList: FC<Props<any>> = ({
|
|||||||
<VirtualList data={list} itemKey="id">
|
<VirtualList data={list} itemKey="id">
|
||||||
{(item, index) => {
|
{(item, index) => {
|
||||||
const id = item.id || item
|
const id = item.id || item
|
||||||
return (
|
if (!item.disabled) {
|
||||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
return (
|
||||||
{(provided) => (
|
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||||
<div
|
{(provided) => (
|
||||||
ref={provided.innerRef}
|
<div
|
||||||
{...provided.draggableProps}
|
ref={provided.innerRef}
|
||||||
{...provided.dragHandleProps}
|
{...provided.draggableProps}
|
||||||
style={{
|
{...provided.dragHandleProps}
|
||||||
marginBottom: 8,
|
style={{
|
||||||
...listStyle,
|
...listStyle,
|
||||||
...provided.draggableProps.style
|
...provided.draggableProps.style,
|
||||||
}}>
|
marginBottom: 8
|
||||||
{children(item, index)}
|
}}>
|
||||||
</div>
|
{children(item, index)}
|
||||||
)}
|
</div>
|
||||||
</Draggable>
|
)}
|
||||||
)
|
</Draggable>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <div> {children(item, index)}</div>
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
</VirtualList>
|
</VirtualList>
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const useTags = () => {
|
|||||||
|
|
||||||
// 计算所有标签
|
// 计算所有标签
|
||||||
const allTags = useMemo(() => {
|
const allTags = useMemo(() => {
|
||||||
const tags = uniq(flatMap(assistants, (assistant) => assistant.tags || []))
|
const tags = uniq(flatMap(assistants, (assistant) => assistant?.tags || []))
|
||||||
if (savedTagsOrder.length > 0) {
|
if (savedTagsOrder.length > 0) {
|
||||||
return [
|
return [
|
||||||
...savedTagsOrder.filter((tag) => tags.includes(tag)),
|
...savedTagsOrder.filter((tag) => tags.includes(tag)),
|
||||||
@ -69,6 +69,7 @@ export const useTags = () => {
|
|||||||
|
|
||||||
// 按标签分组并构建结果
|
// 按标签分组并构建结果
|
||||||
const grouped = Object.entries(groupBy(assistantsByTags, 'tag')).map(([tag, group]) => ({
|
const grouped = Object.entries(groupBy(assistantsByTags, 'tag')).map(([tag, group]) => ({
|
||||||
|
id: tag,
|
||||||
tag,
|
tag,
|
||||||
assistants: group.map((g) => g.assistant)
|
assistants: group.map((g) => g.assistant)
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { DownOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons'
|
import { DownOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons'
|
||||||
|
import { Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
||||||
import DragableList from '@renderer/components/DragableList'
|
import DragableList from '@renderer/components/DragableList'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
@ -30,7 +31,7 @@ const Assistants: FC<AssistantsTabProps> = ({
|
|||||||
const [collapsedTags, setCollapsedTags] = useState<Record<string, boolean>>({})
|
const [collapsedTags, setCollapsedTags] = useState<Record<string, boolean>>({})
|
||||||
const { addAgent } = useAgents()
|
const { addAgent } = useAgents()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { getGroupedAssistants } = useTags()
|
const { getGroupedAssistants, allTags, updateTagsOrder } = useTags()
|
||||||
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@ -59,72 +60,168 @@ const Assistants: FC<AssistantsTabProps> = ({
|
|||||||
},
|
},
|
||||||
[setAssistantsTabSortType]
|
[setAssistantsTabSortType]
|
||||||
)
|
)
|
||||||
|
// 修改tag的方式得调整
|
||||||
|
// 多条修改 同时修改assistants的tag
|
||||||
const handleGroupReorder = useCallback(
|
const handleGroupReorder = useCallback(
|
||||||
(tag: string, newGroupList: Assistant[]) => {
|
(newGroupList: { tag: string; newGroup: Assistant[] }[], _assistants: Assistant[]) => {
|
||||||
let insertIndex = 0
|
const insertIndexMap = {}
|
||||||
const newGlobal = assistants.map((a) => {
|
|
||||||
const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')]
|
newGroupList.map((_) => {
|
||||||
if (tags.includes(tag)) {
|
insertIndexMap[_.tag] = 0
|
||||||
const replaced = newGroupList[insertIndex]
|
})
|
||||||
insertIndex += 1
|
|
||||||
return replaced
|
const newGlobal = _assistants.map((a) => {
|
||||||
|
const tag = (a.tags?.length ? a.tags : [t('assistants.tags.untagged')])[0]
|
||||||
|
for (const group of newGroupList) {
|
||||||
|
if (group.tag === tag) {
|
||||||
|
const replaced = group.newGroup[insertIndexMap[tag]]
|
||||||
|
insertIndexMap[tag] += 1
|
||||||
|
return replaced
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
})
|
})
|
||||||
updateAssistants(newGlobal)
|
updateAssistants(newGlobal)
|
||||||
},
|
},
|
||||||
[assistants, t, updateAssistants]
|
[t, updateAssistants]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleGroupDragEnd = useCallback(
|
||||||
|
(result: DropResult) => {
|
||||||
|
const { type, source, destination } = result
|
||||||
|
if (!destination) return // 没有目标视作放弃移动 或者后续可以改成删除?
|
||||||
|
if (type === 'ASSISTANT') {
|
||||||
|
const sourceTag = source.droppableId
|
||||||
|
const destTag = destination?.droppableId
|
||||||
|
if (sourceTag !== destTag) {
|
||||||
|
// 组件移动的时候要修改tag再移动
|
||||||
|
// 移动到不同组
|
||||||
|
const sourceGroup = getGroupedAssistants.find((g) => g.id === sourceTag)
|
||||||
|
const sourceGroupAssistants = [...sourceGroup!.assistants]
|
||||||
|
const destGroup = getGroupedAssistants.find((g) => g.id === destTag)
|
||||||
|
const destGroupAssistants = [...destGroup!.assistants]
|
||||||
|
|
||||||
|
// 未分组的情况 分组的加上
|
||||||
|
const sourceAssitant = {
|
||||||
|
...sourceGroupAssistants.splice(source.index, 1)[0]
|
||||||
|
}
|
||||||
|
if (destTag === t('assistants.tags.untagged')) {
|
||||||
|
delete sourceAssitant.tags
|
||||||
|
} else {
|
||||||
|
sourceAssitant.tags = [destGroup!.tag]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改对应tag
|
||||||
|
const _assistants = assistants.map((_) => {
|
||||||
|
if (_.id === sourceAssitant.id) {
|
||||||
|
const newAssistant = { ..._ }
|
||||||
|
if (destTag === t('assistants.tags.untagged')) {
|
||||||
|
delete newAssistant.tags
|
||||||
|
} else {
|
||||||
|
newAssistant.tags = [destGroup!.tag]
|
||||||
|
}
|
||||||
|
return newAssistant
|
||||||
|
}
|
||||||
|
return _
|
||||||
|
})
|
||||||
|
|
||||||
|
// 进行置换
|
||||||
|
destGroupAssistants?.splice(destination.index, 0, sourceAssitant)
|
||||||
|
|
||||||
|
return handleGroupReorder(
|
||||||
|
[
|
||||||
|
{ tag: sourceTag, newGroup: sourceGroupAssistants },
|
||||||
|
{ tag: destTag, newGroup: destGroupAssistants }
|
||||||
|
],
|
||||||
|
_assistants
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (sourceTag === destTag) {
|
||||||
|
// 移动到同一组
|
||||||
|
const sourceGroup = getGroupedAssistants.find((g) => g.id === sourceTag)
|
||||||
|
const sourceAssitant = sourceGroup!.assistants.splice(source.index, 1)
|
||||||
|
sourceGroup!.assistants.splice(destination.index, 0, sourceAssitant[0])
|
||||||
|
|
||||||
|
return handleGroupReorder([{ tag: sourceTag, newGroup: sourceGroup!.assistants }], assistants)
|
||||||
|
}
|
||||||
|
} else if (type === 'TAG') {
|
||||||
|
const items = Array.from(allTags)
|
||||||
|
const [reorderedItem] = items.splice(source.index - 1, 1)
|
||||||
|
items.splice(destination.index - 1, 0, reorderedItem)
|
||||||
|
updateTagsOrder(items)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
return
|
||||||
|
},
|
||||||
|
[allTags, assistants, getGroupedAssistants, handleGroupReorder, t, updateTagsOrder]
|
||||||
|
)
|
||||||
|
//尝试过使用两个DragableList 但是会有问题
|
||||||
|
//也试过不用DragList 直接写 但是会有问题
|
||||||
|
//发现只有这样写是符合预期效果的
|
||||||
if (assistantsTabSortType === 'tags') {
|
if (assistantsTabSortType === 'tags') {
|
||||||
return (
|
return (
|
||||||
<Container className="assistants-tab" ref={containerRef}>
|
<Container className="assistants-tab" ref={containerRef}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: 4, gap: 10 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: 4, gap: 10 }}>
|
||||||
{getGroupedAssistants.map((group) => (
|
<DragableList
|
||||||
<TagsContainer key={group.tag}>
|
droppableProps={{ type: 'TAG' }}
|
||||||
{group.tag !== t('assistants.tags.untagged') && (
|
list={getGroupedAssistants.map((_) => ({
|
||||||
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
..._,
|
||||||
<Tooltip title={group.tag}>
|
disabled: _.tag === t('assistants.tags.untagged')
|
||||||
<GroupTitleName>
|
}))}
|
||||||
{collapsedTags[group.tag] ? (
|
onUpdate={() => {}}
|
||||||
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
onDragEnd={handleGroupDragEnd}
|
||||||
) : (
|
style={{ paddingBottom: 0 }}>
|
||||||
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
{(group) => (
|
||||||
)}
|
<Droppable droppableId={group.tag} type="ASSISTANT">
|
||||||
{group.tag}
|
{(provided) => (
|
||||||
</GroupTitleName>
|
<TagsContainer key={group.tag} {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
</Tooltip>
|
{group.tag !== t('assistants.tags.untagged') && (
|
||||||
<GroupTitleDivider />
|
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
||||||
</GroupTitle>
|
<Tooltip title={group.tag}>
|
||||||
)}
|
<GroupTitleName>
|
||||||
{!collapsedTags[group.tag] && (
|
{collapsedTags[group.tag] ? (
|
||||||
<div>
|
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||||
<DragableList
|
) : (
|
||||||
list={group.assistants}
|
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||||
onUpdate={(newList) => handleGroupReorder(group.tag, newList)}
|
)}
|
||||||
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
{group.tag}
|
||||||
onDragStart={() => setDragging(true)}
|
</GroupTitleName>
|
||||||
onDragEnd={() => setDragging(false)}>
|
</Tooltip>
|
||||||
{(assistant) => (
|
<GroupTitleDivider />
|
||||||
<AssistantItem
|
</GroupTitle>
|
||||||
key={assistant.id}
|
|
||||||
assistant={assistant}
|
|
||||||
isActive={assistant.id === activeAssistant.id}
|
|
||||||
sortBy={assistantsTabSortType}
|
|
||||||
onSwitch={setActiveAssistant}
|
|
||||||
onDelete={onDelete}
|
|
||||||
addAgent={addAgent}
|
|
||||||
addAssistant={addAssistant}
|
|
||||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
|
||||||
handleSortByChange={handleSortByChange}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</DragableList>
|
{!collapsedTags[group.tag] && (
|
||||||
</div>
|
<div>
|
||||||
)}
|
{group.assistants.map((assistant, index) => (
|
||||||
</TagsContainer>
|
<Draggable
|
||||||
))}
|
key={`draggable_${group.tag}_${assistant?.id}_${index}`}
|
||||||
|
draggableId={`draggable_${group.tag}_${assistant?.id}_${index}`}
|
||||||
|
index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||||
|
<AssistantItem
|
||||||
|
key={assistant?.id}
|
||||||
|
assistant={assistant}
|
||||||
|
sortBy="tags"
|
||||||
|
isActive={assistant?.id === activeAssistant.id}
|
||||||
|
onSwitch={setActiveAssistant}
|
||||||
|
onDelete={onDelete}
|
||||||
|
addAgent={addAgent}
|
||||||
|
addAssistant={addAssistant}
|
||||||
|
onCreateDefaultAssistant={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{provided.placeholder}
|
||||||
|
</TagsContainer>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
)}
|
||||||
|
</DragableList>
|
||||||
</div>
|
</div>
|
||||||
<AssistantAddItem onClick={onCreateAssistant}>
|
<AssistantAddItem onClick={onCreateAssistant}>
|
||||||
<AssistantName>
|
<AssistantName>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user