feat: added drag and drop sorting for topics list

This commit is contained in:
kangfenmao 2024-07-10 17:11:59 +08:00
parent cf3ba3c440
commit c9b57536c4
6 changed files with 66 additions and 23 deletions

View File

@ -8,6 +8,7 @@ import {
updateAssistants as _updateAssistants, updateAssistants as _updateAssistants,
updateDefaultAssistant as _updateDefaultAssistant, updateDefaultAssistant as _updateDefaultAssistant,
updateTopic as _updateTopic, updateTopic as _updateTopic,
updateTopics as _updateTopics,
addAssistant, addAssistant,
removeAssistant, removeAssistant,
updateAssistant updateAssistant
@ -46,6 +47,7 @@ export function useAssistant(id: string) {
addTopic: (topic: Topic) => dispatch(_addTopic({ assistantId: assistant.id, topic })), addTopic: (topic: Topic) => dispatch(_addTopic({ assistantId: assistant.id, topic })),
removeTopic: (topic: Topic) => dispatch(_removeTopic({ assistantId: assistant.id, topic })), removeTopic: (topic: Topic) => dispatch(_removeTopic({ assistantId: assistant.id, topic })),
updateTopic: (topic: Topic) => dispatch(_updateTopic({ assistantId: assistant.id, topic })), updateTopic: (topic: Topic) => dispatch(_updateTopic({ assistantId: assistant.id, topic })),
updateTopics: (topics: Topic[]) => dispatch(_updateTopics({ assistantId: assistant.id, topics })),
removeAllTopics: () => dispatch(_removeAllTopics({ assistantId: assistant.id })), removeAllTopics: () => dispatch(_removeAllTopics({ assistantId: assistant.id })),
setModel: (model: Model) => dispatch(_setModel({ assistantId: assistant.id, model })) setModel: (model: Model) => dispatch(_setModel({ assistantId: assistant.id, model }))
} }

View File

@ -1,12 +1,16 @@
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { find } from 'lodash'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
export function useActiveTopic(assistant: Assistant) { export function useActiveTopic(assistant: Assistant) {
const [activeTopic, setActiveTopic] = useState(assistant?.topics[0]) const [activeTopic, setActiveTopic] = useState(assistant?.topics[0])
useEffect(() => { useEffect(() => {
assistant?.topics && setActiveTopic(assistant?.topics[0]) // activeTopic not in assistant.topics
}, [assistant]) if (!find(assistant.topics, { id: activeTopic?.id })) {
setActiveTopic(assistant.topics[0])
}
}, [activeTopic?.id, assistant])
return { activeTopic, setActiveTopic } return { activeTopic, setActiveTopic }
} }

View File

@ -4,7 +4,7 @@ import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingP
import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistants } from '@renderer/hooks/useAssistant'
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { uuid } from '@renderer/utils' import { droppableReorder, uuid } from '@renderer/utils'
import { Dropdown, MenuProps } from 'antd' import { Dropdown, MenuProps } from 'antd'
import { last } from 'lodash' import { last } from 'lodash'
import { FC, useRef } from 'react' import { FC, useRef } from 'react'
@ -16,13 +16,6 @@ interface Props {
onCreateAssistant: () => void onCreateAssistant: () => void
} }
const reorder = (list: Assistant[], startIndex: number, endIndex: number, len = 1) => {
const result = Array.from(list)
const removed = result.splice(startIndex, len)
result.splice(endIndex, 0, ...removed)
return result
}
const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => { const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAssistant }) => {
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants() const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants()
const targetAssistant = useRef<Assistant | null>(null) const targetAssistant = useRef<Assistant | null>(null)
@ -73,7 +66,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
if (result.destination) { if (result.destination) {
const sourceIndex = result.source.index const sourceIndex = result.source.index
const destIndex = result.destination.index const destIndex = result.destination.index
const reorderAssistants = reorder(assistants, sourceIndex, destIndex) const reorderAssistants = droppableReorder<Assistant>(assistants, sourceIndex, destIndex)
updateAssistants(reorderAssistants) updateAssistants(reorderAssistants)
} }
} }

