feat: implement select mode menu autoscroll for long mode lists

This commit is contained in:
FischLu 2025-02-14 11:52:36 +01:00 committed by 亢奋猫
parent 87bce69a13
commit 3f656ab202

View File

@ -8,7 +8,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
import { Model, Provider } from '@renderer/types' import { Model, Provider } from '@renderer/types'
import { Avatar, Dropdown, Tooltip } from 'antd' import { Avatar, Dropdown, Tooltip } from 'antd'
import { first, sortBy } from 'lodash' import { first, sortBy } from 'lodash'
import { FC, useEffect, useMemo, useRef, useState } from 'react' import { FC, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled, { createGlobalStyle } from 'styled-components' import styled, { createGlobalStyle } from 'styled-components'
@ -27,6 +27,11 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
const [searchText, setSearchText] = useState('') const [searchText, setSearchText] = useState('')
const itemRefs = useRef<Array<HTMLDivElement | null>>([])
const setItemRef = (index: number, el: HTMLDivElement | null) => {
itemRefs.current[index] = el
}
const togglePin = async (modelId: string) => { const togglePin = async (modelId: string) => {
const newPinnedModels = pinnedModels.includes(modelId) const newPinnedModels = pinnedModels.includes(modelId)
@ -167,9 +172,17 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
loadPinnedModels() loadPinnedModels()
}, []) }, [])
// Scroll to the first menu item when the mode selection menu opens
useLayoutEffect(() => {
if (isOpen && flatModelItems.length > 0 && itemRefs.current[0]) {
itemRefs.current[0].scrollIntoView({ block: 'nearest' })
}
}, [isOpen, flatModelItems])
useEffect(() => { useEffect(() => {
const showModelSelector = () => { const showModelSelector = () => {
dropdownRef.current?.click() dropdownRef.current?.click()
itemRefs.current = []
setIsOpen(true) setIsOpen(true)
setSelectedIndex(0) setSelectedIndex(0)
setSearchText('') setSearchText('')
@ -180,10 +193,18 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
if (e.key === 'ArrowDown') { if (e.key === 'ArrowDown') {
e.preventDefault() e.preventDefault()
setSelectedIndex((prev) => (prev < flatModelItems.length - 1 ? prev + 1 : 0)) setSelectedIndex((prev) => {
const newIndex = prev < flatModelItems.length - 1 ? prev + 1 : 0
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
return newIndex
})
} else if (e.key === 'ArrowUp') { } else if (e.key === 'ArrowUp') {
e.preventDefault() e.preventDefault()
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : flatModelItems.length - 1)) setSelectedIndex((prev) => {
const newIndex = prev > 0 ? prev - 1 : flatModelItems.length - 1
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
return newIndex
})
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
e.preventDefault() e.preventDefault()
if (selectedIndex >= 0 && selectedIndex < flatModelItems.length) { if (selectedIndex >= 0 && selectedIndex < flatModelItems.length) {
@ -250,15 +271,20 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
<div key={group.key} className="ant-dropdown-menu-item-group"> <div key={group.key} className="ant-dropdown-menu-item-group">
<div className="ant-dropdown-menu-item-group-title">{group.label}</div> <div className="ant-dropdown-menu-item-group-title">{group.label}</div>
<div> <div>
{group.children.map((item, idx) => ( {group.children.map((item, idx) => {
<div // calculate item global idx
key={item.key} const index = startIndex + idx
className={`ant-dropdown-menu-item ${selectedIndex === startIndex + idx ? 'ant-dropdown-menu-item-selected' : ''}`} return (
onClick={item.onClick}> <div
<span className="ant-dropdown-menu-item-icon">{item.icon}</span> key={item.key}
{item.label} ref={(el) => setItemRef(index, el)}
</div> className={`ant-dropdown-menu-item ${selectedIndex === index ? 'ant-dropdown-menu-item-selected' : ''}`}
))} onClick={item.onClick}>
<span className="ant-dropdown-menu-item-icon">{item.icon}</span>
{item.label}
</div>
)
})}
</div> </div>
</div> </div>
) )