Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add switch navigation shortcut #2119 #2334

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/renderer/src/components/app/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ import MinAppIcon from '../Icons/MinAppIcon'
import MinApp from '../MinApp'
import UserPopup from '../Popups/UserPopup'

export const locationPathnameMappingPathMap = {
assistants: '/',
agents: '/agents',
paintings: '/paintings',
translate: '/translate',
minapp: '/apps',
knowledge: '/knowledge',
files: '/files'
}

const Sidebar: FC = () => {
const { pathname } = useLocation()
const avatar = useAvatar()
Expand Down Expand Up @@ -133,15 +143,7 @@ const MainMenus: FC = () => {
files: <FolderOutlined />
}

const pathMap = {
assistants: '/',
agents: '/agents',
paintings: '/paintings',
translate: '/translate',
minapp: '/apps',
knowledge: '/knowledge',
files: '/files'
}
const pathMap = locationPathnameMappingPathMap

return sidebarIcons.visible.map((icon) => {
const path = pathMap[icon]
Expand Down
89 changes: 88 additions & 1 deletion src/renderer/src/handler/NavigationHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { locationPathnameMappingPathMap } from '@renderer/components/app/Sidebar'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { settingMenuItemPathList } from '@renderer/pages/settings/SettingsPage'
import Logger from 'electron-log'
import React from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useNavigate } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'

const NavigationHandler: React.FC = () => {
const navigate = useNavigate()
const { sidebarIcons } = useSettings()
const { pathname: navigationPathname } = useLocation()
useHotkeys(
'meta+, ! ctrl+,',
function () {
Expand All @@ -11,6 +19,85 @@ const NavigationHandler: React.FC = () => {
{ splitKey: '!' }
)

const navigationListAndIndex = (): {
err: boolean
errorMessage: string
navigationList: string[]
index: number
defaultSettingSubPathname?: string
} => {
const pinnedPathname = sidebarIcons.visible.map((iconName) => locationPathnameMappingPathMap[iconName])
if (pinnedPathname.length !== sidebarIcons.visible.length) {
return {
err: true,
errorMessage: '[NavigationHandler] pinnedPathname length not match',
navigationList: [],
index: 0
}
} else {
const navigationList = [...pinnedPathname, '/settings']
const currentPathname = (() => {
const path = navigationPathname.split('/')[1]
if (path === '') {
return '/'
} else {
return `/${path}`
}
})()

const index = navigationList.findIndex((iconName) => iconName === currentPathname)
if (index === -1) {
return {
err: true,
errorMessage: '[NavigationHandler] currentPathname not found',
navigationList,
index: 0
}
} else {
return {
err: false,
errorMessage: '',
navigationList,
index
}
}
}
}

const gotoNavigateByIndex = (navigationList: string[], index: number) => {
const listLength = navigationList.length
let i = index % listLength
if (i < 0) {
i = (i + listLength) % listLength
}
const targetPathname = navigationList[i]
if (targetPathname.startsWith('/settings')) {
navigate(settingMenuItemPathList?.[0] ?? '/settings/provider')
} else {
navigate(targetPathname)
}
}

useShortcut('switch_to_prev_main_navigation', () => {
const result = navigationListAndIndex()
if (result.err) {
Logger.error(result.errorMessage)
} else {
const { navigationList, index } = result
gotoNavigateByIndex(navigationList, index - 1)
}
})

useShortcut('switch_to_next_main_navigation', () => {
const result = navigationListAndIndex()
if (result.err) {
Logger.error(result.errorMessage)
} else {
const { navigationList, index } = result
gotoNavigateByIndex(navigationList, index + 1)
}
})

return null
}

Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,13 @@
"toggle_show_topics": "Toggle Topics",
"zoom_in": "Zoom In",
"zoom_out": "Zoom Out",
"zoom_reset": "Reset Zoom"
"zoom_reset": "Reset Zoom",
"switch_to_prev_main_navigation": "Switch to previous main navigation",
"switch_to_next_main_navigation": "Switch to next main navigation",
"switch_to_prev_main_tab": "Switch to previous main tab",
"switch_to_next_main_tab": "Switch to next main tab",
"switch_to_prev_horizontal_tab": "Switch to previous horizontal tab",
"switch_to_next_horizontal_tab": "Switch to next horizontal tab"
},
"theme.auto": "Auto",
"theme.dark": "Dark",
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/i18n/locales/ja-jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,13 @@
"toggle_show_topics": "トピックの表示を切り替え",
"zoom_in": "ズームイン",
"zoom_out": "ズームアウト",
"zoom_reset": "ズームをリセット"
"zoom_reset": "ズームをリセット",
"switch_to_prev_main_navigation": "前のメインナビゲーションに切り替え",
"switch_to_next_main_navigation": "次のメインナビゲーションに切り替え",
"switch_to_prev_main_tab": "前のメインタブに切り替え",
"switch_to_next_main_tab": "次のメインタブに切り替え",
"switch_to_prev_horizontal_tab": "前の水平タブに切り替え",
"switch_to_next_horizontal_tab": "次の水平タブに切り替え"
},
"theme.auto": "自動",
"theme.dark": "ダークテーマ",
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/i18n/locales/ru-ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,13 @@
"toggle_show_topics": "Переключить отображение топиков",
"zoom_in": "Увеличить",
"zoom_out": "Уменьшить",
"zoom_reset": "Сбросить масштаб"
"zoom_reset": "Сбросить масштаб",
"switch_to_prev_main_navigation": "Переключиться к предыдущей главной навигации",
"switch_to_next_main_navigation": "Переключиться к следующей главной навигации",
"switch_to_prev_main_tab": "Переключиться к предыдущей главной вкладке",
"switch_to_next_main_tab": "Переключиться к следующей главной вкладке",
"switch_to_prev_horizontal_tab": "Переключиться к предыдущей горизонтальной вкладке",
"switch_to_next_horizontal_tab": "Переключиться к следующей горизонтальной вкладке"
},
"theme.auto": "Автоматически",
"theme.dark": "Темная",
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,13 @@
"toggle_show_topics": "切换话题显示",
"zoom_in": "放大界面",
"zoom_out": "缩小界面",
"zoom_reset": "重置缩放"
"zoom_reset": "重置缩放",
"switch_to_prev_main_navigation": "导航栏向上切换",
"switch_to_next_main_navigation": "导航栏向下切换",
"switch_to_prev_main_tab": "侧边栏向上切换",
"switch_to_next_main_tab": "侧边栏向下切换",
"switch_to_prev_horizontal_tab": "选项卡向左切换",
"switch_to_next_horizontal_tab": "选项卡向右切换"
},
"theme.auto": "跟随系统",
"theme.dark": "深色主题",
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,13 @@
"toggle_show_topics": "切換話題顯示",
"zoom_in": "放大界面",
"zoom_out": "縮小界面",
"zoom_reset": "重置縮放"
"zoom_reset": "重置縮放",
"switch_to_prev_main_navigation": "導航欄向上切換",
"switch_to_next_main_navigation": "導航欄向下切換",
"switch_to_prev_main_tab": "側邊欄向上切換",
"switch_to_next_main_tab": "側邊欄向下切換",
"switch_to_prev_horizontal_tab": "選項卡向左切換",
"switch_to_next_horizontal_tab": "選項卡向右切換"
},
"theme.auto": "自動",
"theme.dark": "深色主題",
Expand Down
48 changes: 46 additions & 2 deletions src/renderer/src/pages/agents/AgentsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SearchOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import Scrollbar from '@renderer/components/Scrollbar'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'
import { Agent } from '@renderer/types'
import { uuid } from '@renderer/utils'
Expand All @@ -24,6 +25,7 @@ const AgentsPage: FC = () => {
const [search, setSearch] = useState('')
const [searchInput, setSearchInput] = useState('')
const systemAgents = useSystemAgents()
const [tabActiveKey, setTabActiveKey] = useState('1')

const agentGroups = useMemo(() => {
if (Object.keys(_agentGroups).length === 0) {
Expand Down Expand Up @@ -159,6 +161,24 @@ const AgentsPage: FC = () => {
}
}

useShortcut('switch_to_prev_main_tab', () => {
if (tabItems.length > 1) {
const index = tabItems.findIndex((item) => item.key === tabActiveKey)
if (index !== -1) {
setTabActiveKey(tabItems[index === 0 ? tabItems.length - 1 : index - 1].key)
}
}
})

useShortcut('switch_to_next_main_tab', () => {
if (tabItems.length > 1) {
const index = tabItems.findIndex((item) => item.key === tabActiveKey)
if (index !== -1) {
setTabActiveKey(tabItems[index === tabItems.length - 1 ? 0 : index + 1].key)
}
}
})

return (
<Container>
<Navbar>
Expand All @@ -177,6 +197,7 @@ const AgentsPage: FC = () => {
maxLength={50}
onChange={(e) => setSearchInput(e.target.value)}
onPressEnter={handleSearch}
autoFocus
/>
<div style={{ width: 80 }} />
</NavbarCenter>
Expand All @@ -187,7 +208,14 @@ const AgentsPage: FC = () => {
search.trim() ? (
<TabContent>{renderAgentList(Object.values(filteredAgentGroups).flat())}</TabContent>
) : (
<Tabs tabPosition="right" animated={false} items={tabItems} $language={i18n.language} />
<Tabs
tabPosition="right"
animated={false}
items={tabItems}
$language={i18n.language}
activeKey={tabActiveKey}
onTabClick={(key) => setTabActiveKey(key)}
/>
)
) : (
<EmptyView>
Expand Down Expand Up @@ -251,26 +279,34 @@ const EmptyView = styled.div`
color: var(--color-text-secondary);
`

const Tabs = styled(TabsAntd)<{ $language: string }>`
const Tabs = styled(TabsAntd)<{
$language: string
activeKey: string
onTabClick: (key: string, event: MouseEvent) => void
}>`
display: flex;
flex: 1;
flex-direction: row-reverse;

.ant-tabs-tabpane {
padding-right: 0 !important;
}

.ant-tabs-nav {
min-width: ${({ $language }) => ($language.startsWith('zh') ? '120px' : '140px')};
max-width: ${({ $language }) => ($language.startsWith('zh') ? '120px' : '140px')};
position: relative;
overflow: hidden;
}

.ant-tabs-nav-list {
padding: 10px 8px;
}

.ant-tabs-nav-operations {
display: none !important;
}

.ant-tabs-tab {
margin: 0 !important;
border-radius: var(--list-item-border-radius);
Expand All @@ -283,6 +319,7 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
user-select: none;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
outline: none !important;

.ant-tabs-tab-btn {
white-space: nowrap;
overflow: hidden;
Expand All @@ -291,31 +328,38 @@ const Tabs = styled(TabsAntd)<{ $language: string }>`
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
outline: none !important;
}

&:hover {
color: var(--color-text) !important;
background-color: var(--color-background-soft);
}
}

.ant-tabs-tab-active {
background-color: var(--color-background-soft);
border: 0.5px solid var(--color-border);
transform: scale(1.02);
}

.ant-tabs-content-holder {
border-left: 0.5px solid var(--color-border);
border-right: none;
}

.ant-tabs-ink-bar {
display: none;
}

.ant-tabs-tab-btn:active {
color: var(--color-text) !important;
}

.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: var(--color-text) !important;
}
}

.ant-tabs-content {
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/pages/apps/AppsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const AppsPage: FC = () => {
suffix={<SearchOutlined />}
value={search}
onChange={(e) => setSearch(e.target.value)}
autoFocus
/>
<div style={{ width: 80 }} />
</NavbarCenter>
Expand Down
Loading