View File

@ -8,6 +8,8 @@ import { FC, useRef } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons' import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons'
import LocalStorage from '@renderer/services/storage' import LocalStorage from '@renderer/services/storage'
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import { droppableReorder } from '@renderer/utils'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
@ -17,7 +19,7 @@ interface Props {
const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => { const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
const { showRightSidebar } = useShowRightSidebar() const { showRightSidebar } = useShowRightSidebar()
const { removeTopic, updateTopic, removeAllTopics } = useAssistant(assistant.id) const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id)
const currentTopic = useRef<Topic | null>(null) const currentTopic = useRef<Topic | null>(null)
const topicMenuItems: MenuProps['items'] = [ const topicMenuItems: MenuProps['items'] = [
@ -70,6 +72,14 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
}) })
} }
const onDragEnd = (result: DropResult) => {
if (result.destination) {
const sourceIndex = result.source.index
const destIndex = result.destination.index
updateTopics(droppableReorder(assistant.topics, sourceIndex, destIndex))
}
}
if (!showRightSidebar) { if (!showRightSidebar) {
return null return null
} }
@ -92,17 +102,33 @@ const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
</DeleteButton> </DeleteButton>
</Popconfirm> </Popconfirm>
</TopicTitle> </TopicTitle>
{assistant.topics.map((topic) => ( <DragDropContext onDragEnd={onDragEnd}>
<Dropdown <Droppable droppableId="droppable">
menu={{ items: topicMenuItems }} {(provided) => (
trigger={['contextMenu']} <div {...provided.droppableProps} ref={provided.innerRef}>
key={topic.id} {assistant.topics.map((topic, index) => (
onOpenChange={(open) => open && (currentTopic.current = topic)}> <Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
<TopicListItem className={topic.id === activeTopic?.id ? 'active' : ''} onClick={() => setActiveTopic(topic)}> {(provided) => (
{topic.name} <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
</TopicListItem> <Dropdown
</Dropdown> menu={{ items: topicMenuItems }}
))} trigger={['contextMenu']}
key={topic.id}
onOpenChange={(open) => open && (currentTopic.current = topic)}>
<TopicListItem
className={topic.id === activeTopic?.id ? 'active' : ''}
onClick={() => setActiveTopic(topic)}>
{topic.name}
</TopicListItem>
</Dropdown>
</div>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
</Container> </Container>
) )
} }

View File

@ -65,6 +65,16 @@ const assistantsSlice = createSlice({
: assistant : assistant
) )
}, },
updateTopics: (state, action: PayloadAction<{ assistantId: string; topics: Topic[] }>) => {
state.assistants = state.assistants.map((assistant) =>
assistant.id === action.payload.assistantId
? {
...assistant,
topics: action.payload.topics
}
: assistant
)
},
removeAllTopics: (state, action: PayloadAction<{ assistantId: string }>) => { removeAllTopics: (state, action: PayloadAction<{ assistantId: string }>) => {
state.assistants = state.assistants.map((assistant) => { state.assistants = state.assistants.map((assistant) => {
if (assistant.id === action.payload.assistantId) { if (assistant.id === action.payload.assistantId) {
@ -99,6 +109,7 @@ export const {
addTopic, addTopic,
removeTopic, removeTopic,
updateTopic, updateTopic,
updateTopics,
removeAllTopics, removeAllTopics,
setModel setModel
} = assistantsSlice.actions } = assistantsSlice.actions

View File

@ -81,3 +81,10 @@ export const getDefaultGroupName = (id: string) => {
return id.toUpperCase() return id.toUpperCase()
} }
export function droppableReorder<T>(list: T[], startIndex: number, endIndex: number, len = 1) {
const result = Array.from(list)
const removed = result.splice(startIndex, len)
result.splice(endIndex, 0, ...removed)
return result
}