feat: remember selected sider card

This commit is contained in:
zjdndjf
2026-06-13 21:13:46 +08:00
parent 38e33eea2a
commit 36dbf3dcc6
5 changed files with 105 additions and 54 deletions

View File

@@ -50,6 +50,7 @@ export const defaultConfig: IAppConfig = {
'substore',
'network'
],
lastSelectedSiderCard: 'proxy',
siderWidth: 250,
sysProxy: { enable: false, mode: 'manual' },
triggerMainWindowBehavior: 'show',

View File

@@ -39,38 +39,16 @@ import { createTourDriver, getDriver, startTourIfNeeded } from '@renderer/utils/
import 'driver.js/dist/driver.css'
import { useTranslation } from 'react-i18next'
import MihomoIcon from './components/base/mihomo-icon'
import { SIDER_CARD_ROUTES, getSiderCardByPath, mergeSiderOrder } from './utils/sider'
let navigate: NavigateFunction
export { getDriver }
const ALL_SIDER_KEYS = [
'sysproxy',
'tun',
'profile',
'proxy',
'rule',
'resource',
'override',
'connection',
'mihomo',
'dns',
'sniff',
'log',
'substore',
'network',
'usage'
]
function mergeSiderOrder(saved: string[]): string[] {
const valid = saved.filter((k) => ALL_SIDER_KEYS.includes(k))
const missing = ALL_SIDER_KEYS.filter((k) => !valid.includes(k))
return [...valid, ...missing]
}
const App: React.FC = () => {
const { t } = useTranslation()
const { appConfig, patchAppConfig } = useAppConfig()
const hasAppConfig = Boolean(appConfig)
const {
enableTrafficLogger = true,
appTheme = 'system',
@@ -93,11 +71,12 @@ const App: React.FC = () => {
'substore',
'network',
'usage'
]
],
lastSelectedSiderCard = 'proxy'
} = appConfig || {}
useTrafficLogger(enableTrafficLogger)
const narrowWidth = platform === 'darwin' ? 70 : 60
const [order, setOrder] = useState(mergeSiderOrder(siderOrder))
const [order, setOrder] = useState<SiderCardKey[]>(mergeSiderOrder(siderOrder))
const [siderWidthValue, setSiderWidthValue] = useState(siderWidth)
const siderWidthValueRef = useRef(siderWidthValue)
const [resizing, setResizing] = useState(false)
@@ -127,6 +106,13 @@ const App: React.FC = () => {
setSiderWidthValue(siderWidth)
}, [siderOrder, siderWidth])
useEffect(() => {
if (!hasAppConfig) return
const currentSiderCard = getSiderCardByPath(location.pathname)
if (!currentSiderCard || currentSiderCard === lastSelectedSiderCard) return
patchAppConfig({ lastSelectedSiderCard: currentSiderCard })
}, [hasAppConfig, lastSelectedSiderCard, location.pathname, patchAppConfig])
useEffect(() => {
siderWidthValueRef.current = siderWidthValue
resizingRef.current = resizing
@@ -166,41 +152,26 @@ const App: React.FC = () => {
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
const { active, over } = event
const activeId = active.id as SiderCardKey
if (over) {
if (active.id !== over.id) {
const overId = over.id as SiderCardKey
const newOrder = order.slice()
const activeIndex = newOrder.indexOf(active.id as string)
const overIndex = newOrder.indexOf(over.id as string)
const activeIndex = newOrder.indexOf(activeId)
const overIndex = newOrder.indexOf(overId)
if (activeIndex === -1 || overIndex === -1) return
newOrder.splice(activeIndex, 1)
newOrder.splice(overIndex, 0, active.id as string)
newOrder.splice(overIndex, 0, activeId)
setOrder(newOrder)
await patchAppConfig({ siderOrder: newOrder })
return
}
}
const dest = navigateMap[active.id as string]
const dest = SIDER_CARD_ROUTES[activeId]
if (dest) navigate(dest)
}
const navigateMap = {
sysproxy: 'sysproxy',
tun: 'tun',
profile: 'profiles',
proxy: 'proxies',
mihomo: 'mihomo',
connection: 'connections',
dns: 'dns',
sniff: 'sniffer',
log: 'logs',
rule: 'rules',
resource: 'resources',
override: 'override',
substore: 'substore',
network: 'network',
usage: 'traffic'
}
const componentMap = {
const componentMap: Record<SiderCardKey, React.FC<{ iconOnly?: boolean }>> = {
sysproxy: SysproxySwitcher,
tun: TunSwitcher,
profile: ProfileCard,
@@ -242,9 +213,8 @@ const App: React.FC = () => {
</div>
<div className="h-[calc(100%-110px)] overflow-y-auto no-scrollbar">
<div className="h-full w-full flex flex-col gap-2">
{order.map((key: string) => {
{order.map((key) => {
const Component = componentMap[key]
if (!Component) return null
return <Component key={key} iconOnly={true} />
})}
</div>
@@ -299,9 +269,8 @@ const App: React.FC = () => {
<DndContext sensors={sensors} collisionDetection={closestCorners} onDragEnd={onDragEnd}>
<div className="grid grid-cols-2 gap-2 m-2">
<SortableContext items={order}>
{order.map((key: string) => {
{order.map((key) => {
const Component = componentMap[key]
if (!Component) return null
return <Component key={key} />
})}
</SortableContext>

View File

@@ -15,6 +15,16 @@ import DNS from '@renderer/pages/dns'
import Sniffer from '@renderer/pages/sniffer'
import SubStore from '@renderer/pages/substore'
import Traffic from '@renderer/pages/traffic'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { getSiderCardRoute } from '@renderer/utils/sider'
const HomeRedirect: React.FC = () => {
const { appConfig } = useAppConfig()
if (!appConfig) return null
return <Navigate to={getSiderCardRoute(appConfig.lastSelectedSiderCard)} replace />
}
const routes = [
{
path: '/network',
@@ -82,7 +92,7 @@ const routes = [
},
{
path: '/',
element: <Navigate to="/proxies" />
element: <HomeRedirect />
}
]

View File

@@ -0,0 +1,54 @@
export const SIDER_CARD_KEYS: SiderCardKey[] = [
'sysproxy',
'tun',
'profile',
'proxy',
'rule',
'resource',
'override',
'connection',
'mihomo',
'dns',
'sniff',
'log',
'substore',
'network',
'usage'
]
export const SIDER_CARD_ROUTES: Record<SiderCardKey, string> = {
sysproxy: '/sysproxy',
tun: '/tun',
profile: '/profiles',
proxy: '/proxies',
rule: '/rules',
resource: '/resources',
override: '/override',
connection: '/connections',
mihomo: '/mihomo',
dns: '/dns',
sniff: '/sniffer',
log: '/logs',
substore: '/substore',
network: '/network',
usage: '/traffic'
}
export function mergeSiderOrder(saved: string[] = []): SiderCardKey[] {
const valid = saved.filter((key): key is SiderCardKey =>
SIDER_CARD_KEYS.includes(key as SiderCardKey)
)
const missing = SIDER_CARD_KEYS.filter((key) => !valid.includes(key))
return [...valid, ...missing]
}
export function getSiderCardRoute(card?: string): string {
if (!card || !SIDER_CARD_KEYS.includes(card as SiderCardKey)) {
return SIDER_CARD_ROUTES.proxy
}
return SIDER_CARD_ROUTES[card as SiderCardKey]
}
export function getSiderCardByPath(pathname: string): SiderCardKey | undefined {
return SIDER_CARD_KEYS.find((key) => SIDER_CARD_ROUTES[key] === pathname)
}

17
src/shared/types.d.ts vendored
View File

@@ -2,6 +2,22 @@ type OutboundMode = 'rule' | 'global' | 'direct'
type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
type SysProxyMode = 'auto' | 'manual'
type CardStatus = 'col-span-2' | 'col-span-1' | 'hidden'
type SiderCardKey =
| 'sysproxy'
| 'tun'
| 'profile'
| 'proxy'
| 'rule'
| 'resource'
| 'override'
| 'connection'
| 'mihomo'
| 'dns'
| 'sniff'
| 'log'
| 'substore'
| 'network'
| 'usage'
type AppTheme = 'system' | 'light' | 'dark'
type MihomoGroupType = 'Selector' | 'URLTest' | 'Fallback' | 'LoadBalance' | 'Relay'
type Priority =
@@ -310,6 +326,7 @@ interface IAppConfig {
showCurrentProxyInTray: boolean
enableTrafficLogger?: boolean
siderOrder: string[]
lastSelectedSiderCard?: SiderCardKey
siderWidth: number
appTheme: AppTheme
customTheme?: string