feat: add chat navigation bar close (#4019)
* feat(聊天导航): 新增关闭、置顶和置底按钮并更新图标 在聊天导航组件中新增了关闭、置顶和置底按钮,并更新了相关图标以提升用户体验。同时,添加了点击关闭按钮时隐藏导航的功能。 * feat(消息导航): 添加手动关闭状态以避免误触 在 ChatNavigation 组件中添加 `manuallyClosedUntil` 状态,用于在用户手动关闭导航后,1分钟内不响应鼠标靠近事件。这可以防止用户在操作时误触导航栏,提升用户体验。 * refactor(ChatNavigation): 重命名函数并添加滚动处理逻辑 重命名 handleChatNavigationClick 为 handleCloseChatNavigation 以提高代码可读性,并添加 handleScrollToTop 和 handleScrollToBottom 函数以处理滚动逻辑 * fix: 修复滚动到顶部时位置不正确的问题 将 `scrollToTop` 函数中的 `top` 值从 `0` 改为 `-container.scrollHeight`,以确保滚动到顶部时位置正确 * docs(i18n): 添加新的翻译字符串以支持更多操作 在多个语言文件中添加了“回到顶部”、“回到底部”和“关闭”的翻译字符串,以支持更多用户界面操作。 * refactor: 移除未使用的变量以简化代码 ``` 解释: - **类型**: `refactor`,因为这是代码重构,移除了未使用的变量,没有改变功能行为。 - **描述**: 移除了未使用的变量以简化代码,符合简洁和可维护性的原则。
This commit is contained in:
parent
a90be7e83f
commit
8b9929cc7b
@ -150,7 +150,10 @@
|
|||||||
"history": "Chat History",
|
"history": "Chat History",
|
||||||
"last": "Already at the last message",
|
"last": "Already at the last message",
|
||||||
"next": "Next Message",
|
"next": "Next Message",
|
||||||
"prev": "Previous Message"
|
"prev": "Previous Message",
|
||||||
|
"top": "Back to top",
|
||||||
|
"bottom": "Back to bottom",
|
||||||
|
"close": "Close"
|
||||||
},
|
},
|
||||||
"resend": "Resend",
|
"resend": "Resend",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
|||||||
@ -150,7 +150,10 @@
|
|||||||
"history": "チャット履歴",
|
"history": "チャット履歴",
|
||||||
"last": "最後のメッセージです",
|
"last": "最後のメッセージです",
|
||||||
"next": "次のメッセージ",
|
"next": "次のメッセージ",
|
||||||
"prev": "前のメッセージ"
|
"prev": "前のメッセージ",
|
||||||
|
"top": "トップに戻る",
|
||||||
|
"bottom": "下部に戻る",
|
||||||
|
"close": "閉じる"
|
||||||
},
|
},
|
||||||
"resend": "再送信",
|
"resend": "再送信",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
|||||||
@ -150,7 +150,10 @@
|
|||||||
"history": "История чата",
|
"history": "История чата",
|
||||||
"last": "Уже последнее сообщение",
|
"last": "Уже последнее сообщение",
|
||||||
"next": "Следующее сообщение",
|
"next": "Следующее сообщение",
|
||||||
"prev": "Предыдущее сообщение"
|
"prev": "Предыдущее сообщение",
|
||||||
|
"top": "Вернуться наверх",
|
||||||
|
"bottom": "Вернуться вниз",
|
||||||
|
"close": "Закрыть"
|
||||||
},
|
},
|
||||||
"resend": "Переотправить",
|
"resend": "Переотправить",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
|
|||||||
@ -150,7 +150,10 @@
|
|||||||
"history": "聊天历史",
|
"history": "聊天历史",
|
||||||
"last": "已经是最后一条消息",
|
"last": "已经是最后一条消息",
|
||||||
"next": "下一条消息",
|
"next": "下一条消息",
|
||||||
"prev": "上一条消息"
|
"prev": "上一条消息",
|
||||||
|
"top": "回到顶部",
|
||||||
|
"bottom": "回到底部",
|
||||||
|
"close": "关闭"
|
||||||
},
|
},
|
||||||
"resend": "重新发送",
|
"resend": "重新发送",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
|||||||
@ -150,7 +150,10 @@
|
|||||||
"history": "聊天歷史",
|
"history": "聊天歷史",
|
||||||
"last": "已經是最後一條訊息",
|
"last": "已經是最後一條訊息",
|
||||||
"next": "下一條訊息",
|
"next": "下一條訊息",
|
||||||
"prev": "上一條訊息"
|
"prev": "上一條訊息",
|
||||||
|
"top": "回到頂部",
|
||||||
|
"bottom": "回到底部",
|
||||||
|
"close": "關閉"
|
||||||
},
|
},
|
||||||
"resend": "重新傳送",
|
"resend": "重新傳送",
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
|
|||||||
@ -130,7 +130,10 @@
|
|||||||
"first": "Ήδη το πρώτο μήνυμα",
|
"first": "Ήδη το πρώτο μήνυμα",
|
||||||
"last": "Ήδη το τελευταίο μήνυμα",
|
"last": "Ήδη το τελευταίο μήνυμα",
|
||||||
"next": "Επόμενο μήνυμα",
|
"next": "Επόμενο μήνυμα",
|
||||||
"prev": "Προηγούμενο μήνυμα"
|
"prev": "Προηγούμενο μήνυμα",
|
||||||
|
"top": "Επιστροφή στην κορυφή",
|
||||||
|
"bottom": "Επιστροφή στο κάτω μέρος",
|
||||||
|
"close": "Κλείσιμο"
|
||||||
},
|
},
|
||||||
"resend": "Ξαναστείλε",
|
"resend": "Ξαναστείλε",
|
||||||
"save": "Αποθήκευση",
|
"save": "Αποθήκευση",
|
||||||
|
|||||||
@ -130,7 +130,10 @@
|
|||||||
"first": "Ya es el primer mensaje",
|
"first": "Ya es el primer mensaje",
|
||||||
"last": "Ya es el último mensaje",
|
"last": "Ya es el último mensaje",
|
||||||
"next": "Siguiente mensaje",
|
"next": "Siguiente mensaje",
|
||||||
"prev": "Mensaje anterior"
|
"prev": "Mensaje anterior",
|
||||||
|
"top": "Volver arriba",
|
||||||
|
"bottom": "Volver abajo",
|
||||||
|
"close": "Cerrar"
|
||||||
},
|
},
|
||||||
"resend": "Reenviar",
|
"resend": "Reenviar",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
|
|||||||
@ -130,7 +130,10 @@
|
|||||||
"first": "Déjà premier message",
|
"first": "Déjà premier message",
|
||||||
"last": "Déjà dernier message",
|
"last": "Déjà dernier message",
|
||||||
"next": "Prochain message",
|
"next": "Prochain message",
|
||||||
"prev": "Précédent message"
|
"prev": "Précédent message",
|
||||||
|
"top": "Retour en haut",
|
||||||
|
"bottom": "Retour en bas",
|
||||||
|
"close": "Fermer"
|
||||||
},
|
},
|
||||||
"resend": "Réenvoyer",
|
"resend": "Réenvoyer",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
|
|||||||
@ -130,7 +130,10 @@
|
|||||||
"first": "Esta é a primeira mensagem",
|
"first": "Esta é a primeira mensagem",
|
||||||
"last": "Esta é a última mensagem",
|
"last": "Esta é a última mensagem",
|
||||||
"next": "Próxima mensagem",
|
"next": "Próxima mensagem",
|
||||||
"prev": "Mensagem anterior"
|
"prev": "Mensagem anterior",
|
||||||
|
"top": "Voltar ao topo",
|
||||||
|
"bottom": "Voltar ao fundo",
|
||||||
|
"close": "Fechar"
|
||||||
},
|
},
|
||||||
"resend": "Reenviar",
|
"resend": "Reenviar",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { DownOutlined, HistoryOutlined, UpOutlined } from '@ant-design/icons'
|
import {
|
||||||
|
ArrowDownOutlined,
|
||||||
|
ArrowUpOutlined,
|
||||||
|
CloseOutlined,
|
||||||
|
HistoryOutlined,
|
||||||
|
VerticalAlignBottomOutlined,
|
||||||
|
VerticalAlignTopOutlined
|
||||||
|
} from '@ant-design/icons'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { RootState } from '@renderer/store'
|
import { RootState } from '@renderer/store'
|
||||||
import { selectCurrentTopicId } from '@renderer/store/messages'
|
import { selectCurrentTopicId } from '@renderer/store/messages'
|
||||||
@ -20,6 +27,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
const [isNearButtons, setIsNearButtons] = useState(false)
|
const [isNearButtons, setIsNearButtons] = useState(false)
|
||||||
const [hideTimer, setHideTimer] = useState<NodeJS.Timeout | null>(null)
|
const [hideTimer, setHideTimer] = useState<NodeJS.Timeout | null>(null)
|
||||||
const [showChatHistory, setShowChatHistory] = useState(false)
|
const [showChatHistory, setShowChatHistory] = useState(false)
|
||||||
|
const [manuallyClosedUntil, setManuallyClosedUntil] = useState<number | null>(null)
|
||||||
const currentTopicId = useSelector((state: RootState) => selectCurrentTopicId(state))
|
const currentTopicId = useSelector((state: RootState) => selectCurrentTopicId(state))
|
||||||
const lastMoveTime = useRef(0)
|
const lastMoveTime = useRef(0)
|
||||||
const { topicPosition, showTopics } = useSettings()
|
const { topicPosition, showTopics } = useSettings()
|
||||||
@ -44,6 +52,10 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
|
|
||||||
// Handle mouse entering button area
|
// Handle mouse entering button area
|
||||||
const handleMouseEnter = useCallback(() => {
|
const handleMouseEnter = useCallback(() => {
|
||||||
|
if (manuallyClosedUntil && Date.now() < manuallyClosedUntil) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setIsNearButtons(true)
|
setIsNearButtons(true)
|
||||||
setIsVisible(true)
|
setIsVisible(true)
|
||||||
|
|
||||||
@ -52,7 +64,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
clearTimeout(hideTimer)
|
clearTimeout(hideTimer)
|
||||||
setHideTimer(null)
|
setHideTimer(null)
|
||||||
}
|
}
|
||||||
}, [hideTimer])
|
}, [hideTimer, manuallyClosedUntil])
|
||||||
|
|
||||||
// Handle mouse leaving button area
|
// Handle mouse leaving button area
|
||||||
const handleMouseLeave = useCallback(() => {
|
const handleMouseLeave = useCallback(() => {
|
||||||
@ -97,7 +109,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
|
|
||||||
const scrollToTop = () => {
|
const scrollToTop = () => {
|
||||||
const container = document.getElementById(containerId)
|
const container = document.getElementById(containerId)
|
||||||
container && container.scrollTo({ top: 0, behavior: 'smooth' })
|
container && container.scrollTo({ top: -container.scrollHeight, behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
@ -148,6 +160,23 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改 handleCloseChatNavigation 函数
|
||||||
|
const handleCloseChatNavigation = () => {
|
||||||
|
setIsVisible(false)
|
||||||
|
// 设置手动关闭状态,1分钟内不响应鼠标靠近事件
|
||||||
|
setManuallyClosedUntil(Date.now() + 60000) // 60000毫秒 = 1分钟
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScrollToTop = () => {
|
||||||
|
resetHideTimer()
|
||||||
|
scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScrollToBottom = () => {
|
||||||
|
resetHideTimer()
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
const handleNextMessage = () => {
|
const handleNextMessage = () => {
|
||||||
resetHideTimer()
|
resetHideTimer()
|
||||||
const userMessages = findUserMessages()
|
const userMessages = findUserMessages()
|
||||||
@ -216,6 +245,11 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
|
|
||||||
// Throttled mouse move handler to improve performance
|
// Throttled mouse move handler to improve performance
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
// 如果在手动关闭期间,不响应鼠标移动事件
|
||||||
|
if (manuallyClosedUntil && Date.now() < manuallyClosedUntil) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Throttle mouse move to every 50ms for performance
|
// Throttle mouse move to every 50ms for performance
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now - lastMoveTime.current < 50) return
|
if (now - lastMoveTime.current < 50) return
|
||||||
@ -262,16 +296,43 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
clearTimeout(hideTimer)
|
clearTimeout(hideTimer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [containerId, hideTimer, resetHideTimer, isNearButtons, handleMouseEnter, handleMouseLeave, showRightTopics])
|
}, [
|
||||||
|
containerId,
|
||||||
|
hideTimer,
|
||||||
|
resetHideTimer,
|
||||||
|
isNearButtons,
|
||||||
|
handleMouseEnter,
|
||||||
|
handleMouseLeave,
|
||||||
|
showRightTopics,
|
||||||
|
manuallyClosedUntil
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NavigationContainer $isVisible={isVisible} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
<NavigationContainer $isVisible={isVisible} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
|
<Tooltip title={t('chat.navigation.close')} placement="left">
|
||||||
|
<NavigationButton
|
||||||
|
type="text"
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
onClick={handleCloseChatNavigation}
|
||||||
|
aria-label={t('chat.navigation.close')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Divider />
|
||||||
|
<Tooltip title={t('chat.navigation.top')} placement="left">
|
||||||
|
<NavigationButton
|
||||||
|
type="text"
|
||||||
|
icon={<VerticalAlignTopOutlined />}
|
||||||
|
onClick={handleScrollToTop}
|
||||||
|
aria-label={t('chat.navigation.top')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Divider />
|
||||||
<Tooltip title={t('chat.navigation.prev')} placement="left">
|
<Tooltip title={t('chat.navigation.prev')} placement="left">
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
type="text"
|
type="text"
|
||||||
icon={<UpOutlined />}
|
icon={<ArrowUpOutlined />}
|
||||||
onClick={handlePrevMessage}
|
onClick={handlePrevMessage}
|
||||||
aria-label={t('chat.navigation.prev')}
|
aria-label={t('chat.navigation.prev')}
|
||||||
/>
|
/>
|
||||||
@ -280,12 +341,21 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
|
|||||||
<Tooltip title={t('chat.navigation.next')} placement="left">
|
<Tooltip title={t('chat.navigation.next')} placement="left">
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
type="text"
|
type="text"
|
||||||
icon={<DownOutlined />}
|
icon={<ArrowDownOutlined />}
|
||||||
onClick={handleNextMessage}
|
onClick={handleNextMessage}
|
||||||
aria-label={t('chat.navigation.next')}
|
aria-label={t('chat.navigation.next')}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Tooltip title={t('chat.navigation.bottom')} placement="left">
|
||||||
|
<NavigationButton
|
||||||
|
type="text"
|
||||||
|
icon={<VerticalAlignBottomOutlined />}
|
||||||
|
onClick={handleScrollToBottom}
|
||||||
|
aria-label={t('chat.navigation.bottom')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Divider />
|
||||||
<Tooltip title={t('chat.navigation.history')} placement="left">
|
<Tooltip title={t('chat.navigation.history')} placement="left">
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user