From 922e85754ac4b5a06d2cec1ab38b6ef0b61cba61 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 11 Jun 2025 20:50:35 +0800 Subject: [PATCH] feat(Navigation): add IPC channels for navigation handling and implement cache service for URL management --- packages/shared/IpcChannel.ts | 6 ++++- src/main/ipc.ts | 8 ++++++- src/main/services/CacheService.ts | 16 ++++++++++--- src/main/services/WindowService.ts | 6 +++++ src/preload/index.ts | 5 +++- src/renderer/src/Routes.tsx | 13 +++++++++- src/renderer/src/components/app/Navbar.tsx | 28 +++++++++++----------- 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8a0f266579..22cf012761 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -197,5 +197,9 @@ export enum IpcChannel { Selection_ActionWindowMinimize = 'selection:action-window-minimize', Selection_ActionWindowPin = 'selection:action-window-pin', Selection_ProcessAction = 'selection:process-action', - Selection_UpdateActionData = 'selection:update-action-data' + Selection_UpdateActionData = 'selection:update-action-data', + + // Navigation + Navigation_Url = 'navigation:url', + Navigation_Close = 'navigation:close' } diff --git a/src/main/ipc.ts b/src/main/ipc.ts index a8f02fe543..49301514c7 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -4,6 +4,7 @@ import { arch } from 'node:os' import { isMac, isWin } from '@main/constant' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { handleZoomFactor } from '@main/utils/zoom' +import { FeedUrl } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { Shortcut, ThemeMode } from '@types' import { BrowserWindow, ipcMain, session, shell } from 'electron' @@ -12,6 +13,7 @@ import { Notification } from 'src/renderer/src/types/notification' import AppUpdater from './services/AppUpdater' import BackupManager from './services/BackupManager' +import { CacheService } from './services/CacheService' import { configManager } from './services/ConfigManager' import CopilotService from './services/CopilotService' import { ExportService } from './services/ExportService' @@ -34,7 +36,6 @@ import { calculateDirectorySize, getResourcePath } from './utils' import { decrypt, encrypt } from './utils/aes' import { getCacheDir, getConfigDir, getFilesDir } from './utils/file' import { compress, decompress } from './utils/zip' -import { FeedUrl } from '@shared/config/constant' const fileManager = new FileStorage() const backupManager = new BackupManager() @@ -353,4 +354,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { SelectionService.registerIpcHandler() ipcMain.handle(IpcChannel.App_QuoteToMain, (_, text: string) => windowService.quoteToMainWindow(text)) + + // Navigation + ipcMain.handle(IpcChannel.Navigation_Url, (_, url: string) => { + CacheService.set('navigation-url', url) + }) } diff --git a/src/main/services/CacheService.ts b/src/main/services/CacheService.ts index d2984a9984..413ebc3d3e 100644 --- a/src/main/services/CacheService.ts +++ b/src/main/services/CacheService.ts @@ -1,7 +1,7 @@ interface CacheItem { data: T timestamp: number - duration: number + duration?: number } export class CacheService { @@ -11,9 +11,9 @@ export class CacheService { * Set cache * @param key Cache key * @param data Cache data - * @param duration Cache duration (in milliseconds) + * @param duration Cache duration (in milliseconds), if not set the cache will never expire */ - static set(key: string, data: T, duration: number): void { + static set(key: string, data: T, duration?: number): void { this.cache.set(key, { data, timestamp: Date.now(), @@ -30,6 +30,11 @@ export class CacheService { const item = this.cache.get(key) if (!item) return null + // If duration is undefined, cache never expires + if (item.duration === undefined) { + return item.data + } + const now = Date.now() if (now - item.timestamp > item.duration) { this.remove(key) @@ -63,6 +68,11 @@ export class CacheService { const item = this.cache.get(key) if (!item) return false + // If duration is undefined, cache never expires + if (item.duration === undefined) { + return true + } + const now = Date.now() if (now - item.timestamp > item.duration) { this.remove(key) diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 469ba1bd5e..3283055987 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -12,6 +12,7 @@ import { join } from 'path' import icon from '../../../build/icon.png?asset' import { titleBarOverlayDark, titleBarOverlayLight } from '../config' +import { CacheService } from './CacheService' import { configManager } from './ConfigManager' import { contextMenu } from './ContextMenu' import { initSessionUserAgent } from './WebviewService' @@ -299,6 +300,11 @@ export class WindowService { return app.quit() } + if (CacheService.get('navigation-url') !== '/') { + event.preventDefault() + return mainWindow.webContents.send(IpcChannel.Navigation_Close) + } + // 托盘及关闭行为设置 const isShowTray = configManager.getTray() const isTrayOnClose = configManager.getTrayOnClose() diff --git a/src/preload/index.ts b/src/preload/index.ts index 59496df4b8..274ba25702 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -229,7 +229,10 @@ const api = { minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize), pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned) }, - quoteToMainWindow: (text: string) => ipcRenderer.invoke(IpcChannel.App_QuoteToMain, text) + quoteToMainWindow: (text: string) => ipcRenderer.invoke(IpcChannel.App_QuoteToMain, text), + navigation: { + url: (url: string) => ipcRenderer.invoke(IpcChannel.Navigation_Url, url) + } } // Use `contextBridge` APIs to expose Electron APIs to diff --git a/src/renderer/src/Routes.tsx b/src/renderer/src/Routes.tsx index 50064f9490..62319e0c60 100644 --- a/src/renderer/src/Routes.tsx +++ b/src/renderer/src/Routes.tsx @@ -1,5 +1,7 @@ +import { IpcChannel } from '@shared/IpcChannel' import { AnimatePresence, motion } from 'framer-motion' -import { Route, Routes, useLocation } from 'react-router-dom' +import { useEffect } from 'react' +import { Route, Routes, useLocation, useNavigate } from 'react-router-dom' import styled from 'styled-components' import AgentsPage from './pages/agents/AgentsPage' @@ -15,6 +17,7 @@ import TranslatePage from './pages/translate/TranslatePage' const WILDCARD_ROUTES = ['/settings', '/paintings', '/mcp-servers'] const RouteContainer = () => { + const navigate = useNavigate() const location = useLocation() const isHomePage = location.pathname === '/' @@ -24,6 +27,14 @@ const RouteContainer = () => { // 使用主路由作为 key,这样同一主路由下的切换不会触发动画 const animationKey = mainPath || location.pathname + useEffect(() => { + window.api.navigation.url(location.pathname) + }, [location.pathname]) + + useEffect(() => { + window.electron.ipcRenderer.on(IpcChannel.Navigation_Close, () => navigate('/')) + }, [navigate]) + return ( diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index d78b347194..fa165a7505 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -38,19 +38,6 @@ export const NavbarMain: FC = ({ children, ...props }) => { ) } -const rotateAnimation = keyframes` - from { - transform: rotate(-180deg); - } - to { - transform: rotate(0); - } -` - -const AnimatedButton = styled(Button)` - animation: ${rotateAnimation} 0.4s ease-out; -` - const MacCloseIcon = () => { const navigate = useNavigate() @@ -108,7 +95,7 @@ const NavbarMainContainer = styled.div<{ $isFullscreen: boolean }>` max-height: var(--navbar-height); min-height: var(--navbar-height); justify-content: space-between; - padding-left: ${isMac ? '70px' : '10px'}; + padding-left: ${({ $isFullscreen }) => ($isFullscreen ? '10px' : isMac ? '70px' : '10px')}; font-weight: bold; color: var(--color-text-1); padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '135px' : isLinux ? '120px' : '12px')}; @@ -127,3 +114,16 @@ const NavbarCenterContainer = styled.div` justify-content: space-between; color: var(--color-text-1); ` + +const rotateAnimation = keyframes` + from { + transform: rotate(-180deg); + } + to { + transform: rotate(0); + } +` + +const AnimatedButton = styled(Button)` + animation: ${rotateAnimation} 0.4s ease-out; +`