mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 11:44:28 +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">
|
||||
{(item, index) => {
|
||||
const id = item.id || item
|
||||
return (
|
||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
...listStyle,
|
||||
...provided.draggableProps.style
|
||||
}}>
|
||||
{children(item, index)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)
|
||||
if (!item.disabled) {
|
||||
return (
|
||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
...listStyle,
|
||||
...provided.draggableProps.style,
|
||||
marginBottom: 8
|
||||
}}>
|
||||
{children(item, index)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)
|
||||
} else {
|
||||
return <div> {children(item, index)}</div>
|
||||
}
|
||||
}}
|
||||
</VirtualList>
|
||||
{provided.placeholder}
|
||||
|
||||
@ -23,7 +23,7 @@ export const useTags = () => {
|
||||
|
||||
// 计算所有标签
|
||||
const allTags = useMemo(() => {
|
||||
const tags = uniq(flatMap(assistants, (assistant) => assistant.tags || []))
|
||||
const tags = uniq(flatMap(assistants, (assistant) => assistant?.tags || []))
|
||||
if (savedTagsOrder.length > 0) {
|
||||
return [
|
||||
...savedTagsOrder.filter((tag) => tags.includes(tag)),
|
||||
@ -69,6 +69,7 @@ export const useTags = () => {
|
||||
|
||||
// 按标签分组并构建结果
|
||||
const grouped = Object.entries(groupBy(assistantsByTags, 'tag')).map(([tag, group]) => ({
|
||||
id: tag,
|
||||
tag,
|
||||
assistants: group.map((g) => g.assistant)
|
||||
}))
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { DownOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons'
|
||||
import { Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
@ -30,7 +31,7 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
const [collapsedTags, setCollapsedTags] = useState<Record<string, boolean>>({})
|
||||
const { addAgent } = useAgents()
|
||||
const { t } = useTranslation()
|
||||
const { getGroupedAssistants } = useTags()
|
||||
const { getGroupedAssistants, allTags, updateTagsOrder } = useTags()
|
||||
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -59,72 +60,168 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
},
|
||||
[setAssistantsTabSortType]
|
||||
)
|
||||
|
||||
// 修改tag的方式得调整
|
||||
// 多条修改 同时修改assistants的tag
|
||||
const handleGroupReorder = useCallback(
|
||||
(tag: string, newGroupList: Assistant[]) => {
|
||||
let insertIndex = 0
|
||||
const newGlobal = assistants.map((a) => {
|
||||
const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')]
|
||||
if (tags.includes(tag)) {
|
||||
const replaced = newGroupList[insertIndex]
|
||||
insertIndex += 1
|
||||
return replaced
|
||||
(newGroupList: { tag: string; newGroup: Assistant[] }[], _assistants: Assistant[]) => {
|
||||
const insertIndexMap = {}
|
||||
|
||||
newGroupList.map((_) => {
|
||||
insertIndexMap[_.tag] = 0
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
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') {
|
||||
return (
|
||||
<Container className="assistants-tab" ref={containerRef}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: 4, gap: 10 }}>
|
||||
{getGroupedAssistants.map((group) => (
|
||||
<TagsContainer key={group.tag}>
|
||||
{group.tag !== t('assistants.tags.untagged') && (
|
||||
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>
|
||||
{collapsedTags[group.tag] ? (
|
||||
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
) : (
|
||||
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
)}
|
||||
{group.tag}
|
||||
</GroupTitleName>
|
||||
</Tooltip>
|
||||
<GroupTitleDivider />
|
||||
</GroupTitle>
|
||||
)}
|
||||
{!collapsedTags[group.tag] && (
|
||||
<div>
|
||||
<DragableList
|
||||
list={group.assistants}
|
||||
onUpdate={(newList) => handleGroupReorder(group.tag, newList)}
|
||||
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(assistant) => (
|
||||
<AssistantItem
|
||||
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
|
||||
droppableProps={{ type: 'TAG' }}
|
||||
list={getGroupedAssistants.map((_) => ({
|
||||
..._,
|
||||
disabled: _.tag === t('assistants.tags.untagged')
|
||||
}))}
|
||||
onUpdate={() => {}}
|
||||
onDragEnd={handleGroupDragEnd}
|
||||
style={{ paddingBottom: 0 }}>
|
||||
{(group) => (
|
||||
<Droppable droppableId={group.tag} type="ASSISTANT">
|
||||
{(provided) => (
|
||||
<TagsContainer key={group.tag} {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{group.tag !== t('assistants.tags.untagged') && (
|
||||
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>
|
||||
{collapsedTags[group.tag] ? (
|
||||
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
) : (
|
||||
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
)}
|
||||
{group.tag}
|
||||
</GroupTitleName>
|
||||
</Tooltip>
|
||||
<GroupTitleDivider />
|
||||
</GroupTitle>
|
||||
)}
|
||||
</DragableList>
|
||||
</div>
|
||||
)}
|
||||
</TagsContainer>
|
||||
))}
|
||||
{!collapsedTags[group.tag] && (
|
||||
<div>
|
||||
{group.assistants.map((assistant, index) => (
|
||||
<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>
|
||||
<AssistantAddItem onClick={onCreateAssistant}>
|
||||
<AssistantName>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user